diff --git a/.github/wip.yml b/.github/wip.yml new file mode 100644 index 0000000000..77872ee99b --- /dev/null +++ b/.github/wip.yml @@ -0,0 +1,8 @@ +locations: + - title + - label_name + - commit_subject +terms: + - wip + - ⛔ + - 🚧 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 08d0495e31..f1ba8a3251 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -5,7 +5,7 @@ on: [workflow_dispatch, pull_request] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: benchmark: - runs-on: [self-hosted, benchmark] + runs-on: [self-hosted, benchmark, flutter_2.5.3] timeout-minutes: 30 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/bridge_compile_test.yml b/.github/workflows/bridge_compile_test.yml new file mode 100644 index 0000000000..0126488802 --- /dev/null +++ b/.github/workflows/bridge_compile_test.yml @@ -0,0 +1,45 @@ +name: Bridge Compile Test + +on: [push] + +jobs: + build_for_android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r21e + - name: npm install + run: npm install + - name: compile for android + run: npm run build:bridge:android:release + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + build_for_ios: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r21e + - name: npm install + run: npm install + - name: compile for ios + run: npm run build:bridge:ios:release + + build_for_macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r21e + - name: npm install + run: npm install + - name: compile for macos + run: npm run build:bridge:macos:release diff --git a/.github/workflows/code_linter.yml b/.github/workflows/code_linter.yml index be55a3f2e3..03dda295b9 100644 --- a/.github/workflows/code_linter.yml +++ b/.github/workflows/code_linter.yml @@ -3,6 +3,15 @@ name: Run Code Linter on: [push] jobs: + check_merge_conflict: + runs-on: ubuntu-latest + name: Find merge conflicts + steps: + # Checkout the source code so we have some files to look at. + - uses: actions/checkout@v2 + # Run the actual merge conflict finder + - name: Merge Conflict finder + uses: olivernybroe/action-conflict-finder@v3.0 reformat-bridge: runs-on: ubuntu-latest diff --git a/.github/workflows/integration_test_flutter_2.2.0.yml b/.github/workflows/integration_test_flutter.yml similarity index 81% rename from .github/workflows/integration_test_flutter_2.2.0.yml rename to .github/workflows/integration_test_flutter.yml index e962d883db..cdc7fc1e33 100644 --- a/.github/workflows/integration_test_flutter_2.2.0.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -1,11 +1,11 @@ -name: Integration Test with Flutter 2.2.0 +name: Integration Test on: [workflow_dispatch, pull_request] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - integration_test: - runs-on: [self-hosted, flutter_2.2.0] + integration_test_f_253: + runs-on: [self-hosted, flutter_2.5.3] steps: - uses: actions/checkout@v2 - name: Run Test @@ -18,3 +18,4 @@ jobs: - name: Check on failures if: steps.test.outcome != 'success' run: exit 1 + diff --git a/.github/workflows/plugin_test_flutter_2.2.0.yml b/.github/workflows/plugin_test_flutter.yml similarity index 83% rename from .github/workflows/plugin_test_flutter_2.2.0.yml rename to .github/workflows/plugin_test_flutter.yml index ba818499ab..eafbf2903d 100644 --- a/.github/workflows/plugin_test_flutter_2.2.0.yml +++ b/.github/workflows/plugin_test_flutter.yml @@ -1,11 +1,11 @@ -name: Plugin Test with Flutter 2.2.0 +name: Plugin Test on: [workflow_dispatch, pull_request] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - integration_test: - runs-on: [self-hosted, flutter_2.2.0] + plugin_test_f_253: + runs-on: [self-hosted, flutter_2.5.3] steps: - uses: actions/checkout@v2 - name: Run Plugin Test diff --git a/.github/workflows/sync_to_release.yml b/.github/workflows/sync_to_release.yml new file mode 100644 index 0000000000..c4272196ac --- /dev/null +++ b/.github/workflows/sync_to_release.yml @@ -0,0 +1,22 @@ +name: Sync main code to release branch + +on: + push: + branches: + - main + +# Sync main code to release/flutter-2.2.x branch. +jobs: + flutter_2_2_x: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v2 + with: + ref: 'release/flutter-2.2.x' + - name: Opening pull request + uses: tretuna/sync-branches@1.2.0 + with: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + FROM_BRANCH: 'main' + TO_BRANCH: 'release/flutter-2.2.x' diff --git a/.gitignore b/.gitignore index eefac22e5f..7a8a8cb2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ Podfile.lock temp coverage +pubspec.lock diff --git a/README.md b/README.md index 61cd9c75a9..e42cbc340e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ Only flutter stable released version are fully tested. | Kraken | Flutter | | ------------- | ------------- | | >= 0.7.0 < 0.8.0 | 1.22.0 ~ 1.22.6 | -| >= 0.8.0 < 0.10.0 | 2.2.0 ~ 2.2.3 | +| >= 0.8.0 < 0.10.0 | 2.2.0 ~ 2.2.3 | +| >= 0.10.0 < 0.12.0 | 2.5.0 ~ 2.5.3 | ## 👏 Contributing @@ -102,3 +103,7 @@ By contributing to Kraken, you agree that your contributions will be licensed un ```shell $ npm test ``` + + + + diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 683ce86f99..edbc676993 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -99,6 +99,7 @@ list(APPEND BRIDGE_SOURCE foundation/task_queue.cc foundation/task_queue.h foundation/ui_command_buffer.cc + foundation/ui_command_buffer.h foundation/ui_command_callback_queue.cc foundation/closure.h dart_methods.cc @@ -173,16 +174,22 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs-atom.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs-opcode.h ) - add_library(quickjs SHARED ${QUICK_JS_SOURCE}) + if(${STATIC_QUICKJS}) + add_library(quickjs STATIC ${QUICK_JS_SOURCE}) + else() + add_library(quickjs SHARED ${QUICK_JS_SOURCE}) + endif() list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/third_party) list(APPEND BRIDGE_LINK_LIBS quickjs) list(APPEND BRIDGE_SOURCE - bridge_qjs.cc - bridge_qjs.h - bindings/qjs/js_context.cc - bindings/qjs/js_context.h + page.cc + page.h + bindings/qjs/garbage_collected.h + bindings/qjs/executing_context.cc + bindings/qjs/executing_context.h + bindings/qjs/heap_hashmap.h bindings/qjs/native_value.cc bindings/qjs/native_value.h bindings/qjs/host_object.h @@ -200,6 +207,14 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/bom/screen.h bindings/qjs/bom/timer.cc bindings/qjs/bom/timer.h + bindings/qjs/bom/dom_timer_coordinator.cc + bindings/qjs/bom/dom_timer_coordinator.h + bindings/qjs/dom/frame_request_callback_collection.cc + bindings/qjs/dom/frame_request_callback_collection.h + bindings/qjs/dom/event_listener_map.cc + bindings/qjs/dom/event_listener_map.h + bindings/qjs/dom/script_animation_controller.cc + bindings/qjs/dom/script_animation_controller.h bindings/qjs/dom/event_target.cc bindings/qjs/dom/event_target.h bindings/qjs/dom/event.cc @@ -359,11 +374,14 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "iOS") PUBLIC_HEADER ${KRAKEN_PUBLIC_HEADERS} ) - set_target_properties(quickjs PROPERTIES - OUTPUT_NAME quickjs - FRAMEWORK TRUE - FRAMEWORK_VERSION C - MACOSX_FRAMEWORK_IDENTIFIER com.openkraken.quickjs - PUBLIC_HEADER ${QUICKJS_PUBLIC_HEADERS} - ) + # If quickjs is static, there will be no quickjs.framework any more. + if(NOT DEFINED STATIC_QUICKJS) + set_target_properties(quickjs PROPERTIES + OUTPUT_NAME quickjs + FRAMEWORK TRUE + FRAMEWORK_VERSION C + MACOSX_FRAMEWORK_IDENTIFIER com.openkraken.quickjs + PUBLIC_HEADER ${QUICKJS_PUBLIC_HEADERS} + ) + endif() endif () diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc index 3a3fc19bb8..c797e945a5 100644 --- a/bridge/bindings/qjs/bom/blob.cc +++ b/bridge/bindings/qjs/bom/blob.cc @@ -10,23 +10,23 @@ namespace kraken::binding::qjs { std::once_flag kBlobInitOnceFlag; -void bindBlob(std::unique_ptr& context) { +void bindBlob(std::unique_ptr& context) { auto* constructor = Blob::instance(context.get()); - context->defineGlobalProperty("Blob", constructor->classObject); + context->defineGlobalProperty("Blob", constructor->jsObject); } -Blob::Blob(JSContext* context) : HostClass(context, "Blob") { +Blob::Blob(ExecutionContext* context) : HostClass(context, "Blob") { std::call_once(kBlobInitOnceFlag, []() { JS_NewClassID(&kBlobClassID); }); } JSClassID Blob::kBlobClassID{0}; -JSValue Blob::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue Blob::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { BlobBuilder builder; - auto constructor = static_cast(JS_GetOpaque(func_obj, JSContext::kHostClassClassId)); + auto constructor = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); if (argc == 0) { auto blob = new BlobInstance(constructor); - return blob->instanceObject; + return blob->jsObject; } JSValue arrayValue = argv[0]; @@ -43,7 +43,7 @@ JSValue Blob::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue thi if (argc == 1 || JS_IsUndefined(optionValue)) { builder.append(*constructor->m_context, arrayValue); auto blob = new BlobInstance(constructor, builder.finalize()); - return blob->instanceObject; + return blob->jsObject; } if (!JS_IsObject(optionValue)) { @@ -65,32 +65,26 @@ JSValue Blob::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue thi JS_FreeCString(ctx, mimeType.c_str()); JS_FreeAtom(ctx, mimeTypeKey); - return blob->instanceObject; + return blob->jsObject; } -PROP_GETTER(BlobInstance, type)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Blob, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* blobInstance = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); return JS_NewString(blobInstance->m_ctx, blobInstance->mimeType.empty() ? "" : blobInstance->mimeType.c_str()); } -PROP_SETTER(BlobInstance, type)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BlobInstance, size)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Blob, size)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* blobInstance = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); return JS_NewFloat64(blobInstance->m_ctx, blobInstance->_size); } -PROP_SETTER(BlobInstance, size)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -JSValue Blob::arrayBuffer(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Blob::arrayBuffer(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue resolving_funcs[2]; JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); auto blob = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - JS_DupValue(ctx, blob->instanceObject); + JS_DupValue(ctx, blob->jsObject); auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; auto callback = [](void* callbackContext, int32_t contextId, const char* errmsg) { @@ -98,7 +92,7 @@ JSValue Blob::arrayBuffer(QjsContext* ctx, JSValue this_val, int argc, JSValue* return; auto* promiseContext = static_cast(callbackContext); auto* blob = static_cast(promiseContext->data); - QjsContext* ctx = blob->m_ctx; + JSContext* ctx = blob->m_ctx; JSValue arrayBuffer = JS_NewArrayBuffer( ctx, blob->bytes(), blob->size(), [](JSRuntime* rt, void* opaque, void* ptr) {}, nullptr, false); @@ -116,7 +110,7 @@ JSValue Blob::arrayBuffer(QjsContext* ctx, JSValue this_val, int argc, JSValue* JS_FreeValue(ctx, promiseContext->resolveFunc); JS_FreeValue(ctx, promiseContext->rejectFunc); JS_FreeValue(ctx, arrayBuffer); - JS_FreeValue(ctx, blob->instanceObject); + JS_FreeValue(ctx, blob->jsObject); list_del(&promiseContext->link); delete promiseContext; }; @@ -128,7 +122,7 @@ JSValue Blob::arrayBuffer(QjsContext* ctx, JSValue this_val, int argc, JSValue* return promise; } -JSValue Blob::slice(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Blob::slice(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue startValue = argv[0]; JSValue endValue = argv[1]; JSValue contentTypeValue = argv[2]; @@ -154,24 +148,22 @@ JSValue Blob::slice(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) if (start == 0 && end == blob->_data.size()) { auto newBlob = new BlobInstance(reinterpret_cast(blob->m_hostClass), std::move(blob->_data), mimeType); - JS_SetPrototype(blob->m_ctx, newBlob->instanceObject, blob->m_hostClass->prototype()); - return newBlob->instanceObject; + return newBlob->jsObject; } std::vector newData; newData.reserve(blob->_data.size() - (end - start)); newData.insert(newData.begin(), blob->_data.begin() + start, blob->_data.end() - (blob->_data.size() - end)); auto newBlob = new BlobInstance(reinterpret_cast(blob->m_hostClass), std::move(newData), mimeType); - JS_SetPrototype(blob->m_ctx, newBlob->instanceObject, blob->m_hostClass->prototype()); - return newBlob->instanceObject; + return newBlob->jsObject; } -JSValue Blob::text(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Blob::text(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue resolving_funcs[2]; JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); auto blob = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - JS_DupValue(ctx, blob->instanceObject); + JS_DupValue(ctx, blob->jsObject); auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; auto callback = [](void* callbackContext, int32_t contextId, const char* errmsg) { @@ -180,7 +172,7 @@ JSValue Blob::text(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* promiseContext = static_cast(callbackContext); auto* blob = static_cast(promiseContext->data); - QjsContext* ctx = blob->m_ctx; + JSContext* ctx = blob->m_ctx; JSValue text = JS_NewStringLen(ctx, reinterpret_cast(blob->bytes()), blob->size()); JSValue arguments[] = {text}; @@ -197,7 +189,7 @@ JSValue Blob::text(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { JS_FreeValue(ctx, promiseContext->resolveFunc); JS_FreeValue(ctx, promiseContext->rejectFunc); JS_FreeValue(ctx, text); - JS_FreeValue(ctx, blob->instanceObject); + JS_FreeValue(ctx, blob->jsObject); list_del(&promiseContext->link); delete promiseContext; }; @@ -213,13 +205,13 @@ void BlobInstance::finalize(JSRuntime* rt, JSValue val) { delete eventTarget; } -void BlobBuilder::append(JSContext& context, BlobInstance* blob) { +void BlobBuilder::append(ExecutionContext& context, BlobInstance* blob) { std::vector blobData = blob->_data; _data.reserve(_data.size() + blobData.size()); _data.insert(_data.end(), blobData.begin(), blobData.end()); } -void BlobBuilder::append(JSContext& context, JSValue& value) { +void BlobBuilder::append(ExecutionContext& context, JSValue& value) { if (JS_IsString(value)) { const char* buffer = JS_ToCString(context.ctx(), value); std::string str = std::string(buffer); @@ -242,7 +234,7 @@ void BlobBuilder::append(JSContext& context, JSValue& value) { JS_FreeValue(context.ctx(), v); } } else if (JS_IsObject(value)) { - if (JS_IsInstanceOf(context.ctx(), value, Blob::instance(&context)->classObject)) { + if (JS_IsInstanceOf(context.ctx(), value, Blob::instance(&context)->jsObject)) { auto blob = static_cast(JS_GetOpaque(value, Blob::kBlobClassID)); if (blob == nullptr) return; diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h index a0442b74ec..54734508cb 100644 --- a/bridge/bindings/qjs/bom/blob.h +++ b/bridge/bindings/qjs/bom/blob.h @@ -13,7 +13,7 @@ namespace kraken::binding::qjs { class BlobBuilder; class BlobInstance; -void bindBlob(std::unique_ptr& context); +void bindBlob(std::unique_ptr& context); class Blob : public HostClass { public: @@ -21,20 +21,22 @@ class Blob : public HostClass { OBJECT_INSTANCE(Blob); Blob() = delete; - explicit Blob(JSContext* context); + explicit Blob(ExecutionContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - static JSValue arrayBuffer(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue slice(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue text(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue arrayBuffer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue slice(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue text(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); private: friend BlobInstance; + DEFINE_PROTOTYPE_READONLY_PROPERTY(type); + DEFINE_PROTOTYPE_READONLY_PROPERTY(size); - ObjectFunction m_arrayBuffer{m_context, m_prototypeObject, "arrayBuffer", arrayBuffer, 0}; - ObjectFunction m_slice{m_context, m_prototypeObject, "slice", slice, 3}; - ObjectFunction m_text{m_context, m_prototypeObject, "text", text, 0}; + DEFINE_PROTOTYPE_FUNCTION(arrayBuffer, 0); + DEFINE_PROTOTYPE_FUNCTION(slice, 3); + DEFINE_PROTOTYPE_FUNCTION(text, 0); }; class BlobInstance : public Instance { @@ -51,7 +53,6 @@ class BlobInstance : public Instance { int32_t size(); private: - DEFINE_HOST_CLASS_PROPERTY(2, type, size); size_t _size; std::string mimeType{""}; std::vector _data; @@ -63,8 +64,8 @@ class BlobInstance : public Instance { class BlobBuilder { public: - void append(JSContext& context, JSValue& value); - void append(JSContext& context, BlobInstance* blob); + void append(ExecutionContext& context, JSValue& value); + void append(ExecutionContext& context, BlobInstance* blob); std::vector finalize(); diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/bindings/qjs/bom/console.cc index dabcca1ca5..f5304f3441 100644 --- a/bridge/bindings/qjs/bom/console.cc +++ b/bridge/bindings/qjs/bom/console.cc @@ -7,7 +7,7 @@ namespace kraken::binding::qjs { -JSValue print(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +JSValue print(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { std::stringstream stream; JSValue log = argv[0]; if (JS_IsString(log)) { @@ -18,7 +18,7 @@ JSValue print(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* ar return JS_ThrowTypeError(ctx, "Failed to execute 'print': log must be string."); } - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); const char* logLevel = "info"; JSValue level = argv[1]; if (JS_IsString(level)) { @@ -30,7 +30,7 @@ JSValue print(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* ar return JS_UNDEFINED; } -void bindConsole(std::unique_ptr& context) { +void bindConsole(std::unique_ptr& context) { QJS_GLOBAL_BINDING_FUNCTION(context, print, "__kraken_print__", 2); } diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/bindings/qjs/bom/console.h index ad750335a8..75f2f55c43 100644 --- a/bridge/bindings/qjs/bom/console.h +++ b/bridge/bindings/qjs/bom/console.h @@ -6,11 +6,11 @@ #ifndef KRAKENBRIDGE_CONSOLE_H #define KRAKENBRIDGE_CONSOLE_H -#include "bindings/qjs/js_context.h" +#include "bindings/qjs/executing_context.h" namespace kraken::binding::qjs { -void bindConsole(std::unique_ptr& context); +void bindConsole(std::unique_ptr& context); } diff --git a/bridge/bindings/qjs/bom/console_test.cc b/bridge/bindings/qjs/bom/console_test.cc index 9c1c73b8b8..c64ff2c01b 100644 --- a/bridge/bindings/qjs/bom/console_test.cc +++ b/bridge/bindings/qjs/bom/console_test.cc @@ -4,36 +4,35 @@ */ #include "console.h" -#include "bridge_qjs.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" std::once_flag kGlobalClassIdFlag; TEST(Console, rawPrintShouldWork) { static bool logExecuted = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logExecuted = true; EXPECT_STREQ(message.c_str(), "1234"); }; - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {}); + auto bridge = TEST_init(); const char* code = "__kraken_print__('1234', 'info')"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(logExecuted, true); - delete bridge; } TEST(Console, log) { static bool logExecuted = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { KRAKEN_LOG(VERBOSE) << message; logExecuted = true; }; - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; exit(1); }); const char* code = "console.log(1234);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(logExecuted, true); - delete bridge; } diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc b/bridge/bindings/qjs/bom/dom_timer_coordinator.cc new file mode 100644 index 0000000000..d95e0338c7 --- /dev/null +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.cc @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dom_timer_coordinator.h" +#include "dart_methods.h" +#include "timer.h" + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken::binding::qjs { + +static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + + if (errmsg != nullptr) { + JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); + context->handleException(&exception); + return; + } + + // Trigger timer callbacks. + timer->fire(); + + // Executing pending async jobs. + context->drainPendingPromiseJobs(); +} + +static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { + auto* timer = static_cast(ptr); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + + if (!checkPage(contextId, context)) + return; + if (!context->isValid()) + return; + + handleTimerCallback(timer, errmsg); + + context->timers()->removeTimeoutById(timer->timerId()); +} + +void DOMTimerCoordinator::installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer) { + m_activeTimers[timerId] = timer; +} + +void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { + if (m_activeTimers.count(timerId) == 0) + return nullptr; + DOMTimer* timer = m_activeTimers[timerId]; + + // Push this timer to abandoned list to mark this timer is deprecated. + m_abandonedTimers.emplace_back(timer); + + m_activeTimers.erase(timerId); + return nullptr; +} + +DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { + if (m_activeTimers.count(timerId) == 0) + return nullptr; + return m_activeTimers[timerId]; +} + +void DOMTimerCoordinator::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + for (auto& timer : m_activeTimers) { + JS_MarkValue(rt, timer.second->toQuickJS(), mark_func); + } + + // Recycle all abandoned timers. + if (!m_abandonedTimers.empty()) { + for (auto& timer : m_abandonedTimers) { + JS_MarkValue(rt, timer->toQuickJS(), mark_func); + } + // All abandoned timers should be freed at the sweep stage. + m_abandonedTimers.clear(); + } +} + +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/bindings/qjs/bom/dom_timer_coordinator.h new file mode 100644 index 0000000000..d496693d89 --- /dev/null +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ + +#include +#include +#include + +namespace kraken::binding::qjs { + +class ExecutionContext; +class DOMTimer; + +// Maintains a set of DOMTimers for a given page +// DOMTimerCoordinator assigns IDs to timers; these IDs are +// the ones returned to web authors from setTimeout or setInterval. It +// also tracks recursive creation or iterative scheduling of timers, +// which is used as a signal for throttling repetitive timers. +class DOMTimerCoordinator { + public: + // Creates and installs a new timer. Returns the assigned ID. + void installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer); + + // Removes and disposes the timer with the specified ID, if any. This may + // destroy the timer. + void* removeTimeoutById(int32_t timerId); + DOMTimer* getTimerById(int32_t timerId); + + void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func); + + private: + std::unordered_map m_activeTimers; + std::vector m_abandonedTimers; +}; + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc index 5c2ded8989..f58fc0a44c 100644 --- a/bridge/bindings/qjs/bom/location.cc +++ b/bridge/bindings/qjs/bom/location.cc @@ -9,8 +9,8 @@ namespace kraken::binding::qjs { -JSValue Location::reload(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* location = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Location::reload(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* location = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); if (getDartMethod()->reloadApp == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute 'reload': dart method (reloadApp) is not registered."); } diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h index f0cb31c7d7..f3f87c7300 100644 --- a/bridge/bindings/qjs/bom/location.h +++ b/bridge/bindings/qjs/bom/location.h @@ -6,20 +6,20 @@ #ifndef KRAKENBRIDGE_LOCATION_H #define KRAKENBRIDGE_LOCATION_H +#include "bindings/qjs/executing_context.h" #include "bindings/qjs/host_object.h" -#include "bindings/qjs/js_context.h" namespace kraken::binding::qjs { class Location : public HostObject { public: Location() = delete; - explicit Location(JSContext* context) : HostObject(context, "Location") {} + explicit Location(ExecutionContext* context) : HostObject(context, "Location") {} - static JSValue reload(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue reload(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); private: - ObjectFunction m_reload{m_context, jsObject, "reload", reload, 0}; + DEFINE_FUNCTION(reload, 0); }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/bindings/qjs/bom/performance.cc index 3ba5aec814..ab2813c2ef 100644 --- a/bridge/bindings/qjs/bom/performance.cc +++ b/bridge/bindings/qjs/bom/performance.cc @@ -11,60 +11,45 @@ namespace kraken::binding::qjs { -void bindPerformance(std::unique_ptr& context) { +void bindPerformance(std::unique_ptr& context) { auto* performance = Performance::instance(context.get()); context->defineGlobalProperty("performance", performance->jsObject); } using namespace std::chrono; -PROP_GETTER(PerformanceEntry, name)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewString(ctx, entry->m_nativePerformanceEntry->name); } -PROP_SETTER(PerformanceEntry, name)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(PerformanceEntry, entryType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(PerformanceEntry, entryType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewString(ctx, entry->m_nativePerformanceEntry->entryType); } -PROP_SETTER(PerformanceEntry, entryType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(PerformanceEntry, startTime)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(PerformanceEntry, startTime)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewUint32(ctx, entry->m_nativePerformanceEntry->startTime); } -PROP_SETTER(PerformanceEntry, startTime)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(PerformanceEntry, duration)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(PerformanceEntry, duration)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewUint32(ctx, entry->m_nativePerformanceEntry->duration); } -PROP_SETTER(PerformanceEntry, duration)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Performance, timeOrigin)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Performance, timeOrigin)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); int64_t time = std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()).count(); return JS_NewUint32(ctx, time); } -PROP_SETTER(Performance, timeOrigin)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -JSValue Performance::now(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, performance->internalNow()); } -JSValue Performance::toJSON(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); double now = performance->internalNow(); int64_t timeOrigin = std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()).count(); @@ -74,7 +59,7 @@ JSValue Performance::toJSON(QjsContext* ctx, JSValue this_val, int argc, JSValue return object; } -static JSValue buildPerformanceEntry(const std::string& entryType, JSContext* context, NativePerformanceEntry* nativePerformanceEntry) { +static JSValue buildPerformanceEntry(const std::string& entryType, ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) { if (entryType == "mark") { auto* mark = new PerformanceMark(context, nativePerformanceEntry); return mark->jsObject; @@ -85,8 +70,8 @@ static JSValue buildPerformanceEntry(const std::string& entryType, JSContext* co return JS_NULL; } -JSValue Performance::clearMarks(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Performance::clearMarks(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); JSValue targetMark = JS_NULL; if (argc == 1) { targetMark = argv[0]; @@ -116,13 +101,13 @@ JSValue Performance::clearMarks(QjsContext* ctx, JSValue this_val, int argc, JSV return JS_NULL; } -JSValue Performance::clearMeasures(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue targetMark = JS_NULL; if (argc == 1) { targetMark = argv[0]; } - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); auto entries = performance->m_nativePerformance.entries; auto it = std::begin(*entries); @@ -147,8 +132,8 @@ JSValue Performance::clearMeasures(QjsContext* ctx, JSValue this_val, int argc, return JS_NULL; } -JSValue Performance::getEntries(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); auto entries = performance->getFullEntries(); size_t entriesSize = entries.size(); @@ -166,13 +151,13 @@ JSValue Performance::getEntries(QjsContext* ctx, JSValue this_val, int argc, JSV JS_FreeValue(ctx, pushMethod); return returnArray; } -JSValue Performance::getEntriesByName(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string targetName = jsValueToStdString(ctx, argv[0]); - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); auto entries = performance->getFullEntries(); JSValue targetEntriesArray = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); @@ -188,13 +173,13 @@ JSValue Performance::getEntriesByName(QjsContext* ctx, JSValue this_val, int arg JS_FreeValue(ctx, pushMethod); return targetEntriesArray; } -JSValue Performance::getEntriesByType(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string entryType = jsValueToStdString(ctx, argv[0]); - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); auto entries = performance->getFullEntries(); JSValue targetEntriesArray = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); @@ -209,18 +194,18 @@ JSValue Performance::getEntriesByType(QjsContext* ctx, JSValue this_val, int arg JS_FreeValue(ctx, pushMethod); return targetEntriesArray; } -JSValue Performance::mark(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); } - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); std::string markName = jsValueToStdString(ctx, argv[0]); performance->m_nativePerformance.mark(markName); return JS_NULL; } -JSValue Performance::measure(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { return JS_ThrowTypeError(ctx, "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); } @@ -239,7 +224,7 @@ JSValue Performance::measure(QjsContext* ctx, JSValue this_val, int argc, JSValu endMark = jsValueToStdString(ctx, argv[2]); } - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); JSValue exception = JS_NULL; performance->internalMeasure(name, startMark, endMark, &exception); @@ -249,14 +234,15 @@ JSValue Performance::measure(QjsContext* ctx, JSValue this_val, int argc, JSValu return JS_NULL; } -PerformanceEntry::PerformanceEntry(JSContext* context, NativePerformanceEntry* nativePerformanceEntry) : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} +PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) + : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} -PerformanceMark::PerformanceMark(JSContext* context, std::string& name, int64_t startTime) +PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) : PerformanceEntry(context, new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMark::PerformanceMark(JSContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} -PerformanceMeasure::PerformanceMeasure(JSContext* context, std::string& name, int64_t startTime, int64_t duration) +PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration) : PerformanceEntry(context, new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMeasure::PerformanceMeasure(JSContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} void NativePerformance::mark(const std::string& markName) { int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; @@ -267,7 +253,7 @@ void NativePerformance::mark(const std::string& markName, int64_t startTime) { entries->emplace_back(nativePerformanceEntry); } -Performance::Performance(JSContext* context) : HostObject(context, "Performance") {} +Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} void Performance::internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception) { auto entries = getFullEntries(); @@ -425,8 +411,8 @@ double getMeasureTotalDuration(const std::vector& measu return duration / 1000; } -JSValue Performance::__kraken_navigation_summary__(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +JSValue Performance::__kraken_navigation_summary__(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); JSValue exception = JS_NULL; performance->measureSummary(&exception); diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/bindings/qjs/bom/performance.h index caffcf2c17..e8ba582351 100644 --- a/bridge/bindings/qjs/bom/performance.h +++ b/bridge/bindings/qjs/bom/performance.h @@ -125,7 +125,7 @@ namespace kraken::binding::qjs { -void bindPerformance(std::unique_ptr& context); +void bindPerformance(std::unique_ptr& context); struct NativePerformanceEntry { NativePerformanceEntry(const std::string& name, const std::string& entryType, int64_t startTime, int64_t duration, int64_t uniqueId) : startTime(startTime), duration(duration), uniqueId(uniqueId) { @@ -144,9 +144,12 @@ struct NativePerformanceEntry { class PerformanceEntry : public HostObject { public: PerformanceEntry() = delete; - explicit PerformanceEntry(JSContext* context, NativePerformanceEntry* m_nativePerformanceEntry); + explicit PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* m_nativePerformanceEntry); - DEFINE_HOST_OBJECT_PROPERTY(4, name, entryType, startTime, duration) + DEFINE_READONLY_PROPERTY(name); + DEFINE_READONLY_PROPERTY(entryType); + DEFINE_READONLY_PROPERTY(startTime); + DEFINE_READONLY_PROPERTY(duration); private: NativePerformanceEntry* m_nativePerformanceEntry{nullptr}; @@ -155,15 +158,15 @@ class PerformanceEntry : public HostObject { class PerformanceMark : public PerformanceEntry { public: PerformanceMark() = delete; - explicit PerformanceMark(JSContext* context, std::string& name, int64_t startTime); - explicit PerformanceMark(JSContext* context, NativePerformanceEntry* nativePerformanceEntry); + explicit PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime); + explicit PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry); }; class PerformanceMeasure : public PerformanceEntry { public: PerformanceMeasure() = delete; - explicit PerformanceMeasure(JSContext* context, std::string& name, int64_t startTime, int64_t duration); - explicit PerformanceMeasure(JSContext* context, NativePerformanceEntry* nativePerformanceEntry); + explicit PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration); + explicit PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry); }; class NativePerformance { @@ -176,45 +179,46 @@ class NativePerformance { class Performance : public HostObject { public: Performance() = delete; - explicit Performance(JSContext* context); + explicit Performance(ExecutionContext* context); OBJECT_INSTANCE(Performance); - static JSValue now(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue toJSON(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue clearMarks(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue clearMeasures(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntries(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntriesByName(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntriesByType(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue mark(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue measure(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue now(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue toJSON(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue clearMarks(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue clearMeasures(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getEntries(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getEntriesByName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getEntriesByType(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue mark(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue measure(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); #if ENABLE_PROFILE - static JSValue __kraken_navigation_summary__(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue __kraken_navigation_summary__(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); void measureSummary(JSValue* exception); #endif NativePerformance m_nativePerformance; - DEFINE_HOST_OBJECT_PROPERTY(1, timeOrigin); + DEFINE_READONLY_PROPERTY(timeOrigin); private: void internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception); double internalNow(); std::vector getFullEntries(); - ObjectFunction m_now{m_context, jsObject, "now", now, 0}; - ObjectFunction m_toJSON{m_context, jsObject, "toJSON", toJSON, 0}; - ObjectFunction m_clearMarks{m_context, jsObject, "clearMarks", clearMarks, 1}; - ObjectFunction m_clearMeasures{m_context, jsObject, "clearMeasures", clearMeasures, 1}; - ObjectFunction m_getEntries{m_context, jsObject, "getEntries", getEntries, 0}; - ObjectFunction m_getEntriesByName{m_context, jsObject, "getEntriesByName", getEntriesByName, 2}; - ObjectFunction m_getEntriesByType{m_context, jsObject, "getEntriesByType", getEntriesByType, 1}; - ObjectFunction m_mark{m_context, jsObject, "mark", mark, 1}; - ObjectFunction m_measure{m_context, jsObject, "measure", measure, 4}; + + DEFINE_FUNCTION(now, 0); + DEFINE_FUNCTION(toJSON, 0); + DEFINE_FUNCTION(clearMarks, 1); + DEFINE_FUNCTION(clearMeasures, 1); + DEFINE_FUNCTION(getEntries, 0); + DEFINE_FUNCTION(getEntriesByName, 2); + DEFINE_FUNCTION(getEntriesByType, 1); + DEFINE_FUNCTION(mark, 1); + DEFINE_FUNCTION(measure, 4); #if ENABLE_PROFILE - ObjectFunction m___kraken_navigation_summary__{m_context, jsObject, "__kraken_navigation_summary__", __kraken_navigation_summary__, 0}; + DEFINE_FUNCTION(__kraken_navigation_summary__, 0); #endif }; diff --git a/bridge/bindings/qjs/bom/screen.cc b/bridge/bindings/qjs/bom/screen.cc index 4f235a8b3a..320ee20105 100644 --- a/bridge/bindings/qjs/bom/screen.cc +++ b/bridge/bindings/qjs/bom/screen.cc @@ -7,35 +7,29 @@ namespace kraken::binding::qjs { -void bindScreen(std::unique_ptr& context) { +void bindScreen(std::unique_ptr& context) { auto* screen = new Screen(context.get()); context->defineGlobalProperty("screen", screen->jsObject); } -PROP_GETTER(Screen, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Screen, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (getDartMethod()->getScreen == nullptr) { return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); return JS_NewFloat64(ctx, screen->width); } -PROP_SETTER(Screen, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_UNDEFINED; -} -PROP_GETTER(Screen, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Screen, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (getDartMethod()->getScreen == nullptr) { return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); return JS_NewFloat64(ctx, screen->height); } -PROP_SETTER(Screen, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_UNDEFINED; -} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/bindings/qjs/bom/screen.h index f634fbde1d..69e84b60f2 100644 --- a/bridge/bindings/qjs/bom/screen.h +++ b/bridge/bindings/qjs/bom/screen.h @@ -6,21 +6,22 @@ #ifndef KRAKENBRIDGE_SCREEN_H #define KRAKENBRIDGE_SCREEN_H +#include "bindings/qjs/executing_context.h" #include "bindings/qjs/host_object.h" -#include "bindings/qjs/js_context.h" #include "dart_methods.h" namespace kraken::binding::qjs { class Screen : public HostObject { public: - explicit Screen(JSContext* context) : HostObject(context, "Screen"){}; + explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; private: - DEFINE_HOST_OBJECT_PROPERTY(2, width, height); + DEFINE_READONLY_PROPERTY(width); + DEFINE_READONLY_PROPERTY(height); }; -void bindScreen(std::unique_ptr& context); +void bindScreen(std::unique_ptr& context); } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/timer.cc b/bridge/bindings/qjs/bom/timer.cc index c141ffd3dc..a90ee67088 100644 --- a/bridge/bindings/qjs/bom/timer.cc +++ b/bridge/bindings/qjs/bom/timer.cc @@ -4,68 +4,104 @@ */ #include "timer.h" +#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_patch.h" #include "dart_methods.h" +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + namespace kraken::binding::qjs { -static void handleTimerCallback(TimerCallbackContext* callbackContext, const char* errmsg) { - if (JS_IsNull(callbackContext->callback)) { - // throw JSError inside of dart function callback will directly cause crash - // so we handle it instead of throw - JSValue exception = JS_ThrowTypeError(callbackContext->context->ctx(), "Failed to trigger callback: timer callback is null."); - callbackContext->context->handleException(&exception); - return; - } +DOMTimer::DOMTimer(JSValue callback) : m_callback(callback) {} - if (!JS_IsObject(callbackContext->callback)) { +JSClassID DOMTimer::classId{0}; + +void DOMTimer::fire() { + // 'callback' might be destroyed when calling itself (if it frees the handler), so must take extra care. + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + if (!JS_IsFunction(m_ctx, m_callback)) return; + + JS_DupValue(m_ctx, m_callback); + JSValue returnValue = JS_Call(m_ctx, m_callback, JS_UNDEFINED, 0, nullptr); + JS_FreeValue(m_ctx, m_callback); + + if (JS_IsException(returnValue)) { + context->handleException(&returnValue); } + JS_FreeValue(m_ctx, returnValue); +} + +void DOMTimer::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + JS_MarkValue(rt, m_callback, mark_func); +} + +void DOMTimer::dispose() const { + JS_FreeValueRT(m_runtime, m_callback); +} + +int32_t DOMTimer::timerId() { + return m_timerId; +} + +void DOMTimer::setTimerId(int32_t timerId) { + m_timerId = timerId; +} + +static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(callbackContext->context->ctx(), "%s", errmsg); - callbackContext->context->handleException(&exception); + JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); + context->handleException(&exception); return; } - JSValue returnValue = JS_Call(callbackContext->context->ctx(), callbackContext->callback, callbackContext->context->global(), 0, nullptr); - callbackContext->context->handleException(&returnValue); + if (context->timers()->getTimerById(timer->timerId()) == nullptr) + return; - callbackContext->context->drainPendingPromiseJobs(); + // Trigger timer callbacks. + timer->fire(); - JS_FreeValue(callbackContext->context->ctx(), returnValue); + // Executing pending async jobs. + context->drainPendingPromiseJobs(); } static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { - auto* callbackContext = static_cast(ptr); - if (!checkContext(contextId, callbackContext->context)) + auto* timer = static_cast(ptr); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + + if (!checkPage(contextId, context)) return; - if (!callbackContext->context->isValid()) + if (!context->isValid()) return; - handleTimerCallback(callbackContext, errmsg); + handleTimerCallback(timer, errmsg); - list_del(&callbackContext->link); - JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - delete callbackContext; + context->timers()->removeTimeoutById(timer->timerId()); } static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { - auto* callbackContext = static_cast(ptr); - if (!checkContext(contextId, callbackContext->context)) + auto* timer = static_cast(ptr); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + + if (!checkPage(contextId, context)) return; - if (!callbackContext->context->isValid()) + if (!context->isValid()) return; - handleTimerCallback(callbackContext, errmsg); + handleTimerCallback(timer, errmsg); } -static JSValue setTimeout(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue callbackValue = argv[0]; JSValue timeoutValue = argv[1]; @@ -87,14 +123,21 @@ static JSValue setTimeout(QjsContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } +#if FLUTTER_BACKEND if (getDartMethod()->setTimeout == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); } +#endif + + // Create a timer object to keep track timer callback. + auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); + + auto timerId = getDartMethod()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); - auto* callbackContext = new TimerCallbackContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&callbackContext->link, &context->timer_job_list); + // Register timerId. + timer->setTimerId(timerId); - auto timerId = getDartMethod()->setTimeout(callbackContext, context->getContextId(), handleTransientCallback, timeout); + context->timers()->installNewTimer(context, timerId, timer); // `-1` represents ffi error occurred. if (timerId == -1) { @@ -104,51 +147,12 @@ static JSValue setTimeout(QjsContext* ctx, JSValueConst this_val, int argc, JSVa return JS_NewUint32(ctx, timerId); } -static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { - auto* callbackContext = static_cast(ptr); - if (!checkContext(contextId, callbackContext->context)) - return; - - if (!callbackContext->context->isValid()) - return; - - if (JS_IsNull(callbackContext->callback)) { - // throw JSError inside of dart function callback will directly cause crash - // so we handle it instead of throw - JSValue exception = JS_ThrowTypeError(callbackContext->context->ctx(), "Failed to trigger callback: requestAnimationFrame callback is null."); - callbackContext->context->handleException(&exception); - return; - } - - if (!JS_IsObject(callbackContext->callback)) { - return; - } - - if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(callbackContext->context->ctx(), "%s", errmsg); - callbackContext->context->handleException(&exception); - return; - } - - JSValue arguments[] = {JS_NewFloat64(callbackContext->context->ctx(), highResTimeStamp)}; - - JSValue returnValue = JS_Call(callbackContext->context->ctx(), callbackContext->callback, callbackContext->context->global(), 1, arguments); - callbackContext->context->handleException(&returnValue); - - callbackContext->context->drainPendingPromiseJobs(); - - list_del(&callbackContext->link); - JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - JS_FreeValue(callbackContext->context->ctx(), returnValue); - delete callbackContext; -} - -static JSValue setInterval(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue callbackValue = argv[0]; JSValue timeoutValue = argv[1]; @@ -169,14 +173,19 @@ static JSValue setInterval(QjsContext* ctx, JSValueConst this_val, int argc, JSV } else { return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } + if (getDartMethod()->setInterval == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': dart method (setInterval) is not registered."); } - // the context pointer which will be pass by pointer address to dart code. - auto* callbackContext = new TimerCallbackContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&callbackContext->link, &context->timer_job_list); - uint32_t timerId = getDartMethod()->setInterval(callbackContext, context->getContextId(), handlePersistentCallback, timeout); + // Create a timer object to keep track timer callback. + auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); + + uint32_t timerId = getDartMethod()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); + + // Register timerId. + timer->setTimerId(timerId); + context->timers()->installNewTimer(context, timerId, timer); if (timerId == -1) { return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': dart method (setInterval) got unexpected error."); @@ -185,53 +194,12 @@ static JSValue setInterval(QjsContext* ctx, JSValueConst this_val, int argc, JSV return JS_NewUint32(ctx, timerId); } -static JSValue requestAnimationFrame(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc <= 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': 1 argument required, but only 0 present."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - JSValue callbackValue = argv[0]; - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - // Flush all pending ui messages. - getDartMethod()->flushUICommand(); - - if (getDartMethod()->requestAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); - } - - auto* callbackContext = new TimerCallbackContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&callbackContext->link, &context->timer_job_list); - - uint32_t requestId = getDartMethod()->requestAnimationFrame(callbackContext, context->getContextId(), handleRAFTransientCallback); - - // `-1` represents some error occurred. - if (requestId == -1) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) executed " - "with unexpected error."); - } - - return JS_NewUint32(ctx, requestId); -} - -static JSValue clearTimeout(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc <= 0) { return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue timeIdValue = argv[0]; if (!JS_IsNumber(timeIdValue)) { @@ -246,36 +214,15 @@ static JSValue clearTimeout(QjsContext* ctx, JSValueConst this_val, int argc, JS } getDartMethod()->clearTimeout(context->getContextId(), id); - return JS_NULL; -} - -static JSValue cancelAnimationFrame(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc <= 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': 1 argument required, but only 0 present."); - } - auto context = static_cast(JS_GetContextOpaque(ctx)); - JSValue requestIdValue = argv[0]; - if (!JS_IsNumber(requestIdValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': parameter 1 (timer) is not a timer kind."); - } - - int32_t id; - JS_ToInt32(ctx, &id, requestIdValue); - - if (getDartMethod()->cancelAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); - } - getDartMethod()->cancelAnimationFrame(context->getContextId(), id); + context->timers()->removeTimeoutById(id); return JS_NULL; } -void bindTimer(std::unique_ptr& context) { +void bindTimer(std::unique_ptr& context) { QJS_GLOBAL_BINDING_FUNCTION(context, setTimeout, "setTimeout", 2); QJS_GLOBAL_BINDING_FUNCTION(context, setInterval, "setInterval", 2); - QJS_GLOBAL_BINDING_FUNCTION(context, requestAnimationFrame, "requestAnimationFrame", 1); QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearTimeout", 1); QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearInterval", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, cancelAnimationFrame, "cancelAnimationFrame", 1); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/timer.h b/bridge/bindings/qjs/bom/timer.h index b86a754f29..08bea4f1b9 100644 --- a/bridge/bindings/qjs/bom/timer.h +++ b/bridge/bindings/qjs/bom/timer.h @@ -6,17 +6,35 @@ #ifndef KRAKENBRIDGE_TIMER_H #define KRAKENBRIDGE_TIMER_H -#include "bindings/qjs/js_context.h" +#include "bindings/qjs/executing_context.h" +#include "bindings/qjs/garbage_collected.h" +#include "dom_timer_coordinator.h" namespace kraken::binding::qjs { -struct TimerCallbackContext { - JSValue callback; - JSContext* context; - list_head link; +class DOMTimer : public GarbageCollected { + public: + static JSClassID classId; + DOMTimer(JSValue callback); + + // Trigger timer callback. + void fire(); + + int32_t timerId(); + void setTimerId(int32_t timerId); + + [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "DOMTimer"; } + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: + int32_t m_timerId{-1}; + int32_t m_isInterval{false}; + JSValue m_callback; }; -void bindTimer(std::unique_ptr& context); +void bindTimer(std::unique_ptr& context); } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/timer_test.cc b/bridge/bindings/qjs/bom/timer_test.cc new file mode 100644 index 0000000000..863b493c5a --- /dev/null +++ b/bridge/bindings/qjs/bom/timer_test.cc @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "gtest/gtest.h" +#include "kraken_bridge.h" +#include "kraken_test_env.h" +#include "page.h" + +TEST(Timer, setTimeout) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + static int logIdx = 0; + switch (logIdx) { + case 0: + EXPECT_STREQ(message.c_str(), "1234"); + break; + case 1: + EXPECT_STREQ(message.c_str(), "789"); + break; + case 2: + EXPECT_STREQ(message.c_str(), "456"); + break; + } + + logIdx++; + }; + + std::string code = R"( +setTimeout(() => { + console.log('456'); +}); +new Promise((resolve, reject) => {resolve();}).then(() => { console.log('789') }); +console.log('1234'); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->getContext().get()); + disposePage(0); +} + +TEST(Timer, clearTimeout) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + std::string code = R"( +function getCachedData() { + let index = 0; + + return function() { + return index++; + } +} + +let timer = setTimeout(async () => { + let data = getCachedData()(); + console.log(data); +}, 10); +clearTimeout(timer); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->getContext().get()); + disposePage(0); +} diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc index 3f305116d7..7c032a7f7c 100644 --- a/bridge/bindings/qjs/bom/window.cc +++ b/bridge/bindings/qjs/bom/window.cc @@ -4,7 +4,9 @@ */ #include "window.h" +#include "bindings/qjs/dom/document.h" #include "bindings/qjs/dom/events/.gen/message_event.h" +#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_patch.h" #include "dart_methods.h" @@ -12,20 +14,20 @@ namespace kraken::binding::qjs { std::once_flag kWindowInitOnceFlag; -void bindWindow(std::unique_ptr& context) { +void bindWindow(std::unique_ptr& context) { // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. auto* windowConstructor = new Window(context.get()); JS_SetPrototype(context->ctx(), context->global(), windowConstructor->prototype()); - context->defineGlobalProperty("Window", windowConstructor->classObject); + context->defineGlobalProperty("Window", windowConstructor->jsObject); auto* window = new WindowInstance(windowConstructor); JS_SetOpaque(context->global(), window); - context->defineGlobalProperty("__window__", window->instanceObject); + context->defineGlobalProperty("__window__", window->jsObject); } JSClassID Window::kWindowClassId{0}; -Window::Window(JSContext* context) : EventTarget(context, "Window") { +Window::Window(ExecutionContext* context) : EventTarget(context, "Window") { std::call_once(kWindowInitOnceFlag, []() { JS_NewClassID(&kWindowClassId); }); JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } @@ -34,26 +36,27 @@ JSClassID Window::classId() { return 1; } -JSValue Window::open(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue url = argv[0]; +JSValue Window::open(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; return window->callNativeMethods("open", 1, arguments); } -JSValue Window::scrollTo(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Window::scrollTo(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +#if FLUTTER_BACKEND auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return window->callNativeMethods("scroll", 2, arguments); +#else + return JS_UNDEFINED; +#endif } -JSValue Window::scrollBy(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue x = argv[0]; - JSValue y = argv[1]; +JSValue Window::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return window->callNativeMethods("scrollBy", 2, arguments); } -JSValue Window::postMessage(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Window::postMessage(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue messageValue = argv[0]; JSValue originValue = argv[1]; JSValue globalObjectValue = JS_GetGlobalObject(ctx); @@ -63,7 +66,7 @@ JSValue Window::postMessage(QjsContext* ctx, JSValue this_val, int argc, JSValue JS_SetPropertyStr(ctx, messageEventInitValue, "data", JS_DupValue(ctx, messageValue)); JS_SetPropertyStr(ctx, originValue, "origin", JS_DupValue(ctx, originValue)); - JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->m_context)->classObject, 1, &messageEventInitValue); + JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->m_context)->jsObject, 1, &messageEventInitValue); auto* event = static_cast(JS_GetOpaque(messageEventValue, Event::kEventClassID)); window->dispatchEvent(event); @@ -73,102 +76,147 @@ JSValue Window::postMessage(QjsContext* ctx, JSValue this_val, int argc, JSValue return JS_NULL; } -PROP_GETTER(Window, devicePixelRatio)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Window::requestAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + if (argc <= 0) { + return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': 1 argument required, but only 0 present."); + } + + auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto window = static_cast(JS_GetOpaque(context->global(), Window::classId())); + + JSValue callbackValue = argv[0]; + + if (!JS_IsObject(callbackValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); + } + + if (!JS_IsFunction(ctx, callbackValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); + } + + // Flutter backend implements check +#if FLUTTER_BACKEND + if (getDartMethod()->flushUICommand == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); + } + // Flush all pending ui messages. + getDartMethod()->flushUICommand(); + + if (getDartMethod()->requestAnimationFrame == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); + } +#endif + + auto* frameCallback = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(ctx, &FrameCallback::classId); + + int32_t requestId = window->document()->requestAnimationFrame(frameCallback); + + // `-1` represents some error occurred. + if (requestId == -1) { + return JS_ThrowTypeError(ctx, + "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) executed " + "with unexpected error."); + } + + return JS_NewUint32(ctx, requestId); +} + +JSValue Window::cancelAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + if (argc <= 0) { + return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': 1 argument required, but only 0 present."); + } + + auto context = static_cast(JS_GetContextOpaque(ctx)); + auto window = static_cast(JS_GetOpaque(context->global(), Window::classId())); + + JSValue requestIdValue = argv[0]; + if (!JS_IsNumber(requestIdValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': parameter 1 (timer) is not a timer kind."); + } + + int32_t id; + JS_ToInt32(ctx, &id, requestIdValue); + + if (getDartMethod()->cancelAnimationFrame == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); + } + + window->document()->cancelAnimationFrame(id); + + return JS_NULL; +} + +IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (getDartMethod()->devicePixelRatio == nullptr) { return JS_ThrowTypeError(ctx, "Failed to read devicePixelRatio: dart method (devicePixelRatio) is not register."); } - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); double devicePixelRatio = getDartMethod()->devicePixelRatio(context->getContextId()); return JS_NewFloat64(ctx, devicePixelRatio); } -PROP_SETTER(Window, devicePixelRatio)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, colorScheme)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, colorScheme)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (getDartMethod()->platformBrightness == nullptr) { return JS_ThrowTypeError(ctx, "Failed to read colorScheme: dart method (platformBrightness) not register."); } - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); const NativeString* code = getDartMethod()->platformBrightness(context->getContextId()); return JS_NewUnicodeString(context->runtime(), ctx, code->string, code->length); } -PROP_SETTER(Window, colorScheme)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, __location__)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, __location__)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); if (window == nullptr) return JS_UNDEFINED; - return JS_DupValue(ctx, window->m_location->jsObject); -} -PROP_SETTER(Window, __location__)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return JS_DupValue(ctx, window->m_location.value()); } -PROP_GETTER(Window, location)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); return JS_GetPropertyStr(ctx, window->m_context->global(), "location"); } -PROP_SETTER(Window, location)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, window)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, window)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_GetGlobalObject(ctx); } -PROP_SETTER(Window, window)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, parent)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(Window, parent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_GetGlobalObject(ctx); } -PROP_SETTER(Window, parent)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, scrollX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, scrollX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); return window->callNativeMethods("scrollX", 0, nullptr); } -PROP_SETTER(Window, scrollX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, scrollY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, scrollY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); return window->callNativeMethods("scrollY", 0, nullptr); } -PROP_SETTER(Window, scrollY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, onerror)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, onerror)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); return JS_DupValue(ctx, window->onerror); } -PROP_SETTER(Window, onerror)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Window, onerror)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* window = static_cast(JS_GetOpaque(this_val, 1)); JSValue eventString = JS_NewString(ctx, "onerror"); JSString* p = JS_VALUE_GET_STRING(eventString); - window->setPropertyHandler(p, argv[0]); + JSValue onerrorHandler = argv[0]; + window->setAttributesEventHandler(p, onerrorHandler); if (!JS_IsNull(window->onerror)) { JS_FreeValue(ctx, window->onerror); } - window->onerror = JS_DupValue(ctx, argv[0]); + window->onerror = JS_DupValue(ctx, onerrorHandler); JS_FreeValue(ctx, eventString); return JS_NULL; } -PROP_SETTER(Window, self)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Window, self)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_GetGlobalObject(ctx); } @@ -179,14 +227,14 @@ WindowInstance::WindowInstance(Window* window) : EventTargetInstance(window, Win m_context->m_window = this; } -void WindowInstance::gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::gcMark(rt, val, mark_func); +void WindowInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + EventTargetInstance::trace(rt, val, mark_func); + + JS_MarkValue(rt, onerror, mark_func); +} - // Should check object is already inited before gc mark. - if (JS_IsObject(m_location->jsObject)) - JS_MarkValue(rt, m_location->jsObject, mark_func); - if (JS_IsObject(onerror)) - JS_MarkValue(rt, onerror, mark_func); +DocumentInstance* WindowInstance::document() { + return m_context->m_document; } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h index 3cbc13939e..4a1cbccfbd 100644 --- a/bridge/bindings/qjs/bom/window.h +++ b/bridge/bindings/qjs/bom/window.h @@ -8,11 +8,11 @@ #include "bindings/qjs/bom/location.h" #include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/js_context.h" +#include "bindings/qjs/executing_context.h" namespace kraken::binding::qjs { -void bindWindow(std::unique_ptr& context); +void bindWindow(std::unique_ptr& context); class WindowInstance; @@ -22,24 +22,40 @@ class Window : public EventTarget { static JSClassID classId(); - static JSValue open(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollTo(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollBy(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue postMessage(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue open(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue scrollTo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue scrollBy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue postMessage(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue requestAnimationFrame(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue cancelAnimationFrame(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); Window() = delete; - explicit Window(JSContext* context); + explicit Window(ExecutionContext* context); OBJECT_INSTANCE(Window); private: - ObjectFunction m_open{m_context, m_prototypeObject, "open", open, 1}; + DEFINE_PROTOTYPE_READONLY_PROPERTY(devicePixelRatio); + DEFINE_PROTOTYPE_READONLY_PROPERTY(colorScheme); + DEFINE_PROTOTYPE_READONLY_PROPERTY(__location__); + DEFINE_PROTOTYPE_READONLY_PROPERTY(location); + DEFINE_PROTOTYPE_READONLY_PROPERTY(window); + DEFINE_PROTOTYPE_READONLY_PROPERTY(parent); + DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollX); + DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollY); + DEFINE_PROTOTYPE_READONLY_PROPERTY(self); + + DEFINE_PROTOTYPE_PROPERTY(onerror); + + DEFINE_PROTOTYPE_FUNCTION(open, 1); + // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. ObjectFunction m_scroll{m_context, m_prototypeObject, "scroll", scrollTo, 2}; - ObjectFunction m_scrollTo{m_context, m_prototypeObject, "scrollTo", scrollTo, 2}; - ObjectFunction m_scrollBy{m_context, m_prototypeObject, "scrollBy", scrollBy, 2}; - ObjectFunction m_postMessage{m_context, m_prototypeObject, "postMessage", postMessage, 3}; + DEFINE_PROTOTYPE_FUNCTION(scrollTo, 2); + DEFINE_PROTOTYPE_FUNCTION(scrollBy, 2); + DEFINE_PROTOTYPE_FUNCTION(postMessage, 3); + DEFINE_PROTOTYPE_FUNCTION(requestAnimationFrame, 1); + DEFINE_PROTOTYPE_FUNCTION(cancelAnimationFrame, 1); - DEFINE_HOST_CLASS_PROTOTYPE_PROPERTY(10, devicePixelRatio, colorScheme, __location__, location, window, parent, scrollX, scrollY, onerror, self); friend WindowInstance; }; @@ -47,15 +63,16 @@ class WindowInstance : public EventTargetInstance { public: WindowInstance() = delete; explicit WindowInstance(Window* window); - ~WindowInstance() { JS_FreeValue(m_ctx, onerror); } + ~WindowInstance() {} private: - void gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + DocumentInstance* document(); - Location* m_location{new Location(m_context)}; + ObjectProperty m_location{m_context, jsObject, "m_location", (new Location(m_context))->jsObject}; JSValue onerror{JS_NULL}; friend Window; - friend JSContext; + friend ExecutionContext; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window_test.cc b/bridge/bindings/qjs/bom/window_test.cc index b7d922531c..8abfa12955 100644 --- a/bridge/bindings/qjs/bom/window_test.cc +++ b/bridge/bindings/qjs/bom/window_test.cc @@ -4,24 +4,56 @@ */ #include "window.h" -#include "bridge_qjs.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(Window, instanceofEventTarget) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "console.log(window instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } + +TEST(Window, requestAnimationFrame) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); }; + + std::string code = R"( +requestAnimationFrame(() => { + console.log('456'); +}); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->getContext().get()); +} + +TEST(Window, cancelAnimationFrame) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; + + std::string code = R"( +let id = requestAnimationFrame(() => { + console.log('456'); +}); +cancelAnimationFrame(id); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->getContext().get()); +} diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc index 0a2054651d..9a5692b416 100644 --- a/bridge/bindings/qjs/dom/all_collection.cc +++ b/bridge/bindings/qjs/dom/all_collection.cc @@ -7,23 +7,23 @@ namespace kraken::binding::qjs { -JSValue AllCollection::item(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_NULL; } uint32_t index; JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); if (index >= collection->m_nodes.size()) { return JS_NULL; } auto node = collection->m_nodes[index]; - return node->instanceObject; + return node->jsObject; } -JSValue AllCollection::add(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); } @@ -38,26 +38,26 @@ JSValue AllCollection::add(QjsContext* ctx, JSValue this_val, int argc, JSValue* before = argv[1]; } - auto* node = static_cast(JS_GetOpaque(argv[0], JSContext::kHostObjectClassId)); - auto* collection = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* node = static_cast(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); + auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); NodeInstance* beforeNode = nullptr; if (!JS_IsNull(before)) { - beforeNode = static_cast(JS_GetOpaque(before, JSContext::kHostObjectClassId)); + beforeNode = static_cast(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); } collection->internalAdd(node, beforeNode); return JS_NULL; } -JSValue AllCollection::remove(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); } uint32_t index; JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); collection->m_nodes.erase(collection->m_nodes.begin() + index); return JS_NULL; } @@ -71,12 +71,9 @@ void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { } } -PROP_GETTER(AllCollection, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* collection = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewUint32(ctx, collection->m_nodes.size()); } -PROP_SETTER(AllCollection, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/all_collection.h b/bridge/bindings/qjs/dom/all_collection.h index 6dc267c395..0c1a43213f 100644 --- a/bridge/bindings/qjs/dom/all_collection.h +++ b/bridge/bindings/qjs/dom/all_collection.h @@ -13,13 +13,13 @@ namespace kraken::binding::qjs { class AllCollection : public HostObject { public: - AllCollection(JSContext* context) : HostObject(context, "AllCollection"){}; + AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; - static JSValue item(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue add(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue remove(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - DEFINE_HOST_OBJECT_PROPERTY(1, length); + DEFINE_READONLY_PROPERTY(length); void internalAdd(NodeInstance* node, NodeInstance* before); diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc index 96b265f7ef..6b3ac811af 100644 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ b/bridge/bindings/qjs/dom/comment_node.cc @@ -13,47 +13,38 @@ std::once_flag kCommentInitFlag; JSClassID Comment::kCommentClassId{0}; -void bindCommentNode(std::unique_ptr& context) { +void bindCommentNode(std::unique_ptr& context) { auto* constructor = Comment::instance(context.get()); - context->defineGlobalProperty("Comment", constructor->classObject); + context->defineGlobalProperty("Comment", constructor->jsObject); } JSClassID Comment::classId() { return kCommentClassId; } -Comment::Comment(JSContext* context) : Node(context, "Comment") { +Comment::Comment(ExecutionContext* context) : Node(context, "Comment") { std::call_once(kCommentInitFlag, []() { JS_NewClassID(&kCommentClassId); }); JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); } -JSValue Comment::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new CommentInstance(this))->instanceObject; +JSValue Comment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { + return (new CommentInstance(this))->jsObject; } -PROP_GETTER(CommentInstance, data)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Comment, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NewString(ctx, ""); } -PROP_SETTER(CommentInstance, data)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(CommentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Comment, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NewString(ctx, "#comment"); } -PROP_SETTER(CommentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(CommentInstance, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Comment, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NewUint32(ctx, 0); } -PROP_SETTER(CommentInstance, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -CommentInstance::CommentInstance(Comment* comment) : NodeInstance(comment, NodeType::COMMENT_NODE, DocumentInstance::instance(Document::instance(comment->m_context)), Comment::classId(), "Comment") { - ::foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); +CommentInstance::CommentInstance(Comment* comment) : NodeInstance(comment, NodeType::COMMENT_NODE, Comment::classId(), "Comment") { + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h index 4a7bc14b6c..61777b04ca 100644 --- a/bridge/bindings/qjs/dom/comment_node.h +++ b/bridge/bindings/qjs/dom/comment_node.h @@ -10,7 +10,7 @@ namespace kraken::binding::qjs { -void bindCommentNode(std::unique_ptr& context); +void bindCommentNode(std::unique_ptr& context); class CommentInstance; @@ -19,13 +19,17 @@ class Comment : public Node { static JSClassID kCommentClassId; static JSClassID classId(); Comment() = delete; - explicit Comment(JSContext* context); + explicit Comment(ExecutionContext* context); OBJECT_INSTANCE(Comment); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; private: + DEFINE_PROTOTYPE_READONLY_PROPERTY(data); + DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); + DEFINE_PROTOTYPE_READONLY_PROPERTY(length); + friend CommentInstance; }; @@ -35,8 +39,6 @@ class CommentInstance : public NodeInstance { explicit CommentInstance(Comment* comment); private: - DEFINE_HOST_CLASS_PROPERTY(3, data, nodeName, length) - friend Comment; }; diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc index 19e703e50b..b7f5e03d7b 100644 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ b/bridge/bindings/qjs/dom/custom_event.cc @@ -11,12 +11,12 @@ namespace kraken::binding::qjs { -void bindCustomEvent(std::unique_ptr& context) { +void bindCustomEvent(std::unique_ptr& context) { auto* constructor = CustomEvent::instance(context.get()); - context->defineGlobalProperty("CustomEvent", constructor->classObject); + context->defineGlobalProperty("CustomEvent", constructor->jsObject); } -JSValue CustomEvent::initCustomEvent(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); } @@ -27,7 +27,7 @@ JSValue CustomEvent::initCustomEvent(QjsContext* ctx, JSValue this_val, int argc } JSValue typeValue = argv[0]; - eventInstance->nativeEvent->type = jsValueToNativeString(ctx, typeValue); + eventInstance->nativeEvent->type = jsValueToNativeString(ctx, typeValue).release(); if (argc <= 2) { bool canBubble = JS_ToBool(ctx, argv[1]); @@ -45,7 +45,7 @@ JSValue CustomEvent::initCustomEvent(QjsContext* ctx, JSValue this_val, int argc return JS_NULL; } -JSValue CustomEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue CustomEvent::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); } @@ -61,20 +61,13 @@ JSValue CustomEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVa auto* customEvent = new CustomEventInstance(CustomEvent::instance(context()), typeAtom, customEventInit); JS_FreeAtom(m_ctx, typeAtom); - return customEvent->instanceObject; + return customEvent->jsObject; } -PROP_GETTER(CustomEventInstance, detail)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* customEventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return customEventInstance->m_detail.value(); } -PROP_SETTER(CustomEventInstance, detail)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) - return JS_NULL; - auto* customEventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - customEventInstance->m_detail.value(argv[0]); - return JS_NULL; -} CustomEventInstance::CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom customEventType, JSValue eventInit) : EventInstance(jsCustomEvent, customEventType, eventInit) { if (!JS_IsNull(eventInit)) { diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h index 1e438f9ff8..b14eeecf01 100644 --- a/bridge/bindings/qjs/dom/custom_event.h +++ b/bridge/bindings/qjs/dom/custom_event.h @@ -10,7 +10,7 @@ namespace kraken::binding::qjs { -void bindCustomEvent(std::unique_ptr& context); +void bindCustomEvent(std::unique_ptr& context); struct NativeCustomEvent { NativeEvent nativeEvent; @@ -22,14 +22,16 @@ class CustomEventInstance; class CustomEvent : public Event { public: CustomEvent() = delete; - explicit CustomEvent(JSContext* context) : Event(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Event::instance(m_context)->prototype()); }; - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + explicit CustomEvent(ExecutionContext* context) : Event(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Event::instance(m_context)->prototype()); }; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - static JSValue initCustomEvent(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue initCustomEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); OBJECT_INSTANCE(CustomEvent); private: - ObjectFunction m_initCustomEvent{m_context, m_prototypeObject, "initCustomEvent", initCustomEvent, 4}; + DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); + + DEFINE_PROTOTYPE_FUNCTION(initCustomEvent, 4); friend CustomEventInstance; }; @@ -39,8 +41,6 @@ class CustomEventInstance : public EventInstance { explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); private: - DEFINE_HOST_CLASS_PROPERTY(1, detail); - JSValueHolder m_detail{m_ctx, JS_NULL}; NativeCustomEvent* nativeCustomEvent{nullptr}; friend CustomEvent; diff --git a/bridge/bindings/qjs/dom/custom_event_test.cc b/bridge/bindings/qjs/dom/custom_event_test.cc index ed285c927d..68c92a7741 100644 --- a/bridge/bindings/qjs/dom/custom_event_test.cc +++ b/bridge/bindings/qjs/dom/custom_event_test.cc @@ -3,18 +3,19 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "event_target.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(CustomEvent, instanceofEvent) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -23,7 +24,6 @@ TEST(CustomEvent, instanceofEvent) { "let customEvent = new CustomEvent('abc', { detail: 'helloworld'});" "console.log(customEvent instanceof Event);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index ad7f3399f2..abebe9d9e9 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -6,7 +6,7 @@ #include "document.h" #include #include "all_collection.h" -#include "bindings/qjs/js_context.h" +#include "bindings/qjs/executing_context.h" #include "comment_node.h" #include "dart_methods.h" #include "document_fragment.h" @@ -39,7 +39,7 @@ void traverseNode(NodeInstance* node, TraverseHandler handler) { if (shouldExit) return; - QjsContext* ctx = node->context()->ctx(); + JSContext* ctx = node->context()->ctx(); int childNodesLen = arrayGetLength(ctx, node->childNodes); if (childNodesLen != 0) { @@ -55,64 +55,74 @@ void traverseNode(NodeInstance* node, TraverseHandler handler) { std::once_flag kDocumentInitOnceFlag; -void bindDocument(std::unique_ptr& context) { +void bindDocument(std::unique_ptr& context) { auto* documentConstructor = Document::instance(context.get()); - context->defineGlobalProperty("Document", documentConstructor->classObject); - JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->classObject, 0, nullptr); + context->defineGlobalProperty("Document", documentConstructor->jsObject); + JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->jsObject, 0, nullptr); context->defineGlobalProperty("document", documentInstance); } JSClassID Document::kDocumentClassID{0}; -Document::Document(JSContext* context) : Node(context, "Document") { +Document::Document(ExecutionContext* context) : Node(context, "Document") { std::call_once(kDocumentInitOnceFlag, []() { JS_NewClassID(&kDocumentClassID); }); JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); if (!document_registered) { - Element::defineElement("img", ImageElement::instance(m_context)); - Element::defineElement("a", AnchorElement::instance(m_context)); - Element::defineElement("canvas", CanvasElement::instance(m_context)); - Element::defineElement("input", InputElement::instance(m_context)); - Element::defineElement("object", ObjectElement::instance(m_context)); - Element::defineElement("script", ScriptElement::instance(m_context)); - Element::defineElement("template", TemplateElement::instance(m_context)); + defineElement("img", ImageElement::instance(m_context)); + defineElement("a", AnchorElement::instance(m_context)); + defineElement("canvas", CanvasElement::instance(m_context)); + defineElement("input", InputElement::instance(m_context)); + defineElement("object", ObjectElement::instance(m_context)); + defineElement("script", ScriptElement::instance(m_context)); + defineElement("template", TemplateElement::instance(m_context)); document_registered = true; } if (!event_registered) { event_registered = true; - Event::defineEvent(EVENT_INPUT, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new InputEventInstance(InputEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_MEDIA_ERROR, [](JSContext* context, void* nativeEvent) -> EventInstance* { + Event::defineEvent( + EVENT_INPUT, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new InputEventInstance(InputEvent::instance(context), reinterpret_cast(nativeEvent)); }); + Event::defineEvent(EVENT_MEDIA_ERROR, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new MediaErrorEventInstance(MediaErrorEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_MESSAGE, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new MessageEventInstance(MessageEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_CLOSE, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new CloseEventInstance(CloseEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_INTERSECTION_CHANGE, [](JSContext* context, void* nativeEvent) -> EventInstance* { + Event::defineEvent(EVENT_MESSAGE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new MessageEventInstance(MessageEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent( + EVENT_CLOSE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new CloseEventInstance(CloseEvent::instance(context), reinterpret_cast(nativeEvent)); }); + Event::defineEvent(EVENT_INTERSECTION_CHANGE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new IntersectionChangeEventInstance(IntersectionChangeEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_TOUCH_START, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_TOUCH_END, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_TOUCH_MOVE, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_TOUCH_CANCEL, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_SWIPE, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_PAN, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_LONG_PRESS, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_SCALE, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_CLICK, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_CANCEL, - [](JSContext* context, void* nativeEvent) -> EventInstance* { return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_POPSTATE, [](JSContext* context, void* nativeEvent) -> EventInstance* { + Event::defineEvent(EVENT_TOUCH_START, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_TOUCH_END, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_TOUCH_MOVE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_TOUCH_CANCEL, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_SWIPE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_PAN, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_LONG_PRESS, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_SCALE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent( + EVENT_CLICK, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); }); + Event::defineEvent(EVENT_CANCEL, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { + return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); + }); + Event::defineEvent(EVENT_POPSTATE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new PopStateEventInstance(PopStateEvent::instance(context), reinterpret_cast(nativeEvent)); }); } @@ -122,12 +132,12 @@ JSClassID Document::classId() { return kDocumentClassID; } -JSValue Document::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue Document::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { auto* instance = new DocumentInstance(this); - return instance->instanceObject; + return instance->jsObject; } -JSValue Document::createEvent(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Document::createEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to argumentCount: 1 argument required, but only 0 present."); } @@ -140,18 +150,18 @@ JSValue Document::createEvent(QjsContext* ctx, JSValue this_val, int argc, JSVal JS_FreeCString(ctx, c_eventType); std::string eventType = std::string(c_eventType); if (eventType == "Event") { - NativeString* nativeEventType = jsValueToNativeString(ctx, eventTypeValue); - auto nativeEvent = new NativeEvent{nativeEventType}; + std::unique_ptr nativeEventType = jsValueToNativeString(ctx, eventTypeValue); + auto nativeEvent = new NativeEvent{nativeEventType.release()}; auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); auto e = Event::buildEventInstance(eventType, document->context(), nativeEvent, false); - return e->instanceObject; + return e->jsObject; } else { return JS_NULL; } } -JSValue Document::createElement(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Document::createElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to createElement: 1 argument required, but only 0 present."); } @@ -162,33 +172,36 @@ JSValue Document::createElement(QjsContext* ctx, JSValue this_val, int argc, JSV } auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); std::string tagName = jsValueToStdString(ctx, tagNameValue); - JSValue constructor = Element::getConstructor(document->m_context, tagName); + JSValue constructor = static_cast(document->prototype())->getElementConstructor(document->m_context, tagName); JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); return element; } -JSValue Document::createTextNode(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Document::createTextNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'createTextNode' on 'Document': 1 argument required, but only 0 present."); } auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue textNode = JS_CallConstructor(ctx, TextNode::instance(document->m_context)->classObject, argc, argv); + JSValue textNode = JS_CallConstructor(ctx, TextNode::instance(document->m_context)->jsObject, argc, argv); return textNode; } -JSValue Document::createDocumentFragment(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Document::createDocumentFragment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - return JS_CallConstructor(ctx, DocumentFragment::instance(document->m_context)->classObject, 0, nullptr); + return JS_CallConstructor(ctx, DocumentFragment::instance(document->m_context)->jsObject, 0, nullptr); } -JSValue Document::createComment(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Document::createComment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue commentNode = JS_CallConstructor(ctx, Comment::instance(document->m_context)->classObject, argc, argv); + JSValue commentNode = JS_CallConstructor(ctx, Comment::instance(document->m_context)->jsObject, argc, argv); return commentNode; } -JSValue Document::getElementById(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Document::getElementById(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementById' on 'Document': 1 argument required, but only 0 present."); } @@ -214,12 +227,13 @@ JSValue Document::getElementById(QjsContext* ctx, JSValue this_val, int argc, JS for (auto& element : targetElementList) { if (element->isConnected()) - return JS_DupValue(ctx, element->instanceObject); + return JS_DupValue(ctx, element->jsObject); } return JS_NULL; } -JSValue Document::getElementsByTagName(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Document::getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementsByTagName' on 'Document': 1 argument required, " @@ -233,9 +247,9 @@ JSValue Document::getElementsByTagName(QjsContext* ctx, JSValue this_val, int ar std::vector elements; - traverseNode(document->m_documentElement, [tagName, &elements](NodeInstance* node) { + traverseNode(document, [tagName, &elements](NodeInstance* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { - auto element = static_cast(node); + auto* element = static_cast(node); if (element->tagName() == tagName || tagName == "*") { elements.emplace_back(element); } @@ -248,14 +262,14 @@ JSValue Document::getElementsByTagName(QjsContext* ctx, JSValue this_val, int ar JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); for (auto& element : elements) { - JS_Call(ctx, pushMethod, array, 1, &element->instanceObject); + JS_Call(ctx, pushMethod, array, 1, &element->jsObject); } JS_FreeValue(ctx, pushMethod); return array; } -JSValue Document::getElementsByClassName(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Document::getElementsByClassName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementsByClassName' on 'Document': 1 argument required, but only 0 present."); } @@ -264,7 +278,7 @@ JSValue Document::getElementsByClassName(QjsContext* ctx, JSValue this_val, int std::string className = jsValueToStdString(ctx, argv[0]); std::vector elements; - traverseNode(document->m_documentElement, [ctx, className, &elements](NodeInstance* node) { + traverseNode(document, [ctx, className, &elements](NodeInstance* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { auto element = reinterpret_cast(node); if (element->classNames()->containsAll(className)) { @@ -279,41 +293,171 @@ JSValue Document::getElementsByClassName(QjsContext* ctx, JSValue this_val, int JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); for (auto& element : elements) { - JS_Call(ctx, pushMethod, array, 1, &element->instanceObject); + JS_Call(ctx, pushMethod, array, 1, &element->jsObject); } JS_FreeValue(ctx, pushMethod); return array; } -PROP_GETTER(DocumentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, "#document"); +void Document::defineElement(const std::string& tagName, Element* constructor) { + elementConstructorMap[tagName] = constructor; } -PROP_SETTER(DocumentInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + +JSValue Document::getElementConstructor(ExecutionContext* context, const std::string& tagName) { + if (elementConstructorMap.count(tagName) > 0) + return elementConstructorMap[tagName]->jsObject; + return Element::instance(context)->jsObject; +} + +bool Document::isCustomElement(const std::string& tagName) { + return elementConstructorMap.count(tagName) > 0; +} + +IMPL_PROPERTY_GETTER(Document, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + return JS_NewString(ctx, "#document"); } -PROP_GETTER(DocumentInstance, all)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Document, all)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); auto all = new AllCollection(document->m_context); - traverseNode(document->m_documentElement, [&all](NodeInstance* node) { + traverseNode(document, [&all](NodeInstance* node) { all->internalAdd(node, nullptr); return false; }); return all->jsObject; } -PROP_SETTER(DocumentInstance, all)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + +// document.documentElement +IMPL_PROPERTY_GETTER(Document, documentElement)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + ElementInstance* documentElement = document->getDocumentElement(); + return documentElement == nullptr ? JS_NULL : documentElement->jsObject; +} + +// document.head +IMPL_PROPERTY_GETTER(Document, head)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + ElementInstance* documentElement = document->getDocumentElement(); + int32_t len = arrayGetLength(ctx, documentElement->childNodes); + JSValue head = JS_NULL; + if (documentElement != nullptr) { + for (int i = 0; i < len; i++) { + JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); + auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); + if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { + auto* elementInstance = static_cast(nodeInstance); + if (elementInstance->tagName() == "HEAD") { + head = elementInstance->jsObject; + break; + } + } + JS_FreeValue(ctx, v); + } + + JS_FreeValue(ctx, documentElement->jsObject); + } + + return head; +} + +// document.body: https://html.spec.whatwg.org/multipage/dom.html#dom-document-body-dev +IMPL_PROPERTY_GETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + ElementInstance* documentElement = document->getDocumentElement(); + JSValue body = JS_NULL; + + if (documentElement != nullptr) { + int32_t len = arrayGetLength(ctx, documentElement->childNodes); + // The body element of a document is the first of the html documentElement's children that + // is either a body element or a frameset element, or null if there is no such element. + for (int i = 0; i < len; i++) { + JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); + auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); + if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { + auto* elementInstance = static_cast(nodeInstance); + if (elementInstance->tagName() == "BODY") { + body = elementInstance->jsObject; + break; + } + } + JS_FreeValue(ctx, v); + } + JS_FreeValue(ctx, documentElement->jsObject); + } + return body; +} + +// The body property is settable, setting a new body on a document will effectively remove all +// the current children of the existing element. +IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + ElementInstance* documentElement = document->getDocumentElement(); + // If there is no document element, throw a Exception. + if (documentElement == nullptr) { + return JS_ThrowInternalError(ctx, "No document element exists"); + } + JSValue result = JS_NULL; + JSValue newBody = argv[0]; + // If the body element is not null, then replace the body element with the new value within the body element's parent and return. + if (JS_IsInstanceOf(ctx, newBody, Element::instance(document->m_context)->jsObject)) { + auto* newElementInstance = static_cast(JS_GetOpaque(newBody, Element::classId())); + // If the new value is not a body element, then throw a Exception. + if (newElementInstance->tagName() == "BODY") { + JSValue oldBody = JS_GetPropertyStr(ctx, document->jsObject, "body"); + if (JS_VALUE_GET_PTR(oldBody) != JS_VALUE_GET_PTR(newBody)) { + // If the new value is the same as the body element. + if (JS_IsNull(oldBody)) { + // The old body element is null, but there's a document element. Append the new value to the document element. + documentElement->internalAppendChild(newElementInstance); + } else { + // Otherwise, replace the body element with the new value within the body element's parent. + auto* oldElementInstance = static_cast(JS_GetOpaque(oldBody, Element::classId())); + documentElement->internalReplaceChild(newElementInstance, oldElementInstance); + } + } + JS_FreeValue(ctx, oldBody); + result = JS_DupValue(ctx, newBody); + } else { + result = JS_ThrowTypeError(ctx, "The new body element must be a 'BODY' element"); + } + } else { + result = JS_ThrowTypeError(ctx, "The 1st argument provided is either null, or an invalid HTMLElement"); + } + + JS_FreeValue(ctx, documentElement->jsObject); + return result; +} + +// document.children +IMPL_PROPERTY_GETTER(Document, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + JSValue array = JS_NewArray(ctx); + JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); + + int32_t len = arrayGetLength(ctx, document->childNodes); + for (int i = 0; i < len; i++) { + JSValue v = JS_GetPropertyUint32(ctx, document->childNodes, i); + auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); + if (instance->nodeType == NodeType::ELEMENT_NODE) { + JSValue arguments[] = {v}; + JS_Call(ctx, pushMethod, array, 1, arguments); + } + JS_FreeValue(ctx, v); + } + + JS_FreeValue(ctx, pushMethod); + return array; } -PROP_GETTER(DocumentInstance, cookie)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Document, cookie)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string cookie = document->m_cookie->getCookie(); return JS_NewString(ctx, cookie.c_str()); } -PROP_SETTER(DocumentInstance, cookie)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Document, cookie)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string value = jsValueToStdString(ctx, argv[0]); document->m_cookie->setCookie(value); @@ -372,59 +516,87 @@ void DocumentCookie::setCookie(std::string& cookieStr) { cookiePairs[key] = value; } -DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document, NodeType::DOCUMENT_NODE, this, Document::classId(), "document") { +DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document, NodeType::DOCUMENT_NODE, Document::classId(), "document") { + m_context->m_document = this; + m_document = this; m_cookie = std::make_unique(); - m_instanceMap[Document::instance(m_context)] = this; m_eventTargetId = DOCUMENT_TARGET_ID; - JSAtom htmlTagName = JS_NewAtom(m_ctx, "HTML"); - JSValue htmlTagValue = JS_AtomToValue(m_ctx, htmlTagName); - JSValue htmlArgs[] = {htmlTagValue}; - JSValue documentElementValue = JS_CallConstructor(m_ctx, Element::instance(m_context)->classObject, 1, htmlArgs); - m_documentElement = static_cast(JS_GetOpaque(documentElementValue, Element::classId())); - m_documentElement->parentNode = JS_DupValue(m_ctx, instanceObject); - - JSAtom documentElementTag = JS_NewAtom(m_ctx, "documentElement"); - JS_SetProperty(m_ctx, instanceObject, documentElementTag, documentElementValue); - - JS_FreeAtom(m_ctx, documentElementTag); - JS_FreeAtom(m_ctx, htmlTagName); - JS_FreeValue(m_ctx, htmlTagValue); + m_scriptAnimationController = makeGarbageCollected()->initialize(m_ctx, &ScriptAnimationController::classId); #if FLUTTER_BACKEND - getDartMethod()->initHTML(m_context->getContextId(), m_documentElement->nativeEventTarget); getDartMethod()->initDocument(m_context->getContextId(), nativeEventTarget); #endif } -std::unordered_map DocumentInstance::m_instanceMap{}; - -DocumentInstance::~DocumentInstance() {} +DocumentInstance::~DocumentInstance() { + // Atom string should keep alive in memory to make sure same string have the corresponding id. + // Only freed after document finalized. + for (auto& entry : m_elementMapById) { + JS_FreeAtomRT(m_context->runtime(), entry.first); + // Note: someone may be curious why there are no JS_FreeValueRT() call in this finalize callbacks. + // m_elementMapById's value are all elements, which are JavaScript objects. Will be freed by GC at marking phase. + } +} void DocumentInstance::removeElementById(JSAtom id, ElementInstance* element) { if (m_elementMapById.count(id) > 0) { auto& list = m_elementMapById[id]; - JS_FreeValue(m_ctx, element->instanceObject); - list_del(&element->documentLink.link); - list.erase(std::find(list.begin(), list.end(), element)); + auto idx = std::find(list.begin(), list.end(), element); + assert_m(idx != list.end(), "Element should exist in idMap"); + list.erase(idx); + JS_FreeValue(m_ctx, element->jsObject); } } void DocumentInstance::addElementById(JSAtom id, ElementInstance* element) { if (m_elementMapById.count(id) == 0) { m_elementMapById[id] = std::vector(); + JS_DupAtom(m_ctx, id); } auto& list = m_elementMapById[id]; auto it = std::find(list.begin(), list.end(), element); if (it == list.end()) { - JS_DupValue(m_ctx, element->instanceObject); - list_add_tail(&element->documentLink.link, &m_context->document_job_list); + JS_DupValue(m_ctx, element->jsObject); m_elementMapById[id].emplace_back(element); } } -ElementInstance* DocumentInstance::documentElement() { - return m_documentElement; +ElementInstance* DocumentInstance::getDocumentElement() { + int32_t len = arrayGetLength(m_ctx, childNodes); + + for (int i = 0; i < len; i++) { + JSValue v = JS_GetPropertyUint32(m_ctx, childNodes, i); + auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); + if (instance->nodeType == NodeType::ELEMENT_NODE) { + return static_cast(instance); + } + JS_FreeValue(m_ctx, v); + } + + return nullptr; +} + +int32_t DocumentInstance::requestAnimationFrame(FrameCallback* frameCallback) { + return m_scriptAnimationController->registerFrameCallback(frameCallback); +} + +void DocumentInstance::cancelAnimationFrame(uint32_t callbackId) { + m_scriptAnimationController->cancelFrameCallback(callbackId); +} + +void DocumentInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + NodeInstance::trace(rt, val, mark_func); + // Trace scriptAnimationController + if (m_scriptAnimationController != nullptr) { + JS_MarkValue(rt, m_scriptAnimationController->toQuickJS(), mark_func); + } + // Trace elementByIdMaps + for (auto& entry : m_elementMapById) { + for (auto& value : entry.second) { + JS_MarkValue(rt, value->jsObject, mark_func); + } + } } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index 41978dc579..161ec71502 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -7,11 +7,13 @@ #define KRAKENBRIDGE_DOCUMENT_H #include "element.h" +#include "frame_request_callback_collection.h" #include "node.h" +#include "script_animation_controller.h" namespace kraken::binding::qjs { -void bindDocument(std::unique_ptr& context); +void bindDocument(std::unique_ptr& context); using TraverseHandler = std::function; @@ -22,36 +24,52 @@ class Document : public Node { static JSClassID kDocumentClassID; Document() = delete; - Document(JSContext* context); + Document(ExecutionContext* context); static JSClassID classId(); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; OBJECT_INSTANCE(Document); - static JSValue createEvent(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createElement(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createTextNode(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createDocumentFragment(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createComment(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementById(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByTagName(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByClassName(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue createEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue createElement(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue createTextNode(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue createDocumentFragment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue createComment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getElementById(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getElementsByTagName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getElementsByClassName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + + JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); + bool isCustomElement(const std::string& tagName); private: - ObjectFunction m_createEvent{m_context, m_prototypeObject, "createEvent", createEvent, 1}; - ObjectFunction m_createElement{m_context, m_prototypeObject, "createElement", createElement, 1}; - ObjectFunction m_createDocumentFragment{m_context, m_prototypeObject, "createDocumentFragment", createDocumentFragment, 0}; - ObjectFunction m_createTextNode{m_context, m_prototypeObject, "createTextNode", createTextNode, 1}; - ObjectFunction m_createComment{m_context, m_prototypeObject, "createComment", createComment, 1}; - ObjectFunction m_getElementById{m_context, m_prototypeObject, "getElementById", getElementById, 1}; - ObjectFunction m_getElementsByTagName{m_context, m_prototypeObject, "getElementsByTagName", getElementsByTagName, 1}; - ObjectFunction m_getElementsByClassName{m_context, m_prototypeObject, "getElementsByClassName", getElementsByClassName, 1}; + DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); + DEFINE_PROTOTYPE_READONLY_PROPERTY(all); + DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); + DEFINE_PROTOTYPE_READONLY_PROPERTY(children); + DEFINE_PROTOTYPE_READONLY_PROPERTY(head); + + DEFINE_PROTOTYPE_PROPERTY(cookie); + DEFINE_PROTOTYPE_PROPERTY(body); + + DEFINE_PROTOTYPE_FUNCTION(createEvent, 1); + DEFINE_PROTOTYPE_FUNCTION(createElement, 1); + DEFINE_PROTOTYPE_FUNCTION(createDocumentFragment, 0); + DEFINE_PROTOTYPE_FUNCTION(createTextNode, 1); + DEFINE_PROTOTYPE_FUNCTION(createComment, 1); + DEFINE_PROTOTYPE_FUNCTION(getElementById, 1); + DEFINE_PROTOTYPE_FUNCTION(getElementsByTagName, 1); + DEFINE_PROTOTYPE_FUNCTION(getElementsByClassName, 1); + + void defineElement(const std::string& tagName, Element* constructor); + friend DocumentInstance; bool event_registered{false}; bool document_registered{false}; + std::unordered_map elementConstructorMap; }; class DocumentCookie { @@ -70,27 +88,24 @@ class DocumentInstance : public NodeInstance { DocumentInstance() = delete; explicit DocumentInstance(Document* document); ~DocumentInstance(); - static std::unordered_map m_instanceMap; - ElementInstance* documentElement(); - static DocumentInstance* instance(Document* document) { - if (m_instanceMap.count(document) == 0) { - m_instanceMap[document] = new DocumentInstance(document); - } - return m_instanceMap[document]; - } - private: - DEFINE_HOST_CLASS_PROPERTY(3, nodeName, all, cookie); + int32_t requestAnimationFrame(FrameCallback* frameCallback); + void cancelAnimationFrame(uint32_t callbackId); + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + private: void removeElementById(JSAtom id, ElementInstance* element); void addElementById(JSAtom id, ElementInstance* element); + ElementInstance* getDocumentElement(); std::unordered_map> m_elementMapById; ElementInstance* m_documentElement{nullptr}; std::unique_ptr m_cookie; + ScriptAnimationController* m_scriptAnimationController; + friend Document; friend ElementInstance; - friend JSContext; + friend ExecutionContext; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc index 03f1a1f8b4..1ebe5a0754 100644 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ b/bridge/bindings/qjs/dom/document_fragment.cc @@ -9,16 +9,16 @@ namespace kraken::binding::qjs { -void bindDocumentFragment(std::unique_ptr& context) { +void bindDocumentFragment(std::unique_ptr& context) { auto* constructor = DocumentFragment::instance(context.get()); - context->defineGlobalProperty("DocumentFragment", constructor->classObject); + context->defineGlobalProperty("DocumentFragment", constructor->jsObject); } std::once_flag kDocumentFragmentFlag; JSClassID DocumentFragment::kDocumentFragmentID{0}; -DocumentFragment::DocumentFragment(JSContext* context) : Node(context) { +DocumentFragment::DocumentFragment(ExecutionContext* context) : Node(context) { std::call_once(kDocumentFragmentFlag, []() { JS_NewClassID(&kDocumentFragmentID); }); JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); } @@ -27,13 +27,12 @@ JSClassID DocumentFragment::classId() { return kDocumentFragmentID; } -JSValue DocumentFragment::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new DocumentFragmentInstance(this))->instanceObject; +JSValue DocumentFragment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { + return (new DocumentFragmentInstance(this))->jsObject; } -DocumentFragmentInstance::DocumentFragmentInstance(DocumentFragment* fragment) - : NodeInstance(fragment, NodeType::DOCUMENT_FRAGMENT_NODE, DocumentInstance::instance(Document::instance(fragment->context())), DocumentFragment::classId(), "DocumentFragment") { +DocumentFragmentInstance::DocumentFragmentInstance(DocumentFragment* fragment) : NodeInstance(fragment, NodeType::DOCUMENT_FRAGMENT_NODE, DocumentFragment::classId(), "DocumentFragment") { setNodeFlag(DocumentFragmentInstance::NodeFlag::IsDocumentFragment); - foundation::UICommandBuffer::instance(m_contextId)->addCommand(m_eventTargetId, UICommand::createDocumentFragment, nativeEventTarget); + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createDocumentFragment, nativeEventTarget); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h index 8f9687d21e..95f74303d5 100644 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ b/bridge/bindings/qjs/dom/document_fragment.h @@ -10,7 +10,7 @@ namespace kraken::binding::qjs { -void bindDocumentFragment(std::unique_ptr& context); +void bindDocumentFragment(std::unique_ptr& context); class DocumentFragment : public Node { public: @@ -18,9 +18,9 @@ class DocumentFragment : public Node { static JSClassID classId(); DocumentFragment() = delete; - explicit DocumentFragment(JSContext* context); + explicit DocumentFragment(ExecutionContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; OBJECT_INSTANCE(DocumentFragment); }; diff --git a/bridge/bindings/qjs/dom/document_test.cc b/bridge/bindings/qjs/dom/document_test.cc index 1d1acf9638..648c3f843d 100644 --- a/bridge/bindings/qjs/dom/document_test.cc +++ b/bridge/bindings/qjs/dom/document_test.cc @@ -3,18 +3,19 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "event_target.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(Document, createTextNode) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "
"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -27,7 +28,6 @@ TEST(Document, createTextNode) { "div.appendChild(text);" "console.log(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -35,18 +35,43 @@ TEST(Document, createTextNode) { TEST(Document, instanceofNode) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } + +TEST(Document, createElementShouldWorkWithMultipleContext) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + kraken::KrakenPage* bridge1; + + const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; + + { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto& context = bridge->getContext(); + bridge->evaluateScript(code, strlen(code), "vm://", 0); + bridge1 = bridge.release(); + } + + { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto& context = bridge->getContext(); + const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + } + + bridge1->evaluateScript(code, strlen(code), "vm://", 0); + + delete bridge1; +} diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index 364befb7c2..2a60786c66 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -8,20 +8,26 @@ #include "bindings/qjs/html_parser.h" #include "dart_methods.h" #include "document.h" +#include "elements/template_element.h" #include "text_node.h" +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + namespace kraken::binding::qjs { std::once_flag kElementInitOnceFlag; -void bindElement(std::unique_ptr& context) { +void bindElement(std::unique_ptr& context) { auto* constructor = Element::instance(context.get()); - context->defineGlobalProperty("Element", constructor->classObject); - context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), constructor->classObject)); + // auto* domRectConstructor = BoundingClientRect + context->defineGlobalProperty("Element", constructor->jsObject); + context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), constructor->jsObject)); } -bool isJavaScriptExtensionElementInstance(JSContext* context, JSValue instance) { - if (JS_IsInstanceOf(context->ctx(), instance, Element::instance(context)->classObject)) { +bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance) { + if (JS_IsInstanceOf(context->ctx(), instance, Element::instance(context)->jsObject)) { auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId())); std::string tagName = elementInstance->getRegisteredTagName(); @@ -40,7 +46,7 @@ bool isJavaScriptExtensionElementInstance(JSContext* context, JSValue instance) JSClassID Element::kElementClassId{0}; -Element::Element(JSContext* context) : Node(context, "Element") { +Element::Element(ExecutionContext* context) : Node(context, "Element") { std::call_once(kElementInitOnceFlag, []() { JS_NewClassID(&kElementClassId); }); JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); } @@ -49,23 +55,18 @@ JSClassID Element::classId() { return kElementClassId; } -JSAtom ElementAttributes::getAttribute(const std::string& name) { +JSClassID ElementAttributes::classId{0}; +JSValue ElementAttributes::getAttribute(const std::string& name) { bool numberIndex = isNumberIndex(name); if (numberIndex) { - return JS_ATOM_NULL; + return JS_NULL; } - return m_attributes[name]; -} - -ElementAttributes::~ElementAttributes() { - for (auto& attr : m_attributes) { - JS_FreeAtom(m_ctx, attr.second); - } + return JS_DupValue(m_ctx, m_attributes[name]); } -JSValue ElementAttributes::setAttribute(const std::string& name, JSAtom atom) { +JSValue ElementAttributes::setAttribute(const std::string& name, JSValue value) { bool numberIndex = isNumberIndex(name); if (numberIndex) { @@ -73,11 +74,16 @@ JSValue ElementAttributes::setAttribute(const std::string& name, JSAtom atom) { } if (name == "class") { - std::string classNameString = jsAtomToStdString(m_ctx, atom); + std::string classNameString = jsValueToStdString(m_ctx, value); m_className->set(classNameString); } - m_attributes[name] = JS_DupAtom(m_ctx, atom); + // If attribute exists, should free the previous value. + if (m_attributes.count(name) > 0) { + JS_FreeValue(m_ctx, m_attributes[name]); + } + + m_attributes[name] = JS_DupValue(m_ctx, value); return JS_NULL; } @@ -93,14 +99,14 @@ bool ElementAttributes::hasAttribute(std::string& name) { } void ElementAttributes::removeAttribute(std::string& name) { - JSAtom value = m_attributes[name]; - JS_FreeAtom(m_ctx, value); + JSValue value = m_attributes[name]; + JS_FreeValue(m_ctx, value); m_attributes.erase(name); } void ElementAttributes::copyWith(ElementAttributes* attributes) { for (auto& attr : attributes->m_attributes) { - m_attributes[attr.first] = JS_DupAtom(m_ctx, attr.second); + m_attributes[attr.first] = JS_DupValue(m_ctx, attr.second); } } @@ -113,7 +119,7 @@ std::string ElementAttributes::toString() { for (auto& attr : m_attributes) { s += attr.first + "="; - const char* pstr = JS_AtomToCString(m_ctx, attr.second); + const char* pstr = JS_ToCString(m_ctx, attr.second); s += "\"" + std::string(pstr) + "\""; JS_FreeCString(m_ctx, pstr); } @@ -121,7 +127,18 @@ std::string ElementAttributes::toString() { return s; } -JSValue Element::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +void ElementAttributes::dispose() const { + for (auto& attr : m_attributes) { + JS_FreeValueRT(m_runtime, attr.second); + } +} +void ElementAttributes::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + for (auto& attr : m_attributes) { + JS_MarkValue(rt, attr.second, mark_func); + } +} + +JSValue Element::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) return JS_ThrowTypeError(ctx, "Illegal constructor"); JSValue tagName = argv[0]; @@ -130,31 +147,25 @@ JSValue Element::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue return JS_ThrowTypeError(ctx, "Illegal constructor"); } + auto* context = static_cast(JS_GetContextOpaque(ctx)); std::string name = jsValueToStdString(ctx, tagName); - if (elementConstructorMap.count(name) > 0) { - return JS_CallConstructor(ctx, elementConstructorMap[name]->classObject, argc, argv); - } - - ElementInstance* element; - if (name == "HTML") { - element = new ElementInstance(this, name, false); - element->m_eventTargetId = HTML_TARGET_ID; - } else { - // Fallback to default Element class - element = new ElementInstance(this, name, true); + auto* Document = Document::instance(context); + if (Document->isCustomElement(name)) { + return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); } - return element->instanceObject; + auto* element = new ElementInstance(this, name, true); + return element->jsObject; } -JSValue Element::getBoundingClientRect(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::getBoundingClientRect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); getDartMethod()->flushUICommand(); return element->callNativeMethods("getBoundingClientRect", 0, nullptr); } -JSValue Element::hasAttribute(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'hasAttribute' on 'Element': 1 argument required, but only 0 present"); } @@ -177,15 +188,13 @@ JSValue Element::hasAttribute(QjsContext* ctx, JSValue this_val, int argc, JSVal return result; } -JSValue Element::setAttribute(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 2) { return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': 2 arguments required, but only %d present", argc); } JSValue nameValue = argv[0]; - JSValue attributeValue = argv[1]; - JSValue attributeString = JS_ToString(ctx, attributeValue); - JSAtom attributeAtom = JS_ValueToAtom(ctx, attributeString); + JSValue attributeValue = JS_ToString(ctx, argv[1]); if (!JS_IsString(nameValue)) { return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); @@ -198,31 +207,30 @@ JSValue Element::setAttribute(QjsContext* ctx, JSValue this_val, int argc, JSVal auto* attributes = element->m_attributes; if (attributes->hasAttribute(name)) { - JSAtom oldAtom = attributes->getAttribute(name); - JSValue exception = attributes->setAttribute(name, attributeAtom); + JSValue oldAttribute = attributes->getAttribute(name); + JSValue exception = attributes->setAttribute(name, attributeValue); if (JS_IsException(exception)) return exception; - element->_didModifyAttribute(name, oldAtom, attributeAtom); - JS_FreeAtom(ctx, oldAtom); + element->_didModifyAttribute(name, oldAttribute, attributeValue); + JS_FreeValue(ctx, oldAttribute); } else { - JSValue exception = attributes->setAttribute(name, attributeAtom); + JSValue exception = attributes->setAttribute(name, attributeValue); if (JS_IsException(exception)) return exception; - element->_didModifyAttribute(name, JS_ATOM_NULL, attributeAtom); + element->_didModifyAttribute(name, JS_NULL, attributeValue); } - NativeString* args_01 = stringToNativeString(name); - NativeString* args_02 = jsValueToNativeString(ctx, attributeString); + std::unique_ptr args_01 = stringToNativeString(name); + std::unique_ptr args_02 = jsValueToNativeString(ctx, attributeValue); - ::foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); - JS_FreeValue(ctx, attributeString); - JS_FreeAtom(ctx, attributeAtom); + JS_FreeValue(ctx, attributeValue); return JS_NULL; } -JSValue Element::getAttribute(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'getAttribute' on 'Element': 1 argument required, but only 0 present"); } @@ -239,13 +247,13 @@ JSValue Element::getAttribute(QjsContext* ctx, JSValue this_val, int argc, JSVal auto* attributes = element->m_attributes; if (attributes->hasAttribute(name)) { - return JS_AtomToValue(ctx, attributes->getAttribute(name)); + return attributes->getAttribute(name); } return JS_NULL; } -JSValue Element::removeAttribute(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'removeAttribute' on 'Element': 1 argument required, but only 0 present"); } @@ -261,18 +269,19 @@ JSValue Element::removeAttribute(QjsContext* ctx, JSValue this_val, int argc, JS auto* attributes = element->m_attributes; if (attributes->hasAttribute(name)) { - JSAtom id = attributes->getAttribute(name); + JSValue targetValue = attributes->getAttribute(name); element->m_attributes->removeAttribute(name); - element->_didModifyAttribute(name, id, JS_ATOM_NULL); + element->_didModifyAttribute(name, targetValue, JS_NULL); + JS_FreeValue(ctx, targetValue); - NativeString* args_01 = stringToNativeString(name); - ::foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::removeProperty, *args_01, nullptr); + std::unique_ptr args_01 = stringToNativeString(name); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeProperty, *args_01, nullptr); } return JS_NULL; } -JSValue Element::toBlob(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { double devicePixelRatio = 1.0; if (argc > 0) { @@ -297,7 +306,7 @@ JSValue Element::toBlob(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar return; auto promiseContext = static_cast(callbackContext); - QjsContext* ctx = promiseContext->context->ctx(); + JSContext* ctx = promiseContext->context->ctx(); if (error == nullptr) { std::vector vec(bytes, bytes + length); JSValue arrayBuffer = JS_NewArrayBuffer(ctx, bytes, length, nullptr, nullptr, false); @@ -305,7 +314,7 @@ JSValue Element::toBlob(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar JSValue argumentsArray = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, argumentsArray, "push"); JS_Call(ctx, pushMethod, argumentsArray, 1, &arrayBuffer); - JSValue blobValue = JS_CallConstructor(ctx, constructor->classObject, 1, &argumentsArray); + JSValue blobValue = JS_CallConstructor(ctx, constructor->jsObject, 1, &argumentsArray); if (JS_IsException(blobValue)) { promiseContext->context->handleException(&blobValue); @@ -348,204 +357,159 @@ JSValue Element::toBlob(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar return promise; } -JSValue Element::click(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +#if FLUTTER_BACKEND getDartMethod()->flushUICommand(); auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->callNativeMethods("click", 0, nullptr); +#elif UNIT_TEST + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + TEST_dispatchEvent(element->m_contextId, element, "click"); + return JS_UNDEFINED; +#else + return JS_UNDEFINED; +#endif } -JSValue Element::scroll(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::scroll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("scroll", 2, arguments); } -JSValue Element::scrollBy(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Element::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("scrollBy", 2, arguments); } -std::unordered_map Element::elementConstructorMap{}; - -void Element::defineElement(const std::string& tagName, Element* constructor) { - elementConstructorMap[tagName] = constructor; -} - -JSValue Element::getConstructor(JSContext* context, const std::string& tagName) { - if (elementConstructorMap.count(tagName) > 0) - return elementConstructorMap[tagName]->classObject; - return Element::instance(context)->classObject; -} - -PROP_GETTER(ElementInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string tagName = element->tagName(); return JS_NewString(ctx, tagName.c_str()); } -PROP_SETTER(ElementInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} - -PROP_GETTER(ElementInstance, tagName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, tagName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string tagName = element->tagName(); return JS_NewString(ctx, tagName.c_str()); } -PROP_SETTER(ElementInstance, tagName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} - -PROP_GETTER(ElementInstance, className)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSAtom valueAtom = element->m_attributes->getAttribute("class"); - return JS_AtomToString(ctx, valueAtom); + return element->m_attributes->getAttribute("class"); } -PROP_SETTER(ElementInstance, className)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSAtom atom = JS_ValueToAtom(ctx, argv[0]); - element->m_attributes->setAttribute("class", atom); - NativeString* args_01 = stringToNativeString("class"); - NativeString* args_02 = jsValueToNativeString(ctx, argv[0]); - ::foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); - JS_FreeAtom(ctx, atom); + element->m_attributes->setAttribute("class", argv[0]); + std::unique_ptr args_01 = stringToNativeString("class"); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL; } enum class ViewModuleProperty { offsetTop, offsetLeft, offsetWidth, offsetHeight, clientWidth, clientHeight, clientTop, clientLeft, scrollTop, scrollLeft, scrollHeight, scrollWidth }; -PROP_GETTER(ElementInstance, offsetLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, offsetLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, offsetTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, offsetTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, offsetTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, offsetWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, offsetWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, offsetWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, offsetHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, offsetHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, offsetHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, clientWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, clientWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, clientWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, clientHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, clientHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, clientHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, clientTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, clientTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, clientTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, clientLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, clientLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, clientLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, scrollTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, scrollTop)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } -PROP_GETTER(ElementInstance, scrollLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, scrollLeft)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } -PROP_GETTER(ElementInstance, scrollHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, scrollHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, scrollHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, scrollWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, scrollWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } -PROP_SETTER(ElementInstance, scrollWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} // Definition for firstElementChild -PROP_GETTER(ElementInstance, firstElementChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, firstElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); int32_t len = arrayGetLength(ctx, element->childNodes); @@ -553,19 +517,16 @@ PROP_GETTER(ElementInstance, firstElementChild)(QjsContext* ctx, JSValue this_va JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->instanceObject; + return instance->jsObject; } JS_FreeValue(ctx, v); } return JS_NULL; } -PROP_SETTER(ElementInstance, firstElementChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} // Definition for lastElementChild -PROP_GETTER(ElementInstance, lastElementChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, lastElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); int32_t len = arrayGetLength(ctx, element->childNodes); @@ -573,18 +534,15 @@ PROP_GETTER(ElementInstance, lastElementChild)(QjsContext* ctx, JSValue this_val JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->instanceObject; + return instance->jsObject; } JS_FreeValue(ctx, v); } return JS_NULL; } -PROP_SETTER(ElementInstance, lastElementChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ElementInstance, children)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); JSValue array = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); @@ -605,27 +563,36 @@ PROP_GETTER(ElementInstance, children)(QjsContext* ctx, JSValue this_val, int ar return array; } -PROP_SETTER(ElementInstance, children)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + +IMPL_PROPERTY_GETTER(Element, attributes)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + return JS_DupValue(ctx, element->m_attributes->toQuickJS()); } -PROP_GETTER(ElementInstance, innerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return JS_NewString(ctx, element->innerHTML().c_str()); } -PROP_SETTER(ElementInstance, innerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); const char* chtml = JS_ToCString(ctx, argv[0]); - HTMLParser::parseHTML(chtml, strlen(chtml), element); + + if (element->hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { + auto* templateElement = static_cast(element); + HTMLParser::parseHTML(chtml, strlen(chtml), templateElement->content()); + } else { + HTMLParser::parseHTML(chtml, strlen(chtml), element); + } + JS_FreeCString(ctx, chtml); return JS_NULL; } -PROP_GETTER(ElementInstance, outerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return JS_NewString(ctx, element->outerHTML().c_str()); } -PROP_SETTER(ElementInstance, outerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NULL; } @@ -665,7 +632,7 @@ JSValue ElementInstance::internalGetTextContent() { void ElementInstance::internalSetTextContent(JSValue content) { internalClearChild(); - JSValue textNodeValue = JS_CallConstructor(m_ctx, TextNode::instance(m_context)->classObject, 1, &content); + JSValue textNodeValue = JS_CallConstructor(m_ctx, TextNode::instance(m_context)->jsObject, 1, &content); auto* textNodeInstance = static_cast(JS_GetOpaque(textNodeValue, TextNode::classId())); internalAppendChild(textNodeInstance); JS_FreeValue(m_ctx, textNodeValue); @@ -761,14 +728,21 @@ std::string ElementInstance::outerHTML() { std::string ElementInstance::innerHTML() { std::string s; + + // If Element is TemplateElement, the innerHTML content is the content of documentFragment. + NodeInstance* parent = this; + if (hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { + parent = static_cast(this)->content(); + } + // Children toString - int32_t childLen = arrayGetLength(m_ctx, childNodes); + int32_t childLen = arrayGetLength(m_ctx, parent->childNodes); if (childLen == 0) return s; for (int i = 0; i < childLen; i++) { - JSValue c = JS_GetPropertyUint32(m_ctx, childNodes, i); + JSValue c = JS_GetPropertyUint32(m_ctx, parent->childNodes, i); auto* node = static_cast(JS_GetOpaque(c, Node::classId(c))); if (node->nodeType == NodeType::ELEMENT_NODE) { s += reinterpret_cast(node)->outerHTML(); @@ -796,10 +770,13 @@ void ElementInstance::_notifyNodeRemoved(NodeInstance* insertionNode) { } void ElementInstance::_notifyChildRemoved() { - std::string id = "id"; - if (m_attributes->hasAttribute(id)) { - JSAtom v = m_attributes->getAttribute(id); - document()->removeElementById(v, this); + std::string prop = "id"; + if (m_attributes->hasAttribute(prop)) { + JSValue idValue = m_attributes->getAttribute(prop); + JSAtom id = JS_ValueToAtom(m_ctx, idValue); + document()->removeElementById(id, this); + JS_FreeValue(m_ctx, idValue); + JS_FreeAtom(m_ctx, id); } } @@ -818,46 +795,63 @@ void ElementInstance::_notifyNodeInsert(NodeInstance* insertNode) { } void ElementInstance::_notifyChildInsert() { - std::string idKey = "id"; - if (m_attributes->hasAttribute(idKey)) { - JSAtom v = m_attributes->getAttribute(idKey); - document()->addElementById(v, this); + std::string prop = "id"; + if (m_attributes->hasAttribute(prop)) { + JSValue idValue = m_attributes->getAttribute(prop); + JSAtom id = JS_ValueToAtom(m_ctx, idValue); + document()->addElementById(id, this); + JS_FreeValue(m_ctx, idValue); + JS_FreeAtom(m_ctx, id); } } -void ElementInstance::_didModifyAttribute(std::string& name, JSAtom oldId, JSAtom newId) { +void ElementInstance::_didModifyAttribute(std::string& name, JSValue oldId, JSValue newId) { if (name == "id") { _beforeUpdateId(oldId, newId); } } -void ElementInstance::_beforeUpdateId(JSAtom oldId, JSAtom newId) { +void ElementInstance::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { + JSAtom oldId = JS_ValueToAtom(m_ctx, oldIdValue); + JSAtom newId = JS_ValueToAtom(m_ctx, newIdValue); + if (oldId == newId) { + JS_FreeAtom(m_ctx, oldId); + JS_FreeAtom(m_ctx, newId); return; } - if (oldId != JS_ATOM_NULL) { + if (!JS_IsNull(oldIdValue)) { document()->removeElementById(oldId, this); } - if (newId != JS_ATOM_NULL) { + if (!JS_IsNull(newIdValue)) { document()->addElementById(newId, this); } + + JS_FreeAtom(m_ctx, oldId); + JS_FreeAtom(m_ctx, newId); +} + +void ElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + if (m_attributes != nullptr) { + JS_MarkValue(rt, m_attributes->toQuickJS(), mark_func); + } + NodeInstance::trace(rt, val, mark_func); } ElementInstance::ElementInstance(Element* element, std::string tagName, bool shouldAddUICommand) - : m_tagName(tagName), NodeInstance(element, NodeType::ELEMENT_NODE, DocumentInstance::instance(Document::instance(element->m_context)), Element::classId(), exoticMethods, "Element") { - m_attributes = new ElementAttributes(m_context); - JSValue arguments[] = {instanceObject}; - JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->classObject, 1, arguments); + : m_tagName(tagName), NodeInstance(element, NodeType::ELEMENT_NODE, Element::classId(), exoticMethods, "Element") { + m_attributes = makeGarbageCollected()->initialize(m_ctx, &ElementAttributes::classId); + JSValue arguments[] = {jsObject}; + JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->jsObject, 1, arguments); m_style = static_cast(JS_GetOpaque(style, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - JS_DefinePropertyValueStr(m_ctx, instanceObject, "style", m_style->instanceObject, JS_PROP_C_W_E); - JS_DefinePropertyValueStr(m_ctx, instanceObject, "attributes", m_attributes->jsObject, JS_PROP_C_W_E); + JS_DefinePropertyValueStr(m_ctx, jsObject, "style", m_style->jsObject, JS_PROP_C_W_E); if (shouldAddUICommand) { - NativeString* args_01 = stringToNativeString(tagName); - ::foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(m_eventTargetId, UICommand::createElement, *args_01, nativeEventTarget); + std::unique_ptr args_01 = stringToNativeString(tagName); + element->m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createElement, *args_01, nativeEventTarget); } } @@ -867,72 +861,44 @@ StyleDeclarationInstance* ElementInstance::style() { return m_style; } -ElementAttributes* ElementInstance::attributes() { - return m_attributes; -} - -PROP_GETTER(BoundingClientRect, x)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, x)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->x); } -PROP_SETTER(BoundingClientRect, x)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, y)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, y)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->y); } -PROP_SETTER(BoundingClientRect, y)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->width); } -PROP_SETTER(BoundingClientRect, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->height); } -PROP_SETTER(BoundingClientRect, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, top)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, top)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->top); } -PROP_SETTER(BoundingClientRect, top)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, right)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, right)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->right); } -PROP_SETTER(BoundingClientRect, right)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, bottom)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, bottom)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->bottom); } -PROP_SETTER(BoundingClientRect, bottom)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(BoundingClientRect, left)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(BoundingClientRect, left)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->left); } -PROP_SETTER(BoundingClientRect, left)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index 40309243c0..e94295155d 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -7,13 +7,14 @@ #define KRAKENBRIDGE_ELEMENT_H #include +#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/host_object.h" #include "node.h" #include "style_declaration.h" namespace kraken::binding::qjs { -void bindElement(std::unique_ptr& context); +void bindElement(std::unique_ptr& context); class ElementInstance; @@ -46,14 +47,18 @@ class SpaceSplitString { std::vector m_szData; }; -class ElementAttributes : public HostObject { +// TODO: refactor for better W3C standard support and higher performance. +class ElementAttributes : public GarbageCollected { public: - ElementAttributes() = delete; - explicit ElementAttributes(JSContext* context) : HostObject(context, "ElementAttributes") {} - ~ElementAttributes(); + static JSClassID classId; - JSAtom getAttribute(const std::string& name); - JSValue setAttribute(const std::string& name, JSAtom value); + FORCE_INLINE const char* getHumanReadableName() const override { return "ElementAttributes"; } + + void dispose() const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + + JSValue getAttribute(const std::string& name); + JSValue setAttribute(const std::string& name, JSValue value); bool hasAttribute(std::string& name); void removeAttribute(std::string& name); void copyWith(ElementAttributes* attributes); @@ -61,50 +66,69 @@ class ElementAttributes : public HostObject { std::string toString(); private: - std::unordered_map m_attributes; + std::unordered_map m_attributes; std::shared_ptr m_className{std::make_shared("")}; }; -bool isJavaScriptExtensionElementInstance(JSContext* context, JSValue instance); +bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); class Element : public Node { public: static JSClassID kElementClassId; Element() = delete; - explicit Element(JSContext* context); + explicit Element(ExecutionContext* context); static JSClassID classId(); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSValue getBoundingClientRect(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue hasAttribute(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue setAttribute(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getAttribute(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeAttribute(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue toBlob(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue click(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scroll(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollBy(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - static void defineElement(const std::string& tagName, Element* constructor); - static JSValue getConstructor(JSContext* context, const std::string& tagName); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - static std::unordered_map elementConstructorMap; + static JSValue getBoundingClientRect(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue hasAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue setAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue removeAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue toBlob(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue click(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue scroll(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue scrollBy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); OBJECT_INSTANCE(Element); private: - ObjectFunction m_getBoundingClientRect{m_context, m_prototypeObject, "getBoundingClientRect", getBoundingClientRect, 0}; - ObjectFunction m_hasAttribute{m_context, m_prototypeObject, "hasAttribute", hasAttribute, 1}; - ObjectFunction m_setAttribute{m_context, m_prototypeObject, "setAttribute", setAttribute, 2}; - ObjectFunction m_getAttribute{m_context, m_prototypeObject, "getAttribute", getAttribute, 2}; - ObjectFunction m_removeAttribute{m_context, m_prototypeObject, "removeAttribute", removeAttribute, 1}; - ObjectFunction m_toBlob{m_context, m_prototypeObject, "toBlob", toBlob, 0}; - ObjectFunction m_click{m_context, m_prototypeObject, "click", click, 0}; - ObjectFunction m_scroll{m_context, m_prototypeObject, "scroll", scroll, 2}; + DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); + DEFINE_PROTOTYPE_READONLY_PROPERTY(tagName); + DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetLeft); + DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetTop); + DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetWidth); + DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetHeight); + DEFINE_PROTOTYPE_READONLY_PROPERTY(clientWidth); + DEFINE_PROTOTYPE_READONLY_PROPERTY(clientHeight); + DEFINE_PROTOTYPE_READONLY_PROPERTY(clientTop); + DEFINE_PROTOTYPE_READONLY_PROPERTY(clientLeft); + DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollHeight); + DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollWidth); + DEFINE_PROTOTYPE_READONLY_PROPERTY(firstElementChild); + DEFINE_PROTOTYPE_READONLY_PROPERTY(lastElementChild); + DEFINE_PROTOTYPE_READONLY_PROPERTY(children); + DEFINE_PROTOTYPE_READONLY_PROPERTY(attributes); + + DEFINE_PROTOTYPE_PROPERTY(className); + DEFINE_PROTOTYPE_PROPERTY(innerHTML); + DEFINE_PROTOTYPE_PROPERTY(outerHTML); + DEFINE_PROTOTYPE_PROPERTY(scrollTop); + DEFINE_PROTOTYPE_PROPERTY(scrollLeft); + + DEFINE_PROTOTYPE_FUNCTION(getBoundingClientRect, 0); + DEFINE_PROTOTYPE_FUNCTION(hasAttribute, 1); + DEFINE_PROTOTYPE_FUNCTION(setAttribute, 2); + DEFINE_PROTOTYPE_FUNCTION(getAttribute, 2); + DEFINE_PROTOTYPE_FUNCTION(removeAttribute, 1); + DEFINE_PROTOTYPE_FUNCTION(toBlob, 0); + DEFINE_PROTOTYPE_FUNCTION(click, 2); + DEFINE_PROTOTYPE_FUNCTION(scroll, 2); + // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. ObjectFunction m_scrollTo{m_context, m_prototypeObject, "scrollTo", scroll, 2}; - ObjectFunction m_scrollBy{m_context, m_prototypeObject, "scrollBy", scrollBy, 2}; + DEFINE_PROTOTYPE_FUNCTION(scrollBy, 2); friend ElementInstance; }; @@ -126,41 +150,21 @@ class ElementInstance : public NodeInstance { std::string outerHTML(); std::string innerHTML(); StyleDeclarationInstance* style(); - ElementAttributes* attributes(); static inline JSClassID classID(); protected: explicit ElementInstance(Element* element, std::string tagName, bool shouldAddUICommand); + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + private: - DEFINE_HOST_CLASS_PROPERTY(20, - nodeName, - tagName, - className, - offsetLeft, - offsetTop, - offsetWidth, - offsetHeight, - clientWidth, - clientHeight, - clientTop, - clientLeft, - scrollTop, - scrollLeft, - scrollHeight, - scrollWidth, - firstElementChild, - lastElementChild, - children, - innerHTML, - outerHTML); void _notifyNodeRemoved(NodeInstance* node) override; void _notifyChildRemoved(); void _notifyNodeInsert(NodeInstance* insertNode) override; void _notifyChildInsert(); - void _didModifyAttribute(std::string& name, JSAtom oldId, JSAtom newId); - void _beforeUpdateId(JSAtom oldId, JSAtom newId); + void _didModifyAttribute(std::string& name, JSValue oldId, JSValue newId); + void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); std::string m_tagName; friend Element; @@ -176,15 +180,19 @@ class ElementInstance : public NodeInstance { class BoundingClientRect : public HostObject { public: BoundingClientRect() = delete; - explicit BoundingClientRect(JSContext* context, NativeBoundingClientRect* nativeBoundingClientRect) - : HostObject(context, "BoundingClientRect"), - m_nativeBoundingClientRect(nativeBoundingClientRect){ - - }; - - DEFINE_HOST_OBJECT_PROPERTY(8, x, y, width, height, top, right, bottom, left); + explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) + : HostObject(context, "BoundingClientRect"), m_nativeBoundingClientRect(nativeBoundingClientRect){}; private: + DEFINE_READONLY_PROPERTY(x); + DEFINE_READONLY_PROPERTY(y); + DEFINE_READONLY_PROPERTY(width); + DEFINE_READONLY_PROPERTY(height); + DEFINE_READONLY_PROPERTY(top); + DEFINE_READONLY_PROPERTY(right); + DEFINE_READONLY_PROPERTY(bottom); + DEFINE_READONLY_PROPERTY(left); + NativeBoundingClientRect* m_nativeBoundingClientRect{nullptr}; }; diff --git a/bridge/bindings/qjs/dom/element_test.cc b/bridge/bindings/qjs/dom/element_test.cc index e1b2efdcee..d6e5e06e36 100644 --- a/bridge/bindings/qjs/dom/element_test.cc +++ b/bridge/bindings/qjs/dom/element_test.cc @@ -3,18 +3,19 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "event_target.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(Element, setAttribute) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "1234"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -25,19 +26,62 @@ TEST(Element, setAttribute) { "document.body.appendChild(div);" "console.log(div.getAttribute('hello'))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } +TEST(Element, getAttribute) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "helloworld"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "let string = 'helloworld';" + "let string2 = 'helloworld';" + "div.setAttribute('hello', '456');" + "div.setAttribute('hello', string);" + "let otherDiv = div.cloneNode(true);" + "otherDiv.setAttribute('hello', string2);" + "document.body.appendChild(div);" + "console.log(div.getAttribute('hello'));" + "console.log(otherDiv.getAttribute('hello'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Element, setAttributeWithHTML) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "div.innerHTML = '';"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} + TEST(Element, instanceofNode) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -46,7 +90,7 @@ TEST(Element, instanceofNode) { "let div = document.createElement('div');" "console.log(div instanceof Node)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -54,11 +98,11 @@ TEST(Element, instanceofNode) { TEST(Element, instanceofEventTarget) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -67,7 +111,36 @@ TEST(Element, instanceofEventTarget) { "let div = document.createElement('div');" "console.log(div instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Element, stringifyBoundingClientRect) { + using namespace kraken::binding::qjs; + + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "{\"x\":10,\"y\":20,\"width\":30,\"height\":40,\"top\":10,\"right\":20,\"bottom\":30,\"left\":40}"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + + NativeBoundingClientRect nativeRect{ + 10.0, 20.0, 30.0, 40.0, 10.0, 20.0, 30.0, 40.0, + }; + + auto* clientRect = new BoundingClientRect(context.get(), &nativeRect); + context->defineGlobalProperty("boundingClient", clientRect->jsObject); + + const char* code = "console.log(JSON.stringify(boundingClient))"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } diff --git a/bridge/bindings/qjs/dom/elements/image_element.cc b/bridge/bindings/qjs/dom/elements/image_element.cc index 6c237107bb..0e75310379 100644 --- a/bridge/bindings/qjs/dom/elements/image_element.cc +++ b/bridge/bindings/qjs/dom/elements/image_element.cc @@ -5,90 +5,84 @@ #include "image_element.h" #include "bindings/qjs/qjs_patch.h" -#include "bridge_qjs.h" +#include "page.h" namespace kraken::binding::qjs { -ImageElement::ImageElement(JSContext* context) : Element(context) { +ImageElement::ImageElement(ExecutionContext* context) : Element(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); } -void bindImageElement(std::unique_ptr& context) { +void bindImageElement(std::unique_ptr& context) { auto* constructor = ImageElement::instance(context.get()); - context->defineGlobalProperty("HTMLImageElement", constructor->classObject); - context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->classObject)); + context->defineGlobalProperty("HTMLImageElement", constructor->jsObject); + context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->jsObject)); } -JSValue ImageElement::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue ImageElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { auto instance = new ImageElementInstance(this); - return instance->instanceObject; + return instance->jsObject; } -PROP_GETTER(ImageElementInstance, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("width"); } -PROP_SETTER(ImageElementInstance, width)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string key = "width"; - NativeString* args_01 = stringToNativeString(key); - NativeString* args_02 = jsValueToNativeString(ctx, argv[0]); - foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL; } -PROP_GETTER(ImageElementInstance, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("height"); } -PROP_SETTER(ImageElementInstance, height)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string key = "height"; - NativeString* args_01 = stringToNativeString(key); - NativeString* args_02 = jsValueToNativeString(ctx, argv[0]); - foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL; } -PROP_GETTER(ImageElementInstance, naturalWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, naturalWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("naturalWidth"); } -PROP_SETTER(ImageElementInstance, naturalWidth)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ImageElementInstance, naturalHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, naturalHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("naturalHeight"); } -PROP_SETTER(ImageElementInstance, naturalHeight)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(ImageElementInstance, src)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("src"); } -PROP_SETTER(ImageElementInstance, src)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string key = "src"; - NativeString* args_01 = stringToNativeString(key); - NativeString* args_02 = jsValueToNativeString(ctx, argv[0]); - foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL; } -PROP_GETTER(ImageElementInstance, loading)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->getNativeProperty("loading"); } -PROP_SETTER(ImageElementInstance, loading)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string key = "loading"; - NativeString* args_01 = stringToNativeString(key); - NativeString* args_02 = jsValueToNativeString(ctx, argv[0]); - foundation::UICommandBuffer::instance(element->m_context->getContextId())->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL; } diff --git a/bridge/bindings/qjs/dom/elements/image_element.h b/bridge/bindings/qjs/dom/elements/image_element.h index 7cbec6be9a..0c831fd72f 100644 --- a/bridge/bindings/qjs/dom/elements/image_element.h +++ b/bridge/bindings/qjs/dom/elements/image_element.h @@ -10,18 +10,28 @@ namespace kraken::binding::qjs { -void bindImageElement(std::unique_ptr& context); +void bindImageElement(std::unique_ptr& context); +class ImageElementInstance; class ImageElement : public Element { public: ImageElement() = delete; - explicit ImageElement(JSContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + explicit ImageElement(ExecutionContext* context); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; OBJECT_INSTANCE(ImageElement); private: + DEFINE_PROTOTYPE_READONLY_PROPERTY(naturalWidth); + DEFINE_PROTOTYPE_READONLY_PROPERTY(naturalHeight); + + DEFINE_PROTOTYPE_PROPERTY(width); + DEFINE_PROTOTYPE_PROPERTY(height); + DEFINE_PROTOTYPE_PROPERTY(src); + DEFINE_PROTOTYPE_PROPERTY(loading); + friend ImageElementInstance; }; + class ImageElementInstance : public ElementInstance { public: ImageElementInstance() = delete; @@ -30,7 +40,7 @@ class ImageElementInstance : public ElementInstance { private: bool freed{false}; - DEFINE_HOST_CLASS_PROPERTY(6, width, height, naturalWidth, naturalHeight, src, loading) + friend ImageElement; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/elements/template_element.cc b/bridge/bindings/qjs/dom/elements/template_element.cc index 458c6c7f7e..ecf2396418 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.cc +++ b/bridge/bindings/qjs/dom/elements/template_element.cc @@ -6,72 +6,36 @@ #include "template_element.h" #include "bindings/qjs/dom/text_node.h" #include "bindings/qjs/qjs_patch.h" -#include "bridge_qjs.h" +#include "page.h" namespace kraken::binding::qjs { -TemplateElement::TemplateElement(JSContext* context) : Element(context) { +TemplateElement::TemplateElement(ExecutionContext* context) : Element(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); } -void bindTemplateElement(std::unique_ptr& context) { +void bindTemplateElement(std::unique_ptr& context) { auto* constructor = TemplateElement::instance(context.get()); - context->defineGlobalProperty("HTMLTemplateElement", constructor->classObject); + context->defineGlobalProperty("HTMLTemplateElement", constructor->jsObject); } -JSValue TemplateElement::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue TemplateElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { auto instance = new TemplateElementInstance(this); - return instance->instanceObject; + return instance->jsObject; } -PROP_GETTER(TemplateElementInstance, content)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_DupValue(ctx, element->m_content->instanceObject); -} -PROP_SETTER(TemplateElementInstance, content)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TemplateElementInstance, innerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - - std::string s = ""; - int32_t childLen = arrayGetLength(ctx, element->m_content->childNodes); - for (int i = 0; i < childLen; i++) { - JSValue v = JS_GetPropertyUint32(ctx, element->m_content->childNodes, i); - auto* node = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (node->nodeType == NodeType::ELEMENT_NODE) { - s += reinterpret_cast(node)->outerHTML(); - } else if (node->nodeType == NodeType::TEXT_NODE) { - s += reinterpret_cast(node)->toString(); - } - JS_FreeValue(ctx, v); - } - return JS_NewString(ctx, s.c_str()); -} -PROP_SETTER(TemplateElementInstance, innerHTML)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - const char* codeString = JS_ToCString(ctx, argv[0]); - size_t len = strlen(codeString); - HTMLParser::parseHTML(codeString, len, element->m_content); - return JS_NULL; +DocumentFragmentInstance* TemplateElementInstance::content() const { + return static_cast(JS_GetOpaque(m_content.value(), DocumentFragment::classId())); } TemplateElementInstance::TemplateElementInstance(TemplateElement* element) : ElementInstance(element, "template", true) { - JSValue documentFragmentValue = JS_CallConstructor(m_ctx, DocumentFragment::instance(m_context)->classObject, 0, nullptr); - m_content = static_cast(JS_GetOpaque(documentFragmentValue, DocumentFragment::classId())); + setNodeFlag(NodeFlag::IsTemplateElement); } -TemplateElementInstance::~TemplateElementInstance() { - JS_FreeValue(m_ctx, m_content->instanceObject); -} +TemplateElementInstance::~TemplateElementInstance() {} -void TemplateElementInstance::gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - NodeInstance::gcMark(rt, val, mark_func); - // Should check object is already inited before gc mark. - if (JS_IsObject(m_content->instanceObject)) - JS_MarkValue(rt, m_content->instanceObject, mark_func); +void TemplateElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + ElementInstance::trace(rt, val, mark_func); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/elements/template_element.h b/bridge/bindings/qjs/dom/elements/template_element.h index f44223a15e..3a8346c02f 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.h +++ b/bridge/bindings/qjs/dom/elements/template_element.h @@ -11,30 +11,35 @@ namespace kraken::binding::qjs { -void bindTemplateElement(std::unique_ptr& context); +void bindTemplateElement(std::unique_ptr& context); +class TemplateElementInstance; class TemplateElement : public Element { public: TemplateElement() = delete; - explicit TemplateElement(JSContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + explicit TemplateElement(ExecutionContext* context); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; OBJECT_INSTANCE(TemplateElement); private: + friend TemplateElementInstance; }; + class TemplateElementInstance : public ElementInstance { public: TemplateElementInstance() = delete; explicit TemplateElementInstance(TemplateElement* element); ~TemplateElementInstance(); + DocumentFragmentInstance* content() const; + protected: - void gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; private: - DocumentFragmentInstance* m_content{nullptr}; - DEFINE_HOST_CLASS_PROPERTY(2, content, innerHTML) + ObjectProperty m_content{m_context, jsObject, "content", JS_CallConstructor(m_ctx, DocumentFragment::instance(m_context)->jsObject, 0, nullptr)}; + friend TemplateElement; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc index 1e3790ce75..88bde595c9 100644 --- a/bridge/bindings/qjs/dom/event.cc +++ b/bridge/bindings/qjs/dom/event.cc @@ -13,18 +13,18 @@ namespace kraken::binding::qjs { std::once_flag kEventInitOnceFlag; -void bindEvent(std::unique_ptr& context) { +void bindEvent(std::unique_ptr& context) { auto* constructor = Event::instance(context.get()); - context->defineGlobalProperty("Event", constructor->classObject); + context->defineGlobalProperty("Event", constructor->jsObject); } JSClassID Event::kEventClassID{0}; -Event::Event(JSContext* context) : HostClass(context, "Event") { +Event::Event(ExecutionContext* context) : HostClass(context, "Event") { std::call_once(kEventInitOnceFlag, []() { JS_NewClassID(&kEventClassID); }); } -JSValue Event::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue Event::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to construct 'Event': 1 argument required, but only 0 present."); } @@ -32,114 +32,76 @@ JSValue Event::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue th JSValue eventTypeValue = argv[0]; std::string eventType = jsValueToStdString(ctx, eventTypeValue); - auto* nativeEvent = new NativeEvent{stringToNativeString(eventType)}; + auto* nativeEvent = new NativeEvent{stringToNativeString(eventType).release()}; auto* event = Event::buildEventInstance(eventType, m_context, nativeEvent, false); - return event->instanceObject; + return event->jsObject; } std::unordered_map Event::m_eventCreatorMap{}; -PROP_GETTER(EventInstance, type)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewUnicodeString(eventInstance->context()->runtime(), eventInstance->context()->ctx(), eventInstance->nativeEvent->type->string, eventInstance->nativeEvent->type->length); -} -PROP_SETTER(EventInstance, type)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return JS_NewUnicodeString(ExecutionContext::runtime(), eventInstance->context()->ctx(), eventInstance->nativeEvent->type->string, eventInstance->nativeEvent->type->length); } -PROP_GETTER(EventInstance, bubbles)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, bubbles)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewBool(ctx, eventInstance->nativeEvent->bubbles); } -PROP_SETTER(EventInstance, bubbles)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, cancelable)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, cancelable)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewBool(ctx, eventInstance->nativeEvent->cancelable); } -PROP_SETTER(EventInstance, cancelable)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, timestamp)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, timestamp)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewInt64(ctx, eventInstance->nativeEvent->timeStamp); } -PROP_SETTER(EventInstance, timestamp)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, defaultPrevented)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, defaultPrevented)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewBool(ctx, eventInstance->cancelled()); } -PROP_SETTER(EventInstance, defaultPrevented)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, target)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, target)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); if (eventInstance->nativeEvent->target != nullptr) { auto instance = reinterpret_cast(eventInstance->nativeEvent->target); - return JS_DupValue(ctx, instance->instanceObject); + return JS_DupValue(ctx, instance->jsObject); } return JS_NULL; } -PROP_SETTER(EventInstance, target)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, srcElement)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, srcElement)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); if (eventInstance->nativeEvent->target != nullptr) { auto instance = reinterpret_cast(eventInstance->nativeEvent->target); - return JS_DupValue(ctx, instance->instanceObject); + return JS_DupValue(ctx, instance->jsObject); } return JS_NULL; } -PROP_SETTER(EventInstance, srcElement)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, currentTarget)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, currentTarget)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); if (eventInstance->nativeEvent->currentTarget != nullptr) { auto instance = reinterpret_cast(eventInstance->nativeEvent->currentTarget); - return JS_DupValue(ctx, instance->instanceObject); + return JS_DupValue(ctx, instance->jsObject); } return JS_NULL; } -PROP_SETTER(EventInstance, currentTarget)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, returnValue)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, returnValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewBool(ctx, !eventInstance->cancelled()); } -PROP_SETTER(EventInstance, returnValue)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(EventInstance, cancelBubble)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Event, cancelBubble)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); return JS_NewBool(ctx, eventInstance->cancelled()); } -PROP_SETTER(EventInstance, cancelBubble)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) - return JS_NULL; - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - bool v = JS_ToBool(ctx, argv[0]); - if (v) { - eventInstance->cancelled(v); - } - return JS_NULL; -} - -EventInstance* Event::buildEventInstance(std::string& eventType, JSContext* context, void* nativeEvent, bool isCustomEvent) { +EventInstance* Event::buildEventInstance(std::string& eventType, ExecutionContext* context, void* nativeEvent, bool isCustomEvent) { EventInstance* eventInstance; if (isCustomEvent) { eventInstance = new CustomEventInstance(CustomEvent::instance(context), reinterpret_cast(nativeEvent)); @@ -149,8 +111,6 @@ EventInstance* Event::buildEventInstance(std::string& eventType, JSContext* cont eventInstance = EventInstance::fromNativeEvent(Event::instance(context), static_cast(nativeEvent)); } - JS_SetPrototype(context->ctx(), eventInstance->instanceObject, Event::instance(context)->m_prototypeObject); - return eventInstance; } @@ -158,20 +118,20 @@ void Event::defineEvent(const std::string& eventType, EventCreator creator) { m_eventCreatorMap[eventType] = creator; } -JSValue Event::stopPropagation(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Event::stopPropagation(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); event->m_propagationStopped = true; return JS_NULL; } -JSValue Event::stopImmediatePropagation(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Event::stopImmediatePropagation(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); event->m_propagationStopped = true; event->m_propagationImmediatelyStopped = true; return JS_NULL; } -JSValue Event::preventDefault(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Event::preventDefault(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); if (event->nativeEvent->cancelable) { event->m_cancelled = true; @@ -179,7 +139,7 @@ JSValue Event::preventDefault(QjsContext* ctx, JSValue this_val, int argc, JSVal return JS_NULL; } -JSValue Event::initEvent(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Event::initEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to initEvent required, but only 0 present."); } @@ -200,7 +160,7 @@ JSValue Event::initEvent(QjsContext* ctx, JSValue this_val, int argc, JSValue* a } auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - event->nativeEvent->type = jsValueToNativeString(ctx, typeValue); + event->nativeEvent->type = jsValueToNativeString(ctx, typeValue).release(); if (!JS_IsNull(bubblesValue)) { event->nativeEvent->bubbles = JS_IsBool(bubblesValue) ? 1 : 0; @@ -218,7 +178,7 @@ EventInstance* EventInstance::fromNativeEvent(Event* event, NativeEvent* nativeE EventInstance::EventInstance(Event* event, NativeEvent* nativeEvent) : nativeEvent(nativeEvent), Instance(event, "Event", nullptr, Event::kEventClassID, finalizer) {} EventInstance::EventInstance(Event* jsEvent, JSAtom eventType, JSValue eventInit) : Instance(jsEvent, "Event", nullptr, Event::kEventClassID, finalizer) { JSValue v = JS_AtomToValue(m_ctx, eventType); - nativeEvent = new NativeEvent{jsValueToNativeString(m_ctx, v)}; + nativeEvent = new NativeEvent{jsValueToNativeString(m_ctx, v).release()}; JS_FreeValue(m_ctx, v); auto ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); @@ -243,7 +203,7 @@ EventInstance::EventInstance(Event* jsEvent, JSAtom eventType, JSValue eventInit void EventInstance::finalizer(JSRuntime* rt, JSValue val) { auto* event = static_cast(JS_GetOpaque(val, Event::kEventClassID)); if (event->context()->isValid()) { - JS_FreeValue(event->m_ctx, event->instanceObject); + JS_FreeValue(event->m_ctx, event->jsObject); } delete event; } diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h index 77f980e07c..e161ed623d 100644 --- a/bridge/bindings/qjs/dom/event.h +++ b/bridge/bindings/qjs/dom/event.h @@ -50,37 +50,48 @@ namespace kraken::binding::qjs { #define EVENT_LONG_PRESS "longpress" #define EVENT_SCALE "scale" -void bindEvent(std::unique_ptr& context); +void bindEvent(std::unique_ptr& context); class EventInstance; -using EventCreator = EventInstance* (*)(JSContext* context, void* nativeEvent); +using EventCreator = EventInstance* (*)(ExecutionContext* context, void* nativeEvent); class Event : public HostClass { public: static JSClassID kEventClassID; - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; Event() = delete; - explicit Event(JSContext* context); + explicit Event(ExecutionContext* context); - static EventInstance* buildEventInstance(std::string& eventType, JSContext* context, void* nativeEvent, bool isCustomEvent); + static EventInstance* buildEventInstance(std::string& eventType, ExecutionContext* context, void* nativeEvent, bool isCustomEvent); static void defineEvent(const std::string& eventType, EventCreator creator); OBJECT_INSTANCE(Event); - static JSValue stopPropagation(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue stopImmediatePropagation(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue preventDefault(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue initEvent(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue stopPropagation(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue stopImmediatePropagation(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue preventDefault(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue initEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); private: static std::unordered_map m_eventCreatorMap; - ObjectFunction m_stopPropagation{m_context, m_prototypeObject, "stopPropagation", stopPropagation, 0}; - ObjectFunction m_stopImmediatePropagation{m_context, m_prototypeObject, "immediatePropagation", stopImmediatePropagation, 0}; - ObjectFunction m_preventDefault{m_context, m_prototypeObject, "preventDefault", preventDefault, 1}; - ObjectFunction m_initEvent{m_context, m_prototypeObject, "initEvent", initEvent, 3}; + DEFINE_PROTOTYPE_READONLY_PROPERTY(type); + DEFINE_PROTOTYPE_READONLY_PROPERTY(bubbles); + DEFINE_PROTOTYPE_READONLY_PROPERTY(cancelable); + DEFINE_PROTOTYPE_READONLY_PROPERTY(timestamp); + DEFINE_PROTOTYPE_READONLY_PROPERTY(defaultPrevented); + DEFINE_PROTOTYPE_READONLY_PROPERTY(target); + DEFINE_PROTOTYPE_READONLY_PROPERTY(srcElement); + DEFINE_PROTOTYPE_READONLY_PROPERTY(currentTarget); + DEFINE_PROTOTYPE_READONLY_PROPERTY(returnValue); + DEFINE_PROTOTYPE_READONLY_PROPERTY(cancelBubble); + + DEFINE_PROTOTYPE_FUNCTION(stopPropagation, 0); + DEFINE_PROTOTYPE_FUNCTION(stopImmediatePropagation, 0); + DEFINE_PROTOTYPE_FUNCTION(preventDefault, 1); + DEFINE_PROTOTYPE_FUNCTION(initEvent, 3); friend EventInstance; }; @@ -123,8 +134,6 @@ class EventInstance : public Instance { bool m_propagationImmediatelyStopped{false}; private: - DEFINE_HOST_CLASS_PROPERTY(10, type, bubbles, cancelable, timestamp, defaultPrevented, target, srcElement, currentTarget, returnValue, cancelBubble) - static void finalizer(JSRuntime* rt, JSValue val); friend Event; }; diff --git a/bridge/bindings/qjs/dom/event_listener_map.cc b/bridge/bindings/qjs/dom/event_listener_map.cc new file mode 100644 index 0000000000..a31cc79692 --- /dev/null +++ b/bridge/bindings/qjs/dom/event_listener_map.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_listener_map.h" + +namespace kraken::binding::qjs { + +static bool addListenerToVector(EventListenerVector* vector, JSValue callback) { + if (std::find_if(vector->begin(), vector->end(), [&callback](JSValue fn) { return JS_VALUE_GET_PTR(fn) == JS_VALUE_GET_PTR(callback); }) != vector->end()) { + return false; // Duplicate listener. + } + + vector->push_back(callback); + return true; +} + +static bool removeListenerFromVector(EventListenerVector* listenerVector, JSValue callback) { + // Do a manual search for the matching listener. It is not + // possible to create a listener on the stack because of the + // const on |listener|. + auto it = std::find_if(listenerVector->begin(), listenerVector->end(), [&callback](const JSValue& listener) -> bool { return JS_VALUE_GET_PTR(listener) == JS_VALUE_GET_PTR(callback); }); + + if (it == listenerVector->end()) { + return false; + } + listenerVector->erase(it); + return true; +} + +bool EventListenerMap::contains(JSAtom eventType) const { + for (const auto& entry : m_entries) { + if (entry.first == eventType) + return true; + } + return false; +} + +void EventListenerMap::clear() { + m_entries.clear(); +} + +bool EventListenerMap::add(JSAtom eventType, JSValue callback) { + for (const auto& entry : m_entries) { + if (entry.first == eventType) { + return addListenerToVector(const_cast(&entry.second), callback); + } + } + + std::vector list; + list.reserve(8); + m_entries.emplace_back(std::make_pair(eventType, list)); + + return addListenerToVector(&m_entries.back().second, callback); +} + +bool EventListenerMap::remove(JSAtom eventType, JSValue callback) { + for (unsigned i = 0; i < m_entries.size(); ++i) { + if (m_entries[i].first == eventType) { + bool was_removed = removeListenerFromVector(&m_entries[i].second, callback); + if (m_entries[i].second.empty()) { + m_entries.erase(m_entries.begin() + i); + } + return was_removed; + } + } + + return false; +} + +const EventListenerVector* EventListenerMap::find(JSAtom eventType) { + for (const auto& entry : m_entries) { + if (entry.first == eventType) + return &entry.second; + } + + return nullptr; +} + +void EventListenerMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + for (const auto& entry : m_entries) { + for (const auto& vector : entry.second) { + JS_MarkValue(rt, vector, mark_func); + } + } +} + +EventListenerMap::~EventListenerMap() { + for (const auto& entry : m_entries) { + for (const auto& vector : entry.second) { + JS_FreeAtomRT(m_runtime, entry.first); + JS_FreeValueRT(m_runtime, vector); + } + } +} + +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_listener_map.h b/bridge/bindings/qjs/dom/event_listener_map.h new file mode 100644 index 0000000000..edfc165729 --- /dev/null +++ b/bridge/bindings/qjs/dom/event_listener_map.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ + +#include +#include +#include "include/kraken_foundation.h" + +namespace kraken::binding::qjs { + +using EventListenerVector = std::vector; + +class EventListenerMap final { + public: + EventListenerMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)){}; + ~EventListenerMap(); + + [[nodiscard]] bool empty() const { return m_entries.empty(); } + [[nodiscard]] bool contains(JSAtom eventType) const; + void clear(); + bool add(JSAtom eventType, JSValue callback); + bool remove(JSAtom eventType, JSValue callback); + const EventListenerVector* find(JSAtom eventType); + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + + private: + // EventListener handlers registered with addEventListener API. + // We use vector instead of hashMap because + // - vector is much more space efficient than hashMap. + // - An EventTarget rarely has event listeners for many event types, and + // vector is faster in such cases. + std::vector> m_entries; + + JSRuntime* m_runtime; +}; + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index 3728e29857..87af0f2b41 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -14,29 +14,33 @@ #include "event.h" #include "kraken_bridge.h" +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + namespace kraken::binding::qjs { static std::atomic globalEventTargetId{0}; std::once_flag kEventTargetInitFlag; #define GetPropertyCallPreFix "_getProperty_" -void bindEventTarget(std::unique_ptr& context) { +void bindEventTarget(std::unique_ptr& context) { auto* constructor = EventTarget::instance(context.get()); // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - JS_SetPrototype(context->ctx(), context->global(), constructor->classObject); - context->defineGlobalProperty("EventTarget", constructor->classObject); + JS_SetPrototype(context->ctx(), context->global(), constructor->jsObject); + context->defineGlobalProperty("EventTarget", constructor->jsObject); } JSClassID EventTarget::kEventTargetClassId{0}; -EventTarget::EventTarget(JSContext* context, const char* name) : HostClass(context, name) {} -EventTarget::EventTarget(JSContext* context) : HostClass(context, "EventTarget") { +EventTarget::EventTarget(ExecutionContext* context, const char* name) : HostClass(context, name) {} +EventTarget::EventTarget(ExecutionContext* context) : HostClass(context, "EventTarget") { std::call_once(kEventTargetInitFlag, []() { JS_NewClassID(&kEventTargetClassId); }); } -JSValue EventTarget::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue EventTarget::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { auto eventTarget = new EventTargetInstance(this, kEventTargetClassId, "EventTarget"); - return eventTarget->instanceObject; + return eventTarget->jsObject; } JSClassID EventTarget::classId() { @@ -49,7 +53,7 @@ JSClassID EventTarget::classId(JSValue& value) { return classId; } -JSValue EventTarget::addEventListener(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: type and listener are required."); } @@ -66,36 +70,30 @@ JSValue EventTarget::addEventListener(QjsContext* ctx, JSValue this_val, int arg return JS_UNDEFINED; } - JSAtom eventTypeAtom = JS_ValueToAtom(ctx, eventTypeValue); - - // Init list. - if (!JS_HasProperty(ctx, eventTargetInstance->m_eventHandlers, eventTypeAtom)) { - JS_DupAtom(ctx, eventTypeAtom); - auto* atomJob = new AtomJob{eventTypeAtom}; - list_add_tail(&atomJob->link, &eventTargetInstance->m_context->atom_job_list); - JS_SetProperty(ctx, eventTargetInstance->m_eventHandlers, eventTypeAtom, JS_NewArray(ctx)); - } - - JSValue eventHandlers = JS_GetProperty(ctx, eventTargetInstance->m_eventHandlers, eventTypeAtom); - int32_t eventHandlerLen = arrayGetLength(ctx, eventHandlers); + // EventType atom will be freed when eventTarget finalized. + JSAtom eventType = JS_ValueToAtom(ctx, eventTypeValue); // Dart needs to be notified for the first registration event. - if (eventHandlerLen == 0 || JS_HasProperty(ctx, eventTargetInstance->m_propertyEventHandler, eventTypeAtom)) { + if (!eventTargetInstance->m_eventListenerMap.contains(eventType) || eventTargetInstance->m_eventHandlerMap.contains(eventType)) { int32_t contextId = eventTargetInstance->prototype()->contextId(); NativeString args_01{}; buildUICommandArgs(ctx, eventTypeValue, args_01); - foundation::UICommandBuffer::instance(contextId)->addCommand(eventTargetInstance->m_eventTargetId, UICommand::addEvent, args_01, nullptr); + eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::addEvent, args_01, nullptr); + } + + bool success = eventTargetInstance->m_eventListenerMap.add(eventType, JS_DupValue(ctx, callback)); + // Callback didn't saved to eventListenerMap. + if (!success) { + JS_FreeAtom(ctx, eventType); + JS_FreeValue(ctx, callback); } - arrayPushValue(ctx, eventHandlers, callback); - JS_FreeAtom(ctx, eventTypeAtom); - JS_FreeValue(ctx, eventHandlers); return JS_UNDEFINED; } -JSValue EventTarget::removeEventListener(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "Failed to removeEventListener: at least type and listener are required."); } @@ -112,38 +110,34 @@ JSValue EventTarget::removeEventListener(QjsContext* ctx, JSValue this_val, int return JS_ThrowTypeError(ctx, "Failed to removeEventListener: eventName should be an string."); } - JSAtom eventTypeAtom = JS_ValueToAtom(ctx, eventTypeValue); + JSAtom eventType = JS_ValueToAtom(ctx, eventTypeValue); + auto& eventHandlers = eventTargetInstance->m_eventListenerMap; - if (!JS_HasProperty(ctx, eventTargetInstance->m_eventHandlers, eventTypeAtom)) { - JS_FreeAtom(ctx, eventTypeAtom); + if (!eventTargetInstance->m_eventListenerMap.contains(eventType)) { + JS_FreeAtom(ctx, eventType); return JS_UNDEFINED; } - JSValue eventHandlers = JS_GetProperty(ctx, eventTargetInstance->m_eventHandlers, eventTypeAtom); - int32_t targetIdx = arrayFindIdx(ctx, eventHandlers, callback); - - if (targetIdx != -1) { - arraySpliceValue(ctx, eventHandlers, targetIdx, 1); + if (eventHandlers.remove(eventType, callback)) { + JS_FreeAtom(ctx, eventType); + JS_FreeValue(ctx, callback); } - int32_t eventHandlersLen = arrayGetLength(ctx, eventHandlers); - - if (eventHandlersLen && JS_HasProperty(ctx, eventTargetInstance->m_propertyEventHandler, eventTypeAtom)) { + if (eventHandlers.empty() && eventTargetInstance->m_eventHandlerMap.contains(eventType)) { // Dart needs to be notified for handles is empty. int32_t contextId = eventTargetInstance->prototype()->contextId(); NativeString args_01{}; buildUICommandArgs(ctx, eventTypeValue, args_01); - foundation::UICommandBuffer::instance(contextId)->addCommand(eventTargetInstance->m_eventTargetId, UICommand::removeEvent, args_01, nullptr); + eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::removeEvent, args_01, nullptr); } - JS_FreeAtom(ctx, eventTypeAtom); - JS_FreeValue(ctx, eventHandlers); + JS_FreeAtom(ctx, eventType); return JS_UNDEFINED; } -JSValue EventTarget::dispatchEvent(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue EventTarget::dispatchEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to dispatchEvent: first arguments should be an event object"); } @@ -163,7 +157,7 @@ bool EventTargetInstance::dispatchEvent(EventInstance* event) { std::string eventType = toUTF8(u16EventType); // protect this util event trigger finished. - JS_DupValue(m_ctx, instanceObject); + JS_DupValue(m_ctx, jsObject); internalDispatchEvent(event); @@ -183,55 +177,62 @@ bool EventTargetInstance::dispatchEvent(EventInstance* event) { } } - JS_FreeValue(m_ctx, instanceObject); + JS_FreeValue(m_ctx, jsObject); return event->cancelled(); } bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { std::u16string u16EventType = std::u16string(reinterpret_cast(eventInstance->nativeEvent->type->string), eventInstance->nativeEvent->type->length); - std::string eventType = toUTF8(u16EventType); - JSAtom eventTypeAtom = JS_NewAtom(m_ctx, eventType.c_str()); + std::string eventTypeStr = toUTF8(u16EventType); + JSAtom eventType = JS_NewAtom(m_ctx, eventTypeStr.c_str()); // Modify the currentTarget to this. eventInstance->nativeEvent->currentTarget = this; // Dispatch event listeners writen by addEventListener - auto _dispatchEvent = [&eventInstance, this](JSValue& handler) { + auto _dispatchEvent = [&eventInstance, this](JSValue handler) { + if (!JS_IsFunction(m_ctx, handler)) + return; + if (eventInstance->propagationImmediatelyStopped()) return; + + /* 'handler' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + JS_DupValue(m_ctx, handler); + // The third params `thisObject` to null equals global object. - JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, &eventInstance->instanceObject); + JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, &eventInstance->jsObject); + + JS_FreeValue(m_ctx, handler); m_context->handleException(&returnedValue); m_context->drainPendingPromiseJobs(); JS_FreeValue(m_ctx, returnedValue); }; - if (JS_HasProperty(m_ctx, m_eventHandlers, eventTypeAtom)) { - JSValue eventHandlers = JS_GetProperty(m_ctx, m_eventHandlers, eventTypeAtom); - int32_t len = arrayGetLength(m_ctx, eventHandlers); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(m_ctx, eventHandlers, i); - _dispatchEvent(v); - JS_FreeValue(m_ctx, v); + if (m_eventListenerMap.contains(eventType)) { + const EventListenerVector* vector = m_eventListenerMap.find(eventType); + for (auto& eventHandler : *vector) { + _dispatchEvent(eventHandler); } - - JS_FreeValue(m_ctx, eventHandlers); } // Dispatch event listener white by 'on' prefix property. - if (JS_HasProperty(m_ctx, m_propertyEventHandler, eventTypeAtom)) { - if (eventType == "error") { - auto _dispatchErrorEvent = [&eventInstance, this, eventType](JSValue& handler) { - JSValue error = JS_GetPropertyStr(m_ctx, eventInstance->instanceObject, "error"); + if (m_eventHandlerMap.contains(eventType)) { + // Let special error event handling be true if event is an ErrorEvent. + bool specialErrorEventHanding = eventTypeStr == "error"; + + if (specialErrorEventHanding) { + auto _dispatchErrorEvent = [&eventInstance, this, eventTypeStr](JSValue handler) { + JSValue error = JS_GetPropertyStr(m_ctx, eventInstance->jsObject, "error"); JSValue messageValue = JS_GetPropertyStr(m_ctx, error, "message"); JSValue lineNumberValue = JS_GetPropertyStr(m_ctx, error, "lineNumber"); JSValue fileNameValue = JS_GetPropertyStr(m_ctx, error, "fileName"); JSValue columnValue = JS_NewUint32(m_ctx, 0); JSValue args[]{messageValue, fileNameValue, lineNumberValue, columnValue, error}; - JS_Call(m_ctx, handler, eventInstance->instanceObject, 5, args); + JS_Call(m_ctx, handler, eventInstance->jsObject, 5, args); m_context->drainPendingPromiseJobs(); JS_FreeValue(m_ctx, error); @@ -240,29 +241,19 @@ bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { JS_FreeValue(m_ctx, lineNumberValue); JS_FreeValue(m_ctx, columnValue); }; - JSValue v = JS_GetProperty(m_ctx, m_propertyEventHandler, eventTypeAtom); - _dispatchErrorEvent(v); - JS_FreeValue(m_ctx, v); + _dispatchErrorEvent(m_eventHandlerMap.getProperty(eventType)); } else { - JSValue v = JS_GetProperty(m_ctx, m_propertyEventHandler, eventTypeAtom); - _dispatchEvent(v); - JS_FreeValue(m_ctx, v); + _dispatchEvent(m_eventHandlerMap.getProperty(eventType)); } } - JS_FreeAtom(m_ctx, eventTypeAtom); + JS_FreeAtom(m_ctx, eventType); // do not dispatch event when event has been canceled // true is prevented. return eventInstance->cancelled(); } -#if IS_TEST -JSValue EventTarget::__kraken_clear_event_listener(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -#endif - EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) : Instance(eventTarget, name, &exoticMethods, classId, finalize) { m_eventTargetId = globalEventTargetId++; @@ -281,16 +272,19 @@ JSClassID EventTargetInstance::classId() { } EventTargetInstance::~EventTargetInstance() { - foundation::UICommandBuffer::instance(m_contextId)->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false); -#if FLUTTER_BACKEND - getDartMethod()->flushUICommand(); +#if UNIT_TEST + // Callback to unit test specs before eventTarget finalized. + if (TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed != nullptr) { + TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed(this); + } #endif - JS_FreeValue(m_ctx, m_properties); - JS_FreeValue(m_ctx, m_eventHandlers); - JS_FreeValue(m_ctx, m_propertyEventHandler); + + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false); + getDartMethod()->flushUICommand(); + delete nativeEventTarget; } -int EventTargetInstance::hasProperty(QjsContext* ctx, JSValue obj, JSAtom atom) { +int EventTargetInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* prototype = static_cast(eventTarget->prototype()); @@ -303,17 +297,17 @@ int EventTargetInstance::hasProperty(QjsContext* ctx, JSValue obj, JSAtom atom) JS_FreeValue(ctx, atomString); if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - return !JS_IsNull(eventTarget->getPropertyHandler(p)); + return !JS_IsNull(eventTarget->getAttributesEventHandler(p)); } - return JS_HasProperty(ctx, eventTarget->m_properties, atom); + return eventTarget->m_properties.contains(atom); } -JSValue EventTargetInstance::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue EventTargetInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, eventTarget->instanceObject); + JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); if (JS_HasProperty(ctx, prototype, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, eventTarget->instanceObject, 0); + JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, eventTarget->jsObject, 0); JS_FreeValue(ctx, prototype); return ret; } @@ -325,15 +319,15 @@ JSValue EventTargetInstance::getProperty(QjsContext* ctx, JSValue obj, JSAtom at JS_FreeValue(ctx, atomString); if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - return eventTarget->getPropertyHandler(p); + return eventTarget->getAttributesEventHandler(p); } - if (JS_HasProperty(ctx, eventTarget->m_properties, atom)) { - return JS_GetProperty(ctx, eventTarget->m_properties, atom); + if (eventTarget->m_properties.contains(atom)) { + return JS_DupValue(ctx, eventTarget->m_properties.getProperty(atom)); } // For plugin elements, try to auto generate properties and functions from dart response. - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->instanceObject)) { + if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject)) { const char* cmethod = JS_AtomToCString(eventTarget->m_ctx, atom); // Property starts with underscore are taken as private property in javascript object. if (cmethod[0] == '_') { @@ -348,28 +342,41 @@ JSValue EventTargetInstance::getProperty(QjsContext* ctx, JSValue obj, JSAtom at return JS_UNDEFINED; } -int EventTargetInstance::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +int EventTargetInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); + + // Check there are setter functions on prototype. + if (JS_HasProperty(ctx, prototype, atom)) { + // Read setter function from prototype Object. + JSPropertyDescriptor descriptor; + JS_GetOwnProperty(ctx, &descriptor, prototype, atom); + JSValue setterFunc = descriptor.setter; + assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); + JSValue ret = JS_Call(ctx, setterFunc, eventTarget->jsObject, 1, &value); + if (JS_IsException(ret)) + return -1; + + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, descriptor.setter); + JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, prototype); + return 1; + } + + JS_FreeValue(ctx, prototype); JSValue atomString = JS_AtomToString(ctx, atom); JSString* p = JS_VALUE_GET_STRING(atomString); - if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - eventTarget->setPropertyHandler(p, value); + if (!p->is_wide_char && p->len > 2 && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { + eventTarget->setAttributesEventHandler(p, value); } else { - if (!JS_HasProperty(ctx, eventTarget->m_properties, atom)) { - auto* atomJob = new AtomJob{atom}; - list_add_tail(&atomJob->link, &eventTarget->m_context->atom_job_list); - // Increase one reference count for atom to hold this atom value until eventTarget disposed. - JS_DupAtom(ctx, atom); - } - - JS_SetProperty(ctx, eventTarget->m_properties, atom, JS_DupValue(ctx, value)); - - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->instanceObject) && !p->is_wide_char && p->u.str8[0] != '_') { - NativeString* args_01 = atomToNativeString(ctx, atom); - NativeString* args_02 = jsValueToNativeString(ctx, value); - foundation::UICommandBuffer::instance(eventTarget->m_contextId)->addCommand(eventTarget->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + eventTarget->m_properties.setProperty(JS_DupAtom(ctx, atom), JS_DupValue(ctx, value)); + if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject) && !p->is_wide_char && p->u.str8[0] != '_') { + std::unique_ptr args_01 = atomToNativeString(ctx, atom); + std::unique_ptr args_02 = jsValueToNativeString(ctx, value); + eventTarget->m_context->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); } } @@ -378,7 +385,7 @@ int EventTargetInstance::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, return 0; } -int EventTargetInstance::deleteProperty(QjsContext* ctx, JSValue obj, JSAtom prop) { +int EventTargetInstance::deleteProperty(JSContext* ctx, JSValue obj, JSAtom prop) { return 0; } @@ -390,7 +397,7 @@ JSValue EventTargetInstance::callNativeMethods(const char* method, int32_t argc, std::u16string methodString; fromUTF8(method, methodString); - NativeString m{reinterpret_cast(methodString.c_str()), static_cast(methodString.size())}; + NativeString m{reinterpret_cast(methodString.c_str()), static_cast(methodString.size())}; NativeValue nativeValue{}; nativeEventTarget->callNativeMethods(nativeEventTarget, &nativeValue, &m, argc, argv); @@ -398,47 +405,40 @@ JSValue EventTargetInstance::callNativeMethods(const char* method, int32_t argc, return returnValue; } -void EventTargetInstance::setPropertyHandler(JSString* p, JSValue value) { +void EventTargetInstance::setAttributesEventHandler(JSString* p, JSValue value) { char eventType[p->len + 1 - 2]; memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); JSAtom atom = JS_NewAtom(m_ctx, eventType); - auto* atomJob = new AtomJob{atom}; - list_add_tail(&atomJob->link, &m_context->atom_job_list); // When evaluate scripts like 'element.onclick = null', we needs to remove the event handlers callbacks if (JS_IsNull(value)) { + m_eventHandlerMap.erase(atom); JS_FreeAtom(m_ctx, atom); - list_del(&atomJob->link); - JS_DeleteProperty(m_ctx, m_propertyEventHandler, atom, 0); - return; - } - - if (!JS_IsFunction(m_ctx, value)) { - JS_FreeAtom(m_ctx, atom); - list_del(&atomJob->link); return; } - JSValue newCallback = JS_DupValue(m_ctx, value); - JS_SetProperty(m_ctx, m_propertyEventHandler, atom, newCallback); + m_eventHandlerMap.setProperty(atom, JS_DupValue(m_ctx, value)); - int32_t eventHandlerLen = arrayGetLength(m_ctx, m_eventHandlers); - if (eventHandlerLen == 0) { + if (JS_IsFunction(m_ctx, value) && m_eventListenerMap.empty()) { int32_t contextId = m_context->getContextId(); - NativeString* args_01 = atomToNativeString(m_ctx, atom); + std::unique_ptr args_01 = atomToNativeString(m_ctx, atom); int32_t type = JS_IsFunction(m_ctx, value) ? UICommand::addEvent : UICommand::removeEvent; - foundation::UICommandBuffer::instance(contextId)->addCommand(m_eventTargetId, type, *args_01, nullptr); + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, type, *args_01, nullptr); } } -JSValue EventTargetInstance::getPropertyHandler(JSString* p) { +JSValue EventTargetInstance::getAttributesEventHandler(JSString* p) { char eventType[p->len + 1 - 2]; memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); JSAtom atom = JS_NewAtom(m_ctx, eventType); - if (!JS_HasProperty(m_ctx, m_propertyEventHandler, atom)) { + if (!m_eventHandlerMap.contains(atom)) { + JS_FreeAtom(m_ctx, atom); return JS_NULL; } - return JS_GetProperty(m_ctx, m_propertyEventHandler, atom); + + JSValue handler = JS_DupValue(m_ctx, m_eventHandlerMap.getProperty(atom)); + JS_FreeAtom(m_ctx, atom); + return handler; } void EventTargetInstance::finalize(JSRuntime* rt, JSValue val) { @@ -453,39 +453,40 @@ JSValue EventTargetInstance::getNativeProperty(const char* prop) { return result; } -void EventTargetInstance::gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - // Tell gc eventTargetInstance have these properties. - // Should check object is already inited before gc mark. - if (JS_IsObject(m_eventHandlers)) - JS_MarkValue(rt, m_eventHandlers, mark_func); - if (JS_IsObject(m_propertyEventHandler)) - JS_MarkValue(rt, m_propertyEventHandler, mark_func); - if (JS_IsObject(m_properties)) - JS_MarkValue(rt, m_properties, mark_func); +// JSValues are stored in this class are no visible to QuickJS GC. +// We needs to gc which JSValues are still holding. +void EventTargetInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + // Trace m_eventListeners. + m_eventListenerMap.trace(rt, JS_UNDEFINED, mark_func); + + // Trace m_eventHandlers. + m_eventHandlerMap.trace(rt, JS_UNDEFINED, mark_func); + + // Trace properties. + m_properties.trace(rt, JS_UNDEFINED, mark_func); } void EventTargetInstance::copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode) { - QjsContext* ctx = referenceNode->m_ctx; - JSValue propKeys = objectGetKeys(ctx, referenceNode->m_properties); - uint32_t propKeyLen = arrayGetLength(ctx, propKeys); - - for (int i = 0; i < propKeyLen; i++) { - JSValue k = JS_GetPropertyUint32(ctx, propKeys, i); - JSAtom kt = JS_ValueToAtom(ctx, k); - JSValue v = JS_GetProperty(ctx, referenceNode->m_properties, kt); - JS_SetProperty(ctx, newNode->m_properties, kt, JS_DupValue(ctx, v)); - - JS_FreeAtom(ctx, kt); - JS_FreeValue(ctx, k); - } - - JS_FreeValue(ctx, propKeys); + referenceNode->m_properties.copyWith(&newNode->m_properties); } -void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, NativeString* nativeEventType, void* rawEvent, int32_t isCustomEvent) { +void NativeEventTarget::dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* nativeEventType, void* rawEvent, int32_t isCustomEvent) { assert_m(nativeEventTarget->instance != nullptr, "NativeEventTarget should have owner"); EventTargetInstance* eventTargetInstance = nativeEventTarget->instance; - JSContext* context = eventTargetInstance->context(); + + auto* runtime = ExecutionContext::runtime(); + + // Should avoid dispatch event is ctx is invalid. + if (!isContextValid(contextId)) { + return; + } + + // We should avoid trigger event if eventTarget are no long live on heap. + if (!JS_IsLiveObject(runtime, eventTargetInstance->jsObject)) { + return; + } + + ExecutionContext* context = eventTargetInstance->context(); std::u16string u16EventType = std::u16string(reinterpret_cast(nativeEventType->string), nativeEventType->length); std::string eventType = toUTF8(u16EventType); auto* raw = static_cast(rawEvent); @@ -495,7 +496,7 @@ void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, EventInstance* eventInstance = Event::buildEventInstance(eventType, context, nativeEvent, isCustomEvent == 1); eventInstance->nativeEvent->target = eventTargetInstance; eventTargetInstance->dispatchEvent(eventInstance); - JS_FreeValue(context->ctx(), eventInstance->instanceObject); + JS_FreeValue(context->ctx(), eventInstance->jsObject); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index 6b4d028454..8e31eaaa22 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -7,11 +7,17 @@ #define KRAKENBRIDGE_EVENT_TARGET_H #include "bindings/qjs/dom/event.h" +#include "bindings/qjs/executing_context.h" +#include "bindings/qjs/heap_hashmap.h" #include "bindings/qjs/host_class.h" #include "bindings/qjs/host_object.h" -#include "bindings/qjs/js_context.h" #include "bindings/qjs/native_value.h" #include "bindings/qjs/qjs_patch.h" +#include "event_listener_map.h" + +#if UNIT_TEST +void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +#endif namespace kraken::binding::qjs { @@ -20,15 +26,15 @@ class NativeEventTarget; class CSSStyleDeclaration; class StyleDeclarationInstance; -void bindEventTarget(std::unique_ptr& context); +void bindEventTarget(std::unique_ptr& context); class EventTarget : public HostClass { public: static JSClassID kEventTargetClassId; - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; EventTarget() = delete; - explicit EventTarget(JSContext* context, const char* name); - explicit EventTarget(JSContext* context); + explicit EventTarget(ExecutionContext* context, const char* name); + explicit EventTarget(ExecutionContext* context); static JSClassID classId(); static JSClassID classId(JSValue& value); @@ -36,33 +42,42 @@ class EventTarget : public HostClass { OBJECT_INSTANCE(EventTarget); private: - static JSValue addEventListener(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeEventListener(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue dispatchEvent(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); -#if IS_TEST - static JSValue __kraken_clear_event_listener(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); -#endif + static JSValue addEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue removeEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue dispatchEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - ObjectFunction m_addEventListener{m_context, m_prototypeObject, "addEventListener", addEventListener, 3}; - ObjectFunction m_removeEventListener{m_context, m_prototypeObject, "removeEventListener", removeEventListener, 2}; - ObjectFunction m_dispatchEvent{m_context, m_prototypeObject, "dispatchEvent", dispatchEvent, 1}; -#if IS_TEST - ObjectFunction m_kraken_clear_event_listener_{m_context, m_prototypeObject, "__kraken_clear_event_listeners__", __kraken_clear_event_listener, 0}; -#endif + DEFINE_PROTOTYPE_FUNCTION(addEventListener, 3); + DEFINE_PROTOTYPE_FUNCTION(removeEventListener, 2); + DEFINE_PROTOTYPE_FUNCTION(dispatchEvent, 1); friend EventTargetInstance; }; -using NativeDispatchEvent = void (*)(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); +using NativeDispatchEvent = void (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); using CallNativeMethods = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); struct NativeEventTarget { NativeEventTarget() = delete; explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(NativeEventTarget::dispatchEventImpl){}; - static void dispatchEventImpl(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); + // Add more memory valid check with contextId. + static void dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); EventTargetInstance* instance{nullptr}; NativeDispatchEvent dispatchEvent{nullptr}; +#if UNIT_TEST + CallNativeMethods callNativeMethods{reinterpret_cast(TEST_callNativeMethod)}; +#else CallNativeMethods callNativeMethods{nullptr}; +#endif +}; + +class EventTargetProperties : public HeapHashMap { + public: + EventTargetProperties(JSContext* ctx) : HeapHashMap(ctx){}; +}; + +class EventHandlerMap : public HeapHashMap { + public: + EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; }; class EventTargetInstance : public Instance { @@ -84,20 +99,30 @@ class EventTargetInstance : public Instance { protected: int32_t m_eventTargetId; - JSValue m_eventHandlers{JS_NewObject(m_ctx)}; - JSValue m_propertyEventHandler{JS_NewObject(m_ctx)}; - JSValue m_properties{JS_NewObject(m_ctx)}; + // EventListener handlers registered with addEventListener API. + // https://dom.spec.whatwg.org/#concept-event-listener + EventListenerMap m_eventListenerMap{m_ctx}; + + // EventListener handlers registered with DOM attributes API. + // https://html.spec.whatwg.org/C/#event-handler-attributes + EventHandlerMap m_eventHandlerMap{m_ctx}; + + // When javascript code set a property on EventTarget instance, EventTarget::setProperty callback will be called when + // property are not defined by Object.defineProperty or setProperty. + // We store there values in here. + EventTargetProperties m_properties{m_ctx}; - void gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; static void copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode); - static int hasProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom); - static JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - static int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - static int deleteProperty(QjsContext* ctx, JSValueConst obj, JSAtom prop); + static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); + static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + static int deleteProperty(JSContext* ctx, JSValueConst obj, JSAtom prop); - void setPropertyHandler(JSString* p, JSValue value); - JSValue getPropertyHandler(JSString* p); + // Used for legacy "onEvent" attribute APIs. + void setAttributesEventHandler(JSString* p, JSValue value); + JSValue getAttributesEventHandler(JSString* p); private: bool internalDispatchEvent(EventInstance* eventInstance); diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/bindings/qjs/dom/event_target_test.cc index e57a6535d4..04db3accac 100644 --- a/bridge/bindings/qjs/dom/event_target_test.cc +++ b/bridge/bindings/qjs/dom/event_target_test.cc @@ -4,50 +4,70 @@ */ #include "event_target.h" -#include "bridge_qjs.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(EventTarget, addEventListener) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); - const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f);"; + const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); div.dispatchEvent(new Event('click'));"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); } +TEST(EventTarget, removeEventListener) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + const char* code = + "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(logCalled, false); +} + TEST(EventTarget, setNoEventTargetProperties) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "{name: 1}"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); + auto& context = bridge->getContext(); const char* code = "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); document.body.appendChild(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); } TEST(EventTarget, propertyEventHandler) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "ƒ () 1234"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -59,39 +79,100 @@ TEST(EventTarget, propertyEventHandler) { "let f = div.onclick;" "console.log(f, div.onclick());"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -// TEST(EventTarget, propertyEventOnWindow) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::JSBridge::consoleMessageHandler = [](void *ctx, const std::string &message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "1234"); -// }; -// auto *bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto &context = bridge->getContext(); -// const char* code = "window.onclick = function() { console.log(1234); };" -// "window.dispatchEvent(new Event('click'));"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// delete bridge; -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} +TEST(EventTarget, setUnExpectedAttributeEventHandler) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = false; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + const char* code = + "let div = document.createElement('div'); " + "div.onclick = function() { return 1234; };" + "document.body.appendChild(div);" + "div.onclick = undefined;" + "div.click()"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} + +TEST(EventTarget, propertyEventOnWindow) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "1234"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + const char* code = + "window.onclick = function() { console.log(1234); };" + "window.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(EventTarget, asyncFunctionCallback) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "done"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto& context = bridge->getContext(); + std::string code = R"( + const img = document.createElement('img'); + img.style.width = '100px'; + img.style.height = '100px'; + img.src = "assets/kraken.png"; + document.body.appendChild(img); + const img2 = img.cloneNode(false); + document.body.appendChild(img2); + + let anotherImgHasLoad = false; + async function loadImg() { + if (anotherImgHasLoad) { + console.log('done'); + } else { + anotherImgHasLoad = true; + } + } + + img.addEventListener('load', loadImg); + img2.addEventListener('load', loadImg); + + img.dispatchEvent(new Event('load')); + img2.dispatchEvent(new Event('load')); +)"; + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} TEST(EventTarget, ClassInheritEventTarget) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -107,7 +188,91 @@ let s = new Sample(); console.log(s.addEventListener, s.removeEventListener) )"); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(EventTarget, wontLeakWithStringProperty) { + auto bridge = TEST_init(); + std::string code = + "var img = new Image();\n" + "img.any = '1234'"; + bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +} + +TEST(EventTarget, dispatchEventOnGC) { + using namespace kraken::binding::qjs; + + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "1234"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto& context = bridge->getContext(); + std::string code = std::string(R"( +{ +// Wrap div in a block scope will be freed by GC +let div = document.createElement('div'); +} +window.onclick = () => {console.log(1234);} + +setTimeout(() => {}); +)"); + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + static auto* window = static_cast(JS_GetOpaque(context->global(), 1)); + static int32_t contextId = context->getContextId(); + + TEST_registerEventTargetDisposedCallback(context->uniqueId, [](EventTargetInstance* eventTargetInstance) { + // Check to not crash when trigger click on disposed eventTarget + TEST_dispatchEvent(contextId, eventTargetInstance, "click"); + + // Check to not crash when trigger event on any eventTarget. + TEST_dispatchEvent(contextId, window, "click"); + }); + + // Run gc to trigger eventTarget been disposed by GC. + JS_RunGC(context->runtime()); + + TEST_runLoop(context.get()); + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } + +TEST(EventTarget, globalBindListener) { + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "clicked"); + }; + auto bridge = TEST_init(); + std::string code = "addEventListener('click', () => {console.log('clicked'); }); dispatchEvent(new Event('click'))"; + bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); + EXPECT_EQ(logCalled, true); +} + +TEST(EventTarget, shouldKeepAtom) { + auto bridge = TEST_init(); + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "2"); + }; + std::string code = "addEventListener('click', () => {console.log(1)});"; + bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); + JS_RunGC(bridge->getContext()->runtime()); + + std::string code2 = "addEventListener('appear', () => {console.log(2)});"; + bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); + + JS_RunGC(bridge->getContext()->runtime()); + + std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; + bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/bindings/qjs/dom/event_test.cc b/bridge/bindings/qjs/dom/event_test.cc new file mode 100644 index 0000000000..a78d6e8694 --- /dev/null +++ b/bridge/bindings/qjs/dom/event_test.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_target.h" +#include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" + +TEST(MouseEvent, init) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "10"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto& context = bridge->getContext(); + const char* code = "let mouseEvent = new MouseEvent('click', {clientX: 10, clientY: 20}); console.log(mouseEvent.clientX);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/bindings/qjs/dom/events/touch_event.cc b/bridge/bindings/qjs/dom/events/touch_event.cc index fee9b6c550..2cff68a97d 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.cc +++ b/bridge/bindings/qjs/dom/events/touch_event.cc @@ -5,18 +5,18 @@ #include "touch_event.h" #include "bindings/qjs/qjs_patch.h" -#include "bridge_qjs.h" +#include "page.h" namespace kraken::binding::qjs { -void bindTouchEvent(std::unique_ptr& context) { +void bindTouchEvent(std::unique_ptr& context) { auto* constructor = TouchEvent::instance(context.get()); - context->defineGlobalProperty("TouchEvent", constructor->classObject); + context->defineGlobalProperty("TouchEvent", constructor->jsObject); } -TouchList::TouchList(JSContext* context, NativeTouch** touches, int64_t length) : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} +TouchList::TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length) : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} -JSValue TouchList::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue TouchList::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { std::string key = jsAtomToStdString(ctx, atom); if (isNumberIndex(key)) { size_t index = std::stoi(key); @@ -26,130 +26,85 @@ JSValue TouchList::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValu return JS_NULL; } -int TouchList::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +int TouchList::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { return 0; } -PROP_GETTER(TouchList, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* touchList = static_cast(JS_GetOpaque(this_val, JSContext::kHostExoticObjectClassId)); +IMPL_PROPERTY_GETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* touchList = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostExoticObjectClassId)); return JS_NewUint32(ctx, touchList->_length); } -PROP_SETTER(TouchList, length)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NULL; } -Touch::Touch(JSContext* context, NativeTouch* nativeTouch) : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} +Touch::Touch(ExecutionContext* context, NativeTouch* nativeTouch) : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} -PROP_GETTER(Touch, identifier)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, identifier)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewUint32(ctx, object->m_nativeTouch->identifier); } -PROP_SETTER(Touch, identifier)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, target)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, target)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); auto* eventTarget = object->m_nativeTouch->target; - return JS_DupValue(ctx, eventTarget->instance->instanceObject); -} -PROP_SETTER(Touch, target)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return JS_DupValue(ctx, eventTarget->instance->jsObject); } -PROP_GETTER(Touch, clientX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, clientX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->clientX); } -PROP_SETTER(Touch, clientX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, clientY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, clientY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->clientY); } -PROP_SETTER(Touch, clientY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, screenX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, screenX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->screenX); } -PROP_SETTER(Touch, screenX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, screenY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, screenY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->screenY); } -PROP_SETTER(Touch, screenY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, pageX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, pageX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->pageX); } -PROP_SETTER(Touch, pageX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, pageY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, pageY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->pageY); } -PROP_SETTER(Touch, pageY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, radiusX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, radiusX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->radiusX); } -PROP_SETTER(Touch, radiusX)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, radiusY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, radiusY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->radiusY); } -PROP_SETTER(Touch, radiusY)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, rotationAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, rotationAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->rotationAngle); } -PROP_SETTER(Touch, rotationAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, force)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, force)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->force); } -PROP_SETTER(Touch, force)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, altitudeAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, altitudeAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->altitudeAngle); } -PROP_SETTER(Touch, altitudeAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, azimuthAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, azimuthAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, object->m_nativeTouch->azimuthAngle); } -PROP_SETTER(Touch, azimuthAngle)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(Touch, touchType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); +IMPL_PROPERTY_GETTER(Touch, touchType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewUint32(ctx, object->m_nativeTouch->touchType); } -PROP_SETTER(Touch, touchType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -TouchEvent::TouchEvent(JSContext* context) : Event(context) {} +TouchEvent::TouchEvent(ExecutionContext* context) : Event(context) {} -JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue TouchEvent::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to construct 'TouchEvent': 1 argument required, but only 0 present."); } @@ -162,7 +117,7 @@ JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVal } auto* nativeEvent = new NativeTouchEvent(); - nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue); + nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue).release(); if (JS_IsObject(eventInit)) { JSAtom touchesAtom = JS_NewAtom(m_ctx, "touches"); @@ -185,8 +140,8 @@ JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVal ne->touchLength = length; for (int i = 0; i < length; i++) { JSValue v = JS_GetPropertyUint32(ctx, touchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->classObject)) { - ne->touches[i] = static_cast(JS_GetOpaque(v, JSContext::kHostObjectClassId)); + if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { + ne->touches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); } } } @@ -202,8 +157,8 @@ JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVal ne->targetTouchesLength = length; for (int i = 0; i < length; i++) { JSValue v = JS_GetPropertyUint32(ctx, targetTouchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->classObject)) { - ne->targetTouches[i] = static_cast(JS_GetOpaque(v, JSContext::kHostObjectClassId)); + if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { + ne->targetTouches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); } } } @@ -219,8 +174,8 @@ JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVal ne->changedTouchesLength = length; for (int i = 0; i < length; i++) { JSValue v = JS_GetPropertyUint32(ctx, changedTouchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->classObject)) { - ne->changedTouches[i] = static_cast(JS_GetOpaque(v, JSContext::kHostObjectClassId)); + if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { + ne->changedTouches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); } } } @@ -248,67 +203,52 @@ JSValue TouchEvent::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSVal } auto event = new TouchEventInstance(this, reinterpret_cast(nativeEvent)); - return event->instanceObject; + return event->jsObject; } -PROP_GETTER(TouchEventInstance, touches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(TouchEvent, touches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); auto* touchList = new TouchList(event->m_context, nativeEvent->touches, nativeEvent->touchLength); return touchList->jsObject; } -PROP_SETTER(TouchEventInstance, touches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, targetTouches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, targetTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); auto* targetTouchList = new TouchList(event->m_context, nativeEvent->targetTouches, nativeEvent->targetTouchesLength); return targetTouchList->jsObject; } -PROP_SETTER(TouchEventInstance, targetTouches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, changedTouches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, changedTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); auto* changedTouchList = new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); return changedTouchList->jsObject; } -PROP_SETTER(TouchEventInstance, changedTouches)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, altKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, altKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); return JS_NewBool(ctx, nativeEvent->altKey ? 1 : 0); } -PROP_SETTER(TouchEventInstance, altKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, metaKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, metaKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); return JS_NewBool(ctx, nativeEvent->metaKey ? 1 : 0); } -PROP_SETTER(TouchEventInstance, metaKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, ctrlKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, ctrlKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); return JS_NewBool(ctx, nativeEvent->ctrlKey ? 1 : 0); } -PROP_SETTER(TouchEventInstance, ctrlKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(TouchEventInstance, shiftKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); return JS_NewBool(ctx, nativeEvent->shiftKey ? 1 : 0); } -PROP_SETTER(TouchEventInstance, shiftKey)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} diff --git a/bridge/bindings/qjs/dom/events/touch_event.h b/bridge/bindings/qjs/dom/events/touch_event.h index a401884293..d688d437c7 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.h +++ b/bridge/bindings/qjs/dom/events/touch_event.h @@ -10,7 +10,7 @@ namespace kraken::binding::qjs { -void bindTouchEvent(std::unique_ptr& context); +void bindTouchEvent(std::unique_ptr& context); struct NativeTouch { int64_t identifier; @@ -33,23 +33,37 @@ struct NativeTouch { class Touch : public HostObject { public: Touch() = delete; - explicit Touch(JSContext* context, NativeTouch* nativePtr); + explicit Touch(ExecutionContext* context, NativeTouch* nativePtr); private: NativeTouch* m_nativeTouch{nullptr}; - DEFINE_HOST_OBJECT_PROPERTY(15, identifier, target, clientX, clientY, screenX, screenY, pageX, pageY, radiusX, radiusY, rotationAngle, force, altitudeAngle, azimuthAngle, touchType) + DEFINE_READONLY_PROPERTY(identifier); + DEFINE_READONLY_PROPERTY(target); + DEFINE_READONLY_PROPERTY(clientX); + DEFINE_READONLY_PROPERTY(clientY); + DEFINE_READONLY_PROPERTY(screenX); + DEFINE_READONLY_PROPERTY(screenY); + DEFINE_READONLY_PROPERTY(pageX); + DEFINE_READONLY_PROPERTY(pageY); + DEFINE_READONLY_PROPERTY(radiusX); + DEFINE_READONLY_PROPERTY(radiusY); + DEFINE_READONLY_PROPERTY(rotationAngle); + DEFINE_READONLY_PROPERTY(force); + DEFINE_READONLY_PROPERTY(altitudeAngle); + DEFINE_READONLY_PROPERTY(azimuthAngle); + DEFINE_READONLY_PROPERTY(touchType); }; class TouchList : public ExoticHostObject { public: TouchList() = delete; - explicit TouchList(JSContext* context, NativeTouch** touches, int64_t length); + explicit TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length); - JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); private: - DEFINE_HOST_OBJECT_PROPERTY(1, length) + DEFINE_PROPERTY(length); NativeTouch** m_touches{nullptr}; int64_t _length; }; @@ -69,23 +83,34 @@ struct NativeTouchEvent { int64_t ctrlKey; int64_t shiftKey; }; +class TouchEventInstance; class TouchEvent : public Event { public: TouchEvent() = delete; - explicit TouchEvent(JSContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + explicit TouchEvent(ExecutionContext* context); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; OBJECT_INSTANCE(TouchEvent); private: + DEFINE_PROTOTYPE_READONLY_PROPERTY(touches); + DEFINE_PROTOTYPE_READONLY_PROPERTY(targetTouches); + DEFINE_PROTOTYPE_READONLY_PROPERTY(changedTouches); + DEFINE_PROTOTYPE_READONLY_PROPERTY(altKey); + DEFINE_PROTOTYPE_READONLY_PROPERTY(metaKey); + DEFINE_PROTOTYPE_READONLY_PROPERTY(ctrlKey); + DEFINE_PROTOTYPE_READONLY_PROPERTY(shiftKey); + + friend TouchEventInstance; }; + class TouchEventInstance : public EventInstance { public: TouchEventInstance() = delete; explicit TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent); private: - DEFINE_HOST_CLASS_PROPERTY(7, touches, targetTouches, changedTouches, altKey, metaKey, ctrlKey, shiftKey) + friend TouchEvent; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc b/bridge/bindings/qjs/dom/frame_request_callback_collection.cc new file mode 100644 index 0000000000..fb9837b2f8 --- /dev/null +++ b/bridge/bindings/qjs/dom/frame_request_callback_collection.cc @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "frame_request_callback_collection.h" + +namespace kraken::binding::qjs { + +JSClassID FrameCallback::classId{0}; +FrameCallback::FrameCallback(JSValue callback) : m_callback(callback) {} + +void FrameCallback::fire(double highResTimeStamp) { + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + if (!JS_IsFunction(m_ctx, m_callback)) + return; + + /* 'callback' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + JS_DupValue(m_ctx, m_callback); + + JSValue arguments[] = {JS_NewFloat64(m_ctx, highResTimeStamp)}; + + JSValue returnValue = JS_Call(m_ctx, m_callback, JS_UNDEFINED, 1, arguments); + JS_FreeValue(m_ctx, m_callback); + + if (JS_IsException(returnValue)) { + context->handleException(&returnValue); + } + + JS_FreeValue(m_ctx, returnValue); +} + +void FrameCallback::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + JS_MarkValue(rt, m_callback, mark_func); +} + +void FrameCallback::dispose() const { + JS_FreeValueRT(m_runtime, m_callback); +} + +void FrameRequestCallbackCollection::registerFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { + m_frameCallbacks[callbackId] = frameCallback; +} + +void FrameRequestCallbackCollection::cancelFrameCallback(uint32_t callbackId) { + if (m_frameCallbacks.count(callbackId) == 0) + return; + FrameCallback* callback = m_frameCallbacks[callbackId]; + + // Push this timer to abandoned list to mark this timer is deprecated. + m_abandonedCallbacks.emplace_back(callback); + + m_frameCallbacks.erase(callbackId); +} + +void FrameRequestCallbackCollection::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + for (auto& callback : m_frameCallbacks) { + JS_MarkValue(rt, callback.second->toQuickJS(), mark_func); + } + + // Recycle all abandoned callbacks. + if (!m_abandonedCallbacks.empty()) { + for (auto& callback : m_abandonedCallbacks) { + JS_MarkValue(rt, callback->toQuickJS(), mark_func); + } + // All abandoned timers should be freed at the sweep stage. + m_abandonedCallbacks.clear(); + } +} + +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.h b/bridge/bindings/qjs/dom/frame_request_callback_collection.h new file mode 100644 index 0000000000..8327555b7a --- /dev/null +++ b/bridge/bindings/qjs/dom/frame_request_callback_collection.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ + +#include "bindings/qjs/executing_context.h" + +namespace kraken::binding::qjs { + +// |FrameCallback| is an interface type which generalizes callbacks which are +// invoked when a script-based animation needs to be resampled. +class FrameCallback : public GarbageCollected { + public: + static JSClassID classId; + + FrameCallback(JSValue callback); + + void fire(double highResTimeStamp); + + [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "FrameCallback"; } + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: + JSValue m_callback{JS_NULL}; + int32_t m_callbackId{-1}; +}; + +class FrameRequestCallbackCollection final { + public: + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + void registerFrameCallback(uint32_t callbackId, FrameCallback* frameCallback); + void cancelFrameCallback(uint32_t callbackId); + + private: + std::unordered_map m_frameCallbacks; + std::vector m_abandonedCallbacks; +}; + +} // namespace kraken::binding::qjs + +class frame_request_callback_collection {}; + +#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/bindings/qjs/dom/node.cc index 08e2bf08fa..63cfc06efb 100644 --- a/bridge/bindings/qjs/dom/node.cc +++ b/bridge/bindings/qjs/dom/node.cc @@ -14,12 +14,12 @@ namespace kraken::binding::qjs { -void bindNode(std::unique_ptr& context) { +void bindNode(std::unique_ptr& context) { auto* constructor = Node::instance(context.get()); - context->defineGlobalProperty("Node", constructor->classObject); + context->defineGlobalProperty("Node", constructor->jsObject); } -JSValue Node::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue Node::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { return JS_ThrowTypeError(ctx, "Illegal constructor"); } @@ -37,7 +37,7 @@ JSClassID Node::classId(JSValue& value) { return 0; } -JSValue Node::cloneNode(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Node::cloneNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); JSValue deepValue; @@ -59,13 +59,13 @@ JSValue Node::cloneNode(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar if (deep) { traverseCloneNode(ctx, selfInstance, newElementInstance); } - return newElementInstance->instanceObject; + return newElementInstance->jsObject; } else if (selfInstance->nodeType == NodeType::TEXT_NODE) { auto textNode = static_cast(selfInstance); JSValue newTextNode = copyNodeValue(ctx, static_cast(textNode)); return newTextNode; } else if (selfInstance->nodeType == NodeType::DOCUMENT_FRAGMENT_NODE) { - JSValue newFragment = JS_CallConstructor(ctx, DocumentFragment::instance(selfInstance->m_context)->classObject, 0, nullptr); + JSValue newFragment = JS_CallConstructor(ctx, DocumentFragment::instance(selfInstance->m_context)->jsObject, 0, nullptr); auto* newFragmentInstance = static_cast(JS_GetOpaque(newFragment, Node::classId(newFragment))); if (deep) { @@ -76,7 +76,8 @@ JSValue Node::cloneNode(QjsContext* ctx, JSValue this_val, int argc, JSValue* ar } return JS_NULL; } -JSValue Node::appendChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Node::appendChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first argument is required."); } @@ -96,7 +97,7 @@ JSValue Node::appendChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type."); } - if (nodeInstance->m_eventTargetId == HTML_TARGET_ID || nodeInstance == selfInstance) { + if (nodeInstance == selfInstance) { return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."); } @@ -115,14 +116,14 @@ JSValue Node::appendChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* selfInstance->internalAppendChild(nodeInstance); } - return JS_DupValue(ctx, nodeInstance->instanceObject); + return JS_DupValue(ctx, nodeInstance->jsObject); } -JSValue Node::remove(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Node::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); selfInstance->internalRemove(); return JS_UNDEFINED; } -JSValue Node::removeChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue Node::removeChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'removeChild' on 'Node': 1 arguments required"); } @@ -141,9 +142,10 @@ JSValue Node::removeChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* } auto removedNode = selfInstance->internalRemoveChild(nodeInstance); - return JS_DupValue(ctx, removedNode->instanceObject); + return JS_DupValue(ctx, removedNode->jsObject); } -JSValue Node::insertBefore(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Node::insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': 2 arguments is required."); } @@ -188,7 +190,8 @@ JSValue Node::insertBefore(QjsContext* ctx, JSValue this_val, int argc, JSValue* return JS_NULL; } -JSValue Node::replaceChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +JSValue Node::replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 2 arguments required"); } @@ -208,7 +211,7 @@ JSValue Node::replaceChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* auto newChildInstance = static_cast(JS_GetOpaque(newChildValue, Node::classId(newChildValue))); auto oldChildInstance = static_cast(JS_GetOpaque(oldChildValue, Node::classId(oldChildValue))); - if (oldChildInstance == nullptr || JS_VALUE_GET_PTR(oldChildInstance->parentNode) != JS_VALUE_GET_PTR(selfInstance->instanceObject) || oldChildInstance->document() != selfInstance->document()) { + if (oldChildInstance == nullptr || JS_VALUE_GET_PTR(oldChildInstance->parentNode) != JS_VALUE_GET_PTR(selfInstance->jsObject) || oldChildInstance->document() != selfInstance->document()) { return JS_ThrowTypeError(ctx, "Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node."); } @@ -231,10 +234,10 @@ JSValue Node::replaceChild(QjsContext* ctx, JSValue this_val, int argc, JSValue* selfInstance->ensureDetached(newChildInstance); selfInstance->internalReplaceChild(newChildInstance, oldChildInstance); } - return JS_DupValue(ctx, oldChildInstance->instanceObject); + return JS_DupValue(ctx, oldChildInstance->jsObject); } -void Node::traverseCloneNode(QjsContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode) { +void Node::traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode) { int32_t len = arrayGetLength(ctx, baseNode->childNodes); for (int i = 0; i < len; i++) { JSValue n = JS_GetPropertyUint32(ctx, baseNode->childNodes, i); @@ -252,7 +255,7 @@ void Node::traverseCloneNode(QjsContext* ctx, NodeInstance* baseNode, NodeInstan } } -JSValue Node::copyNodeValue(QjsContext* ctx, NodeInstance* node) { +JSValue Node::copyNodeValue(JSContext* ctx, NodeInstance* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { auto* element = reinterpret_cast(node); @@ -260,7 +263,7 @@ JSValue Node::copyNodeValue(QjsContext* ctx, NodeInstance* node) { std::string tagName = element->getRegisteredTagName(); JSValue tagNameValue = JS_NewString(element->m_ctx, tagName.c_str()); JSValue arguments[] = {tagNameValue}; - JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), Element::instance(element->context())->classObject, 1, arguments); + JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), Element::instance(element->context())->jsObject, 1, arguments); JS_FreeValue(ctx, tagNameValue); auto* newElement = static_cast(JS_GetOpaque(newElementValue, Node::classId(newElementValue))); @@ -275,105 +278,81 @@ JSValue Node::copyNodeValue(QjsContext* ctx, NodeInstance* node) { ElementInstance::copyNodeProperties(newElement, element); std::string newNodeEventTargetId = std::to_string(newElement->m_eventTargetId); - NativeString* args_01 = stringToNativeString(newNodeEventTargetId); - foundation::UICommandBuffer::instance(newElement->context()->getContextId())->addCommand(element->m_eventTargetId, UICommand::cloneNode, *args_01, nullptr); + std::unique_ptr args_01 = stringToNativeString(newNodeEventTargetId); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::cloneNode, *args_01, nullptr); - return newElement->instanceObject; + return newElement->jsObject; } else if (node->nodeType == TEXT_NODE) { auto* textNode = reinterpret_cast(node); JSValue textContent = textNode->internalGetTextContent(); JSValue arguments[] = {textContent}; - JSValue result = JS_CallConstructor(ctx, TextNode::instance(textNode->m_context)->classObject, 1, arguments); + JSValue result = JS_CallConstructor(ctx, TextNode::instance(textNode->m_context)->jsObject, 1, arguments); JS_FreeValue(ctx, textContent); return result; } return JS_NULL; } -PROP_GETTER(NodeInstance, isConnected)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, isConnected)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); return JS_NewBool(ctx, nodeInstance->isConnected()); } -PROP_SETTER(NodeInstance, isConnected)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(NodeInstance, ownerDocument)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, ownerDocument)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return JS_DupValue(ctx, nodeInstance->m_document->instanceObject); -} -PROP_SETTER(NodeInstance, ownerDocument)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return JS_DupValue(ctx, nodeInstance->m_document->jsObject); } -PROP_GETTER(NodeInstance, firstChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, firstChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); auto* instance = nodeInstance->firstChild(); - return instance != nullptr ? instance->instanceObject : JS_NULL; -} -PROP_SETTER(NodeInstance, firstChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return instance != nullptr ? instance->jsObject : JS_NULL; } -PROP_GETTER(NodeInstance, lastChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, lastChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); auto* instance = nodeInstance->lastChild(); - return instance != nullptr ? instance->instanceObject : JS_NULL; -} -PROP_SETTER(NodeInstance, lastChild)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return instance != nullptr ? instance->jsObject : JS_NULL; } -PROP_GETTER(NodeInstance, parentNode)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, parentNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); return JS_DupValue(ctx, nodeInstance->parentNode); } -PROP_SETTER(NodeInstance, parentNode)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(NodeInstance, previousSibling)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, previousSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); auto* instance = nodeInstance->previousSibling(); - return instance != nullptr ? instance->instanceObject : JS_NULL; -} -PROP_SETTER(NodeInstance, previousSibling)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return instance != nullptr ? instance->jsObject : JS_NULL; } -PROP_GETTER(NodeInstance, nextSibling)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, nextSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); auto* instance = nodeInstance->nextSibling(); - return instance != nullptr ? instance->instanceObject : JS_NULL; -} -PROP_SETTER(NodeInstance, nextSibling)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; + return instance != nullptr ? instance->jsObject : JS_NULL; } -PROP_GETTER(NodeInstance, nodeType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, nodeType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); return JS_NewUint32(ctx, nodeInstance->nodeType); } -PROP_SETTER(NodeInstance, nodeType)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -PROP_GETTER(NodeInstance, textContent)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); return nodeInstance->internalGetTextContent(); } -PROP_SETTER(NodeInstance, textContent)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); nodeInstance->internalSetTextContent(argv[0]); return JS_NULL; } bool NodeInstance::isConnected() { - bool _isConnected = m_eventTargetId == HTML_TARGET_ID; + bool _isConnected = this == document(); auto parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); while (parent != nullptr && !_isConnected) { - _isConnected = parent->m_eventTargetId == HTML_TARGET_ID; + _isConnected = parent == document(); JSValue parentParentNode = parent->parentNode; parent = static_cast(JS_GetOpaque(parentParentNode, Node::classId(parentParentNode))); } @@ -409,7 +388,7 @@ NodeInstance* NodeInstance::previousSibling() { auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); auto parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, instanceObject); + int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, jsObject); int32_t parentChildNodeLen = arrayGetLength(m_ctx, parentChildNodes); if (idx - 1 < parentChildNodeLen) { @@ -424,7 +403,7 @@ NodeInstance* NodeInstance::nextSibling() { return nullptr; auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); auto parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, instanceObject); + int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, jsObject); int32_t parentChildNodeLen = arrayGetLength(m_ctx, parentChildNodes); if (idx + 1 < parentChildNodeLen) { @@ -435,7 +414,7 @@ NodeInstance* NodeInstance::nextSibling() { return nullptr; } void NodeInstance::internalAppendChild(NodeInstance* node) { - arrayPushValue(m_ctx, childNodes, node->instanceObject); + arrayPushValue(m_ctx, childNodes, node->jsObject); node->setParentNode(this); node->_notifyNodeInsert(this); @@ -443,10 +422,10 @@ void NodeInstance::internalAppendChild(NodeInstance* node) { std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); std::string position = std::string("beforeend"); - NativeString* args_01 = stringToNativeString(nodeEventTargetId); - NativeString* args_02 = stringToNativeString(position); + std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); + std::unique_ptr args_02 = stringToNativeString(position); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); } void NodeInstance::internalRemove() { if (JS_IsNull(parentNode)) @@ -462,20 +441,20 @@ void NodeInstance::internalClearChild() { auto* node = static_cast(JS_GetOpaque(v, Node::classId(v))); node->removeParentNode(); node->_notifyNodeRemoved(this); - foundation::UICommandBuffer::instance(node->m_context->getContextId())->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); + node->m_context->uiCommandBuffer()->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); JS_FreeValue(m_ctx, v); } JS_SetPropertyStr(m_ctx, childNodes, "length", JS_NewUint32(m_ctx, 0)); } NodeInstance* NodeInstance::internalRemoveChild(NodeInstance* node) { - int32_t idx = arrayFindIdx(m_ctx, childNodes, node->instanceObject); + int32_t idx = arrayFindIdx(m_ctx, childNodes, node->jsObject); if (idx != -1) { arraySpliceValue(m_ctx, childNodes, idx, 1); node->removeParentNode(); node->_notifyNodeRemoved(this); - foundation::UICommandBuffer::instance(node->m_context->getContextId())->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); + node->m_context->uiCommandBuffer()->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); } return node; @@ -484,7 +463,7 @@ JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* ref if (referenceNode == nullptr) { internalAppendChild(node); } else { - if (JS_VALUE_GET_PTR(referenceNode->parentNode) != JS_VALUE_GET_PTR(instanceObject)) { + if (JS_VALUE_GET_PTR(referenceNode->parentNode) != JS_VALUE_GET_PTR(jsObject)) { return JS_ThrowTypeError(m_ctx, "Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': reference node is not a child of this node."); } @@ -492,23 +471,23 @@ JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* ref auto* parent = static_cast(JS_GetOpaque(parentNodeValue, Node::classId(parentNodeValue))); if (parent != nullptr) { JSValue parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, referenceNode->instanceObject); + int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, referenceNode->jsObject); if (idx == -1) { return JS_ThrowTypeError(m_ctx, "Failed to execute 'insertBefore' on 'Node': reference node is not a child of this node."); } - arrayInsert(m_ctx, parentChildNodes, idx, node->instanceObject); + arrayInsert(m_ctx, parentChildNodes, idx, node->jsObject); node->setParentNode(parent); node->_notifyNodeInsert(parent); std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); std::string position = std::string("beforebegin"); - NativeString* args_01 = stringToNativeString(nodeEventTargetId); - NativeString* args_02 = stringToNativeString(position); + std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); + std::unique_ptr args_02 = stringToNativeString(position); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(referenceNode->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); + m_context->uiCommandBuffer()->addCommand(referenceNode->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); } } @@ -522,14 +501,14 @@ JSValue NodeInstance::internalReplaceChild(NodeInstance* newChild, NodeInstance* assert_m(JS_IsNull(newChild->parentNode), "ReplaceChild Error: newChild was not detached."); oldChild->removeParentNode(); - int32_t childIndex = arrayFindIdx(m_ctx, childNodes, oldChild->instanceObject); + int32_t childIndex = arrayFindIdx(m_ctx, childNodes, oldChild->jsObject); if (childIndex == -1) { return JS_ThrowTypeError(m_ctx, "Failed to execute 'replaceChild' on 'Node': old child is not exist on childNodes."); } newChild->setParentNode(this); - arraySpliceValue(m_ctx, childNodes, childIndex, 1, newChild->instanceObject); + arraySpliceValue(m_ctx, childNodes, childIndex, 1, newChild->jsObject); oldChild->_notifyNodeRemoved(this); newChild->_notifyNodeInsert(this); @@ -537,14 +516,14 @@ JSValue NodeInstance::internalReplaceChild(NodeInstance* newChild, NodeInstance* std::string newChildEventTargetId = std::to_string(newChild->m_eventTargetId); std::string position = std::string("afterend"); - NativeString* args_01 = stringToNativeString(newChildEventTargetId); - NativeString* args_02 = stringToNativeString(position); + std::unique_ptr args_01 = stringToNativeString(newChildEventTargetId); + std::unique_ptr args_02 = stringToNativeString(position); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(oldChild->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); + m_context->uiCommandBuffer()->addCommand(oldChild->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(oldChild->m_eventTargetId, UICommand::removeNode, nullptr); + m_context->uiCommandBuffer()->addCommand(oldChild->m_eventTargetId, UICommand::removeNode, nullptr); - return oldChild->instanceObject; + return oldChild->jsObject; } void NodeInstance::setParentNode(NodeInstance* parent) { @@ -552,7 +531,7 @@ void NodeInstance::setParentNode(NodeInstance* parent) { JS_FreeValue(m_ctx, parentNode); } - parentNode = JS_DupValue(m_ctx, parent->instanceObject); + parentNode = JS_DupValue(m_ctx, parent->jsObject); } void NodeInstance::removeParentNode() { @@ -565,12 +544,12 @@ void NodeInstance::removeParentNode() { NodeInstance::~NodeInstance() {} void NodeInstance::refer() { - JS_DupValue(m_ctx, instanceObject); + JS_DupValue(m_ctx, jsObject); list_add_tail(&nodeLink.link, &m_context->node_job_list); } void NodeInstance::unrefer() { list_del(&nodeLink.link); - JS_FreeValue(m_ctx, instanceObject); + JS_FreeValue(m_ctx, jsObject); } void NodeInstance::_notifyNodeRemoved(NodeInstance* node) {} void NodeInstance::_notifyNodeInsert(NodeInstance* node) {} @@ -578,7 +557,7 @@ void NodeInstance::ensureDetached(NodeInstance* node) { auto* nodeParent = static_cast(JS_GetOpaque(node->parentNode, Node::classId(node->parentNode))); if (nodeParent != nullptr) { - int32_t idx = arrayFindIdx(m_ctx, nodeParent->childNodes, node->instanceObject); + int32_t idx = arrayFindIdx(m_ctx, nodeParent->childNodes, node->jsObject); if (idx != -1) { node->_notifyNodeRemoved(nodeParent); arraySpliceValue(m_ctx, nodeParent->childNodes, idx, 1); @@ -587,8 +566,8 @@ void NodeInstance::ensureDetached(NodeInstance* node) { } } -void NodeInstance::gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::gcMark(rt, val, mark_func); +void NodeInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + EventTargetInstance::trace(rt, val, mark_func); // Should check object is already inited before gc mark. if (JS_IsObject(parentNode)) diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index b0e43bb296..ab708d882b 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -13,7 +13,7 @@ namespace kraken::binding::qjs { -void bindNode(std::unique_ptr& context); +void bindNode(std::unique_ptr& context); enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11 }; @@ -25,34 +25,45 @@ class TextNodeInstance; class Node : public EventTarget { public: Node() = delete; - Node(JSContext* context, const std::string& className) : EventTarget(context, className.c_str()) { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } - Node(JSContext* context) : EventTarget(context, "Node") { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } + Node(ExecutionContext* context, const std::string& className) : EventTarget(context, className.c_str()) { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } + Node(ExecutionContext* context) : EventTarget(context, "Node") { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } OBJECT_INSTANCE(Node); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; static JSClassID classId(); static JSClassID classId(JSValue& value); - static JSValue cloneNode(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue appendChild(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue remove(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeChild(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue insertBefore(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue replaceChild(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue cloneNode(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue appendChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue removeChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue insertBefore(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue replaceChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); private: - ObjectFunction m_cloneNode{m_context, m_prototypeObject, "cloneNode", cloneNode, 1}; - ObjectFunction m_appendChild{m_context, m_prototypeObject, "appendChild", appendChild, 1}; - ObjectFunction m_remove{m_context, m_prototypeObject, "remove", remove, 0}; - ObjectFunction m_removeChild{m_context, m_prototypeObject, "removeChild", removeChild, 1}; - ObjectFunction m_insertBefore{m_context, m_prototypeObject, "insertBefore", insertBefore, 2}; - ObjectFunction m_replaceChild{m_context, m_prototypeObject, "replaceChild", replaceChild, 2}; - - static void traverseCloneNode(QjsContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode); - static JSValue copyNodeValue(QjsContext* ctx, NodeInstance* node); + DEFINE_PROTOTYPE_PROPERTY(textContent); + + DEFINE_PROTOTYPE_READONLY_PROPERTY(isConnected); + DEFINE_PROTOTYPE_READONLY_PROPERTY(ownerDocument); + DEFINE_PROTOTYPE_READONLY_PROPERTY(firstChild); + DEFINE_PROTOTYPE_READONLY_PROPERTY(lastChild); + DEFINE_PROTOTYPE_READONLY_PROPERTY(parentNode); + DEFINE_PROTOTYPE_READONLY_PROPERTY(previousSibling); + DEFINE_PROTOTYPE_READONLY_PROPERTY(nextSibling); + DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeType); + + DEFINE_PROTOTYPE_FUNCTION(cloneNode, 1); + DEFINE_PROTOTYPE_FUNCTION(appendChild, 1); + DEFINE_PROTOTYPE_FUNCTION(remove, 0); + DEFINE_PROTOTYPE_FUNCTION(removeChild, 1); + DEFINE_PROTOTYPE_FUNCTION(insertBefore, 2); + DEFINE_PROTOTYPE_FUNCTION(replaceChild, 2); + + static void traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode); + static JSValue copyNodeValue(JSContext* ctx, NodeInstance* node); friend ElementInstance; friend TextNodeInstance; }; @@ -64,17 +75,17 @@ struct NodeJob { class NodeInstance : public EventTargetInstance { public: - enum class NodeFlag : uint32_t { IsDocumentFragment = 1 << 0 }; + enum class NodeFlag : uint32_t { IsDocumentFragment = 1 << 0, IsTemplateElement = 1 << 1 }; mutable std::set m_nodeFlags; bool hasNodeFlag(NodeFlag flag) const { return m_nodeFlags.size() != 0 && m_nodeFlags.find(flag) != m_nodeFlags.end(); } void setNodeFlag(NodeFlag flag) const { m_nodeFlags.insert(flag); } void removeNodeFlag(NodeFlag flag) const { m_nodeFlags.erase(flag); } NodeInstance() = delete; - explicit NodeInstance(Node* node, NodeType nodeType, DocumentInstance* document, JSClassID classId, std::string name) - : EventTargetInstance(node, classId, std::move(name)), m_document(document), nodeType(nodeType) {} - explicit NodeInstance(Node* node, NodeType nodeType, DocumentInstance* document, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) - : EventTargetInstance(node, classId, exoticMethods, name), m_document(document), nodeType(nodeType) {} + explicit NodeInstance(Node* node, NodeType nodeType, JSClassID classId, std::string name) + : EventTargetInstance(node, classId, std::move(name)), m_document(m_context->document()), nodeType(nodeType) {} + explicit NodeInstance(Node* node, NodeType nodeType, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) + : EventTargetInstance(node, classId, exoticMethods, name), m_document(m_context->document()), nodeType(nodeType) {} ~NodeInstance(); bool isConnected(); DocumentInstance* ownerDocument(); @@ -98,7 +109,6 @@ class NodeInstance : public EventTargetInstance { JSValue childNodes{JS_NewArray(m_ctx)}; NodeJob nodeLink{this}; - NodeJob documentLink{this}; void refer(); void unrefer(); @@ -108,12 +118,11 @@ class NodeInstance : public EventTargetInstance { virtual void _notifyNodeInsert(NodeInstance* node); protected: - void gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; private: - DEFINE_HOST_CLASS_PROPERTY(9, isConnected, ownerDocument, firstChild, lastChild, parentNode, previousSibling, nextSibling, nodeType, textContent); DocumentInstance* m_document{nullptr}; - ObjectProperty m_childNodes{m_context, instanceObject, "childNodes", childNodes}; + ObjectProperty m_childNodes{m_context, jsObject, "childNodes", childNodes}; void ensureDetached(NodeInstance* node); friend DocumentInstance; friend Node; diff --git a/bridge/bindings/qjs/dom/node_test.cc b/bridge/bindings/qjs/dom/node_test.cc index 6c9dca8ac6..636495dc22 100644 --- a/bridge/bindings/qjs/dom/node_test.cc +++ b/bridge/bindings/qjs/dom/node_test.cc @@ -3,25 +3,26 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "event_target.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(Node, appendChild) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "true true true"); logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" "console.log(document.body.firstChild === div, document.body.lastChild === div, div.parentNode === document.body);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -29,11 +30,11 @@ TEST(Node, appendChild) { TEST(Node, childNodes) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "true true true true"); logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "let div1 = document.createElement('div');" @@ -46,7 +47,7 @@ TEST(Node, childNodes) { "div1.nextSibling === div2," "div2.previousSibling === div1)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -54,11 +55,11 @@ TEST(Node, childNodes) { TEST(Node, textContent) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "1234helloworld"); logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "let text1 = document.createTextNode('1234');" @@ -68,7 +69,26 @@ TEST(Node, textContent) { "div.appendChild(text2);" "console.log(div.textContent)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Node, setTextContent) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto& context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "div.textContent = '1234';" + "console.log(div.textContent);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -76,11 +96,11 @@ TEST(Node, textContent) { TEST(Node, ensureDetached) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "true true"); logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "let div = document.createElement('div');" @@ -90,7 +110,7 @@ TEST(Node, ensureDetached) { "document.body.appendChild(container);" "console.log(document.body.firstChild === container, container.firstChild === div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -98,15 +118,15 @@ TEST(Node, ensureDetached) { TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); const char* code = "document.body = document.createElement('body');"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); } @@ -130,17 +150,17 @@ console.log(div.style.width == div2.style.height, div.getAttribute('id') == '123 bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -178,17 +198,17 @@ console.log( bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } diff --git a/bridge/bindings/qjs/dom/script_animation_controller.cc b/bridge/bindings/qjs/dom/script_animation_controller.cc new file mode 100644 index 0000000000..f28b4f552b --- /dev/null +++ b/bridge/bindings/qjs/dom/script_animation_controller.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_animation_controller.h" +#include "dart_methods.h" +#include "frame_request_callback_collection.h" + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken::binding::qjs { + +JSClassID ScriptAnimationController::classId{0}; + +void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + auto* controller = static_cast(JS_GetOpaque(val, ScriptAnimationController::classId)); + controller->m_frameRequestCallbackCollection.trace(rt, JS_UNDEFINED, mark_func); +} +void ScriptAnimationController::dispose() const {} + +ScriptAnimationController* ScriptAnimationController::initialize(JSContext* ctx, JSClassID* classId) { + return GarbageCollected::initialize(ctx, classId); +} + +static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { + auto* frameCallback = static_cast(ptr); + auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); + + if (!context->isValid()) + return; + + if (errmsg != nullptr) { + JSValue exception = JS_ThrowTypeError(frameCallback->ctx(), "%s", errmsg); + context->handleException(&exception); + return; + } + + // Trigger callbacks. + frameCallback->fire(highResTimeStamp); + + context->drainPendingPromiseJobs(); +} + +uint32_t ScriptAnimationController::registerFrameCallback(FrameCallback* frameCallback) { + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + + uint32_t requestId = getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); + + // Register frame callback to collection. + m_frameRequestCallbackCollection.registerFrameCallback(requestId, frameCallback); + + return requestId; +} +void ScriptAnimationController::cancelFrameCallback(uint32_t callbackId) { + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + + getDartMethod()->cancelAnimationFrame(context->getContextId(), callbackId); + + m_frameRequestCallbackCollection.cancelFrameCallback(callbackId); +} + +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/script_animation_controller.h b/bridge/bindings/qjs/dom/script_animation_controller.h new file mode 100644 index 0000000000..cd07d97c56 --- /dev/null +++ b/bridge/bindings/qjs/dom/script_animation_controller.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ + +#include "bindings/qjs/garbage_collected.h" +#include "frame_request_callback_collection.h" + +namespace kraken::binding::qjs { + +class ScriptAnimationController : public GarbageCollected { + public: + static JSClassID classId; + + ScriptAnimationController* initialize(JSContext* ctx, JSClassID* classId) override; + + // Animation frame callbacks are used for requestAnimationFrame(). + uint32_t registerFrameCallback(FrameCallback* frameCallback); + void cancelFrameCallback(uint32_t callbackId); + + [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "ScriptAnimationController"; } + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: + FrameRequestCallbackCollection m_frameRequestCallbackCollection; +}; + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/bindings/qjs/dom/style_declaration.cc index 1e0c7ba8af..fb8030370b 100644 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ b/bridge/bindings/qjs/dom/style_declaration.cc @@ -11,9 +11,9 @@ namespace kraken::binding::qjs { std::once_flag kinitCSSStyleDeclarationFlag; -void bindCSSStyleDeclaration(std::unique_ptr& context) { +void bindCSSStyleDeclaration(std::unique_ptr& context) { auto style = CSSStyleDeclaration::instance(context.get()); - context->defineGlobalProperty("CSSStyleDeclaration", style->classObject); + context->defineGlobalProperty("CSSStyleDeclaration", style->jsObject); } static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { @@ -46,7 +46,7 @@ static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { return result; } -JSValue CSSStyleDeclaration::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue CSSStyleDeclaration::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { return JS_ThrowTypeError(ctx, "Illegal constructor"); } @@ -55,16 +55,16 @@ JSValue CSSStyleDeclaration::instanceConstructor(QjsContext* ctx, JSValue func_o auto eventTargetInstance = static_cast(JS_GetOpaque(eventTargetValue, EventTarget::classId(eventTargetValue))); auto style = new StyleDeclarationInstance(this, eventTargetInstance); - return style->instanceObject; + return style->jsObject; } JSClassID CSSStyleDeclaration::kCSSStyleDeclarationClassId{0}; -CSSStyleDeclaration::CSSStyleDeclaration(JSContext* context) : HostClass(context, "CSSStyleDeclaration") { +CSSStyleDeclaration::CSSStyleDeclaration(ExecutionContext* context) : HostClass(context, "CSSStyleDeclaration") { std::call_once(kinitCSSStyleDeclarationFlag, []() { JS_NewClassID(&kCSSStyleDeclarationClassId); }); } -JSValue CSSStyleDeclaration::setProperty(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue CSSStyleDeclaration::setProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 2) return JS_ThrowTypeError(ctx, "Failed to execute 'setProperty' on 'CSSStyleDeclaration': 2 arguments required, but only %d present.", argc); auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); @@ -81,7 +81,7 @@ JSValue CSSStyleDeclaration::setProperty(QjsContext* ctx, JSValue this_val, int return JS_UNDEFINED; } -JSValue CSSStyleDeclaration::removeProperty(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue CSSStyleDeclaration::removeProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "Failed to execute 'removeProperty' on 'CSSStyleDeclaration': 1 arguments required, but only 0 present."); auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); @@ -98,7 +98,7 @@ JSValue CSSStyleDeclaration::removeProperty(QjsContext* ctx, JSValue this_val, i return JS_UNDEFINED; } -JSValue CSSStyleDeclaration::getPropertyValue(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +JSValue CSSStyleDeclaration::getPropertyValue(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "Failed to execute 'getPropertyValue' on 'CSSStyleDeclaration': 1 arguments required, but only 0 present."); auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); @@ -113,7 +113,7 @@ JSValue CSSStyleDeclaration::getPropertyValue(QjsContext* ctx, JSValue this_val, StyleDeclarationInstance::StyleDeclarationInstance(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget) : Instance(cssStyleDeclaration, "CSSStyleDeclaration", &m_exoticMethods, CSSStyleDeclaration::kCSSStyleDeclarationClassId, finalize), ownerEventTarget(ownerEventTarget) { - JS_DupValue(m_ctx, ownerEventTarget->instanceObject); + JS_DupValue(m_ctx, ownerEventTarget->jsObject); } StyleDeclarationInstance::~StyleDeclarationInstance() {} @@ -123,9 +123,9 @@ bool StyleDeclarationInstance::internalSetProperty(std::string& name, JSValue va properties[name] = jsValueToStdString(m_ctx, value); if (ownerEventTarget != nullptr) { - NativeString* args_01 = stringToNativeString(name); - NativeString* args_02 = jsValueToNativeString(m_ctx, value); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(name); + std::unique_ptr args_02 = jsValueToNativeString(m_ctx, value); + m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); } return true; @@ -141,9 +141,9 @@ void StyleDeclarationInstance::internalRemoveProperty(std::string& name) { properties.erase(name); if (ownerEventTarget != nullptr) { - NativeString* args_01 = stringToNativeString(name); - NativeString* args_02 = jsValueToNativeString(m_ctx, JS_NULL); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(name); + std::unique_ptr args_02 = jsValueToNativeString(m_ctx, JS_NULL); + m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); } } @@ -178,7 +178,7 @@ void StyleDeclarationInstance::copyWith(StyleDeclarationInstance* instance) { } } -int StyleDeclarationInstance::hasProperty(QjsContext* ctx, JSValue obj, JSAtom atom) { +int StyleDeclarationInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { auto* style = static_cast(JS_GetOpaque(obj, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); const char* cname = JS_AtomToCString(ctx, atom); std::string name = std::string(cname); @@ -187,7 +187,7 @@ int StyleDeclarationInstance::hasProperty(QjsContext* ctx, JSValue obj, JSAtom a return match; } -int StyleDeclarationInstance::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +int StyleDeclarationInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); const char* cname = JS_AtomToCString(ctx, atom); std::string name = std::string(cname); @@ -196,11 +196,11 @@ int StyleDeclarationInstance::setProperty(QjsContext* ctx, JSValue obj, JSAtom a return success; } -JSValue StyleDeclarationInstance::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue StyleDeclarationInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { auto* styleInstance = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, styleInstance->instanceObject); + JSValue prototype = JS_GetPrototype(ctx, styleInstance->jsObject); if (JS_HasProperty(ctx, prototype, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, styleInstance->instanceObject, 0); + JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, styleInstance->jsObject, 0); JS_FreeValue(ctx, prototype); return ret; } @@ -218,10 +218,10 @@ JSClassExoticMethods StyleDeclarationInstance::m_exoticMethods{ nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty, }; -void StyleDeclarationInstance::gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - Instance::gcMark(rt, val, mark_func); +void StyleDeclarationInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + Instance::trace(rt, val, mark_func); // We should tel gc style relies on element - JS_MarkValue(rt, ownerEventTarget->instanceObject, mark_func); + JS_MarkValue(rt, ownerEventTarget->jsObject, mark_func); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index d0a79e6d5d..56f90c9118 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -11,7 +11,7 @@ namespace kraken::binding::qjs { class EventTargetInstance; -void bindCSSStyleDeclaration(std::unique_ptr& context); +void bindCSSStyleDeclaration(std::unique_ptr& context); template inline bool isASCIILower(CharacterType character) { @@ -31,18 +31,18 @@ class CSSStyleDeclaration : public HostClass { CSSStyleDeclaration() = delete; ~CSSStyleDeclaration(){}; - explicit CSSStyleDeclaration(JSContext* context); + explicit CSSStyleDeclaration(ExecutionContext* context); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - static JSValue setProperty(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeProperty(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getPropertyValue(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue setProperty(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue removeProperty(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + static JSValue getPropertyValue(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); protected: - ObjectFunction m_setProperty{m_context, m_prototypeObject, "setProperty", setProperty, 2}; - ObjectFunction m_getPropertyValue{m_context, m_prototypeObject, "getPropertyValue", getPropertyValue, 2}; - ObjectFunction m_removeProperty{m_context, m_prototypeObject, "removeProperty", removeProperty, 2}; + DEFINE_PROTOTYPE_FUNCTION(setProperty, 2); + DEFINE_PROTOTYPE_FUNCTION(getPropertyValue, 2); + DEFINE_PROTOTYPE_FUNCTION(removeProperty, 2); }; class StyleDeclarationInstance : public Instance { @@ -56,15 +56,15 @@ class StyleDeclarationInstance : public Instance { std::string toString(); void copyWith(StyleDeclarationInstance* instance); - void gcMark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; const EventTargetInstance* ownerEventTarget; private: - static int hasProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom); - static int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); + static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - static JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); static void finalize(JSRuntime* rt, JSValue val) { auto* instance = static_cast(JS_GetOpaque(val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); diff --git a/bridge/bindings/qjs/dom/text_node.cc b/bridge/bindings/qjs/dom/text_node.cc index bbd4b660d7..29c162b37e 100644 --- a/bridge/bindings/qjs/dom/text_node.cc +++ b/bridge/bindings/qjs/dom/text_node.cc @@ -11,88 +11,76 @@ namespace kraken::binding::qjs { std::once_flag kTextNodeInitFlag; -void bindTextNode(std::unique_ptr& context) { +void bindTextNode(std::unique_ptr& context) { auto* constructor = TextNode::instance(context.get()); - context->defineGlobalProperty("Text", constructor->classObject); + context->defineGlobalProperty("Text", constructor->jsObject); } JSClassID TextNode::kTextNodeClassId{0}; -TextNode::TextNode(JSContext* context) : Node(context, "TextNode") { +TextNode::TextNode(ExecutionContext* context) : Node(context, "TextNode") { std::call_once(kTextNodeInitFlag, []() { JS_NewClassID(&kTextNodeClassId); }); JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); } -JSValue TextNode::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue TextNode::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { JSValue textContent = JS_NULL; if (argc == 1) { textContent = argv[0]; } - return (new TextNodeInstance(this, textContent))->instanceObject; + return (new TextNodeInstance(this, textContent))->jsObject; } JSClassID TextNode::classId() { return kTextNodeClassId; } -PROP_GETTER(TextNodeInstance, data)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(TextNode, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - return JS_DupValue(ctx, textNode->m_data); + return JS_NewString(ctx, textNode->m_data.c_str()); } -PROP_SETTER(TextNodeInstance, data)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(TextNode, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); textNode->internalSetTextContent(argv[0]); return JS_NULL; } -PROP_GETTER(TextNodeInstance, nodeValue)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(TextNode, nodeValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - return JS_DupValue(ctx, textNode->m_data); + return JS_NewString(ctx, textNode->m_data.c_str()); } -PROP_SETTER(TextNodeInstance, nodeValue)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_SETTER(TextNode, nodeValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); textNode->internalSetTextContent(argv[0]); return JS_NULL; } -PROP_GETTER(TextNodeInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_PROPERTY_GETTER(TextNode, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { return JS_NewString(ctx, "#text"); } -PROP_SETTER(TextNodeInstance, nodeName)(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} -TextNodeInstance::TextNodeInstance(TextNode* textNode, JSValue text) - : NodeInstance(textNode, NodeType::TEXT_NODE, DocumentInstance::instance(Document::instance(textNode->m_context)), TextNode::classId(), "TextNode"), m_data(JS_DupValue(m_ctx, text)) { - NativeString* args_01 = jsValueToNativeString(m_ctx, m_data); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(m_eventTargetId, UICommand::createTextNode, *args_01, nativeEventTarget); +TextNodeInstance::TextNodeInstance(TextNode* textNode, JSValue text) : NodeInstance(textNode, NodeType::TEXT_NODE, TextNode::classId(), "TextNode") { + m_data = jsValueToStdString(m_ctx, text); + std::unique_ptr args_01 = stringToNativeString(m_data); + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createTextNode, *args_01, nativeEventTarget); } -TextNodeInstance::~TextNodeInstance() { - JS_FreeValue(m_ctx, m_data); -} +TextNodeInstance::~TextNodeInstance() {} std::string TextNodeInstance::toString() { - const char* pstring = JS_ToCString(m_ctx, m_data); - std::string result = std::string(pstring); - JS_FreeCString(m_ctx, pstring); - return result; + return m_data; } JSValue TextNodeInstance::internalGetTextContent() { - return JS_DupValue(m_ctx, m_data); + return JS_NewString(m_ctx, m_data.c_str()); } void TextNodeInstance::internalSetTextContent(JSValue content) { - if (!JS_IsNull(m_data)) { - JS_FreeValue(m_ctx, m_data); - } - - m_data = JS_DupValue(m_ctx, content); + m_data = jsValueToStdString(m_ctx, content); std::string key = "data"; - NativeString* args_01 = stringToNativeString(key); - NativeString* args_02 = jsValueToNativeString(m_ctx, content); - foundation::UICommandBuffer::instance(m_context->getContextId())->addCommand(m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(m_ctx, content); + m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/bindings/qjs/dom/text_node.h index 68e4355c29..1591831e96 100644 --- a/bridge/bindings/qjs/dom/text_node.h +++ b/bridge/bindings/qjs/dom/text_node.h @@ -12,20 +12,24 @@ namespace kraken::binding::qjs { class TextNodeInstance; -void bindTextNode(std::unique_ptr& context); +void bindTextNode(std::unique_ptr& context); class TextNode : public Node { public: static JSClassID kTextNodeClassId; static JSClassID classId(); TextNode() = delete; - explicit TextNode(JSContext* context); + explicit TextNode(ExecutionContext* context); OBJECT_INSTANCE(TextNode); - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; private: + DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); + + DEFINE_PROTOTYPE_PROPERTY(data); + DEFINE_PROTOTYPE_PROPERTY(nodeValue); friend TextNodeInstance; }; @@ -38,13 +42,12 @@ class TextNodeInstance : public NodeInstance { std::string toString(); private: - DEFINE_HOST_CLASS_PROPERTY(3, data, nodeValue, nodeName); JSValue internalGetTextContent() override; void internalSetTextContent(JSValue content) override; friend TextNode; friend Node; - JSValue m_data{JS_NULL}; + std::string m_data; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/text_node_test.cc b/bridge/bindings/qjs/dom/text_node_test.cc index 944f9caba5..35b954b970 100644 --- a/bridge/bindings/qjs/dom/text_node_test.cc +++ b/bridge/bindings/qjs/dom/text_node_test.cc @@ -3,18 +3,19 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "event_target.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(TextNode, instanceofNode) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -23,7 +24,7 @@ TEST(TextNode, instanceofNode) { "let text = document.createTextNode('1234');" "console.log(text instanceof Node, text instanceof Text);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } diff --git a/bridge/bindings/qjs/js_context.cc b/bridge/bindings/qjs/executing_context.cc similarity index 70% rename from bridge/bindings/qjs/js_context.cc rename to bridge/bindings/qjs/executing_context.cc index b76463ebc3..fea22013e0 100644 --- a/bridge/bindings/qjs/js_context.cc +++ b/bridge/bindings/qjs/executing_context.cc @@ -3,11 +3,13 @@ * Author: Kraken Team. */ -#include "js_context.h" +#include "executing_context.h" #include "bindings/qjs/bom/timer.h" #include "bindings/qjs/bom/window.h" #include "bindings/qjs/dom/document.h" #include "bindings/qjs/module_manager.h" +#include "bom/dom_timer_coordinator.h" +#include "garbage_collected.h" #include "kraken_bridge.h" #include "qjs_patch.h" @@ -15,9 +17,9 @@ namespace kraken::binding::qjs { static std::atomic context_unique_id{0}; -JSClassID JSContext::kHostClassClassId{0}; -JSClassID JSContext::kHostObjectClassId{0}; -JSClassID JSContext::kHostExoticObjectClassId{0}; +JSClassID ExecutionContext::kHostClassClassId{0}; +JSClassID ExecutionContext::kHostObjectClassId{0}; +JSClassID ExecutionContext::kHostExoticObjectClassId{0}; std::atomic runningContexts{0}; @@ -25,13 +27,22 @@ std::atomic runningContexts{0}; bool valid_contexts[MAX_JS_CONTEXT]; std::atomic running_context_list{0}; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { - return std::make_unique(contextId, handler, owner); +std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { + return std::make_unique(contextId, handler, owner); } static JSRuntime* m_runtime{nullptr}; -JSContext::JSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { +void ExecutionContextGCTracker::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + context->trace(rt, context->global(), mark_func); +} +void ExecutionContextGCTracker::dispose() const {} + +JSClassID ExecutionContextGCTracker::contextGcTrackerClassId{0}; + +ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) + : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT valid_contexts[contextId] = true; if (contextId > running_context_list) @@ -44,12 +55,9 @@ JSContext::JSContext(int32_t contextId, const JSExceptionHandler& handler, void* }); init_list_head(&node_job_list); - init_list_head(&timer_job_list); - init_list_head(&document_job_list); init_list_head(&module_job_list); init_list_head(&module_callback_job_list); init_list_head(&promise_job_list); - init_list_head(&atom_job_list); init_list_head(&native_function_job_list); if (m_runtime == nullptr) { @@ -60,17 +68,20 @@ JSContext::JSContext(int32_t contextId, const JSExceptionHandler& handler, void* timeOrigin = std::chrono::system_clock::now(); globalObject = JS_GetGlobalObject(m_ctx); JSValue windowGetter = JS_NewCFunction( - m_ctx, [](QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); + m_ctx, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); JSAtom windowKey = JS_NewAtom(m_ctx, "window"); JS_DefinePropertyGetSet(m_ctx, globalObject, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); JS_FreeAtom(m_ctx, windowKey); JS_SetContextOpaque(m_ctx, this); JS_SetHostPromiseRejectionTracker(m_runtime, promiseRejectTracker, nullptr); + m_gcTracker = makeGarbageCollected()->initialize(m_ctx, &ExecutionContextGCTracker::contextGcTrackerClassId); + JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->toQuickJS(), JS_PROP_NORMAL); + runningContexts++; } -JSContext::~JSContext() { +ExecutionContext::~ExecutionContext() { valid_contexts[contextId] = false; ctxInvalid_ = true; @@ -79,25 +90,7 @@ JSContext::~JSContext() { struct list_head *el, *el1; list_for_each_safe(el, el1, &node_job_list) { auto* node = list_entry(el, NodeJob, link); - JS_FreeValue(m_ctx, node->nodeInstance->instanceObject); - } - } - - // Manual free nodes bound by document. - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &document_job_list) { - auto* node = list_entry(el, NodeJob, link); - JS_FreeValue(m_ctx, node->nodeInstance->instanceObject); - } - } - // Manual free timers - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &timer_job_list) { - auto* callbackContext = list_entry(el, TimerCallbackContext, link); - JS_FreeValue(m_ctx, callbackContext->callback); - delete callbackContext; + JS_FreeValue(m_ctx, node->nodeInstance->jsObject); } } @@ -131,16 +124,6 @@ JSContext::~JSContext() { } } - // Free unreleased atoms. - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &atom_job_list) { - auto* job = list_entry(el, AtomJob, link); - JS_FreeAtom(m_ctx, job->atom); - delete job; - } - } - // Free unreleased native_functions. { struct list_head *el, *el1; @@ -152,6 +135,8 @@ JSContext::~JSContext() { JS_FreeValue(m_ctx, globalObject); JS_FreeContext(m_ctx); + + // Run GC to clean up remaining objects about m_ctx; JS_RunGC(m_runtime); #if DUMP_LEAKS @@ -163,7 +148,7 @@ JSContext::~JSContext() { m_ctx = nullptr; } -bool JSContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutionContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); JSValue result = JS_Eval(m_ctx, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); drainPendingPromiseJobs(); @@ -172,7 +157,7 @@ bool JSContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, cons return success; } -bool JSContext::evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { +bool ExecutionContext::evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), length)); JSValue result = JS_Eval(m_ctx, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); drainPendingPromiseJobs(); @@ -181,7 +166,7 @@ bool JSContext::evaluateJavaScript(const char16_t* code, size_t length, const ch return success; } -bool JSContext::evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutionContext::evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { JSValue result = JS_Eval(m_ctx, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); drainPendingPromiseJobs(); bool success = handleException(&result); @@ -189,7 +174,7 @@ bool JSContext::evaluateJavaScript(const char* code, size_t codeLength, const ch return success; } -bool JSContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { +bool ExecutionContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { JSValue obj, val; obj = JS_ReadObject(m_ctx, bytes, byteLength, JS_READ_OBJ_BYTECODE); if (!handleException(&obj)) @@ -201,21 +186,21 @@ bool JSContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { return true; } -bool JSContext::isValid() const { +bool ExecutionContext::isValid() const { return !ctxInvalid_; } -int32_t JSContext::getContextId() const { +int32_t ExecutionContext::getContextId() const { assert(!ctxInvalid_ && "context has been released"); return contextId; } -void* JSContext::getOwner() { +void* ExecutionContext::getOwner() { assert(!ctxInvalid_ && "context has been released"); return owner; } -bool JSContext::handleException(JSValue* exception) { +bool ExecutionContext::handleException(JSValue* exception) { if (JS_IsException(*exception)) { JSValue error = JS_GetException(m_ctx); reportError(error); @@ -227,20 +212,20 @@ bool JSContext::handleException(JSValue* exception) { return true; } -JSValue JSContext::global() { +JSValue ExecutionContext::global() { return globalObject; } -QjsContext* JSContext::ctx() { +JSContext* ExecutionContext::ctx() { assert(!ctxInvalid_ && "context has been released"); return m_ctx; } -JSRuntime* JSContext::runtime() { +JSRuntime* ExecutionContext::runtime() { return m_runtime; } -void JSContext::reportError(JSValueConst error) { +void ExecutionContext::reportError(JSValueConst error) { if (!JS_IsError(m_ctx, error)) return; @@ -275,9 +260,9 @@ void JSContext::reportError(JSValueConst error) { JS_FreeCString(m_ctx, type); } -void JSContext::drainPendingPromiseJobs() { +void ExecutionContext::drainPendingPromiseJobs() { // should executing pending promise jobs. - QjsContext* pctx; + JSContext* pctx; int finished = JS_ExecutePendingJob(runtime(), &pctx); while (finished != 0) { finished = JS_ExecutePendingJob(runtime(), &pctx); @@ -287,13 +272,13 @@ void JSContext::drainPendingPromiseJobs() { } } -void JSContext::defineGlobalProperty(const char* prop, JSValue value) { +void ExecutionContext::defineGlobalProperty(const char* prop, JSValue value) { JSAtom atom = JS_NewAtom(m_ctx, prop); JS_SetProperty(m_ctx, globalObject, atom, value); JS_FreeAtom(m_ctx, atom); } -uint8_t* JSContext::dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { +uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { JSValue object = JS_Eval(m_ctx, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); bool success = handleException(&object); if (!success) @@ -303,7 +288,7 @@ uint8_t* JSContext::dumpByteCode(const char* code, uint32_t codeLength, const ch return bytes; } -void JSContext::dispatchGlobalErrorEvent(JSValueConst error) { +void ExecutionContext::dispatchGlobalErrorEvent(JSValueConst error) { JSValue errorHandler = JS_GetPropertyStr(m_ctx, globalObject, "__global_onerror_handler__"); JSValue returnValue = JS_Call(m_ctx, errorHandler, globalObject, 1, &error); drainPendingPromiseJobs(); @@ -316,7 +301,7 @@ void JSContext::dispatchGlobalErrorEvent(JSValueConst error) { JS_FreeValue(m_ctx, errorHandler); } -void JSContext::dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error) { +void ExecutionContext::dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error) { JSValue errorHandler = JS_GetPropertyStr(m_ctx, globalObject, "__global_unhandled_promise_handler__"); JSValue arguments[] = {promise, error}; JSValue returnValue = JS_Call(m_ctx, errorHandler, globalObject, 2, arguments); @@ -326,13 +311,17 @@ void JSContext::dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValu JS_FreeValue(m_ctx, errorHandler); } -void JSContext::promiseRejectTracker(QjsContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); +void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); context->reportError(reason); context->dispatchGlobalPromiseRejectionEvent(promise, reason); } -NativeString* jsValueToNativeString(QjsContext* ctx, JSValue value) { +DOMTimerCoordinator* ExecutionContext::timers() { + return &m_timers; +} + +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { bool isValueString = true; if (JS_IsNull(value)) { value = JS_NewString(ctx, ""); @@ -344,18 +333,17 @@ NativeString* jsValueToNativeString(QjsContext* ctx, JSValue value) { uint32_t length; uint16_t* buffer = JS_ToUnicode(ctx, value, &length); - NativeString tmp{}; - tmp.string = buffer; - tmp.length = length; - NativeString* cloneString = tmp.clone(); + std::unique_ptr ptr = std::make_unique(); + ptr->string = buffer; + ptr->length = length; if (!isValueString) { JS_FreeValue(ctx, value); } - return cloneString; + return ptr; } -void buildUICommandArgs(QjsContext* ctx, JSValue key, NativeString& args_01) { +void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { if (!JS_IsString(key)) return; @@ -365,34 +353,30 @@ void buildUICommandArgs(QjsContext* ctx, JSValue key, NativeString& args_01) { args_01.length = length; } -NativeString* stringToNativeString(const std::string& string) { +std::unique_ptr stringToNativeString(const std::string& string) { std::u16string utf16; fromUTF8(string, utf16); NativeString tmp{}; tmp.string = reinterpret_cast(utf16.c_str()); tmp.length = utf16.size(); - return tmp.clone(); + return std::unique_ptr(tmp.clone()); } -NativeString* atomToNativeString(QjsContext* ctx, JSAtom atom) { +std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { JSValue stringValue = JS_AtomToString(ctx, atom); - NativeString* string = jsValueToNativeString(ctx, stringValue); + std::unique_ptr string = jsValueToNativeString(ctx, stringValue); JS_FreeValue(ctx, stringValue); return string; } -JSRuntime* getGlobalJSRuntime() { - return m_runtime; -} - -std::string jsValueToStdString(QjsContext* ctx, JSValue& value) { +std::string jsValueToStdString(JSContext* ctx, JSValue& value) { const char* cString = JS_ToCString(ctx, value); std::string str = std::string(cString); JS_FreeCString(ctx, cString); return str; } -std::string jsAtomToStdString(QjsContext* ctx, JSAtom atom) { +std::string jsAtomToStdString(JSContext* ctx, JSAtom atom) { const char* cstr = JS_AtomToCString(ctx, atom); std::string str = std::string(cstr); JS_FreeCString(ctx, cstr); @@ -406,7 +390,7 @@ bool isContextValid(int32_t contextId) { return valid_contexts[contextId]; } -void arrayPushValue(QjsContext* ctx, JSValue array, JSValue val) { +void arrayPushValue(JSContext* ctx, JSValue array, JSValue val) { JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); JSValue arguments[] = {val}; JSValue result = JS_Call(ctx, pushMethod, array, 1, arguments); @@ -414,7 +398,7 @@ void arrayPushValue(QjsContext* ctx, JSValue array, JSValue val) { JS_FreeValue(ctx, result); } -void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount) { +void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount) { JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, deleteCount)}; JSValue result = JS_Call(ctx, spliceMethod, array, 2, arguments); @@ -422,7 +406,7 @@ void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t d JS_FreeValue(ctx, result); } -void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue) { +void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue) { JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, deleteCount), replacedValue}; JSValue result = JS_Call(ctx, spliceMethod, array, 3, arguments); @@ -430,7 +414,7 @@ void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t d JS_FreeValue(ctx, result); } -void arrayInsert(QjsContext* ctx, JSValue array, uint32_t start, JSValue targetValue) { +void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue) { JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, 0), targetValue}; JSValue result = JS_Call(ctx, spliceMethod, array, 3, arguments); @@ -438,7 +422,7 @@ void arrayInsert(QjsContext* ctx, JSValue array, uint32_t start, JSValue targetV JS_FreeValue(ctx, result); } -int32_t arrayGetLength(QjsContext* ctx, JSValue array) { +int32_t arrayGetLength(JSContext* ctx, JSValue array) { JSValue lenVal = JS_GetPropertyStr(ctx, array, "length"); int32_t len; JS_ToInt32(ctx, &len, lenVal); @@ -446,7 +430,7 @@ int32_t arrayGetLength(QjsContext* ctx, JSValue array) { return len; } -int32_t arrayFindIdx(QjsContext* ctx, JSValue array, JSValue target) { +int32_t arrayFindIdx(JSContext* ctx, JSValue array, JSValue target) { int32_t len = arrayGetLength(ctx, array); for (int i = 0; i < len; i++) { JSValue v = JS_GetPropertyUint32(ctx, array, i); @@ -459,7 +443,7 @@ int32_t arrayFindIdx(QjsContext* ctx, JSValue array, JSValue target) { return -1; } -JSValue objectGetKeys(QjsContext* ctx, JSValue obj) { +JSValue objectGetKeys(JSContext* ctx, JSValue obj) { JSValue globalObject = JS_GetGlobalObject(ctx); JSValue object = JS_GetPropertyStr(ctx, globalObject, "Object"); JSValue keysFunc = JS_GetPropertyStr(ctx, object, "keys"); @@ -473,4 +457,8 @@ JSValue objectGetKeys(QjsContext* ctx, JSValue obj) { return result; } +void ExecutionContext::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + m_timers.trace(rt, JS_NULL, mark_func); +} + } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h new file mode 100644 index 0000000000..a97d9655c9 --- /dev/null +++ b/bridge/bindings/qjs/executing_context.h @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_JS_CONTEXT_H +#define KRAKENBRIDGE_JS_CONTEXT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bindings/qjs/bom/dom_timer_coordinator.h" +#include "foundation/ui_command_buffer.h" +#include "garbage_collected.h" +#include "js_context_macros.h" +#include "kraken_foundation.h" +#include "qjs_patch.h" + +using JSExceptionHandler = std::function; + +namespace kraken::binding::qjs { + +static std::once_flag kinitJSClassIDFlag; + +class WindowInstance; +class DocumentInstance; +class ExecutionContext; +struct DOMTimerCallbackContext; + +std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); + +static inline bool isNumberIndex(const std::string& name) { + if (name.empty()) + return false; + char f = name[0]; + return f >= '0' && f <= '9'; +} + +struct PromiseContext { + void* data; + ExecutionContext* context; + JSValue resolveFunc; + JSValue rejectFunc; + JSValue promise; + list_head link; +}; + +bool isContextValid(int32_t contextId); + +class ExecutionContextGCTracker : public GarbageCollected { + public: + static JSClassID contextGcTrackerClassId; + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: +}; + +// An environment in which script can execute. This class exposes the common +// properties of script execution environments on the kraken. +// Window : Document : ExecutionContext = 1 : 1 : 1 at any point in time. +class ExecutionContext { + public: + ExecutionContext() = delete; + ExecutionContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + ~ExecutionContext(); + + bool evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); + bool evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); + bool evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); + bool evaluateByteCode(uint8_t* bytes, size_t byteLength); + bool isValid() const; + JSValue global(); + JSContext* ctx(); + static JSRuntime* runtime(); + int32_t getContextId() const; + void* getOwner(); + bool handleException(JSValue* exc); + void drainPendingPromiseJobs(); + void defineGlobalProperty(const char* prop, JSValueConst value); + uint8_t* dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); + + // Gets the DOMTimerCoordinator which maintains the "active timer + // list" of tasks created by setTimeout and setInterval. The + // DOMTimerCoordinator is owned by the ExecutionContext and should + // not be used after the ExecutionContext is destroyed. + DOMTimerCoordinator* timers(); + + FORCE_INLINE DocumentInstance* document() { return m_document; }; + FORCE_INLINE foundation::UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + + std::chrono::time_point timeOrigin; + std::unordered_map constructorMap; + + int32_t uniqueId; + struct list_head node_job_list; + struct list_head module_job_list; + struct list_head module_callback_job_list; + struct list_head promise_job_list; + struct list_head native_function_job_list; + + static JSClassID kHostClassClassId; + static JSClassID kHostObjectClassId; + static JSClassID kHostExoticObjectClassId; + + private: + static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); + void dispatchGlobalErrorEvent(JSValueConst error); + void dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error); + void reportError(JSValueConst error); + + int32_t contextId; + JSExceptionHandler _handler; + void* owner; + JSValue globalObject{JS_NULL}; + bool ctxInvalid_{false}; + JSContext* m_ctx{nullptr}; + friend WindowInstance; + friend DocumentInstance; + WindowInstance* m_window{nullptr}; + DocumentInstance* m_document{nullptr}; + DOMTimerCoordinator m_timers; + ExecutionContextGCTracker* m_gcTracker{nullptr}; + foundation::UICommandBuffer m_commandBuffer{contextId}; +}; + +// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of +// proxy object. +static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int data_len, JSValueConst* data) { + JSValue f = data[0]; + JSValue result; + if (JS_IsProxy(this_val)) { + result = JS_Call(ctx, f, JS_GetProxyTarget(this_val), argc, argv); + } else { + // If this_val is undefined or null, this_val should set to globalThis. + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) { + this_val = JS_GetGlobalObject(ctx); + result = JS_Call(ctx, f, this_val, argc, argv); + JS_FreeValue(ctx, this_val); + } else { + result = JS_Call(ctx, f, this_val, argc, argv); + } + } + return result; +} + +class ObjectProperty { + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); + + public: + ObjectProperty() = delete; + + // Define a property on object with a getter and setter function. + explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction, JSCFunction setterFunction) { + // Getter on jsObject works well with all conditions. + // We create an getter function and define to jsObject directly. + JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); + JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); + JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); + + // Getter on jsObject works well with all conditions. + // We create an getter function and define to jsObject directly. + JSValue setter = JS_NewCFunction(context->ctx(), setterFunction, "setter", 0); + JSValue setterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 1, 0, 1, &setter); + + // Define getter and setter property. + JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, setterProxy, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); + + JS_FreeAtom(context->ctx(), propertyKeyAtom); + JS_FreeValue(context->ctx(), getter); + JS_FreeValue(context->ctx(), setter); + }; + + explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction) { + // Getter on jsObject works well with all conditions. + // We create an getter function and define to jsObject directly. + JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); + JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); + JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); + JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, JS_UNDEFINED, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); + JS_FreeAtom(context->ctx(), propertyKeyAtom); + JS_FreeValue(context->ctx(), getter); + }; + + // Define an property on object with a JSValue. + explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { + JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); + } + + JSValue value() const { return m_value; } + + private: + JSValue m_value{JS_NULL}; +}; + +class ObjectFunction { + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectFunction); + + public: + ObjectFunction() = delete; + explicit ObjectFunction(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc) { + JSValue f = JS_NewCFunction(context->ctx(), function, functionName, argc); + JSValue pf = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, argc, 0, 1, &f); + JSAtom key = JS_NewAtom(context->ctx(), functionName); + + JS_FreeValue(context->ctx(), f); + +// We should avoid overwrite exist property functions. +#ifdef DEBUG + assert_m(JS_HasProperty(context->ctx(), thisObject, key) == 0, (std::string("Found exist function property: ") + std::string(functionName)).c_str()); +#endif + + JS_DefinePropertyValue(context->ctx(), thisObject, key, pf, JS_PROP_ENUMERABLE); + JS_FreeAtom(context->ctx(), key); + }; +}; + +class JSValueHolder { + public: + JSValueHolder() = delete; + explicit JSValueHolder(JSContext* ctx, JSValue value) : m_value(value), m_ctx(ctx){}; + ~JSValueHolder() { JS_FreeValue(m_ctx, m_value); } + inline void value(JSValue value) { + if (!JS_IsNull(m_value)) { + JS_FreeValue(m_ctx, m_value); + } + m_value = JS_DupValue(m_ctx, value); + }; + inline JSValue value() const { return JS_DupValue(m_ctx, m_value); } + + private: + JSContext* m_ctx{nullptr}; + JSValue m_value{JS_NULL}; +}; + +std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + +// Convert to string and return a full copy of NativeString from JSValue. +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); + +void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01); + +// Encode utf-8 to utf-16, and return a full copy of NativeString. +std::unique_ptr stringToNativeString(const std::string& string); + +// Return a full copy of NativeString form JSAtom. +std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom); + +// Convert to string and return a full copy of std::string from JSValue. +std::string jsValueToStdString(JSContext* ctx, JSValue& value); + +// Return a full copy of std::string form JSAtom. +std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); + +// JS array operation utilities. +void arrayPushValue(JSContext* ctx, JSValue array, JSValue val); +void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue); +int32_t arrayGetLength(JSContext* ctx, JSValue array); +int32_t arrayFindIdx(JSContext* ctx, JSValue array, JSValue target); +void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount); +void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue); + +// JS object operation utilities. +JSValue objectGetKeys(JSContext* ctx, JSValue obj); + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h new file mode 100644 index 0000000000..768adfbfa8 --- /dev/null +++ b/bridge/bindings/qjs/garbage_collected.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_GARBAGE_COLLECTED_H +#define KRAKENBRIDGE_GARBAGE_COLLECTED_H + +#include +#include "include/kraken_foundation.h" +#include "qjs_patch.h" + +namespace kraken::binding::qjs { + +template +class MakeGarbageCollectedTrait; + +/** + * Base class for GC managed objects. Only descendent types of `GarbageCollected` + * can be constructed using `MakeGarbageCollected()`. Must be inherited from as + * left-most base class. + * + * \code + * // Example using final class. + * class FinalType final : public GarbageCollected { + * public: + * void Trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) const { + * // trace all memory wants to collected by GC. + * } + * }; + */ +template +class GarbageCollected { + public: + using ParentMostGarbageCollectedType = T; + + virtual T* initialize(JSContext* ctx, JSClassID* classId); + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + + // The garbage collector is taking care of reclaiming the object. + void operator delete(void*) = delete; + void operator delete[](void*) = delete; + + /** + * This Trace method must be override by objects inheriting from + * GarbageCollected. + */ + virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) const = 0; + + /** + * Called before underline JavaScript object been collected by GC. + * Note: JS_FreeValue and JS_FreeAtom is not available, use JS_FreeValueRT and JS_FreeAtomRT instead. + */ + virtual void dispose() const = 0; + + /** + * Specifies a name for the garbage-collected object. Such names will never + * be hidden, as they are explicitly specified by the user of this API. + * + * @returns a human readable name for the object. + */ + [[nodiscard]] FORCE_INLINE virtual const char* getHumanReadableName() const { return ""; }; + + FORCE_INLINE JSValue toQuickJS() { return jsObject; }; + + FORCE_INLINE JSContext* ctx() { return m_ctx; } + + // A anchor to efficiently bind the current object to a linked-list. + list_head link; + + protected: + JSValue jsObject{JS_NULL}; + JSContext* m_ctx{nullptr}; + JSRuntime* m_runtime{nullptr}; + GarbageCollected(){}; + friend class MakeGarbageCollectedTrait; +}; + +template +class MakeGarbageCollectedTrait { + public: + template + static T* allocate(Args&&... args) { + T* object = ::new T(std::forward(args)...); + return object; + } + + friend GarbageCollected; +}; + +template +T* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { + JSRuntime* runtime = JS_GetRuntime(ctx); + + /// When classId is 0, it means this class are not initialized. We should create a JSClassDef to describe the behavior of this class and associate with classID. + /// ClassId should be a static value to make sure JSClassDef when this class are created at the first class. + if (*classId == 0 || !JS_HasClassId(runtime, *classId)) { + /// Allocate a new unique classID from QuickJS. + JS_NewClassID(classId); + /// Basic template to describe the behavior about this class. + JSClassDef def{}; + + def.class_name = getHumanReadableName(); + + /// This callback will be called when QuickJS GC is running at marking stage. + /// Users of this class should override `void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to tell GC + /// which member of their class should be collected by GC. + def.gc_mark = [](JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); + object->trace(rt, val, mark_func); + }; + + /// This callback will be called when QuickJS GC will release the `jsObject` object memory of this class. + /// The deconstruct method of this class will be called and all memory about this class will be freed when finalize completed. + def.finalizer = [](JSRuntime* rt, JSValue val) { + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); + object->dispose(); + free(object); + }; + + JS_NewClass(runtime, *classId, &def); + } + + /// The JavaScript object underline this class. This `jsObject` is the JavaScript object which can be directly access within JavaScript code. + /// When the reference count of `jsObject` decrease to 0, QuickJS will trigger `finalizer` callback and free `jsObject` memory. + /// When QuickJS GC found `jsObject` at marking stage, `gc_mark` callback will be triggered. + jsObject = JS_NewObjectClass(ctx, *classId); + JS_SetOpaque(jsObject, this); + + m_ctx = ctx; + m_runtime = JS_GetRuntime(m_ctx); + + return static_cast(this); +} + +template +T* makeGarbageCollected(Args&&... args) { + static_assert(std::is_base_of::value, + "U of GarbageCollected must be a base of T. Check " + "GarbageCollected base class inheritance."); + return MakeGarbageCollectedTrait::allocate(std::forward(args)...); +} + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h new file mode 100644 index 0000000000..8a5e98d8ad --- /dev/null +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ + +#include +#include + +namespace kraken::binding::qjs { + +template +class HeapHashMap { + public: + HeapHashMap() = delete; + explicit HeapHashMap(JSContext* ctx); + ~HeapHashMap(); + + bool contains(K key); + JSValue getProperty(K key); + void setProperty(K key, JSValue value); + void copyWith(HeapHashMap* newValue); + void erase(K key); + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; + + private: + JSRuntime* m_runtime{nullptr}; + JSContext* m_ctx{nullptr}; + std::unordered_map m_entries; +}; + +template +HeapHashMap::HeapHashMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {} + +template +HeapHashMap::~HeapHashMap() { + for (auto& entry : m_entries) { + JS_FreeAtomRT(m_runtime, entry.first); + JS_FreeValueRT(m_runtime, entry.second); + } +} +template +bool HeapHashMap::contains(K key) { + return m_entries.count(key) > 0; +} + +template +JSValue HeapHashMap::getProperty(K key) { + if (m_entries.count(key) == 0) + return JS_NULL; + + return m_entries[key]; +} + +template +void HeapHashMap::setProperty(K key, JSValue value) { + // GC can't track the value if key had been override. + // Should free the value if exist on m_properties. + if (m_entries.count(key) > 0) { + JS_FreeAtom(m_ctx, key); + JS_FreeValue(m_ctx, m_entries[key]); + } + + m_entries[key] = value; +} + +template +void HeapHashMap::copyWith(HeapHashMap* newValue) { + for (auto& entry : m_entries) { + // We should also dup atom if K is JSAtom. + if (std::is_same::value) { + JS_DupAtom(m_ctx, entry.first); + } + + newValue->m_entries[entry.first] = JS_DupValue(m_ctx, entry.second); + } +} + +template +void HeapHashMap::erase(K key) { + if (m_entries.count(key) == 0) + return; + // We should also free atom if K is JSAtom. + if (std::is_same::value) { + JS_FreeAtomRT(m_runtime, key); + } + JS_FreeValueRT(m_runtime, m_entries[key]); + m_entries.erase(key); +} + +template +void HeapHashMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + for (auto& entry : m_entries) { + JS_MarkValue(rt, entry.second, mark_func); + } +} + +} // namespace kraken::binding::qjs + +#endif // KRAKENBRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h index db029fc65d..3e07173ced 100644 --- a/bridge/bindings/qjs/host_class.h +++ b/bridge/bindings/qjs/host_class.h @@ -6,74 +6,100 @@ #ifndef KRAKENBRIDGE_HOST_CLASS_H #define KRAKENBRIDGE_HOST_CLASS_H -#include "js_context.h" +#include "executing_context.h" #include "qjs_patch.h" #include "third_party/quickjs/quickjs.h" namespace kraken::binding::qjs { +class Instance; + class HostClass { public: KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostClass); - HostClass(JSContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { + HostClass(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { + /// JavaScript object in QuickJS are created by template, in QuickJS, these template is called JSClassDef. + /// JSClassDef define this JSObject's base behavior like className, property getter and setter, and advanced feature such as run a callback when JSObject had been freed by QuickJS garbage + /// collector. Every JSClassDef must have a unique ID, called JSClassID, you can obtain this ID from JS_NewClassID() API. If your wants to create JSObjects defined by your own template, please + /// follow this steps: + /// 1. Use JS_NewClassID() to allocate new id for your template. + /// 2. Create JSClassDef and set up your customized behavior about your JSObject. + /// 3. Use JS_NewClass() to initialize your template and you can use your unique JSClassID to create JSObjects. + /// 4. Use JS_NewObjectClass() to create your JSObjects. + /// Example: + /// JSClassID sampleId; + /// JS_NewClassID(&sampleId); + /// + /// JSClassDef def{}; + /// def.class_name = "SampleClass"; + /// def.finalizer = [](JSRuntime* rt, JSValue val) { + /// // Do something when jsObject been freed by GC + /// }; + /// def.call = [](JSContext * ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + /// // Do something when jsObject been called as function or called as constructor. + /// }; + /// JS_NewClass(runtime, sampleId, &def); + /// JSValue jsObject = JS_NewObjectClass(ctx, sampleId); JSClassDef def{}; def.class_name = "HostClass"; def.finalizer = proxyFinalize; def.call = proxyCall; - JS_NewClass(context->runtime(), JSContext::kHostClassClassId, &def); - classObject = JS_NewObjectClass(context->ctx(), JSContext::kHostClassClassId); + JS_NewClass(context->runtime(), ExecutionContext::kHostClassClassId, &def); + jsObject = JS_NewObjectClass(context->ctx(), ExecutionContext::kHostClassClassId); m_prototypeObject = JS_NewObject(m_ctx); // Make constructor function inherit to Function.prototype JSValue functionConstructor = JS_GetPropertyStr(m_ctx, m_context->global(), "Function"); JSValue functionPrototype = JS_GetPropertyStr(m_ctx, functionConstructor, "prototype"); - JS_SetPrototype(m_ctx, classObject, functionPrototype); + JS_SetPrototype(m_ctx, jsObject, functionPrototype); JS_FreeValue(m_ctx, functionPrototype); JS_FreeValue(m_ctx, functionConstructor); JSAtom prototypeKey = JS_NewAtom(m_ctx, "prototype"); - JS_DefinePropertyValue(m_ctx, classObject, prototypeKey, m_prototypeObject, JS_PROP_C_W_E); + JS_DefinePropertyValue(m_ctx, jsObject, prototypeKey, m_prototypeObject, JS_PROP_C_W_E); JS_FreeAtom(m_ctx, prototypeKey); - JS_SetConstructorBit(m_ctx, classObject, true); - JS_SetOpaque(classObject, this); + JS_SetConstructorBit(m_ctx, jsObject, true); + JS_SetOpaque(jsObject, this); }; virtual ~HostClass() = default; - virtual JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) { return JS_NewObject(ctx); }; - JSValue classObject; + virtual JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) { return JS_NewObject(ctx); }; + JSValue jsObject; inline uint32_t contextId() const { return m_contextId; } - inline JSContext* context() const { return m_context; } + inline ExecutionContext* context() const { return m_context; } inline JSValue prototype() const { return m_prototypeObject; }; protected: JSValue m_prototypeObject{JS_NULL}; std::string m_name; - JSContext* m_context; + ExecutionContext* m_context; int32_t m_contextId; - QjsContext* m_ctx; + JSContext* m_ctx; private: + friend Instance; static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, JSContext::kHostClassClassId)); + auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostClassClassId)); if (hostObject->context()->isValid()) { - JS_FreeValue(hostObject->m_ctx, hostObject->classObject); + JS_FreeValue(hostObject->m_ctx, hostObject->jsObject); } delete hostObject; }; - static JSValue proxyCall(QjsContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) { - // TODO: handle flags when flags is not JS_CALL_FLAG_CONSTRUCTOR - auto* hostClass = static_cast(JS_GetOpaque(func_obj, JSContext::kHostClassClassId)); - - JSAtom prototypeKey = JS_NewAtom(ctx, "prototype"); - JSValue proto = JS_GetProperty(ctx, this_val, prototypeKey); - JSValue instance = hostClass->instanceConstructor(ctx, func_obj, this_val, argc, argv); - JS_SetPrototype(ctx, instance, proto); - JS_FreeAtom(ctx, prototypeKey); - JS_FreeValue(ctx, proto); - return instance; + static JSValue proxyCall(JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) { + // This jsObject is called as a constructor. + if ((flags & JS_CALL_FLAG_CONSTRUCTOR) != 0) { + auto* hostClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); + JSValue instance = hostClass->instanceConstructor(ctx, func_obj, this_val, argc, argv); + JSValue proto = JS_GetPropertyStr(ctx, this_val, "prototype"); + JS_SetPrototype(ctx, instance, proto); + JS_FreeValue(ctx, proto); + return instance; + } + + return this_val; } }; @@ -87,30 +113,34 @@ class Instance { def.exotic = exotic; def.gc_mark = proxyGCMark; int32_t success = JS_NewClass(m_context->runtime(), classId, &def); - instanceObject = JS_NewObjectClass(m_ctx, classId); - JS_SetOpaque(instanceObject, this); + jsObject = JS_NewObjectProtoClass(m_ctx, hostClass->m_prototypeObject, classId); + JS_SetOpaque(jsObject, this); }; - JSValue instanceObject; + JSValue jsObject; virtual ~Instance() = default; inline HostClass* prototype() const { return m_hostClass; } - inline JSContext* context() const { return m_context; } + inline ExecutionContext* context() const { return m_context; } inline std::string name() const { return m_name; } private: static void proxyGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { auto* instance = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - instance->gcMark(rt, val, mark_func); + instance->trace(rt, val, mark_func); } protected: - virtual void gcMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func){}; + // Subclass must to provider a method of void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) + // to tell GC all JSValues are managed by them. + virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func){}; - JSContext* m_context{nullptr}; - QjsContext* m_ctx{nullptr}; + ExecutionContext* m_context{nullptr}; + JSContext* m_ctx{nullptr}; HostClass* m_hostClass{nullptr}; std::string m_name; int64_t m_contextId{-1}; + + friend HostClass; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_class_test.cc b/bridge/bindings/qjs/host_class_test.cc index 0b1259ea6e..61a705720c 100644 --- a/bridge/bindings/qjs/host_class_test.cc +++ b/bridge/bindings/qjs/host_class_test.cc @@ -5,21 +5,20 @@ #include "host_class.h" #include -#include "bridge_qjs.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" namespace kraken::binding::qjs { class ParentClass : public HostClass { public: - explicit ParentClass(JSContext* context) : HostClass(context, "ParentClass") {} - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) override { - return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); - } + explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); } OBJECT_INSTANCE(ParentClass); - static JSValue foo(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 20); } + static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 20); } private: ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; @@ -36,7 +35,7 @@ class SampleClassInstance : public Instance { static void finalizer(JSRuntime* rt, JSValue v) { auto* instance = static_cast(JS_GetOpaque(v, kSampleClassId)); if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->instanceObject); + JS_FreeValue(instance->m_ctx, instance->jsObject); } delete instance; } @@ -45,19 +44,19 @@ class SampleClassInstance : public Instance { std::once_flag kSampleClassOnceFlag; class SampleClass : public ParentClass { public: - explicit SampleClass(JSContext* context) : ParentClass(context) { + explicit SampleClass(ExecutionContext* context) : ParentClass(context) { std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); JS_SetPrototype(m_ctx, m_prototypeObject, ParentClass::instance(m_context)->prototype()); } - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { - auto* sampleClass = static_cast(JS_GetOpaque(func_obj, JSContext::kHostClassClassId)); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { + auto* sampleClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); auto* instance = new SampleClassInstance(sampleClass); - return instance->instanceObject; + return instance->jsObject; } ~SampleClass() {} private: - static JSValue f(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } + static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; }; @@ -65,22 +64,22 @@ class SampleClass : public ParentClass { TEST(HostClass, newInstance) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "10"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto& context = bridge->getContext(); auto* sampleObject = new SampleClass(context.get()); auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("SampleClass", sampleObject->classObject); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("SampleClass", sampleObject->jsObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -88,11 +87,11 @@ TEST(HostClass, newInstance) { TEST(HostClass, instanceOf) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; KRAKEN_LOG(VERBOSE) << errmsg; }); @@ -100,18 +99,18 @@ TEST(HostClass, instanceOf) { auto* sampleObject = new SampleClass(context.get()); auto* parentObject = ParentClass::instance(context.get()); // Test for C API - context->defineGlobalProperty("SampleClass", sampleObject->classObject); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("SampleClass", sampleObject->jsObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->classObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->classObject); + JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); + bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); EXPECT_EQ(isInstanceof, true); JS_FreeValue(context->ctx(), object); // Test with Javascript const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -119,11 +118,11 @@ TEST(HostClass, instanceOf) { TEST(HostClass, inheritance) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "20"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; KRAKEN_LOG(VERBOSE) << errmsg; }); @@ -131,15 +130,15 @@ TEST(HostClass, inheritance) { auto* sampleObject = new SampleClass(context.get()); auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); - context->defineGlobalProperty("SampleClass", sampleObject->classObject); + context->defineGlobalProperty("SampleClass", sampleObject->jsObject); const char* code = "let obj = new SampleClass(1,2,3,4);\n" "console.log(obj.foo())"; context->evaluateJavaScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -147,11 +146,11 @@ TEST(HostClass, inheritance) { TEST(HostClass, inherintanceInJavaScript) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "TEST 10 20"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; KRAKEN_LOG(VERBOSE) << errmsg; }); @@ -159,9 +158,9 @@ TEST(HostClass, inherintanceInJavaScript) { auto* sampleObject = new SampleClass(context.get()); auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); - context->defineGlobalProperty("SampleClass", sampleObject->classObject); + context->defineGlobalProperty("SampleClass", sampleObject->jsObject); const char* code = R"( class Demo extends SampleClass { @@ -178,7 +177,7 @@ let demo = new Demo('test'); console.log(demo.getName(), demo.f(), demo.foo()); )"; context->evaluateJavaScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } @@ -186,17 +185,17 @@ console.log(demo.getName(), demo.f(), demo.foo()); TEST(HostClass, haveFunctionProtoMethods) { bool static errorCalled = false; bool static logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "ƒ ()"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; KRAKEN_LOG(VERBOSE) << errmsg; }); auto& context = bridge->getContext(); auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); const char* code = R"( class Demo extends ParentClass { @@ -212,29 +211,29 @@ class Demo extends ParentClass { console.log(Demo.call); )"; context->evaluateJavaScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } TEST(HostClass, multipleInstance) { bool static errorCalled = false; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; KRAKEN_LOG(VERBOSE) << errmsg; }); auto& context = bridge->getContext(); auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->classObject); + context->defineGlobalProperty("ParentClass", parentObject->jsObject); // Test for C API 1 { auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass1", sampleObject->classObject); + context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->classObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->classObject); + JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); + bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); EXPECT_EQ(isInstanceof, true); JS_FreeValue(context->ctx(), object); } @@ -242,35 +241,34 @@ TEST(HostClass, multipleInstance) { // Test for C API 2 { auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass2", sampleObject->classObject); + context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->classObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->classObject); + JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); + bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); EXPECT_EQ(isInstanceof, true); JS_FreeValue(context->ctx(), object); } { auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass3", sampleObject->classObject); + context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->classObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->classObject); + JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); + bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); EXPECT_EQ(isInstanceof, true); JS_FreeValue(context->ctx(), object); } { auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass4", sampleObject->classObject); + context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->classObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->classObject); + JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); + bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); EXPECT_EQ(isInstanceof, true); JS_FreeValue(context->ctx(), object); } - delete bridge; EXPECT_EQ(errorCalled, false); } @@ -281,10 +279,10 @@ class ExoticClass : public HostClass { public: static JSClassID exoticClassID; ExoticClass() = delete; - explicit ExoticClass(JSContext* context) : HostClass(context, "ExoticClass") { + explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); } - JSValue instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); + JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); private: friend ExoticClassInstance; @@ -300,7 +298,7 @@ class ExoticClassInstance : public Instance { explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, ExoticClass::exoticClassID, finalizer){}; - static JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { + static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); auto* prototype = static_cast(instance->prototype()); if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) { @@ -317,12 +315,12 @@ class ExoticClassInstance : public Instance { static void finalizer(JSRuntime* rt, JSValue val) { auto* instance = static_cast(JS_GetOpaque(val, ExoticClass::exoticClassID)); if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->instanceObject); + JS_FreeValue(instance->m_ctx, instance->jsObject); } delete instance; }; - static int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { + static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); instance->m_properties[atom] = JS_DupValue(ctx, value); return 0; @@ -332,11 +330,11 @@ class ExoticClassInstance : public Instance { class ClassNamePropertyDescriptor { public: - static JSValue getter(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + static JSValue getter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); return JS_NewFloat64(ctx, instance->classValue); }; - static JSValue setter(QjsContext* ctx, JSValue this_val, int argc, JSValue* argv) { + static JSValue setter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); double v; JS_ToFloat64(ctx, &v, argv[0]); @@ -344,7 +342,7 @@ class ExoticClassInstance : public Instance { return JS_NULL; }; }; - ObjectProperty m_getClassName{m_context, instanceObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; + ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; private: std::unordered_map m_properties; @@ -353,25 +351,25 @@ class ExoticClassInstance : public Instance { JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty}; -JSValue ExoticClass::instanceConstructor(QjsContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new ExoticClassInstance(this))->instanceObject; +JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { + return (new ExoticClassInstance(this))->jsObject; } TEST(HostClass, exoticClass) { bool static errorCalled = false; bool static logCalled = false; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "10"); }; auto& context = bridge->getContext(); auto* constructor = new ExoticClass(context.get()); - context->defineGlobalProperty("ExoticClass", constructor->classObject); + context->defineGlobalProperty("ExoticClass", constructor->jsObject); std::string code = "globalThis.obj = new ExoticClass();" @@ -380,34 +378,33 @@ TEST(HostClass, exoticClass) { "obj[key] = function() {return 10;};" "console.log(obj[otherKey]());"; context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); - EXPECT_EQ(exoticClassFreed, true); EXPECT_EQ(logCalled, true); } TEST(HostClass, setExoticClassProperty) { bool static errorCalled = false; bool static logCalled = false; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "200"); }; auto& context = bridge->getContext(); auto* constructor = new ExoticClass(context.get()); - context->defineGlobalProperty("ExoticClass", constructor->classObject); + context->defineGlobalProperty("ExoticClass", constructor->jsObject); std::string code = "var obj = new ExoticClass();" "obj.className = 200.0;" "console.log(obj.className);"; context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + EXPECT_EQ(errorCalled, false); EXPECT_EQ(exoticClassFreed, true); EXPECT_EQ(logCalled, true); diff --git a/bridge/bindings/qjs/host_object.cc b/bridge/bindings/qjs/host_object.cc index 2e2bc7a8df..ba0f8a2c31 100644 --- a/bridge/bindings/qjs/host_object.cc +++ b/bridge/bindings/qjs/host_object.cc @@ -7,10 +7,10 @@ namespace kraken::binding::qjs { -JSValue ExoticHostObject::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue ExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { return JS_NULL; } -int ExoticHostObject::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +int ExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { return 0; } diff --git a/bridge/bindings/qjs/host_object.h b/bridge/bindings/qjs/host_object.h index d947976a5f..5ba3f01db4 100644 --- a/bridge/bindings/qjs/host_object.h +++ b/bridge/bindings/qjs/host_object.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_HOST_OBJECT_H #define KRAKENBRIDGE_HOST_OBJECT_H -#include "js_context.h" +#include "executing_context.h" namespace kraken::binding::qjs { @@ -15,12 +15,12 @@ class HostObject { KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostObject); HostObject() = delete; - HostObject(JSContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { + HostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { JSClassDef def{}; def.class_name = "HostObject"; def.finalizer = proxyFinalize; - JS_NewClass(context->runtime(), JSContext::kHostObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, JSContext::kHostObjectClassId); + JS_NewClass(context->runtime(), ExecutionContext::kHostObjectClassId, &def); + jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostObjectClassId); JS_SetOpaque(jsObject, this); } @@ -29,13 +29,13 @@ class HostObject { protected: virtual ~HostObject() = default; std::string m_name; - JSContext* m_context; + ExecutionContext* m_context; int32_t m_contextId; - QjsContext* m_ctx; + JSContext* m_ctx; private: static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, JSContext::kHostObjectClassId)); + auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostObjectClassId)); delete hostObject; }; }; @@ -45,40 +45,40 @@ class ExoticHostObject { KRAKEN_DISALLOW_COPY_AND_ASSIGN(ExoticHostObject); ExoticHostObject() = delete; - ExoticHostObject(JSContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { + ExoticHostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { JSClassExoticMethods* m_exoticMethods = new JSClassExoticMethods{nullptr, nullptr, nullptr, nullptr, nullptr, proxyGetProperty, proxySetProperty}; JSClassDef def{}; def.class_name = m_name.c_str(); def.finalizer = proxyFinalize; def.exotic = m_exoticMethods; - JS_NewClass(context->runtime(), JSContext::kHostExoticObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, JSContext::kHostExoticObjectClassId); + JS_NewClass(context->runtime(), ExecutionContext::kHostExoticObjectClassId, &def); + jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostExoticObjectClassId); JS_SetOpaque(jsObject, this); } JSValue jsObject{JS_NULL}; - static JSValue proxyGetProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - auto* object = static_cast(JS_GetOpaque(obj, JSContext::kHostExoticObjectClassId)); + static JSValue proxyGetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { + auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); return object->getProperty(ctx, obj, atom, receiver); }; - static int proxySetProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { - auto* object = static_cast(JS_GetOpaque(obj, JSContext::kHostExoticObjectClassId)); + static int proxySetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { + auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); return object->setProperty(ctx, obj, atom, value, receiver, flags); }; - virtual JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - virtual int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + virtual JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + virtual int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); protected: virtual ~ExoticHostObject() = default; std::string m_name; - JSContext* m_context; + ExecutionContext* m_context; int32_t m_contextId; - QjsContext* m_ctx; + JSContext* m_ctx; static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, JSContext::kHostExoticObjectClassId)); + auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostExoticObjectClassId)); delete hostObject; }; }; diff --git a/bridge/bindings/qjs/host_object_test.cc b/bridge/bindings/qjs/host_object_test.cc index d84e9113c6..de7d997945 100644 --- a/bridge/bindings/qjs/host_object_test.cc +++ b/bridge/bindings/qjs/host_object_test.cc @@ -5,8 +5,9 @@ #include "host_object.h" #include -#include "bridge_qjs.h" -#include "js_context.h" +#include "executing_context.h" +#include "kraken_test_env.h" +#include "page.h" namespace kraken::binding::qjs { @@ -14,18 +15,18 @@ static bool isSampleFree = false; class SampleObject : public HostObject { public: - explicit SampleObject(JSContext* context) : HostObject(context, "SampleObject"){}; + explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; ~SampleObject() { isSampleFree = true; } private: class FooPropertyDescriptor { public: - static JSValue getter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, sampleObject->m_foo); } - static JSValue setter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, JSContext::kHostObjectClassId)); + static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); double f; JS_ToFloat64(ctx, &f, argv[0]); sampleObject->m_foo = f; @@ -33,7 +34,7 @@ class SampleObject : public HostObject { } }; - static JSValue f(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { double v; JS_ToFloat64(ctx, &v, argv[0]); return JS_NewFloat64(ctx, 10 + v); @@ -47,19 +48,19 @@ class SampleObject : public HostObject { TEST(HostObject, defineProperty) { bool static logCalled = false; bool static errorCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto& context = bridge->getContext(); auto* sampleObject = new SampleObject(context.get()); JSValue object = sampleObject->jsObject; context->defineGlobalProperty("o", object); const char* code = "o.foo++; console.log(o);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(logCalled, true); EXPECT_EQ(errorCalled, false); } @@ -67,11 +68,11 @@ TEST(HostObject, defineProperty) { TEST(ObjectProperty, worksWithProxy) { bool static logCalled = false; bool static errorCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "0"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -88,7 +89,7 @@ let p = new Proxy(o, { console.log(p.foo); )"); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - delete bridge; + EXPECT_EQ(logCalled, true); EXPECT_EQ(errorCalled, false); } @@ -96,11 +97,11 @@ console.log(p.foo); TEST(HostObject, defineFunction) { bool static logCalled = false; bool static errorCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "20"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -110,7 +111,7 @@ TEST(HostObject, defineFunction) { context->defineGlobalProperty("o", object); const char* code = "console.log(o.f(10))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(logCalled, true); EXPECT_EQ(errorCalled, false); EXPECT_EQ(isSampleFree, true); @@ -118,30 +119,30 @@ TEST(HostObject, defineFunction) { class SampleExoticHostObject : public ExoticHostObject { public: - explicit SampleExoticHostObject(JSContext* context) : ExoticHostObject(context, "SampleObject"){}; + explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; ~SampleExoticHostObject() { isSampleFree = true; } - JSValue getProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - int setProperty(QjsContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); private: }; -JSValue SampleExoticHostObject::getProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { return JS_NewFloat64(ctx, 100.0); } -int SampleExoticHostObject::setProperty(QjsContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { return 0; } TEST(ExoticHostObject, overriteGetterSetter) { bool static logCalled = false; bool static errorCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "100"); }; - auto* bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -151,7 +152,7 @@ TEST(ExoticHostObject, overriteGetterSetter) { context->defineGlobalProperty("o", object); const char* code = "console.log(o.abc)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - delete bridge; + EXPECT_EQ(logCalled, true); EXPECT_EQ(errorCalled, false); EXPECT_EQ(isSampleFree, true); diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index e685dc3763..4dc56ec44e 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -6,7 +6,7 @@ #include "html_parser.h" #include "dom/document.h" #include "dom/text_node.h" -#include "js_context.h" +#include "executing_context.h" #include @@ -19,8 +19,8 @@ inline std::string trim(std::string& str) { } void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { - JSContext* context = root->context(); - QjsContext* ctx = context->ctx(); + ExecutionContext* context = root->context(); + JSContext* ctx = context->ctx(); const GumboVector* children = &node->v.element.children; for (int i = 0; i < children->length; ++i) { @@ -36,7 +36,8 @@ void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { tagName = std::string(piece.data, piece.length); } - JSValue constructor = Element::getConstructor(context, tagName); + auto* Document = Document::instance(context); + JSValue constructor = Document->getElementConstructor(context, tagName); JSValue tagNameValue = JS_NewString(ctx, tagName.c_str()); JSValue argv[] = {tagNameValue}; @@ -60,7 +61,7 @@ void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { } else if (child->type == GUMBO_NODE_TEXT) { JSValue textContentValue = JS_NewString(ctx, child->v.text.text); JSValue argv[] = {textContentValue}; - JSValue textNodeValue = JS_CallConstructor(ctx, TextNode::instance(context)->classObject, 1, argv); + JSValue textNodeValue = JS_CallConstructor(ctx, TextNode::instance(context)->jsObject, 1, argv); JS_FreeValue(ctx, textContentValue); auto* textNodeInstance = static_cast(JS_GetOpaque(textNodeValue, TextNode::classId())); @@ -69,9 +70,8 @@ void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { } } } -bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode) { - std::string html = std::string(code, codeLength); +bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode) { if (rootNode != nullptr) { rootNode->internalClearChild(); @@ -80,6 +80,8 @@ bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* ro size_t html_length = html.length(); auto* htmlTree = gumbo_parse_with_options(&kGumboDefaultOptions, html.c_str(), html_length); traverseHTML(rootNode, htmlTree->root); + // Free gumbo parse nodes. + gumbo_destroy_output(&kGumboDefaultOptions, htmlTree); } } else { KRAKEN_LOG(ERROR) << "Root node is null."; @@ -87,9 +89,15 @@ bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* ro return true; } + +bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode) { + std::string html = std::string(code, codeLength); + return parseHTML(html, rootNode); +} + void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElement) { - JSContext* context = element->context(); - QjsContext* ctx = context->ctx(); + ExecutionContext* context = element->context(); + JSContext* ctx = context->ctx(); GumboVector* attributes = &gumboElement->attributes; for (int j = 0; j < attributes->length; ++j) { @@ -130,10 +138,10 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem JSValue key = JS_NewString(ctx, strName.c_str()); JSValue value = JS_NewString(ctx, strValue.c_str()); - JSValue setAttributeFunc = JS_GetPropertyStr(ctx, element->instanceObject, "setAttribute"); + JSValue setAttributeFunc = JS_GetPropertyStr(ctx, element->jsObject, "setAttribute"); JSValue arguments[] = {key, value}; - JS_Call(ctx, setAttributeFunc, element->instanceObject, 2, arguments); + JS_Call(ctx, setAttributeFunc, element->jsObject, 2, arguments); JS_FreeValue(ctx, setAttributeFunc); JS_FreeValue(ctx, key); diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h index 7cd4c75df6..1b29853287 100644 --- a/bridge/bindings/qjs/html_parser.h +++ b/bridge/bindings/qjs/html_parser.h @@ -7,8 +7,8 @@ #define KRAKENBRIDGE_HTML_PARSER_H #include "bindings/qjs/dom/element.h" +#include "executing_context.h" #include "include/kraken_bridge.h" -#include "js_context.h" #include "third_party/gumbo-parser/src/gumbo.h" namespace kraken::binding::qjs { @@ -16,9 +16,10 @@ namespace kraken::binding::qjs { class HTMLParser { public: static bool parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode); + static bool parseHTML(std::string html, NodeInstance* rootNode); private: - JSContext* m_context; + ExecutionContext* m_context; static void traverseHTML(NodeInstance* root, GumboNode* node); static void parseProperty(ElementInstance* element, GumboElement* gumboElement); }; diff --git a/bridge/bindings/qjs/js_context.h b/bridge/bindings/qjs/js_context.h deleted file mode 100644 index d482d9dc62..0000000000 --- a/bridge/bindings/qjs/js_context.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_JS_CONTEXT_H -#define KRAKENBRIDGE_JS_CONTEXT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "js_context_macros.h" -#include "kraken_foundation.h" -#include "qjs_patch.h" -using QjsContext = JSContext; -using JSExceptionHandler = std::function; - -namespace kraken::binding::qjs { - -static std::once_flag kinitJSClassIDFlag; - -JSRuntime* getGlobalJSRuntime(); -class WindowInstance; -class DocumentInstance; -class JSContext; - -static inline bool isNumberIndex(const std::string& name) { - if (name.empty()) - return false; - char f = name[0]; - return f >= '0' && f <= '9'; -} - -struct PromiseContext { - void* data; - JSContext* context; - JSValue resolveFunc; - JSValue rejectFunc; - JSValue promise; - list_head link; -}; - -struct AtomJob { - JSAtom atom; - list_head link; -}; - -bool isContextValid(int32_t contextId); - -class JSContext { - public: - JSContext() = delete; - JSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - ~JSContext(); - - bool evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); - bool evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); - bool evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); - bool evaluateByteCode(uint8_t* bytes, size_t byteLength); - bool isValid() const; - JSValue global(); - QjsContext* ctx(); - JSRuntime* runtime(); - int32_t getContextId() const; - void* getOwner(); - bool handleException(JSValue* exc); - void drainPendingPromiseJobs(); - void defineGlobalProperty(const char* prop, JSValueConst value); - uint8_t* dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); - - std::chrono::time_point timeOrigin; - std::unordered_map constructorMap; - - int32_t uniqueId; - struct list_head node_job_list; - struct list_head timer_job_list; - struct list_head document_job_list; - struct list_head module_job_list; - struct list_head module_callback_job_list; - struct list_head promise_job_list; - struct list_head atom_job_list; - struct list_head native_function_job_list; - - static JSClassID kHostClassClassId; - static JSClassID kHostObjectClassId; - static JSClassID kHostExoticObjectClassId; - - private: - static void promiseRejectTracker(QjsContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); - void dispatchGlobalErrorEvent(JSValueConst error); - void dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error); - void reportError(JSValueConst error); - - int32_t contextId; - JSExceptionHandler _handler; - void* owner; - JSValue globalObject{JS_NULL}; - bool ctxInvalid_{false}; - QjsContext* m_ctx{nullptr}; - friend WindowInstance; - friend DocumentInstance; - WindowInstance* m_window{nullptr}; -}; - -// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of -// proxy object. -static JSValue handleCallThisOnProxy(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int data_len, JSValueConst* data) { - JSValue f = data[0]; - JSValue result; - if (JS_IsProxy(this_val)) { - result = JS_Call(ctx, f, JS_GetProxyTarget(this_val), argc, argv); - } else { - result = JS_Call(ctx, f, this_val, argc, argv); - } - return result; -} - -class ObjectProperty { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); - - public: - ObjectProperty() = delete; - explicit ObjectProperty(JSContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction) { - JSValue ge = JS_NewCFunction(context->ctx(), getterFunction, "get", 0); - JSValue se = JS_NewCFunction(context->ctx(), setterFunction, "set", 1); - - JSValue pge = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &ge); - JSValue pse = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 1, 0, 1, &se); - - JS_FreeValue(context->ctx(), ge); - JS_FreeValue(context->ctx(), se); - - JSAtom key = JS_NewAtom(context->ctx(), property); - JS_DefinePropertyGetSet(context->ctx(), thisObject, key, pge, pse, JS_PROP_C_W_E); - JS_FreeAtom(context->ctx(), key); - }; - explicit ObjectProperty(JSContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction) { - JSValue get = JS_NewCFunction(context->ctx(), getterFunction, "get", 0); - JSAtom key = JS_NewAtom(context->ctx(), property); - JS_DefineProperty(context->ctx(), thisObject, key, JS_UNDEFINED, get, JS_UNDEFINED, JS_PROP_HAS_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_HAS_GET); - JS_FreeAtom(context->ctx(), key); - } - explicit ObjectProperty(JSContext* context, JSValueConst thisObject, const char* property, JSValue value) { - JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); - } -}; - -class ObjectFunction { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectFunction); - - public: - ObjectFunction() = delete; - explicit ObjectFunction(JSContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc) { - JSValue f = JS_NewCFunction(context->ctx(), function, functionName, argc); - JSValue pf = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, argc, 0, 1, &f); - JSAtom key = JS_NewAtom(context->ctx(), functionName); - - JS_FreeValue(context->ctx(), f); - -// We should avoid overwrite exist property functions. -#ifdef DEBUG - assert_m(JS_HasProperty(context->ctx(), thisObject, key) == 0, (std::string("Found exist function property: ") + std::string(functionName)).c_str()); -#endif - - JS_DefinePropertyValue(context->ctx(), thisObject, key, pf, JS_PROP_ENUMERABLE); - JS_FreeAtom(context->ctx(), key); - }; -}; - -class JSValueHolder { - public: - JSValueHolder() = delete; - explicit JSValueHolder(QjsContext* ctx, JSValue value) : m_value(value), m_ctx(ctx){}; - ~JSValueHolder() { JS_FreeValue(m_ctx, m_value); } - inline void value(JSValue value) { - if (!JS_IsNull(m_value)) { - JS_FreeValue(m_ctx, m_value); - } - m_value = JS_DupValue(m_ctx, value); - }; - inline JSValue value() const { return JS_DupValue(m_ctx, m_value); } - - private: - QjsContext* m_ctx{nullptr}; - JSValue m_value{JS_NULL}; -}; - -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); -NativeString* jsValueToNativeString(QjsContext* ctx, JSValue value); -void buildUICommandArgs(QjsContext* ctx, JSValue key, NativeString& args_01); -NativeString* stringToNativeString(const std::string& string); -NativeString* atomToNativeString(QjsContext* ctx, JSAtom atom); -std::string jsValueToStdString(QjsContext* ctx, JSValue& value); -std::string jsAtomToStdString(QjsContext* ctx, JSAtom atom); -void extractErrorInfo(JSValueConst error); -void arrayPushValue(QjsContext* ctx, JSValue array, JSValue val); -void arrayInsert(QjsContext* ctx, JSValue array, uint32_t start, JSValue targetValue); -int32_t arrayGetLength(QjsContext* ctx, JSValue array); -int32_t arrayFindIdx(QjsContext* ctx, JSValue array, JSValue target); -void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount); -void arraySpliceValue(QjsContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue); -JSValue objectGetKeys(QjsContext* ctx, JSValue obj); - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/js_context_macros.h index 08dbfaee9c..3701708db4 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/js_context_macros.h @@ -7,7 +7,7 @@ #define KRAKENBRIDGE_JS_CONTEXT_MACROS_H #define OBJECT_INSTANCE(NAME) \ - static NAME* instance(JSContext* context) { \ + static NAME* instance(ExecutionContext* context) { \ if (context->constructorMap.count(#NAME) == 0) { \ context->constructorMap[#NAME] = static_cast(new NAME(context)); \ } \ @@ -20,219 +20,43 @@ context->defineGlobalProperty(name, f); \ } -#define PROP_GETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::getter -#define PROP_SETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::setter - -#define HOST_CLASS_PROPERTY_ITEM(NAME) \ - class NAME##PropertyDescriptor { \ - public: \ - static JSValue getter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - static JSValue setter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##NAME{m_context, instanceObject, #NAME, NAME##PropertyDescriptor::getter, NAME##PropertyDescriptor::setter}; - -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(NAME) \ - class NAME##PropertyDescriptor { \ - public: \ - static JSValue getter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - static JSValue setter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##NAME{m_context, m_prototypeObject, #NAME, NAME##PropertyDescriptor::getter, NAME##PropertyDescriptor::setter}; - -#define HOST_OBJECT_PROPERTY_ITEM(NAME) \ - class NAME##PropertyDescriptor { \ - public: \ - static JSValue getter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - static JSValue setter(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty m_##NAME{m_context, jsObject, #NAME, NAME##PropertyDescriptor::getter, NAME##PropertyDescriptor::setter}; - -#define HOST_CLASS_PROPERTY_ITEM_1(_0) HOST_CLASS_PROPERTY_ITEM(_0) -#define HOST_CLASS_PROPERTY_ITEM_2(_0, _1) HOST_CLASS_PROPERTY_ITEM_1(_0) HOST_CLASS_PROPERTY_ITEM(_1) -#define HOST_CLASS_PROPERTY_ITEM_3(_0, _1, _2) HOST_CLASS_PROPERTY_ITEM_2(_0, _1) HOST_CLASS_PROPERTY_ITEM(_2) -#define HOST_CLASS_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_CLASS_PROPERTY_ITEM_3(_0, _1, _2) HOST_CLASS_PROPERTY_ITEM(_3) -#define HOST_CLASS_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_CLASS_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_CLASS_PROPERTY_ITEM(_4) -#define HOST_CLASS_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_CLASS_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_CLASS_PROPERTY_ITEM(_5) -#define HOST_CLASS_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_CLASS_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_CLASS_PROPERTY_ITEM(_6) -#define HOST_CLASS_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_CLASS_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_CLASS_PROPERTY_ITEM(_7) -#define HOST_CLASS_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_CLASS_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_CLASS_PROPERTY_ITEM(_8) -#define HOST_CLASS_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_CLASS_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_CLASS_PROPERTY_ITEM(_9) -#define HOST_CLASS_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) HOST_CLASS_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_CLASS_PROPERTY_ITEM(_10) -#define HOST_CLASS_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) HOST_CLASS_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) HOST_CLASS_PROPERTY_ITEM(_11) -#define HOST_CLASS_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) HOST_CLASS_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) HOST_CLASS_PROPERTY_ITEM(_12) -#define HOST_CLASS_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - HOST_CLASS_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) HOST_CLASS_PROPERTY_ITEM(_13) -#define HOST_CLASS_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_CLASS_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) HOST_CLASS_PROPERTY_ITEM(_14) -#define HOST_CLASS_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_CLASS_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_CLASS_PROPERTY_ITEM(_15) -#define HOST_CLASS_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_CLASS_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_CLASS_PROPERTY_ITEM(_16) -#define HOST_CLASS_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_CLASS_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_CLASS_PROPERTY_ITEM(_17) -#define HOST_CLASS_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_CLASS_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_CLASS_PROPERTY_ITEM(_18) -#define HOST_CLASS_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_CLASS_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_CLASS_PROPERTY_ITEM(_19) -#define HOST_CLASS_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_CLASS_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_CLASS_PROPERTY_ITEM(_20) -#define HOST_CLASS_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_CLASS_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_CLASS_PROPERTY_ITEM(_21) -#define HOST_CLASS_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_CLASS_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_CLASS_PROPERTY_ITEM(_22) -#define HOST_CLASS_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_CLASS_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_CLASS_PROPERTY_ITEM(_23) -#define HOST_CLASS_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_CLASS_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_CLASS_PROPERTY_ITEM(_24) -#define HOST_CLASS_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_CLASS_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_CLASS_PROPERTY_ITEM(_25) -#define HOST_CLASS_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_CLASS_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_CLASS_PROPERTY_ITEM(_26) -#define HOST_CLASS_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_CLASS_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_CLASS_PROPERTY_ITEM(_27) -#define HOST_CLASS_PROPERTY_ITEM_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - HOST_CLASS_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_CLASS_PROPERTY_ITEM(_28) - -#define HOST_OBJECT_PROPERTY_ITEM_1(_0) HOST_OBJECT_PROPERTY_ITEM(_0) -#define HOST_OBJECT_PROPERTY_ITEM_2(_0, _1) HOST_OBJECT_PROPERTY_ITEM_1(_0) HOST_OBJECT_PROPERTY_ITEM(_1) -#define HOST_OBJECT_PROPERTY_ITEM_3(_0, _1, _2) HOST_OBJECT_PROPERTY_ITEM_2(_0, _1) HOST_OBJECT_PROPERTY_ITEM(_2) -#define HOST_OBJECT_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_OBJECT_PROPERTY_ITEM_3(_0, _1, _2) HOST_OBJECT_PROPERTY_ITEM(_3) -#define HOST_OBJECT_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_OBJECT_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_OBJECT_PROPERTY_ITEM(_4) -#define HOST_OBJECT_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_OBJECT_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_OBJECT_PROPERTY_ITEM(_5) -#define HOST_OBJECT_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_OBJECT_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_OBJECT_PROPERTY_ITEM(_6) -#define HOST_OBJECT_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_OBJECT_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_OBJECT_PROPERTY_ITEM(_7) -#define HOST_OBJECT_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_OBJECT_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_OBJECT_PROPERTY_ITEM(_8) -#define HOST_OBJECT_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_OBJECT_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_OBJECT_PROPERTY_ITEM(_9) -#define HOST_OBJECT_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) HOST_OBJECT_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_OBJECT_PROPERTY_ITEM(_10) -#define HOST_OBJECT_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) HOST_OBJECT_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) HOST_OBJECT_PROPERTY_ITEM(_11) -#define HOST_OBJECT_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ - HOST_OBJECT_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) HOST_OBJECT_PROPERTY_ITEM(_12) -#define HOST_OBJECT_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - HOST_OBJECT_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) HOST_OBJECT_PROPERTY_ITEM(_13) -#define HOST_OBJECT_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_OBJECT_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) HOST_OBJECT_PROPERTY_ITEM(_14) -#define HOST_OBJECT_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_OBJECT_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_OBJECT_PROPERTY_ITEM(_15) -#define HOST_OBJECT_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_OBJECT_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_OBJECT_PROPERTY_ITEM(_16) -#define HOST_OBJECT_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_OBJECT_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_OBJECT_PROPERTY_ITEM(_17) -#define HOST_OBJECT_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_OBJECT_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_OBJECT_PROPERTY_ITEM(_18) -#define HOST_OBJECT_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_OBJECT_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_OBJECT_PROPERTY_ITEM(_19) -#define HOST_OBJECT_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_OBJECT_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_OBJECT_PROPERTY_ITEM(_20) -#define HOST_OBJECT_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_OBJECT_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_OBJECT_PROPERTY_ITEM(_21) -#define HOST_OBJECT_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_OBJECT_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_OBJECT_PROPERTY_ITEM(_22) -#define HOST_OBJECT_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_OBJECT_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_OBJECT_PROPERTY_ITEM(_23) -#define HOST_OBJECT_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_OBJECT_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_OBJECT_PROPERTY_ITEM(_24) -#define HOST_OBJECT_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_OBJECT_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_OBJECT_PROPERTY_ITEM(_25) -#define HOST_OBJECT_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_OBJECT_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_OBJECT_PROPERTY_ITEM(_26) -#define HOST_OBJECT_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_OBJECT_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_OBJECT_PROPERTY_ITEM(_27) -#define HOST_OBJECT_PROPERTY_ITEM_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - HOST_OBJECT_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_OBJECT_PROPERTY_ITEM(_28) - -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_1(_0) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_0) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_2(_0, _1) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_1(_0) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_1) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_3(_0, _1, _2) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_2(_0, _1) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_2) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_3(_0, _1, _2) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_3) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_4(_0, _1, _2, _3) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_4) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_5(_0, _1, _2, _3, _4) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_5) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_6(_0, _1, _2, _3, _4, _5) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_6) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_7(_0, _1, _2, _3, _4, _5, _6) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_7) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_8(_0, _1, _2, _3, _4, _5, _6, _7) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_8) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_9) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_10) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_11) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_12) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_13) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_14) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_15) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_16) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_17) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_18) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_19) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_20) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_21) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_22) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_23) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_24) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_25) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_26) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_27) -#define HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - HOST_CLASS_PROTOTYPE_PROPERTY_ITEM(_28) - -#define DEFINE_HOST_CLASS_PROPERTY(ARGS_COUNT, ...) HOST_CLASS_PROPERTY_ITEM_##ARGS_COUNT(__VA_ARGS__) -#define DEFINE_HOST_CLASS_PROTOTYPE_PROPERTY(ARGS_COUNT, ...) HOST_CLASS_PROTOTYPE_PROPERTY_ITEM_##ARGS_COUNT(__VA_ARGS__) -#define DEFINE_HOST_OBJECT_PROPERTY(ARGS_COUNT, ...) HOST_OBJECT_PROPERTY_ITEM_##ARGS_COUNT(__VA_ARGS__) +#define IMPL_PROPERTY_GETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::getter +#define IMPL_PROPERTY_SETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::setter + +#define DEFINE_PROTOTYPE_READONLY_PROPERTY(PROPERTY) \ + class PROPERTY##PropertyDescriptor { \ + public: \ + static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + }; \ + ObjectProperty __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter } + +#define DEFINE_PROTOTYPE_FUNCTION(PROPERTY, ARGS_COUNT) \ + ObjectFunction __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY, ARGS_COUNT } + +#define DEFINE_FUNCTION(PROPERTY, ARGS_COUNT) \ + ObjectFunction __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY, ARGS_COUNT } + +#define DEFINE_PROTOTYPE_PROPERTY(PROPERTY) \ + class PROPERTY##PropertyDescriptor { \ + public: \ + static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + }; \ + ObjectProperty __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter, PROPERTY##PropertyDescriptor::setter } + +#define DEFINE_READONLY_PROPERTY(PROPERTY) \ + class PROPERTY##PropertyDescriptor { \ + public: \ + static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + }; \ + ObjectProperty __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter } + +#define DEFINE_PROPERTY(PROPERTY) \ + class PROPERTY##PropertyDescriptor { \ + public: \ + static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ + }; \ + ObjectProperty __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter, PROPERTY##PropertyDescriptor::setter } #endif // KRAKENBRIDGE_JS_CONTEXT_MACROS_H diff --git a/bridge/bindings/qjs/js_context_test.cc b/bridge/bindings/qjs/js_context_test.cc index fb8dd8f881..367ff6d55f 100644 --- a/bridge/bindings/qjs/js_context_test.cc +++ b/bridge/bindings/qjs/js_context_test.cc @@ -3,33 +3,32 @@ * Author: Kraken Team. */ -#include "bridge_qjs.h" #include "gtest/gtest.h" +#include "kraken_test_env.h" +#include "page.h" TEST(Context, isValid) { - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {}); + auto bridge = TEST_init(); EXPECT_EQ(bridge->getContext()->isValid(), true); - delete bridge; } TEST(Context, evalWithError) { - bool errorHandlerExecuted = false; - auto errorHandler = [&errorHandlerExecuted](int32_t contextId, const char* errmsg) { + static bool errorHandlerExecuted = false; + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; EXPECT_STREQ(errmsg, "TypeError: cannot read property 'toString' of null\n" " at (file://:1)\n"); }; - auto bridge = new kraken::JSBridge(0, errorHandler); + auto bridge = TEST_init(errorHandler); const char* code = "let object = null; object.toString();"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, true); - delete bridge; } TEST(Context, unrejectPromiseError) { - bool errorHandlerExecuted = false; - auto errorHandler = [&errorHandlerExecuted](int32_t contextId, const char* errmsg) { + static bool errorHandlerExecuted = false; + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; EXPECT_STREQ(errmsg, "TypeError: cannot read property 'forceNullError' of null\n" @@ -37,7 +36,7 @@ TEST(Context, unrejectPromiseError) { " at Promise (native)\n" " at (file://:6)\n"); }; - auto bridge = new kraken::JSBridge(0, errorHandler); + auto bridge = TEST_init(errorHandler); const char* code = " var p = new Promise(function (resolve, reject) {\n" " var nullObject = null;\n" @@ -48,13 +47,12 @@ TEST(Context, unrejectPromiseError) { "\n"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, true); - delete bridge; } TEST(Context, unrejectPromiseErrorWithMultipleContext) { - bool errorHandlerExecuted = false; - int32_t errorCalledCount = 0; - auto errorHandler = [&errorHandlerExecuted, &errorCalledCount](int32_t contextId, const char* errmsg) { + static bool errorHandlerExecuted = false; + static int32_t errorCalledCount = 0; + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; errorCalledCount++; EXPECT_STREQ(errmsg, @@ -63,8 +61,9 @@ TEST(Context, unrejectPromiseErrorWithMultipleContext) { " at Promise (native)\n" " at (file://:6)\n"); }; - auto bridge2 = new kraken::JSBridge(0, errorHandler); - auto bridge = new kraken::JSBridge(0, errorHandler); + + auto bridge = TEST_init(errorHandler); + auto bridge2 = TEST_allocateNewPage(); const char* code = " var p = new Promise(function (resolve, reject) {\n" " var nullObject = null;\n" @@ -77,59 +76,77 @@ TEST(Context, unrejectPromiseErrorWithMultipleContext) { bridge2->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, true); EXPECT_EQ(errorCalledCount, 2); - delete bridge; +} + +TEST(Context, accessGetUICommandItemsAfterDisposed) { + int32_t contextId; + { + auto bridge = TEST_init(); + contextId = bridge->getContext()->getContextId(); + } + + EXPECT_EQ(getUICommandItems(contextId), nullptr); +} + +TEST(Context, disposeContext) { + initJSPagePool(1024 * 1024); + TEST_mockDartMethods(nullptr); + uint32_t contextId = 0; + auto bridge = static_cast(getPage(contextId)); + static bool disposed = false; + bridge->disposeCallback = [](kraken::KrakenPage* bridge) { disposed = true; }; + disposePage(bridge->getContext()->getContextId()); + EXPECT_EQ(disposed, true); } TEST(Context, window) { - bool errorHandlerExecuted = false; + static bool errorHandlerExecuted = false; static bool logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto errorHandler = [&errorHandlerExecuted](int32_t contextId, const char* errmsg) { + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; KRAKEN_LOG(VERBOSE) << errmsg; }; - auto bridge = new kraken::JSBridge(0, errorHandler); + auto bridge = TEST_init(errorHandler); const char* code = "console.log(window == globalThis)"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); - delete bridge; } TEST(Context, windowInheritEventTarget) { - bool errorHandlerExecuted = false; + static bool errorHandlerExecuted = false; static bool logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "ƒ () ƒ () ƒ () true"); }; - auto errorHandler = [&errorHandlerExecuted](int32_t contextId, const char* errmsg) { + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; KRAKEN_LOG(VERBOSE) << errmsg; }; - auto bridge = new kraken::JSBridge(0, errorHandler); + auto bridge = TEST_init(errorHandler); const char* code = "console.log(window.addEventListener, addEventListener, globalThis.addEventListener, window.addEventListener === addEventListener)"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); - delete bridge; } TEST(Context, evaluateByteCode) { - bool errorHandlerExecuted = false; + static bool errorHandlerExecuted = false; static bool logCalled = false; - kraken::JSBridge::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "Arguments {0: 1, 1: 2, 2: 3, 3: 4, callee: ƒ (), length: 4}"); }; - auto errorHandler = [&errorHandlerExecuted](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; - auto bridge = new kraken::JSBridge(0, errorHandler); + auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto bridge = TEST_init(errorHandler); const char* code = "function f() { console.log(arguments)} f(1,2,3,4);"; size_t byteLen; uint8_t* bytes = bridge->dumpByteCode(code, strlen(code), "vm://", &byteLen); @@ -137,44 +154,40 @@ TEST(Context, evaluateByteCode) { EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); - delete bridge; } TEST(jsValueToNativeString, utf8String) { - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {}); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->getContext()->ctx(), "helloworld"); - NativeString* nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); EXPECT_EQ(nativeString->length, 10); uint8_t expectedString[10] = {104, 101, 108, 108, 111, 119, 111, 114, 108, 100}; for (int i = 0; i < 10; i++) { EXPECT_EQ(expectedString[i], *(nativeString->string + i)); } JS_FreeValue(bridge->getContext()->ctx(), str); - delete bridge; } TEST(jsValueToNativeString, unicodeChinese) { - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {}); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->getContext()->ctx(), "这是你的优乐美"); - NativeString* nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); std::u16string expectedString = u"这是你的优乐美"; EXPECT_EQ(nativeString->length, expectedString.size()); for (int i = 0; i < nativeString->length; i++) { EXPECT_EQ(expectedString[i], *(nativeString->string + i)); } JS_FreeValue(bridge->getContext()->ctx(), str); - delete bridge; } TEST(jsValueToNativeString, emoji) { - auto bridge = new kraken::JSBridge(0, [](int32_t contextId, const char* errmsg) {}); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->getContext()->ctx(), "……🤪"); - NativeString* nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); std::u16string expectedString = u"……🤪"; EXPECT_EQ(nativeString->length, expectedString.length()); for (int i = 0; i < nativeString->length; i++) { EXPECT_EQ(expectedString[i], *(nativeString->string + i)); } JS_FreeValue(bridge->getContext()->ctx(), str); - delete bridge; } diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/bindings/qjs/module_manager.cc index 3b4d217392..8a05671632 100644 --- a/bridge/bindings/qjs/module_manager.cc +++ b/bridge/bindings/qjs/module_manager.cc @@ -4,12 +4,12 @@ */ #include "module_manager.h" -#include "bridge_qjs.h" +#include "page.h" #include "qjs_patch.h" namespace kraken::binding::qjs { -JSValue krakenModuleListener(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); } @@ -23,7 +23,7 @@ JSValue krakenModuleListener(QjsContext* ctx, JSValueConst this_val, int argc, J return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); auto* link = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; list_add_tail(&link->link, &context->module_job_list); @@ -32,9 +32,9 @@ JSValue krakenModuleListener(QjsContext* ctx, JSValueConst this_val, int argc, J void handleInvokeModuleTransientCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json) { auto* moduleContext = static_cast(callbackContext); - JSContext* context = moduleContext->context; + ExecutionContext* context = moduleContext->context; - if (!checkContext(contextId, context)) + if (!checkPage(contextId, context)) return; if (!context->isValid()) return; @@ -45,7 +45,7 @@ void handleInvokeModuleTransientCallback(void* callbackContext, int32_t contextI return; } - QjsContext* ctx = moduleContext->context->ctx(); + JSContext* ctx = moduleContext->context->ctx(); if (!JS_IsObject(moduleContext->callback)) { return; } @@ -80,7 +80,7 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "Failed to execute 'kraken.invokeModule()': 2 arguments required."); } @@ -90,7 +90,7 @@ JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSV JSValue paramsValue = JS_NULL; JSValue callbackValue = JS_NULL; - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); if (argc > 2 && !JS_IsNull(argv[2])) { paramsValue = argv[2]; @@ -100,6 +100,18 @@ JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSV callbackValue = argv[3]; } + std::unique_ptr moduleName = jsValueToNativeString(ctx, moduleNameValue); + std::unique_ptr method = jsValueToNativeString(ctx, methodValue); + std::unique_ptr params; + if (!JS_IsNull(paramsValue)) { + JSValue stringifyedValue = JS_JSONStringify(ctx, paramsValue, JS_NULL, JS_NULL); + // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. + if (JS_IsException(stringifyedValue)) + return stringifyedValue; + params = jsValueToNativeString(ctx, stringifyedValue); + JS_FreeValue(ctx, stringifyedValue); + } + if (getDartMethod()->invokeModule == nullptr) { #if FLUTTER_BACKEND return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); @@ -108,18 +120,9 @@ JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSV #endif } - NativeString* moduleName = jsValueToNativeString(ctx, moduleNameValue); - NativeString* method = jsValueToNativeString(ctx, methodValue); - NativeString* params = nullptr; - if (!JS_IsNull(paramsValue)) { - JSValue stringifyedValue = JS_JSONStringify(ctx, paramsValue, JS_NULL, JS_NULL); - params = jsValueToNativeString(ctx, stringifyedValue); - JS_FreeValue(ctx, stringifyedValue); - } - ModuleContext* moduleContext; if (JS_IsNull(callbackValue)) { - auto emptyFunction = [](QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_NULL; }; + auto emptyFunction = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_NULL; }; JSValue callbackFunc = JS_NewCFunction(ctx, emptyFunction, "_f", 0); moduleContext = new ModuleContext{callbackFunc, context}; } else { @@ -130,9 +133,15 @@ JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSV NativeString* result; if (!JS_IsNull(callbackValue)) { - result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName, method, params, handleInvokeModuleTransientCallback); + result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); } else { - result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName, method, params, handleInvokeModuleUnexpectedCallback); + result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); + } + + moduleName->free(); + method->free(); + if (params != nullptr) { + params->free(); } if (result == nullptr) { @@ -141,16 +150,11 @@ JSValue krakenInvokeModule(QjsContext* ctx, JSValueConst this_val, int argc, JSV JSValue resultString = JS_NewUnicodeString(context->runtime(), ctx, result->string, result->length); result->free(); - moduleName->free(); - method->free(); - if (params != nullptr) { - params->free(); - } return resultString; } -JSValue flushUICommand(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +JSValue flushUICommand(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (getDartMethod()->flushUICommand == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); } @@ -158,7 +162,7 @@ JSValue flushUICommand(QjsContext* ctx, JSValueConst this_val, int argc, JSValue return JS_NULL; } -void bindModuleManager(std::unique_ptr& context) { +void bindModuleManager(std::unique_ptr& context) { QJS_GLOBAL_BINDING_FUNCTION(context, krakenModuleListener, "__kraken_module_listener__", 1); QJS_GLOBAL_BINDING_FUNCTION(context, krakenInvokeModule, "__kraken_invoke_module__", 3); QJS_GLOBAL_BINDING_FUNCTION(context, flushUICommand, "__kraken_flush_ui_command__", 0); diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/bindings/qjs/module_manager.h index c232fdc394..54aeeb3c54 100644 --- a/bridge/bindings/qjs/module_manager.h +++ b/bridge/bindings/qjs/module_manager.h @@ -6,17 +6,17 @@ #ifndef KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H -#include "js_context.h" +#include "executing_context.h" namespace kraken::binding::qjs { struct ModuleContext { JSValue callback; - JSContext* context; + ExecutionContext* context; list_head link; }; -void bindModuleManager(std::unique_ptr& context); +void bindModuleManager(std::unique_ptr& context); void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/module_manager_test.cc b/bridge/bindings/qjs/module_manager_test.cc new file mode 100644 index 0000000000..4177f811e5 --- /dev/null +++ b/bridge/bindings/qjs/module_manager_test.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include +#include "executing_context.h" +#include "host_object.h" +#include "kraken_test_env.h" +#include "page.h" + +namespace kraken::binding::qjs { + +TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { + bool static errorCalled = false; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + std::string stdErrorMsg = std::string(errmsg); + EXPECT_EQ(stdErrorMsg.find("TypeError: circular reference") != std::string::npos, true); + errorCalled = true; + }); + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + auto& context = bridge->getContext(); + + std::string code = std::string(R"( +let object = { + key: { + v: { + a: { + other: null + } + } + } +}; +object.other = object; +kraken.methodChannel.invokeMethod('abc', 'fn', object); +)"); + context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, true); +} + +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 164caf3154..4d945694a9 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -28,8 +28,9 @@ NativeValue Native_NewString(NativeString* string) { } NativeValue Native_NewCString(std::string string) { - NativeString* nativeString = stringToNativeString(string); - return Native_NewString(nativeString); + std::unique_ptr nativeString = stringToNativeString(string); + // NativeString owned by NativeValue will be freed by users. + return Native_NewString(nativeString.release()); } NativeValue Native_NewFloat64(double value) { @@ -60,9 +61,13 @@ NativeValue Native_NewInt32(int32_t value) { }; } -NativeValue Native_NewJSON(JSContext* context, JSValue& value) { +NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value) { JSValue stringifiedValue = JS_JSONStringify(context->ctx(), value, JS_UNDEFINED, JS_UNDEFINED); - NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue); + if (JS_IsException(stringifiedValue)) + return Native_NewNull(); + + // NativeString owned by NativeValue will be freed by users. + NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue).release(); NativeValue result = (NativeValue){ 0, .u = {.ptr = static_cast(string)}, @@ -93,7 +98,7 @@ void call_native_function(NativeFunctionContext* functionContext, int32_t argc, delete functionContext; } -NativeValue jsValueToNativeValue(QjsContext* ctx, JSValue& value) { +NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { if (JS_IsNull(value) || JS_IsUndefined(value)) { return Native_NewNull(); } else if (JS_IsBool(value)) { @@ -110,15 +115,16 @@ NativeValue jsValueToNativeValue(QjsContext* ctx, JSValue& value) { return Native_NewInt32(v); } } else if (JS_IsString(value)) { - NativeString* string = jsValueToNativeString(ctx, value); + // NativeString owned by NativeValue will be freed by users. + NativeString* string = jsValueToNativeString(ctx, value).release(); return Native_NewString(string); } else if (JS_IsFunction(ctx, value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); auto* functionContext = new NativeFunctionContext{context, value}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->classObject)) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); } @@ -129,7 +135,7 @@ NativeValue jsValueToNativeValue(QjsContext* ctx, JSValue& value) { return Native_NewNull(); } -NativeFunctionContext::NativeFunctionContext(JSContext* context, JSValue callback) : m_context(context), m_ctx(context->ctx()), m_callback(callback), call(call_native_function) { +NativeFunctionContext::NativeFunctionContext(ExecutionContext* context, JSValue callback) : m_context(context), m_ctx(context->ctx()), m_callback(callback), call(call_native_function) { JS_DupValue(context->ctx(), callback); list_add_tail(&link, &m_context->native_function_job_list); }; @@ -139,7 +145,7 @@ NativeFunctionContext::~NativeFunctionContext() { JS_FreeValue(m_ctx, m_callback); } -static JSValue anonymousFunction(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { +static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { auto id = magic; auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); @@ -184,7 +190,7 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int list_del(&promiseContext->link); } -static JSValue anonymousAsyncFunction(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { +static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { JSValue resolving_funcs[2]; JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); @@ -212,7 +218,7 @@ static JSValue anonymousAsyncFunction(QjsContext* ctx, JSValueConst this_val, in return promise; } -JSValue nativeValueToJSValue(JSContext* context, NativeValue& value) { +JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { auto* string = static_cast(value.u.ptr); @@ -243,13 +249,13 @@ JSValue nativeValueToJSValue(JSContext* context, NativeValue& value) { case NativeTag::TAG_POINTER: { auto* ptr = value.u.ptr; int ptrType = (int)value.float64; - if (ptrType == JSPointerType::NativeBoundingClientRect) { + if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; - } else if (ptrType == JSPointerType::NativeCanvasRenderingContext2D) { + } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; - } else if (ptrType == JSPointerType::NativeEventTarget) { + } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { auto* nativeEventTarget = static_cast(ptr); - return JS_DupValue(context->ctx(), nativeEventTarget->instance->instanceObject); + return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); } } case NativeTag::TAG_FUNCTION: { diff --git a/bridge/bindings/qjs/native_value.h b/bridge/bindings/qjs/native_value.h index 816c0153b2..3e7ae0dda0 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/bindings/qjs/native_value.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_NATIVE_VALUE_H #define KRAKENBRIDGE_NATIVE_VALUE_H -#include "js_context.h" +#include "executing_context.h" enum NativeTag { TAG_STRING = 0, @@ -20,7 +20,7 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 8, }; -enum JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; namespace kraken::binding::qjs { @@ -42,11 +42,11 @@ static void call_native_function(NativeFunctionContext* functionContext, int32_t struct NativeFunctionContext { CallNativeFunction call; - NativeFunctionContext(JSContext* context, JSValue callback); + NativeFunctionContext(ExecutionContext* context, JSValue callback); ~NativeFunctionContext(); JSValue m_callback{JS_NULL}; - JSContext* m_context{nullptr}; - QjsContext* m_ctx{nullptr}; + ExecutionContext* m_context{nullptr}; + JSContext* m_ctx{nullptr}; list_head link; }; @@ -57,9 +57,9 @@ NativeValue Native_NewFloat64(double value); NativeValue Native_NewBool(bool value); NativeValue Native_NewInt32(int32_t value); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(JSContext* context, JSValue& value); -NativeValue jsValueToNativeValue(QjsContext* ctx, JSValue& value); -JSValue nativeValueToJSValue(JSContext* context, NativeValue& value); +NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value); +NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); +JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/qjs_patch.cc b/bridge/bindings/qjs/qjs_patch.cc index cacd8d9174..264820fbb6 100644 --- a/bridge/bindings/qjs/qjs_patch.cc +++ b/bridge/bindings/qjs/qjs_patch.cc @@ -66,6 +66,16 @@ struct JSShape { JSShapeProperty prop[0]; /* prop_size elements */ }; +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer* finalizer; + JSClassGCMark* gc_mark; + JSClassCall* call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods* exotic; +}; + struct JSRuntime { JSMallocFunctions mf; JSMallocState malloc_state; @@ -233,30 +243,30 @@ uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length) { if (JS_VALUE_GET_TAG(value) != JS_TAG_STRING) { value = JS_ToString(ctx, value); if (JS_IsException(value)) - return NULL; + return nullptr; } else { value = JS_DupValue(ctx, value); } + uint16_t* buffer; JSString* string = JS_VALUE_GET_STRING(value); if (!string->is_wide_char) { uint8_t* p = string->u.str8; uint32_t len = *length = string->len; - auto* newBuf = (uint16_t*)malloc(sizeof(uint16_t) * len * 2); + buffer = (uint16_t*)malloc(sizeof(uint16_t) * len * 2); for (size_t i = 0; i < len; i++) { - newBuf[i] = p[i]; - newBuf[i + 1] = 0x00; + buffer[i] = p[i]; + buffer[i + 1] = 0x00; } - JS_FreeValue(ctx, value); - return newBuf; } else { *length = string->len; + buffer = (uint16_t*)malloc(sizeof(uint16_t) * string->len); + memcpy(buffer, string->u.str16, sizeof(uint16_t) * string->len); } JS_FreeValue(ctx, value); - - return string->u.str16; + return buffer; } static JSString* js_alloc_string_rt(JSRuntime* rt, int max_len, int is_wide_char) { @@ -310,6 +320,12 @@ bool JS_IsProxy(JSValue value) { return p->class_id == JS_CLASS_PROXY; } +bool JS_HasClassId(JSRuntime* runtime, JSClassID classId) { + if (runtime->class_count <= classId) + return false; + return runtime->class_array[classId].class_id == classId; +} + JSValue JS_GetProxyTarget(JSValue value) { JSObject* p = JS_VALUE_GET_OBJ(value); return p->u.proxy_data->target; diff --git a/bridge/bindings/qjs/qjs_patch.h b/bridge/bindings/qjs/qjs_patch.h index d57c68a711..75b8b80e8c 100644 --- a/bridge/bindings/qjs/qjs_patch.h +++ b/bridge/bindings/qjs/qjs_patch.h @@ -103,6 +103,7 @@ uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length); JSValue JS_NewUnicodeString(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, uint32_t length); JSClassID JSValueGetClassId(JSValue); bool JS_IsProxy(JSValue value); +bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); JSValue JS_GetProxyTarget(JSValue value); #ifdef __cplusplus diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_patch_test.cc index ff3ebd2f7b..8ef564d28c 100644 --- a/bridge/bindings/qjs/qjs_patch_test.cc +++ b/bridge/bindings/qjs/qjs_patch_test.cc @@ -21,6 +21,7 @@ TEST(JS_ToUnicode, asciiWords) { JS_FreeValue(ctx, value); JS_FreeContext(ctx); JS_FreeRuntime(runtime); + delete buffer; } TEST(JS_ToUnicode, chineseWords) { diff --git a/bridge/dart_methods.cc b/bridge/dart_methods.cc index 9bce4a036b..b815895f64 100644 --- a/bridge/dart_methods.cc +++ b/bridge/dart_methods.cc @@ -43,7 +43,6 @@ void registerDartMethods(uint64_t* methodBytes, int32_t length) { methodPointer->platformBrightness = reinterpret_cast(methodBytes[i++]); methodPointer->toBlob = reinterpret_cast(methodBytes[i++]); methodPointer->flushUICommand = reinterpret_cast(methodBytes[i++]); - methodPointer->initHTML = reinterpret_cast(methodBytes[i++]); methodPointer->initWindow = reinterpret_cast(methodBytes[i++]); methodPointer->initDocument = reinterpret_cast(methodBytes[i++]); diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index b5393793b8..e8e312ac78 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -7,11 +7,7 @@ #include #include "colors.h" -#if KRAKEN_JSC_ENGINE -#include "bridge_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#include "bridge_qjs.h" -#endif +#include "page.h" #if defined(IS_ANDROID) #include @@ -140,8 +136,8 @@ void printLog(int32_t contextId, std::stringstream& stream, std::string level, v KRAKEN_LOG(VERBOSE) << stream.str(); } - if (kraken::JSBridge::consoleMessageHandler != nullptr) { - kraken::JSBridge::consoleMessageHandler(ctx, stream.str(), static_cast(_log_level)); + if (kraken::KrakenPage::consoleMessageHandler != nullptr) { + kraken::KrakenPage::consoleMessageHandler(ctx, stream.str(), static_cast(_log_level)); } } diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index d6d4e8f8f2..0d14560f24 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -3,6 +3,7 @@ * Author: Kraken Team. */ +#include "ui_command_buffer.h" #include "dart_methods.h" #include "include/kraken_bridge.h" @@ -55,16 +56,6 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01 queue.emplace_back(item); } -UICommandBuffer* UICommandBuffer::instance(int32_t contextId) { - static std::unordered_map instanceMap; - - if (instanceMap.count(contextId) == 0) { - instanceMap[contextId] = new UICommandBuffer(contextId); - } - - return instanceMap[contextId]; -} - UICommandItem* UICommandBuffer::data() { return queue.data(); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h new file mode 100644 index 0000000000..d38776e201 --- /dev/null +++ b/bridge/foundation/ui_command_buffer.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ +#define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ + +#include "include/kraken_bridge.h" + +namespace foundation { + +class UICommandBuffer { + public: + UICommandBuffer() = delete; + explicit UICommandBuffer(int32_t contextId); + void addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate); + void addCommand(int32_t id, int32_t type, void* nativePtr); + void addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr); + void addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr); + UICommandItem* data(); + int64_t size(); + void clear(); + + private: + int32_t contextId; + std::atomic update_batched{false}; + std::vector queue; +}; + +} // namespace foundation + +#endif // KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ diff --git a/bridge/include/dart_methods.h b/bridge/include/dart_methods.h index 4a58e332e6..d07c37b0ad 100644 --- a/bridge/include/dart_methods.h +++ b/bridge/include/dart_methods.h @@ -34,7 +34,6 @@ typedef NativeString* (*PlatformBrightness)(int32_t contextId); typedef void (*ToBlob)(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio); typedef void (*OnJSError)(int32_t contextId, const char*); typedef void (*FlushUICommand)(); -typedef void (*InitHTML)(int32_t contextId, void* nativePtr); typedef void (*InitWindow)(int32_t contextId, void* nativePtr); typedef void (*InitDocument)(int32_t contextId, void* nativePtr); @@ -83,7 +82,6 @@ struct DartMethodPointer { #if ENABLE_PROFILE GetPerformanceEntries getPerformanceEntries{nullptr}; #endif - InitHTML initHTML{nullptr}; InitWindow initWindow{nullptr}; InitDocument initDocument{nullptr}; }; diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index e7a375fc78..3a1935b2b2 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -25,7 +25,7 @@ std::thread::id getUIThreadId(); struct NativeString { const uint16_t* string; - int32_t length; + uint32_t length; NativeString* clone(); void free(); @@ -91,15 +91,15 @@ typedef void (*Task)(void*); typedef void (*ConsoleMessageHandler)(void* ctx, const std::string& message, int logLevel); KRAKEN_EXPORT_C -void initJSContextPool(int poolSize); +void initJSPagePool(int poolSize); KRAKEN_EXPORT_C -void disposeContext(int32_t contextId); +void disposePage(int32_t contextId); KRAKEN_EXPORT_C -int32_t allocateNewContext(int32_t targetContextId); +int32_t allocateNewPage(int32_t targetContextId); KRAKEN_EXPORT_C -void* getJSContext(int32_t contextId); -bool checkContext(int32_t contextId); -bool checkContext(int32_t contextId, void* context); +void* getPage(int32_t contextId); +bool checkPage(int32_t contextId); +bool checkPage(int32_t contextId, void* context); KRAKEN_EXPORT_C void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); KRAKEN_EXPORT_C diff --git a/bridge/include/kraken_bridge_jsc.h b/bridge/include/kraken_bridge_jsc.h deleted file mode 100644 index 46f3950d0b..0000000000 --- a/bridge/include/kraken_bridge_jsc.h +++ /dev/null @@ -1,1148 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKEN_BRIDGE_JSC_H -#define KRAKEN_BRIDGE_JSC_H - -// MUST READ: -// All the struct which prefix with NativeXXX struct (exp: NativeElement) has a corresponding struct in Dart code. -// All struct members include variables and functions must be follow the same order with Dart class, to keep the same -// memory layout cross dart and C++ code. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "kraken_bridge_jsc_config.h" -#include "kraken_foundation.h" -#include "third_party/gumbo-parser/src/gumbo.h" - -class NativeString; - -namespace kraken::binding::jsc { - -class JSContext; -class JSFunctionHolder; -class JSStringHolder; -class JSValueHolder; -class HostObject; -template -class JSHostObjectHolder; -class HostClass; -class JSEvent; -class EventInstance; -class NativeEvent; -class NativeEventTarget; -class JSEventTarget; -class EventTargetInstance; -class JSNode; -class NodeInstance; -struct NativeNode; -class JSDocument; -class DocumentCookie; -class DocumentInstance; -struct NativeDocument; -class CSSStyleDeclaration; -class JSElementAttributes; -class JSElement; -class JSImageElement; -class ElementInstance; -struct NativeBoundingClientRect; -class BoundingClientRect; -struct NativeElement; -struct NativeImageElement; -class JSEvent; -struct NativeEvent; -class JSGestureEvent; -struct NativeGestureEvent; -class GestureEventInstance; -struct NativeMouseEvent; -class MouseEventInstance; -struct NativePopStateEvent; -class PopStateEventInstance; - -class SpaceSplitString { - public: - SpaceSplitString() = default; - SpaceSplitString(std::string& string) { set(string); } - - void set(std::string& string) { - size_t pos = 0; - std::string token; - std::string s = string; - while ((pos = s.find(m_delimiter)) != std::string::npos) { - token = s.substr(0, pos); - m_szData.push_back(token); - s.erase(0, pos + m_delimiter.length()); - } - m_szData.push_back(s); - } - - bool contains(std::string& string) { - for (std::string s : m_szData) { - if (s == string) { - return true; - } - } - - return false; - } - - bool containsAll(std::string string) { - std::vector szData; - size_t pos = 0; - std::string token; - std::string s = string; - - while ((pos = s.find(m_delimiter)) != std::string::npos) { - token = s.substr(0, pos); - szData.push_back(token); - s.erase(0, pos + m_delimiter.length()); - } - szData.push_back(s); - - bool flag = true; - for (std::string str : szData) { - bool isContains = false; - for (std::string data : m_szData) { - if (data == str) { - isContains = true; - break; - } - } - flag &= isContains; - } - - return flag; - } - - private: - std::string m_delimiter = " "; - std::vector m_szData; -}; - -class JSContext { - public: - static std::vector globalFunctions; - static std::vector globalValue; - - JSContext() = delete; - JSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - ~JSContext(); - - KRAKEN_EXPORT bool evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); - KRAKEN_EXPORT bool evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); - - KRAKEN_EXPORT bool isValid(); - - KRAKEN_EXPORT JSObjectRef global(); - KRAKEN_EXPORT JSGlobalContextRef context(); - - KRAKEN_EXPORT int32_t getContextId(); - - KRAKEN_EXPORT void* getOwner(); - - KRAKEN_EXPORT bool handleException(JSValueRef exc); - - KRAKEN_EXPORT void reportError(const char* errmsg); - - std::chrono::time_point timeOrigin; - - int32_t uniqueId; - - private: - int32_t contextId; - JSExceptionHandler _handler; - void* owner; - std::atomic ctxInvalid_{false}; - JSGlobalContextRef ctx_; -}; - -class HTMLParser { - public: - bool parseHTML(JSContext* context, JSStringRef sourceRef, NodeInstance* rootNode); - - static HTMLParser* instance() { - if (m_instance == nullptr) { - m_instance = new HTMLParser(); - } - return m_instance; - } - - private: - static HTMLParser* m_instance; - void traverseHTML(JSContext* context, GumboNode* node, NodeInstance* rootNode); - void parseProperty(JSContext* context, ElementInstance* element, GumboElement* gumboElement); -}; - -class KRAKEN_EXPORT JSFunctionHolder { - public: - JSFunctionHolder() = delete; - KRAKEN_EXPORT explicit JSFunctionHolder(JSContext* context, JSObjectRef root, void* data, const std::string& name, JSObjectCallAsFunctionCallback callback); - JSObjectRef function(); - - private: - JSObjectRef m_function{nullptr}; - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(JSFunctionHolder); -}; - -class KRAKEN_EXPORT JSStringHolder { - public: - JSStringHolder() = delete; - explicit JSStringHolder(JSContext* context, const std::string& string); - ~JSStringHolder(); - - JSValueRef makeString(); - JSStringRef getString(); - std::string string(); - - const JSChar* ptr(); - size_t utf8Size(); - size_t size(); - bool empty(); - - void setString(JSStringRef value); - void setString(NativeString* value); - - private: - JSContext* m_context; - JSStringRef m_string{nullptr}; - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(JSStringHolder); -}; - -class KRAKEN_EXPORT JSValueHolder { - public: - JSValueHolder() = delete; - explicit JSValueHolder(JSContext* context, JSValueRef value); - ~JSValueHolder(); - JSValueRef value(); - void setValue(JSValueRef value); - - private: - JSContext* m_context; - JSValueRef m_value{nullptr}; - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(JSValueHolder); -}; - -void KRAKEN_EXPORT buildUICommandArgs(JSStringRef key, NativeString& args_01); -void KRAKEN_EXPORT buildUICommandArgs(std::string& key, NativeString& args_01); -void KRAKEN_EXPORT buildUICommandArgs(std::string& key, JSStringRef value, NativeString& args_01, NativeString& args_02); -void KRAKEN_EXPORT buildUICommandArgs(std::string& key, std::string& value, NativeString& args_01, NativeString& args_02); - -void KRAKEN_EXPORT throwJSError(JSContextRef ctx, const char* msg, JSValueRef* exception); - -KRAKEN_EXPORT NativeString* stringToNativeString(std::string& string); -KRAKEN_EXPORT NativeString* stringRefToNativeString(JSStringRef string); - -KRAKEN_EXPORT JSObjectRef makeObjectFunctionWithPrivateData(JSContext* context, void* data, const char* name, JSObjectCallAsFunctionCallback callback); - -KRAKEN_EXPORT JSObjectRef JSObjectMakePromise(JSContext* context, void* data, JSObjectCallAsFunctionCallback callback, JSValueRef* exception); - -KRAKEN_EXPORT std::string JSStringToStdString(JSStringRef jsString); - -class HostObject { - public: - static JSValueRef proxyGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - static bool proxySetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); - static void proxyGetPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); - static void proxyFinalize(JSObjectRef obj); - - HostObject() = delete; - HostObject(JSContext* context, std::string name); - std::string name; - - JSContext* context; - int32_t contextId; - JSObjectRef jsObject; - JSContextRef ctx; - // The C++ object's dtor will be called when the GC finalizes this - // object. (This may be as late as when the JSContext is shut down.) - // You have no control over which thread it is called on. This will - // be called from inside the GC, so it is unsafe to do any VM - // operations which require a JSContext&. Derived classes' dtors - // should also avoid doing anything expensive. Calling the dtor on - // a js object is explicitly ok. If you want to do JS operations, - // or any nontrivial work, you should add it to a work queue, and - // manage it externally. - virtual ~HostObject(); - - // When JS wants a property with a given name from the HostObject, - // it will call this method. If it throws an exception, the call - // will throw a JS \c Error object. By default this returns undefined. - // \return the value for the property. - KRAKEN_EXPORT virtual JSValueRef getProperty(std::string& name, JSValueRef* exception); - - // When JS wants to set a property with a given name on the HostObject, - // it will call this method. If it throws an exception, the call will - // throw a JS \c Error object. By default this throws a type error exception - // mimicking the behavior of a frozen object in strict mode. - KRAKEN_EXPORT virtual bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception); - - KRAKEN_EXPORT virtual void getPropertyNames(JSPropertyNameAccumulatorRef accumulator); - - private: - JSClassRef jsClass; -}; - -template -class JSHostObjectHolder { - public: - JSHostObjectHolder() = delete; - explicit JSHostObjectHolder(JSContext* context, JSObjectRef root, const char* key, T* hostObject) : m_object(hostObject), m_context(context) { - JSStringHolder keyStringHolder = JSStringHolder(context, key); - JSObjectSetProperty(context->context(), root, keyStringHolder.getString(), hostObject->jsObject, kJSPropertyAttributeNone, nullptr); - } - T* operator*() { return m_object; } - - private: - T* m_object; - JSContext* m_context{nullptr}; -}; - -class HostClass { - public: - static void proxyFinalize(JSObjectRef object); - static bool proxyHasInstance(JSContextRef ctx, JSObjectRef constructor, JSValueRef possibleInstance, JSValueRef* exception); - static JSValueRef proxyCallAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSObjectRef proxyCallAsConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef proxyGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - static JSValueRef proxyInstanceGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - static JSValueRef proxyPrototypeGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - static bool proxyInstanceSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); - static void proxyInstanceGetPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); - static void proxyInstanceFinalize(JSObjectRef obj); - - HostClass() = delete; - HostClass(JSContext* context, std::string name); - HostClass(JSContext* context, HostClass* parentHostClass, std::string name, const JSStaticFunction* staticFunction, const JSStaticValue* staticValue); - - KRAKEN_EXPORT virtual JSValueRef getProperty(std::string& name, JSValueRef* exception); - KRAKEN_EXPORT virtual JSValueRef prototypeGetProperty(std::string& name, JSValueRef* exception); - - // Triggered when this HostClass had been finalized by GC. - KRAKEN_EXPORT virtual ~HostClass(); - - KRAKEN_EXPORT virtual JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception); - - // The instance class represent every javascript instance objects created by new expression. - class Instance { - public: - Instance() = delete; - KRAKEN_EXPORT explicit Instance(HostClass* hostClass); - KRAKEN_EXPORT virtual ~Instance(); - KRAKEN_EXPORT virtual JSValueRef getProperty(std::string& name, JSValueRef* exception); - KRAKEN_EXPORT virtual bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception); - KRAKEN_EXPORT virtual void getPropertyNames(JSPropertyNameAccumulatorRef accumulator); - - template - T* prototype() { - return reinterpret_cast(_hostClass); - } - - JSObjectRef object{nullptr}; - HostClass* _hostClass{nullptr}; - JSContext* context{nullptr}; - JSContextRef ctx{nullptr}; - int32_t contextId; - }; - - static bool hasProto(JSContextRef ctx, JSObjectRef child, JSValueRef* exception); - static JSObjectRef getProto(JSContextRef ctx, JSObjectRef child, JSValueRef* exception); - static void setProto(JSContextRef ctx, JSObjectRef prototype, JSObjectRef child, JSValueRef* exception); - - std::string _name{""}; - JSContext* context{nullptr}; - int32_t contextId; - JSContextRef ctx{nullptr}; - // The javascript constructor function. - JSObjectRef classObject{nullptr}; - // The class template of javascript instance objects. - JSClassRef instanceClass{nullptr}; - // The prototype object of this class. - JSObjectRef prototypeObject{nullptr}; - JSObjectRef _call{nullptr}; - - private: - // The class template of javascript constructor function. - JSClassRef jsClass{nullptr}; - HostClass* _parentHostClass{nullptr}; -}; - -class JSHostClassHolder { - public: - JSHostClassHolder() = delete; - explicit JSHostClassHolder(JSContext* context, JSObjectRef root, const char* key, HostClass::Instance* hostClass) : m_object(hostClass), m_context(context) { - JSStringHolder keyStringHolder = JSStringHolder(context, key); - JSObjectSetProperty(context->context(), root, keyStringHolder.getString(), hostClass->object, kJSPropertyAttributeNone, nullptr); - } - HostClass::Instance* operator*() { return m_object; } - - private: - HostClass::Instance* m_object; - JSContext* m_context{nullptr}; -}; - -using EventCreator = EventInstance* (*)(JSContext* context, void* nativeEvent); - -class JSEvent : public HostClass { - public: - DEFINE_OBJECT_PROPERTY(Event, 10, type, bubbles, cancelable, timestamp, defaultPrevented, target, srcElement, currentTarget, returnValue, cancelBubble) - DEFINE_PROTOTYPE_OBJECT_PROPERTY(Event, 4, stopImmediatePropagation, stopPropagation, preventDefault, initEvent) - - static std::unordered_map instanceMap; - static std::unordered_map eventCreatorMap; - OBJECT_INSTANCE(JSEvent) - // Create an Event Object from an nativeEvent address which allocated by dart side. - static JSValueRef initWithNativeEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef stopPropagation(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef initEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef stopImmediatePropagation(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef preventDefault(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static EventInstance* buildEventInstance(std::string& eventType, JSContext* context, void* nativeEvent, bool isCustomEvent); - - static void defineEvent(std::string eventType, EventCreator creator); - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSEvent() = delete; - explicit JSEvent(JSContext* context, const char* name); - explicit JSEvent(JSContext* context); - ~JSEvent() override; - - private: - friend EventInstance; - JSFunctionHolder m_initWithNativeEvent{context, classObject, this, "__initWithNativeEvent__", initWithNativeEvent}; - JSFunctionHolder m_stopImmediatePropagation{context, prototypeObject, this, "stopImmediatePropagation", stopImmediatePropagation}; - JSFunctionHolder m_stopPropagation{context, prototypeObject, this, "stopPropagation", stopPropagation}; - JSFunctionHolder m_initEvent{context, prototypeObject, this, "initEvent", initEvent}; - JSFunctionHolder m_preventDefault{context, prototypeObject, this, "preventDefault", preventDefault}; -}; - -class EventInstance : public HostClass::Instance { - public: - EventInstance() = delete; - - explicit EventInstance(JSEvent* jsEvent, NativeEvent* nativeEvent); - explicit EventInstance(JSEvent* jsEvent, std::string eventType, JSValueRef eventInit, JSValueRef* exception); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - ~EventInstance() override; - NativeEvent* nativeEvent; - bool _cancelled{false}; - bool _propagationStopped{false}; - bool _propagationImmediatelyStopped{false}; - - private: - friend JSEvent; -}; - -struct NativeEvent { - NativeEvent() = delete; - explicit KRAKEN_EXPORT NativeEvent(NativeString* eventType) : type(eventType){}; - NativeString* type; - int64_t bubbles{0}; - int64_t cancelable{0}; - int64_t timeStamp{0}; - int64_t defaultPrevented{0}; - // The pointer address of target EventTargetInstance object. - void* target{nullptr}; - // The pointer address of current target EventTargetInstance object. - void* currentTarget{nullptr}; -}; - -class JSEventTarget : public HostClass { - public: - static std::unordered_map instanceMap; - static JSEventTarget* instance(JSContext* context); - DEFINE_OBJECT_PROPERTY(EventTarget, 1, eventTargetId); - -#if defined(IS_TEST) - DEFINE_PROTOTYPE_OBJECT_PROPERTY(EventTarget, 4, addEventListener, removeEventListener, dispatchEvent, __kraken_clear_event_listeners__); -#else - DEFINE_PROTOTYPE_OBJECT_PROPERTY(EventTarget, 3, addEventListener, removeEventListener, dispatchEvent); -#endif - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - JSValueRef prototypeGetProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSEventTarget() = delete; - friend EventTargetInstance; - KRAKEN_EXPORT explicit JSEventTarget(JSContext* context, const char* name); - KRAKEN_EXPORT explicit JSEventTarget(JSContext* context, const JSStaticFunction* staticFunction, const JSStaticValue* staticValue); - ~JSEventTarget(); - - private: - std::vector m_jsOnlyEvents; - - static JSValueRef addEventListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef removeEventListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef dispatchEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef clearListeners(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - JSFunctionHolder m_removeEventListener{context, prototypeObject, nullptr, "removeEventListener", removeEventListener}; - JSFunctionHolder m_dispatchEvent{context, prototypeObject, nullptr, "dispatchEvent", dispatchEvent}; - JSFunctionHolder m_addEventListener{context, prototypeObject, nullptr, "addEventListener", addEventListener}; -#ifdef IS_TEST - JSFunctionHolder m_clearListeners{context, prototypeObject, nullptr, "__kraken_clear_event_listeners__", clearListeners}; -#endif -}; - -class EventTargetInstance : public HostClass::Instance { - public: - EventTargetInstance() = delete; - KRAKEN_EXPORT explicit EventTargetInstance(JSEventTarget* eventTarget); - KRAKEN_EXPORT explicit EventTargetInstance(JSEventTarget* eventTarget, int64_t targetId); - KRAKEN_EXPORT JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - KRAKEN_EXPORT bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - KRAKEN_EXPORT void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - JSValueRef getPropertyHandler(std::string& name, JSValueRef* exception); - void setPropertyHandler(std::string& name, JSValueRef value, JSValueRef* exception); - bool dispatchEvent(EventInstance* event); - - ~EventTargetInstance() override; - int32_t eventTargetId; - NativeEventTarget* nativeEventTarget{nullptr}; - - private: - friend JSEventTarget; - // TODO: use std::u16string for better performance. - std::unordered_map> _eventHandlers; - std::unordered_map _propertyEventHandler; - bool internalDispatchEvent(EventInstance* eventInstance); -}; - -using NativeDispatchEvent = void (*)(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); - -struct NativeEventTarget { - NativeEventTarget() = delete; - NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(NativeEventTarget::dispatchEventImpl){}; - - KRAKEN_EXPORT static void dispatchEventImpl(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); - - EventTargetInstance* instance; - NativeDispatchEvent dispatchEvent; -}; - -enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11 }; - -class JSNode : public JSEventTarget { - public: - static std::unordered_map instanceMap; - static JSNode* instance(JSContext* context); - DEFINE_OBJECT_PROPERTY(Node, 10, isConnected, ownerDocument, firstChild, lastChild, parentNode, childNodes, previousSibling, nextSibling, nodeType, textContent); - DEFINE_PROTOTYPE_OBJECT_PROPERTY(Node, 6, appendChild, remove, removeChild, insertBefore, replaceChild, cloneNode); - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - static JSValueRef cloneNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef appendChild(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - /** - * The ChildNode.remove() method removes the object - * from the tree it belongs to. - * reference: https://dom.spec.whatwg.org/#dom-childnode-remove - */ - static JSValueRef remove(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef removeChild(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef insertBefore(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef replaceChild(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - JSValueRef prototypeGetProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSNode() = delete; - explicit JSNode(JSContext* context); - explicit JSNode(JSContext* context, const char* name); - ~JSNode(); - - JSFunctionHolder m_cloneNode{context, prototypeObject, this, "cloneNode", cloneNode}; - JSFunctionHolder m_removeChild{context, prototypeObject, this, "removeChild", removeChild}; - JSFunctionHolder m_appendChild{context, prototypeObject, this, "appendChild", appendChild}; - JSFunctionHolder m_remove{context, prototypeObject, this, "remove", remove}; - JSFunctionHolder m_insertBefore{context, prototypeObject, this, "insertBefore", insertBefore}; - JSFunctionHolder m_replaceChild{context, prototypeObject, this, "replaceChild", replaceChild}; - - private: - friend NodeInstance; - static void traverseCloneNode(JSContextRef ctx, NodeInstance* element, NodeInstance* parentElement); - static JSValueRef copyNodeValue(JSContextRef ctx, NodeInstance* element); -}; - -class NodeInstance : public EventTargetInstance { - public: - enum class NodeFlag : uint32_t { IsDocumentFragment = 1 << 0 }; - - mutable std::set m_nodeFlags; - bool hasNodeFlag(NodeFlag flag) const { return m_nodeFlags.size() != 0 && m_nodeFlags.find(flag) != m_nodeFlags.end(); } - void setNodeFlag(NodeFlag flag) const { m_nodeFlags.insert(flag); } - void removeNodeFlag(NodeFlag flag) const { m_nodeFlags.erase(flag); } - - NodeInstance() = delete; - NodeInstance(JSNode* node, NodeType nodeType); - NodeInstance(JSNode* node, NodeType nodeType, int64_t targetId); - ~NodeInstance() override; - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - - bool isConnected(); - DocumentInstance* ownerDocument(); - NodeInstance* firstChild(); - NodeInstance* lastChild(); - NodeInstance* previousSibling(); - NodeInstance* nextSibling(); - void internalAppendChild(NodeInstance* node); - void internalRemove(JSValueRef* exception); - NodeInstance* internalRemoveChild(NodeInstance* node, JSValueRef* exception); - void internalInsertBefore(NodeInstance* node, NodeInstance* referenceNode, JSValueRef* exception); - virtual std::string internalGetTextContent(); - virtual void internalSetTextContent(JSStringRef content, JSValueRef* exception); - NodeInstance* internalReplaceChild(NodeInstance* newChild, NodeInstance* oldChild, JSValueRef* exception); - - NodeType nodeType; - NodeInstance* parentNode{nullptr}; - std::vector childNodes; - - NativeNode* nativeNode{nullptr}; - - void refer(); - void unrefer(); - - inline DocumentInstance* document() { return m_document; } - - int32_t _referenceCount{0}; - virtual void _notifyNodeRemoved(NodeInstance* node); - virtual void _notifyNodeInsert(NodeInstance* node); - - private: - DocumentInstance* m_document{nullptr}; - void ensureDetached(NodeInstance* node); - friend DocumentInstance; - friend JSNode; -}; - -struct NativeNode { - NativeNode() = delete; - KRAKEN_EXPORT NativeNode(NativeEventTarget* nativeEventTarget) : nativeEventTarget(nativeEventTarget){}; - NativeEventTarget* nativeEventTarget; -}; - -class JSDocument : public JSNode { - public: - static std::unordered_map instanceMap; - static JSDocument* instance(JSContext* context); - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - static JSValueRef createEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef createElement(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef createDocumentFragment(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef createTextNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef createComment(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef getElementById(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef getElementsByTagName(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef getElementsByClassName(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - private: - protected: - JSDocument() = delete; - JSDocument(JSContext* context); - ~JSDocument(); - - JSFunctionHolder m_createEvent{context, prototypeObject, this, "createEvent", createEvent}; - JSFunctionHolder m_createElement{context, prototypeObject, this, "createElement", createElement}; - JSFunctionHolder m_createDocumentFragment{context, prototypeObject, this, "createDocumentFragment", createDocumentFragment}; - JSFunctionHolder m_createTextNode{context, prototypeObject, this, "createTextNode", createTextNode}; - JSFunctionHolder m_createComment{context, prototypeObject, this, "createComment", createComment}; - JSFunctionHolder m_getElementById{context, prototypeObject, this, "getElementById", getElementById}; - JSFunctionHolder m_getElementsByTagName{context, prototypeObject, this, "getElementsByTagName", getElementsByTagName}; - JSFunctionHolder m_getElementsByClassName{context, prototypeObject, this, "getElementsByClassName", getElementsByClassName}; -}; - -class DocumentCookie { - public: - KRAKEN_EXPORT DocumentCookie() = default; - - KRAKEN_EXPORT std::string getCookie(); - KRAKEN_EXPORT void setCookie(std::string& str); - - private: - std::unordered_map cookiePairs; -}; - -struct NativeDocument { - NativeDocument() = delete; - KRAKEN_EXPORT explicit NativeDocument(NativeNode* nativeNode) : nativeNode(nativeNode){}; - - NativeNode* nativeNode; -}; - -class DocumentInstance : public NodeInstance { - public: - DEFINE_OBJECT_PROPERTY(Document, 4, nodeName, all, cookie, documentElement); - DEFINE_PROTOTYPE_OBJECT_PROPERTY(Document, 8, createElement, createDocumentFragment, createTextNode, createComment, getElementById, getElementsByClassName, getElementsByTagName, createEvent); - - static DocumentInstance* instance(JSContext* context); - - DocumentInstance() = delete; - KRAKEN_EXPORT explicit DocumentInstance(JSDocument* document); - KRAKEN_EXPORT ~DocumentInstance(); - KRAKEN_EXPORT JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - KRAKEN_EXPORT bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - KRAKEN_EXPORT void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - - void removeElementById(JSValueRef id, ElementInstance* element); - void addElementById(JSValueRef id, ElementInstance* element); - - NativeDocument* nativeDocument; - std::unordered_map> elementMapById; - - ElementInstance* documentElement; - - private: - DocumentCookie m_cookie; - friend NodeInstance; -}; - -class JSElementAttributes : public HostObject { - public: - JSElementAttributes() = delete; - JSElementAttributes(JSContext* context) : HostObject(context, "NamedNodeMap") {} - ~JSElementAttributes() override; - - enum class AttributeProperty { kLength }; - - static std::vector& getAttributePropertyNames(); - static std::unordered_map& getAttributePropertyMap(); - - KRAKEN_EXPORT JSValueRef getAttribute(std::string& name); - KRAKEN_EXPORT void setAttribute(std::string& name, JSValueRef value); - KRAKEN_EXPORT bool hasAttribute(std::string& name); - KRAKEN_EXPORT void removeAttribute(std::string& name); - - KRAKEN_EXPORT std::map& getAttributesMap(); - KRAKEN_EXPORT void setAttributesMap(std::map& attributes); - - KRAKEN_EXPORT std::vector& getAttributesVector(); - KRAKEN_EXPORT void setAttributesVector(std::vector& attributes); - - KRAKEN_EXPORT JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - KRAKEN_EXPORT bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - KRAKEN_EXPORT void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - - private: - std::map m_attributes; - std::vector v_attributes; -}; - -struct NativeBoundingClientRect { - double x; - double y; - double width; - double height; - double top; - double right; - double bottom; - double left; -}; - -class CSSStyleDeclaration : public HostClass { - public: - static std::unordered_map instanceMap; - static CSSStyleDeclaration* instance(JSContext* context); - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - static JSValueRef setProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef removeProperty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef getPropertyValue(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - protected: - CSSStyleDeclaration() = delete; - ~CSSStyleDeclaration(); - explicit CSSStyleDeclaration(JSContext* context); - - JSFunctionHolder m_setProperty{context, prototypeObject, this, "setProperty", setProperty}; - JSFunctionHolder m_getPropertyValue{context, prototypeObject, this, "getPropertyValue", getPropertyValue}; - JSFunctionHolder m_removeProperty{context, prototypeObject, this, "removeProperty", removeProperty}; -}; - -class StyleDeclarationInstance : public HostClass::Instance { - public: - DEFINE_PROTOTYPE_OBJECT_PROPERTY(CSSStyleDeclaration, 3, setProperty, removeProperty, getPropertyValue); - - StyleDeclarationInstance() = delete; - StyleDeclarationInstance(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget); - ~StyleDeclarationInstance(); - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - bool internalSetProperty(std::string& name, JSValueRef value, JSValueRef* exception); - void internalRemoveProperty(std::string& name, JSValueRef* exception); - JSValueRef internalGetPropertyValue(std::string& name, JSValueRef* exception); - std::string toString(); - - private: - std::unordered_map properties; - const EventTargetInstance* ownerEventTarget; -}; - -using ElementCreator = ElementInstance* (*)(JSContext* context); - -class KRAKEN_EXPORT JSElement : public JSNode { - public: - DEFINE_OBJECT_PROPERTY(Element, - 19, - style, - attributes, - nodeName, - tagName, - offsetLeft, - offsetTop, - offsetWidth, - offsetHeight, - clientWidth, - clientHeight, - clientTop, - clientLeft, - scrollTop, - scrollLeft, - scrollHeight, - scrollWidth, - children, - className, - innerHTML); - - DEFINE_PROTOTYPE_OBJECT_PROPERTY(Element, 10, getBoundingClientRect, getAttribute, setAttribute, hasAttribute, removeAttribute, toBlob, click, scroll, scrollBy, scrollTo); - - static std::unordered_map instanceMap; - static std::unordered_map elementCreatorMap; - OBJECT_INSTANCE(JSElement) - - static ElementInstance* buildElementInstance(JSContext* context, std::string& tagName); - - JSValueRef prototypeGetProperty(std::string& name, JSValueRef* exception) override; - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - static void defineElement(std::string tagName, ElementCreator creator); - - protected: - JSElement() = delete; - explicit JSElement(JSContext* context); - ~JSElement(); - - private: - friend ElementInstance; - - static JSValueRef getBoundingClientRect(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - - static JSValueRef hasAttribute(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef setAttribute(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef getAttribute(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef removeAttribute(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef toBlob(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef click(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef scroll(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef scrollBy(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - JSFunctionHolder m_getBoundingClientRect{context, prototypeObject, this, "getBoundingClientRect", getBoundingClientRect}; - JSFunctionHolder m_setAttribute{context, prototypeObject, this, "setAttribute", setAttribute}; - JSFunctionHolder m_getAttribute{context, prototypeObject, this, "getAttribute", getAttribute}; - JSFunctionHolder m_hasAttribute{context, prototypeObject, this, "hasAttribute", hasAttribute}; - JSFunctionHolder m_removeAttribute{context, prototypeObject, this, "removeAttribute", removeAttribute}; - JSFunctionHolder m_toBlob{context, prototypeObject, this, "toBlob", toBlob}; - JSFunctionHolder m_click{context, prototypeObject, this, "click", click}; - JSFunctionHolder m_scroll{context, prototypeObject, this, "scroll", scroll}; - JSFunctionHolder m_scrollTo{context, prototypeObject, this, "scrollTo", scroll}; - JSFunctionHolder m_scrollBy{context, prototypeObject, this, "scrollBy", scrollBy}; -}; - -class KRAKEN_EXPORT ElementInstance : public NodeInstance { - public: - ElementInstance() = delete; - explicit ElementInstance(JSElement* element, const char* tagName, bool shouldAddUICommand); - explicit ElementInstance(JSElement* element, JSStringRef tagName, double targetId); - ~ElementInstance(); - - JSValueRef getStringValueProperty(std::string& name); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - std::string internalGetTextContent() override; - void internalSetTextContent(JSStringRef content, JSValueRef* exception) override; - JSHostObjectHolder& getAttributes(); - JSHostClassHolder& getStyle(); - void setStyle(JSHostClassHolder& style); - void setAttributes(JSHostObjectHolder& attributes); - SpaceSplitString classNames(); - NativeElement* nativeElement{nullptr}; - std::string tagName(); - std::string getRegisteredTagName(); - std::string toString(); - - private: - friend JSElement; - JSStringHolder m_tagName{context, ""}; - - KRAKEN_EXPORT void _notifyNodeRemoved(NodeInstance* node) override; - void _notifyChildRemoved(); - KRAKEN_EXPORT void _notifyNodeInsert(NodeInstance* insertNode) override; - void _notifyChildInsert(); - void _didModifyAttribute(std::string& name, JSValueRef oldId, JSValueRef newId); - void _beforeUpdateId(JSValueRef oldId, JSValueRef newId); - JSHostObjectHolder m_attributes{context, object, "attributes", new JSElementAttributes(context)}; - JSHostClassHolder m_style{context, object, "style", new StyleDeclarationInstance(CSSStyleDeclaration::instance(context), this)}; -}; - -enum class ViewModuleProperty { offsetTop, offsetLeft, offsetWidth, offsetHeight, clientWidth, clientHeight, clientTop, clientLeft, scrollTop, scrollLeft, scrollHeight, scrollWidth }; -using GetViewModuleProperty = double (*)(NativeElement* nativeElement, int64_t property); -using SetViewModuleProperty = void (*)(NativeElement* nativeElement, int64_t property, double value); -using GetBoundingClientRect = NativeBoundingClientRect* (*)(NativeElement* nativeElement); -using GetStringValueProperty = NativeString* (*)(NativeElement* nativeElement, NativeString* property); -using Click = void (*)(NativeElement* nativeElement); -using Scroll = void (*)(NativeElement* nativeElement, int32_t x, int32_t y); -using ScrollBy = void (*)(NativeElement* nativeElement, int32_t x, int32_t y); - -class BoundingClientRect : public HostObject { - public: - enum BoundingClientRectProperty { kX, kY, kWidth, kHeight, kLeft, kTop, kRight, kBottom }; - - static std::array& getBoundingClientRectPropertyNames(); - static std::unordered_map& getPropertyMap(); - - BoundingClientRect() = delete; - ~BoundingClientRect() override; - BoundingClientRect(JSContext* context, NativeBoundingClientRect* boundingClientRect); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - - private: - NativeBoundingClientRect* nativeBoundingClientRect; -}; - -// An struct represent Element object from dart side. -struct NativeElement { - NativeElement() = delete; - explicit NativeElement(NativeNode* nativeNode) : nativeNode(nativeNode){}; - - const NativeNode* nativeNode; - - GetViewModuleProperty getViewModuleProperty{nullptr}; - SetViewModuleProperty setViewModuleProperty{nullptr}; - GetBoundingClientRect getBoundingClientRect{nullptr}; - GetStringValueProperty getStringValueProperty{nullptr}; - Click click{nullptr}; - Scroll scroll{nullptr}; - ScrollBy scrollBy{nullptr}; -}; - -struct NativeGestureEvent { - NativeGestureEvent() = delete; - explicit NativeGestureEvent(NativeEvent* nativeEvent) : nativeEvent(nativeEvent){}; - - NativeEvent* nativeEvent; - NativeString* state; - NativeString* direction; - double_t deltaX; - double_t deltaY; - double_t velocityX; - double_t velocityY; - double_t scale; - double_t rotation; -}; - -class JSGestureEvent : public JSEvent { - public: - DEFINE_OBJECT_PROPERTY(GestureEvent, 8, state, direction, deltaX, deltaY, velocityX, velocityY, scale, rotation); - - DEFINE_PROTOTYPE_OBJECT_PROPERTY(GestureEvent, 1, initGestureEvent); - - static std::unordered_map instanceMap; - OBJECT_INSTANCE(JSGestureEvent) - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSGestureEvent() = delete; - explicit JSGestureEvent(JSContext* context); - ~JSGestureEvent() override; - - private: - friend GestureEventInstance; - - JSFunctionHolder m_initGestureEvent{context, prototypeObject, this, "initGestureEvent", initGestureEvent}; - - static JSValueRef initGestureEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); -}; - -class GestureEventInstance : public EventInstance { - public: - GestureEventInstance() = delete; - explicit GestureEventInstance(JSGestureEvent* jsGestureEvent, std::string GestureEventType, JSValueRef eventInit, JSValueRef* exception); - explicit GestureEventInstance(JSGestureEvent* jsGestureEvent, NativeGestureEvent* nativeGestureEvent); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - ~GestureEventInstance() override; - - private: - friend JSGestureEvent; - JSValueHolder m_state{context, nullptr}; - JSValueHolder m_direction{context, nullptr}; - JSValueHolder m_deltaX{context, nullptr}; - JSValueHolder m_deltaY{context, nullptr}; - JSValueHolder m_velocityX{context, nullptr}; - JSValueHolder m_velocityY{context, nullptr}; - JSValueHolder m_scale{context, nullptr}; - JSValueHolder m_rotation{context, nullptr}; - NativeGestureEvent* nativeGestureEvent; -}; - -struct NativeMouseEvent { - NativeMouseEvent() = delete; - explicit NativeMouseEvent(NativeEvent* nativeEvent) : nativeEvent(nativeEvent){}; - - NativeEvent* nativeEvent; - - double_t clientX; - - double_t clientY; - - double_t offsetX; - - double_t offsetY; -}; - -class JSMouseEvent : public JSEvent { - public: - DEFINE_OBJECT_PROPERTY(MouseEvent, 4, clientX, clientY, offsetX, offsetY); - - DEFINE_PROTOTYPE_OBJECT_PROPERTY(MouseEvent, 1, initMouseEvent); - - static std::unordered_map instanceMap; - OBJECT_INSTANCE(JSMouseEvent) - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSMouseEvent() = delete; - explicit JSMouseEvent(JSContext* context); - ~JSMouseEvent() override; - - private: - friend MouseEventInstance; - - JSFunctionHolder m_initMouseEvent{context, prototypeObject, this, "initMouseEvent", initMouseEvent}; - - static JSValueRef initMouseEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); -}; - -class MouseEventInstance : public EventInstance { - public: - MouseEventInstance() = delete; - explicit MouseEventInstance(JSMouseEvent* jsMouseEvent, std::string MouseEventType, JSValueRef eventInit, JSValueRef* exception); - explicit MouseEventInstance(JSMouseEvent* jsMouseEvent, NativeMouseEvent* nativeMouseEvent); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - ~MouseEventInstance() override; - - private: - friend JSMouseEvent; - JSValueHolder m_clientX{context, nullptr}; - JSValueHolder m_clientY{context, nullptr}; - JSValueHolder m_offsetX{context, nullptr}; - JSValueHolder m_offsetY{context, nullptr}; - NativeMouseEvent* nativeMouseEvent; -}; - -struct NativePopStateEvent { - NativePopStateEvent() = delete; - explicit NativePopStateEvent(NativeEvent* nativeEvent) : nativeEvent(nativeEvent){}; - - NativeEvent* nativeEvent; - - JSValueRef state; -}; - -class JSPopStateEvent : public JSEvent { - public: - DEFINE_OBJECT_PROPERTY(PopStateEvent, 1, state); - - DEFINE_PROTOTYPE_OBJECT_PROPERTY(PopStateEvent, 1, initPopStateEvent); - - static std::unordered_map instanceMap; - OBJECT_INSTANCE(JSPopStateEvent) - - JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef* arguments, JSValueRef* exception) override; - - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - - protected: - JSPopStateEvent() = delete; - explicit JSPopStateEvent(JSContext* context); - ~JSPopStateEvent() override; - - private: - friend PopStateEventInstance; - - JSFunctionHolder m_initPopStateEvent{context, prototypeObject, this, "initPopStateEvent", initPopStateEvent}; - - static JSValueRef initPopStateEvent(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); -}; - -class PopStateEventInstance : public EventInstance { - public: - PopStateEventInstance() = delete; - explicit PopStateEventInstance(JSPopStateEvent* jsPopStateEvent, std::string eventType, JSValueRef eventInit, JSValueRef* exception); - explicit PopStateEventInstance(JSPopStateEvent* jsPopStateEvent, NativePopStateEvent* nativePopStateEvent); - JSValueRef getProperty(std::string& name, JSValueRef* exception) override; - bool setProperty(std::string& name, JSValueRef value, JSValueRef* exception) override; - void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override; - ~PopStateEventInstance() override; - - private: - friend JSPopStateEvent; - JSValueHolder m_state{context, nullptr}; - NativePopStateEvent* nativePopStateEvent; -}; - -} // namespace kraken::binding::jsc - -KRAKEN_EXPORT -JSGlobalContextRef getGlobalContextRef(int32_t contextId); - -#endif diff --git a/bridge/include/kraken_bridge_jsc_config.h b/bridge/include/kraken_bridge_jsc_config.h deleted file mode 100644 index a01cf146ec..0000000000 --- a/bridge/include/kraken_bridge_jsc_config.h +++ /dev/null @@ -1,886 +0,0 @@ - - -#define OBJECT_INSTANCE(NAME) \ - static NAME* instance(JSContext* context) { \ - if (instanceMap.count(context) == 0) { \ - instanceMap[context] = new NAME(context); \ - } \ - return instanceMap[context]; \ - } - -#define JSC_GLOBAL_BINDING_FUNCTION(context, nameStr, func) \ - { \ - JSClassDefinition functionDefinition = kJSClassDefinitionEmpty; \ - functionDefinition.className = nameStr; \ - functionDefinition.callAsFunction = func; \ - functionDefinition.version = 0; \ - JSClassRef functionClass = JSClassCreate(&functionDefinition); \ - JSObjectRef function = JSObjectMake(context->context(), functionClass, context.get()); \ - JSValueProtect(context->context(), function); \ - JSStringRef name = JSStringCreateWithUTF8CString(nameStr); \ - JSValueRef exc = nullptr; \ - JSObjectSetProperty(context->context(), context->global(), name, function, kJSPropertyAttributeNone, &exc); \ - JSStringRelease(name); \ - context->handleException(exc); \ - } - -#define JSC_GLOBAL_BINDING_HOST_OBJECT(context, nameStr, hostObject) \ - { \ - JSObjectRef object = hostObject->jsObject; \ - JSValueProtect(context->context(), object); \ - JSStringRef name = JSStringCreateWithUTF8CString(nameStr); \ - JSObjectSetProperty(context->context(), context->global(), name, object, kJSPropertyAttributeReadOnly, nullptr); \ - JSStringRelease(name); \ - } - -#define JSC_SET_STRING_PROPERTY(context, object, name, valueRef) \ - { \ - JSStringRef keyRef = JSStringCreateWithUTF8CString(name); \ - JSObjectSetProperty(context->context(), object, keyRef, valueRef, kJSPropertyAttributeNone, nullptr); \ - JSStringRelease(keyRef); \ - } - -#define JSC_GLOBAL_SET_PROPERTY(context, key, value) \ - { \ - JSStringRef keyString = JSStringCreateWithUTF8CString(key); \ - JSObjectSetProperty(context->context(), context->global(), keyString, value, kJSPropertyAttributeReadOnly, nullptr); \ - JSStringRelease(keyString); \ - } - -#define HANDLE_JSC_EXCEPTION(ctx_, exc, handler) \ - { \ - JSObjectRef error = JSValueToObject(ctx_, exc, nullptr); \ - JSStringRef messageKey = JSStringCreateWithUTF8CString("message"); \ - JSStringRef stackKey = JSStringCreateWithUTF8CString("stack"); \ - JSValueRef messageRef = JSObjectGetProperty(ctx_, error, messageKey, nullptr); \ - JSValueRef stackRef = JSObjectGetProperty(ctx_, error, stackKey, nullptr); \ - JSStringRef messageStr = JSValueToStringCopy(ctx_, messageRef, nullptr); \ - JSStringRef stackStr = JSValueToStringCopy(ctx_, stackRef, nullptr); \ - std::string&& message = JSStringToStdString(messageStr); \ - std::string&& stack = JSStringToStdString(stackStr); \ - handler(getContextId(), (message + '\n' + stack).c_str(), error); \ - JSStringRelease(messageKey); \ - JSStringRelease(stackKey); \ - JSStringRelease(messageStr); \ - JSStringRelease(stackStr); \ - } - -#define JSC_CREATE_HOST_OBJECT_DEFINITION(definition, name, classObject) \ - { \ - definition.version = 0; \ - definition.className = name; \ - definition.attributes = kJSClassAttributeNoAutomaticPrototype; \ - definition.finalize = classObject::proxyFinalize; \ - definition.getProperty = classObject::proxyGetProperty; \ - definition.setProperty = classObject::proxySetProperty; \ - definition.getPropertyNames = classObject::proxyGetPropertyNames; \ - } - -#define JSC_CREATE_HOST_CLASS_INSTANCE_DEFINITION(definition, name, classObject, staticFunction) \ - { \ - definition.version = 0; \ - definition.className = name; \ - definition.attributes = kJSClassAttributeNoAutomaticPrototype; \ - definition.finalize = classObject::proxyInstanceFinalize; \ - definition.staticFunctions = staticFunction; \ - definition.getProperty = classObject::proxyInstanceGetProperty; \ - definition.setProperty = classObject::proxyInstanceSetProperty; \ - definition.getPropertyNames = classObject::proxyInstanceGetPropertyNames; \ - } - -#define JSC_CREATE_HOST_CLASS_DEFINITION(definition, parent, name, staticFunction, staticValue, HostClass) \ - { \ - definition.version = 0; \ - definition.parentClass = parent; \ - definition.className = name; \ - definition.attributes = kJSClassAttributeNoAutomaticPrototype; \ - definition.staticFunctions = staticFunction; \ - definition.staticValues = staticValue; \ - definition.finalize = HostClass::proxyFinalize; \ - definition.hasInstance = HostClass::proxyHasInstance; \ - definition.callAsConstructor = HostClass::proxyCallAsConstructor; \ - definition.callAsFunction = HostClass::proxyCallAsFunction; \ - definition.getProperty = HostClass::proxyGetProperty; \ - } - -#define OBJECT_PROPERTY(name, ...) \ - name { __VA_ARGS__ } -#define OBJECT_PROPERTY_ITEM(NAME, KEY) \ - { #KEY, NAME##Property::KEY } -#define OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, KEY) \ - { #KEY, NAME##PrototypeProperty::KEY } - -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_1(NAME, _1) OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _1) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_2(NAME, _1, _2) OBJECT_PROTOTYPE_PROPERTY_ITEM_1(NAME, _1), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _2) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_3(NAME, _1, _2, _3) OBJECT_PROTOTYPE_PROPERTY_ITEM_2(NAME, _1, _2), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _3) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_4(NAME, _1, _2, _3, _4) OBJECT_PROTOTYPE_PROPERTY_ITEM_3(NAME, _1, _2, _3), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _4) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_5(NAME, _1, _2, _3, _4, _5) OBJECT_PROTOTYPE_PROPERTY_ITEM_4(NAME, _1, _2, _3, _4), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _5) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_6(NAME, _1, _2, _3, _4, _5, _6) OBJECT_PROTOTYPE_PROPERTY_ITEM_5(NAME, _1, _2, _3, _4, _5), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _6) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_7(NAME, _1, _2, _3, _4, _5, _6, _7) OBJECT_PROTOTYPE_PROPERTY_ITEM_6(NAME, _1, _2, _3, _4, _5, _6), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _7) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_8(NAME, _1, _2, _3, _4, _5, _6, _7, _8) OBJECT_PROTOTYPE_PROPERTY_ITEM_7(NAME, _1, _2, _3, _4, _5, _6, _7), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _8) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_9(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9) OBJECT_PROTOTYPE_PROPERTY_ITEM_8(NAME, _1, _2, _3, _4, _5, _6, _7, _8), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _9) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_10(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_9(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _10) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_11(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_10(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _11) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_12(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_11(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _12) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_13(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_12(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _13) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_14(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_13(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _14) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_15(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_14(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _15) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_16(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_15(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _16) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_17(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_16(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _17) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_18(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_17(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _18) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_19(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_18(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _19) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_20(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_19(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _20) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_21(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_20(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _21) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_22(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_21(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _22) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_23(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_22(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _23) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_24(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_23(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _24) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_25(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_24(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _25) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_26(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_25(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25), OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _26) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_27(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_26(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _27) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_28(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_27(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _28) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_29(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_28(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _29) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_30(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_29(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _30) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_31(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_30(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _31) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_32(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_31(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _32) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_33(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_32(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _33) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_34(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_33(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _34) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_35(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_34(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _35) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_36(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_35(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _36) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_37(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_36(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _37) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_38(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_37(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _38) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_39(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_38(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _39) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_40(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_39(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _40) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_41(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_40(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _41) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_42(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_41(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _42) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_43(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_42(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _43) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_44(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_43(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _44) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_45(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_44(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _45) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_46(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_45(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _46) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_47(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_46(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _47) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_48(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_47(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _48) -#define OBJECT_PROTOTYPE_PROPERTY_ITEM_49(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ - _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49) \ - OBJECT_PROTOTYPE_PROPERTY_ITEM_48(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, \ - _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48), \ - OBJECT_PROTOTYPE_PROPERTY_ITEM(NAME, _49) - -#define OBJECT_PROPERTY_ITEM_1(NAME, _1) OBJECT_PROPERTY_ITEM(NAME, _1), -#define OBJECT_PROPERTY_ITEM_2(NAME, _1, _2) OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), -#define OBJECT_PROPERTY_ITEM_3(NAME, _1, _2, _3) OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), -#define OBJECT_PROPERTY_ITEM_4(NAME, _1, _2, _3, _4) OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), -#define OBJECT_PROPERTY_ITEM_5(NAME, _1, _2, _3, _4, _5) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), -#define OBJECT_PROPERTY_ITEM_6(NAME, _1, _2, _3, _4, _5, _6) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), -#define OBJECT_PROPERTY_ITEM_7(NAME, _1, _2, _3, _4, _5, _6, _7) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), -#define OBJECT_PROPERTY_ITEM_8(NAME, _1, _2, _3, _4, _5, _6, _7, _8) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), -#define OBJECT_PROPERTY_ITEM_9(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), -#define OBJECT_PROPERTY_ITEM_10(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), -#define OBJECT_PROPERTY_ITEM_11(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), -#define OBJECT_PROPERTY_ITEM_12(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), -#define OBJECT_PROPERTY_ITEM_13(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), -#define OBJECT_PROPERTY_ITEM_14(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), -#define OBJECT_PROPERTY_ITEM_15(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), -#define OBJECT_PROPERTY_ITEM_16(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), -#define OBJECT_PROPERTY_ITEM_17(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), -#define OBJECT_PROPERTY_ITEM_18(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), -#define OBJECT_PROPERTY_ITEM_19(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), -#define OBJECT_PROPERTY_ITEM_20(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), -#define OBJECT_PROPERTY_ITEM_21(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), -#define OBJECT_PROPERTY_ITEM_22(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), -#define OBJECT_PROPERTY_ITEM_23(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), -#define OBJECT_PROPERTY_ITEM_24(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), -#define OBJECT_PROPERTY_ITEM_25(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), -#define OBJECT_PROPERTY_ITEM_26(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), -#define OBJECT_PROPERTY_ITEM_27(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), -#define OBJECT_PROPERTY_ITEM_28(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), -#define OBJECT_PROPERTY_ITEM_29(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), -#define OBJECT_PROPERTY_ITEM_30(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), -#define OBJECT_PROPERTY_ITEM_31(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), -#define OBJECT_PROPERTY_ITEM_32(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), -#define OBJECT_PROPERTY_ITEM_33(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), -#define OBJECT_PROPERTY_ITEM_34(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), -#define OBJECT_PROPERTY_ITEM_35(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), -#define OBJECT_PROPERTY_ITEM_36(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), -#define OBJECT_PROPERTY_ITEM_37(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), -#define OBJECT_PROPERTY_ITEM_38(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), -#define OBJECT_PROPERTY_ITEM_39(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), -#define OBJECT_PROPERTY_ITEM_40(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), -#define OBJECT_PROPERTY_ITEM_41(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), -#define OBJECT_PROPERTY_ITEM_42(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), -#define OBJECT_PROPERTY_ITEM_43(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), -#define OBJECT_PROPERTY_ITEM_44(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), -#define OBJECT_PROPERTY_ITEM_45(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), -#define OBJECT_PROPERTY_ITEM_46(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), OBJECT_PROPERTY_ITEM(NAME, _46), -#define OBJECT_PROPERTY_ITEM_47(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), OBJECT_PROPERTY_ITEM(NAME, _46), \ - OBJECT_PROPERTY_ITEM(NAME, _47), -#define OBJECT_PROPERTY_ITEM_48(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), OBJECT_PROPERTY_ITEM(NAME, _46), \ - OBJECT_PROPERTY_ITEM(NAME, _47), OBJECT_PROPERTY_ITEM(NAME, _48), -#define OBJECT_PROPERTY_ITEM_49(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), OBJECT_PROPERTY_ITEM(NAME, _46), \ - OBJECT_PROPERTY_ITEM(NAME, _47), OBJECT_PROPERTY_ITEM(NAME, _48), OBJECT_PROPERTY_ITEM(NAME, _49), -#define OBJECT_PROPERTY_ITEM_50(NAME, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \ - _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50) \ - OBJECT_PROPERTY_ITEM(NAME, _1), OBJECT_PROPERTY_ITEM(NAME, _2), OBJECT_PROPERTY_ITEM(NAME, _3), OBJECT_PROPERTY_ITEM(NAME, _4), OBJECT_PROPERTY_ITEM(NAME, _5), OBJECT_PROPERTY_ITEM(NAME, _6), \ - OBJECT_PROPERTY_ITEM(NAME, _7), OBJECT_PROPERTY_ITEM(NAME, _8), OBJECT_PROPERTY_ITEM(NAME, _9), OBJECT_PROPERTY_ITEM(NAME, _10), OBJECT_PROPERTY_ITEM(NAME, _11), \ - OBJECT_PROPERTY_ITEM(NAME, _12), OBJECT_PROPERTY_ITEM(NAME, _13), OBJECT_PROPERTY_ITEM(NAME, _14), OBJECT_PROPERTY_ITEM(NAME, _15), OBJECT_PROPERTY_ITEM(NAME, _16), \ - OBJECT_PROPERTY_ITEM(NAME, _17), OBJECT_PROPERTY_ITEM(NAME, _18), OBJECT_PROPERTY_ITEM(NAME, _19), OBJECT_PROPERTY_ITEM(NAME, _20), OBJECT_PROPERTY_ITEM(NAME, _21), \ - OBJECT_PROPERTY_ITEM(NAME, _22), OBJECT_PROPERTY_ITEM(NAME, _23), OBJECT_PROPERTY_ITEM(NAME, _24), OBJECT_PROPERTY_ITEM(NAME, _25), OBJECT_PROPERTY_ITEM(NAME, _26), \ - OBJECT_PROPERTY_ITEM(NAME, _27), OBJECT_PROPERTY_ITEM(NAME, _28), OBJECT_PROPERTY_ITEM(NAME, _29), OBJECT_PROPERTY_ITEM(NAME, _30), OBJECT_PROPERTY_ITEM(NAME, _31), \ - OBJECT_PROPERTY_ITEM(NAME, _32), OBJECT_PROPERTY_ITEM(NAME, _33), OBJECT_PROPERTY_ITEM(NAME, _34), OBJECT_PROPERTY_ITEM(NAME, _35), OBJECT_PROPERTY_ITEM(NAME, _36), \ - OBJECT_PROPERTY_ITEM(NAME, _37), OBJECT_PROPERTY_ITEM(NAME, _38), OBJECT_PROPERTY_ITEM(NAME, _39), OBJECT_PROPERTY_ITEM(NAME, _40), OBJECT_PROPERTY_ITEM(NAME, _41), \ - OBJECT_PROPERTY_ITEM(NAME, _42), OBJECT_PROPERTY_ITEM(NAME, _43), OBJECT_PROPERTY_ITEM(NAME, _44), OBJECT_PROPERTY_ITEM(NAME, _45), OBJECT_PROPERTY_ITEM(NAME, _46), \ - OBJECT_PROPERTY_ITEM(NAME, _47), OBJECT_PROPERTY_ITEM(NAME, _48), OBJECT_PROPERTY_ITEM(NAME, _49), OBJECT_PROPERTY_ITEM(NAME, _50), - -#define OBJECT_PROPERTY_MAP_FUNCTION(NAME, ARGS_COUNT, ...) \ - static std::unordered_map& get##NAME##PropertyMap() { \ - static std::unordered_map propertyMap{OBJECT_PROPERTY_ITEM_##ARGS_COUNT(NAME, __VA_ARGS__)}; \ - return propertyMap; \ - }; - -#define OBJECT_PROTOTYPE_PROPERTY_MAP_FUNCTION(NAME, ARGS_COUNT, ...) \ - static std::unordered_map& get##NAME##PrototypePropertyMap() { \ - static std::unordered_map prototypePropertyMap{OBJECT_PROTOTYPE_PROPERTY_ITEM_##ARGS_COUNT(NAME, __VA_ARGS__)}; \ - return prototypePropertyMap; \ - }; - -#define OBJECT_PROPERTY_NAME(KEY) JSStringCreateWithUTF8CString(#KEY) - -#define OBJECT_PROPERTY_NAME_1(_1) OBJECT_PROPERTY_NAME(_1), -#define OBJECT_PROPERTY_NAME_2(_1, _2) OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), -#define OBJECT_PROPERTY_NAME_3(_1, _2, _3) OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), -#define OBJECT_PROPERTY_NAME_4(_1, _2, _3, _4) OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), -#define OBJECT_PROPERTY_NAME_5(_1, _2, _3, _4, _5) OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), -#define OBJECT_PROPERTY_NAME_6(_1, _2, _3, _4, _5, _6) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), -#define OBJECT_PROPERTY_NAME_7(_1, _2, _3, _4, _5, _6, _7) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), -#define OBJECT_PROPERTY_NAME_8(_1, _2, _3, _4, _5, _6, _7, _8) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), -#define OBJECT_PROPERTY_NAME_9(_1, _2, _3, _4, _5, _6, _7, _8, _9) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), -#define OBJECT_PROPERTY_NAME_10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), -#define OBJECT_PROPERTY_NAME_11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), -#define OBJECT_PROPERTY_NAME_12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), -#define OBJECT_PROPERTY_NAME_13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), -#define OBJECT_PROPERTY_NAME_14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), -#define OBJECT_PROPERTY_NAME_15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), -#define OBJECT_PROPERTY_NAME_16(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), -#define OBJECT_PROPERTY_NAME_17(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), -#define OBJECT_PROPERTY_NAME_18(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), -#define OBJECT_PROPERTY_NAME_19(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), -#define OBJECT_PROPERTY_NAME_20(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), -#define OBJECT_PROPERTY_NAME_21(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), -#define OBJECT_PROPERTY_NAME_22(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), -#define OBJECT_PROPERTY_NAME_23(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), -#define OBJECT_PROPERTY_NAME_24(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), -#define OBJECT_PROPERTY_NAME_25(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), -#define OBJECT_PROPERTY_NAME_26(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), -#define OBJECT_PROPERTY_NAME_27(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), -#define OBJECT_PROPERTY_NAME_28(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), -#define OBJECT_PROPERTY_NAME_29(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), -#define OBJECT_PROPERTY_NAME_30(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), -#define OBJECT_PROPERTY_NAME_31(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), -#define OBJECT_PROPERTY_NAME_32(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), -#define OBJECT_PROPERTY_NAME_33(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), -#define OBJECT_PROPERTY_NAME_34(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), -#define OBJECT_PROPERTY_NAME_35(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), -#define OBJECT_PROPERTY_NAME_36(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), -#define OBJECT_PROPERTY_NAME_37(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), -#define OBJECT_PROPERTY_NAME_38(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), -#define OBJECT_PROPERTY_NAME_39(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), -#define OBJECT_PROPERTY_NAME_40(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), -#define OBJECT_PROPERTY_NAME_41(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), -#define OBJECT_PROPERTY_NAME_42(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), -#define OBJECT_PROPERTY_NAME_43(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), -#define OBJECT_PROPERTY_NAME_44(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), -#define OBJECT_PROPERTY_NAME_45(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), -#define OBJECT_PROPERTY_NAME_46(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), OBJECT_PROPERTY_NAME(_46), -#define OBJECT_PROPERTY_NAME_47(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), OBJECT_PROPERTY_NAME(_46), OBJECT_PROPERTY_NAME(_47), -#define OBJECT_PROPERTY_NAME_48(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), OBJECT_PROPERTY_NAME(_46), OBJECT_PROPERTY_NAME(_47), OBJECT_PROPERTY_NAME(_48), -#define OBJECT_PROPERTY_NAME_49(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), OBJECT_PROPERTY_NAME(_46), OBJECT_PROPERTY_NAME(_47), OBJECT_PROPERTY_NAME(_48), OBJECT_PROPERTY_NAME(_49), -#define OBJECT_PROPERTY_NAME_50(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ - _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50) \ - OBJECT_PROPERTY_NAME(_1), OBJECT_PROPERTY_NAME(_2), OBJECT_PROPERTY_NAME(_3), OBJECT_PROPERTY_NAME(_4), OBJECT_PROPERTY_NAME(_5), OBJECT_PROPERTY_NAME(_6), OBJECT_PROPERTY_NAME(_7), \ - OBJECT_PROPERTY_NAME(_8), OBJECT_PROPERTY_NAME(_9), OBJECT_PROPERTY_NAME(_10), OBJECT_PROPERTY_NAME(_11), OBJECT_PROPERTY_NAME(_12), OBJECT_PROPERTY_NAME(_13), OBJECT_PROPERTY_NAME(_14), \ - OBJECT_PROPERTY_NAME(_15), OBJECT_PROPERTY_NAME(_16), OBJECT_PROPERTY_NAME(_17), OBJECT_PROPERTY_NAME(_18), OBJECT_PROPERTY_NAME(_19), OBJECT_PROPERTY_NAME(_20), OBJECT_PROPERTY_NAME(_21), \ - OBJECT_PROPERTY_NAME(_22), OBJECT_PROPERTY_NAME(_23), OBJECT_PROPERTY_NAME(_24), OBJECT_PROPERTY_NAME(_25), OBJECT_PROPERTY_NAME(_26), OBJECT_PROPERTY_NAME(_27), OBJECT_PROPERTY_NAME(_28), \ - OBJECT_PROPERTY_NAME(_29), OBJECT_PROPERTY_NAME(_30), OBJECT_PROPERTY_NAME(_31), OBJECT_PROPERTY_NAME(_32), OBJECT_PROPERTY_NAME(_33), OBJECT_PROPERTY_NAME(_34), OBJECT_PROPERTY_NAME(_35), \ - OBJECT_PROPERTY_NAME(_36), OBJECT_PROPERTY_NAME(_37), OBJECT_PROPERTY_NAME(_38), OBJECT_PROPERTY_NAME(_39), OBJECT_PROPERTY_NAME(_40), OBJECT_PROPERTY_NAME(_41), OBJECT_PROPERTY_NAME(_42), \ - OBJECT_PROPERTY_NAME(_43), OBJECT_PROPERTY_NAME(_44), OBJECT_PROPERTY_NAME(_45), OBJECT_PROPERTY_NAME(_46), OBJECT_PROPERTY_NAME(_47), OBJECT_PROPERTY_NAME(_48), OBJECT_PROPERTY_NAME(_49), \ - OBJECT_PROPERTY_NAME(_50), - -#define OBJECT_PROPERTY_NAME_FUNCTION(NAME, ARGS_COUNT, ...) \ - static std::vector& get##NAME##PropertyNames() { \ - static std::vector propertyNames{OBJECT_PROPERTY_NAME_##ARGS_COUNT(__VA_ARGS__)}; \ - return propertyNames; \ - } - -#define OBJECT_PROTOTYPE_PROPERTY_NAME_FUNCTION(NAME, ARGS_COUNT, ...) \ - static std::vector& get##NAME##PrototypePropertyNames() { \ - static std::vector propertyNames{OBJECT_PROPERTY_NAME_##ARGS_COUNT(__VA_ARGS__)}; \ - return propertyNames; \ - } - -#define DEFINE_OBJECT_PROPERTY(NAME, ARGS_COUNT, ...) \ - enum class OBJECT_PROPERTY(NAME##Property, __VA_ARGS__); \ - OBJECT_PROPERTY_MAP_FUNCTION(NAME, ARGS_COUNT, __VA_ARGS__); \ - OBJECT_PROPERTY_NAME_FUNCTION(NAME, ARGS_COUNT, __VA_ARGS__) - -#define DEFINE_PROTOTYPE_OBJECT_PROPERTY(NAME, ARGS_COUNT, ...) \ - enum class OBJECT_PROPERTY(NAME##PrototypeProperty, __VA_ARGS__); \ - OBJECT_PROTOTYPE_PROPERTY_MAP_FUNCTION(NAME, ARGS_COUNT, __VA_ARGS__) \ - OBJECT_PROTOTYPE_PROPERTY_NAME_FUNCTION(NAME, ARGS_COUNT, __VA_ARGS__) diff --git a/bridge/include/kraken_foundation.h b/bridge/include/kraken_foundation.h index 3932c515b8..eb85806234 100644 --- a/bridge/include/kraken_foundation.h +++ b/bridge/include/kraken_foundation.h @@ -16,14 +16,23 @@ #include #include #include -#define HTML_TARGET_ID -1 -#define WINDOW_TARGET_ID -2 -#define DOCUMENT_TARGET_ID -3 +#define WINDOW_TARGET_ID -1 +#define DOCUMENT_TARGET_ID -2 #define assert_m(exp, msg) assert(((void)msg, exp)) #define KRAKEN_EXPORT __attribute__((__visibility__("default"))) +#if defined(__GNUC__) || defined(__clang__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define LIKELY(x) (x) +#define UNLIKELY(x) (x) +#define FORCE_INLINE inline +#endif + #define KRAKEN_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete #define KRAKEN_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete @@ -70,26 +79,6 @@ class UICommandCallbackQueue { std::vector queue; }; -class UICommandBuffer { - public: - UICommandBuffer() = delete; - explicit UICommandBuffer(int32_t contextId); - static KRAKEN_EXPORT UICommandBuffer* instance(int32_t contextId); - - KRAKEN_EXPORT void addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate); - KRAKEN_EXPORT void addCommand(int32_t id, int32_t type, void* nativePtr); - KRAKEN_EXPORT void addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr); - KRAKEN_EXPORT void addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr); - KRAKEN_EXPORT UICommandItem* data(); - KRAKEN_EXPORT int64_t size(); - KRAKEN_EXPORT void clear(); - - private: - int32_t contextId; - std::atomic update_batched{false}; - std::vector queue; -}; - typedef int LogSeverity; // Default log levels. Negative values can be used for verbose log levels. diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 8705282c8b..864d6219d3 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -12,7 +12,7 @@ #if KRAKEN_JSC_ENGINE #include "bindings/jsc/KOM/performance.h" #elif KRAKEN_QUICK_JS_ENGINE -#include "bridge_qjs.h" +#include "page.h" #endif #if KRAKEN_JSC_ENGINE @@ -49,7 +49,6 @@ std::atomic inited{false}; std::atomic poolIndex{0}; int maxPoolSize = 0; -kraken::JSBridge** contextPool; NativeScreen screen; std::thread::id uiThreadId; @@ -67,9 +66,9 @@ void printError(int32_t contextId, const char* errmsg) { namespace { -void disposeAllBridge() { +void disposeAllPages() { for (int i = 0; i <= poolIndex && i < maxPoolSize; i++) { - disposeContext(i); + disposePage(i); } poolIndex = 0; inited = false; @@ -77,7 +76,7 @@ void disposeAllBridge() { int32_t searchForAvailableContextId() { for (int i = 0; i < maxPoolSize; i++) { - if (contextPool[i] == nullptr) { + if (kraken::KrakenPage::pageContextPool[i] == nullptr) { return i; } } @@ -86,33 +85,34 @@ int32_t searchForAvailableContextId() { } // namespace -void initJSContextPool(int poolSize) { +void initJSPagePool(int poolSize) { uiThreadId = std::this_thread::get_id(); // When dart hot restarted, should dispose previous bridge and clear task message queue. if (inited) { - disposeAllBridge(); - foundation::UICommandBuffer::instance(0)->clear(); + disposeAllPages(); }; - contextPool = new kraken::JSBridge*[poolSize]; + kraken::KrakenPage::pageContextPool = new kraken::KrakenPage*[poolSize]; for (int i = 1; i < poolSize; i++) { - contextPool[i] = nullptr; + kraken::KrakenPage::pageContextPool[i] = nullptr; } - contextPool[0] = new kraken::JSBridge(0, printError); + kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, printError); inited = true; maxPoolSize = poolSize; } -void disposeContext(int32_t contextId) { +void disposePage(int32_t contextId) { assert(contextId < maxPoolSize); - if (contextPool[contextId] == nullptr) + if (kraken::KrakenPage::pageContextPool[contextId] == nullptr) return; - auto context = static_cast(contextPool[contextId]); - delete context; - contextPool[contextId] = nullptr; + + auto* page = static_cast(kraken::KrakenPage::pageContextPool[contextId]); + // In order to avoid accessing pageContextPool when the page is being released. We need to clear the value in pageContextPool before releasing. + kraken::KrakenPage::pageContextPool[contextId] = nullptr; + delete page; } -int32_t allocateNewContext(int32_t targetContextId) { +int32_t allocateNewPage(int32_t targetContextId) { if (targetContextId == -1) { targetContextId = ++poolIndex; } @@ -121,58 +121,60 @@ int32_t allocateNewContext(int32_t targetContextId) { targetContextId = searchForAvailableContextId(); } - assert(contextPool[targetContextId] == nullptr && (std::string("can not allocate JSBridge at index") + std::to_string(targetContextId) + std::string(": bridge have already exist.")).c_str()); - auto context = new kraken::JSBridge(targetContextId, printError); - contextPool[targetContextId] = context; + assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && + (std::string("can not allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); + auto* page = new kraken::KrakenPage(targetContextId, printError); + kraken::KrakenPage::pageContextPool[targetContextId] = page; return targetContextId; } -void* getJSContext(int32_t contextId) { - assert(checkContext(contextId) && "getJSContext: contextId is not valid."); - return contextPool[contextId]; +void* getPage(int32_t contextId) { + if (!checkPage(contextId)) + return nullptr; + return kraken::KrakenPage::pageContextPool[contextId]; } -bool checkContext(int32_t contextId) { - return inited && contextId < maxPoolSize && contextPool[contextId] != nullptr; +bool checkPage(int32_t contextId) { + return inited && contextId < maxPoolSize && kraken::KrakenPage::pageContextPool[contextId] != nullptr; } -bool checkContext(int32_t contextId, void* context) { - if (contextPool[contextId] == nullptr) +bool checkPage(int32_t contextId, void* context) { + if (kraken::KrakenPage::pageContextPool[contextId] == nullptr) return false; - auto bridge = static_cast(getJSContext(contextId)); - return bridge->getContext().get() == context; + auto* page = static_cast(getPage(contextId)); + return page->getContext().get() == context; } void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { - assert(checkContext(contextId) && "evaluateScripts: contextId is not valid"); - auto context = static_cast(getJSContext(contextId)); + assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); + auto context = static_cast(getPage(contextId)); context->evaluateScript(code, bundleFilename, startLine); } void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) { - assert(checkContext(contextId) && "evaluateScripts: contextId is not valid"); - auto context = static_cast(getJSContext(contextId)); + assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); + auto context = static_cast(getPage(contextId)); context->evaluateByteCode(bytes, byteLen); } void parseHTML(int32_t contextId, const char* code, int32_t length) { - assert(checkContext(contextId) && "parseHTML: contextId is not valid"); - auto context = static_cast(getJSContext(contextId)); + assert(checkPage(contextId) && "parseHTML: contextId is not valid"); + auto context = static_cast(getPage(contextId)); context->parseHTML(code, length); } void reloadJsContext(int32_t contextId) { - assert(checkContext(contextId) && "reloadJSContext: contextId is not valid"); - auto bridgePtr = getJSContext(contextId); - auto context = static_cast(bridgePtr); - auto newContext = new kraken::JSBridge(contextId, printError); + assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); + auto bridgePtr = getPage(contextId); + auto context = static_cast(bridgePtr); + auto newContext = new kraken::KrakenPage(contextId, printError); delete context; - contextPool[contextId] = newContext; + kraken::KrakenPage::pageContextPool[contextId] = newContext; } void invokeModuleEvent(int32_t contextId, NativeString* moduleName, const char* eventType, void* event, NativeString* extra) { - assert(checkContext(contextId) && "invokeEventListener: contextId is not valid"); - auto context = static_cast(getJSContext(contextId)); + assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); + auto context = static_cast(getPage(contextId)); context->invokeModuleEvent(moduleName, eventType, event, extra); } @@ -201,7 +203,7 @@ KrakenInfo* getKrakenInfo() { } void setConsoleMessageHandler(ConsoleMessageHandler handler) { - kraken::JSBridge::consoleMessageHandler = handler; + kraken::KrakenPage::consoleMessageHandler = handler; } void dispatchUITask(int32_t contextId, void* context, void* callback) { @@ -222,24 +224,33 @@ void flushUICommandCallback() { } UICommandItem* getUICommandItems(int32_t contextId) { - return foundation::UICommandBuffer::instance(contextId)->data(); + auto* page = static_cast(getPage(contextId)); + if (page == nullptr) + return nullptr; + return page->getContext()->uiCommandBuffer()->data(); } int64_t getUICommandItemSize(int32_t contextId) { - return foundation::UICommandBuffer::instance(contextId)->size(); + auto* page = static_cast(getPage(contextId)); + if (page == nullptr) + return 0; + return page->getContext()->uiCommandBuffer()->size(); } void clearUICommandItems(int32_t contextId) { - return foundation::UICommandBuffer::instance(contextId)->clear(); + auto* page = static_cast(getPage(contextId)); + if (page == nullptr) + return; + page->getContext()->uiCommandBuffer()->clear(); } void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) { - assert(checkContext(contextId)); - auto context = static_cast(getJSContext(contextId)); + assert(checkPage(contextId)); + auto context = static_cast(getPage(contextId)); } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::JSBridge::pluginByteCode[pluginName] = NativeByteCode{bytes, length}; + kraken::KrakenPage::pluginByteCode[pluginName] = NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { @@ -251,13 +262,10 @@ int32_t profileModeEnabled() { } NativeString* NativeString::clone() { - NativeString* newNativeString = new NativeString(); - uint16_t* newString = new uint16_t[length]; - - for (size_t i = 0; i < length; i++) { - newString[i] = string[i]; - } + auto* newNativeString = new NativeString(); + auto* newString = new uint16_t[length]; + memcpy(newString, string, length * sizeof(uint16_t)); newNativeString->string = newString; newNativeString->length = length; return newNativeString; @@ -265,5 +273,4 @@ NativeString* NativeString::clone() { void NativeString::free() { delete[] string; - delete this; } diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 7dd23ce727..efca8deb6e 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -9,19 +9,15 @@ #if KRAKEN_JSC_ENGINE #include "bridge_test_jsc.h" #elif KRAKEN_QUICK_JS_ENGINE -#include "bridge_test_qjs.h" +#include "page_test.h" #endif #include -kraken::JSBridgeTest** bridgeTestPool{nullptr}; +std::unordered_map bridgeTestPool = std::unordered_map(); void initTestFramework(int32_t contextId) { - if (bridgeTestPool == nullptr) { - bridgeTestPool = new kraken::JSBridgeTest*[10]; - } - - auto bridge = static_cast(getJSContext(contextId)); - auto bridgeTest = new kraken::JSBridgeTest(bridge); + auto* page = static_cast(getPage(contextId)); + auto bridgeTest = new kraken::KrakenPageTest(page); bridgeTestPool[contextId] = bridgeTest; } diff --git a/bridge/bridge_qjs.cc b/bridge/page.cc similarity index 79% rename from bridge/bridge_qjs.cc rename to bridge/page.cc index 6702a24c60..2678169ffb 100644 --- a/bridge/bridge_qjs.cc +++ b/bridge/page.cc @@ -7,8 +7,8 @@ #include #include "bindings/qjs/qjs_patch.h" -#include "bridge_qjs.h" #include "dart_methods.h" +#include "page.h" #include "bindings/qjs/bom/blob.h" #include "bindings/qjs/bom/console.h" @@ -47,15 +47,14 @@ namespace kraken { using namespace binding::qjs; -std::unordered_map JSBridge::pluginByteCode{}; -ConsoleMessageHandler JSBridge::consoleMessageHandler{nullptr}; +std::unordered_map KrakenPage::pluginByteCode{}; +ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; -/** - * JSRuntime - */ -JSBridge::JSBridge(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { +kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; + +KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { #if ENABLE_PROFILE - double jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); #endif m_context = binding::qjs::createJSContext(contextId, handler, this); @@ -116,18 +115,17 @@ JSBridge::JSBridge(int32_t contextId, const JSExceptionHandler& handler) : conte #endif } -void JSBridge::parseHTML(const char* code, size_t length) { +bool KrakenPage::parseHTML(const char* code, size_t length) { if (!m_context->isValid()) - return; - Document* Document = Document::instance(m_context.get()); - auto document = DocumentInstance::instance(Document); - JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), document->instanceObject, "body"); + return false; + JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); HTMLParser::parseHTML(code, length, body); JS_FreeValue(m_context->ctx(), bodyValue); + return true; } -void JSBridge::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* rawEvent, NativeString* extra) { +void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* rawEvent, NativeString* extra) { if (!m_context->isValid()) return; @@ -136,7 +134,7 @@ void JSBridge::invokeModuleEvent(NativeString* moduleName, const char* eventType std::string type = std::string(eventType); auto* event = static_cast(rawEvent)->bytes; EventInstance* eventInstance = Event::buildEventInstance(type, m_context.get(), event, false); - eventObject = eventInstance->instanceObject; + eventObject = eventInstance->jsObject; } JSValue moduleNameValue = JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); @@ -170,7 +168,7 @@ void JSBridge::invokeModuleEvent(NativeString* moduleName, const char* eventType } } -void JSBridge::evaluateScript(const NativeString* script, const char* url, int startLine) { +void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { if (!m_context->isValid()) return; @@ -184,50 +182,41 @@ void JSBridge::evaluateScript(const NativeString* script, const char* url, int s #endif } -void JSBridge::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { +void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { if (!m_context->isValid()) return; m_context->evaluateJavaScript(script, length, url, startLine); } -void JSBridge::evaluateScript(const char* script, size_t length, const char* url, int startLine) { +void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { if (!m_context->isValid()) return; m_context->evaluateJavaScript(script, length, url, startLine); } -uint8_t* JSBridge::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { +uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { if (!m_context->isValid()) return nullptr; return m_context->dumpByteCode(script, length, url, byteLength); } -void JSBridge::evaluateByteCode(uint8_t* bytes, size_t byteLength) { +void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { if (!m_context->isValid()) return; m_context->evaluateByteCode(bytes, byteLength); } -JSBridge::~JSBridge() { +KrakenPage::~KrakenPage() { +#if IS_TEST if (disposeCallback != nullptr) { disposeCallback(this); } - - if (!m_context->isValid()) - return; - - if (m_disposeCallback != nullptr) { - this->m_disposeCallback(m_disposePrivateData); - } +#endif + pageContextPool[contextId] = nullptr; } -void JSBridge::reportError(const char* errmsg) { +void KrakenPage::reportError(const char* errmsg) { m_handler(m_context->getContextId(), errmsg); } -void JSBridge::setDisposeCallback(Task task, void* data) { - m_disposeCallback = task; - m_disposePrivateData = data; -} - } // namespace kraken diff --git a/bridge/bridge_qjs.h b/bridge/page.h similarity index 55% rename from bridge/bridge_qjs.h rename to bridge/page.h index 56fa0b2f0a..880604bb4d 100644 --- a/bridge/bridge_qjs.h +++ b/bridge/page.h @@ -7,8 +7,8 @@ #define KRAKEN_JS_QJS_BRIDGE_H_ #include +#include "bindings/qjs/executing_context.h" #include "bindings/qjs/html_parser.h" -#include "bindings/qjs/js_context.h" #include "include/kraken_bridge.h" #include @@ -17,43 +17,46 @@ namespace kraken { -class JSBridge; -using JSBridgeDisposeCallback = void (*)(JSBridge* bridge); +class KrakenPage; +using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); -class JSBridge final { +/// KrakenPage is class which manage all js objects create by flutter widget. +/// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, +/// and there is no data sharing between objects between different KrakenPages. +/// It's safe to allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. +class KrakenPage final { public: + static kraken::KrakenPage** pageContextPool; static ConsoleMessageHandler consoleMessageHandler; - JSBridge() = delete; - JSBridge(int32_t jsContext, const JSExceptionHandler& handler); - ~JSBridge(); + KrakenPage() = delete; + KrakenPage(int32_t jsContext, const JSExceptionHandler& handler); + ~KrakenPage(); + // Bytecodes which registered by kraken plugins. static std::unordered_map pluginByteCode; - int32_t contextId; - // the owner pointer which take JSBridge as property. - void* owner; - JSBridgeDisposeCallback disposeCallback{nullptr}; // evaluate JavaScript source codes in standard mode. void evaluateScript(const NativeString* script, const char* url, int startLine); void evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine); - void parseHTML(const char* code, size_t length); + bool parseHTML(const char* code, size_t length); void evaluateScript(const char* script, size_t length, const char* url, int startLine); uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - const std::unique_ptr& getContext() const { return m_context; } + [[nodiscard]] const std::unique_ptr& getContext() const { return m_context; } void invokeModuleEvent(NativeString* moduleName, const char* eventType, void* event, NativeString* extra); void reportError(const char* errmsg); - void setDisposeCallback(Task task, void* data); - - std::atomic event_registered = false; + int32_t contextId; +#if IS_TEST + // the owner pointer which take JSBridge as property. + void* owner; + JSBridgeDisposeCallback disposeCallback{nullptr}; +#endif private: - std::unique_ptr m_context; + std::unique_ptr m_context; JSExceptionHandler m_handler; - Task m_disposeCallback{nullptr}; - void* m_disposePrivateData{nullptr}; }; } // namespace kraken diff --git a/bridge/bridge_test_qjs.cc b/bridge/page_test.cc similarity index 59% rename from bridge/bridge_test_qjs.cc rename to bridge/page_test.cc index 35ddc8d226..ebcc0ea07d 100644 --- a/bridge/bridge_test_qjs.cc +++ b/bridge/page_test.cc @@ -3,22 +3,28 @@ * Author: Kraken Team. */ -#include "bridge_test_qjs.h" +#include "page_test.h" #include "bindings/qjs/bom/blob.h" #include "testframework.h" namespace kraken { -bool JSBridgeTest::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!context->isValid()) +bool KrakenPageTest::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { + if (!m_page_context->isValid()) return false; - // binding::jsc::updateLocation(sourceURL); - return context->evaluateJavaScript(code, codeLength, sourceURL, startLine); + return m_page_context->evaluateJavaScript(code, codeLength, sourceURL, startLine); } -static JSValue executeTest(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +bool KrakenPageTest::parseTestHTML(const uint16_t* code, size_t codeLength) { + if (!m_page_context->isValid()) + return false; + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); + return m_page->parseHTML(utf8Code.c_str(), utf8Code.length()); +} + +static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue& callback = argv[0]; - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); if (!JS_IsObject(callback)) { return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); } @@ -26,18 +32,18 @@ static JSValue executeTest(QjsContext* ctx, JSValueConst this_val, int argc, JSV if (!JS_IsFunction(ctx, callback)) { return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); } - auto bridge = static_cast(context->getOwner()); - auto bridgeTest = static_cast(bridge->owner); + auto bridge = static_cast(context->getOwner()); + auto bridgeTest = static_cast(bridge->owner); JS_DupValue(ctx, callback); bridgeTest->executeTestCallback = callback; return JS_NULL; } -static JSValue matchImageSnapshot(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue& blobValue = argv[0]; JSValue& screenShotValue = argv[1]; JSValue& callbackValue = argv[2]; - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); if (!JS_IsObject(blobValue)) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); @@ -64,14 +70,14 @@ static JSValue matchImageSnapshot(QjsContext* ctx, JSValueConst this_val, int ar return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); } - NativeString* screenShotNativeString = kraken::binding::qjs::jsValueToNativeString(ctx, screenShotValue); - auto bridge = static_cast(static_cast(context->getOwner())->owner); + std::unique_ptr screenShotNativeString = kraken::binding::qjs::jsValueToNativeString(ctx, screenShotValue); + auto bridge = static_cast(static_cast(context->getOwner())->owner); auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; list_add_tail(&callbackContext->link, &bridge->image_link); auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { auto* callbackContext = static_cast(ptr); - QjsContext* ctx = callbackContext->context->ctx(); + JSContext* ctx = callbackContext->context->ctx(); if (errmsg == nullptr) { JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; @@ -90,11 +96,11 @@ static JSValue matchImageSnapshot(QjsContext* ctx, JSValueConst this_val, int ar list_del(&callbackContext->link); }; - getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), screenShotNativeString, fn); + getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), screenShotNativeString.get(), fn); return JS_NULL; } -static JSValue environment(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { #if FLUTTER_BACKEND if (getDartMethod()->environment == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); @@ -106,19 +112,19 @@ static JSValue environment(QjsContext* ctx, JSValueConst this_val, int argc, JSV #endif } -static JSValue simulatePointer(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (getDartMethod()->simulatePointer == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); } - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue& inputArrayValue = argv[0]; + JSValue inputArrayValue = argv[0]; if (!JS_IsObject(inputArrayValue)) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); } - JSValue& pointerValue = argv[1]; + JSValue pointerValue = argv[1]; if (!JS_IsNumber(pointerValue)) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); } @@ -167,7 +173,7 @@ static JSValue simulatePointer(QjsContext* ctx, JSValueConst this_val, int argc, return JS_NULL; } -static JSValue simulateInputText(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (getDartMethod()->simulateInputText == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); } @@ -178,20 +184,32 @@ static JSValue simulateInputText(QjsContext* ctx, JSValueConst this_val, int arg return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); } - NativeString* nativeString = kraken::binding::qjs::jsValueToNativeString(ctx, charStringValue); - getDartMethod()->simulateInputText(nativeString); + std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(ctx, charStringValue); + getDartMethod()->simulateInputText(nativeString.get()); nativeString->free(); return JS_NULL; }; -static JSValue runGC(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JS_RunGC(context->runtime()); +static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + + if (argc == 1) { + JSValue& html = argv[0]; + + std::string strHTML = binding::qjs::jsValueToStdString(ctx, html); + + JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); + auto* body = static_cast(JS_GetOpaque(bodyValue, binding::qjs::Element::classId())); + binding::qjs::HTMLParser::parseHTML(strHTML, body); + + JS_FreeValue(ctx, bodyValue); + } + return JS_NULL; } -static JSValue triggerGlobalError(QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); +static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->global(), "triggerGlobalError"); @@ -204,16 +222,16 @@ static JSValue triggerGlobalError(QjsContext* ctx, JSValueConst this_val, int ar return JS_NULL; } -JSBridgeTest::JSBridgeTest(JSBridge* bridge) : bridge_(bridge), context(bridge->getContext()) { +KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { bridge->owner = this; - bridge->disposeCallback = [](JSBridge* bridge) { delete static_cast(bridge->owner); }; - QJS_GLOBAL_BINDING_FUNCTION(context, executeTest, "__kraken_execute_test__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); - QJS_GLOBAL_BINDING_FUNCTION(context, environment, "__kraken_environment__", 0); - QJS_GLOBAL_BINDING_FUNCTION(context, simulatePointer, "__kraken_simulate_pointer__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, simulateInputText, "__kraken_simulate_inputtext__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, triggerGlobalError, "__kraken_trigger_global_error__", 0); - QJS_GLOBAL_BINDING_FUNCTION(context, runGC, "__kraken_run_gc__", 0); + bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); + QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); initKrakenTestFramework(bridge); init_list_head(&image_link); @@ -222,20 +240,20 @@ JSBridgeTest::JSBridgeTest(JSBridge* bridge) : bridge_(bridge), context(bridge-> struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; - explicit ExecuteCallbackContext(binding::qjs::JSContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; + explicit ExecuteCallbackContext(binding::qjs::ExecutionContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; ExecuteCallback executeCallback; - binding::qjs::JSContext* context; + binding::qjs::ExecutionContext* context; }; -void JSBridgeTest::invokeExecuteTest(ExecuteCallback executeCallback) { +void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { if (JS_IsNull(executeTestCallback)) { return; } - if (!JS_IsFunction(context->ctx(), executeTestCallback)) { + if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { return; } - auto done = [](QjsContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { + auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { JSValue& statusValue = argv[0]; JSValue proxyObject = func_data[0]; auto* callbackContext = static_cast(JS_GetOpaque(proxyObject, 1)); @@ -244,23 +262,22 @@ void JSBridgeTest::invokeExecuteTest(ExecuteCallback executeCallback) { return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); } - NativeString* status = kraken::binding::qjs::jsValueToNativeString(ctx, statusValue); - callbackContext->executeCallback(callbackContext->context->getContextId(), status); - status->free(); + std::unique_ptr status = kraken::binding::qjs::jsValueToNativeString(ctx, statusValue); + callbackContext->executeCallback(callbackContext->context->getContextId(), status.get()); return JS_NULL; }; - auto* callbackContext = new ExecuteCallbackContext(context.get(), executeCallback); - executeTestProxyObject = JS_NewObject(context->ctx()); + auto* callbackContext = new ExecuteCallbackContext(m_page_context.get(), executeCallback); + executeTestProxyObject = JS_NewObject(m_page_context->ctx()); JS_SetOpaque(executeTestProxyObject, callbackContext); JSValue callbackData[]{executeTestProxyObject}; - JSValue callback = JS_NewCFunctionData(context->ctx(), done, 0, 0, 1, callbackData); + JSValue callback = JS_NewCFunctionData(m_page_context->ctx(), done, 0, 0, 1, callbackData); JSValue arguments[] = {callback}; - JSValue result = JS_Call(context->ctx(), executeTestCallback, executeTestCallback, 1, arguments); - context->handleException(&result); - context->drainPendingPromiseJobs(); - JS_FreeValue(context->ctx(), executeTestCallback); - JS_FreeValue(context->ctx(), callback); + JSValue result = JS_Call(m_page_context->ctx(), executeTestCallback, executeTestCallback, 1, arguments); + m_page_context->handleException(&result); + m_page_context->drainPendingPromiseJobs(); + JS_FreeValue(m_page_context->ctx(), executeTestCallback); + JS_FreeValue(m_page_context->ctx(), callback); executeTestCallback = JS_NULL; } diff --git a/bridge/bridge_test_qjs.h b/bridge/page_test.h similarity index 58% rename from bridge/bridge_test_qjs.h rename to bridge/page_test.h index b8e20b4a2b..cff8117976 100644 --- a/bridge/bridge_test_qjs.h +++ b/bridge/page_test.h @@ -3,44 +3,47 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_BRIDGE_TEST_QJS_H -#define KRAKENBRIDGE_BRIDGE_TEST_QJS_H +#ifndef KRAKENBRIDGE_PAGE_TEST_H +#define KRAKENBRIDGE_PAGE_TEST_H -#include "bridge_qjs.h" +#include "bindings/qjs/dom/document.h" +#include "bindings/qjs/html_parser.h" #include "kraken_bridge_test.h" +#include "page.h" namespace kraken { struct ImageSnapShotContext { JSValue callback; - binding::qjs::JSContext* context; + binding::qjs::ExecutionContext* context; list_head link; }; -class JSBridgeTest final { +class KrakenPageTest final { public: - explicit JSBridgeTest() = delete; - explicit JSBridgeTest(JSBridge* bridge); + explicit KrakenPageTest() = delete; + explicit KrakenPageTest(KrakenPage* bridge); - ~JSBridgeTest() { + ~KrakenPageTest() { if (!JS_IsNull(executeTestCallback)) { - JS_FreeValue(context->ctx(), executeTestCallback); + JS_FreeValue(m_page_context->ctx(), executeTestCallback); } if (!JS_IsNull(executeTestProxyObject)) { - JS_FreeValue(context->ctx(), executeTestProxyObject); + JS_FreeValue(m_page_context->ctx(), executeTestProxyObject); } { struct list_head *el, *el1; list_for_each_safe(el, el1, &image_link) { auto* image = list_entry(el, ImageSnapShotContext, link); - JS_FreeValue(context->ctx(), image->callback); + JS_FreeValue(m_page_context->ctx(), image->callback); } } } /// evaluete JavaScript source code with build-in test frameworks, use in test only. bool evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); + bool parseTestHTML(const uint16_t* code, size_t codeLength); void invokeExecuteTest(ExecuteCallback executeCallback); JSValue executeTestCallback{JS_NULL}; @@ -49,11 +52,11 @@ class JSBridgeTest final { private: /// the pointer of bridge, ownership belongs to JSBridge - JSBridge* bridge_; + KrakenPage* m_page; /// the pointer of JSContext, overship belongs to JSContext - const std::unique_ptr& context; + const std::unique_ptr& m_page_context; }; } // namespace kraken -#endif // KRAKENBRIDGE_BRIDGE_TEST_QJS_H +#endif // KRAKENBRIDGE_PAGE_TEST_H diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index d39ba07285..6f3fa43b71 100644 --- a/bridge/polyfill/scripts/js_to_c.js +++ b/bridge/polyfill/scripts/js_to_c.js @@ -37,10 +37,10 @@ const getPolyFillHeader = (outputName) => `/* #if KRAKEN_JSC_ENGINE #include "bridge_jsc.h" #elif KRAKEN_QUICK_JS_ENGINE -#include "bridge_qjs.h" +#include "page.h" #endif -void initKraken${outputName}(kraken::JSBridge *bridge); +void initKraken${outputName}(kraken::KrakenPage *page); #endif // KRAKEN_${outputName.toUpperCase()}_H `; @@ -53,7 +53,7 @@ uint8_t bytes[${uint8Array.length}] = {${uint8Array.join(',')}}; }`; }; const getPolyfillEvalCall = () => { - return 'bridge->evaluateByteCode(bytes, byteLength);'; + return 'page->evaluateByteCode(bytes, byteLength);'; } const getPolyFillSource = (source, outputName) => `/* @@ -65,7 +65,7 @@ const getPolyFillSource = (source, outputName) => `/* ${getPolyFillJavaScriptSource(source)} -void initKraken${outputName}(kraken::JSBridge *bridge) { +void initKraken${outputName}(kraken::KrakenPage *page) { ${getPolyfillEvalCall()} } `; diff --git a/bridge/polyfill/src/dom.ts b/bridge/polyfill/src/dom.ts index 4afe2c7c8e..c64e6aa3c9 100644 --- a/bridge/polyfill/src/dom.ts +++ b/bridge/polyfill/src/dom.ts @@ -1,36 +1,11 @@ +let html = document.createElement('html'); +document.appendChild(html); + let head = document.createElement('head'); document.documentElement.appendChild(head); -Object.defineProperty(document, 'head', { - set(value: HTMLHeadElement) { - if (value == null || value.tagName != 'HEAD') { - throw TypeError(`Failed to set the 'head' property on 'Document': The new body element must be a 'head' element.`) - } - document.documentElement.replaceChild(value, head); - head = value; - }, - get() { - return head; - }, - enumerable: true, - configurable: false -}); let body = document.createElement('body'); document.documentElement.appendChild(body); -Object.defineProperty(document, 'body', { - set(value: HTMLBodyElement) { - if (value == null || value.tagName != 'BODY') { - throw TypeError(`Failed to set the 'body' property on 'Document': The new body element must be a 'BODY' element.`) - } - document.documentElement.replaceChild(value, body); - body = value; - }, - get() { - return body; - }, - enumerable: true, - configurable: false -}); // @ts-ignore class SVGElement extends Element { diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index ce3b72efa9..967b90ab86 100644 --- a/bridge/polyfill/src/test/index.js +++ b/bridge/polyfill/src/test/index.js @@ -58,10 +58,8 @@ class JasmineTracker { specDone(result) { clearAllTimer(); - clearAllEventsListeners(); resetDocumentElement(); kraken.methodChannel.clearMethodCallHandler(); - __kraken_run_gc__(); } specStarted(result) { } @@ -160,8 +158,17 @@ global.simulateInputText = __kraken_simulate_inputtext__; function resetDocumentElement() { window.scrollTo(0, 0); - document.head = document.createElement('head'); - document.body = document.createElement('body'); + document.removeChild(document.documentElement); + let html = document.createElement('html'); + document.appendChild(html); + + let head = document.createElement('head'); + document.documentElement.appendChild(head); + + let body = document.createElement('body'); + document.documentElement.appendChild(body); + + document.documentElement.style.backgroundColor = 'white'; } function traverseNode(node, handle) { @@ -175,13 +182,6 @@ function traverseNode(node, handle) { } } -function clearAllEventsListeners() { - window.__kraken_clear_event_listeners__(); - traverseNode(document.body, (node) => { - node.__kraken_clear_event_listeners__(); - }); -} - __kraken_execute_test__((done) => { jasmineTracker.onJasmineDone = (result) => { done(result.overallStatus); diff --git a/bridge/polyfill/src/xhr.ts b/bridge/polyfill/src/xhr.ts index ad6ea3f3e3..54893cc9a0 100644 --- a/bridge/polyfill/src/xhr.ts +++ b/bridge/polyfill/src/xhr.ts @@ -16,8 +16,13 @@ const builtInEvents = [ // Set some default headers const defaultHeaders = { - "User-Agent": navigator.userAgent, - "Accept": "*/*", + // Use getter instead of value for lazy read value at initialize time. + get "User-Agent"() { + return navigator.userAgent; + }, + get "Accept"() { + return "*/*"; + } }; // These request methods are not allowed diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index bd8c5242c0..3895bd4235 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -6,7 +6,27 @@ import {addIndent} from "./utils"; function generatePropsHeader(object: ClassObject, type: PropType) { let propsDefine = ''; if (object.props.length > 0) { - propsDefine = `${type == PropType.hostObject ? 'DEFINE_HOST_OBJECT_PROPERTY' : 'DEFINE_HOST_CLASS_PROPERTY'}(${object.props.length}, ${object.props.map(o => o.name).join(', ')})`; + + if (type == PropType.hostObject) { + for (let i = 0; i < object.props.length; i ++) { + let p = object.props[i]; + + if (p.readonly) { + propsDefine += `DEFINE_READONLY_PROPERTY(${p.name});\n`; + } else { + propsDefine += `DEFINE_PROPERTY(${p.name});\n`; + } + } + } else { + for (let i = 0; i < object.props.length; i ++) { + let p = object.props[i]; + if (p.readonly) { + propsDefine += `DEFINE_PROTOTYPE_READONLY_PROPERTY(${p.name});\n`; + } else { + propsDefine += `DEFINE_PROTOTYPE_PROPERTY(${p.name});\n`; + } + } + } } return propsDefine; } @@ -21,8 +41,13 @@ function generateMethodsHeader(object: ClassObject, type: PropType) { let methodsImpl: string[] = []; if (object.methods.length > 0) { let methods = uniqBy(object.methods, (o) => o.name); - methodsDefine = methods.map(o => `static JSValue ${o.name}(QjsContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);`); - methodsImpl = methods.map(o => `ObjectFunction m_${o.name}{m_context, ${type == PropType.hostClass ? 'm_prototypeObject' : 'jsObject'}, "${o.name}", ${o.name}, ${o.args.length}};`) + methodsDefine = methods.map(o => `static JSValue ${o.name}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);`); + + if (type == PropType.hostClass) { + methodsImpl = methods.map(o => `DEFINE_PROTOTYPE_FUNCTION(${o.name}, ${o.args.length});`) + } else { + methodsImpl = methods.map(o => `DEFINE_FUNCTION(${o.name}, ${o.args.length});`) + } } return { methodsImpl, @@ -42,7 +67,7 @@ struct Native${object.name} { class ${object.name} : public ${object.type} { public: ${object.name}() = delete; - explicit ${object.name}(JSContext *context, Native${object.name} *nativePtr); + explicit ${object.name}(ExecutionContext *context, Native${object.name} *nativePtr); JSValue callNativeMethods(const char* method, int32_t argc, NativeValue *argv); @@ -86,18 +111,22 @@ ${addIndent(nativeStructPropsCode.join('\n'), 2)} } let constructorHeader = `\n -void bind${object.name}(std::unique_ptr &context); +void bind${object.name}(std::unique_ptr &context); + +class ${object.name}Instance; ${nativeStructCode} class ${object.name} : public ${object.type} { public: ${object.name}() = delete; - explicit ${object.name}(JSContext *context); - JSValue instanceConstructor(QjsContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) override; + explicit ${object.name}(ExecutionContext *context); + JSValue instanceConstructor(JSContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) override; ${methodsDefine.join('\n ')} OBJECT_INSTANCE(${object.name}); private: + ${propsDefine} ${methodsImpl.join('\n ')} + friend ${object.name}Instance; };`; let instanceConstructorHeader = ``; @@ -112,7 +141,7 @@ public: ${object.name}Instance() = delete; ${instanceConstructorHeader} private: - ${propsDefine} + friend ${object.name}; }; `; diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 2384dd3789..d79205ef81 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -12,7 +12,7 @@ import {addIndent} from "./utils"; function generateHostObjectSource(object: ClassObject) { let propSource: string[] = generatePropsSource(object, PropType.hostObject); let methodsSource: string[] = generateMethodsSource(object, PropType.hostObject); - return `${object.name}::${object.name}(JSContext *context, + return `${object.name}::${object.name}(ExecutionContext *context, Native${object.name} *nativePtr) : HostObject(context, "${object.name}"), m_nativePtr(nativePtr) { } @@ -27,7 +27,7 @@ JSValue ${object.name}::callNativeMethods(const char *method, int32_t argc, NativeString m{ reinterpret_cast(methodString.c_str()), - static_cast(methodString.size()) + static_cast(methodString.size()) }; NativeValue nativeValue{}; @@ -48,6 +48,7 @@ enum PropType { function getPropsVars(object: ClassObject, type: PropType) { let classSubFix = object.name; + let className = object.name; if (type == PropType.Element || type == PropType.Event) { classSubFix += 'Instance'; } @@ -56,7 +57,7 @@ function getPropsVars(object: ClassObject, type: PropType) { let classId = ''; if (type == PropType.hostObject) { instanceName = 'object'; - classId = 'JSContext::kHostObjectClassId'; + classId = 'ExecutionContext::kHostObjectClassId'; } else if (type == PropType.Element) { instanceName = 'element'; classId = 'Element::classId()'; @@ -66,6 +67,7 @@ function getPropsVars(object: ClassObject, type: PropType) { } return { + className, classSubFix, classId, instanceName @@ -76,6 +78,7 @@ function generatePropsGetter(object: ClassObject, type: PropType, p: PropsDeclar let { classId, classSubFix, + className, instanceName } = getPropsVars(object, type); @@ -113,7 +116,7 @@ function generatePropsGetter(object: ClassObject, type: PropType, p: PropsDeclar flushUICommandCode = 'getDartMethod()->flushUICommand();' } - return `PROP_GETTER(${classSubFix}, ${p.name})(QjsContext *ctx, JSValue this_val, int argc, JSValue *argv) { + return `IMPL_PROPERTY_GETTER(${className}, ${p.name})(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { ${flushUICommandCode} ${getterCode} }`; @@ -123,21 +126,20 @@ function generatePropsSetter(object: ClassObject, type: PropType, p: PropsDeclar let { classId, classSubFix, + className, instanceName } = getPropsVars(object, type); if (p.readonly) { - return `PROP_SETTER(${classSubFix}, ${p.name})(QjsContext *ctx, JSValue this_val, int argc, JSValue *argv) { - return JS_NULL; -}`; + return ''; } let setterCode = ''; if (object.type == 'Element') { setterCode = `std::string key = "${p.name}"; - NativeString *args_01 = stringToNativeString(key); - NativeString *args_02 = jsValueToNativeString(ctx, argv[0]); - foundation::UICommandBuffer::instance(${instanceName}->m_context->getContextId()) + std::unique_ptr args_01 = stringToNativeString(key); + std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); + element->m_context->uiCommandBuffer() ->addCommand(${instanceName}->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); return JS_NULL;`; } else { @@ -148,7 +150,7 @@ function generatePropsSetter(object: ClassObject, type: PropType, p: PropsDeclar } - return `PROP_SETTER(${classSubFix}, ${p.name})(QjsContext *ctx, JSValue this_val, int argc, JSValue *argv) { + return `IMPL_PROPERTY_SETTER(${className}, ${p.name})(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); ${setterCode} }`; @@ -275,7 +277,7 @@ ${addIndent(createMethodBody(allConditions[i]), 2)} }\n `) } - let polymorphismTemplate = `JSValue ${object.name}::${m.name}(QjsContext *ctx, JSValue this_val, int argc, JSValue *argv) { + let polymorphismTemplate = `JSValue ${object.name}::${m.name}(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { switch(argc) { ${addIndent(caseCode.join(''), 2)} default: @@ -286,7 +288,7 @@ ${addIndent(createMethodBody(allConditions[i]), 2)} } else { let body = createMethodBody(m); - methodsSource.push(`JSValue ${object.name}::${m.name}(QjsContext *ctx, JSValue this_val, int argc, JSValue *argv) { + methodsSource.push(`JSValue ${object.name}::${m.name}(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { ${body} }`); } @@ -309,12 +311,12 @@ function generateEventConstructorCode(object: ClassObject) { } auto *nativeEvent = new Native${object.name}(); - nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue); + nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue).release(); ${generateEventInstanceConstructorCode(object)} auto event = new ${object.name}Instance(this, reinterpret_cast(nativeEvent)); - return event->instanceObject;`; + return event->jsObject;`; } function generateEventInstanceConstructorCode(object: ClassObject) { @@ -333,14 +335,15 @@ function generateEventInstanceConstructorCode(object: ClassObject) { propApplyCode = `JS_ToInt32(m_ctx, reinterpret_cast(&nativeEvent->${p.name}), JS_GetProperty(m_ctx, eventInit, ${p.name}Atom));` } else if (p.kind === PropsDeclarationKind.string) { propApplyCode = addIndent(`JSValue v = JS_GetProperty(m_ctx, eventInit, ${p.name}Atom); - nativeEvent->${p.name} = jsValueToNativeString(m_ctx, v); + nativeEvent->${p.name} = jsValueToNativeString(m_ctx, v).release(); JS_FreeValue(m_ctx, v);`, 0); } else if (p.kind === PropsDeclarationKind.double) { propApplyCode = `JS_ToFloat64(m_ctx, &nativeEvent->${p.name}, JS_GetProperty(m_ctx, eventInit, ${p.name}Atom));`; } else if (p.kind === PropsDeclarationKind.object) { propApplyCode = addIndent(`JSValue v = JS_GetProperty(m_ctx, eventInit, ${p.name}Atom); JSValue json = JS_JSONStringify(m_ctx, v, JS_NULL, JS_NULL); - nativeEvent->${p.name} = jsValueToNativeString(m_ctx, json); + if (JS_IsException(json)) return json; + nativeEvent->${p.name} = jsValueToNativeString(m_ctx, json).release(); JS_FreeValue(m_ctx, json); JS_FreeValue(m_ctx, v);`, 0); } @@ -385,7 +388,7 @@ function generateHostClassSource(object: ClassObject) { let constructorCode = ''; if (object.type === 'Element') { constructorCode = `auto instance = new ${object.name}Instance(this); - return instance->instanceObject;`; + return instance->jsObject;`; } else if (object.type === 'Event') { constructorCode = generateEventConstructorCode(object); } @@ -406,7 +409,7 @@ function generateHostClassSource(object: ClassObject) { let specialBind = ''; if (object.name === 'ImageElement') { - specialBind = `context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->classObject));` + specialBind = `context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->jsObject));` } let classInheritCode = ''; @@ -417,17 +420,17 @@ function generateHostClassSource(object: ClassObject) { } return ` -${object.name}::${object.name}(JSContext *context) : ${object.type}(context) { +${object.name}::${object.name}(ExecutionContext *context) : ${object.type}(context) { ${classInheritCode} } -void bind${object.name}(std::unique_ptr &context) { +void bind${object.name}(std::unique_ptr &context) { auto *constructor = ${object.name}::instance(context.get()); - context->defineGlobalProperty("${globalBindingName}", constructor->classObject); + context->defineGlobalProperty("${globalBindingName}", constructor->jsObject); ${specialBind} } -JSValue ${object.name}::instanceConstructor(QjsContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) { +JSValue ${object.name}::instanceConstructor(JSContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) { ${constructorCode} } ${propSource.join('\n')} @@ -453,7 +456,7 @@ export function generateCppSource(blob: Blob) { */ #include "${blob.filename}.h" -#include "bridge_qjs.h" +#include "page.h" #include "bindings/qjs/qjs_patch.h" namespace kraken::binding::qjs { diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc new file mode 100644 index 0000000000..ad7b150014 --- /dev/null +++ b/bridge/test/benchmark/create_element.cc @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include +#include "kraken_test_env.h" +#include "page.h" + +auto bridge = TEST_init(); + +static void CreateRawJavaScriptObjects(benchmark::State& state) { + auto& context = bridge->getContext(); + std::string code = "var a = {}"; + // Perform setup here + for (auto _ : state) { + context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + } +} + +static void CreateDivElement(benchmark::State& state) { + auto& context = bridge->getContext(); + std::string code = "var a = document.createElement('div');"; + // Perform setup here + for (auto _ : state) { + context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + } +} + +BENCHMARK(CreateRawJavaScriptObjects)->Threads(1); +BENCHMARK(CreateDivElement)->Threads(1); + +// Run the benchmark +BENCHMARK_MAIN(); diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc new file mode 100644 index 0000000000..4573db1678 --- /dev/null +++ b/bridge/test/kraken_test_env.cc @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "kraken_test_env.h" +#include +#include +#include "bindings/qjs/dom/event_target.h" +#include "dart_methods.h" +#include "include/kraken_bridge.h" +#include "kraken_bridge_test.h" +#include "page.h" + +#if defined(__linux__) || defined(__APPLE__) +static int64_t get_time_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +} +#else +/* more portable, but does not work if the date is updated */ +static int64_t get_time_ms(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} +#endif + +typedef struct { + struct list_head link; + int64_t timeout; + DOMTimer* timer; + int32_t contextId; + bool isInterval; + AsyncCallback func; +} JSOSTimer; + +typedef struct { + struct list_head link; + FrameCallback* callback; + int32_t contextId; + AsyncRAFCallback handler; + int32_t callbackId; +} JSFrameCallback; + +typedef struct JSThreadState { + std::unordered_map os_timers; /* list of timer.link */ + std::unordered_map os_frameCallbacks; +} JSThreadState; + +static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { + ts->os_timers.erase(th->timer->timerId()); +} + +static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { + ts->os_frameCallbacks.erase(th->callbackId); +} + +NativeString* TEST_invokeModule(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, NativeString* params, AsyncModuleCallback callback) { + return nullptr; +}; + +void TEST_requestBatchUpdate(int32_t contextId){}; + +void TEST_reloadApp(int32_t contextId) {} + +int32_t timerId = 0; + +int32_t TEST_setTimeout(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = JS_GetRuntime(timer->ctx()); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->timeout = get_time_ms() + timeout; + th->func = callback; + th->timer = timer; + th->contextId = contextId; + th->isInterval = false; + int32_t id = timerId++; + + ts->os_timers[id] = th; + + return id; +} + +int32_t TEST_setInterval(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = JS_GetRuntime(timer->ctx()); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->timeout = get_time_ms() + timeout; + th->func = callback; + th->timer = timer; + th->contextId = contextId; + th->isInterval = true; + int32_t id = timerId++; + + ts->os_timers[id] = th; + + return id; +} + +int32_t callbackId = 0; + +uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { + JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); + auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->handler = handler; + th->callback = frameCallback; + th->contextId = context->getContextId(); + int32_t id = callbackId++; + + th->callbackId = id; + + ts->os_frameCallbacks[id] = th; + + return id; +} + +void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext().get(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + ts->os_frameCallbacks.erase(id); +} + +void TEST_clearTimeout(int32_t contextId, int32_t timerId) { + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext().get(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + ts->os_timers.erase(timerId); +} + +NativeScreen* TEST_getScreen(int32_t contextId) { + return nullptr; +}; + +double TEST_devicePixelRatio(int32_t contextId) { + return 1.0; +} + +NativeString* TEST_platformBrightness(int32_t contextId) { + return nullptr; +} + +void TEST_toBlob(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) {} + +void TEST_flushUICommand() {} + +void TEST_initWindow(int32_t contextId, void* nativePtr) {} + +void TEST_initDocument(int32_t contextId, void* nativePtr) {} + +#if ENABLE_PROFILE +struct NativePerformanceEntryList { + uint64_t* entries; + int32_t length; +}; +NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} +#endif + +std::once_flag testInitOnceFlag; +static int32_t inited{false}; + +std::unique_ptr TEST_init(OnJSError onJsError) { + uint32_t contextId; + if (inited) { + contextId = allocateNewPage(-1); + } else { + contextId = 0; + } + std::call_once(testInitOnceFlag, []() { + initJSPagePool(1024 * 1024); + inited = true; + }); + initTestFramework(contextId); + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext().get(); + JSThreadState* th = new JSThreadState(); + JS_SetRuntimeOpaque(context->runtime(), th); + + TEST_mockDartMethods(onJsError); + + return std::unique_ptr(page); +} + +std::unique_ptr TEST_init() { + return TEST_init(nullptr); +} + +std::unique_ptr TEST_allocateNewPage() { + uint32_t newContextId = allocateNewPage(-1); + initTestFramework(newContextId); + return std::unique_ptr(static_cast(getPage(newContextId))); +} + +static bool jsPool(ExecutionContext* context) { + JSRuntime* rt = context->runtime(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + int64_t cur_time, delay; + struct list_head* el; + + if (ts->os_timers.empty() && ts->os_frameCallbacks.empty()) + return true; /* no more events */ + + if (!ts->os_timers.empty()) { + cur_time = get_time_ms(); + for (auto& entry : ts->os_timers) { + JSOSTimer* th = entry.second; + delay = th->timeout - cur_time; + if (delay <= 0) { + AsyncCallback func; + /* the timer expired */ + func = th->func; + + if (th->isInterval) { + func(th->timer, th->contextId, nullptr); + } else { + th->func = nullptr; + func(th->timer, th->contextId, nullptr); + unlink_timer(ts, th); + } + + return false; + } + } + } + + if (!ts->os_frameCallbacks.empty()) { + for (auto& entry : ts->os_frameCallbacks) { + JSFrameCallback* th = entry.second; + AsyncRAFCallback handler = th->handler; + th->handler = nullptr; + handler(th->callback, th->contextId, 0, nullptr); + unlink_callback(ts, th); + return false; + } + } + + return false; +} + +void TEST_runLoop(ExecutionContext* context) { + for (;;) { + context->drainPendingPromiseJobs(); + if (jsPool(context)) + break; + } +} + +void TEST_dispatchEvent(int32_t contextId, EventTargetInstance* eventTarget, const std::string type) { + NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); + auto nativeEventType = stringToNativeString(type); + NativeString* rawEventType = nativeEventType.release(); + + NativeEvent* nativeEvent = new NativeEvent{rawEventType}; + + RawEvent* rawEvent = new RawEvent{reinterpret_cast(nativeEvent)}; + + NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); +} + +void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} + +std::unordered_map> unitTestEnvMap; +std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { + if (unitTestEnvMap.count(contextUniqueId) == 0) { + unitTestEnvMap[contextUniqueId] = std::make_shared(); + } + + return unitTestEnvMap[contextUniqueId]; +} + +void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { + if (unitTestEnvMap.count(contextUniqueId) == 0) { + unitTestEnvMap[contextUniqueId] = std::make_shared(); + } + + unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; +} + +void TEST_mockDartMethods(OnJSError onJSError) { + std::vector mockMethods{ + reinterpret_cast(TEST_invokeModule), + reinterpret_cast(TEST_requestBatchUpdate), + reinterpret_cast(TEST_reloadApp), + reinterpret_cast(TEST_setTimeout), + reinterpret_cast(TEST_setInterval), + reinterpret_cast(TEST_clearTimeout), + reinterpret_cast(TEST_requestAnimationFrame), + reinterpret_cast(TEST_cancelAnimationFrame), + reinterpret_cast(TEST_getScreen), + reinterpret_cast(TEST_devicePixelRatio), + reinterpret_cast(TEST_platformBrightness), + reinterpret_cast(TEST_toBlob), + reinterpret_cast(TEST_flushUICommand), + reinterpret_cast(TEST_initWindow), + reinterpret_cast(TEST_initDocument), + }; + +#if ENABLE_PROFILE + mockMethods.emplace_pack(reinterpret_cast(TEST_getPerformanceEntries)); +#else + mockMethods.emplace_back(0); +#endif + + mockMethods.emplace_back(reinterpret_cast(onJSError)); + registerDartMethods(mockMethods.data(), mockMethods.size()); +} diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h new file mode 100644 index 0000000000..a13bedfeae --- /dev/null +++ b/bridge/test/kraken_test_env.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ +#define KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ + +#include +#include "bindings/qjs/bom/timer.h" +#include "bindings/qjs/dom/event_target.h" +#include "bindings/qjs/dom/frame_request_callback_collection.h" +#include "include/dart_methods.h" +#include "page.h" + +using namespace kraken::binding::qjs; + +// Trigger a callbacks before GC free the eventTargets. +using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); +struct UnitTestEnv { + TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; +}; + +// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. + +std::unique_ptr TEST_init(OnJSError onJsError); +std::unique_ptr TEST_init(); +std::unique_ptr TEST_allocateNewPage(); +void TEST_runLoop(ExecutionContext* context); +void TEST_dispatchEvent(int32_t contextId, EventTargetInstance* eventTarget, const std::string type); +void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); +void TEST_mockDartMethods(OnJSError onJSError); +std::shared_ptr TEST_getEnv(int32_t contextUniqueId); + +#endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc new file mode 100644 index 0000000000..e7c9778b50 --- /dev/null +++ b/bridge/test/run_integration_test.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include +#include "gtest/gtest.h" +#include "kraken_bridge_test.h" +#include "kraken_test_env.h" +#include "page.h" + +std::string readTestSpec() { + std::string filepath = std::string(SPEC_FILE_PATH) + "/../integration_tests/.specs/core.build.js"; + + std::ifstream file; + file.open(filepath); + + std::string content; + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + content += line + "\n"; + } + file.close(); + } + + return content; +} + +// Run kraken integration test specs with Google Test. +// Very useful to fix bridge bugs. +TEST(IntegrationTest, runSpecs) { + auto bridge = TEST_init(); + auto& context = bridge->getContext(); + + std::string code = readTestSpec(); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + executeTest(context->getContextId(), [](int32_t contextId, NativeString* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; }); + + TEST_runLoop(context.get()); +} diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 46b6167494..a7aa81b38f 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -7,51 +7,90 @@ list(APPEND KRAKEN_TEST_SOURCE set(gtest_disable_pthreads ON) add_subdirectory(./third_party/googletest) +add_subdirectory(./third_party/benchmark) -if ($ENV{KRAKEN_JS_ENGINE} MATCHES "jsc") - list(APPEND KRAKEN_TEST_SOURCE - bridge_test_jsc.cc - bridge_test_jsc.h - ) -elseif($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") - list(APPEND KRAKEN_TEST_SOURCE - bridge_test_qjs.cc - bridge_test_qjs.h - ) - list(APPEND KRAKEN_UNIT_TEST_SOURCE - ./bindings/qjs/js_context_test.cc - ./bindings/qjs/bom/console_test.cc - ./bindings/qjs/qjs_patch_test.cc - ./bindings/qjs/host_object_test.cc - ./bindings/qjs/host_class_test.cc - ./bindings/qjs/dom/event_target_test.cc - ./bindings/qjs/dom/node_test.cc - ./bindings/qjs/dom/element_test.cc - ./bindings/qjs/dom/document_test.cc - ./bindings/qjs/dom/text_node_test.cc - ./bindings/qjs/bom/window_test.cc - ./bindings/qjs/dom/custom_event_test.cc - ) +list(APPEND KRAKEN_TEST_SOURCE + page_test.cc + page_test.h +) +list(APPEND KRAKEN_UNIT_TEST_SOURCE + ./test/kraken_test_env.cc + ./test/kraken_test_env.h + ./bindings/qjs/js_context_test.cc + ./bindings/qjs/bom/timer_test.cc + ./bindings/qjs/bom/console_test.cc + ./bindings/qjs/qjs_patch_test.cc + ./bindings/qjs/host_object_test.cc + ./bindings/qjs/host_class_test.cc + ./bindings/qjs/dom/event_target_test.cc + ./bindings/qjs/module_manager_test.cc + ./bindings/qjs/dom/node_test.cc + ./bindings/qjs/dom/event_test.cc + ./bindings/qjs/dom/element_test.cc + ./bindings/qjs/dom/document_test.cc + ./bindings/qjs/dom/text_node_test.cc + ./bindings/qjs/bom/window_test.cc + ./bindings/qjs/dom/custom_event_test.cc + ./bindings/qjs/module_manager_test.cc +) + +### kraken_unit_test executable +add_executable(kraken_unit_test + ${KRAKEN_UNIT_TEST_SOURCE} + ${KRAKEN_TEST_SOURCE} + ${BRIDGE_SOURCE} +) - ### kraken_unit_test executable - add_executable(kraken_unit_test ${KRAKEN_UNIT_TEST_SOURCE} ${KRAKEN_TEST_SOURCE} ${BRIDGE_SOURCE} ../bindings/qjs/html_parser.cc ../bindings/qjs/html_parser.h) - target_include_directories(kraken_unit_test PUBLIC ./third_party/googletest/googletest/include ${BRIDGE_INCLUDE}) - target_link_libraries(kraken_unit_test gtest gtest_main ${BRIDGE_LINK_LIBS}) +target_include_directories(kraken_unit_test PUBLIC ./third_party/googletest/googletest/include ${BRIDGE_INCLUDE} ./test) +target_link_libraries(kraken_unit_test gtest gtest_main ${BRIDGE_LINK_LIBS}) - target_compile_options(quickjs PUBLIC -DDUMP_LEAKS=1) - target_compile_options(kraken PUBLIC -DDUMP_LEAKS=1) +target_compile_options(quickjs PUBLIC -DDUMP_LEAKS=1) +target_compile_options(kraken PUBLIC -DDUMP_LEAKS=1) - target_compile_definitions(kraken_unit_test PUBLIC -DFLUTTER_BACKEND=0) - target_compile_definitions(kraken_static PUBLIC -DFLUTTER_BACKEND=1) - if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) - set_target_properties(kraken_unit_test - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" - ) - endif() +target_compile_definitions(kraken_unit_test PUBLIC -DFLUTTER_BACKEND=0) +target_compile_definitions(kraken_unit_test PUBLIC -DSPEC_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") +target_compile_definitions(kraken_unit_test PUBLIC -DUNIT_TEST=1) + +target_compile_definitions(kraken_static PUBLIC -DFLUTTER_BACKEND=1) +if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) + set_target_properties(kraken_unit_test + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "$ENV{LIBRARY_OUTPUT_DIR}" + ) endif() -### kraken_integration support library +# Run Kraken integration without flutter. +add_executable(kraken_integration_test + ${KRAKEN_TEST_SOURCE} + ${BRIDGE_SOURCE} + ./test/kraken_test_env.cc + ./test/kraken_test_env.h + ./test/run_integration_test.cc + ) +target_include_directories(kraken_integration_test PUBLIC ./third_party/googletest/googletest/include ${BRIDGE_INCLUDE} ./test) +target_link_libraries(kraken_integration_test gtest gtest_main ${BRIDGE_LINK_LIBS}) +target_compile_definitions(kraken_integration_test PUBLIC -DFLUTTER_BACKEND=0) +target_compile_definitions(kraken_integration_test PUBLIC -DUNIT_TEST=1) +target_compile_definitions(kraken_integration_test PUBLIC -DSPEC_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") + +# Benchmark test +add_executable(kraken_benchmark + ${KRAKEN_TEST_SOURCE} + ${BRIDGE_SOURCE} + ./test/kraken_test_env.cc + ./test/kraken_test_env.h + ./test/benchmark/create_element.cc +) +target_include_directories(kraken_benchmark PUBLIC + ./third_party/googletest/googletest/include + ./third_party/benchmark/include/ + ${BRIDGE_INCLUDE} + ./test) +target_link_libraries(kraken_benchmark gtest gtest_main benchmark::benchmark ${BRIDGE_LINK_LIBS}) +target_compile_definitions(kraken_benchmark PUBLIC -DFLUTTER_BACKEND=0) +target_compile_definitions(kraken_benchmark PUBLIC -DUNIT_TEST=1) + +# Built libkraken_test.dylib library for integration test with flutter. add_library(kraken_test SHARED ${KRAKEN_TEST_SOURCE}) target_link_libraries(kraken_test PRIVATE ${BRIDGE_LINK_LIBS} kraken) target_include_directories(kraken_test PRIVATE diff --git a/bridge/third_party/benchmark/AUTHORS b/bridge/third_party/benchmark/AUTHORS new file mode 100644 index 0000000000..54770f3549 --- /dev/null +++ b/bridge/third_party/benchmark/AUTHORS @@ -0,0 +1,62 @@ +# This is the official list of benchmark authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. +# +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. +# +# Please keep the list sorted. + +Albert Pretorius +Alex Steele +Andriy Berestovskyy +Arne Beer +Carto +Christian Wassermann +Christopher Seymour +Colin Braley +Daniel Harvey +David Coeurjolly +Deniz Evrenci +Dirac Research +Dominik Czarnota +Dominik Korman +Donald Aingworth +Eric Backus +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Federico Ficarelli +Felix Homann +Gergő Szitár +Google Inc. +International Business Machines Corporation +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +Jordan Williams +Jussi Knuuttila +Kaito Udagawa +Kishan Kumar +Lei Xu +Matt Clarkson +Maxim Vafin +MongoDB Inc. +Nick Hutchinson +Norman Heino +Oleksandr Sochka +Ori Livneh +Paul Redmond +Radoslav Yovchev +Roman Lebedev +Sayan Bhattacharjee +Shuo Chen +Steinar H. Gunderson +Stripe, Inc. +Tobias Schmidt +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron +Min-Yih Hsu diff --git a/bridge/third_party/benchmark/BUILD.bazel b/bridge/third_party/benchmark/BUILD.bazel new file mode 100644 index 0000000000..904c691d64 --- /dev/null +++ b/bridge/third_party/benchmark/BUILD.bazel @@ -0,0 +1,52 @@ +licenses(["notice"]) + +config_setting( + name = "qnx", + constraint_values = ["@platforms//os:qnx"], + values = { + "cpu": "x64_qnx", + }, + visibility = [":__subpackages__"], +) + +config_setting( + name = "windows", + constraint_values = ["@platforms//os:windows"], + values = { + "cpu": "x64_windows", + }, + visibility = [":__subpackages__"], +) + +cc_library( + name = "benchmark", + srcs = glob( + [ + "src/*.cc", + "src/*.h", + ], + exclude = ["src/benchmark_main.cc"], + ), + hdrs = ["include/benchmark/benchmark.h"], + linkopts = select({ + ":windows": ["-DEFAULTLIB:shlwapi.lib"], + "//conditions:default": ["-pthread"], + }), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +cc_library( + name = "benchmark_main", + srcs = ["src/benchmark_main.cc"], + hdrs = ["include/benchmark/benchmark.h"], + strip_include_prefix = "include", + visibility = ["//visibility:public"], + deps = [":benchmark"], +) + +cc_library( + name = "benchmark_internal_headers", + hdrs = glob(["src/*.h"]), + visibility = ["//test:__pkg__"], +) diff --git a/bridge/third_party/benchmark/CMakeLists.txt b/bridge/third_party/benchmark/CMakeLists.txt new file mode 100644 index 0000000000..8af49406d0 --- /dev/null +++ b/bridge/third_party/benchmark/CMakeLists.txt @@ -0,0 +1,342 @@ +cmake_minimum_required (VERSION 3.5.1) + +foreach(p + CMP0048 # OK to clear PROJECT_VERSION on project() + CMP0054 # CMake 3.1 + CMP0056 # export EXE_LINKER_FLAGS to try_run + CMP0057 # Support no if() IN_LIST operator + CMP0063 # Honor visibility properties for all targets + CMP0077 # Allow option() overrides in importing projects + ) + if(POLICY ${p}) + cmake_policy(SET ${p} NEW) + endif() +endforeach() + +project (benchmark VERSION 1.6.0 LANGUAGES CXX) + +option(BENCHMARK_ENABLE_TESTING "Enable testing of the benchmark library." ON) +option(BENCHMARK_ENABLE_EXCEPTIONS "Enable the use of exceptions in the benchmark library." ON) +option(BENCHMARK_ENABLE_LTO "Enable link time optimisation of the benchmark library." OFF) +option(BENCHMARK_USE_LIBCXX "Build and test using libc++ as the standard library." OFF) +option(BENCHMARK_ENABLE_WERROR "Build Release candidates with -Werror." ON) +option(BENCHMARK_FORCE_WERROR "Build Release candidates with -Werror regardless of compiler issues." OFF) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "PGI") + # PGC++ maybe reporting false positives. + set(BENCHMARK_ENABLE_WERROR OFF) +endif() +if(BENCHMARK_FORCE_WERROR) + set(BENCHMARK_ENABLE_WERROR ON) +endif(BENCHMARK_FORCE_WERROR) + +if(NOT MSVC) + option(BENCHMARK_BUILD_32_BITS "Build a 32 bit version of the library." OFF) +else() + set(BENCHMARK_BUILD_32_BITS OFF CACHE BOOL "Build a 32 bit version of the library - unsupported when using MSVC)" FORCE) +endif() +option(BENCHMARK_ENABLE_INSTALL "Enable installation of benchmark. (Projects embedding benchmark may want to turn this OFF.)" ON) +option(BENCHMARK_ENABLE_DOXYGEN "Build documentation with Doxygen." OFF) +option(BENCHMARK_INSTALL_DOCS "Enable installation of documentation." ON) + +# Allow unmet dependencies to be met using CMake's ExternalProject mechanics, which +# may require downloading the source code. +option(BENCHMARK_DOWNLOAD_DEPENDENCIES "Allow the downloading and in-tree building of unmet dependencies" OFF) + +# This option can be used to disable building and running unit tests which depend on gtest +# in cases where it is not possible to build or find a valid version of gtest. +option(BENCHMARK_ENABLE_GTEST_TESTS "Enable building the unit tests which depend on gtest" ON) +option(BENCHMARK_USE_BUNDLED_GTEST "Use bundled GoogleTest. If disabled, the find_package(GTest) will be used." ON) + +option(BENCHMARK_ENABLE_LIBPFM "Enable performance counters provided by libpfm" OFF) + +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +if(MSVC) + # As of CMake 3.18, CMAKE_SYSTEM_PROCESSOR is not set properly for MSVC and + # cross-compilation (e.g. Host=x86_64, target=aarch64) requires using the + # undocumented, but working variable. + # See https://gitlab.kitware.com/cmake/cmake/-/issues/15170 + set(CMAKE_SYSTEM_PROCESSOR ${MSVC_CXX_ARCHITECTURE_ID}) + if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "ARM") + set(CMAKE_CROSSCOMPILING TRUE) + endif() +endif() + +set(ENABLE_ASSEMBLY_TESTS_DEFAULT OFF) +function(should_enable_assembly_tests) + if(CMAKE_BUILD_TYPE) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER) + if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage") + # FIXME: The --coverage flag needs to be removed when building assembly + # tests for this to work. + return() + endif() + endif() + if (MSVC) + return() + elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + return() + elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + # FIXME: Make these work on 32 bit builds + return() + elseif(BENCHMARK_BUILD_32_BITS) + # FIXME: Make these work on 32 bit builds + return() + endif() + find_program(LLVM_FILECHECK_EXE FileCheck) + if (LLVM_FILECHECK_EXE) + set(LLVM_FILECHECK_EXE "${LLVM_FILECHECK_EXE}" CACHE PATH "llvm filecheck" FORCE) + message(STATUS "LLVM FileCheck Found: ${LLVM_FILECHECK_EXE}") + else() + message(STATUS "Failed to find LLVM FileCheck") + return() + endif() + set(ENABLE_ASSEMBLY_TESTS_DEFAULT ON PARENT_SCOPE) +endfunction() +should_enable_assembly_tests() + +# This option disables the building and running of the assembly verification tests +option(BENCHMARK_ENABLE_ASSEMBLY_TESTS "Enable building and running the assembly tests" + ${ENABLE_ASSEMBLY_TESTS_DEFAULT}) + +# Make sure we can import out CMake functions +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + + +# Read the git tags to determine the project version +include(GetGitVersion) +get_git_version(GIT_VERSION) + +# If no git version can be determined, use the version +# from the project() command +if ("${GIT_VERSION}" STREQUAL "0.0.0") + set(VERSION "${benchmark_VERSION}") +else() + set(VERSION "${GIT_VERSION}") +endif() +# Tell the user what versions we are using +message(STATUS "Version: ${VERSION}") + +# The version of the libraries +set(GENERIC_LIB_VERSION ${VERSION}) +string(SUBSTRING ${VERSION} 0 1 GENERIC_LIB_SOVERSION) + +# Import our CMake modules +include(CheckCXXCompilerFlag) +include(AddCXXCompilerFlag) +include(CXXFeatureCheck) +include(CheckLibraryExists) + +check_library_exists(rt shm_open "" HAVE_LIB_RT) + +if (BENCHMARK_BUILD_32_BITS) + add_required_cxx_compiler_flag(-m32) +endif() + +if (MSVC) + # Turn compiler warnings up to 11 + string(REGEX REPLACE "[-/]W[1-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + + if (NOT BENCHMARK_ENABLE_EXCEPTIONS) + add_cxx_compiler_flag(-EHs-) + add_cxx_compiler_flag(-EHa-) + add_definitions(-D_HAS_EXCEPTIONS=0) + endif() + # Link time optimisation + if (BENCHMARK_ENABLE_LTO) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /GL") + set(CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL "${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL} /LTCG") + set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /LTCG") + endif() +else() + # Try and enable C++11. Don't use C++14 because it doesn't work in some + # configurations. + add_cxx_compiler_flag(-std=c++11) + if (NOT HAVE_CXX_FLAG_STD_CXX11) + add_cxx_compiler_flag(-std=c++0x) + endif() + + # Turn compiler warnings up to 11 + add_cxx_compiler_flag(-Wall) + add_cxx_compiler_flag(-Wextra) + add_cxx_compiler_flag(-Wshadow) + if(BENCHMARK_ENABLE_WERROR) + add_cxx_compiler_flag(-Werror RELEASE) + add_cxx_compiler_flag(-Werror RELWITHDEBINFO) + add_cxx_compiler_flag(-Werror MINSIZEREL) + endif() + if (NOT BENCHMARK_ENABLE_TESTING) + # Disable warning when compiling tests as gtest does not use 'override'. + add_cxx_compiler_flag(-Wsuggest-override) + endif() + add_cxx_compiler_flag(-pedantic) + add_cxx_compiler_flag(-pedantic-errors) + add_cxx_compiler_flag(-Wshorten-64-to-32) + add_cxx_compiler_flag(-fstrict-aliasing) + # Disable warnings regarding deprecated parts of the library while building + # and testing those parts of the library. + add_cxx_compiler_flag(-Wno-deprecated-declarations) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + # Intel silently ignores '-Wno-deprecated-declarations', + # warning no. 1786 must be explicitly disabled. + # See #631 for rationale. + add_cxx_compiler_flag(-wd1786) + endif() + # Disable deprecation warnings for release builds (when -Werror is enabled). + if(BENCHMARK_ENABLE_WERROR) + add_cxx_compiler_flag(-Wno-deprecated RELEASE) + add_cxx_compiler_flag(-Wno-deprecated RELWITHDEBINFO) + add_cxx_compiler_flag(-Wno-deprecated MINSIZEREL) + endif() + if (NOT BENCHMARK_ENABLE_EXCEPTIONS) + add_cxx_compiler_flag(-fno-exceptions) + endif() + + if (HAVE_CXX_FLAG_FSTRICT_ALIASING) + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") #ICC17u2: Many false positives for Wstrict-aliasing + add_cxx_compiler_flag(-Wstrict-aliasing) + endif() + endif() + # ICC17u2: overloaded virtual function "benchmark::Fixture::SetUp" is only partially overridden + # (because of deprecated overload) + add_cxx_compiler_flag(-wd654) + add_cxx_compiler_flag(-Wthread-safety) + if (HAVE_CXX_FLAG_WTHREAD_SAFETY) + cxx_feature_check(THREAD_SAFETY_ATTRIBUTES) + endif() + + # On most UNIX like platforms g++ and clang++ define _GNU_SOURCE as a + # predefined macro, which turns on all of the wonderful libc extensions. + # However g++ doesn't do this in Cygwin so we have to define it ourselfs + # since we depend on GNU/POSIX/BSD extensions. + if (CYGWIN) + add_definitions(-D_GNU_SOURCE=1) + endif() + + if (QNXNTO) + add_definitions(-D_QNX_SOURCE) + endif() + + # Link time optimisation + if (BENCHMARK_ENABLE_LTO) + add_cxx_compiler_flag(-flto) + add_cxx_compiler_flag(-Wno-lto-type-mismatch) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + find_program(GCC_AR gcc-ar) + if (GCC_AR) + set(CMAKE_AR ${GCC_AR}) + endif() + find_program(GCC_RANLIB gcc-ranlib) + if (GCC_RANLIB) + set(CMAKE_RANLIB ${GCC_RANLIB}) + endif() + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + include(llvm-toolchain) + endif() + endif() + + # Coverage build type + set(BENCHMARK_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG}" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE) + set(BENCHMARK_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG}" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE) + set(BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE) + mark_as_advanced( + BENCHMARK_CXX_FLAGS_COVERAGE + BENCHMARK_EXE_LINKER_FLAGS_COVERAGE + BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE) + set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage.") + add_cxx_compiler_flag(--coverage COVERAGE) +endif() + +if (BENCHMARK_USE_LIBCXX) + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_cxx_compiler_flag(-stdlib=libc++) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR + "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") + add_cxx_compiler_flag(-nostdinc++) + message(WARNING "libc++ header path must be manually specified using CMAKE_CXX_FLAGS") + # Adding -nodefaultlibs directly to CMAKE__LINKER_FLAGS will break + # configuration checks such as 'find_package(Threads)' + list(APPEND BENCHMARK_CXX_LINKER_FLAGS -nodefaultlibs) + # -lc++ cannot be added directly to CMAKE__LINKER_FLAGS because + # linker flags appear before all linker inputs and -lc++ must appear after. + list(APPEND BENCHMARK_CXX_LIBRARIES c++) + else() + message(FATAL_ERROR "-DBENCHMARK_USE_LIBCXX:BOOL=ON is not supported for compiler") + endif() +endif(BENCHMARK_USE_LIBCXX) + +set(EXTRA_CXX_FLAGS "") +if (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + # Clang on Windows fails to compile the regex feature check under C++11 + set(EXTRA_CXX_FLAGS "-DCMAKE_CXX_STANDARD=14") +endif() + +# C++ feature checks +# Determine the correct regular expression engine to use +cxx_feature_check(STD_REGEX ${EXTRA_CXX_FLAGS}) +cxx_feature_check(GNU_POSIX_REGEX ${EXTRA_CXX_FLAGS}) +cxx_feature_check(POSIX_REGEX ${EXTRA_CXX_FLAGS}) +if(NOT HAVE_STD_REGEX AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX) + message(FATAL_ERROR "Failed to determine the source files for the regular expression backend") +endif() +if (NOT BENCHMARK_ENABLE_EXCEPTIONS AND HAVE_STD_REGEX + AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX) + message(WARNING "Using std::regex with exceptions disabled is not fully supported") +endif() + +cxx_feature_check(STEADY_CLOCK) +# Ensure we have pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +if (BENCHMARK_ENABLE_LIBPFM) + find_package(PFM) +endif() + +# Set up directories +include_directories(${PROJECT_SOURCE_DIR}/include) + +# Build the targets +add_subdirectory(src) + +if (BENCHMARK_ENABLE_TESTING) + enable_testing() + if (BENCHMARK_ENABLE_GTEST_TESTS AND + NOT (TARGET gtest AND TARGET gtest_main AND + TARGET gmock AND TARGET gmock_main)) + if (BENCHMARK_USE_BUNDLED_GTEST) + include(GoogleTest) + else() + find_package(GTest CONFIG REQUIRED) + add_library(gtest ALIAS GTest::gtest) + add_library(gtest_main ALIAS GTest::gtest_main) + add_library(gmock ALIAS GTest::gmock) + add_library(gmock_main ALIAS GTest::gmock_main) + endif() + endif() + add_subdirectory(test) +endif() diff --git a/bridge/third_party/benchmark/CONTRIBUTING.md b/bridge/third_party/benchmark/CONTRIBUTING.md new file mode 100644 index 0000000000..43de4c9d47 --- /dev/null +++ b/bridge/third_party/benchmark/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# How to contribute # + +We'd love to accept your patches and contributions to this project. There are +a just a few small guidelines you need to follow. + + +## Contributor License Agreement ## + +Contributions to any Google project must be accompanied by a Contributor +License Agreement. This is not a copyright **assignment**, it simply gives +Google permission to use and redistribute your contributions as part of the +project. + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual + CLA][]. + + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA][]. + +You generally only need to submit a CLA once, so if you've already submitted +one (even if it was for a different project), you probably don't need to do it +again. + +[individual CLA]: https://developers.google.com/open-source/cla/individual +[corporate CLA]: https://developers.google.com/open-source/cla/corporate + +Once your CLA is submitted (or if you already submitted one for +another Google project), make a commit adding yourself to the +[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part +of your first [pull request][]. + +[AUTHORS]: AUTHORS +[CONTRIBUTORS]: CONTRIBUTORS + + +## Submitting a patch ## + + 1. It's generally best to start by opening a new issue describing the bug or + feature you're intending to fix. Even if you think it's relatively minor, + it's helpful to know what people are working on. Mention in the initial + issue that you are planning to work on that bug or feature so that it can + be assigned to you. + + 1. Follow the normal process of [forking][] the project, and setup a new + branch to work in. It's important that each group of changes be done in + separate branches in order to ensure that a pull request only includes the + commits related to that bug or feature. + + 1. Do your best to have [well-formed commit messages][] for each change. + This provides consistency throughout the project, and ensures that commit + messages are able to be formatted properly by various git tools. + + 1. Finally, push the commits to your fork and submit a [pull request][]. + +[forking]: https://help.github.com/articles/fork-a-repo +[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[pull request]: https://help.github.com/articles/creating-a-pull-request diff --git a/bridge/third_party/benchmark/CONTRIBUTORS b/bridge/third_party/benchmark/CONTRIBUTORS new file mode 100644 index 0000000000..651fbeafe6 --- /dev/null +++ b/bridge/third_party/benchmark/CONTRIBUTORS @@ -0,0 +1,87 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. +# +# Names should be added to this file as: +# Name +# +# Please keep the list sorted. + +Abhina Sreeskantharajan +Albert Pretorius +Alex Steele +Andriy Berestovskyy +Arne Beer +Billy Robert O'Neal III +Chris Kennelly +Christian Wassermann +Christopher Seymour +Colin Braley +Cyrille Faucheux +Daniel Harvey +David Coeurjolly +Deniz Evrenci +Dominic Hamon +Dominik Czarnota +Dominik Korman +Donald Aingworth +Eric Backus +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Fanbo Meng +Federico Ficarelli +Felix Homann +Geoffrey Martin-Noble +Gergő Szitár +Hannes Hauswedell +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +John Millikin +Jordan Williams +Jussi Knuuttila +Kai Wolf +Kaito Udagawa +Kishan Kumar +Lei Xu +Matt Clarkson +Maxim Vafin +Nick Hutchinson +Norman Heino +Oleksandr Sochka +Ori Livneh +Pascal Leroy +Paul Redmond +Pierre Phaneuf +Radoslav Yovchev +Raul Marin +Ray Glover +Robert Guo +Roman Lebedev +Sayan Bhattacharjee +Shuo Chen +Steven Wan +Tobias Schmidt +Tobias Ulvgård +Tom Madams +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron +Min-Yih Hsu diff --git a/bridge/third_party/benchmark/LICENSE b/bridge/third_party/benchmark/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/bridge/third_party/benchmark/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bridge/third_party/benchmark/README.md b/bridge/third_party/benchmark/README.md new file mode 100644 index 0000000000..7b81d960fc --- /dev/null +++ b/bridge/third_party/benchmark/README.md @@ -0,0 +1,216 @@ +# Benchmark + +[![build-and-test](https://github.com/google/benchmark/workflows/build-and-test/badge.svg)](https://github.com/google/benchmark/actions?query=workflow%3Abuild-and-test) +[![bazel](https://github.com/google/benchmark/actions/workflows/bazel.yml/badge.svg)](https://github.com/google/benchmark/actions/workflows/bazel.yml) +[![pylint](https://github.com/google/benchmark/workflows/pylint/badge.svg)](https://github.com/google/benchmark/actions?query=workflow%3Apylint) +[![test-bindings](https://github.com/google/benchmark/workflows/test-bindings/badge.svg)](https://github.com/google/benchmark/actions?query=workflow%3Atest-bindings) + +[![Build Status](https://travis-ci.org/google/benchmark.svg?branch=master)](https://travis-ci.org/google/benchmark) +[![Coverage Status](https://coveralls.io/repos/google/benchmark/badge.svg)](https://coveralls.io/r/google/benchmark) + + +A library to benchmark code snippets, similar to unit tests. Example: + +```c++ +#include + +static void BM_SomeFunction(benchmark::State& state) { + // Perform setup here + for (auto _ : state) { + // This code gets timed + SomeFunction(); + } +} +// Register the function as a benchmark +BENCHMARK(BM_SomeFunction); +// Run the benchmark +BENCHMARK_MAIN(); +``` + +## Getting Started + +To get started, see [Requirements](#requirements) and +[Installation](#installation). See [Usage](#usage) for a full example and the +[User Guide](docs/user_guide.md) for a more comprehensive feature overview. + +It may also help to read the [Google Test documentation](https://github.com/google/googletest/blob/master/docs/primer.md) +as some of the structural aspects of the APIs are similar. + +## Resources + +[Discussion group](https://groups.google.com/d/forum/benchmark-discuss) + +IRC channels: +* [libera](https://libera.chat) #benchmark + +[Additional Tooling Documentation](docs/tools.md) + +[Assembly Testing Documentation](docs/AssemblyTests.md) + +## Requirements + +The library can be used with C++03. However, it requires C++11 to build, +including compiler and standard library support. + +The following minimum versions are required to build the library: + +* GCC 4.8 +* Clang 3.4 +* Visual Studio 14 2015 +* Intel 2015 Update 1 + +See [Platform-Specific Build Instructions](docs/platform_specific_build_instructions.md). + +## Installation + +This describes the installation process using cmake. As pre-requisites, you'll +need git and cmake installed. + +_See [dependencies.md](docs/dependencies.md) for more details regarding supported +versions of build tools._ + +```bash +# Check out the library. +$ git clone https://github.com/google/benchmark.git +# Go to the library root directory +$ cd benchmark +# Make a build directory to place the build output. +$ cmake -E make_directory "build" +# Generate build system files with cmake, and download any dependencies. +$ cmake -E chdir "build" cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release ../ +# or, starting with CMake 3.13, use a simpler form: +# cmake -DCMAKE_BUILD_TYPE=Release -S . -B "build" +# Build the library. +$ cmake --build "build" --config Release +``` +This builds the `benchmark` and `benchmark_main` libraries and tests. +On a unix system, the build directory should now look something like this: + +``` +/benchmark + /build + /src + /libbenchmark.a + /libbenchmark_main.a + /test + ... +``` + +Next, you can run the tests to check the build. + +```bash +$ cmake -E chdir "build" ctest --build-config Release +``` + +If you want to install the library globally, also run: + +``` +sudo cmake --build "build" --config Release --target install +``` + +Note that Google Benchmark requires Google Test to build and run the tests. This +dependency can be provided two ways: + +* Checkout the Google Test sources into `benchmark/googletest`. +* Otherwise, if `-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON` is specified during + configuration as above, the library will automatically download and build + any required dependencies. + +If you do not wish to build and run the tests, add `-DBENCHMARK_ENABLE_GTEST_TESTS=OFF` +to `CMAKE_ARGS`. + +### Debug vs Release + +By default, benchmark builds as a debug library. You will see a warning in the +output when this is the case. To build it as a release library instead, add +`-DCMAKE_BUILD_TYPE=Release` when generating the build system files, as shown +above. The use of `--config Release` in build commands is needed to properly +support multi-configuration tools (like Visual Studio for example) and can be +skipped for other build systems (like Makefile). + +To enable link-time optimisation, also add `-DBENCHMARK_ENABLE_LTO=true` when +generating the build system files. + +If you are using gcc, you might need to set `GCC_AR` and `GCC_RANLIB` cmake +cache variables, if autodetection fails. + +If you are using clang, you may need to set `LLVMAR_EXECUTABLE`, +`LLVMNM_EXECUTABLE` and `LLVMRANLIB_EXECUTABLE` cmake cache variables. + +### Stable and Experimental Library Versions + +The main branch contains the latest stable version of the benchmarking library; +the API of which can be considered largely stable, with source breaking changes +being made only upon the release of a new major version. + +Newer, experimental, features are implemented and tested on the +[`v2` branch](https://github.com/google/benchmark/tree/v2). Users who wish +to use, test, and provide feedback on the new features are encouraged to try +this branch. However, this branch provides no stability guarantees and reserves +the right to change and break the API at any time. + +## Usage + +### Basic usage + +Define a function that executes the code to measure, register it as a benchmark +function using the `BENCHMARK` macro, and ensure an appropriate `main` function +is available: + +```c++ +#include + +static void BM_StringCreation(benchmark::State& state) { + for (auto _ : state) + std::string empty_string; +} +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + for (auto _ : state) + std::string copy(x); +} +BENCHMARK(BM_StringCopy); + +BENCHMARK_MAIN(); +``` + +To run the benchmark, compile and link against the `benchmark` library +(libbenchmark.a/.so). If you followed the build steps above, this library will +be under the build directory you created. + +```bash +# Example on linux after running the build steps above. Assumes the +# `benchmark` and `build` directories are under the current directory. +$ g++ mybenchmark.cc -std=c++11 -isystem benchmark/include \ + -Lbenchmark/build/src -lbenchmark -lpthread -o mybenchmark +``` + +Alternatively, link against the `benchmark_main` library and remove +`BENCHMARK_MAIN();` above to get the same behavior. + +The compiled executable will run all benchmarks by default. Pass the `--help` +flag for option information or see the [User Guide](docs/user_guide.md). + +### Usage with CMake + +If using CMake, it is recommended to link against the project-provided +`benchmark::benchmark` and `benchmark::benchmark_main` targets using +`target_link_libraries`. +It is possible to use ```find_package``` to import an installed version of the +library. +```cmake +find_package(benchmark REQUIRED) +``` +Alternatively, ```add_subdirectory``` will incorporate the library directly in +to one's CMake project. +```cmake +add_subdirectory(benchmark) +``` +Either way, link to the library as follows. +```cmake +target_link_libraries(MyTarget benchmark::benchmark) +``` diff --git a/bridge/third_party/benchmark/WORKSPACE b/bridge/third_party/benchmark/WORKSPACE new file mode 100644 index 0000000000..0531d99d57 --- /dev/null +++ b/bridge/third_party/benchmark/WORKSPACE @@ -0,0 +1,44 @@ +workspace(name = "com_github_google_benchmark") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "com_google_absl", + sha256 = "f41868f7a938605c92936230081175d1eae87f6ea2c248f41077c8f88316f111", + strip_prefix = "abseil-cpp-20200225.2", + urls = ["https://github.com/abseil/abseil-cpp/archive/20200225.2.tar.gz"], +) + +http_archive( + name = "com_google_googletest", + strip_prefix = "googletest-3f0cf6b62ad1eb50d8736538363d3580dd640c3e", + urls = ["https://github.com/google/googletest/archive/3f0cf6b62ad1eb50d8736538363d3580dd640c3e.zip"], + sha256 = "8f827dd550db8b4fdf73904690df0be9fccc161017c9038a724bc9a0617a1bc8", +) + +http_archive( + name = "pybind11", + build_file = "@//bindings/python:pybind11.BUILD", + sha256 = "1eed57bc6863190e35637290f97a20c81cfe4d9090ac0a24f3bbf08f265eb71d", + strip_prefix = "pybind11-2.4.3", + urls = ["https://github.com/pybind/pybind11/archive/v2.4.3.tar.gz"], +) + +new_local_repository( + name = "python_headers", + build_file = "@//bindings/python:python_headers.BUILD", + path = "/usr/include/python3.6", # May be overwritten by setup.py. +) + +http_archive( + name = "rules_python", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.1.0/rules_python-0.1.0.tar.gz", + sha256 = "b6d46438523a3ec0f3cead544190ee13223a52f6a6765a29eae7b7cc24cc83a0", +) + +load("@rules_python//python:pip.bzl", pip3_install="pip_install") + +pip3_install( + name = "py_deps", + requirements = "//:requirements.txt", +) diff --git a/bridge/third_party/benchmark/_config.yml b/bridge/third_party/benchmark/_config.yml new file mode 100644 index 0000000000..1fa5ff852b --- /dev/null +++ b/bridge/third_party/benchmark/_config.yml @@ -0,0 +1,2 @@ +theme: jekyll-theme-midnight +markdown: GFM diff --git a/bridge/third_party/benchmark/appveyor.yml b/bridge/third_party/benchmark/appveyor.yml new file mode 100644 index 0000000000..81da955f02 --- /dev/null +++ b/bridge/third_party/benchmark/appveyor.yml @@ -0,0 +1,50 @@ +version: '{build}' + +image: Visual Studio 2017 + +configuration: + - Debug + - Release + +environment: + matrix: + - compiler: msvc-15-seh + generator: "Visual Studio 15 2017" + + - compiler: msvc-15-seh + generator: "Visual Studio 15 2017 Win64" + + - compiler: msvc-14-seh + generator: "Visual Studio 14 2015" + + - compiler: msvc-14-seh + generator: "Visual Studio 14 2015 Win64" + + - compiler: gcc-5.3.0-posix + generator: "MinGW Makefiles" + cxx_path: 'C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin' + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + +matrix: + fast_finish: true + +install: + # git bash conflicts with MinGW makefiles + - if "%generator%"=="MinGW Makefiles" (set "PATH=%PATH:C:\Program Files\Git\usr\bin;=%") + - if not "%cxx_path%"=="" (set "PATH=%PATH%;%cxx_path%") + +build_script: + - md _build -Force + - cd _build + - echo %configuration% + - cmake -G "%generator%" "-DCMAKE_BUILD_TYPE=%configuration%" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON .. + - cmake --build . --config %configuration% + +test_script: + - ctest --build-config %configuration% --timeout 300 --output-on-failure + +artifacts: + - path: '_build/CMakeFiles/*.log' + name: logs + - path: '_build/Testing/**/*.xml' + name: test_results diff --git a/bridge/third_party/benchmark/bindings/python/build_defs.bzl b/bridge/third_party/benchmark/bindings/python/build_defs.bzl new file mode 100644 index 0000000000..45907aaa5e --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/build_defs.bzl @@ -0,0 +1,25 @@ +_SHARED_LIB_SUFFIX = { + "//conditions:default": ".so", + "//:windows": ".dll", +} + +def py_extension(name, srcs, hdrs = [], copts = [], features = [], deps = []): + for shared_lib_suffix in _SHARED_LIB_SUFFIX.values(): + shared_lib_name = name + shared_lib_suffix + native.cc_binary( + name = shared_lib_name, + linkshared = 1, + linkstatic = 1, + srcs = srcs + hdrs, + copts = copts, + features = features, + deps = deps, + ) + + return native.py_library( + name = name, + data = select({ + platform: [name + shared_lib_suffix] + for platform, shared_lib_suffix in _SHARED_LIB_SUFFIX.items() + }), + ) diff --git a/bridge/third_party/benchmark/bindings/python/google_benchmark/__init__.py b/bridge/third_party/benchmark/bindings/python/google_benchmark/__init__.py new file mode 100644 index 0000000000..1055bf2418 --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/google_benchmark/__init__.py @@ -0,0 +1,158 @@ +# Copyright 2020 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Python benchmarking utilities. + +Example usage: + import google_benchmark as benchmark + + @benchmark.register + def my_benchmark(state): + ... # Code executed outside `while` loop is not timed. + + while state: + ... # Code executed within `while` loop is timed. + + if __name__ == '__main__': + benchmark.main() +""" + +from absl import app +from google_benchmark import _benchmark +from google_benchmark._benchmark import ( + Counter, + kNanosecond, + kMicrosecond, + kMillisecond, + kSecond, + oNone, + o1, + oN, + oNSquared, + oNCubed, + oLogN, + oNLogN, + oAuto, + oLambda, +) + + +__all__ = [ + "register", + "main", + "Counter", + "kNanosecond", + "kMicrosecond", + "kMillisecond", + "kSecond", + "oNone", + "o1", + "oN", + "oNSquared", + "oNCubed", + "oLogN", + "oNLogN", + "oAuto", + "oLambda", +] + +__version__ = "0.2.0" + + +class __OptionMaker: + """A stateless class to collect benchmark options. + + Collect all decorator calls like @option.range(start=0, limit=1<<5). + """ + + class Options: + """Pure data class to store options calls, along with the benchmarked function.""" + + def __init__(self, func): + self.func = func + self.builder_calls = [] + + @classmethod + def make(cls, func_or_options): + """Make Options from Options or the benchmarked function.""" + if isinstance(func_or_options, cls.Options): + return func_or_options + return cls.Options(func_or_options) + + def __getattr__(self, builder_name): + """Append option call in the Options.""" + + # The function that get returned on @option.range(start=0, limit=1<<5). + def __builder_method(*args, **kwargs): + + # The decorator that get called, either with the benchmared function + # or the previous Options + def __decorator(func_or_options): + options = self.make(func_or_options) + options.builder_calls.append((builder_name, args, kwargs)) + # The decorator returns Options so it is not technically a decorator + # and needs a final call to @regiser + return options + + return __decorator + + return __builder_method + + +# Alias for nicer API. +# We have to instantiate an object, even if stateless, to be able to use __getattr__ +# on option.range +option = __OptionMaker() + + +def register(undefined=None, *, name=None): + """Register function for benchmarking.""" + if undefined is None: + # Decorator is called without parenthesis so we return a decorator + return lambda f: register(f, name=name) + + # We have either the function to benchmark (simple case) or an instance of Options + # (@option._ case). + options = __OptionMaker.make(undefined) + + if name is None: + name = options.func.__name__ + + # We register the benchmark and reproduce all the @option._ calls onto the + # benchmark builder pattern + benchmark = _benchmark.RegisterBenchmark(name, options.func) + for name, args, kwargs in options.builder_calls[::-1]: + getattr(benchmark, name)(*args, **kwargs) + + # return the benchmarked function because the decorator does not modify it + return options.func + + +def _flags_parser(argv): + argv = _benchmark.Initialize(argv) + return app.parse_flags_with_usage(argv) + + +def _run_benchmarks(argv): + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + return _benchmark.RunSpecifiedBenchmarks() + + +def main(argv=None): + return app.run(_run_benchmarks, argv=argv, flags_parser=_flags_parser) + + +# Methods for use with custom main function. +initialize = _benchmark.Initialize +run_benchmarks = _benchmark.RunSpecifiedBenchmarks diff --git a/bridge/third_party/benchmark/bindings/python/google_benchmark/benchmark.cc b/bridge/third_party/benchmark/bindings/python/google_benchmark/benchmark.cc new file mode 100644 index 0000000000..02b6ed7ed5 --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/google_benchmark/benchmark.cc @@ -0,0 +1,181 @@ +// Benchmark for Python. + +#include +#include +#include + +#include "pybind11/operators.h" +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" +#include "pybind11/stl_bind.h" + +#include "benchmark/benchmark.h" + +PYBIND11_MAKE_OPAQUE(benchmark::UserCounters); + +namespace { +namespace py = ::pybind11; + +std::vector Initialize(const std::vector& argv) { + // The `argv` pointers here become invalid when this function returns, but + // benchmark holds the pointer to `argv[0]`. We create a static copy of it + // so it persists, and replace the pointer below. + static std::string executable_name(argv[0]); + std::vector ptrs; + ptrs.reserve(argv.size()); + for (auto& arg : argv) { + ptrs.push_back(const_cast(arg.c_str())); + } + ptrs[0] = const_cast(executable_name.c_str()); + int argc = static_cast(argv.size()); + benchmark::Initialize(&argc, ptrs.data()); + std::vector remaining_argv; + remaining_argv.reserve(argc); + for (int i = 0; i < argc; ++i) { + remaining_argv.emplace_back(ptrs[i]); + } + return remaining_argv; +} + +benchmark::internal::Benchmark* RegisterBenchmark(const char* name, + py::function f) { + return benchmark::RegisterBenchmark( + name, [f](benchmark::State& state) { f(&state); }); +} + +PYBIND11_MODULE(_benchmark, m) { + using benchmark::TimeUnit; + py::enum_(m, "TimeUnit") + .value("kNanosecond", TimeUnit::kNanosecond) + .value("kMicrosecond", TimeUnit::kMicrosecond) + .value("kMillisecond", TimeUnit::kMillisecond) + .value("kSecond", TimeUnit::kSecond) + .export_values(); + + using benchmark::BigO; + py::enum_(m, "BigO") + .value("oNone", BigO::oNone) + .value("o1", BigO::o1) + .value("oN", BigO::oN) + .value("oNSquared", BigO::oNSquared) + .value("oNCubed", BigO::oNCubed) + .value("oLogN", BigO::oLogN) + .value("oNLogN", BigO::oLogN) + .value("oAuto", BigO::oAuto) + .value("oLambda", BigO::oLambda) + .export_values(); + + using benchmark::internal::Benchmark; + py::class_(m, "Benchmark") + // For methods returning a pointer tor the current object, reference + // return policy is used to ask pybind not to take ownership oof the + // returned object and avoid calling delete on it. + // https://pybind11.readthedocs.io/en/stable/advanced/functions.html#return-value-policies + // + // For methods taking a const std::vector<...>&, a copy is created + // because a it is bound to a Python list. + // https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html + .def("unit", &Benchmark::Unit, py::return_value_policy::reference) + .def("arg", &Benchmark::Arg, py::return_value_policy::reference) + .def("args", &Benchmark::Args, py::return_value_policy::reference) + .def("range", &Benchmark::Range, py::return_value_policy::reference, + py::arg("start"), py::arg("limit")) + .def("dense_range", &Benchmark::DenseRange, + py::return_value_policy::reference, py::arg("start"), + py::arg("limit"), py::arg("step") = 1) + .def("ranges", &Benchmark::Ranges, py::return_value_policy::reference) + .def("args_product", &Benchmark::ArgsProduct, + py::return_value_policy::reference) + .def("arg_name", &Benchmark::ArgName, py::return_value_policy::reference) + .def("arg_names", &Benchmark::ArgNames, + py::return_value_policy::reference) + .def("range_pair", &Benchmark::RangePair, + py::return_value_policy::reference, py::arg("lo1"), py::arg("hi1"), + py::arg("lo2"), py::arg("hi2")) + .def("range_multiplier", &Benchmark::RangeMultiplier, + py::return_value_policy::reference) + .def("min_time", &Benchmark::MinTime, py::return_value_policy::reference) + .def("iterations", &Benchmark::Iterations, + py::return_value_policy::reference) + .def("repetitions", &Benchmark::Repetitions, + py::return_value_policy::reference) + .def("report_aggregates_only", &Benchmark::ReportAggregatesOnly, + py::return_value_policy::reference, py::arg("value") = true) + .def("display_aggregates_only", &Benchmark::DisplayAggregatesOnly, + py::return_value_policy::reference, py::arg("value") = true) + .def("measure_process_cpu_time", &Benchmark::MeasureProcessCPUTime, + py::return_value_policy::reference) + .def("use_real_time", &Benchmark::UseRealTime, + py::return_value_policy::reference) + .def("use_manual_time", &Benchmark::UseManualTime, + py::return_value_policy::reference) + .def( + "complexity", + (Benchmark * (Benchmark::*)(benchmark::BigO)) & Benchmark::Complexity, + py::return_value_policy::reference, + py::arg("complexity") = benchmark::oAuto); + + using benchmark::Counter; + py::class_ py_counter(m, "Counter"); + + py::enum_(py_counter, "Flags") + .value("kDefaults", Counter::Flags::kDefaults) + .value("kIsRate", Counter::Flags::kIsRate) + .value("kAvgThreads", Counter::Flags::kAvgThreads) + .value("kAvgThreadsRate", Counter::Flags::kAvgThreadsRate) + .value("kIsIterationInvariant", Counter::Flags::kIsIterationInvariant) + .value("kIsIterationInvariantRate", + Counter::Flags::kIsIterationInvariantRate) + .value("kAvgIterations", Counter::Flags::kAvgIterations) + .value("kAvgIterationsRate", Counter::Flags::kAvgIterationsRate) + .value("kInvert", Counter::Flags::kInvert) + .export_values() + .def(py::self | py::self); + + py::enum_(py_counter, "OneK") + .value("kIs1000", Counter::OneK::kIs1000) + .value("kIs1024", Counter::OneK::kIs1024) + .export_values(); + + py_counter + .def(py::init(), + py::arg("value") = 0., py::arg("flags") = Counter::kDefaults, + py::arg("k") = Counter::kIs1000) + .def(py::init([](double value) { return Counter(value); })) + .def_readwrite("value", &Counter::value) + .def_readwrite("flags", &Counter::flags) + .def_readwrite("oneK", &Counter::oneK); + py::implicitly_convertible(); + py::implicitly_convertible(); + + py::bind_map(m, "UserCounters"); + + using benchmark::State; + py::class_(m, "State") + .def("__bool__", &State::KeepRunning) + .def_property_readonly("keep_running", &State::KeepRunning) + .def("pause_timing", &State::PauseTiming) + .def("resume_timing", &State::ResumeTiming) + .def("skip_with_error", &State::SkipWithError) + .def_property_readonly("error_occurred", &State::error_occurred) + .def("set_iteration_time", &State::SetIterationTime) + .def_property("bytes_processed", &State::bytes_processed, + &State::SetBytesProcessed) + .def_property("complexity_n", &State::complexity_length_n, + &State::SetComplexityN) + .def_property("items_processed", &State::items_processed, + &State::SetItemsProcessed) + .def("set_label", (void(State::*)(const char*)) & State::SetLabel) + .def("range", &State::range, py::arg("pos") = 0) + .def_property_readonly("iterations", &State::iterations) + .def_readwrite("counters", &State::counters) + .def_property_readonly("thread_index", &State::thread_index) + .def_property_readonly("threads", &State::threads); + + m.def("Initialize", Initialize); + m.def("RegisterBenchmark", RegisterBenchmark, + py::return_value_policy::reference); + m.def("RunSpecifiedBenchmarks", + []() { benchmark::RunSpecifiedBenchmarks(); }); +}; +} // namespace diff --git a/bridge/third_party/benchmark/bindings/python/google_benchmark/example.py b/bridge/third_party/benchmark/bindings/python/google_benchmark/example.py new file mode 100644 index 0000000000..487acc9f1e --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/google_benchmark/example.py @@ -0,0 +1,136 @@ +# Copyright 2020 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Example of Python using C++ benchmark framework. + +To run this example, you must first install the `google_benchmark` Python package. + +To install using `setup.py`, download and extract the `google_benchmark` source. +In the extracted directory, execute: + python setup.py install +""" + +import random +import time + +import google_benchmark as benchmark +from google_benchmark import Counter + + +@benchmark.register +def empty(state): + while state: + pass + + +@benchmark.register +def sum_million(state): + while state: + sum(range(1_000_000)) + +@benchmark.register +def pause_timing(state): + """Pause timing every iteration.""" + while state: + # Construct a list of random ints every iteration without timing it + state.pause_timing() + random_list = [random.randint(0, 100) for _ in range(100)] + state.resume_timing() + # Time the in place sorting algorithm + random_list.sort() + + +@benchmark.register +def skipped(state): + if True: # Test some predicate here. + state.skip_with_error("some error") + return # NOTE: You must explicitly return, or benchmark will continue. + + ... # Benchmark code would be here. + + +@benchmark.register +def manual_timing(state): + while state: + # Manually count Python CPU time + start = time.perf_counter() # perf_counter_ns() in Python 3.7+ + # Something to benchmark + time.sleep(0.01) + end = time.perf_counter() + state.set_iteration_time(end - start) + + +@benchmark.register +def custom_counters(state): + """Collect cutom metric using benchmark.Counter.""" + num_foo = 0.0 + while state: + # Benchmark some code here + pass + # Collect some custom metric named foo + num_foo += 0.13 + + # Automatic Counter from numbers. + state.counters["foo"] = num_foo + # Set a counter as a rate. + state.counters["foo_rate"] = Counter(num_foo, Counter.kIsRate) + # Set a counter as an inverse of rate. + state.counters["foo_inv_rate"] = Counter(num_foo, Counter.kIsRate | Counter.kInvert) + # Set a counter as a thread-average quantity. + state.counters["foo_avg"] = Counter(num_foo, Counter.kAvgThreads) + # There's also a combined flag: + state.counters["foo_avg_rate"] = Counter(num_foo, Counter.kAvgThreadsRate) + + +@benchmark.register +@benchmark.option.measure_process_cpu_time() +@benchmark.option.use_real_time() +def with_options(state): + while state: + sum(range(1_000_000)) + + +@benchmark.register(name="sum_million_microseconds") +@benchmark.option.unit(benchmark.kMicrosecond) +def with_options2(state): + while state: + sum(range(1_000_000)) + + +@benchmark.register +@benchmark.option.arg(100) +@benchmark.option.arg(1000) +def passing_argument(state): + while state: + sum(range(state.range(0))) + + +@benchmark.register +@benchmark.option.range(8, limit=8 << 10) +def using_range(state): + while state: + sum(range(state.range(0))) + + +@benchmark.register +@benchmark.option.range_multiplier(2) +@benchmark.option.range(1 << 10, 1 << 18) +@benchmark.option.complexity(benchmark.oN) +def computing_complexity(state): + while state: + sum(range(state.range(0))) + state.complexity_n = state.range(0) + + +if __name__ == "__main__": + benchmark.main() diff --git a/bridge/third_party/benchmark/bindings/python/pybind11.BUILD b/bridge/third_party/benchmark/bindings/python/pybind11.BUILD new file mode 100644 index 0000000000..bc83350038 --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/pybind11.BUILD @@ -0,0 +1,20 @@ +cc_library( + name = "pybind11", + hdrs = glob( + include = [ + "include/pybind11/*.h", + "include/pybind11/detail/*.h", + ], + exclude = [ + "include/pybind11/common.h", + "include/pybind11/eigen.h", + ], + ), + copts = [ + "-fexceptions", + "-Wno-undefined-inline", + "-Wno-pragma-once-outside-header", + ], + includes = ["include"], + visibility = ["//visibility:public"], +) diff --git a/bridge/third_party/benchmark/bindings/python/python_headers.BUILD b/bridge/third_party/benchmark/bindings/python/python_headers.BUILD new file mode 100644 index 0000000000..9c34cf6ca4 --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/python_headers.BUILD @@ -0,0 +1,6 @@ +cc_library( + name = "python_headers", + hdrs = glob(["**/*.h"]), + includes = ["."], + visibility = ["//visibility:public"], +) diff --git a/bridge/third_party/benchmark/bindings/python/requirements.txt b/bridge/third_party/benchmark/bindings/python/requirements.txt new file mode 100644 index 0000000000..f5bbe7eca5 --- /dev/null +++ b/bridge/third_party/benchmark/bindings/python/requirements.txt @@ -0,0 +1,2 @@ +absl-py>=0.7.1 + diff --git a/bridge/third_party/benchmark/cmake/AddCXXCompilerFlag.cmake b/bridge/third_party/benchmark/cmake/AddCXXCompilerFlag.cmake new file mode 100644 index 0000000000..858589e977 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/AddCXXCompilerFlag.cmake @@ -0,0 +1,78 @@ +# - Adds a compiler flag if it is supported by the compiler +# +# This function checks that the supplied compiler flag is supported and then +# adds it to the corresponding compiler flags +# +# add_cxx_compiler_flag( []) +# +# - Example +# +# include(AddCXXCompilerFlag) +# add_cxx_compiler_flag(-Wall) +# add_cxx_compiler_flag(-no-strict-aliasing RELEASE) +# Requires CMake 2.6+ + +if(__add_cxx_compiler_flag) + return() +endif() +set(__add_cxx_compiler_flag INCLUDED) + +include(CheckCXXCompilerFlag) + +function(mangle_compiler_flag FLAG OUTPUT) + string(TOUPPER "HAVE_CXX_FLAG_${FLAG}" SANITIZED_FLAG) + string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG}) + string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) + string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) + set(${OUTPUT} "${SANITIZED_FLAG}" PARENT_SCOPE) +endfunction(mangle_compiler_flag) + +function(add_cxx_compiler_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") + if(${MANGLED_FLAG}) + if(ARGC GREATER 1) + set(VARIANT ${ARGV1}) + string(TOUPPER "_${VARIANT}" VARIANT) + else() + set(VARIANT "") + endif() + set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${BENCHMARK_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) + endif() +endfunction() + +function(add_required_cxx_compiler_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") + if(${MANGLED_FLAG}) + if(ARGC GREATER 1) + set(VARIANT ${ARGV1}) + string(TOUPPER "_${VARIANT}" VARIANT) + else() + set(VARIANT "") + endif() + set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}" PARENT_SCOPE) + else() + message(FATAL_ERROR "Required flag '${FLAG}' is not supported by the compiler") + endif() +endfunction() + +function(check_cxx_warning_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + # Add -Werror to ensure the compiler generates an error if the warning flag + # doesn't exist. + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") +endfunction() diff --git a/bridge/third_party/benchmark/cmake/CXXFeatureCheck.cmake b/bridge/third_party/benchmark/cmake/CXXFeatureCheck.cmake new file mode 100644 index 0000000000..62e6741fe3 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/CXXFeatureCheck.cmake @@ -0,0 +1,69 @@ +# - Compile and run code to check for C++ features +# +# This functions compiles a source file under the `cmake` folder +# and adds the corresponding `HAVE_[FILENAME]` flag to the CMake +# environment +# +# cxx_feature_check( []) +# +# - Example +# +# include(CXXFeatureCheck) +# cxx_feature_check(STD_REGEX) +# Requires CMake 2.8.12+ + +if(__cxx_feature_check) + return() +endif() +set(__cxx_feature_check INCLUDED) + +function(cxx_feature_check FILE) + string(TOLOWER ${FILE} FILE) + string(TOUPPER ${FILE} VAR) + string(TOUPPER "HAVE_${VAR}" FEATURE) + if (DEFINED HAVE_${VAR}) + set(HAVE_${VAR} 1 PARENT_SCOPE) + add_definitions(-DHAVE_${VAR}) + return() + endif() + + if (ARGC GREATER 1) + message(STATUS "Enabling additional flags: ${ARGV1}") + list(APPEND BENCHMARK_CXX_LINKER_FLAGS ${ARGV1}) + endif() + + if (NOT DEFINED COMPILE_${FEATURE}) + message(STATUS "Performing Test ${FEATURE}") + if(CMAKE_CROSSCOMPILING) + try_compile(COMPILE_${FEATURE} + ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${FILE}.cpp + CMAKE_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS} + LINK_LIBRARIES ${BENCHMARK_CXX_LIBRARIES}) + if(COMPILE_${FEATURE}) + message(WARNING + "If you see build failures due to cross compilation, try setting HAVE_${VAR} to 0") + set(RUN_${FEATURE} 0 CACHE INTERNAL "") + else() + set(RUN_${FEATURE} 1 CACHE INTERNAL "") + endif() + else() + message(STATUS "Performing Test ${FEATURE}") + try_run(RUN_${FEATURE} COMPILE_${FEATURE} + ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${FILE}.cpp + CMAKE_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS} + LINK_LIBRARIES ${BENCHMARK_CXX_LIBRARIES}) + endif() + endif() + + if(RUN_${FEATURE} EQUAL 0) + message(STATUS "Performing Test ${FEATURE} -- success") + set(HAVE_${VAR} 1 PARENT_SCOPE) + add_definitions(-DHAVE_${VAR}) + else() + if(NOT COMPILE_${FEATURE}) + message(STATUS "Performing Test ${FEATURE} -- failed to compile") + else() + message(STATUS "Performing Test ${FEATURE} -- compiled but failed to run") + endif() + endif() +endfunction() diff --git a/bridge/third_party/benchmark/cmake/Config.cmake.in b/bridge/third_party/benchmark/cmake/Config.cmake.in new file mode 100644 index 0000000000..2e15f0cf82 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/Config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include (CMakeFindDependencyMacro) + +find_dependency (Threads) + +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") diff --git a/bridge/third_party/benchmark/cmake/GetGitVersion.cmake b/bridge/third_party/benchmark/cmake/GetGitVersion.cmake new file mode 100644 index 0000000000..04a1f9b70d --- /dev/null +++ b/bridge/third_party/benchmark/cmake/GetGitVersion.cmake @@ -0,0 +1,58 @@ +# - Returns a version string from Git tags +# +# This function inspects the annotated git tags for the project and returns a string +# into a CMake variable +# +# get_git_version() +# +# - Example +# +# include(GetGitVersion) +# get_git_version(GIT_VERSION) +# +# Requires CMake 2.8.11+ +find_package(Git) + +if(__get_git_version) + return() +endif() +set(__get_git_version INCLUDED) + +function(get_git_version var) + if(GIT_EXECUTABLE) + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --match "v[0-9]*.[0-9]*.[0-9]*" --abbrev=8 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE status + OUTPUT_VARIABLE GIT_DESCRIBE_VERSION + ERROR_QUIET) + if(status) + set(GIT_DESCRIBE_VERSION "v0.0.0") + endif() + + string(STRIP ${GIT_DESCRIBE_VERSION} GIT_DESCRIBE_VERSION) + if(GIT_DESCRIBE_VERSION MATCHES v[^-]*-) + string(REGEX REPLACE "v([^-]*)-([0-9]+)-.*" "\\1.\\2" GIT_VERSION ${GIT_DESCRIBE_VERSION}) + else() + string(REGEX REPLACE "v(.*)" "\\1" GIT_VERSION ${GIT_DESCRIBE_VERSION}) + endif() + + # Work out if the repository is dirty + execute_process(COMMAND ${GIT_EXECUTABLE} update-index -q --refresh + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET) + execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --name-only HEAD -- + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_DIFF_INDEX + ERROR_QUIET) + string(COMPARE NOTEQUAL "${GIT_DIFF_INDEX}" "" GIT_DIRTY) + if (${GIT_DIRTY}) + set(GIT_DESCRIBE_VERSION "${GIT_DESCRIBE_VERSION}-dirty") + endif() + message(STATUS "git version: ${GIT_DESCRIBE_VERSION} normalized to ${GIT_VERSION}") + else() + set(GIT_VERSION "0.0.0") + endif() + + set(${var} ${GIT_VERSION} PARENT_SCOPE) +endfunction() diff --git a/bridge/third_party/benchmark/cmake/GoogleTest.cmake b/bridge/third_party/benchmark/cmake/GoogleTest.cmake new file mode 100644 index 0000000000..66cb91008b --- /dev/null +++ b/bridge/third_party/benchmark/cmake/GoogleTest.cmake @@ -0,0 +1,48 @@ +# Download and unpack googletest at configure time +set(GOOGLETEST_PREFIX "${benchmark_BINARY_DIR}/third_party/googletest") +configure_file(${benchmark_SOURCE_DIR}/cmake/GoogleTest.cmake.in ${GOOGLETEST_PREFIX}/CMakeLists.txt @ONLY) + +set(GOOGLETEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/googletest" CACHE PATH "") # Mind the quotes +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" + -DALLOW_DOWNLOADING_GOOGLETEST=${BENCHMARK_DOWNLOAD_DEPENDENCIES} -DGOOGLETEST_PATH:PATH=${GOOGLETEST_PATH} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_PREFIX} +) + +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() + +execute_process( + COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_PREFIX} +) + +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Prevent overriding the parent project's compiler/linker +# settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +include(${GOOGLETEST_PREFIX}/googletest-paths.cmake) + +# googletest doesn't seem to want to stay build warning clean so let's not hurt ourselves. +add_compile_options(-w) + +# Add googletest directly to our build. This defines +# the gtest and gtest_main targets. +add_subdirectory(${GOOGLETEST_SOURCE_DIR} + ${GOOGLETEST_BINARY_DIR} + EXCLUDE_FROM_ALL) + +if(NOT DEFINED GTEST_COMPILE_COMMANDS) + set(GTEST_COMPILE_COMMANDS ON) +endif() + +set_target_properties(gtest PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $ EXPORT_COMPILE_COMMANDS ${GTEST_COMPILE_COMMANDS}) +set_target_properties(gtest_main PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $ EXPORT_COMPILE_COMMANDS ${GTEST_COMPILE_COMMANDS}) +set_target_properties(gmock PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $ EXPORT_COMPILE_COMMANDS ${GTEST_COMPILE_COMMANDS}) +set_target_properties(gmock_main PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $ EXPORT_COMPILE_COMMANDS ${GTEST_COMPILE_COMMANDS}) diff --git a/bridge/third_party/benchmark/cmake/GoogleTest.cmake.in b/bridge/third_party/benchmark/cmake/GoogleTest.cmake.in new file mode 100644 index 0000000000..bf89c47194 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/GoogleTest.cmake.in @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 2.8.12) + +project(googletest-download NONE) + +# Enable ExternalProject CMake module +include(ExternalProject) + +option(ALLOW_DOWNLOADING_GOOGLETEST "If googletest src tree is not found in location specified by GOOGLETEST_PATH, do fetch the archive from internet" OFF) +set(GOOGLETEST_PATH "/usr/src/googletest" CACHE PATH + "Path to the googletest root tree. Should contain googletest and googlemock subdirs. And CMakeLists.txt in root, and in both of these subdirs") + +# Download and install GoogleTest + +message(STATUS "Looking for Google Test sources") +message(STATUS "Looking for Google Test sources in ${GOOGLETEST_PATH}") +if(EXISTS "${GOOGLETEST_PATH}" AND IS_DIRECTORY "${GOOGLETEST_PATH}" AND EXISTS "${GOOGLETEST_PATH}/CMakeLists.txt" AND + EXISTS "${GOOGLETEST_PATH}/googletest" AND IS_DIRECTORY "${GOOGLETEST_PATH}/googletest" AND EXISTS "${GOOGLETEST_PATH}/googletest/CMakeLists.txt" AND + EXISTS "${GOOGLETEST_PATH}/googlemock" AND IS_DIRECTORY "${GOOGLETEST_PATH}/googlemock" AND EXISTS "${GOOGLETEST_PATH}/googlemock/CMakeLists.txt") + message(STATUS "Found Google Test in ${GOOGLETEST_PATH}") + + ExternalProject_Add( + googletest + PREFIX "${CMAKE_BINARY_DIR}" + DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/download" + SOURCE_DIR "${GOOGLETEST_PATH}" # use existing src dir. + BINARY_DIR "${CMAKE_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) +else() + if(NOT ALLOW_DOWNLOADING_GOOGLETEST) + message(SEND_ERROR "Did not find Google Test sources! Either pass correct path in GOOGLETEST_PATH, or enable BENCHMARK_DOWNLOAD_DEPENDENCIES, or disable BENCHMARK_USE_BUNDLED_GTEST, or disable BENCHMARK_ENABLE_GTEST_TESTS / BENCHMARK_ENABLE_TESTING.") + return() + else() + message(WARNING "Did not find Google Test sources! Fetching from web...") + ExternalProject_Add( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main + PREFIX "${CMAKE_BINARY_DIR}" + STAMP_DIR "${CMAKE_BINARY_DIR}/stamp" + DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/download" + SOURCE_DIR "${CMAKE_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) + endif() +endif() + +ExternalProject_Get_Property(googletest SOURCE_DIR BINARY_DIR) +file(WRITE googletest-paths.cmake +"set(GOOGLETEST_SOURCE_DIR \"${SOURCE_DIR}\") +set(GOOGLETEST_BINARY_DIR \"${BINARY_DIR}\") +") diff --git a/bridge/third_party/benchmark/cmake/Modules/FindLLVMAr.cmake b/bridge/third_party/benchmark/cmake/Modules/FindLLVMAr.cmake new file mode 100644 index 0000000000..23469813cf --- /dev/null +++ b/bridge/third_party/benchmark/cmake/Modules/FindLLVMAr.cmake @@ -0,0 +1,16 @@ +include(FeatureSummary) + +find_program(LLVMAR_EXECUTABLE + NAMES llvm-ar + DOC "The llvm-ar executable" + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLVMAr + DEFAULT_MSG + LLVMAR_EXECUTABLE) + +SET_PACKAGE_PROPERTIES(LLVMAr PROPERTIES + URL https://llvm.org/docs/CommandGuide/llvm-ar.html + DESCRIPTION "create, modify, and extract from archives" +) diff --git a/bridge/third_party/benchmark/cmake/Modules/FindLLVMNm.cmake b/bridge/third_party/benchmark/cmake/Modules/FindLLVMNm.cmake new file mode 100644 index 0000000000..e56430a04f --- /dev/null +++ b/bridge/third_party/benchmark/cmake/Modules/FindLLVMNm.cmake @@ -0,0 +1,16 @@ +include(FeatureSummary) + +find_program(LLVMNM_EXECUTABLE + NAMES llvm-nm + DOC "The llvm-nm executable" + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLVMNm + DEFAULT_MSG + LLVMNM_EXECUTABLE) + +SET_PACKAGE_PROPERTIES(LLVMNm PROPERTIES + URL https://llvm.org/docs/CommandGuide/llvm-nm.html + DESCRIPTION "list LLVM bitcode and object file’s symbol table" +) diff --git a/bridge/third_party/benchmark/cmake/Modules/FindLLVMRanLib.cmake b/bridge/third_party/benchmark/cmake/Modules/FindLLVMRanLib.cmake new file mode 100644 index 0000000000..7b53e1a790 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/Modules/FindLLVMRanLib.cmake @@ -0,0 +1,15 @@ +include(FeatureSummary) + +find_program(LLVMRANLIB_EXECUTABLE + NAMES llvm-ranlib + DOC "The llvm-ranlib executable" + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLVMRanLib + DEFAULT_MSG + LLVMRANLIB_EXECUTABLE) + +SET_PACKAGE_PROPERTIES(LLVMRanLib PROPERTIES + DESCRIPTION "generate index for LLVM archive" +) diff --git a/bridge/third_party/benchmark/cmake/Modules/FindPFM.cmake b/bridge/third_party/benchmark/cmake/Modules/FindPFM.cmake new file mode 100644 index 0000000000..cf807a1ee9 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/Modules/FindPFM.cmake @@ -0,0 +1,26 @@ +# If successful, the following variables will be defined: +# HAVE_LIBPFM. +# Set BENCHMARK_ENABLE_LIBPFM to 0 to disable, regardless of libpfm presence. +include(CheckIncludeFile) +include(CheckLibraryExists) +include(FeatureSummary) +enable_language(C) + +set_package_properties(PFM PROPERTIES + URL http://perfmon2.sourceforge.net/ + DESCRIPTION "a helper library to develop monitoring tools" + PURPOSE "Used to program specific performance monitoring events") + +check_library_exists(libpfm.a pfm_initialize "" HAVE_LIBPFM_INITIALIZE) +if(HAVE_LIBPFM_INITIALIZE) + check_include_file(perfmon/perf_event.h HAVE_PERFMON_PERF_EVENT_H) + check_include_file(perfmon/pfmlib.h HAVE_PERFMON_PFMLIB_H) + check_include_file(perfmon/pfmlib_perf_event.h HAVE_PERFMON_PFMLIB_PERF_EVENT_H) + if(HAVE_PERFMON_PERF_EVENT_H AND HAVE_PERFMON_PFMLIB_H AND HAVE_PERFMON_PFMLIB_PERF_EVENT_H) + message("Using Perf Counters.") + set(HAVE_LIBPFM 1) + set(PFM_FOUND 1) + endif() +else() + message("Perf Counters support requested, but was unable to find libpfm.") +endif() diff --git a/bridge/third_party/benchmark/cmake/benchmark.pc.in b/bridge/third_party/benchmark/cmake/benchmark.pc.in new file mode 100644 index 0000000000..34beb012ee --- /dev/null +++ b/bridge/third_party/benchmark/cmake/benchmark.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: Google microbenchmark framework +Version: @VERSION@ + +Libs: -L${libdir} -lbenchmark +Libs.private: -lpthread +Cflags: -I${includedir} diff --git a/bridge/third_party/benchmark/cmake/gnu_posix_regex.cpp b/bridge/third_party/benchmark/cmake/gnu_posix_regex.cpp new file mode 100644 index 0000000000..b5b91cdab7 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/gnu_posix_regex.cpp @@ -0,0 +1,12 @@ +#include +#include +int main() { + std::string str = "test0159"; + regex_t re; + int ec = regcomp(&re, "^[a-z]+[0-9]+$", REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + return ec; + } + return regexec(&re, str.c_str(), 0, nullptr, 0) ? -1 : 0; +} + diff --git a/bridge/third_party/benchmark/cmake/llvm-toolchain.cmake b/bridge/third_party/benchmark/cmake/llvm-toolchain.cmake new file mode 100644 index 0000000000..fc119e52fd --- /dev/null +++ b/bridge/third_party/benchmark/cmake/llvm-toolchain.cmake @@ -0,0 +1,8 @@ +find_package(LLVMAr REQUIRED) +set(CMAKE_AR "${LLVMAR_EXECUTABLE}" CACHE FILEPATH "" FORCE) + +find_package(LLVMNm REQUIRED) +set(CMAKE_NM "${LLVMNM_EXECUTABLE}" CACHE FILEPATH "" FORCE) + +find_package(LLVMRanLib REQUIRED) +set(CMAKE_RANLIB "${LLVMRANLIB_EXECUTABLE}" CACHE FILEPATH "" FORCE) diff --git a/bridge/third_party/benchmark/cmake/posix_regex.cpp b/bridge/third_party/benchmark/cmake/posix_regex.cpp new file mode 100644 index 0000000000..466dc62560 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/posix_regex.cpp @@ -0,0 +1,14 @@ +#include +#include +int main() { + std::string str = "test0159"; + regex_t re; + int ec = regcomp(&re, "^[a-z]+[0-9]+$", REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + return ec; + } + int ret = regexec(&re, str.c_str(), 0, nullptr, 0) ? -1 : 0; + regfree(&re); + return ret; +} + diff --git a/bridge/third_party/benchmark/cmake/split_list.cmake b/bridge/third_party/benchmark/cmake/split_list.cmake new file mode 100644 index 0000000000..67aed3fdc8 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/split_list.cmake @@ -0,0 +1,3 @@ +macro(split_list listname) + string(REPLACE ";" " " ${listname} "${${listname}}") +endmacro() diff --git a/bridge/third_party/benchmark/cmake/std_regex.cpp b/bridge/third_party/benchmark/cmake/std_regex.cpp new file mode 100644 index 0000000000..696f2a26bc --- /dev/null +++ b/bridge/third_party/benchmark/cmake/std_regex.cpp @@ -0,0 +1,10 @@ +#include +#include +int main() { + const std::string str = "test0159"; + std::regex re; + re = std::regex("^[a-z]+[0-9]+$", + std::regex_constants::extended | std::regex_constants::nosubs); + return std::regex_search(str, re) ? 0 : -1; +} + diff --git a/bridge/third_party/benchmark/cmake/steady_clock.cpp b/bridge/third_party/benchmark/cmake/steady_clock.cpp new file mode 100644 index 0000000000..66d50d17e9 --- /dev/null +++ b/bridge/third_party/benchmark/cmake/steady_clock.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + typedef std::chrono::steady_clock Clock; + Clock::time_point tp = Clock::now(); + ((void)tp); +} diff --git a/bridge/third_party/benchmark/cmake/thread_safety_attributes.cpp b/bridge/third_party/benchmark/cmake/thread_safety_attributes.cpp new file mode 100644 index 0000000000..46161babdb --- /dev/null +++ b/bridge/third_party/benchmark/cmake/thread_safety_attributes.cpp @@ -0,0 +1,4 @@ +#define HAVE_THREAD_SAFETY_ATTRIBUTES +#include "../src/mutex.h" + +int main() {} diff --git a/bridge/third_party/benchmark/docs/AssemblyTests.md b/bridge/third_party/benchmark/docs/AssemblyTests.md new file mode 100644 index 0000000000..1fbdc269b5 --- /dev/null +++ b/bridge/third_party/benchmark/docs/AssemblyTests.md @@ -0,0 +1,147 @@ +# Assembly Tests + +The Benchmark library provides a number of functions whose primary +purpose in to affect assembly generation, including `DoNotOptimize` +and `ClobberMemory`. In addition there are other functions, +such as `KeepRunning`, for which generating good assembly is paramount. + +For these functions it's important to have tests that verify the +correctness and quality of the implementation. This requires testing +the code generated by the compiler. + +This document describes how the Benchmark library tests compiler output, +as well as how to properly write new tests. + + +## Anatomy of a Test + +Writing a test has two steps: + +* Write the code you want to generate assembly for. +* Add `// CHECK` lines to match against the verified assembly. + +Example: +```c++ + +// CHECK-LABEL: test_add: +extern "C" int test_add() { + extern int ExternInt; + return ExternInt + 1; + + // CHECK: movl ExternInt(%rip), %eax + // CHECK: addl %eax + // CHECK: ret +} + +``` + +#### LLVM Filecheck + +[LLVM's Filecheck](https://llvm.org/docs/CommandGuide/FileCheck.html) +is used to test the generated assembly against the `// CHECK` lines +specified in the tests source file. Please see the documentation +linked above for information on how to write `CHECK` directives. + +#### Tips and Tricks: + +* Tests should match the minimal amount of output required to establish +correctness. `CHECK` directives don't have to match on the exact next line +after the previous match, so tests should omit checks for unimportant +bits of assembly. ([`CHECK-NEXT`](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-next-directive) +can be used to ensure a match occurs exactly after the previous match). + +* The tests are compiled with `-O3 -g0`. So we're only testing the +optimized output. + +* The assembly output is further cleaned up using `tools/strip_asm.py`. +This removes comments, assembler directives, and unused labels before +the test is run. + +* The generated and stripped assembly file for a test is output under +`/test/.s` + +* Filecheck supports using [`CHECK` prefixes](https://llvm.org/docs/CommandGuide/FileCheck.html#cmdoption-check-prefixes) +to specify lines that should only match in certain situations. +The Benchmark tests use `CHECK-CLANG` and `CHECK-GNU` for lines that +are only expected to match Clang or GCC's output respectively. Normal +`CHECK` lines match against all compilers. (Note: `CHECK-NOT` and +`CHECK-LABEL` are NOT prefixes. They are versions of non-prefixed +`CHECK` lines) + +* Use `extern "C"` to disable name mangling for specific functions. This +makes them easier to name in the `CHECK` lines. + + +## Problems Writing Portable Tests + +Writing tests which check the code generated by a compiler are +inherently non-portable. Different compilers and even different compiler +versions may generate entirely different code. The Benchmark tests +must tolerate this. + +LLVM Filecheck provides a number of mechanisms to help write +"more portable" tests; including [matching using regular expressions](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-pattern-matching-syntax), +allowing the creation of [named variables](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-variables) +for later matching, and [checking non-sequential matches](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-dag-directive). + +#### Capturing Variables + +For example, say GCC stores a variable in a register but Clang stores +it in memory. To write a test that tolerates both cases we "capture" +the destination of the store, and then use the captured expression +to write the remainder of the test. + +```c++ +// CHECK-LABEL: test_div_no_op_into_shr: +extern "C" void test_div_no_op_into_shr(int value) { + int divisor = 2; + benchmark::DoNotOptimize(divisor); // hide the value from the optimizer + return value / divisor; + + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} +``` + +#### Using Regular Expressions to Match Differing Output + +Often tests require testing assembly lines which may subtly differ +between compilers or compiler versions. A common example of this +is matching stack frame addresses. In this case regular expressions +can be used to match the differing bits of output. For example: + +```c++ +int ExternInt; +struct Point { int x, y, z; }; + +// CHECK-LABEL: test_store_point: +extern "C" void test_store_point() { + Point p{ExternInt, ExternInt, ExternInt}; + benchmark::DoNotOptimize(p); + + // CHECK: movl ExternInt(%rip), %eax + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: ret +} +``` + +## Current Requirements and Limitations + +The tests require Filecheck to be installed along the `PATH` of the +build machine. Otherwise the tests will be disabled. + +Additionally, as mentioned in the previous section, codegen tests are +inherently non-portable. Currently the tests are limited to: + +* x86_64 targets. +* Compiled with GCC or Clang + +Further work could be done, at least on a limited basis, to extend the +tests to other architectures and compilers (using `CHECK` prefixes). + +Furthermore, the tests fail for builds which specify additional flags +that modify code generation, including `--coverage` or `-fsanitize=`. + diff --git a/bridge/third_party/benchmark/docs/_config.yml b/bridge/third_party/benchmark/docs/_config.yml new file mode 100644 index 0000000000..2f7efbeab5 --- /dev/null +++ b/bridge/third_party/benchmark/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/bridge/third_party/benchmark/docs/dependencies.md b/bridge/third_party/benchmark/docs/dependencies.md new file mode 100644 index 0000000000..7af52b95bd --- /dev/null +++ b/bridge/third_party/benchmark/docs/dependencies.md @@ -0,0 +1,19 @@ +# Build tool dependency policy + +To ensure the broadest compatibility when building the benchmark library, but +still allow forward progress, we require any build tooling to be available for: + +* Debian stable _and_ +* The last two Ubuntu LTS releases + +Currently, this means using build tool versions that are available for Ubuntu +18.04 (Bionic Beaver), Ubuntu 20.04 (Focal Fossa), and Debian 11 (bullseye). + +_Note, CI also runs ubuntu-16.04 and ubuntu-14.04 to ensure best effort support +for older versions._ + +## cmake +The current supported version is cmake 3.5.1 as of 2018-06-06. + +_Note, this version is also available for Ubuntu 14.04, an older Ubuntu LTS +release, as `cmake3`._ diff --git a/bridge/third_party/benchmark/docs/index.md b/bridge/third_party/benchmark/docs/index.md new file mode 100644 index 0000000000..eb82eff9ee --- /dev/null +++ b/bridge/third_party/benchmark/docs/index.md @@ -0,0 +1,10 @@ +# Benchmark + +* [Assembly Tests](AssemblyTests.md) +* [Dependencies](dependencies.md) +* [Perf Counters](perf_counters.md) +* [Platform Specific Build Instructions](platform_specific_build_instructions.md) +* [Random Interleaving](random_interleaving.md) +* [Releasing](releasing.md) +* [Tools](tools.md) +* [User Guide](user_guide.md) \ No newline at end of file diff --git a/bridge/third_party/benchmark/docs/perf_counters.md b/bridge/third_party/benchmark/docs/perf_counters.md new file mode 100644 index 0000000000..74560e9669 --- /dev/null +++ b/bridge/third_party/benchmark/docs/perf_counters.md @@ -0,0 +1,34 @@ + + +# User-Requested Performance Counters + +When running benchmarks, the user may choose to request collection of +performance counters. This may be useful in investigation scenarios - narrowing +down the cause of a regression; or verifying that the underlying cause of a +performance improvement matches expectations. + +This feature is available if: + +* The benchmark is run on an architecture featuring a Performance Monitoring + Unit (PMU), +* The benchmark is compiled with support for collecting counters. Currently, + this requires [libpfm](http://perfmon2.sourceforge.net/) be available at build + time + +The feature does not require modifying benchmark code. Counter collection is +handled at the boundaries where timer collection is also handled. + +To opt-in: + +* Install `libpfm4-dev`, e.g. `apt-get install libpfm4-dev`. +* Enable the cmake flag BENCHMARK_ENABLE_LIBPFM. + +To use, pass a comma-separated list of counter names through the +`--benchmark_perf_counters` flag. The names are decoded through libpfm - meaning, +they are platform specific, but some (e.g. `CYCLES` or `INSTRUCTIONS`) are +mapped by libpfm to platform-specifics - see libpfm +[documentation](http://perfmon2.sourceforge.net/docs.html) for more details. + +The counter values are reported back through the [User Counters](../README.md#custom-counters) +mechanism, meaning, they are available in all the formats (e.g. JSON) supported +by User Counters. \ No newline at end of file diff --git a/bridge/third_party/benchmark/docs/platform_specific_build_instructions.md b/bridge/third_party/benchmark/docs/platform_specific_build_instructions.md new file mode 100644 index 0000000000..2d5d6c47ee --- /dev/null +++ b/bridge/third_party/benchmark/docs/platform_specific_build_instructions.md @@ -0,0 +1,48 @@ +# Platform Specific Build Instructions + +## Building with GCC + +When the library is built using GCC it is necessary to link with the pthread +library due to how GCC implements `std::thread`. Failing to link to pthread will +lead to runtime exceptions (unless you're using libc++), not linker errors. See +[issue #67](https://github.com/google/benchmark/issues/67) for more details. You +can link to pthread by adding `-pthread` to your linker command. Note, you can +also use `-lpthread`, but there are potential issues with ordering of command +line parameters if you use that. + +On QNX, the pthread library is part of libc and usually included automatically +(see +[`pthread_create()`](https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.lib_ref/topic/p/pthread_create.html)). +There's no separate pthread library to link. + +## Building with Visual Studio 2015 or 2017 + +The `shlwapi` library (`-lshlwapi`) is required to support a call to `CPUInfo` which reads the registry. Either add `shlwapi.lib` under `[ Configuration Properties > Linker > Input ]`, or use the following: + +``` +// Alternatively, can add libraries using linker options. +#ifdef _WIN32 +#pragma comment ( lib, "Shlwapi.lib" ) +#ifdef _DEBUG +#pragma comment ( lib, "benchmarkd.lib" ) +#else +#pragma comment ( lib, "benchmark.lib" ) +#endif +#endif +``` + +Can also use the graphical version of CMake: +* Open `CMake GUI`. +* Under `Where to build the binaries`, same path as source plus `build`. +* Under `CMAKE_INSTALL_PREFIX`, same path as source plus `install`. +* Click `Configure`, `Generate`, `Open Project`. +* If build fails, try deleting entire directory and starting again, or unticking options to build less. + +## Building with Intel 2015 Update 1 or Intel System Studio Update 4 + +See instructions for building with Visual Studio. Once built, right click on the solution and change the build to Intel. + +## Building on Solaris + +If you're running benchmarks on solaris, you'll want the kstat library linked in +too (`-lkstat`). \ No newline at end of file diff --git a/bridge/third_party/benchmark/docs/random_interleaving.md b/bridge/third_party/benchmark/docs/random_interleaving.md new file mode 100644 index 0000000000..c083036841 --- /dev/null +++ b/bridge/third_party/benchmark/docs/random_interleaving.md @@ -0,0 +1,13 @@ + + +# Random Interleaving + +[Random Interleaving](https://github.com/google/benchmark/issues/1051) is a +technique to lower run-to-run variance. It randomly interleaves repetitions of a +microbenchmark with repetitions from other microbenchmarks in the same benchmark +test. Data shows it is able to lower run-to-run variance by +[40%](https://github.com/google/benchmark/issues/1051) on average. + +To use, you mainly need to set `--benchmark_enable_random_interleaving=true`, +and optionally specify non-zero repetition count `--benchmark_repetitions=9` +and optionally decrease the per-repetition time `--benchmark_min_time=0.1`. diff --git a/bridge/third_party/benchmark/docs/releasing.md b/bridge/third_party/benchmark/docs/releasing.md new file mode 100644 index 0000000000..334f935393 --- /dev/null +++ b/bridge/third_party/benchmark/docs/releasing.md @@ -0,0 +1,35 @@ +# How to release + +* Make sure you're on main and synced to HEAD +* Ensure the project builds and tests run (sanity check only, obviously) + * `parallel -j0 exec ::: test/*_test` can help ensure everything at least + passes +* Prepare release notes + * `git log $(git describe --abbrev=0 --tags)..HEAD` gives you the list of + commits between the last annotated tag and HEAD + * Pick the most interesting. +* Create one last commit that updates the version saved in `CMakeLists.txt` and the + `__version__` variable in `bindings/python/google_benchmark/__init__.py`to the release + version you're creating. (This version will be used if benchmark is installed from the + archive you'll be creating in the next step.) + +``` +project (benchmark VERSION 1.6.0 LANGUAGES CXX) +``` + +```python +# bindings/python/google_benchmark/__init__.py + +# ... + +__version__ = "1.6.0" # <-- change this to the release version you are creating + +# ... +``` + +* Create a release through github's interface + * Note this will create a lightweight tag. + * Update this to an annotated tag: + * `git pull --tags` + * `git tag -a -f ` + * `git push --force --tags origin` diff --git a/bridge/third_party/benchmark/docs/tools.md b/bridge/third_party/benchmark/docs/tools.md new file mode 100644 index 0000000000..f2d0c497f3 --- /dev/null +++ b/bridge/third_party/benchmark/docs/tools.md @@ -0,0 +1,203 @@ +# Benchmark Tools + +## compare.py + +The `compare.py` can be used to compare the result of benchmarks. + +### Dependencies +The utility relies on the [scipy](https://www.scipy.org) package which can be installed using pip: +```bash +pip3 install -r requirements.txt +``` + +### Displaying aggregates only + +The switch `-a` / `--display_aggregates_only` can be used to control the +displayment of the normal iterations vs the aggregates. When passed, it will +be passthrough to the benchmark binaries to be run, and will be accounted for +in the tool itself; only the aggregates will be displayed, but not normal runs. +It only affects the display, the separate runs will still be used to calculate +the U test. + +### Modes of operation + +There are three modes of operation: + +1. Just compare two benchmarks +The program is invoked like: + +``` bash +$ compare.py benchmarks [benchmark options]... +``` +Where `` and `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py benchmarks ./a.out ./a.out +RUNNING: ./a.out --benchmark_out=/tmp/tmprBT5nW +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:16:44 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 19101577 211.669MB/s +BM_memcpy/64 76 ns 76 ns 9412571 800.199MB/s +BM_memcpy/512 84 ns 84 ns 8249070 5.64771GB/s +BM_memcpy/1024 116 ns 116 ns 6181763 8.19505GB/s +BM_memcpy/8192 643 ns 643 ns 1062855 11.8636GB/s +BM_copy/8 222 ns 222 ns 3137987 34.3772MB/s +BM_copy/64 1608 ns 1608 ns 432758 37.9501MB/s +BM_copy/512 12589 ns 12589 ns 54806 38.7867MB/s +BM_copy/1024 25169 ns 25169 ns 27713 38.8003MB/s +BM_copy/8192 201165 ns 201112 ns 3486 38.8466MB/s +RUNNING: ./a.out --benchmark_out=/tmp/tmpt1wwG_ +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:16:53 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 19397903 211.255MB/s +BM_memcpy/64 73 ns 73 ns 9691174 839.635MB/s +BM_memcpy/512 85 ns 85 ns 8312329 5.60101GB/s +BM_memcpy/1024 118 ns 118 ns 6438774 8.11608GB/s +BM_memcpy/8192 656 ns 656 ns 1068644 11.6277GB/s +BM_copy/8 223 ns 223 ns 3146977 34.2338MB/s +BM_copy/64 1611 ns 1611 ns 435340 37.8751MB/s +BM_copy/512 12622 ns 12622 ns 54818 38.6844MB/s +BM_copy/1024 25257 ns 25239 ns 27779 38.6927MB/s +BM_copy/8192 205013 ns 205010 ns 3479 38.108MB/s +Comparing ./a.out to ./a.out +Benchmark Time CPU Time Old Time New CPU Old CPU New +------------------------------------------------------------------------------------------------------ +BM_memcpy/8 +0.0020 +0.0020 36 36 36 36 +BM_memcpy/64 -0.0468 -0.0470 76 73 76 73 +BM_memcpy/512 +0.0081 +0.0083 84 85 84 85 +BM_memcpy/1024 +0.0098 +0.0097 116 118 116 118 +BM_memcpy/8192 +0.0200 +0.0203 643 656 643 656 +BM_copy/8 +0.0046 +0.0042 222 223 222 223 +BM_copy/64 +0.0020 +0.0020 1608 1611 1608 1611 +BM_copy/512 +0.0027 +0.0026 12589 12622 12589 12622 +BM_copy/1024 +0.0035 +0.0028 25169 25257 25169 25239 +BM_copy/8192 +0.0191 +0.0194 201165 205013 201112 205010 +``` + +What it does is for the every benchmark from the first run it looks for the benchmark with exactly the same name in the second run, and then compares the results. If the names differ, the benchmark is omitted from the diff. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +2. Compare two different filters of one benchmark +The program is invoked like: + +``` bash +$ compare.py filters [benchmark options]... +``` +Where `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +Where `` and `` are the same regex filters that you would pass to the `[--benchmark_filter=]` parameter of the benchmark binary. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py filters ./a.out BM_memcpy BM_copy +RUNNING: ./a.out --benchmark_filter=BM_memcpy --benchmark_out=/tmp/tmpBWKk0k +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:37:28 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 17891491 211.215MB/s +BM_memcpy/64 74 ns 74 ns 9400999 825.646MB/s +BM_memcpy/512 87 ns 87 ns 8027453 5.46126GB/s +BM_memcpy/1024 111 ns 111 ns 6116853 8.5648GB/s +BM_memcpy/8192 657 ns 656 ns 1064679 11.6247GB/s +RUNNING: ./a.out --benchmark_filter=BM_copy --benchmark_out=/tmp/tmpAvWcOM +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:37:33 +---------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_copy/8 227 ns 227 ns 3038700 33.6264MB/s +BM_copy/64 1640 ns 1640 ns 426893 37.2154MB/s +BM_copy/512 12804 ns 12801 ns 55417 38.1444MB/s +BM_copy/1024 25409 ns 25407 ns 27516 38.4365MB/s +BM_copy/8192 202986 ns 202990 ns 3454 38.4871MB/s +Comparing BM_memcpy to BM_copy (from ./a.out) +Benchmark Time CPU Time Old Time New CPU Old CPU New +-------------------------------------------------------------------------------------------------------------------- +[BM_memcpy vs. BM_copy]/8 +5.2829 +5.2812 36 227 36 227 +[BM_memcpy vs. BM_copy]/64 +21.1719 +21.1856 74 1640 74 1640 +[BM_memcpy vs. BM_copy]/512 +145.6487 +145.6097 87 12804 87 12801 +[BM_memcpy vs. BM_copy]/1024 +227.1860 +227.1776 111 25409 111 25407 +[BM_memcpy vs. BM_copy]/8192 +308.1664 +308.2898 657 202986 656 202990 +``` + +As you can see, it applies filter to the benchmarks, both when running the benchmark, and before doing the diff. And to make the diff work, the matches are replaced with some common string. Thus, you can compare two different benchmark families within one benchmark binary. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +3. Compare filter one from benchmark one to filter two from benchmark two: +The program is invoked like: + +``` bash +$ compare.py filters [benchmark options]... +``` + +Where `` and `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +Where `` and `` are the same regex filters that you would pass to the `[--benchmark_filter=]` parameter of the benchmark binary. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py benchmarksfiltered ./a.out BM_memcpy ./a.out BM_copy +RUNNING: ./a.out --benchmark_filter=BM_memcpy --benchmark_out=/tmp/tmp_FvbYg +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:38:27 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 37 ns 37 ns 18953482 204.118MB/s +BM_memcpy/64 74 ns 74 ns 9206578 828.245MB/s +BM_memcpy/512 91 ns 91 ns 8086195 5.25476GB/s +BM_memcpy/1024 120 ns 120 ns 5804513 7.95662GB/s +BM_memcpy/8192 664 ns 664 ns 1028363 11.4948GB/s +RUNNING: ./a.out --benchmark_filter=BM_copy --benchmark_out=/tmp/tmpDfL5iE +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:38:32 +---------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_copy/8 230 ns 230 ns 2985909 33.1161MB/s +BM_copy/64 1654 ns 1653 ns 419408 36.9137MB/s +BM_copy/512 13122 ns 13120 ns 53403 37.2156MB/s +BM_copy/1024 26679 ns 26666 ns 26575 36.6218MB/s +BM_copy/8192 215068 ns 215053 ns 3221 36.3283MB/s +Comparing BM_memcpy (from ./a.out) to BM_copy (from ./a.out) +Benchmark Time CPU Time Old Time New CPU Old CPU New +-------------------------------------------------------------------------------------------------------------------- +[BM_memcpy vs. BM_copy]/8 +5.1649 +5.1637 37 230 37 230 +[BM_memcpy vs. BM_copy]/64 +21.4352 +21.4374 74 1654 74 1653 +[BM_memcpy vs. BM_copy]/512 +143.6022 +143.5865 91 13122 91 13120 +[BM_memcpy vs. BM_copy]/1024 +221.5903 +221.4790 120 26679 120 26666 +[BM_memcpy vs. BM_copy]/8192 +322.9059 +323.0096 664 215068 664 215053 +``` +This is a mix of the previous two modes, two (potentially different) benchmark binaries are run, and a different filter is applied to each one. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +### U test + +If there is a sufficient repetition count of the benchmarks, the tool can do +a [U Test](https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test), of the +null hypothesis that it is equally likely that a randomly selected value from +one sample will be less than or greater than a randomly selected value from a +second sample. + +If the calculated p-value is below this value is lower than the significance +level alpha, then the result is said to be statistically significant and the +null hypothesis is rejected. Which in other words means that the two benchmarks +aren't identical. + +**WARNING**: requires **LARGE** (no less than 9) number of repetitions to be +meaningful! diff --git a/bridge/third_party/benchmark/docs/user_guide.md b/bridge/third_party/benchmark/docs/user_guide.md new file mode 100644 index 0000000000..34bea69042 --- /dev/null +++ b/bridge/third_party/benchmark/docs/user_guide.md @@ -0,0 +1,1200 @@ +# User Guide + +## Command Line + +[Output Formats](#output-formats) + +[Output Files](#output-files) + +[Running Benchmarks](#running-benchmarks) + +[Running a Subset of Benchmarks](#running-a-subset-of-benchmarks) + +[Result Comparison](#result-comparison) + +[Extra Context](#extra-context) + +## Library + +[Runtime and Reporting Considerations](#runtime-and-reporting-considerations) + +[Setup/Teardown](#setupteardown) + +[Passing Arguments](#passing-arguments) + +[Custom Benchmark Name](#custom-benchmark-name) + +[Calculating Asymptotic Complexity](#asymptotic-complexity) + +[Templated Benchmarks](#templated-benchmarks) + +[Fixtures](#fixtures) + +[Custom Counters](#custom-counters) + +[Multithreaded Benchmarks](#multithreaded-benchmarks) + +[CPU Timers](#cpu-timers) + +[Manual Timing](#manual-timing) + +[Setting the Time Unit](#setting-the-time-unit) + +[Random Interleaving](random_interleaving.md) + +[User-Requested Performance Counters](perf_counters.md) + +[Preventing Optimization](#preventing-optimization) + +[Reporting Statistics](#reporting-statistics) + +[Custom Statistics](#custom-statistics) + +[Using RegisterBenchmark](#using-register-benchmark) + +[Exiting with an Error](#exiting-with-an-error) + +[A Faster KeepRunning Loop](#a-faster-keep-running-loop) + +[Disabling CPU Frequency Scaling](#disabling-cpu-frequency-scaling) + + + + +## Output Formats + +The library supports multiple output formats. Use the +`--benchmark_format=` flag (or set the +`BENCHMARK_FORMAT=` environment variable) to set +the format type. `console` is the default format. + +The Console format is intended to be a human readable format. By default +the format generates color output. Context is output on stderr and the +tabular data on stdout. Example tabular output looks like: + +``` +Benchmark Time(ns) CPU(ns) Iterations +---------------------------------------------------------------------- +BM_SetInsert/1024/1 28928 29349 23853 133.097kB/s 33.2742k items/s +BM_SetInsert/1024/8 32065 32913 21375 949.487kB/s 237.372k items/s +BM_SetInsert/1024/10 33157 33648 21431 1.13369MB/s 290.225k items/s +``` + +The JSON format outputs human readable json split into two top level attributes. +The `context` attribute contains information about the run in general, including +information about the CPU and the date. +The `benchmarks` attribute contains a list of every benchmark run. Example json +output looks like: + +```json +{ + "context": { + "date": "2015/03/17-18:40:25", + "num_cpus": 40, + "mhz_per_cpu": 2801, + "cpu_scaling_enabled": false, + "build_type": "debug" + }, + "benchmarks": [ + { + "name": "BM_SetInsert/1024/1", + "iterations": 94877, + "real_time": 29275, + "cpu_time": 29836, + "bytes_per_second": 134066, + "items_per_second": 33516 + }, + { + "name": "BM_SetInsert/1024/8", + "iterations": 21609, + "real_time": 32317, + "cpu_time": 32429, + "bytes_per_second": 986770, + "items_per_second": 246693 + }, + { + "name": "BM_SetInsert/1024/10", + "iterations": 21393, + "real_time": 32724, + "cpu_time": 33355, + "bytes_per_second": 1199226, + "items_per_second": 299807 + } + ] +} +``` + +The CSV format outputs comma-separated values. The `context` is output on stderr +and the CSV itself on stdout. Example CSV output looks like: + +``` +name,iterations,real_time,cpu_time,bytes_per_second,items_per_second,label +"BM_SetInsert/1024/1",65465,17890.7,8407.45,475768,118942, +"BM_SetInsert/1024/8",116606,18810.1,9766.64,3.27646e+06,819115, +"BM_SetInsert/1024/10",106365,17238.4,8421.53,4.74973e+06,1.18743e+06, +``` + + + +## Output Files + +Write benchmark results to a file with the `--benchmark_out=` option +(or set `BENCHMARK_OUT`). Specify the output format with +`--benchmark_out_format={json|console|csv}` (or set +`BENCHMARK_OUT_FORMAT={json|console|csv}`). Note that the 'csv' reporter is +deprecated and the saved `.csv` file +[is not parsable](https://github.com/google/benchmark/issues/794) by csv +parsers. + +Specifying `--benchmark_out` does not suppress the console output. + + + +## Running Benchmarks + +Benchmarks are executed by running the produced binaries. Benchmarks binaries, +by default, accept options that may be specified either through their command +line interface or by setting environment variables before execution. For every +`--option_flag=` CLI switch, a corresponding environment variable +`OPTION_FLAG=` exist and is used as default if set (CLI switches always + prevails). A complete list of CLI options is available running benchmarks + with the `--help` switch. + + + +## Running a Subset of Benchmarks + +The `--benchmark_filter=` option (or `BENCHMARK_FILTER=` +environment variable) can be used to only run the benchmarks that match +the specified ``. For example: + +```bash +$ ./run_benchmarks.x --benchmark_filter=BM_memcpy/32 +Run on (1 X 2300 MHz CPU ) +2016-06-25 19:34:24 +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_memcpy/32 11 ns 11 ns 79545455 +BM_memcpy/32k 2181 ns 2185 ns 324074 +BM_memcpy/32 12 ns 12 ns 54687500 +BM_memcpy/32k 1834 ns 1837 ns 357143 +``` + + + +## Result comparison + +It is possible to compare the benchmarking results. +See [Additional Tooling Documentation](tools.md) + + + +## Extra Context + +Sometimes it's useful to add extra context to the content printed before the +results. By default this section includes information about the CPU on which +the benchmarks are running. If you do want to add more context, you can use +the `benchmark_context` command line flag: + +```bash +$ ./run_benchmarks --benchmark_context=pwd=`pwd` +Run on (1 x 2300 MHz CPU) +pwd: /home/user/benchmark/ +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_memcpy/32 11 ns 11 ns 79545455 +BM_memcpy/32k 2181 ns 2185 ns 324074 +``` + +You can get the same effect with the API: + +```c++ + benchmark::AddCustomContext("foo", "bar"); +``` + +Note that attempts to add a second value with the same key will fail with an +error message. + + + +## Runtime and Reporting Considerations + +When the benchmark binary is executed, each benchmark function is run serially. +The number of iterations to run is determined dynamically by running the +benchmark a few times and measuring the time taken and ensuring that the +ultimate result will be statistically stable. As such, faster benchmark +functions will be run for more iterations than slower benchmark functions, and +the number of iterations is thus reported. + +In all cases, the number of iterations for which the benchmark is run is +governed by the amount of time the benchmark takes. Concretely, the number of +iterations is at least one, not more than 1e9, until CPU time is greater than +the minimum time, or the wallclock time is 5x minimum time. The minimum time is +set per benchmark by calling `MinTime` on the registered benchmark object. + +Average timings are then reported over the iterations run. If multiple +repetitions are requested using the `--benchmark_repetitions` command-line +option, or at registration time, the benchmark function will be run several +times and statistical results across these repetitions will also be reported. + +As well as the per-benchmark entries, a preamble in the report will include +information about the machine on which the benchmarks are run. + + + +## Setup/Teardown + +Global setup/teardown specific to each benchmark can be done by +passing a callback to Setup/Teardown: + +The setup/teardown callbacks will be invoked once for each benchmark. +If the benchmark is multi-threaded (will run in k threads), they will be invoked exactly once before +each run with k threads. +If the benchmark uses different size groups of threads, the above will be true for each size group. + +Eg., + +```c++ +static void DoSetup(const benchmark::State& state) { +} + +static void DoTeardown(const benchmark::State& state) { +} + +static void BM_func(benchmark::State& state) {...} + +BENCHMARK(BM_func)->Arg(1)->Arg(3)->Threads(16)->Threads(32)->Setup(DoSetup)->Teardown(DoTeardown); + +``` + +In this example, `DoSetup` and `DoTearDown` will be invoked 4 times each, +specifically, once for each of this family: + - BM_func_Arg_1_Threads_16, BM_func_Arg_1_Threads_32 + - BM_func_Arg_3_Threads_16, BM_func_Arg_3_Threads_32 + + + +## Passing Arguments + +Sometimes a family of benchmarks can be implemented with just one routine that +takes an extra argument to specify which one of the family of benchmarks to +run. For example, the following code defines a family of benchmarks for +measuring the speed of `memcpy()` calls of different lengths: + +```c++ +static void BM_memcpy(benchmark::State& state) { + char* src = new char[state.range(0)]; + char* dst = new char[state.range(0)]; + memset(src, 'x', state.range(0)); + for (auto _ : state) + memcpy(dst, src, state.range(0)); + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); + delete[] src; + delete[] dst; +} +BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10); +``` + +The preceding code is quite repetitive, and can be replaced with the following +short-hand. The following invocation will pick a few appropriate arguments in +the specified range and will generate a benchmark for each such argument. + +```c++ +BENCHMARK(BM_memcpy)->Range(8, 8<<10); +``` + +By default the arguments in the range are generated in multiples of eight and +the command above selects [ 8, 64, 512, 4k, 8k ]. In the following code the +range multiplier is changed to multiples of two. + +```c++ +BENCHMARK(BM_memcpy)->RangeMultiplier(2)->Range(8, 8<<10); +``` + +Now arguments generated are [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ]. + +The preceding code shows a method of defining a sparse range. The following +example shows a method of defining a dense range. It is then used to benchmark +the performance of `std::vector` initialization for uniformly increasing sizes. + +```c++ +static void BM_DenseRange(benchmark::State& state) { + for(auto _ : state) { + std::vector v(state.range(0), state.range(0)); + benchmark::DoNotOptimize(v.data()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_DenseRange)->DenseRange(0, 1024, 128); +``` + +Now arguments generated are [ 0, 128, 256, 384, 512, 640, 768, 896, 1024 ]. + +You might have a benchmark that depends on two or more inputs. For example, the +following code defines a family of benchmarks for measuring the speed of set +insertion. + +```c++ +static void BM_SetInsert(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert) + ->Args({1<<10, 128}) + ->Args({2<<10, 128}) + ->Args({4<<10, 128}) + ->Args({8<<10, 128}) + ->Args({1<<10, 512}) + ->Args({2<<10, 512}) + ->Args({4<<10, 512}) + ->Args({8<<10, 512}); +``` + +The preceding code is quite repetitive, and can be replaced with the following +short-hand. The following macro will pick a few appropriate arguments in the +product of the two specified ranges and will generate a benchmark for each such +pair. + +{% raw %} +```c++ +BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {128, 512}}); +``` +{% endraw %} + +Some benchmarks may require specific argument values that cannot be expressed +with `Ranges`. In this case, `ArgsProduct` offers the ability to generate a +benchmark input for each combination in the product of the supplied vectors. + +{% raw %} +```c++ +BENCHMARK(BM_SetInsert) + ->ArgsProduct({{1<<10, 3<<10, 8<<10}, {20, 40, 60, 80}}) +// would generate the same benchmark arguments as +BENCHMARK(BM_SetInsert) + ->Args({1<<10, 20}) + ->Args({3<<10, 20}) + ->Args({8<<10, 20}) + ->Args({3<<10, 40}) + ->Args({8<<10, 40}) + ->Args({1<<10, 40}) + ->Args({1<<10, 60}) + ->Args({3<<10, 60}) + ->Args({8<<10, 60}) + ->Args({1<<10, 80}) + ->Args({3<<10, 80}) + ->Args({8<<10, 80}); +``` +{% endraw %} + +For the most common scenarios, helper methods for creating a list of +integers for a given sparse or dense range are provided. + +```c++ +BENCHMARK(BM_SetInsert) + ->ArgsProduct({ + benchmark::CreateRange(8, 128, /*multi=*/2), + benchmark::CreateDenseRange(1, 4, /*step=*/1) + }) +// would generate the same benchmark arguments as +BENCHMARK(BM_SetInsert) + ->ArgsProduct({ + {8, 16, 32, 64, 128}, + {1, 2, 3, 4} + }); +``` + +For more complex patterns of inputs, passing a custom function to `Apply` allows +programmatic specification of an arbitrary set of arguments on which to run the +benchmark. The following example enumerates a dense range on one parameter, +and a sparse range on the second. + +```c++ +static void CustomArguments(benchmark::internal::Benchmark* b) { + for (int i = 0; i <= 10; ++i) + for (int j = 32; j <= 1024*1024; j *= 8) + b->Args({i, j}); +} +BENCHMARK(BM_SetInsert)->Apply(CustomArguments); +``` + +### Passing Arbitrary Arguments to a Benchmark + +In C++11 it is possible to define a benchmark that takes an arbitrary number +of extra arguments. The `BENCHMARK_CAPTURE(func, test_case_name, ...args)` +macro creates a benchmark that invokes `func` with the `benchmark::State` as +the first argument followed by the specified `args...`. +The `test_case_name` is appended to the name of the benchmark and +should describe the values passed. + +```c++ +template +void BM_takes_args(benchmark::State& state, ExtraArgs&&... extra_args) { + [...] +} +// Registers a benchmark named "BM_takes_args/int_string_test" that passes +// the specified values to `extra_args`. +BENCHMARK_CAPTURE(BM_takes_args, int_string_test, 42, std::string("abc")); +``` + +Note that elements of `...args` may refer to global variables. Users should +avoid modifying global state inside of a benchmark. + + + +## Calculating Asymptotic Complexity (Big O) + +Asymptotic complexity might be calculated for a family of benchmarks. The +following code will calculate the coefficient for the high-order term in the +running time and the normalized root-mean square error of string comparison. + +```c++ +static void BM_StringCompare(benchmark::State& state) { + std::string s1(state.range(0), '-'); + std::string s2(state.range(0), '-'); + for (auto _ : state) { + benchmark::DoNotOptimize(s1.compare(s2)); + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_StringCompare) + ->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity(benchmark::oN); +``` + +As shown in the following invocation, asymptotic complexity might also be +calculated automatically. + +```c++ +BENCHMARK(BM_StringCompare) + ->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity(); +``` + +The following code will specify asymptotic complexity with a lambda function, +that might be used to customize high-order term calculation. + +```c++ +BENCHMARK(BM_StringCompare)->RangeMultiplier(2) + ->Range(1<<10, 1<<18)->Complexity([](benchmark::IterationCount n)->double{return n; }); +``` + + + +## Custom Benchmark Name + +You can change the benchmark's name as follows: + +```c++ +BENCHMARK(BM_memcpy)->Name("memcpy")->RangeMultiplier(2)->Range(8, 8<<10); +``` + +The invocation will execute the benchmark as before using `BM_memcpy` but changes +the prefix in the report to `memcpy`. + + + +## Templated Benchmarks + +This example produces and consumes messages of size `sizeof(v)` `range_x` +times. It also outputs throughput in the absence of multiprogramming. + +```c++ +template void BM_Sequential(benchmark::State& state) { + Q q; + typename Q::value_type v; + for (auto _ : state) { + for (int i = state.range(0); i--; ) + q.push(v); + for (int e = state.range(0); e--; ) + q.Wait(&v); + } + // actually messages, not bytes: + state.SetBytesProcessed( + static_cast(state.iterations())*state.range(0)); +} +// C++03 +BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue)->Range(1<<0, 1<<10); + +// C++11 or newer, you can use the BENCHMARK macro with template parameters: +BENCHMARK(BM_Sequential>)->Range(1<<0, 1<<10); + +``` + +Three macros are provided for adding benchmark templates. + +```c++ +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK(func<...>) // Takes any number of parameters. +#else // C++ < C++11 +#define BENCHMARK_TEMPLATE(func, arg1) +#endif +#define BENCHMARK_TEMPLATE1(func, arg1) +#define BENCHMARK_TEMPLATE2(func, arg1, arg2) +``` + + + +## Fixtures + +Fixture tests are created by first defining a type that derives from +`::benchmark::Fixture` and then creating/registering the tests using the +following macros: + +* `BENCHMARK_F(ClassName, Method)` +* `BENCHMARK_DEFINE_F(ClassName, Method)` +* `BENCHMARK_REGISTER_F(ClassName, Method)` + +For Example: + +```c++ +class MyFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + } + + void TearDown(const ::benchmark::State& state) { + } +}; + +BENCHMARK_F(MyFixture, FooTest)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_DEFINE_F(MyFixture, BarTest)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} +/* BarTest is NOT registered */ +BENCHMARK_REGISTER_F(MyFixture, BarTest)->Threads(2); +/* BarTest is now registered */ +``` + +### Templated Fixtures + +Also you can create templated fixture by using the following macros: + +* `BENCHMARK_TEMPLATE_F(ClassName, Method, ...)` +* `BENCHMARK_TEMPLATE_DEFINE_F(ClassName, Method, ...)` + +For example: + +```c++ +template +class MyFixture : public benchmark::Fixture {}; + +BENCHMARK_TEMPLATE_F(MyFixture, IntTest, int)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_TEMPLATE_DEFINE_F(MyFixture, DoubleTest, double)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_REGISTER_F(MyFixture, DoubleTest)->Threads(2); +``` + + + +## Custom Counters + +You can add your own counters with user-defined names. The example below +will add columns "Foo", "Bar" and "Baz" in its output: + +```c++ +static void UserCountersExample1(benchmark::State& state) { + double numFoos = 0, numBars = 0, numBazs = 0; + for (auto _ : state) { + // ... count Foo,Bar,Baz events + } + state.counters["Foo"] = numFoos; + state.counters["Bar"] = numBars; + state.counters["Baz"] = numBazs; +} +``` + +The `state.counters` object is a `std::map` with `std::string` keys +and `Counter` values. The latter is a `double`-like class, via an implicit +conversion to `double&`. Thus you can use all of the standard arithmetic +assignment operators (`=,+=,-=,*=,/=`) to change the value of each counter. + +In multithreaded benchmarks, each counter is set on the calling thread only. +When the benchmark finishes, the counters from each thread will be summed; +the resulting sum is the value which will be shown for the benchmark. + +The `Counter` constructor accepts three parameters: the value as a `double` +; a bit flag which allows you to show counters as rates, and/or as per-thread +iteration, and/or as per-thread averages, and/or iteration invariants, +and/or finally inverting the result; and a flag specifying the 'unit' - i.e. +is 1k a 1000 (default, `benchmark::Counter::OneK::kIs1000`), or 1024 +(`benchmark::Counter::OneK::kIs1024`)? + +```c++ + // sets a simple counter + state.counters["Foo"] = numFoos; + + // Set the counter as a rate. It will be presented divided + // by the duration of the benchmark. + // Meaning: per one second, how many 'foo's are processed? + state.counters["FooRate"] = Counter(numFoos, benchmark::Counter::kIsRate); + + // Set the counter as a rate. It will be presented divided + // by the duration of the benchmark, and the result inverted. + // Meaning: how many seconds it takes to process one 'foo'? + state.counters["FooInvRate"] = Counter(numFoos, benchmark::Counter::kIsRate | benchmark::Counter::kInvert); + + // Set the counter as a thread-average quantity. It will + // be presented divided by the number of threads. + state.counters["FooAvg"] = Counter(numFoos, benchmark::Counter::kAvgThreads); + + // There's also a combined flag: + state.counters["FooAvgRate"] = Counter(numFoos,benchmark::Counter::kAvgThreadsRate); + + // This says that we process with the rate of state.range(0) bytes every iteration: + state.counters["BytesProcessed"] = Counter(state.range(0), benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::OneK::kIs1024); +``` + +When you're compiling in C++11 mode or later you can use `insert()` with +`std::initializer_list`: + +{% raw %} +```c++ + // With C++11, this can be done: + state.counters.insert({{"Foo", numFoos}, {"Bar", numBars}, {"Baz", numBazs}}); + // ... instead of: + state.counters["Foo"] = numFoos; + state.counters["Bar"] = numBars; + state.counters["Baz"] = numBazs; +``` +{% endraw %} + +### Counter Reporting + +When using the console reporter, by default, user counters are printed at +the end after the table, the same way as ``bytes_processed`` and +``items_processed``. This is best for cases in which there are few counters, +or where there are only a couple of lines per benchmark. Here's an example of +the default output: + +``` +------------------------------------------------------------------------------ +Benchmark Time CPU Iterations UserCounters... +------------------------------------------------------------------------------ +BM_UserCounter/threads:8 2248 ns 10277 ns 68808 Bar=16 Bat=40 Baz=24 Foo=8 +BM_UserCounter/threads:1 9797 ns 9788 ns 71523 Bar=2 Bat=5 Baz=3 Foo=1024m +BM_UserCounter/threads:2 4924 ns 9842 ns 71036 Bar=4 Bat=10 Baz=6 Foo=2 +BM_UserCounter/threads:4 2589 ns 10284 ns 68012 Bar=8 Bat=20 Baz=12 Foo=4 +BM_UserCounter/threads:8 2212 ns 10287 ns 68040 Bar=16 Bat=40 Baz=24 Foo=8 +BM_UserCounter/threads:16 1782 ns 10278 ns 68144 Bar=32 Bat=80 Baz=48 Foo=16 +BM_UserCounter/threads:32 1291 ns 10296 ns 68256 Bar=64 Bat=160 Baz=96 Foo=32 +BM_UserCounter/threads:4 2615 ns 10307 ns 68040 Bar=8 Bat=20 Baz=12 Foo=4 +BM_Factorial 26 ns 26 ns 26608979 40320 +BM_Factorial/real_time 26 ns 26 ns 26587936 40320 +BM_CalculatePiRange/1 16 ns 16 ns 45704255 0 +BM_CalculatePiRange/8 73 ns 73 ns 9520927 3.28374 +BM_CalculatePiRange/64 609 ns 609 ns 1140647 3.15746 +BM_CalculatePiRange/512 4900 ns 4901 ns 142696 3.14355 +``` + +If this doesn't suit you, you can print each counter as a table column by +passing the flag `--benchmark_counters_tabular=true` to the benchmark +application. This is best for cases in which there are a lot of counters, or +a lot of lines per individual benchmark. Note that this will trigger a +reprinting of the table header any time the counter set changes between +individual benchmarks. Here's an example of corresponding output when +`--benchmark_counters_tabular=true` is passed: + +``` +--------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations Bar Bat Baz Foo +--------------------------------------------------------------------------------------- +BM_UserCounter/threads:8 2198 ns 9953 ns 70688 16 40 24 8 +BM_UserCounter/threads:1 9504 ns 9504 ns 73787 2 5 3 1 +BM_UserCounter/threads:2 4775 ns 9550 ns 72606 4 10 6 2 +BM_UserCounter/threads:4 2508 ns 9951 ns 70332 8 20 12 4 +BM_UserCounter/threads:8 2055 ns 9933 ns 70344 16 40 24 8 +BM_UserCounter/threads:16 1610 ns 9946 ns 70720 32 80 48 16 +BM_UserCounter/threads:32 1192 ns 9948 ns 70496 64 160 96 32 +BM_UserCounter/threads:4 2506 ns 9949 ns 70332 8 20 12 4 +-------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------- +BM_Factorial 26 ns 26 ns 26392245 40320 +BM_Factorial/real_time 26 ns 26 ns 26494107 40320 +BM_CalculatePiRange/1 15 ns 15 ns 45571597 0 +BM_CalculatePiRange/8 74 ns 74 ns 9450212 3.28374 +BM_CalculatePiRange/64 595 ns 595 ns 1173901 3.15746 +BM_CalculatePiRange/512 4752 ns 4752 ns 147380 3.14355 +BM_CalculatePiRange/4k 37970 ns 37972 ns 18453 3.14184 +BM_CalculatePiRange/32k 303733 ns 303744 ns 2305 3.14162 +BM_CalculatePiRange/256k 2434095 ns 2434186 ns 288 3.1416 +BM_CalculatePiRange/1024k 9721140 ns 9721413 ns 71 3.14159 +BM_CalculatePi/threads:8 2255 ns 9943 ns 70936 +``` + +Note above the additional header printed when the benchmark changes from +``BM_UserCounter`` to ``BM_Factorial``. This is because ``BM_Factorial`` does +not have the same counter set as ``BM_UserCounter``. + + + +## Multithreaded Benchmarks + +In a multithreaded test (benchmark invoked by multiple threads simultaneously), +it is guaranteed that none of the threads will start until all have reached +the start of the benchmark loop, and all will have finished before any thread +exits the benchmark loop. (This behavior is also provided by the `KeepRunning()` +API) As such, any global setup or teardown can be wrapped in a check against the thread +index: + +```c++ +static void BM_MultiThreaded(benchmark::State& state) { + if (state.thread_index() == 0) { + // Setup code here. + } + for (auto _ : state) { + // Run the test as normal. + } + if (state.thread_index() == 0) { + // Teardown code here. + } +} +BENCHMARK(BM_MultiThreaded)->Threads(2); +``` + +If the benchmarked code itself uses threads and you want to compare it to +single-threaded code, you may want to use real-time ("wallclock") measurements +for latency comparisons: + +```c++ +BENCHMARK(BM_test)->Range(8, 8<<10)->UseRealTime(); +``` + +Without `UseRealTime`, CPU time is used by default. + + + +## CPU Timers + +By default, the CPU timer only measures the time spent by the main thread. +If the benchmark itself uses threads internally, this measurement may not +be what you are looking for. Instead, there is a way to measure the total +CPU usage of the process, by all the threads. + +```c++ +void callee(int i); + +static void MyMain(int size) { +#pragma omp parallel for + for(int i = 0; i < size; i++) + callee(i); +} + +static void BM_OpenMP(benchmark::State& state) { + for (auto _ : state) + MyMain(state.range(0)); +} + +// Measure the time spent by the main thread, use it to decide for how long to +// run the benchmark loop. Depending on the internal implementation detail may +// measure to anywhere from near-zero (the overhead spent before/after work +// handoff to worker thread[s]) to the whole single-thread time. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10); + +// Measure the user-visible time, the wall clock (literally, the time that +// has passed on the clock on the wall), use it to decide for how long to +// run the benchmark loop. This will always be meaningful, an will match the +// time spent by the main thread in single-threaded case, in general decreasing +// with the number of internal threads doing the work. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->UseRealTime(); + +// Measure the total CPU consumption, use it to decide for how long to +// run the benchmark loop. This will always measure to no less than the +// time spent by the main thread in single-threaded case. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime(); + +// A mixture of the last two. Measure the total CPU consumption, but use the +// wall clock to decide for how long to run the benchmark loop. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime()->UseRealTime(); +``` + +### Controlling Timers + +Normally, the entire duration of the work loop (`for (auto _ : state) {}`) +is measured. But sometimes, it is necessary to do some work inside of +that loop, every iteration, but without counting that time to the benchmark time. +That is possible, although it is not recommended, since it has high overhead. + +{% raw %} +```c++ +static void BM_SetInsert_With_Timer_Control(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); // Stop timers. They will not count until they are resumed. + data = ConstructRandomSet(state.range(0)); // Do something that should not be measured + state.ResumeTiming(); // And resume timers. They are now counting again. + // The rest will be measured. + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert_With_Timer_Control)->Ranges({{1<<10, 8<<10}, {128, 512}}); +``` +{% endraw %} + + + +## Manual Timing + +For benchmarking something for which neither CPU time nor real-time are +correct or accurate enough, completely manual timing is supported using +the `UseManualTime` function. + +When `UseManualTime` is used, the benchmarked code must call +`SetIterationTime` once per iteration of the benchmark loop to +report the manually measured time. + +An example use case for this is benchmarking GPU execution (e.g. OpenCL +or CUDA kernels, OpenGL or Vulkan or Direct3D draw calls), which cannot +be accurately measured using CPU time or real-time. Instead, they can be +measured accurately using a dedicated API, and these measurement results +can be reported back with `SetIterationTime`. + +```c++ +static void BM_ManualTiming(benchmark::State& state) { + int microseconds = state.range(0); + std::chrono::duration sleep_duration { + static_cast(microseconds) + }; + + for (auto _ : state) { + auto start = std::chrono::high_resolution_clock::now(); + // Simulate some useful workload with a sleep + std::this_thread::sleep_for(sleep_duration); + auto end = std::chrono::high_resolution_clock::now(); + + auto elapsed_seconds = + std::chrono::duration_cast>( + end - start); + + state.SetIterationTime(elapsed_seconds.count()); + } +} +BENCHMARK(BM_ManualTiming)->Range(1, 1<<17)->UseManualTime(); +``` + + + +## Setting the Time Unit + +If a benchmark runs a few milliseconds it may be hard to visually compare the +measured times, since the output data is given in nanoseconds per default. In +order to manually set the time unit, you can specify it manually: + +```c++ +BENCHMARK(BM_test)->Unit(benchmark::kMillisecond); +``` + + + +## Preventing Optimization + +To prevent a value or expression from being optimized away by the compiler +the `benchmark::DoNotOptimize(...)` and `benchmark::ClobberMemory()` +functions can be used. + +```c++ +static void BM_test(benchmark::State& state) { + for (auto _ : state) { + int x = 0; + for (int i=0; i < 64; ++i) { + benchmark::DoNotOptimize(x += i); + } + } +} +``` + +`DoNotOptimize()` forces the *result* of `` to be stored in either +memory or a register. For GNU based compilers it acts as read/write barrier +for global memory. More specifically it forces the compiler to flush pending +writes to memory and reload any other values as necessary. + +Note that `DoNotOptimize()` does not prevent optimizations on `` +in any way. `` may even be removed entirely when the result is already +known. For example: + +```c++ + /* Example 1: `` is removed entirely. */ + int foo(int x) { return x + 42; } + while (...) DoNotOptimize(foo(0)); // Optimized to DoNotOptimize(42); + + /* Example 2: Result of '' is only reused */ + int bar(int) __attribute__((const)); + while (...) DoNotOptimize(bar(0)); // Optimized to: + // int __result__ = bar(0); + // while (...) DoNotOptimize(__result__); +``` + +The second tool for preventing optimizations is `ClobberMemory()`. In essence +`ClobberMemory()` forces the compiler to perform all pending writes to global +memory. Memory managed by block scope objects must be "escaped" using +`DoNotOptimize(...)` before it can be clobbered. In the below example +`ClobberMemory()` prevents the call to `v.push_back(42)` from being optimized +away. + +```c++ +static void BM_vector_push_back(benchmark::State& state) { + for (auto _ : state) { + std::vector v; + v.reserve(1); + benchmark::DoNotOptimize(v.data()); // Allow v.data() to be clobbered. + v.push_back(42); + benchmark::ClobberMemory(); // Force 42 to be written to memory. + } +} +``` + +Note that `ClobberMemory()` is only available for GNU or MSVC based compilers. + + + +## Statistics: Reporting the Mean, Median and Standard Deviation / Coefficient of variation of Repeated Benchmarks + +By default each benchmark is run once and that single result is reported. +However benchmarks are often noisy and a single result may not be representative +of the overall behavior. For this reason it's possible to repeatedly rerun the +benchmark. + +The number of runs of each benchmark is specified globally by the +`--benchmark_repetitions` flag or on a per benchmark basis by calling +`Repetitions` on the registered benchmark object. When a benchmark is run more +than once the mean, median, standard deviation and coefficient of variation +of the runs will be reported. + +Additionally the `--benchmark_report_aggregates_only={true|false}`, +`--benchmark_display_aggregates_only={true|false}` flags or +`ReportAggregatesOnly(bool)`, `DisplayAggregatesOnly(bool)` functions can be +used to change how repeated tests are reported. By default the result of each +repeated run is reported. When `report aggregates only` option is `true`, +only the aggregates (i.e. mean, median, standard deviation and coefficient +of variation, maybe complexity measurements if they were requested) of the runs +is reported, to both the reporters - standard output (console), and the file. +However when only the `display aggregates only` option is `true`, +only the aggregates are displayed in the standard output, while the file +output still contains everything. +Calling `ReportAggregatesOnly(bool)` / `DisplayAggregatesOnly(bool)` on a +registered benchmark object overrides the value of the appropriate flag for that +benchmark. + + + +## Custom Statistics + +While having these aggregates is nice, this may not be enough for everyone. +For example you may want to know what the largest observation is, e.g. because +you have some real-time constraints. This is easy. The following code will +specify a custom statistic to be calculated, defined by a lambda function. + +```c++ +void BM_spin_empty(benchmark::State& state) { + for (auto _ : state) { + for (int x = 0; x < state.range(0); ++x) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK(BM_spin_empty) + ->ComputeStatistics("max", [](const std::vector& v) -> double { + return *(std::max_element(std::begin(v), std::end(v))); + }) + ->Arg(512); +``` + +While usually the statistics produce values in time units, +you can also produce percentages: + +```c++ +void BM_spin_empty(benchmark::State& state) { + for (auto _ : state) { + for (int x = 0; x < state.range(0); ++x) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK(BM_spin_empty) + ->ComputeStatistics("ratio", [](const std::vector& v) -> double { + return std::begin(v) / std::end(v); + }, benchmark::StatisticUnit::Percentage) + ->Arg(512); +``` + + + +## Using RegisterBenchmark(name, fn, args...) + +The `RegisterBenchmark(name, func, args...)` function provides an alternative +way to create and register benchmarks. +`RegisterBenchmark(name, func, args...)` creates, registers, and returns a +pointer to a new benchmark with the specified `name` that invokes +`func(st, args...)` where `st` is a `benchmark::State` object. + +Unlike the `BENCHMARK` registration macros, which can only be used at the global +scope, the `RegisterBenchmark` can be called anywhere. This allows for +benchmark tests to be registered programmatically. + +Additionally `RegisterBenchmark` allows any callable object to be registered +as a benchmark. Including capturing lambdas and function objects. + +For Example: +```c++ +auto BM_test = [](benchmark::State& st, auto Inputs) { /* ... */ }; + +int main(int argc, char** argv) { + for (auto& test_input : { /* ... */ }) + benchmark::RegisterBenchmark(test_input.name(), BM_test, test_input); + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); +} +``` + + + +## Exiting with an Error + +When errors caused by external influences, such as file I/O and network +communication, occur within a benchmark the +`State::SkipWithError(const char* msg)` function can be used to skip that run +of benchmark and report the error. Note that only future iterations of the +`KeepRunning()` are skipped. For the ranged-for version of the benchmark loop +Users must explicitly exit the loop, otherwise all iterations will be performed. +Users may explicitly return to exit the benchmark immediately. + +The `SkipWithError(...)` function may be used at any point within the benchmark, +including before and after the benchmark loop. Moreover, if `SkipWithError(...)` +has been used, it is not required to reach the benchmark loop and one may return +from the benchmark function early. + +For example: + +```c++ +static void BM_test(benchmark::State& state) { + auto resource = GetResource(); + if (!resource.good()) { + state.SkipWithError("Resource is not good!"); + // KeepRunning() loop will not be entered. + } + while (state.KeepRunning()) { + auto data = resource.read_data(); + if (!resource.good()) { + state.SkipWithError("Failed to read data!"); + break; // Needed to skip the rest of the iteration. + } + do_stuff(data); + } +} + +static void BM_test_ranged_fo(benchmark::State & state) { + auto resource = GetResource(); + if (!resource.good()) { + state.SkipWithError("Resource is not good!"); + return; // Early return is allowed when SkipWithError() has been used. + } + for (auto _ : state) { + auto data = resource.read_data(); + if (!resource.good()) { + state.SkipWithError("Failed to read data!"); + break; // REQUIRED to prevent all further iterations. + } + do_stuff(data); + } +} +``` + + +## A Faster KeepRunning Loop + +In C++11 mode, a ranged-based for loop should be used in preference to +the `KeepRunning` loop for running the benchmarks. For example: + +```c++ +static void BM_Fast(benchmark::State &state) { + for (auto _ : state) { + FastOperation(); + } +} +BENCHMARK(BM_Fast); +``` + +The reason the ranged-for loop is faster than using `KeepRunning`, is +because `KeepRunning` requires a memory load and store of the iteration count +ever iteration, whereas the ranged-for variant is able to keep the iteration count +in a register. + +For example, an empty inner loop of using the ranged-based for method looks like: + +```asm +# Loop Init + mov rbx, qword ptr [r14 + 104] + call benchmark::State::StartKeepRunning() + test rbx, rbx + je .LoopEnd +.LoopHeader: # =>This Inner Loop Header: Depth=1 + add rbx, -1 + jne .LoopHeader +.LoopEnd: +``` + +Compared to an empty `KeepRunning` loop, which looks like: + +```asm +.LoopHeader: # in Loop: Header=BB0_3 Depth=1 + cmp byte ptr [rbx], 1 + jne .LoopInit +.LoopBody: # =>This Inner Loop Header: Depth=1 + mov rax, qword ptr [rbx + 8] + lea rcx, [rax + 1] + mov qword ptr [rbx + 8], rcx + cmp rax, qword ptr [rbx + 104] + jb .LoopHeader + jmp .LoopEnd +.LoopInit: + mov rdi, rbx + call benchmark::State::StartKeepRunning() + jmp .LoopBody +.LoopEnd: +``` + +Unless C++03 compatibility is required, the ranged-for variant of writing +the benchmark loop should be preferred. + + + +## Disabling CPU Frequency Scaling + +If you see this error: + +``` +***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. +``` + +you might want to disable the CPU frequency scaling while running the benchmark: + +```bash +sudo cpupower frequency-set --governor performance +./mybench +sudo cpupower frequency-set --governor powersave +``` diff --git a/bridge/third_party/benchmark/include/benchmark/benchmark.h b/bridge/third_party/benchmark/include/benchmark/benchmark.h new file mode 100644 index 0000000000..c8ced38771 --- /dev/null +++ b/bridge/third_party/benchmark/include/benchmark/benchmark.h @@ -0,0 +1,1765 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Support for registering benchmarks for functions. + +/* Example usage: +// Define a function that executes the code to be measured a +// specified number of times: +static void BM_StringCreation(benchmark::State& state) { + for (auto _ : state) + std::string empty_string; +} + +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + for (auto _ : state) + std::string copy(x); +} +BENCHMARK(BM_StringCopy); + +// Augment the main() program to invoke benchmarks if specified +// via the --benchmark_filter command line flag. E.g., +// my_unittest --benchmark_filter=all +// my_unittest --benchmark_filter=BM_StringCreation +// my_unittest --benchmark_filter=String +// my_unittest --benchmark_filter='Copy|Creation' +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} + +// Sometimes a family of microbenchmarks can be implemented with +// just one routine that takes an extra argument to specify which +// one of the family of benchmarks to run. For example, the following +// code defines a family of microbenchmarks for measuring the speed +// of memcpy() calls of different lengths: + +static void BM_memcpy(benchmark::State& state) { + char* src = new char[state.range(0)]; char* dst = new char[state.range(0)]; + memset(src, 'x', state.range(0)); + for (auto _ : state) + memcpy(dst, src, state.range(0)); + state.SetBytesProcessed(state.iterations() * state.range(0)); + delete[] src; delete[] dst; +} +BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10); + +// The preceding code is quite repetitive, and can be replaced with the +// following short-hand. The following invocation will pick a few +// appropriate arguments in the specified range and will generate a +// microbenchmark for each such argument. +BENCHMARK(BM_memcpy)->Range(8, 8<<10); + +// You might have a microbenchmark that depends on two inputs. For +// example, the following code defines a family of microbenchmarks for +// measuring the speed of set insertion. +static void BM_SetInsert(benchmark::State& state) { + set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert) + ->Args({1<<10, 128}) + ->Args({2<<10, 128}) + ->Args({4<<10, 128}) + ->Args({8<<10, 128}) + ->Args({1<<10, 512}) + ->Args({2<<10, 512}) + ->Args({4<<10, 512}) + ->Args({8<<10, 512}); + +// The preceding code is quite repetitive, and can be replaced with +// the following short-hand. The following macro will pick a few +// appropriate arguments in the product of the two specified ranges +// and will generate a microbenchmark for each such pair. +BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {128, 512}}); + +// For more complex patterns of inputs, passing a custom function +// to Apply allows programmatic specification of an +// arbitrary set of arguments to run the microbenchmark on. +// The following example enumerates a dense range on +// one parameter, and a sparse range on the second. +static void CustomArguments(benchmark::internal::Benchmark* b) { + for (int i = 0; i <= 10; ++i) + for (int j = 32; j <= 1024*1024; j *= 8) + b->Args({i, j}); +} +BENCHMARK(BM_SetInsert)->Apply(CustomArguments); + +// Templated microbenchmarks work the same way: +// Produce then consume 'size' messages 'iters' times +// Measures throughput in the absence of multiprogramming. +template int BM_Sequential(benchmark::State& state) { + Q q; + typename Q::value_type v; + for (auto _ : state) { + for (int i = state.range(0); i--; ) + q.push(v); + for (int e = state.range(0); e--; ) + q.Wait(&v); + } + // actually messages, not bytes: + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue)->Range(1<<0, 1<<10); + +Use `Benchmark::MinTime(double t)` to set the minimum time used to run the +benchmark. This option overrides the `benchmark_min_time` flag. + +void BM_test(benchmark::State& state) { + ... body ... +} +BENCHMARK(BM_test)->MinTime(2.0); // Run for at least 2 seconds. + +In a multithreaded test, it is guaranteed that none of the threads will start +until all have reached the loop start, and all will have finished before any +thread exits the loop body. As such, any global setup or teardown you want to +do can be wrapped in a check against the thread index: + +static void BM_MultiThreaded(benchmark::State& state) { + if (state.thread_index() == 0) { + // Setup code here. + } + for (auto _ : state) { + // Run the test as normal. + } + if (state.thread_index() == 0) { + // Teardown code here. + } +} +BENCHMARK(BM_MultiThreaded)->Threads(4); + + +If a benchmark runs a few milliseconds it may be hard to visually compare the +measured times, since the output data is given in nanoseconds per default. In +order to manually set the time unit, you can specify it manually: + +BENCHMARK(BM_test)->Unit(benchmark::kMillisecond); +*/ + +#ifndef BENCHMARK_BENCHMARK_H_ +#define BENCHMARK_BENCHMARK_H_ + +// The _MSVC_LANG check should detect Visual Studio 2015 Update 3 and newer. +#if __cplusplus >= 201103L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L) +#define BENCHMARK_HAS_CXX11 +#endif + +// This _MSC_VER check should detect VS 2017 v15.3 and newer. +#if __cplusplus >= 201703L || \ + (defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L) +#define BENCHMARK_HAS_CXX17 +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BENCHMARK_HAS_CXX11) +#include +#include +#include +#include +#endif + +#if defined(_MSC_VER) +#include // for _ReadWriteBarrier +#endif + +#ifndef BENCHMARK_HAS_CXX11 +#define BENCHMARK_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + TypeName& operator=(const TypeName&) +#else +#define BENCHMARK_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete +#endif + +#ifdef BENCHMARK_HAS_CXX17 +#define BENCHMARK_UNUSED [[maybe_unused]] +#elif defined(__GNUC__) || defined(__clang__) +#define BENCHMARK_UNUSED __attribute__((unused)) +#else +#define BENCHMARK_UNUSED +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define BENCHMARK_ALWAYS_INLINE __attribute__((always_inline)) +#define BENCHMARK_NOEXCEPT noexcept +#define BENCHMARK_NOEXCEPT_OP(x) noexcept(x) +#elif defined(_MSC_VER) && !defined(__clang__) +#define BENCHMARK_ALWAYS_INLINE __forceinline +#if _MSC_VER >= 1900 +#define BENCHMARK_NOEXCEPT noexcept +#define BENCHMARK_NOEXCEPT_OP(x) noexcept(x) +#else +#define BENCHMARK_NOEXCEPT +#define BENCHMARK_NOEXCEPT_OP(x) +#endif +#define __func__ __FUNCTION__ +#else +#define BENCHMARK_ALWAYS_INLINE +#define BENCHMARK_NOEXCEPT +#define BENCHMARK_NOEXCEPT_OP(x) +#endif + +#define BENCHMARK_INTERNAL_TOSTRING2(x) #x +#define BENCHMARK_INTERNAL_TOSTRING(x) BENCHMARK_INTERNAL_TOSTRING2(x) + +// clang-format off +#if defined(__GNUC__) || defined(__clang__) +#define BENCHMARK_BUILTIN_EXPECT(x, y) __builtin_expect(x, y) +#define BENCHMARK_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) +#define BENCHMARK_DISABLE_DEPRECATED_WARNING \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#define BENCHMARK_RESTORE_DEPRECATED_WARNING _Pragma("GCC diagnostic pop") +#else +#define BENCHMARK_BUILTIN_EXPECT(x, y) x +#define BENCHMARK_DEPRECATED_MSG(msg) +#define BENCHMARK_WARNING_MSG(msg) \ + __pragma(message(__FILE__ "(" BENCHMARK_INTERNAL_TOSTRING( \ + __LINE__) ") : warning note: " msg)) +#define BENCHMARK_DISABLE_DEPRECATED_WARNING +#define BENCHMARK_RESTORE_DEPRECATED_WARNING +#endif +// clang-format on + +#if defined(__GNUC__) && !defined(__clang__) +#define BENCHMARK_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if defined(__GNUC__) || __has_builtin(__builtin_unreachable) +#define BENCHMARK_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) +#define BENCHMARK_UNREACHABLE() __assume(false) +#else +#define BENCHMARK_UNREACHABLE() ((void)0) +#endif + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_OVERRIDE override +#else +#define BENCHMARK_OVERRIDE +#endif + +namespace benchmark { +class BenchmarkReporter; + +void Initialize(int* argc, char** argv); +void Shutdown(); + +// Report to stdout all arguments in 'argv' as unrecognized except the first. +// Returns true there is at least on unrecognized argument (i.e. 'argc' > 1). +bool ReportUnrecognizedArguments(int argc, char** argv); + +// Returns the current value of --benchmark_filter. +std::string GetBenchmarkFilter(); + +// Generate a list of benchmarks matching the specified --benchmark_filter flag +// and if --benchmark_list_tests is specified return after printing the name +// of each matching benchmark. Otherwise run each matching benchmark and +// report the results. +// +// spec : Specify the benchmarks to run. If users do not specify this arg, +// then the value of FLAGS_benchmark_filter +// will be used. +// +// The second and third overload use the specified 'display_reporter' and +// 'file_reporter' respectively. 'file_reporter' will write to the file +// specified +// by '--benchmark_output'. If '--benchmark_output' is not given the +// 'file_reporter' is ignored. +// +// RETURNS: The number of matching benchmarks. +size_t RunSpecifiedBenchmarks(); +size_t RunSpecifiedBenchmarks(std::string spec); + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter); +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + std::string spec); + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter); +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter, + std::string spec); + +// If a MemoryManager is registered (via RegisterMemoryManager()), +// it can be used to collect and report allocation metrics for a run of the +// benchmark. +class MemoryManager { + public: + static const int64_t TombstoneValue; + + struct Result { + Result() + : num_allocs(0), + max_bytes_used(0), + total_allocated_bytes(TombstoneValue), + net_heap_growth(TombstoneValue) {} + + // The number of allocations made in total between Start and Stop. + int64_t num_allocs; + + // The peak memory use between Start and Stop. + int64_t max_bytes_used; + + // The total memory allocated, in bytes, between Start and Stop. + // Init'ed to TombstoneValue if metric not available. + int64_t total_allocated_bytes; + + // The net changes in memory, in bytes, between Start and Stop. + // ie., total_allocated_bytes - total_deallocated_bytes. + // Init'ed to TombstoneValue if metric not available. + int64_t net_heap_growth; + }; + + virtual ~MemoryManager() {} + + // Implement this to start recording allocation information. + virtual void Start() = 0; + + // Implement this to stop recording and fill out the given Result structure. + BENCHMARK_DEPRECATED_MSG("Use Stop(Result&) instead") + virtual void Stop(Result* result) = 0; + + // FIXME(vyng): Make this pure virtual once we've migrated current users. + BENCHMARK_DISABLE_DEPRECATED_WARNING + virtual void Stop(Result& result) { Stop(&result); } + BENCHMARK_RESTORE_DEPRECATED_WARNING +}; + +// Register a MemoryManager instance that will be used to collect and report +// allocation measurements for benchmark runs. +void RegisterMemoryManager(MemoryManager* memory_manager); + +// Add a key-value pair to output as part of the context stanza in the report. +void AddCustomContext(const std::string& key, const std::string& value); + +namespace internal { +class Benchmark; +class BenchmarkImp; +class BenchmarkFamilies; + +void UseCharPointer(char const volatile*); + +// Take ownership of the pointer and register the benchmark. Return the +// registered benchmark. +Benchmark* RegisterBenchmarkInternal(Benchmark*); + +// Ensure that the standard streams are properly initialized in every TU. +int InitializeStreams(); +BENCHMARK_UNUSED static int stream_init_anchor = InitializeStreams(); + +} // namespace internal + +#if (!defined(__GNUC__) && !defined(__clang__)) || defined(__pnacl__) || \ + defined(__EMSCRIPTEN__) +#define BENCHMARK_HAS_NO_INLINE_ASSEMBLY +#endif + +// Force the compiler to flush pending writes to global memory. Acts as an +// effective read/write barrier +#ifdef BENCHMARK_HAS_CXX11 +inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() { + std::atomic_signal_fence(std::memory_order_acq_rel); +} +#endif + +// The DoNotOptimize(...) function can be used to prevent a value or +// expression from being optimized away by the compiler. This function is +// intended to add little to no overhead. +// See: https://youtu.be/nXaxk27zwlk?t=2441 +#ifndef BENCHMARK_HAS_NO_INLINE_ASSEMBLY +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + asm volatile("" : : "r,m"(value) : "memory"); +} + +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) { +#if defined(__clang__) + asm volatile("" : "+r,m"(value) : : "memory"); +#else + asm volatile("" : "+m,r"(value) : : "memory"); +#endif +} + +#ifndef BENCHMARK_HAS_CXX11 +inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() { + asm volatile("" : : : "memory"); +} +#endif +#elif defined(_MSC_VER) +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + internal::UseCharPointer(&reinterpret_cast(value)); + _ReadWriteBarrier(); +} + +#ifndef BENCHMARK_HAS_CXX11 +inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() { _ReadWriteBarrier(); } +#endif +#else +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + internal::UseCharPointer(&reinterpret_cast(value)); +} +// FIXME Add ClobberMemory() for non-gnu and non-msvc compilers, before C++11. +#endif + +// This class is used for user-defined counters. +class Counter { + public: + enum Flags { + kDefaults = 0, + // Mark the counter as a rate. It will be presented divided + // by the duration of the benchmark. + kIsRate = 1 << 0, + // Mark the counter as a thread-average quantity. It will be + // presented divided by the number of threads. + kAvgThreads = 1 << 1, + // Mark the counter as a thread-average rate. See above. + kAvgThreadsRate = kIsRate | kAvgThreads, + // Mark the counter as a constant value, valid/same for *every* iteration. + // When reporting, it will be *multiplied* by the iteration count. + kIsIterationInvariant = 1 << 2, + // Mark the counter as a constant rate. + // When reporting, it will be *multiplied* by the iteration count + // and then divided by the duration of the benchmark. + kIsIterationInvariantRate = kIsRate | kIsIterationInvariant, + // Mark the counter as a iteration-average quantity. + // It will be presented divided by the number of iterations. + kAvgIterations = 1 << 3, + // Mark the counter as a iteration-average rate. See above. + kAvgIterationsRate = kIsRate | kAvgIterations, + + // In the end, invert the result. This is always done last! + kInvert = 1 << 31 + }; + + enum OneK { + // 1'000 items per 1k + kIs1000 = 1000, + // 1'024 items per 1k + kIs1024 = 1024 + }; + + double value; + Flags flags; + OneK oneK; + + BENCHMARK_ALWAYS_INLINE + Counter(double v = 0., Flags f = kDefaults, OneK k = kIs1000) + : value(v), flags(f), oneK(k) {} + + BENCHMARK_ALWAYS_INLINE operator double const &() const { return value; } + BENCHMARK_ALWAYS_INLINE operator double&() { return value; } +}; + +// A helper for user code to create unforeseen combinations of Flags, without +// having to do this cast manually each time, or providing this operator. +Counter::Flags inline operator|(const Counter::Flags& LHS, + const Counter::Flags& RHS) { + return static_cast(static_cast(LHS) | + static_cast(RHS)); +} + +// This is the container for the user-defined counters. +typedef std::map UserCounters; + +// TimeUnit is passed to a benchmark in order to specify the order of magnitude +// for the measured time. +enum TimeUnit { kNanosecond, kMicrosecond, kMillisecond, kSecond }; + +// BigO is passed to a benchmark in order to specify the asymptotic +// computational +// complexity for the benchmark. In case oAuto is selected, complexity will be +// calculated automatically to the best fit. +enum BigO { oNone, o1, oN, oNSquared, oNCubed, oLogN, oNLogN, oAuto, oLambda }; + +typedef uint64_t IterationCount; + +enum StatisticUnit { kTime, kPercentage }; + +// BigOFunc is passed to a benchmark in order to specify the asymptotic +// computational complexity for the benchmark. +typedef double(BigOFunc)(IterationCount); + +// StatisticsFunc is passed to a benchmark in order to compute some descriptive +// statistics over all the measurements of some type +typedef double(StatisticsFunc)(const std::vector&); + +namespace internal { +struct Statistics { + std::string name_; + StatisticsFunc* compute_; + StatisticUnit unit_; + + Statistics(const std::string& name, StatisticsFunc* compute, + StatisticUnit unit = kTime) + : name_(name), compute_(compute), unit_(unit) {} +}; + +class BenchmarkInstance; +class ThreadTimer; +class ThreadManager; +class PerfCountersMeasurement; + +enum AggregationReportMode +#if defined(BENCHMARK_HAS_CXX11) + : unsigned +#else +#endif +{ + // The mode has not been manually specified + ARM_Unspecified = 0, + // The mode is user-specified. + // This may or may not be set when the following bit-flags are set. + ARM_Default = 1U << 0U, + // File reporter should only output aggregates. + ARM_FileReportAggregatesOnly = 1U << 1U, + // Display reporter should only output aggregates + ARM_DisplayReportAggregatesOnly = 1U << 2U, + // Both reporters should only display aggregates. + ARM_ReportAggregatesOnly = + ARM_FileReportAggregatesOnly | ARM_DisplayReportAggregatesOnly +}; + +} // namespace internal + +// State is passed to a running Benchmark and contains state for the +// benchmark to use. +class State { + public: + struct StateIterator; + friend struct StateIterator; + + // Returns iterators used to run each iteration of a benchmark using a + // C++11 ranged-based for loop. These functions should not be called directly. + // + // REQUIRES: The benchmark has not started running yet. Neither begin nor end + // have been called previously. + // + // NOTE: KeepRunning may not be used after calling either of these functions. + BENCHMARK_ALWAYS_INLINE StateIterator begin(); + BENCHMARK_ALWAYS_INLINE StateIterator end(); + + // Returns true if the benchmark should continue through another iteration. + // NOTE: A benchmark may not return from the test until KeepRunning() has + // returned false. + bool KeepRunning(); + + // Returns true iff the benchmark should run n more iterations. + // REQUIRES: 'n' > 0. + // NOTE: A benchmark must not return from the test until KeepRunningBatch() + // has returned false. + // NOTE: KeepRunningBatch() may overshoot by up to 'n' iterations. + // + // Intended usage: + // while (state.KeepRunningBatch(1000)) { + // // process 1000 elements + // } + bool KeepRunningBatch(IterationCount n); + + // REQUIRES: timer is running and 'SkipWithError(...)' has not been called + // by the current thread. + // Stop the benchmark timer. If not called, the timer will be + // automatically stopped after the last iteration of the benchmark loop. + // + // For threaded benchmarks the PauseTiming() function only pauses the timing + // for the current thread. + // + // NOTE: The "real time" measurement is per-thread. If different threads + // report different measurements the largest one is reported. + // + // NOTE: PauseTiming()/ResumeTiming() are relatively + // heavyweight, and so their use should generally be avoided + // within each benchmark iteration, if possible. + void PauseTiming(); + + // REQUIRES: timer is not running and 'SkipWithError(...)' has not been called + // by the current thread. + // Start the benchmark timer. The timer is NOT running on entrance to the + // benchmark function. It begins running after control flow enters the + // benchmark loop. + // + // NOTE: PauseTiming()/ResumeTiming() are relatively + // heavyweight, and so their use should generally be avoided + // within each benchmark iteration, if possible. + void ResumeTiming(); + + // REQUIRES: 'SkipWithError(...)' has not been called previously by the + // current thread. + // Report the benchmark as resulting in an error with the specified 'msg'. + // After this call the user may explicitly 'return' from the benchmark. + // + // If the ranged-for style of benchmark loop is used, the user must explicitly + // break from the loop, otherwise all future iterations will be run. + // If the 'KeepRunning()' loop is used the current thread will automatically + // exit the loop at the end of the current iteration. + // + // For threaded benchmarks only the current thread stops executing and future + // calls to `KeepRunning()` will block until all threads have completed + // the `KeepRunning()` loop. If multiple threads report an error only the + // first error message is used. + // + // NOTE: Calling 'SkipWithError(...)' does not cause the benchmark to exit + // the current scope immediately. If the function is called from within + // the 'KeepRunning()' loop the current iteration will finish. It is the users + // responsibility to exit the scope as needed. + void SkipWithError(const char* msg); + + // Returns true if an error has been reported with 'SkipWithError(...)'. + bool error_occurred() const { return error_occurred_; } + + // REQUIRES: called exactly once per iteration of the benchmarking loop. + // Set the manually measured time for this benchmark iteration, which + // is used instead of automatically measured time if UseManualTime() was + // specified. + // + // For threaded benchmarks the final value will be set to the largest + // reported values. + void SetIterationTime(double seconds); + + // Set the number of bytes processed by the current benchmark + // execution. This routine is typically called once at the end of a + // throughput oriented benchmark. + // + // REQUIRES: a benchmark has exited its benchmarking loop. + BENCHMARK_ALWAYS_INLINE + void SetBytesProcessed(int64_t bytes) { + counters["bytes_per_second"] = + Counter(static_cast(bytes), Counter::kIsRate, Counter::kIs1024); + } + + BENCHMARK_ALWAYS_INLINE + int64_t bytes_processed() const { + if (counters.find("bytes_per_second") != counters.end()) + return static_cast(counters.at("bytes_per_second")); + return 0; + } + + // If this routine is called with complexity_n > 0 and complexity report is + // requested for the + // family benchmark, then current benchmark will be part of the computation + // and complexity_n will + // represent the length of N. + BENCHMARK_ALWAYS_INLINE + void SetComplexityN(int64_t complexity_n) { complexity_n_ = complexity_n; } + + BENCHMARK_ALWAYS_INLINE + int64_t complexity_length_n() const { return complexity_n_; } + + // If this routine is called with items > 0, then an items/s + // label is printed on the benchmark report line for the currently + // executing benchmark. It is typically called at the end of a processing + // benchmark where a processing items/second output is desired. + // + // REQUIRES: a benchmark has exited its benchmarking loop. + BENCHMARK_ALWAYS_INLINE + void SetItemsProcessed(int64_t items) { + counters["items_per_second"] = + Counter(static_cast(items), benchmark::Counter::kIsRate); + } + + BENCHMARK_ALWAYS_INLINE + int64_t items_processed() const { + if (counters.find("items_per_second") != counters.end()) + return static_cast(counters.at("items_per_second")); + return 0; + } + + // If this routine is called, the specified label is printed at the + // end of the benchmark report line for the currently executing + // benchmark. Example: + // static void BM_Compress(benchmark::State& state) { + // ... + // double compress = input_size / output_size; + // state.SetLabel(StrFormat("compress:%.1f%%", 100.0*compression)); + // } + // Produces output that looks like: + // BM_Compress 50 50 14115038 compress:27.3% + // + // REQUIRES: a benchmark has exited its benchmarking loop. + void SetLabel(const char* label); + + void BENCHMARK_ALWAYS_INLINE SetLabel(const std::string& str) { + this->SetLabel(str.c_str()); + } + + // Range arguments for this run. CHECKs if the argument has been set. + BENCHMARK_ALWAYS_INLINE + int64_t range(std::size_t pos = 0) const { + assert(range_.size() > pos); + return range_[pos]; + } + + BENCHMARK_DEPRECATED_MSG("use 'range(0)' instead") + int64_t range_x() const { return range(0); } + + BENCHMARK_DEPRECATED_MSG("use 'range(1)' instead") + int64_t range_y() const { return range(1); } + + // Number of threads concurrently executing the benchmark. + BENCHMARK_ALWAYS_INLINE + int threads() const { return threads_; } + + // Index of the executing thread. Values from [0, threads). + BENCHMARK_ALWAYS_INLINE + int thread_index() const { return thread_index_; } + + BENCHMARK_ALWAYS_INLINE + IterationCount iterations() const { + if (BENCHMARK_BUILTIN_EXPECT(!started_, false)) { + return 0; + } + return max_iterations - total_iterations_ + batch_leftover_; + } + + private: + // items we expect on the first cache line (ie 64 bytes of the struct) + // When total_iterations_ is 0, KeepRunning() and friends will return false. + // May be larger than max_iterations. + IterationCount total_iterations_; + + // When using KeepRunningBatch(), batch_leftover_ holds the number of + // iterations beyond max_iters that were run. Used to track + // completed_iterations_ accurately. + IterationCount batch_leftover_; + + public: + const IterationCount max_iterations; + + private: + bool started_; + bool finished_; + bool error_occurred_; + + // items we don't need on the first cache line + std::vector range_; + + int64_t complexity_n_; + + public: + // Container for user-defined counters. + UserCounters counters; + + private: + State(IterationCount max_iters, const std::vector& ranges, + int thread_i, int n_threads, internal::ThreadTimer* timer, + internal::ThreadManager* manager, + internal::PerfCountersMeasurement* perf_counters_measurement); + + void StartKeepRunning(); + // Implementation of KeepRunning() and KeepRunningBatch(). + // is_batch must be true unless n is 1. + bool KeepRunningInternal(IterationCount n, bool is_batch); + void FinishKeepRunning(); + + const int thread_index_; + const int threads_; + + internal::ThreadTimer* const timer_; + internal::ThreadManager* const manager_; + internal::PerfCountersMeasurement* const perf_counters_measurement_; + + friend class internal::BenchmarkInstance; +}; + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunning() { + return KeepRunningInternal(1, /*is_batch=*/false); +} + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningBatch(IterationCount n) { + return KeepRunningInternal(n, /*is_batch=*/true); +} + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningInternal(IterationCount n, + bool is_batch) { + // total_iterations_ is set to 0 by the constructor, and always set to a + // nonzero value by StartKepRunning(). + assert(n > 0); + // n must be 1 unless is_batch is true. + assert(is_batch || n == 1); + if (BENCHMARK_BUILTIN_EXPECT(total_iterations_ >= n, true)) { + total_iterations_ -= n; + return true; + } + if (!started_) { + StartKeepRunning(); + if (!error_occurred_ && total_iterations_ >= n) { + total_iterations_ -= n; + return true; + } + } + // For non-batch runs, total_iterations_ must be 0 by now. + if (is_batch && total_iterations_ != 0) { + batch_leftover_ = n - total_iterations_; + total_iterations_ = 0; + return true; + } + FinishKeepRunning(); + return false; +} + +struct State::StateIterator { + struct BENCHMARK_UNUSED Value {}; + typedef std::forward_iterator_tag iterator_category; + typedef Value value_type; + typedef Value reference; + typedef Value pointer; + typedef std::ptrdiff_t difference_type; + + private: + friend class State; + BENCHMARK_ALWAYS_INLINE + StateIterator() : cached_(0), parent_() {} + + BENCHMARK_ALWAYS_INLINE + explicit StateIterator(State* st) + : cached_(st->error_occurred_ ? 0 : st->max_iterations), parent_(st) {} + + public: + BENCHMARK_ALWAYS_INLINE + Value operator*() const { return Value(); } + + BENCHMARK_ALWAYS_INLINE + StateIterator& operator++() { + assert(cached_ > 0); + --cached_; + return *this; + } + + BENCHMARK_ALWAYS_INLINE + bool operator!=(StateIterator const&) const { + if (BENCHMARK_BUILTIN_EXPECT(cached_ != 0, true)) return true; + parent_->FinishKeepRunning(); + return false; + } + + private: + IterationCount cached_; + State* const parent_; +}; + +inline BENCHMARK_ALWAYS_INLINE State::StateIterator State::begin() { + return StateIterator(this); +} +inline BENCHMARK_ALWAYS_INLINE State::StateIterator State::end() { + StartKeepRunning(); + return StateIterator(); +} + +namespace internal { + +typedef void(Function)(State&); + +// ------------------------------------------------------ +// Benchmark registration object. The BENCHMARK() macro expands +// into an internal::Benchmark* object. Various methods can +// be called on this object to change the properties of the benchmark. +// Each method returns "this" so that multiple method calls can +// chained into one expression. +class Benchmark { + public: + virtual ~Benchmark(); + + // Note: the following methods all return "this" so that multiple + // method calls can be chained together in one expression. + + // Specify the name of the benchmark + Benchmark* Name(const std::string& name); + + // Run this benchmark once with "x" as the extra argument passed + // to the function. + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* Arg(int64_t x); + + // Run this benchmark with the given time unit for the generated output report + Benchmark* Unit(TimeUnit unit); + + // Run this benchmark once for a number of values picked from the + // range [start..limit]. (start and limit are always picked.) + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* Range(int64_t start, int64_t limit); + + // Run this benchmark once for all values in the range [start..limit] with + // specific step + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* DenseRange(int64_t start, int64_t limit, int step = 1); + + // Run this benchmark once with "args" as the extra arguments passed + // to the function. + // REQUIRES: The function passed to the constructor must accept arg1, arg2 ... + Benchmark* Args(const std::vector& args); + + // Equivalent to Args({x, y}) + // NOTE: This is a legacy C++03 interface provided for compatibility only. + // New code should use 'Args'. + Benchmark* ArgPair(int64_t x, int64_t y) { + std::vector args; + args.push_back(x); + args.push_back(y); + return Args(args); + } + + // Run this benchmark once for a number of values picked from the + // ranges [start..limit]. (starts and limits are always picked.) + // REQUIRES: The function passed to the constructor must accept arg1, arg2 ... + Benchmark* Ranges(const std::vector >& ranges); + + // Run this benchmark once for each combination of values in the (cartesian) + // product of the supplied argument lists. + // REQUIRES: The function passed to the constructor must accept arg1, arg2 ... + Benchmark* ArgsProduct(const std::vector >& arglists); + + // Equivalent to ArgNames({name}) + Benchmark* ArgName(const std::string& name); + + // Set the argument names to display in the benchmark name. If not called, + // only argument values will be shown. + Benchmark* ArgNames(const std::vector& names); + + // Equivalent to Ranges({{lo1, hi1}, {lo2, hi2}}). + // NOTE: This is a legacy C++03 interface provided for compatibility only. + // New code should use 'Ranges'. + Benchmark* RangePair(int64_t lo1, int64_t hi1, int64_t lo2, int64_t hi2) { + std::vector > ranges; + ranges.push_back(std::make_pair(lo1, hi1)); + ranges.push_back(std::make_pair(lo2, hi2)); + return Ranges(ranges); + } + + // Have "setup" and/or "teardown" invoked once for every benchmark run. + // If the benchmark is multi-threaded (will run in k threads concurrently), + // the setup callback will be be invoked exactly once (not k times) before + // each run with k threads. Time allowing (e.g. for a short benchmark), there + // may be multiple such runs per benchmark, each run with its own + // "setup"/"teardown". + // + // If the benchmark uses different size groups of threads (e.g. via + // ThreadRange), the above will be true for each size group. + // + // The callback will be passed a State object, which includes the number + // of threads, thread-index, benchmark arguments, etc. + // + // The callback must not be NULL or self-deleting. + Benchmark* Setup(void (*setup)(const benchmark::State&)); + Benchmark* Teardown(void (*teardown)(const benchmark::State&)); + + // Pass this benchmark object to *func, which can customize + // the benchmark by calling various methods like Arg, Args, + // Threads, etc. + Benchmark* Apply(void (*func)(Benchmark* benchmark)); + + // Set the range multiplier for non-dense range. If not called, the range + // multiplier kRangeMultiplier will be used. + Benchmark* RangeMultiplier(int multiplier); + + // Set the minimum amount of time to use when running this benchmark. This + // option overrides the `benchmark_min_time` flag. + // REQUIRES: `t > 0` and `Iterations` has not been called on this benchmark. + Benchmark* MinTime(double t); + + // Specify the amount of iterations that should be run by this benchmark. + // REQUIRES: 'n > 0' and `MinTime` has not been called on this benchmark. + // + // NOTE: This function should only be used when *exact* iteration control is + // needed and never to control or limit how long a benchmark runs, where + // `--benchmark_min_time=N` or `MinTime(...)` should be used instead. + Benchmark* Iterations(IterationCount n); + + // Specify the amount of times to repeat this benchmark. This option overrides + // the `benchmark_repetitions` flag. + // REQUIRES: `n > 0` + Benchmark* Repetitions(int n); + + // Specify if each repetition of the benchmark should be reported separately + // or if only the final statistics should be reported. If the benchmark + // is not repeated then the single result is always reported. + // Applies to *ALL* reporters (display and file). + Benchmark* ReportAggregatesOnly(bool value = true); + + // Same as ReportAggregatesOnly(), but applies to display reporter only. + Benchmark* DisplayAggregatesOnly(bool value = true); + + // By default, the CPU time is measured only for the main thread, which may + // be unrepresentative if the benchmark uses threads internally. If called, + // the total CPU time spent by all the threads will be measured instead. + // By default, the only the main thread CPU time will be measured. + Benchmark* MeasureProcessCPUTime(); + + // If a particular benchmark should use the Wall clock instead of the CPU time + // (be it either the CPU time of the main thread only (default), or the + // total CPU usage of the benchmark), call this method. If called, the elapsed + // (wall) time will be used to control how many iterations are run, and in the + // printing of items/second or MB/seconds values. + // If not called, the CPU time used by the benchmark will be used. + Benchmark* UseRealTime(); + + // If a benchmark must measure time manually (e.g. if GPU execution time is + // being + // measured), call this method. If called, each benchmark iteration should + // call + // SetIterationTime(seconds) to report the measured time, which will be used + // to control how many iterations are run, and in the printing of items/second + // or MB/second values. + Benchmark* UseManualTime(); + + // Set the asymptotic computational complexity for the benchmark. If called + // the asymptotic computational complexity will be shown on the output. + Benchmark* Complexity(BigO complexity = benchmark::oAuto); + + // Set the asymptotic computational complexity for the benchmark. If called + // the asymptotic computational complexity will be shown on the output. + Benchmark* Complexity(BigOFunc* complexity); + + // Add this statistics to be computed over all the values of benchmark run + Benchmark* ComputeStatistics(const std::string& name, + StatisticsFunc* statistics, + StatisticUnit unit = kTime); + + // Support for running multiple copies of the same benchmark concurrently + // in multiple threads. This may be useful when measuring the scaling + // of some piece of code. + + // Run one instance of this benchmark concurrently in t threads. + Benchmark* Threads(int t); + + // Pick a set of values T from [min_threads,max_threads]. + // min_threads and max_threads are always included in T. Run this + // benchmark once for each value in T. The benchmark run for a + // particular value t consists of t threads running the benchmark + // function concurrently. For example, consider: + // BENCHMARK(Foo)->ThreadRange(1,16); + // This will run the following benchmarks: + // Foo in 1 thread + // Foo in 2 threads + // Foo in 4 threads + // Foo in 8 threads + // Foo in 16 threads + Benchmark* ThreadRange(int min_threads, int max_threads); + + // For each value n in the range, run this benchmark once using n threads. + // min_threads and max_threads are always included in the range. + // stride specifies the increment. E.g. DenseThreadRange(1, 8, 3) starts + // a benchmark with 1, 4, 7 and 8 threads. + Benchmark* DenseThreadRange(int min_threads, int max_threads, int stride = 1); + + // Equivalent to ThreadRange(NumCPUs(), NumCPUs()) + Benchmark* ThreadPerCpu(); + + virtual void Run(State& state) = 0; + + protected: + explicit Benchmark(const char* name); + Benchmark(Benchmark const&); + void SetName(const char* name); + + int ArgsCnt() const; + + private: + friend class BenchmarkFamilies; + friend class BenchmarkInstance; + + std::string name_; + AggregationReportMode aggregation_report_mode_; + std::vector arg_names_; // Args for all benchmark runs + std::vector > args_; // Args for all benchmark runs + TimeUnit time_unit_; + int range_multiplier_; + double min_time_; + IterationCount iterations_; + int repetitions_; + bool measure_process_cpu_time_; + bool use_real_time_; + bool use_manual_time_; + BigO complexity_; + BigOFunc* complexity_lambda_; + std::vector statistics_; + std::vector thread_counts_; + + typedef void (*callback_function)(const benchmark::State&); + callback_function setup_; + callback_function teardown_; + + Benchmark& operator=(Benchmark const&); +}; + +} // namespace internal + +// Create and register a benchmark with the specified 'name' that invokes +// the specified functor 'fn'. +// +// RETURNS: A pointer to the registered benchmark. +internal::Benchmark* RegisterBenchmark(const char* name, + internal::Function* fn); + +#if defined(BENCHMARK_HAS_CXX11) +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn); +#endif + +// Remove all registered benchmarks. All pointers to previously registered +// benchmarks are invalidated. +void ClearRegisteredBenchmarks(); + +namespace internal { +// The class used to hold all Benchmarks created from static function. +// (ie those created using the BENCHMARK(...) macros. +class FunctionBenchmark : public Benchmark { + public: + FunctionBenchmark(const char* name, Function* func) + : Benchmark(name), func_(func) {} + + virtual void Run(State& st) BENCHMARK_OVERRIDE; + + private: + Function* func_; +}; + +#ifdef BENCHMARK_HAS_CXX11 +template +class LambdaBenchmark : public Benchmark { + public: + virtual void Run(State& st) BENCHMARK_OVERRIDE { lambda_(st); } + + private: + template + LambdaBenchmark(const char* name, OLambda&& lam) + : Benchmark(name), lambda_(std::forward(lam)) {} + + LambdaBenchmark(LambdaBenchmark const&) = delete; + + template // NOLINTNEXTLINE(readability-redundant-declaration) + friend Benchmark* ::benchmark::RegisterBenchmark(const char*, Lam&&); + + Lambda lambda_; +}; +#endif + +} // namespace internal + +inline internal::Benchmark* RegisterBenchmark(const char* name, + internal::Function* fn) { + return internal::RegisterBenchmarkInternal( + ::new internal::FunctionBenchmark(name, fn)); +} + +#ifdef BENCHMARK_HAS_CXX11 +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn) { + using BenchType = + internal::LambdaBenchmark::type>; + return internal::RegisterBenchmarkInternal( + ::new BenchType(name, std::forward(fn))); +} +#endif + +#if defined(BENCHMARK_HAS_CXX11) && \ + (!defined(BENCHMARK_GCC_VERSION) || BENCHMARK_GCC_VERSION >= 409) +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn, + Args&&... args) { + return benchmark::RegisterBenchmark( + name, [=](benchmark::State& st) { fn(st, args...); }); +} +#else +#define BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK +#endif + +// The base class for all fixture tests. +class Fixture : public internal::Benchmark { + public: + Fixture() : internal::Benchmark("") {} + + virtual void Run(State& st) BENCHMARK_OVERRIDE { + this->SetUp(st); + this->BenchmarkCase(st); + this->TearDown(st); + } + + // These will be deprecated ... + virtual void SetUp(const State&) {} + virtual void TearDown(const State&) {} + // ... In favor of these. + virtual void SetUp(State& st) { SetUp(const_cast(st)); } + virtual void TearDown(State& st) { TearDown(const_cast(st)); } + + protected: + virtual void BenchmarkCase(State&) = 0; +}; + +} // namespace benchmark + +// ------------------------------------------------------ +// Macro to register benchmarks + +// Check that __COUNTER__ is defined and that __COUNTER__ increases by 1 +// every time it is expanded. X + 1 == X + 0 is used in case X is defined to be +// empty. If X is empty the expression becomes (+1 == +0). +#if defined(__COUNTER__) && (__COUNTER__ + 1 == __COUNTER__ + 0) +#define BENCHMARK_PRIVATE_UNIQUE_ID __COUNTER__ +#else +#define BENCHMARK_PRIVATE_UNIQUE_ID __LINE__ +#endif + +// Helpers for generating unique variable names +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_PRIVATE_NAME(...) \ + BENCHMARK_PRIVATE_CONCAT(benchmark_uniq_, BENCHMARK_PRIVATE_UNIQUE_ID, \ + __VA_ARGS__) +#else +#define BENCHMARK_PRIVATE_NAME(n) \ + BENCHMARK_PRIVATE_CONCAT(benchmark_uniq_, BENCHMARK_PRIVATE_UNIQUE_ID, n) +#endif // BENCHMARK_HAS_CXX11 + +#define BENCHMARK_PRIVATE_CONCAT(a, b, c) BENCHMARK_PRIVATE_CONCAT2(a, b, c) +#define BENCHMARK_PRIVATE_CONCAT2(a, b, c) a##b##c +// Helper for concatenation with macro name expansion +#define BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method) \ + BaseClass##_##Method##_Benchmark + +#define BENCHMARK_PRIVATE_DECLARE(n) \ + static ::benchmark::internal::Benchmark* BENCHMARK_PRIVATE_NAME(n) \ + BENCHMARK_UNUSED + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK(...) \ + BENCHMARK_PRIVATE_DECLARE(_benchmark_) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#__VA_ARGS__, \ + &__VA_ARGS__))) +#else +#define BENCHMARK(n) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n, n))) +#endif // BENCHMARK_HAS_CXX11 + +// Old-style macros +#define BENCHMARK_WITH_ARG(n, a) BENCHMARK(n)->Arg((a)) +#define BENCHMARK_WITH_ARG2(n, a1, a2) BENCHMARK(n)->Args({(a1), (a2)}) +#define BENCHMARK_WITH_UNIT(n, t) BENCHMARK(n)->Unit((t)) +#define BENCHMARK_RANGE(n, lo, hi) BENCHMARK(n)->Range((lo), (hi)) +#define BENCHMARK_RANGE2(n, l1, h1, l2, h2) \ + BENCHMARK(n)->RangePair({{(l1), (h1)}, {(l2), (h2)}}) + +#ifdef BENCHMARK_HAS_CXX11 + +// Register a benchmark which invokes the function specified by `func` +// with the additional arguments specified by `...`. +// +// For example: +// +// template ` +// void BM_takes_args(benchmark::State& state, ExtraArgs&&... extra_args) { +// [...] +//} +// /* Registers a benchmark named "BM_takes_args/int_string_test` */ +// BENCHMARK_CAPTURE(BM_takes_args, int_string_test, 42, std::string("abc")); +#define BENCHMARK_CAPTURE(func, test_case_name, ...) \ + BENCHMARK_PRIVATE_DECLARE(func) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark( \ + #func "/" #test_case_name, \ + [](::benchmark::State& st) { func(st, __VA_ARGS__); }))) + +#endif // BENCHMARK_HAS_CXX11 + +// This will register a benchmark for a templatized function. For example: +// +// template +// void BM_Foo(int iters); +// +// BENCHMARK_TEMPLATE(BM_Foo, 1); +// +// will register BM_Foo<1> as a benchmark. +#define BENCHMARK_TEMPLATE1(n, a) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n "<" #a ">", n))) + +#define BENCHMARK_TEMPLATE2(n, a, b) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n "<" #a "," #b ">", \ + n))) + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE(n, ...) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark( \ + #n "<" #__VA_ARGS__ ">", n<__VA_ARGS__>))) +#else +#define BENCHMARK_TEMPLATE(n, a) BENCHMARK_TEMPLATE1(n, a) +#endif + +#define BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() { \ + this->SetName(#BaseClass "/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&) BENCHMARK_OVERRIDE; \ + }; + +#define BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() { \ + this->SetName(#BaseClass "<" #a ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&) BENCHMARK_OVERRIDE; \ + }; + +#define BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() { \ + this->SetName(#BaseClass "<" #a "," #b ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&) BENCHMARK_OVERRIDE; \ + }; + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, ...) \ + class BaseClass##_##Method##_Benchmark : public BaseClass<__VA_ARGS__> { \ + public: \ + BaseClass##_##Method##_Benchmark() { \ + this->SetName(#BaseClass "<" #__VA_ARGS__ ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&) BENCHMARK_OVERRIDE; \ + }; +#else +#define BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(n, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(n, a) +#endif + +#define BENCHMARK_DEFINE_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#define BENCHMARK_TEMPLATE1_DEFINE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#define BENCHMARK_TEMPLATE2_DEFINE_F(BaseClass, Method, a, b) \ + BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_DEFINE_F(BaseClass, Method, ...) \ + BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, __VA_ARGS__) \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase +#else +#define BENCHMARK_TEMPLATE_DEFINE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_DEFINE_F(BaseClass, Method, a) +#endif + +#define BENCHMARK_REGISTER_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_REGISTER_F(BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)) + +#define BENCHMARK_PRIVATE_REGISTER_F(TestName) \ + BENCHMARK_PRIVATE_DECLARE(TestName) = \ + (::benchmark::internal::RegisterBenchmarkInternal(new TestName())) + +// This macro will define and register a benchmark within a fixture class. +#define BENCHMARK_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#define BENCHMARK_TEMPLATE1_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#define BENCHMARK_TEMPLATE2_F(BaseClass, Method, a, b) \ + BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_F(BaseClass, Method, ...) \ + BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, __VA_ARGS__) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BENCHMARK_PRIVATE_CONCAT_NAME(BaseClass, Method)::BenchmarkCase +#else +#define BENCHMARK_TEMPLATE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_F(BaseClass, Method, a) +#endif + +// Helper macro to create a main routine in a test that runs the benchmarks +#define BENCHMARK_MAIN() \ + int main(int argc, char** argv) { \ + ::benchmark::Initialize(&argc, argv); \ + if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; \ + ::benchmark::RunSpecifiedBenchmarks(); \ + ::benchmark::Shutdown(); \ + return 0; \ + } \ + int main(int, char**) + +// ------------------------------------------------------ +// Benchmark Reporters + +namespace benchmark { + +struct CPUInfo { + struct CacheInfo { + std::string type; + int level; + int size; + int num_sharing; + }; + + enum Scaling { UNKNOWN, ENABLED, DISABLED }; + + int num_cpus; + Scaling scaling; + double cycles_per_second; + std::vector caches; + std::vector load_avg; + + static const CPUInfo& Get(); + + private: + CPUInfo(); + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(CPUInfo); +}; + +// Adding Struct for System Information +struct SystemInfo { + std::string name; + static const SystemInfo& Get(); + + private: + SystemInfo(); + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(SystemInfo); +}; + +// BenchmarkName contains the components of the Benchmark's name +// which allows individual fields to be modified or cleared before +// building the final name using 'str()'. +struct BenchmarkName { + std::string function_name; + std::string args; + std::string min_time; + std::string iterations; + std::string repetitions; + std::string time_type; + std::string threads; + + // Return the full name of the benchmark with each non-empty + // field separated by a '/' + std::string str() const; +}; + +// Interface for custom benchmark result printers. +// By default, benchmark reports are printed to stdout. However an application +// can control the destination of the reports by calling +// RunSpecifiedBenchmarks and passing it a custom reporter object. +// The reporter object must implement the following interface. +class BenchmarkReporter { + public: + struct Context { + CPUInfo const& cpu_info; + SystemInfo const& sys_info; + // The number of chars in the longest benchmark name. + size_t name_field_width; + static const char* executable_name; + Context(); + }; + + struct Run { + static const int64_t no_repetition_index = -1; + enum RunType { RT_Iteration, RT_Aggregate }; + + Run() + : run_type(RT_Iteration), + aggregate_unit(kTime), + error_occurred(false), + iterations(1), + threads(1), + time_unit(kNanosecond), + real_accumulated_time(0), + cpu_accumulated_time(0), + max_heapbytes_used(0), + complexity(oNone), + complexity_lambda(), + complexity_n(0), + report_big_o(false), + report_rms(false), + memory_result(NULL), + allocs_per_iter(0.0) {} + + std::string benchmark_name() const; + BenchmarkName run_name; + int64_t family_index; + int64_t per_family_instance_index; + RunType run_type; + std::string aggregate_name; + StatisticUnit aggregate_unit; + std::string report_label; // Empty if not set by benchmark. + bool error_occurred; + std::string error_message; + + IterationCount iterations; + int64_t threads; + int64_t repetition_index; + int64_t repetitions; + TimeUnit time_unit; + double real_accumulated_time; + double cpu_accumulated_time; + + // Return a value representing the real time per iteration in the unit + // specified by 'time_unit'. + // NOTE: If 'iterations' is zero the returned value represents the + // accumulated time. + double GetAdjustedRealTime() const; + + // Return a value representing the cpu time per iteration in the unit + // specified by 'time_unit'. + // NOTE: If 'iterations' is zero the returned value represents the + // accumulated time. + double GetAdjustedCPUTime() const; + + // This is set to 0.0 if memory tracing is not enabled. + double max_heapbytes_used; + + // Keep track of arguments to compute asymptotic complexity + BigO complexity; + BigOFunc* complexity_lambda; + int64_t complexity_n; + + // what statistics to compute from the measurements + const std::vector* statistics; + + // Inform print function whether the current run is a complexity report + bool report_big_o; + bool report_rms; + + UserCounters counters; + + // Memory metrics. + const MemoryManager::Result* memory_result; + double allocs_per_iter; + }; + + struct PerFamilyRunReports { + PerFamilyRunReports() : num_runs_total(0), num_runs_done(0) {} + + // How many runs will all instances of this benchmark perform? + int num_runs_total; + + // How many runs have happened already? + int num_runs_done; + + // The reports about (non-errneous!) runs of this family. + std::vector Runs; + }; + + // Construct a BenchmarkReporter with the output stream set to 'std::cout' + // and the error stream set to 'std::cerr' + BenchmarkReporter(); + + // Called once for every suite of benchmarks run. + // The parameter "context" contains information that the + // reporter may wish to use when generating its report, for example the + // platform under which the benchmarks are running. The benchmark run is + // never started if this function returns false, allowing the reporter + // to skip runs based on the context information. + virtual bool ReportContext(const Context& context) = 0; + + // Called once for each group of benchmark runs, gives information about + // cpu-time and heap memory usage during the benchmark run. If the group + // of runs contained more than two entries then 'report' contains additional + // elements representing the mean and standard deviation of those runs. + // Additionally if this group of runs was the last in a family of benchmarks + // 'reports' contains additional entries representing the asymptotic + // complexity and RMS of that benchmark family. + virtual void ReportRuns(const std::vector& report) = 0; + + // Called once and only once after ever group of benchmarks is run and + // reported. + virtual void Finalize() {} + + // REQUIRES: The object referenced by 'out' is valid for the lifetime + // of the reporter. + void SetOutputStream(std::ostream* out) { + assert(out); + output_stream_ = out; + } + + // REQUIRES: The object referenced by 'err' is valid for the lifetime + // of the reporter. + void SetErrorStream(std::ostream* err) { + assert(err); + error_stream_ = err; + } + + std::ostream& GetOutputStream() const { return *output_stream_; } + + std::ostream& GetErrorStream() const { return *error_stream_; } + + virtual ~BenchmarkReporter(); + + // Write a human readable string to 'out' representing the specified + // 'context'. + // REQUIRES: 'out' is non-null. + static void PrintBasicContext(std::ostream* out, Context const& context); + + private: + std::ostream* output_stream_; + std::ostream* error_stream_; +}; + +// Simple reporter that outputs benchmark data to the console. This is the +// default reporter used by RunSpecifiedBenchmarks(). +class ConsoleReporter : public BenchmarkReporter { + public: + enum OutputOptions { + OO_None = 0, + OO_Color = 1, + OO_Tabular = 2, + OO_ColorTabular = OO_Color | OO_Tabular, + OO_Defaults = OO_ColorTabular + }; + explicit ConsoleReporter(OutputOptions opts_ = OO_Defaults) + : output_options_(opts_), name_field_width_(0), printed_header_(false) {} + + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE; + virtual void ReportRuns(const std::vector& reports) BENCHMARK_OVERRIDE; + + protected: + virtual void PrintRunData(const Run& report); + virtual void PrintHeader(const Run& report); + + OutputOptions output_options_; + size_t name_field_width_; + UserCounters prev_counters_; + bool printed_header_; +}; + +class JSONReporter : public BenchmarkReporter { + public: + JSONReporter() : first_report_(true) {} + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE; + virtual void ReportRuns(const std::vector& reports) BENCHMARK_OVERRIDE; + virtual void Finalize() BENCHMARK_OVERRIDE; + + private: + void PrintRunData(const Run& report); + + bool first_report_; +}; + +class BENCHMARK_DEPRECATED_MSG( + "The CSV Reporter will be removed in a future release") CSVReporter + : public BenchmarkReporter { + public: + CSVReporter() : printed_header_(false) {} + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE; + virtual void ReportRuns(const std::vector& reports) BENCHMARK_OVERRIDE; + + private: + void PrintRunData(const Run& report); + + bool printed_header_; + std::set user_counter_names_; +}; + +inline const char* GetTimeUnitString(TimeUnit unit) { + switch (unit) { + case kSecond: + return "s"; + case kMillisecond: + return "ms"; + case kMicrosecond: + return "us"; + case kNanosecond: + return "ns"; + } + BENCHMARK_UNREACHABLE(); +} + +inline double GetTimeUnitMultiplier(TimeUnit unit) { + switch (unit) { + case kSecond: + return 1; + case kMillisecond: + return 1e3; + case kMicrosecond: + return 1e6; + case kNanosecond: + return 1e9; + } + BENCHMARK_UNREACHABLE(); +} + +// Creates a list of integer values for the given range and multiplier. +// This can be used together with ArgsProduct() to allow multiple ranges +// with different multiplers. +// Example: +// ArgsProduct({ +// CreateRange(0, 1024, /*multi=*/32), +// CreateRange(0, 100, /*multi=*/4), +// CreateDenseRange(0, 4, /*step=*/1), +// }); +std::vector CreateRange(int64_t lo, int64_t hi, int multi); + +// Creates a list of integer values for the given range and step. +std::vector CreateDenseRange(int64_t start, int64_t limit, int step); + +} // namespace benchmark + +#endif // BENCHMARK_BENCHMARK_H_ diff --git a/bridge/third_party/benchmark/requirements.txt b/bridge/third_party/benchmark/requirements.txt new file mode 100644 index 0000000000..e451894e23 --- /dev/null +++ b/bridge/third_party/benchmark/requirements.txt @@ -0,0 +1,3 @@ +numpy == 1.19.4 +scipy == 1.5.4 +pandas == 1.1.5 diff --git a/bridge/third_party/benchmark/setup.py b/bridge/third_party/benchmark/setup.py new file mode 100644 index 0000000000..4eaccf8498 --- /dev/null +++ b/bridge/third_party/benchmark/setup.py @@ -0,0 +1,143 @@ +import os +import posixpath +import platform +import re +import shutil +import sys + +from distutils import sysconfig +import setuptools +from setuptools.command import build_ext + + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +IS_WINDOWS = sys.platform.startswith("win") + + +def _get_version(): + """Parse the version string from __init__.py.""" + with open( + os.path.join(HERE, "bindings", "python", "google_benchmark", "__init__.py") + ) as init_file: + try: + version_line = next( + line for line in init_file if line.startswith("__version__") + ) + except StopIteration: + raise ValueError("__version__ not defined in __init__.py") + else: + namespace = {} + exec(version_line, namespace) # pylint: disable=exec-used + return namespace["__version__"] + + +def _parse_requirements(path): + with open(os.path.join(HERE, path)) as requirements: + return [ + line.rstrip() + for line in requirements + if not (line.isspace() or line.startswith("#")) + ] + + +class BazelExtension(setuptools.Extension): + """A C/C++ extension that is defined as a Bazel BUILD target.""" + + def __init__(self, name, bazel_target): + self.bazel_target = bazel_target + self.relpath, self.target_name = posixpath.relpath(bazel_target, "//").split( + ":" + ) + setuptools.Extension.__init__(self, name, sources=[]) + + +class BuildBazelExtension(build_ext.build_ext): + """A command that runs Bazel to build a C/C++ extension.""" + + def run(self): + for ext in self.extensions: + self.bazel_build(ext) + build_ext.build_ext.run(self) + + def bazel_build(self, ext): + """Runs the bazel build to create the package.""" + with open("WORKSPACE", "r") as workspace: + workspace_contents = workspace.read() + + with open("WORKSPACE", "w") as workspace: + workspace.write( + re.sub( + r'(?<=path = ").*(?=", # May be overwritten by setup\.py\.)', + sysconfig.get_python_inc().replace(os.path.sep, posixpath.sep), + workspace_contents, + ) + ) + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + bazel_argv = [ + "bazel", + "build", + ext.bazel_target, + "--symlink_prefix=" + os.path.join(self.build_temp, "bazel-"), + "--compilation_mode=" + ("dbg" if self.debug else "opt"), + ] + + if IS_WINDOWS: + # Link with python*.lib. + for library_dir in self.library_dirs: + bazel_argv.append("--linkopt=/LIBPATH:" + library_dir) + elif sys.platform == "darwin" and platform.machine() == "x86_64": + bazel_argv.append("--macos_minimum_os=10.9") + + self.spawn(bazel_argv) + + shared_lib_suffix = '.dll' if IS_WINDOWS else '.so' + ext_bazel_bin_path = os.path.join( + self.build_temp, 'bazel-bin', + ext.relpath, ext.target_name + shared_lib_suffix) + + ext_dest_path = self.get_ext_fullpath(ext.name) + ext_dest_dir = os.path.dirname(ext_dest_path) + if not os.path.exists(ext_dest_dir): + os.makedirs(ext_dest_dir) + shutil.copyfile(ext_bazel_bin_path, ext_dest_path) + + +setuptools.setup( + name="google_benchmark", + version=_get_version(), + url="https://github.com/google/benchmark", + description="A library to benchmark code snippets.", + author="Google", + author_email="benchmark-py@google.com", + # Contained modules and scripts. + package_dir={"": "bindings/python"}, + packages=setuptools.find_packages("bindings/python"), + install_requires=_parse_requirements("bindings/python/requirements.txt"), + cmdclass=dict(build_ext=BuildBazelExtension), + ext_modules=[ + BazelExtension( + "google_benchmark._benchmark", + "//bindings/python/google_benchmark:_benchmark", + ) + ], + zip_safe=False, + # PyPI package information. + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development :: Testing", + "Topic :: System :: Benchmark", + ], + license="Apache 2.0", + keywords="benchmark", +) diff --git a/bridge/third_party/benchmark/src/CMakeLists.txt b/bridge/third_party/benchmark/src/CMakeLists.txt new file mode 100644 index 0000000000..5d652ea90b --- /dev/null +++ b/bridge/third_party/benchmark/src/CMakeLists.txt @@ -0,0 +1,155 @@ +# Allow the source files to find headers in src/ +include(GNUInstallDirs) +include_directories(${PROJECT_SOURCE_DIR}/src) + +if (DEFINED BENCHMARK_CXX_LINKER_FLAGS) + list(APPEND CMAKE_SHARED_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) + list(APPEND CMAKE_MODULE_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) +endif() + +file(GLOB + SOURCE_FILES + *.cc + ${PROJECT_SOURCE_DIR}/include/benchmark/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +file(GLOB BENCHMARK_MAIN "benchmark_main.cc") +foreach(item ${BENCHMARK_MAIN}) + list(REMOVE_ITEM SOURCE_FILES "${item}") +endforeach() + +add_library(benchmark ${SOURCE_FILES}) +add_library(benchmark::benchmark ALIAS benchmark) +set_target_properties(benchmark PROPERTIES + OUTPUT_NAME "benchmark" + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) +target_include_directories(benchmark PUBLIC + $) + +# libpfm, if available +if (HAVE_LIBPFM) + target_link_libraries(benchmark PRIVATE pfm) + add_definitions(-DHAVE_LIBPFM) +endif() + +# Link threads. +target_link_libraries(benchmark PRIVATE Threads::Threads) + +target_link_libraries(benchmark PRIVATE ${BENCHMARK_CXX_LIBRARIES}) + +if(HAVE_LIB_RT) + target_link_libraries(benchmark PRIVATE rt) +endif(HAVE_LIB_RT) + + +# We need extra libraries on Windows +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + target_link_libraries(benchmark PRIVATE shlwapi) +endif() + +# We need extra libraries on Solaris +if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS") + target_link_libraries(benchmark PRIVATE kstat) +endif() + +# Benchmark main library +add_library(benchmark_main "benchmark_main.cc") +add_library(benchmark::benchmark_main ALIAS benchmark_main) +set_target_properties(benchmark_main PROPERTIES + OUTPUT_NAME "benchmark_main" + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) +target_link_libraries(benchmark_main PUBLIC benchmark::benchmark) + + +set(generated_dir "${PROJECT_BINARY_DIR}") + +set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") +set(pkg_config "${generated_dir}/${PROJECT_NAME}.pc") +set(targets_export_name "${PROJECT_NAME}Targets") + +set(namespace "${PROJECT_NAME}::") + +include(CMakePackageConfigHelpers) + +configure_package_config_file ( + ${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in + ${project_config} + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) +write_basic_package_version_file( + "${version_config}" VERSION ${GENERIC_LIB_VERSION} COMPATIBILITY SameMajorVersion +) + +configure_file("${PROJECT_SOURCE_DIR}/cmake/benchmark.pc.in" "${pkg_config}" @ONLY) + +if (BENCHMARK_ENABLE_INSTALL) + # Install target (will install the library to specified CMAKE_INSTALL_PREFIX variable) + install( + TARGETS benchmark benchmark_main + EXPORT ${targets_export_name} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY "${PROJECT_SOURCE_DIR}/include/benchmark" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.*h") + + install( + FILES "${project_config}" "${version_config}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + install( + FILES "${pkg_config}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + + install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() + +if (BENCHMARK_ENABLE_DOXYGEN) + find_package(Doxygen REQUIRED) + set(DOXYGEN_QUIET YES) + set(DOXYGEN_RECURSIVE YES) + set(DOXYGEN_GENERATE_HTML YES) + set(DOXYGEN_GENERATE_MAN NO) + set(DOXYGEN_MARKDOWN_SUPPORT YES) + set(DOXYGEN_BUILTIN_STL_SUPPORT YES) + set(DOXYGEN_EXTRACT_PACKAGE YES) + set(DOXYGEN_EXTRACT_STATIC YES) + set(DOXYGEN_SHOW_INCLUDE_FILES YES) + set(DOXYGEN_BINARY_TOC YES) + set(DOXYGEN_TOC_EXPAND YES) + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "index.md") + doxygen_add_docs(benchmark_doxygen + docs + include + src + ALL + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Building documentation with Doxygen.") + if (BENCHMARK_INSTALL_DOCS) + install( + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html/" + DESTINATION ${CMAKE_INSTALL_DOCDIR}) + endif() +else() + if (BENCHMARK_INSTALL_DOCS) + install( + DIRECTORY "${PROJECT_SOURCE_DIR}/docs/" + DESTINATION ${CMAKE_INSTALL_DOCDIR}) + endif() + + export (EXPORT ${targets_export_name} NAMESPACE "${namespace}" + FILE ${generated_dir}/${targets_export_name}.cmake) +endif() diff --git a/bridge/third_party/benchmark/src/arraysize.h b/bridge/third_party/benchmark/src/arraysize.h new file mode 100644 index 0000000000..51a50f2dff --- /dev/null +++ b/bridge/third_party/benchmark/src/arraysize.h @@ -0,0 +1,33 @@ +#ifndef BENCHMARK_ARRAYSIZE_H_ +#define BENCHMARK_ARRAYSIZE_H_ + +#include "internal_macros.h" + +namespace benchmark { +namespace internal { +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char (&ArraySizeHelper(T (&array)[N]))[N]; + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef COMPILER_MSVC +template +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#define arraysize(array) (sizeof(::benchmark::internal::ArraySizeHelper(array))) + +} // end namespace internal +} // end namespace benchmark + +#endif // BENCHMARK_ARRAYSIZE_H_ diff --git a/bridge/third_party/benchmark/src/benchmark.cc b/bridge/third_party/benchmark/src/benchmark.cc new file mode 100644 index 0000000000..cedeee31c7 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark.cc @@ -0,0 +1,626 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" + +#include "benchmark_api_internal.h" +#include "benchmark_runner.h" +#include "internal_macros.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "counter.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "perf_counters.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "thread_manager.h" +#include "thread_timer.h" + +namespace benchmark { +// Print a list of benchmarks. This option overrides all other options. +BM_DEFINE_bool(benchmark_list_tests, false); + +// A regular expression that specifies the set of benchmarks to execute. If +// this flag is empty, or if this flag is the string \"all\", all benchmarks +// linked into the binary are run. +BM_DEFINE_string(benchmark_filter, ""); + +// Minimum number of seconds we should run benchmark before results are +// considered significant. For cpu-time based tests, this is the lower bound +// on the total cpu time used by all threads that make up the test. For +// real-time based tests, this is the lower bound on the elapsed time of the +// benchmark execution, regardless of number of threads. +BM_DEFINE_double(benchmark_min_time, 0.5); + +// The number of runs of each benchmark. If greater than 1, the mean and +// standard deviation of the runs will be reported. +BM_DEFINE_int32(benchmark_repetitions, 1); + +// If set, enable random interleaving of repetitions of all benchmarks. +// See http://github.com/google/benchmark/issues/1051 for details. +BM_DEFINE_bool(benchmark_enable_random_interleaving, false); + +// Report the result of each benchmark repetitions. When 'true' is specified +// only the mean, standard deviation, and other statistics are reported for +// repeated benchmarks. Affects all reporters. +BM_DEFINE_bool(benchmark_report_aggregates_only, false); + +// Display the result of each benchmark repetitions. When 'true' is specified +// only the mean, standard deviation, and other statistics are displayed for +// repeated benchmarks. Unlike benchmark_report_aggregates_only, only affects +// the display reporter, but *NOT* file reporter, which will still contain +// all the output. +BM_DEFINE_bool(benchmark_display_aggregates_only, false); + +// The format to use for console output. +// Valid values are 'console', 'json', or 'csv'. +BM_DEFINE_string(benchmark_format, "console"); + +// The format to use for file output. +// Valid values are 'console', 'json', or 'csv'. +BM_DEFINE_string(benchmark_out_format, "json"); + +// The file to write additional output to. +BM_DEFINE_string(benchmark_out, ""); + +// Whether to use colors in the output. Valid values: +// 'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use colors if +// the output is being sent to a terminal and the TERM environment variable is +// set to a terminal type that supports colors. +BM_DEFINE_string(benchmark_color, "auto"); + +// Whether to use tabular format when printing user counters to the console. +// Valid values: 'true'/'yes'/1, 'false'/'no'/0. Defaults to false. +BM_DEFINE_bool(benchmark_counters_tabular, false); + +// List of additional perf counters to collect, in libpfm format. For more +// information about libpfm: https://man7.org/linux/man-pages/man3/libpfm.3.html +BM_DEFINE_string(benchmark_perf_counters, ""); + +// Extra context to include in the output formatted as comma-separated key-value +// pairs. Kept internal as it's only used for parsing from env/command line. +BM_DEFINE_kvpairs(benchmark_context, {}); + +// The level of verbose logging to output +BM_DEFINE_int32(v, 0); + +namespace internal { + +std::map* global_context = nullptr; + +// FIXME: wouldn't LTO mess this up? +void UseCharPointer(char const volatile*) {} + +} // namespace internal + +State::State(IterationCount max_iters, const std::vector& ranges, + int thread_i, int n_threads, internal::ThreadTimer* timer, + internal::ThreadManager* manager, + internal::PerfCountersMeasurement* perf_counters_measurement) + : total_iterations_(0), + batch_leftover_(0), + max_iterations(max_iters), + started_(false), + finished_(false), + error_occurred_(false), + range_(ranges), + complexity_n_(0), + thread_index_(thread_i), + threads_(n_threads), + timer_(timer), + manager_(manager), + perf_counters_measurement_(perf_counters_measurement) { + BM_CHECK(max_iterations != 0) << "At least one iteration must be run"; + BM_CHECK_LT(thread_index_, threads_) + << "thread_index must be less than threads"; + + // Note: The use of offsetof below is technically undefined until C++17 + // because State is not a standard layout type. However, all compilers + // currently provide well-defined behavior as an extension (which is + // demonstrated since constexpr evaluation must diagnose all undefined + // behavior). However, GCC and Clang also warn about this use of offsetof, + // which must be suppressed. +#if defined(__INTEL_COMPILER) +#pragma warning push +#pragma warning(disable : 1875) +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + // Offset tests to ensure commonly accessed data is on the first cache line. + const int cache_line_size = 64; + static_assert(offsetof(State, error_occurred_) <= + (cache_line_size - sizeof(error_occurred_)), + ""); +#if defined(__INTEL_COMPILER) +#pragma warning pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +void State::PauseTiming() { + // Add in time accumulated so far + BM_CHECK(started_ && !finished_ && !error_occurred_); + timer_->StopTimer(); + if (perf_counters_measurement_) { + auto measurements = perf_counters_measurement_->StopAndGetMeasurements(); + for (const auto& name_and_measurement : measurements) { + auto name = name_and_measurement.first; + auto measurement = name_and_measurement.second; + BM_CHECK_EQ(counters[name], 0.0); + counters[name] = Counter(measurement, Counter::kAvgIterations); + } + } +} + +void State::ResumeTiming() { + BM_CHECK(started_ && !finished_ && !error_occurred_); + timer_->StartTimer(); + if (perf_counters_measurement_) { + perf_counters_measurement_->Start(); + } +} + +void State::SkipWithError(const char* msg) { + BM_CHECK(msg); + error_occurred_ = true; + { + MutexLock l(manager_->GetBenchmarkMutex()); + if (manager_->results.has_error_ == false) { + manager_->results.error_message_ = msg; + manager_->results.has_error_ = true; + } + } + total_iterations_ = 0; + if (timer_->running()) timer_->StopTimer(); +} + +void State::SetIterationTime(double seconds) { + timer_->SetIterationTime(seconds); +} + +void State::SetLabel(const char* label) { + MutexLock l(manager_->GetBenchmarkMutex()); + manager_->results.report_label_ = label; +} + +void State::StartKeepRunning() { + BM_CHECK(!started_ && !finished_); + started_ = true; + total_iterations_ = error_occurred_ ? 0 : max_iterations; + manager_->StartStopBarrier(); + if (!error_occurred_) ResumeTiming(); +} + +void State::FinishKeepRunning() { + BM_CHECK(started_ && (!finished_ || error_occurred_)); + if (!error_occurred_) { + PauseTiming(); + } + // Total iterations has now wrapped around past 0. Fix this. + total_iterations_ = 0; + finished_ = true; + manager_->StartStopBarrier(); +} + +namespace internal { +namespace { + +// Flushes streams after invoking reporter methods that write to them. This +// ensures users get timely updates even when streams are not line-buffered. +void FlushStreams(BenchmarkReporter* reporter) { + if (!reporter) return; + std::flush(reporter->GetOutputStream()); + std::flush(reporter->GetErrorStream()); +} + +// Reports in both display and file reporters. +void Report(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter, const RunResults& run_results) { + auto report_one = [](BenchmarkReporter* reporter, bool aggregates_only, + const RunResults& results) { + assert(reporter); + // If there are no aggregates, do output non-aggregates. + aggregates_only &= !results.aggregates_only.empty(); + if (!aggregates_only) reporter->ReportRuns(results.non_aggregates); + if (!results.aggregates_only.empty()) + reporter->ReportRuns(results.aggregates_only); + }; + + report_one(display_reporter, run_results.display_report_aggregates_only, + run_results); + if (file_reporter) + report_one(file_reporter, run_results.file_report_aggregates_only, + run_results); + + FlushStreams(display_reporter); + FlushStreams(file_reporter); +} + +void RunBenchmarks(const std::vector& benchmarks, + BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter) { + // Note the file_reporter can be null. + BM_CHECK(display_reporter != nullptr); + + // Determine the width of the name field using a minimum width of 10. + bool might_have_aggregates = FLAGS_benchmark_repetitions > 1; + size_t name_field_width = 10; + size_t stat_field_width = 0; + for (const BenchmarkInstance& benchmark : benchmarks) { + name_field_width = + std::max(name_field_width, benchmark.name().str().size()); + might_have_aggregates |= benchmark.repetitions() > 1; + + for (const auto& Stat : benchmark.statistics()) + stat_field_width = std::max(stat_field_width, Stat.name_.size()); + } + if (might_have_aggregates) name_field_width += 1 + stat_field_width; + + // Print header here + BenchmarkReporter::Context context; + context.name_field_width = name_field_width; + + // Keep track of running times of all instances of each benchmark family. + std::map + per_family_reports; + + if (display_reporter->ReportContext(context) && + (!file_reporter || file_reporter->ReportContext(context))) { + FlushStreams(display_reporter); + FlushStreams(file_reporter); + + size_t num_repetitions_total = 0; + + std::vector runners; + runners.reserve(benchmarks.size()); + for (const BenchmarkInstance& benchmark : benchmarks) { + BenchmarkReporter::PerFamilyRunReports* reports_for_family = nullptr; + if (benchmark.complexity() != oNone) + reports_for_family = &per_family_reports[benchmark.family_index()]; + + runners.emplace_back(benchmark, reports_for_family); + int num_repeats_of_this_instance = runners.back().GetNumRepeats(); + num_repetitions_total += num_repeats_of_this_instance; + if (reports_for_family) + reports_for_family->num_runs_total += num_repeats_of_this_instance; + } + assert(runners.size() == benchmarks.size() && "Unexpected runner count."); + + std::vector repetition_indices; + repetition_indices.reserve(num_repetitions_total); + for (size_t runner_index = 0, num_runners = runners.size(); + runner_index != num_runners; ++runner_index) { + const internal::BenchmarkRunner& runner = runners[runner_index]; + std::fill_n(std::back_inserter(repetition_indices), + runner.GetNumRepeats(), runner_index); + } + assert(repetition_indices.size() == num_repetitions_total && + "Unexpected number of repetition indexes."); + + if (FLAGS_benchmark_enable_random_interleaving) { + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(repetition_indices.begin(), repetition_indices.end(), g); + } + + for (size_t repetition_index : repetition_indices) { + internal::BenchmarkRunner& runner = runners[repetition_index]; + runner.DoOneRepetition(); + if (runner.HasRepeatsRemaining()) continue; + // FIXME: report each repetition separately, not all of them in bulk. + + RunResults run_results = runner.GetResults(); + + // Maybe calculate complexity report + if (const auto* reports_for_family = runner.GetReportsForFamily()) { + if (reports_for_family->num_runs_done == + reports_for_family->num_runs_total) { + auto additional_run_stats = ComputeBigO(reports_for_family->Runs); + run_results.aggregates_only.insert(run_results.aggregates_only.end(), + additional_run_stats.begin(), + additional_run_stats.end()); + per_family_reports.erase( + static_cast(reports_for_family->Runs.front().family_index)); + } + } + + Report(display_reporter, file_reporter, run_results); + } + } + display_reporter->Finalize(); + if (file_reporter) file_reporter->Finalize(); + FlushStreams(display_reporter); + FlushStreams(file_reporter); +} + +// Disable deprecated warnings temporarily because we need to reference +// CSVReporter but don't want to trigger -Werror=-Wdeprecated-declarations +BENCHMARK_DISABLE_DEPRECATED_WARNING + +std::unique_ptr CreateReporter( + std::string const& name, ConsoleReporter::OutputOptions output_opts) { + typedef std::unique_ptr PtrType; + if (name == "console") { + return PtrType(new ConsoleReporter(output_opts)); + } else if (name == "json") { + return PtrType(new JSONReporter); + } else if (name == "csv") { + return PtrType(new CSVReporter); + } else { + std::cerr << "Unexpected format: '" << name << "'\n"; + std::exit(1); + } +} + +BENCHMARK_RESTORE_DEPRECATED_WARNING + +} // end namespace + +bool IsZero(double n) { + return std::abs(n) < std::numeric_limits::epsilon(); +} + +ConsoleReporter::OutputOptions GetOutputOptions(bool force_no_color) { + int output_opts = ConsoleReporter::OO_Defaults; + auto is_benchmark_color = [force_no_color]() -> bool { + if (force_no_color) { + return false; + } + if (FLAGS_benchmark_color == "auto") { + return IsColorTerminal(); + } + return IsTruthyFlagValue(FLAGS_benchmark_color); + }; + if (is_benchmark_color()) { + output_opts |= ConsoleReporter::OO_Color; + } else { + output_opts &= ~ConsoleReporter::OO_Color; + } + if (FLAGS_benchmark_counters_tabular) { + output_opts |= ConsoleReporter::OO_Tabular; + } else { + output_opts &= ~ConsoleReporter::OO_Tabular; + } + return static_cast(output_opts); +} + +} // end namespace internal + +size_t RunSpecifiedBenchmarks() { + return RunSpecifiedBenchmarks(nullptr, nullptr, FLAGS_benchmark_filter); +} + +size_t RunSpecifiedBenchmarks(std::string spec) { + return RunSpecifiedBenchmarks(nullptr, nullptr, std::move(spec)); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter) { + return RunSpecifiedBenchmarks(display_reporter, nullptr, + FLAGS_benchmark_filter); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + std::string spec) { + return RunSpecifiedBenchmarks(display_reporter, nullptr, std::move(spec)); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter) { + return RunSpecifiedBenchmarks(display_reporter, file_reporter, + FLAGS_benchmark_filter); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter, + std::string spec) { + if (spec.empty() || spec == "all") + spec = "."; // Regexp that matches all benchmarks + + // Setup the reporters + std::ofstream output_file; + std::unique_ptr default_display_reporter; + std::unique_ptr default_file_reporter; + if (!display_reporter) { + default_display_reporter = internal::CreateReporter( + FLAGS_benchmark_format, internal::GetOutputOptions()); + display_reporter = default_display_reporter.get(); + } + auto& Out = display_reporter->GetOutputStream(); + auto& Err = display_reporter->GetErrorStream(); + + std::string const& fname = FLAGS_benchmark_out; + if (fname.empty() && file_reporter) { + Err << "A custom file reporter was provided but " + "--benchmark_out= was not specified." + << std::endl; + std::exit(1); + } + if (!fname.empty()) { + output_file.open(fname); + if (!output_file.is_open()) { + Err << "invalid file name: '" << fname << "'" << std::endl; + std::exit(1); + } + if (!file_reporter) { + default_file_reporter = internal::CreateReporter( + FLAGS_benchmark_out_format, ConsoleReporter::OO_None); + file_reporter = default_file_reporter.get(); + } + file_reporter->SetOutputStream(&output_file); + file_reporter->SetErrorStream(&output_file); + } + + std::vector benchmarks; + if (!FindBenchmarksInternal(spec, &benchmarks, &Err)) return 0; + + if (benchmarks.empty()) { + Err << "Failed to match any benchmarks against regex: " << spec << "\n"; + return 0; + } + + if (FLAGS_benchmark_list_tests) { + for (auto const& benchmark : benchmarks) + Out << benchmark.name().str() << "\n"; + } else { + internal::RunBenchmarks(benchmarks, display_reporter, file_reporter); + } + + return benchmarks.size(); +} + +std::string GetBenchmarkFilter() { return FLAGS_benchmark_filter; } + +void RegisterMemoryManager(MemoryManager* manager) { + internal::memory_manager = manager; +} + +void AddCustomContext(const std::string& key, const std::string& value) { + if (internal::global_context == nullptr) { + internal::global_context = new std::map(); + } + if (!internal::global_context->emplace(key, value).second) { + std::cerr << "Failed to add custom context \"" << key << "\" as it already " + << "exists with value \"" << value << "\"\n"; + } +} + +namespace internal { + +void PrintUsageAndExit() { + fprintf(stdout, + "benchmark" + " [--benchmark_list_tests={true|false}]\n" + " [--benchmark_filter=]\n" + " [--benchmark_min_time=]\n" + " [--benchmark_repetitions=]\n" + " [--benchmark_enable_random_interleaving={true|false}]\n" + " [--benchmark_report_aggregates_only={true|false}]\n" + " [--benchmark_display_aggregates_only={true|false}]\n" + " [--benchmark_format=]\n" + " [--benchmark_out=]\n" + " [--benchmark_out_format=]\n" + " [--benchmark_color={auto|true|false}]\n" + " [--benchmark_counters_tabular={true|false}]\n" + " [--benchmark_perf_counters=,...]\n" + " [--benchmark_context==,...]\n" + " [--v=]\n"); + exit(0); +} + +void ParseCommandLineFlags(int* argc, char** argv) { + using namespace benchmark; + BenchmarkReporter::Context::executable_name = + (argc && *argc > 0) ? argv[0] : "unknown"; + for (int i = 1; argc && i < *argc; ++i) { + if (ParseBoolFlag(argv[i], "benchmark_list_tests", + &FLAGS_benchmark_list_tests) || + ParseStringFlag(argv[i], "benchmark_filter", &FLAGS_benchmark_filter) || + ParseDoubleFlag(argv[i], "benchmark_min_time", + &FLAGS_benchmark_min_time) || + ParseInt32Flag(argv[i], "benchmark_repetitions", + &FLAGS_benchmark_repetitions) || + ParseBoolFlag(argv[i], "benchmark_enable_random_interleaving", + &FLAGS_benchmark_enable_random_interleaving) || + ParseBoolFlag(argv[i], "benchmark_report_aggregates_only", + &FLAGS_benchmark_report_aggregates_only) || + ParseBoolFlag(argv[i], "benchmark_display_aggregates_only", + &FLAGS_benchmark_display_aggregates_only) || + ParseStringFlag(argv[i], "benchmark_format", &FLAGS_benchmark_format) || + ParseStringFlag(argv[i], "benchmark_out", &FLAGS_benchmark_out) || + ParseStringFlag(argv[i], "benchmark_out_format", + &FLAGS_benchmark_out_format) || + ParseStringFlag(argv[i], "benchmark_color", &FLAGS_benchmark_color) || + ParseBoolFlag(argv[i], "benchmark_counters_tabular", + &FLAGS_benchmark_counters_tabular) || + ParseStringFlag(argv[i], "benchmark_perf_counters", + &FLAGS_benchmark_perf_counters) || + ParseKeyValueFlag(argv[i], "benchmark_context", + &FLAGS_benchmark_context) || + ParseInt32Flag(argv[i], "v", &FLAGS_v)) { + for (int j = i; j != *argc - 1; ++j) argv[j] = argv[j + 1]; + + --(*argc); + --i; + } else if (IsFlag(argv[i], "help")) { + PrintUsageAndExit(); + } + } + for (auto const* flag : + {&FLAGS_benchmark_format, &FLAGS_benchmark_out_format}) { + if (*flag != "console" && *flag != "json" && *flag != "csv") { + PrintUsageAndExit(); + } + } + if (FLAGS_benchmark_color.empty()) { + PrintUsageAndExit(); + } + for (const auto& kv : FLAGS_benchmark_context) { + AddCustomContext(kv.first, kv.second); + } +} + +int InitializeStreams() { + static std::ios_base::Init init; + return 0; +} + +} // end namespace internal + +void Initialize(int* argc, char** argv) { + internal::ParseCommandLineFlags(argc, argv); + internal::LogLevel() = FLAGS_v; +} + +void Shutdown() { delete internal::global_context; } + +bool ReportUnrecognizedArguments(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + fprintf(stderr, "%s: error: unrecognized command-line flag: %s\n", argv[0], + argv[i]); + } + return argc > 1; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/benchmark_api_internal.cc b/bridge/third_party/benchmark/src/benchmark_api_internal.cc new file mode 100644 index 0000000000..4de36e3c8b --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_api_internal.cc @@ -0,0 +1,112 @@ +#include "benchmark_api_internal.h" + +#include + +#include "string_util.h" + +namespace benchmark { +namespace internal { + +BenchmarkInstance::BenchmarkInstance(Benchmark* benchmark, int family_idx, + int per_family_instance_idx, + const std::vector& args, + int thread_count) + : benchmark_(*benchmark), + family_index_(family_idx), + per_family_instance_index_(per_family_instance_idx), + aggregation_report_mode_(benchmark_.aggregation_report_mode_), + args_(args), + time_unit_(benchmark_.time_unit_), + measure_process_cpu_time_(benchmark_.measure_process_cpu_time_), + use_real_time_(benchmark_.use_real_time_), + use_manual_time_(benchmark_.use_manual_time_), + complexity_(benchmark_.complexity_), + complexity_lambda_(benchmark_.complexity_lambda_), + statistics_(benchmark_.statistics_), + repetitions_(benchmark_.repetitions_), + min_time_(benchmark_.min_time_), + iterations_(benchmark_.iterations_), + threads_(thread_count) { + name_.function_name = benchmark_.name_; + + size_t arg_i = 0; + for (const auto& arg : args) { + if (!name_.args.empty()) { + name_.args += '/'; + } + + if (arg_i < benchmark->arg_names_.size()) { + const auto& arg_name = benchmark_.arg_names_[arg_i]; + if (!arg_name.empty()) { + name_.args += StrFormat("%s:", arg_name.c_str()); + } + } + + name_.args += StrFormat("%" PRId64, arg); + ++arg_i; + } + + if (!IsZero(benchmark->min_time_)) { + name_.min_time = StrFormat("min_time:%0.3f", benchmark_.min_time_); + } + + if (benchmark_.iterations_ != 0) { + name_.iterations = StrFormat( + "iterations:%lu", static_cast(benchmark_.iterations_)); + } + + if (benchmark_.repetitions_ != 0) { + name_.repetitions = StrFormat("repeats:%d", benchmark_.repetitions_); + } + + if (benchmark_.measure_process_cpu_time_) { + name_.time_type = "process_time"; + } + + if (benchmark_.use_manual_time_) { + if (!name_.time_type.empty()) { + name_.time_type += '/'; + } + name_.time_type += "manual_time"; + } else if (benchmark_.use_real_time_) { + if (!name_.time_type.empty()) { + name_.time_type += '/'; + } + name_.time_type += "real_time"; + } + + if (!benchmark_.thread_counts_.empty()) { + name_.threads = StrFormat("threads:%d", threads_); + } + + setup_ = benchmark_.setup_; + teardown_ = benchmark_.teardown_; +} + +State BenchmarkInstance::Run( + IterationCount iters, int thread_id, internal::ThreadTimer* timer, + internal::ThreadManager* manager, + internal::PerfCountersMeasurement* perf_counters_measurement) const { + State st(iters, args_, thread_id, threads_, timer, manager, + perf_counters_measurement); + benchmark_.Run(st); + return st; +} + +void BenchmarkInstance::Setup() const { + if (setup_) { + State st(/*iters*/ 1, args_, /*thread_id*/ 0, threads_, nullptr, nullptr, + nullptr); + setup_(st); + } +} + +void BenchmarkInstance::Teardown() const { + if (teardown_) { + State st(/*iters*/ 1, args_, /*thread_id*/ 0, threads_, nullptr, nullptr, + nullptr); + teardown_(st); + } +} +} // namespace internal +} // namespace benchmark diff --git a/bridge/third_party/benchmark/src/benchmark_api_internal.h b/bridge/third_party/benchmark/src/benchmark_api_internal.h new file mode 100644 index 0000000000..94c2b2972b --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_api_internal.h @@ -0,0 +1,84 @@ +#ifndef BENCHMARK_API_INTERNAL_H +#define BENCHMARK_API_INTERNAL_H + +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "commandlineflags.h" + +namespace benchmark { +namespace internal { + +// Information kept per benchmark we may want to run +class BenchmarkInstance { + public: + BenchmarkInstance(Benchmark* benchmark, int family_index, + int per_family_instance_index, + const std::vector& args, int threads); + + const BenchmarkName& name() const { return name_; } + int family_index() const { return family_index_; } + int per_family_instance_index() const { return per_family_instance_index_; } + AggregationReportMode aggregation_report_mode() const { + return aggregation_report_mode_; + } + TimeUnit time_unit() const { return time_unit_; } + bool measure_process_cpu_time() const { return measure_process_cpu_time_; } + bool use_real_time() const { return use_real_time_; } + bool use_manual_time() const { return use_manual_time_; } + BigO complexity() const { return complexity_; } + BigOFunc* complexity_lambda() const { return complexity_lambda_; } + const std::vector& statistics() const { return statistics_; } + int repetitions() const { return repetitions_; } + double min_time() const { return min_time_; } + IterationCount iterations() const { return iterations_; } + int threads() const { return threads_; } + void Setup() const; + void Teardown() const; + + State Run(IterationCount iters, int thread_id, internal::ThreadTimer* timer, + internal::ThreadManager* manager, + internal::PerfCountersMeasurement* perf_counters_measurement) const; + + private: + BenchmarkName name_; + Benchmark& benchmark_; + const int family_index_; + const int per_family_instance_index_; + AggregationReportMode aggregation_report_mode_; + const std::vector& args_; + TimeUnit time_unit_; + bool measure_process_cpu_time_; + bool use_real_time_; + bool use_manual_time_; + BigO complexity_; + BigOFunc* complexity_lambda_; + UserCounters counters_; + const std::vector& statistics_; + int repetitions_; + double min_time_; + IterationCount iterations_; + int threads_; // Number of concurrent threads to us + + typedef void (*callback_function)(const benchmark::State&); + callback_function setup_ = nullptr; + callback_function teardown_ = nullptr; +}; + +bool FindBenchmarksInternal(const std::string& re, + std::vector* benchmarks, + std::ostream* Err); + +bool IsZero(double n); + +ConsoleReporter::OutputOptions GetOutputOptions(bool force_no_color = false); + +} // end namespace internal +} // end namespace benchmark + +#endif // BENCHMARK_API_INTERNAL_H diff --git a/bridge/third_party/benchmark/src/benchmark_main.cc b/bridge/third_party/benchmark/src/benchmark_main.cc new file mode 100644 index 0000000000..b3b2478314 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_main.cc @@ -0,0 +1,17 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/src/benchmark_name.cc b/bridge/third_party/benchmark/src/benchmark_name.cc new file mode 100644 index 0000000000..2a17ebce27 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_name.cc @@ -0,0 +1,58 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace benchmark { + +namespace { + +// Compute the total size of a pack of std::strings +size_t size_impl() { return 0; } + +template +size_t size_impl(const Head& head, const Tail&... tail) { + return head.size() + size_impl(tail...); +} + +// Join a pack of std::strings using a delimiter +// TODO: use absl::StrJoin +void join_impl(std::string&, char) {} + +template +void join_impl(std::string& s, const char delimiter, const Head& head, + const Tail&... tail) { + if (!s.empty() && !head.empty()) { + s += delimiter; + } + + s += head; + + join_impl(s, delimiter, tail...); +} + +template +std::string join(char delimiter, const Ts&... ts) { + std::string s; + s.reserve(sizeof...(Ts) + size_impl(ts...)); + join_impl(s, delimiter, ts...); + return s; +} +} // namespace + +std::string BenchmarkName::str() const { + return join('/', function_name, args, min_time, iterations, repetitions, + time_type, threads); +} +} // namespace benchmark diff --git a/bridge/third_party/benchmark/src/benchmark_register.cc b/bridge/third_party/benchmark/src/benchmark_register.cc new file mode 100644 index 0000000000..61a0c26178 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_register.cc @@ -0,0 +1,492 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark_register.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "benchmark_api_internal.h" +#include "check.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { + +namespace { +// For non-dense Range, intermediate values are powers of kRangeMultiplier. +static const int kRangeMultiplier = 8; +// The size of a benchmark family determines is the number of inputs to repeat +// the benchmark on. If this is "large" then warn the user during configuration. +static const size_t kMaxFamilySize = 100; +} // end namespace + +namespace internal { + +//=============================================================================// +// BenchmarkFamilies +//=============================================================================// + +// Class for managing registered benchmarks. Note that each registered +// benchmark identifies a family of related benchmarks to run. +class BenchmarkFamilies { + public: + static BenchmarkFamilies* GetInstance(); + + // Registers a benchmark family and returns the index assigned to it. + size_t AddBenchmark(std::unique_ptr family); + + // Clear all registered benchmark families. + void ClearBenchmarks(); + + // Extract the list of benchmark instances that match the specified + // regular expression. + bool FindBenchmarks(std::string re, + std::vector* benchmarks, + std::ostream* Err); + + private: + BenchmarkFamilies() {} + + std::vector> families_; + Mutex mutex_; +}; + +BenchmarkFamilies* BenchmarkFamilies::GetInstance() { + static BenchmarkFamilies instance; + return &instance; +} + +size_t BenchmarkFamilies::AddBenchmark(std::unique_ptr family) { + MutexLock l(mutex_); + size_t index = families_.size(); + families_.push_back(std::move(family)); + return index; +} + +void BenchmarkFamilies::ClearBenchmarks() { + MutexLock l(mutex_); + families_.clear(); + families_.shrink_to_fit(); +} + +bool BenchmarkFamilies::FindBenchmarks( + std::string spec, std::vector* benchmarks, + std::ostream* ErrStream) { + BM_CHECK(ErrStream); + auto& Err = *ErrStream; + // Make regular expression out of command-line flag + std::string error_msg; + Regex re; + bool isNegativeFilter = false; + if (spec[0] == '-') { + spec.replace(0, 1, ""); + isNegativeFilter = true; + } + if (!re.Init(spec, &error_msg)) { + Err << "Could not compile benchmark re: " << error_msg << std::endl; + return false; + } + + // Special list of thread counts to use when none are specified + const std::vector one_thread = {1}; + + int next_family_index = 0; + + MutexLock l(mutex_); + for (std::unique_ptr& family : families_) { + int family_index = next_family_index; + int per_family_instance_index = 0; + + // Family was deleted or benchmark doesn't match + if (!family) continue; + + if (family->ArgsCnt() == -1) { + family->Args({}); + } + const std::vector* thread_counts = + (family->thread_counts_.empty() + ? &one_thread + : &static_cast&>(family->thread_counts_)); + const size_t family_size = family->args_.size() * thread_counts->size(); + // The benchmark will be run at least 'family_size' different inputs. + // If 'family_size' is very large warn the user. + if (family_size > kMaxFamilySize) { + Err << "The number of inputs is very large. " << family->name_ + << " will be repeated at least " << family_size << " times.\n"; + } + // reserve in the special case the regex ".", since we know the final + // family size. + if (spec == ".") benchmarks->reserve(benchmarks->size() + family_size); + + for (auto const& args : family->args_) { + for (int num_threads : *thread_counts) { + BenchmarkInstance instance(family.get(), family_index, + per_family_instance_index, args, + num_threads); + + const auto full_name = instance.name().str(); + if ((re.Match(full_name) && !isNegativeFilter) || + (!re.Match(full_name) && isNegativeFilter)) { + benchmarks->push_back(std::move(instance)); + + ++per_family_instance_index; + + // Only bump the next family index once we've estabilished that + // at least one instance of this family will be run. + if (next_family_index == family_index) ++next_family_index; + } + } + } + } + return true; +} + +Benchmark* RegisterBenchmarkInternal(Benchmark* bench) { + std::unique_ptr bench_ptr(bench); + BenchmarkFamilies* families = BenchmarkFamilies::GetInstance(); + families->AddBenchmark(std::move(bench_ptr)); + return bench; +} + +// FIXME: This function is a hack so that benchmark.cc can access +// `BenchmarkFamilies` +bool FindBenchmarksInternal(const std::string& re, + std::vector* benchmarks, + std::ostream* Err) { + return BenchmarkFamilies::GetInstance()->FindBenchmarks(re, benchmarks, Err); +} + +//=============================================================================// +// Benchmark +//=============================================================================// + +Benchmark::Benchmark(const char* name) + : name_(name), + aggregation_report_mode_(ARM_Unspecified), + time_unit_(kNanosecond), + range_multiplier_(kRangeMultiplier), + min_time_(0), + iterations_(0), + repetitions_(0), + measure_process_cpu_time_(false), + use_real_time_(false), + use_manual_time_(false), + complexity_(oNone), + complexity_lambda_(nullptr), + setup_(nullptr), + teardown_(nullptr) { + ComputeStatistics("mean", StatisticsMean); + ComputeStatistics("median", StatisticsMedian); + ComputeStatistics("stddev", StatisticsStdDev); + ComputeStatistics("cv", StatisticsCV, kPercentage); +} + +Benchmark::~Benchmark() {} + +Benchmark* Benchmark::Name(const std::string& name) { + SetName(name.c_str()); + return this; +} + +Benchmark* Benchmark::Arg(int64_t x) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + args_.push_back({x}); + return this; +} + +Benchmark* Benchmark::Unit(TimeUnit unit) { + time_unit_ = unit; + return this; +} + +Benchmark* Benchmark::Range(int64_t start, int64_t limit) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + std::vector arglist; + AddRange(&arglist, start, limit, range_multiplier_); + + for (int64_t i : arglist) { + args_.push_back({i}); + } + return this; +} + +Benchmark* Benchmark::Ranges( + const std::vector>& ranges) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(ranges.size())); + std::vector> arglists(ranges.size()); + for (std::size_t i = 0; i < ranges.size(); i++) { + AddRange(&arglists[i], ranges[i].first, ranges[i].second, + range_multiplier_); + } + + ArgsProduct(arglists); + + return this; +} + +Benchmark* Benchmark::ArgsProduct( + const std::vector>& arglists) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(arglists.size())); + + std::vector indices(arglists.size()); + const std::size_t total = std::accumulate( + std::begin(arglists), std::end(arglists), std::size_t{1}, + [](const std::size_t res, const std::vector& arglist) { + return res * arglist.size(); + }); + std::vector args; + args.reserve(arglists.size()); + for (std::size_t i = 0; i < total; i++) { + for (std::size_t arg = 0; arg < arglists.size(); arg++) { + args.push_back(arglists[arg][indices[arg]]); + } + args_.push_back(args); + args.clear(); + + std::size_t arg = 0; + do { + indices[arg] = (indices[arg] + 1) % arglists[arg].size(); + } while (indices[arg++] == 0 && arg < arglists.size()); + } + + return this; +} + +Benchmark* Benchmark::ArgName(const std::string& name) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + arg_names_ = {name}; + return this; +} + +Benchmark* Benchmark::ArgNames(const std::vector& names) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(names.size())); + arg_names_ = names; + return this; +} + +Benchmark* Benchmark::DenseRange(int64_t start, int64_t limit, int step) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + BM_CHECK_LE(start, limit); + for (int64_t arg = start; arg <= limit; arg += step) { + args_.push_back({arg}); + } + return this; +} + +Benchmark* Benchmark::Args(const std::vector& args) { + BM_CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(args.size())); + args_.push_back(args); + return this; +} + +Benchmark* Benchmark::Apply(void (*custom_arguments)(Benchmark* benchmark)) { + custom_arguments(this); + return this; +} + +Benchmark* Benchmark::Setup(void (*setup)(const benchmark::State&)) { + BM_CHECK(setup != nullptr); + setup_ = setup; + return this; +} + +Benchmark* Benchmark::Teardown(void (*teardown)(const benchmark::State&)) { + BM_CHECK(teardown != nullptr); + teardown_ = teardown; + return this; +} + +Benchmark* Benchmark::RangeMultiplier(int multiplier) { + BM_CHECK(multiplier > 1); + range_multiplier_ = multiplier; + return this; +} + +Benchmark* Benchmark::MinTime(double t) { + BM_CHECK(t > 0.0); + BM_CHECK(iterations_ == 0); + min_time_ = t; + return this; +} + +Benchmark* Benchmark::Iterations(IterationCount n) { + BM_CHECK(n > 0); + BM_CHECK(IsZero(min_time_)); + iterations_ = n; + return this; +} + +Benchmark* Benchmark::Repetitions(int n) { + BM_CHECK(n > 0); + repetitions_ = n; + return this; +} + +Benchmark* Benchmark::ReportAggregatesOnly(bool value) { + aggregation_report_mode_ = value ? ARM_ReportAggregatesOnly : ARM_Default; + return this; +} + +Benchmark* Benchmark::DisplayAggregatesOnly(bool value) { + // If we were called, the report mode is no longer 'unspecified', in any case. + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ | ARM_Default); + + if (value) { + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ | ARM_DisplayReportAggregatesOnly); + } else { + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ & ~ARM_DisplayReportAggregatesOnly); + } + + return this; +} + +Benchmark* Benchmark::MeasureProcessCPUTime() { + // Can be used together with UseRealTime() / UseManualTime(). + measure_process_cpu_time_ = true; + return this; +} + +Benchmark* Benchmark::UseRealTime() { + BM_CHECK(!use_manual_time_) + << "Cannot set UseRealTime and UseManualTime simultaneously."; + use_real_time_ = true; + return this; +} + +Benchmark* Benchmark::UseManualTime() { + BM_CHECK(!use_real_time_) + << "Cannot set UseRealTime and UseManualTime simultaneously."; + use_manual_time_ = true; + return this; +} + +Benchmark* Benchmark::Complexity(BigO complexity) { + complexity_ = complexity; + return this; +} + +Benchmark* Benchmark::Complexity(BigOFunc* complexity) { + complexity_lambda_ = complexity; + complexity_ = oLambda; + return this; +} + +Benchmark* Benchmark::ComputeStatistics(const std::string& name, + StatisticsFunc* statistics, + StatisticUnit unit) { + statistics_.emplace_back(name, statistics, unit); + return this; +} + +Benchmark* Benchmark::Threads(int t) { + BM_CHECK_GT(t, 0); + thread_counts_.push_back(t); + return this; +} + +Benchmark* Benchmark::ThreadRange(int min_threads, int max_threads) { + BM_CHECK_GT(min_threads, 0); + BM_CHECK_GE(max_threads, min_threads); + + AddRange(&thread_counts_, min_threads, max_threads, 2); + return this; +} + +Benchmark* Benchmark::DenseThreadRange(int min_threads, int max_threads, + int stride) { + BM_CHECK_GT(min_threads, 0); + BM_CHECK_GE(max_threads, min_threads); + BM_CHECK_GE(stride, 1); + + for (auto i = min_threads; i < max_threads; i += stride) { + thread_counts_.push_back(i); + } + thread_counts_.push_back(max_threads); + return this; +} + +Benchmark* Benchmark::ThreadPerCpu() { + thread_counts_.push_back(CPUInfo::Get().num_cpus); + return this; +} + +void Benchmark::SetName(const char* name) { name_ = name; } + +int Benchmark::ArgsCnt() const { + if (args_.empty()) { + if (arg_names_.empty()) return -1; + return static_cast(arg_names_.size()); + } + return static_cast(args_.front().size()); +} + +//=============================================================================// +// FunctionBenchmark +//=============================================================================// + +void FunctionBenchmark::Run(State& st) { func_(st); } + +} // end namespace internal + +void ClearRegisteredBenchmarks() { + internal::BenchmarkFamilies::GetInstance()->ClearBenchmarks(); +} + +std::vector CreateRange(int64_t lo, int64_t hi, int multi) { + std::vector args; + internal::AddRange(&args, lo, hi, multi); + return args; +} + +std::vector CreateDenseRange(int64_t start, int64_t limit, int step) { + BM_CHECK_LE(start, limit); + std::vector args; + for (int64_t arg = start; arg <= limit; arg += step) { + args.push_back(arg); + } + return args; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/benchmark_register.h b/bridge/third_party/benchmark/src/benchmark_register.h new file mode 100644 index 0000000000..d3f4974e90 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_register.h @@ -0,0 +1,108 @@ +#ifndef BENCHMARK_REGISTER_H +#define BENCHMARK_REGISTER_H + +#include +#include + +#include "check.h" + +namespace benchmark { +namespace internal { + +// Append the powers of 'mult' in the closed interval [lo, hi]. +// Returns iterator to the start of the inserted range. +template +typename std::vector::iterator AddPowers(std::vector* dst, T lo, T hi, + int mult) { + BM_CHECK_GE(lo, 0); + BM_CHECK_GE(hi, lo); + BM_CHECK_GE(mult, 2); + + const size_t start_offset = dst->size(); + + static const T kmax = std::numeric_limits::max(); + + // Space out the values in multiples of "mult" + for (T i = static_cast(1); i <= hi; i *= mult) { + if (i >= lo) { + dst->push_back(i); + } + // Break the loop here since multiplying by + // 'mult' would move outside of the range of T + if (i > kmax / mult) break; + } + + return dst->begin() + start_offset; +} + +template +void AddNegatedPowers(std::vector* dst, T lo, T hi, int mult) { + // We negate lo and hi so we require that they cannot be equal to 'min'. + BM_CHECK_GT(lo, std::numeric_limits::min()); + BM_CHECK_GT(hi, std::numeric_limits::min()); + BM_CHECK_GE(hi, lo); + BM_CHECK_LE(hi, 0); + + // Add positive powers, then negate and reverse. + // Casts necessary since small integers get promoted + // to 'int' when negating. + const auto lo_complement = static_cast(-lo); + const auto hi_complement = static_cast(-hi); + + const auto it = AddPowers(dst, hi_complement, lo_complement, mult); + + std::for_each(it, dst->end(), [](T& t) { t *= -1; }); + std::reverse(it, dst->end()); +} + +template +void AddRange(std::vector* dst, T lo, T hi, int mult) { + static_assert(std::is_integral::value && std::is_signed::value, + "Args type must be a signed integer"); + + BM_CHECK_GE(hi, lo); + BM_CHECK_GE(mult, 2); + + // Add "lo" + dst->push_back(lo); + + // Handle lo == hi as a special case, so we then know + // lo < hi and so it is safe to add 1 to lo and subtract 1 + // from hi without falling outside of the range of T. + if (lo == hi) return; + + // Ensure that lo_inner <= hi_inner below. + if (lo + 1 == hi) { + dst->push_back(hi); + return; + } + + // Add all powers of 'mult' in the range [lo+1, hi-1] (inclusive). + const auto lo_inner = static_cast(lo + 1); + const auto hi_inner = static_cast(hi - 1); + + // Insert negative values + if (lo_inner < 0) { + AddNegatedPowers(dst, lo_inner, std::min(hi_inner, T{-1}), mult); + } + + // Treat 0 as a special case (see discussion on #762). + if (lo < 0 && hi >= 0) { + dst->push_back(0); + } + + // Insert positive values + if (hi_inner > 0) { + AddPowers(dst, std::max(lo_inner, T{1}), hi_inner, mult); + } + + // Add "hi" (if different from last value). + if (hi != dst->back()) { + dst->push_back(hi); + } +} + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_REGISTER_H diff --git a/bridge/third_party/benchmark/src/benchmark_runner.cc b/bridge/third_party/benchmark/src/benchmark_runner.cc new file mode 100644 index 0000000000..eac807b066 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_runner.cc @@ -0,0 +1,359 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark_runner.h" + +#include "benchmark/benchmark.h" +#include "benchmark_api_internal.h" +#include "internal_macros.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "counter.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "perf_counters.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "thread_manager.h" +#include "thread_timer.h" + +namespace benchmark { + +namespace internal { + +MemoryManager* memory_manager = nullptr; + +namespace { + +static constexpr IterationCount kMaxIterations = 1000000000; + +BenchmarkReporter::Run CreateRunReport( + const benchmark::internal::BenchmarkInstance& b, + const internal::ThreadManager::Result& results, + IterationCount memory_iterations, + const MemoryManager::Result* memory_result, double seconds, + int64_t repetition_index, int64_t repeats) { + // Create report about this benchmark run. + BenchmarkReporter::Run report; + + report.run_name = b.name(); + report.family_index = b.family_index(); + report.per_family_instance_index = b.per_family_instance_index(); + report.error_occurred = results.has_error_; + report.error_message = results.error_message_; + report.report_label = results.report_label_; + // This is the total iterations across all threads. + report.iterations = results.iterations; + report.time_unit = b.time_unit(); + report.threads = b.threads(); + report.repetition_index = repetition_index; + report.repetitions = repeats; + + if (!report.error_occurred) { + if (b.use_manual_time()) { + report.real_accumulated_time = results.manual_time_used; + } else { + report.real_accumulated_time = results.real_time_used; + } + report.cpu_accumulated_time = results.cpu_time_used; + report.complexity_n = results.complexity_n; + report.complexity = b.complexity(); + report.complexity_lambda = b.complexity_lambda(); + report.statistics = &b.statistics(); + report.counters = results.counters; + + if (memory_iterations > 0) { + assert(memory_result != nullptr); + report.memory_result = memory_result; + report.allocs_per_iter = + memory_iterations ? static_cast(memory_result->num_allocs) / + memory_iterations + : 0; + } + + internal::Finish(&report.counters, results.iterations, seconds, + b.threads()); + } + return report; +} + +// Execute one thread of benchmark b for the specified number of iterations. +// Adds the stats collected for the thread into manager->results. +void RunInThread(const BenchmarkInstance* b, IterationCount iters, + int thread_id, ThreadManager* manager, + PerfCountersMeasurement* perf_counters_measurement) { + internal::ThreadTimer timer( + b->measure_process_cpu_time() + ? internal::ThreadTimer::CreateProcessCpuTime() + : internal::ThreadTimer::Create()); + State st = + b->Run(iters, thread_id, &timer, manager, perf_counters_measurement); + BM_CHECK(st.error_occurred() || st.iterations() >= st.max_iterations) + << "Benchmark returned before State::KeepRunning() returned false!"; + { + MutexLock l(manager->GetBenchmarkMutex()); + internal::ThreadManager::Result& results = manager->results; + results.iterations += st.iterations(); + results.cpu_time_used += timer.cpu_time_used(); + results.real_time_used += timer.real_time_used(); + results.manual_time_used += timer.manual_time_used(); + results.complexity_n += st.complexity_length_n(); + internal::Increment(&results.counters, st.counters); + } + manager->NotifyThreadComplete(); +} + +} // end namespace + +BenchmarkRunner::BenchmarkRunner( + const benchmark::internal::BenchmarkInstance& b_, + BenchmarkReporter::PerFamilyRunReports* reports_for_family_) + : b(b_), + reports_for_family(reports_for_family_), + min_time(!IsZero(b.min_time()) ? b.min_time() : FLAGS_benchmark_min_time), + repeats(b.repetitions() != 0 ? b.repetitions() + : FLAGS_benchmark_repetitions), + has_explicit_iteration_count(b.iterations() != 0), + pool(b.threads() - 1), + iters(has_explicit_iteration_count ? b.iterations() : 1), + perf_counters_measurement( + PerfCounters::Create(StrSplit(FLAGS_benchmark_perf_counters, ','))), + perf_counters_measurement_ptr(perf_counters_measurement.IsValid() + ? &perf_counters_measurement + : nullptr) { + run_results.display_report_aggregates_only = + (FLAGS_benchmark_report_aggregates_only || + FLAGS_benchmark_display_aggregates_only); + run_results.file_report_aggregates_only = + FLAGS_benchmark_report_aggregates_only; + if (b.aggregation_report_mode() != internal::ARM_Unspecified) { + run_results.display_report_aggregates_only = + (b.aggregation_report_mode() & + internal::ARM_DisplayReportAggregatesOnly); + run_results.file_report_aggregates_only = + (b.aggregation_report_mode() & internal::ARM_FileReportAggregatesOnly); + BM_CHECK(FLAGS_benchmark_perf_counters.empty() || + perf_counters_measurement.IsValid()) + << "Perf counters were requested but could not be set up."; + } +} + +BenchmarkRunner::IterationResults BenchmarkRunner::DoNIterations() { + BM_VLOG(2) << "Running " << b.name().str() << " for " << iters << "\n"; + + std::unique_ptr manager; + manager.reset(new internal::ThreadManager(b.threads())); + + // Run all but one thread in separate threads + for (std::size_t ti = 0; ti < pool.size(); ++ti) { + pool[ti] = std::thread(&RunInThread, &b, iters, static_cast(ti + 1), + manager.get(), perf_counters_measurement_ptr); + } + // And run one thread here directly. + // (If we were asked to run just one thread, we don't create new threads.) + // Yes, we need to do this here *after* we start the separate threads. + RunInThread(&b, iters, 0, manager.get(), perf_counters_measurement_ptr); + + // The main thread has finished. Now let's wait for the other threads. + manager->WaitForAllThreads(); + for (std::thread& thread : pool) thread.join(); + + IterationResults i; + // Acquire the measurements/counters from the manager, UNDER THE LOCK! + { + MutexLock l(manager->GetBenchmarkMutex()); + i.results = manager->results; + } + + // And get rid of the manager. + manager.reset(); + + // Adjust real/manual time stats since they were reported per thread. + i.results.real_time_used /= b.threads(); + i.results.manual_time_used /= b.threads(); + // If we were measuring whole-process CPU usage, adjust the CPU time too. + if (b.measure_process_cpu_time()) i.results.cpu_time_used /= b.threads(); + + BM_VLOG(2) << "Ran in " << i.results.cpu_time_used << "/" + << i.results.real_time_used << "\n"; + + // By using KeepRunningBatch a benchmark can iterate more times than + // requested, so take the iteration count from i.results. + i.iters = i.results.iterations / b.threads(); + + // Base decisions off of real time if requested by this benchmark. + i.seconds = i.results.cpu_time_used; + if (b.use_manual_time()) { + i.seconds = i.results.manual_time_used; + } else if (b.use_real_time()) { + i.seconds = i.results.real_time_used; + } + + return i; +} + +IterationCount BenchmarkRunner::PredictNumItersNeeded( + const IterationResults& i) const { + // See how much iterations should be increased by. + // Note: Avoid division by zero with max(seconds, 1ns). + double multiplier = min_time * 1.4 / std::max(i.seconds, 1e-9); + // If our last run was at least 10% of FLAGS_benchmark_min_time then we + // use the multiplier directly. + // Otherwise we use at most 10 times expansion. + // NOTE: When the last run was at least 10% of the min time the max + // expansion should be 14x. + bool is_significant = (i.seconds / min_time) > 0.1; + multiplier = is_significant ? multiplier : 10.0; + + // So what seems to be the sufficiently-large iteration count? Round up. + const IterationCount max_next_iters = static_cast( + std::lround(std::max(multiplier * static_cast(i.iters), + static_cast(i.iters) + 1.0))); + // But we do have *some* sanity limits though.. + const IterationCount next_iters = std::min(max_next_iters, kMaxIterations); + + BM_VLOG(3) << "Next iters: " << next_iters << ", " << multiplier << "\n"; + return next_iters; // round up before conversion to integer. +} + +bool BenchmarkRunner::ShouldReportIterationResults( + const IterationResults& i) const { + // Determine if this run should be reported; + // Either it has run for a sufficient amount of time + // or because an error was reported. + return i.results.has_error_ || + i.iters >= kMaxIterations || // Too many iterations already. + i.seconds >= min_time || // The elapsed time is large enough. + // CPU time is specified but the elapsed real time greatly exceeds + // the minimum time. + // Note that user provided timers are except from this sanity check. + ((i.results.real_time_used >= 5 * min_time) && !b.use_manual_time()); +} + +void BenchmarkRunner::DoOneRepetition() { + assert(HasRepeatsRemaining() && "Already done all repetitions?"); + + const bool is_the_first_repetition = num_repetitions_done == 0; + IterationResults i; + + // We *may* be gradually increasing the length (iteration count) + // of the benchmark until we decide the results are significant. + // And once we do, we report those last results and exit. + // Please do note that the if there are repetitions, the iteration count + // is *only* calculated for the *first* repetition, and other repetitions + // simply use that precomputed iteration count. + for (;;) { + b.Setup(); + i = DoNIterations(); + b.Teardown(); + + // Do we consider the results to be significant? + // If we are doing repetitions, and the first repetition was already done, + // it has calculated the correct iteration time, so we have run that very + // iteration count just now. No need to calculate anything. Just report. + // Else, the normal rules apply. + const bool results_are_significant = !is_the_first_repetition || + has_explicit_iteration_count || + ShouldReportIterationResults(i); + + if (results_are_significant) break; // Good, let's report them! + + // Nope, bad iteration. Let's re-estimate the hopefully-sufficient + // iteration count, and run the benchmark again... + + iters = PredictNumItersNeeded(i); + assert(iters > i.iters && + "if we did more iterations than we want to do the next time, " + "then we should have accepted the current iteration run."); + } + + // Oh, one last thing, we need to also produce the 'memory measurements'.. + MemoryManager::Result* memory_result = nullptr; + IterationCount memory_iterations = 0; + if (memory_manager != nullptr) { + // TODO(vyng): Consider making BenchmarkReporter::Run::memory_result an + // optional so we don't have to own the Result here. + // Can't do it now due to cxx03. + memory_results.push_back(MemoryManager::Result()); + memory_result = &memory_results.back(); + // Only run a few iterations to reduce the impact of one-time + // allocations in benchmarks that are not properly managed. + memory_iterations = std::min(16, iters); + memory_manager->Start(); + std::unique_ptr manager; + manager.reset(new internal::ThreadManager(1)); + b.Setup(); + RunInThread(&b, memory_iterations, 0, manager.get(), + perf_counters_measurement_ptr); + manager->WaitForAllThreads(); + manager.reset(); + b.Teardown(); + + BENCHMARK_DISABLE_DEPRECATED_WARNING + memory_manager->Stop(memory_result); + BENCHMARK_RESTORE_DEPRECATED_WARNING + } + + // Ok, now actually report. + BenchmarkReporter::Run report = + CreateRunReport(b, i.results, memory_iterations, memory_result, i.seconds, + num_repetitions_done, repeats); + + if (reports_for_family) { + ++reports_for_family->num_runs_done; + if (!report.error_occurred) reports_for_family->Runs.push_back(report); + } + + run_results.non_aggregates.push_back(report); + + ++num_repetitions_done; +} + +RunResults&& BenchmarkRunner::GetResults() { + assert(!HasRepeatsRemaining() && "Did not run all repetitions yet?"); + + // Calculate additional statistics over the repetitions of this instance. + run_results.aggregates_only = ComputeStats(run_results.non_aggregates); + + return std::move(run_results); +} + +} // end namespace internal + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/benchmark_runner.h b/bridge/third_party/benchmark/src/benchmark_runner.h new file mode 100644 index 0000000000..752eefdc26 --- /dev/null +++ b/bridge/third_party/benchmark/src/benchmark_runner.h @@ -0,0 +1,104 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_RUNNER_H_ +#define BENCHMARK_RUNNER_H_ + +#include +#include + +#include "benchmark_api_internal.h" +#include "internal_macros.h" +#include "perf_counters.h" +#include "thread_manager.h" + +namespace benchmark { + +BM_DECLARE_double(benchmark_min_time); +BM_DECLARE_int32(benchmark_repetitions); +BM_DECLARE_bool(benchmark_report_aggregates_only); +BM_DECLARE_bool(benchmark_display_aggregates_only); +BM_DECLARE_string(benchmark_perf_counters); + +namespace internal { + +extern MemoryManager* memory_manager; + +struct RunResults { + std::vector non_aggregates; + std::vector aggregates_only; + + bool display_report_aggregates_only = false; + bool file_report_aggregates_only = false; +}; + +class BenchmarkRunner { + public: + BenchmarkRunner(const benchmark::internal::BenchmarkInstance& b_, + BenchmarkReporter::PerFamilyRunReports* reports_for_family); + + int GetNumRepeats() const { return repeats; } + + bool HasRepeatsRemaining() const { + return GetNumRepeats() != num_repetitions_done; + } + + void DoOneRepetition(); + + RunResults&& GetResults(); + + BenchmarkReporter::PerFamilyRunReports* GetReportsForFamily() const { + return reports_for_family; + } + + private: + RunResults run_results; + + const benchmark::internal::BenchmarkInstance& b; + BenchmarkReporter::PerFamilyRunReports* reports_for_family; + + const double min_time; + const int repeats; + const bool has_explicit_iteration_count; + + int num_repetitions_done = 0; + + std::vector pool; + + std::vector memory_results; + + IterationCount iters; // preserved between repetitions! + // So only the first repetition has to find/calculate it, + // the other repetitions will just use that precomputed iteration count. + + PerfCountersMeasurement perf_counters_measurement; + PerfCountersMeasurement* const perf_counters_measurement_ptr; + + struct IterationResults { + internal::ThreadManager::Result results; + IterationCount iters; + double seconds; + }; + IterationResults DoNIterations(); + + IterationCount PredictNumItersNeeded(const IterationResults& i) const; + + bool ShouldReportIterationResults(const IterationResults& i) const; +}; + +} // namespace internal + +} // end namespace benchmark + +#endif // BENCHMARK_RUNNER_H_ diff --git a/bridge/third_party/benchmark/src/check.h b/bridge/third_party/benchmark/src/check.h new file mode 100644 index 0000000000..0efd13ff4d --- /dev/null +++ b/bridge/third_party/benchmark/src/check.h @@ -0,0 +1,83 @@ +#ifndef CHECK_H_ +#define CHECK_H_ + +#include +#include +#include + +#include "internal_macros.h" +#include "log.h" + +namespace benchmark { +namespace internal { + +typedef void(AbortHandlerT)(); + +inline AbortHandlerT*& GetAbortHandler() { + static AbortHandlerT* handler = &std::abort; + return handler; +} + +BENCHMARK_NORETURN inline void CallAbortHandler() { + GetAbortHandler()(); + std::abort(); // fallback to enforce noreturn +} + +// CheckHandler is the class constructed by failing BM_CHECK macros. +// CheckHandler will log information about the failures and abort when it is +// destructed. +class CheckHandler { + public: + CheckHandler(const char* check, const char* file, const char* func, int line) + : log_(GetErrorLogInstance()) { + log_ << file << ":" << line << ": " << func << ": Check `" << check + << "' failed. "; + } + + LogType& GetLog() { return log_; } + + BENCHMARK_NORETURN ~CheckHandler() BENCHMARK_NOEXCEPT_OP(false) { + log_ << std::endl; + CallAbortHandler(); + } + + CheckHandler& operator=(const CheckHandler&) = delete; + CheckHandler(const CheckHandler&) = delete; + CheckHandler() = delete; + + private: + LogType& log_; +}; + +} // end namespace internal +} // end namespace benchmark + +// The BM_CHECK macro returns a std::ostream object that can have extra +// information written to it. +#ifndef NDEBUG +#define BM_CHECK(b) \ + (b ? ::benchmark::internal::GetNullLogInstance() \ + : ::benchmark::internal::CheckHandler(#b, __FILE__, __func__, __LINE__) \ + .GetLog()) +#else +#define BM_CHECK(b) ::benchmark::internal::GetNullLogInstance() +#endif + +// clang-format off +// preserve whitespacing between operators for alignment +#define BM_CHECK_EQ(a, b) BM_CHECK((a) == (b)) +#define BM_CHECK_NE(a, b) BM_CHECK((a) != (b)) +#define BM_CHECK_GE(a, b) BM_CHECK((a) >= (b)) +#define BM_CHECK_LE(a, b) BM_CHECK((a) <= (b)) +#define BM_CHECK_GT(a, b) BM_CHECK((a) > (b)) +#define BM_CHECK_LT(a, b) BM_CHECK((a) < (b)) + +#define BM_CHECK_FLOAT_EQ(a, b, eps) BM_CHECK(std::fabs((a) - (b)) < (eps)) +#define BM_CHECK_FLOAT_NE(a, b, eps) BM_CHECK(std::fabs((a) - (b)) >= (eps)) +#define BM_CHECK_FLOAT_GE(a, b, eps) BM_CHECK((a) - (b) > -(eps)) +#define BM_CHECK_FLOAT_LE(a, b, eps) BM_CHECK((b) - (a) > -(eps)) +#define BM_CHECK_FLOAT_GT(a, b, eps) BM_CHECK((a) - (b) > (eps)) +#define BM_CHECK_FLOAT_LT(a, b, eps) BM_CHECK((b) - (a) > (eps)) +//clang-format on + +#endif // CHECK_H_ diff --git a/bridge/third_party/benchmark/src/colorprint.cc b/bridge/third_party/benchmark/src/colorprint.cc new file mode 100644 index 0000000000..1a000a0637 --- /dev/null +++ b/bridge/third_party/benchmark/src/colorprint.cc @@ -0,0 +1,188 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "colorprint.h" + +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#include +#else +#include +#endif // BENCHMARK_OS_WINDOWS + +namespace benchmark { +namespace { +#ifdef BENCHMARK_OS_WINDOWS +typedef WORD PlatformColorCode; +#else +typedef const char* PlatformColorCode; +#endif + +PlatformColorCode GetPlatformColorCode(LogColor color) { +#ifdef BENCHMARK_OS_WINDOWS + switch (color) { + case COLOR_RED: + return FOREGROUND_RED; + case COLOR_GREEN: + return FOREGROUND_GREEN; + case COLOR_YELLOW: + return FOREGROUND_RED | FOREGROUND_GREEN; + case COLOR_BLUE: + return FOREGROUND_BLUE; + case COLOR_MAGENTA: + return FOREGROUND_BLUE | FOREGROUND_RED; + case COLOR_CYAN: + return FOREGROUND_BLUE | FOREGROUND_GREEN; + case COLOR_WHITE: // fall through to default + default: + return 0; + } +#else + switch (color) { + case COLOR_RED: + return "1"; + case COLOR_GREEN: + return "2"; + case COLOR_YELLOW: + return "3"; + case COLOR_BLUE: + return "4"; + case COLOR_MAGENTA: + return "5"; + case COLOR_CYAN: + return "6"; + case COLOR_WHITE: + return "7"; + default: + return nullptr; + }; +#endif +} + +} // end namespace + +std::string FormatString(const char* msg, va_list args) { + // we might need a second shot at this, so pre-emptivly make a copy + va_list args_cp; + va_copy(args_cp, args); + + std::size_t size = 256; + char local_buff[256]; + auto ret = vsnprintf(local_buff, size, msg, args_cp); + + va_end(args_cp); + + // currently there is no error handling for failure, so this is hack. + BM_CHECK(ret >= 0); + + if (ret == 0) // handle empty expansion + return {}; + else if (static_cast(ret) < size) + return local_buff; + else { + // we did not provide a long enough buffer on our first attempt. + size = static_cast(ret) + 1; // + 1 for the null byte + std::unique_ptr buff(new char[size]); + ret = vsnprintf(buff.get(), size, msg, args); + BM_CHECK(ret > 0 && (static_cast(ret)) < size); + return buff.get(); + } +} + +std::string FormatString(const char* msg, ...) { + va_list args; + va_start(args, msg); + auto tmp = FormatString(msg, args); + va_end(args); + return tmp; +} + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ColorPrintf(out, color, fmt, args); + va_end(args); +} + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, + va_list args) { +#ifdef BENCHMARK_OS_WINDOWS + ((void)out); // suppress unused warning + + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, + GetPlatformColorCode(color) | FOREGROUND_INTENSITY); + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + const char* color_code = GetPlatformColorCode(color); + if (color_code) out << FormatString("\033[0;3%sm", color_code); + out << FormatString(fmt, args) << "\033[m"; +#endif +} + +bool IsColorTerminal() { +#if BENCHMARK_OS_WINDOWS + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return 0 != _isatty(_fileno(stdout)); +#else + // On non-Windows platforms, we rely on the TERM variable. This list of + // supported TERM values is copied from Google Test: + // . + const char* const SUPPORTED_TERM_VALUES[] = { + "xterm", "xterm-color", "xterm-256color", + "screen", "screen-256color", "tmux", + "tmux-256color", "rxvt-unicode", "rxvt-unicode-256color", + "linux", "cygwin", + }; + + const char* const term = getenv("TERM"); + + bool term_supports_color = false; + for (const char* candidate : SUPPORTED_TERM_VALUES) { + if (term && 0 == strcmp(term, candidate)) { + term_supports_color = true; + break; + } + } + + return 0 != isatty(fileno(stdout)) && term_supports_color; +#endif // BENCHMARK_OS_WINDOWS +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/colorprint.h b/bridge/third_party/benchmark/src/colorprint.h new file mode 100644 index 0000000000..9f6fab9b34 --- /dev/null +++ b/bridge/third_party/benchmark/src/colorprint.h @@ -0,0 +1,33 @@ +#ifndef BENCHMARK_COLORPRINT_H_ +#define BENCHMARK_COLORPRINT_H_ + +#include +#include +#include + +namespace benchmark { +enum LogColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW, + COLOR_BLUE, + COLOR_MAGENTA, + COLOR_CYAN, + COLOR_WHITE +}; + +std::string FormatString(const char* msg, va_list args); +std::string FormatString(const char* msg, ...); + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, + va_list args); +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...); + +// Returns true if stdout appears to be a terminal that supports colored +// output, false otherwise. +bool IsColorTerminal(); + +} // end namespace benchmark + +#endif // BENCHMARK_COLORPRINT_H_ diff --git a/bridge/third_party/benchmark/src/commandlineflags.cc b/bridge/third_party/benchmark/src/commandlineflags.cc new file mode 100644 index 0000000000..9615e351ff --- /dev/null +++ b/bridge/third_party/benchmark/src/commandlineflags.cc @@ -0,0 +1,285 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "commandlineflags.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/string_util.h" + +namespace benchmark { +namespace { + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const std::string& src_text, const char* str, int32_t* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + std::cerr << src_text << " is expected to be a 32-bit integer, " + << "but actually has value \"" << str << "\".\n"; + return false; + } + + // Is the parsed value in the range of an Int32? + const int32_t result = static_cast(long_value); + if (long_value == std::numeric_limits::max() || + long_value == std::numeric_limits::min() || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an Int32. + ) { + std::cerr << src_text << " is expected to be a 32-bit integer, " + << "but actually has value \"" << str << "\", " + << "which overflows.\n"; + return false; + } + + *value = result; + return true; +} + +// Parses 'str' for a double. If successful, writes the result to *value and +// returns true; otherwise leaves *value unchanged and returns false. +bool ParseDouble(const std::string& src_text, const char* str, double* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const double double_value = strtod(str, &end); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + std::cerr << src_text << " is expected to be a double, " + << "but actually has value \"" << str << "\".\n"; + return false; + } + + *value = double_value; + return true; +} + +// Parses 'str' into KV pairs. If successful, writes the result to *value and +// returns true; otherwise leaves *value unchanged and returns false. +bool ParseKvPairs(const std::string& src_text, const char* str, + std::map* value) { + std::map kvs; + for (const auto& kvpair : StrSplit(str, ',')) { + const auto kv = StrSplit(kvpair, '='); + if (kv.size() != 2) { + std::cerr << src_text << " is expected to be a comma-separated list of " + << "= strings, but actually has value \"" << str + << "\".\n"; + return false; + } + if (!kvs.emplace(kv[0], kv[1]).second) { + std::cerr << src_text << " is expected to contain unique keys but key \"" + << kv[0] << "\" was repeated.\n"; + return false; + } + } + + *value = kvs; + return true; +} + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "BENCHMARK_FOO" in the open-source version. +static std::string FlagToEnvVar(const char* flag) { + const std::string flag_str(flag); + + std::string env_var; + for (size_t i = 0; i != flag_str.length(); ++i) + env_var += static_cast(::toupper(flag_str.c_str()[i])); + + return env_var; +} + +} // namespace + +bool BoolFromEnv(const char* flag, bool default_val) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value_str = getenv(env_var.c_str()); + return value_str == nullptr ? default_val : IsTruthyFlagValue(value_str); +} + +int32_t Int32FromEnv(const char* flag, int32_t default_val) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value_str = getenv(env_var.c_str()); + int32_t value = default_val; + if (value_str == nullptr || + !ParseInt32(std::string("Environment variable ") + env_var, value_str, + &value)) { + return default_val; + } + return value; +} + +double DoubleFromEnv(const char* flag, double default_val) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value_str = getenv(env_var.c_str()); + double value = default_val; + if (value_str == nullptr || + !ParseDouble(std::string("Environment variable ") + env_var, value_str, + &value)) { + return default_val; + } + return value; +} + +const char* StringFromEnv(const char* flag, const char* default_val) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value = getenv(env_var.c_str()); + return value == nullptr ? default_val : value; +} + +std::map KvPairsFromEnv( + const char* flag, std::map default_val) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value_str = getenv(env_var.c_str()); + + if (value_str == nullptr) return default_val; + + std::map value; + if (!ParseKvPairs("Environment variable " + env_var, value_str, &value)) { + return default_val; + } + return value; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or nullptr if the parsing failed. +const char* ParseFlagValue(const char* str, const char* flag, + bool def_optional) { + // str and flag must not be nullptr. + if (str == nullptr || flag == nullptr) return nullptr; + + // The flag must start with "--". + const std::string flag_str = std::string("--") + std::string(flag); + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) return flag_end; + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return nullptr; + + // Returns the string after "=". + return flag_end + 1; +} + +bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Converts the string value to a bool. + *value = IsTruthyFlagValue(value_str); + return true; +} + +bool ParseInt32Flag(const char* str, const char* flag, int32_t* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseInt32(std::string("The value of flag --") + flag, value_str, + value); +} + +bool ParseDoubleFlag(const char* str, const char* flag, double* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseDouble(std::string("The value of flag --") + flag, value_str, + value); +} + +bool ParseStringFlag(const char* str, const char* flag, std::string* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + *value = value_str; + return true; +} + +bool ParseKeyValueFlag(const char* str, const char* flag, + std::map* value) { + const char* const value_str = ParseFlagValue(str, flag, false); + + if (value_str == nullptr) return false; + + for (const auto& kvpair : StrSplit(value_str, ',')) { + const auto kv = StrSplit(kvpair, '='); + if (kv.size() != 2) return false; + value->emplace(kv[0], kv[1]); + } + + return true; +} + +bool IsFlag(const char* str, const char* flag) { + return (ParseFlagValue(str, flag, true) != nullptr); +} + +bool IsTruthyFlagValue(const std::string& value) { + if (value.size() == 1) { + char v = value[0]; + return isalnum(v) && + !(v == '0' || v == 'f' || v == 'F' || v == 'n' || v == 'N'); + } else if (!value.empty()) { + std::string value_lower(value); + std::transform(value_lower.begin(), value_lower.end(), value_lower.begin(), + [](char c) { return static_cast(::tolower(c)); }); + return !(value_lower == "false" || value_lower == "no" || + value_lower == "off"); + } else + return true; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/commandlineflags.h b/bridge/third_party/benchmark/src/commandlineflags.h new file mode 100644 index 0000000000..5baaf11784 --- /dev/null +++ b/bridge/third_party/benchmark/src/commandlineflags.h @@ -0,0 +1,116 @@ +#ifndef BENCHMARK_COMMANDLINEFLAGS_H_ +#define BENCHMARK_COMMANDLINEFLAGS_H_ + +#include +#include +#include + +// Macro for referencing flags. +#define FLAG(name) FLAGS_##name + +// Macros for declaring flags. +#define BM_DECLARE_bool(name) extern bool FLAG(name) +#define BM_DECLARE_int32(name) extern int32_t FLAG(name) +#define BM_DECLARE_double(name) extern double FLAG(name) +#define BM_DECLARE_string(name) extern std::string FLAG(name) +#define BM_DECLARE_kvpairs(name) \ + extern std::map FLAG(name) + +// Macros for defining flags. +#define BM_DEFINE_bool(name, default_val) \ + bool FLAG(name) = benchmark::BoolFromEnv(#name, default_val) +#define BM_DEFINE_int32(name, default_val) \ + int32_t FLAG(name) = benchmark::Int32FromEnv(#name, default_val) +#define BM_DEFINE_double(name, default_val) \ + double FLAG(name) = benchmark::DoubleFromEnv(#name, default_val) +#define BM_DEFINE_string(name, default_val) \ + std::string FLAG(name) = benchmark::StringFromEnv(#name, default_val) +#define BM_DEFINE_kvpairs(name, default_val) \ + std::map FLAG(name) = \ + benchmark::KvPairsFromEnv(#name, default_val) + +namespace benchmark { + +// Parses a bool from the environment variable corresponding to the given flag. +// +// If the variable exists, returns IsTruthyFlagValue() value; if not, +// returns the given default value. +bool BoolFromEnv(const char* flag, bool default_val); + +// Parses an Int32 from the environment variable corresponding to the given +// flag. +// +// If the variable exists, returns ParseInt32() value; if not, returns +// the given default value. +int32_t Int32FromEnv(const char* flag, int32_t default_val); + +// Parses an Double from the environment variable corresponding to the given +// flag. +// +// If the variable exists, returns ParseDouble(); if not, returns +// the given default value. +double DoubleFromEnv(const char* flag, double default_val); + +// Parses a string from the environment variable corresponding to the given +// flag. +// +// If variable exists, returns its value; if not, returns +// the given default value. +const char* StringFromEnv(const char* flag, const char* default_val); + +// Parses a set of kvpairs from the environment variable corresponding to the +// given flag. +// +// If variable exists, returns its value; if not, returns +// the given default value. +std::map KvPairsFromEnv( + const char* flag, std::map default_val); + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true if it passes IsTruthyValue(). +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseBoolFlag(const char* str, const char* flag, bool* value); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, int32_t* value); + +// Parses a string for a Double flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseDoubleFlag(const char* str, const char* flag, double* value); + +// Parses a string for a string flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseStringFlag(const char* str, const char* flag, std::string* value); + +// Parses a string for a kvpairs flag in the form "--flag=key=value,key=value" +// +// On success, stores the value of the flag in *value and returns true. On +// failure returns false, though *value may have been mutated. +bool ParseKeyValueFlag(const char* str, const char* flag, + std::map* value); + +// Returns true if the string matches the flag. +bool IsFlag(const char* str, const char* flag); + +// Returns true unless value starts with one of: '0', 'f', 'F', 'n' or 'N', or +// some non-alphanumeric character. Also returns false if the value matches +// one of 'no', 'false', 'off' (case-insensitive). As a special case, also +// returns true if value is the empty string. +bool IsTruthyFlagValue(const std::string& value); + +} // end namespace benchmark + +#endif // BENCHMARK_COMMANDLINEFLAGS_H_ diff --git a/bridge/third_party/benchmark/src/complexity.cc b/bridge/third_party/benchmark/src/complexity.cc new file mode 100644 index 0000000000..825c57394a --- /dev/null +++ b/bridge/third_party/benchmark/src/complexity.cc @@ -0,0 +1,244 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Source project : https://github.com/ismaelJimenez/cpp.leastsq +// Adapted to be used with google benchmark + +#include "complexity.h" + +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" + +namespace benchmark { + +// Internal function to calculate the different scalability forms +BigOFunc* FittingCurve(BigO complexity) { + static const double kLog2E = 1.44269504088896340736; + switch (complexity) { + case oN: + return [](IterationCount n) -> double { return static_cast(n); }; + case oNSquared: + return [](IterationCount n) -> double { return std::pow(n, 2); }; + case oNCubed: + return [](IterationCount n) -> double { return std::pow(n, 3); }; + case oLogN: + /* Note: can't use log2 because Android's GNU STL lacks it */ + return + [](IterationCount n) { return kLog2E * log(static_cast(n)); }; + case oNLogN: + /* Note: can't use log2 because Android's GNU STL lacks it */ + return [](IterationCount n) { + return kLog2E * n * log(static_cast(n)); + }; + case o1: + default: + return [](IterationCount) { return 1.0; }; + } +} + +// Function to return an string for the calculated complexity +std::string GetBigOString(BigO complexity) { + switch (complexity) { + case oN: + return "N"; + case oNSquared: + return "N^2"; + case oNCubed: + return "N^3"; + case oLogN: + return "lgN"; + case oNLogN: + return "NlgN"; + case o1: + return "(1)"; + default: + return "f(N)"; + } +} + +// Find the coefficient for the high-order term in the running time, by +// minimizing the sum of squares of relative error, for the fitting curve +// given by the lambda expression. +// - n : Vector containing the size of the benchmark tests. +// - time : Vector containing the times for the benchmark tests. +// - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };). + +// For a deeper explanation on the algorithm logic, please refer to +// https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics + +LeastSq MinimalLeastSq(const std::vector& n, + const std::vector& time, + BigOFunc* fitting_curve) { + double sigma_gn_squared = 0.0; + double sigma_time = 0.0; + double sigma_time_gn = 0.0; + + // Calculate least square fitting parameter + for (size_t i = 0; i < n.size(); ++i) { + double gn_i = fitting_curve(n[i]); + sigma_gn_squared += gn_i * gn_i; + sigma_time += time[i]; + sigma_time_gn += time[i] * gn_i; + } + + LeastSq result; + result.complexity = oLambda; + + // Calculate complexity. + result.coef = sigma_time_gn / sigma_gn_squared; + + // Calculate RMS + double rms = 0.0; + for (size_t i = 0; i < n.size(); ++i) { + double fit = result.coef * fitting_curve(n[i]); + rms += pow((time[i] - fit), 2); + } + + // Normalized RMS by the mean of the observed values + double mean = sigma_time / n.size(); + result.rms = sqrt(rms / n.size()) / mean; + + return result; +} + +// Find the coefficient for the high-order term in the running time, by +// minimizing the sum of squares of relative error. +// - n : Vector containing the size of the benchmark tests. +// - time : Vector containing the times for the benchmark tests. +// - complexity : If different than oAuto, the fitting curve will stick to +// this one. If it is oAuto, it will be calculated the best +// fitting curve. +LeastSq MinimalLeastSq(const std::vector& n, + const std::vector& time, const BigO complexity) { + BM_CHECK_EQ(n.size(), time.size()); + BM_CHECK_GE(n.size(), 2); // Do not compute fitting curve is less than two + // benchmark runs are given + BM_CHECK_NE(complexity, oNone); + + LeastSq best_fit; + + if (complexity == oAuto) { + std::vector fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed}; + + // Take o1 as default best fitting curve + best_fit = MinimalLeastSq(n, time, FittingCurve(o1)); + best_fit.complexity = o1; + + // Compute all possible fitting curves and stick to the best one + for (const auto& fit : fit_curves) { + LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit)); + if (current_fit.rms < best_fit.rms) { + best_fit = current_fit; + best_fit.complexity = fit; + } + } + } else { + best_fit = MinimalLeastSq(n, time, FittingCurve(complexity)); + best_fit.complexity = complexity; + } + + return best_fit; +} + +std::vector ComputeBigO( + const std::vector& reports) { + typedef BenchmarkReporter::Run Run; + std::vector results; + + if (reports.size() < 2) return results; + + // Accumulators. + std::vector n; + std::vector real_time; + std::vector cpu_time; + + // Populate the accumulators. + for (const Run& run : reports) { + BM_CHECK_GT(run.complexity_n, 0) + << "Did you forget to call SetComplexityN?"; + n.push_back(run.complexity_n); + real_time.push_back(run.real_accumulated_time / run.iterations); + cpu_time.push_back(run.cpu_accumulated_time / run.iterations); + } + + LeastSq result_cpu; + LeastSq result_real; + + if (reports[0].complexity == oLambda) { + result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda); + result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda); + } else { + result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity); + result_real = MinimalLeastSq(n, real_time, result_cpu.complexity); + } + + // Drop the 'args' when reporting complexity. + auto run_name = reports[0].run_name; + run_name.args.clear(); + + // Get the data from the accumulator to BenchmarkReporter::Run's. + Run big_o; + big_o.run_name = run_name; + big_o.family_index = reports[0].family_index; + big_o.per_family_instance_index = reports[0].per_family_instance_index; + big_o.run_type = BenchmarkReporter::Run::RT_Aggregate; + big_o.repetitions = reports[0].repetitions; + big_o.repetition_index = Run::no_repetition_index; + big_o.threads = reports[0].threads; + big_o.aggregate_name = "BigO"; + big_o.aggregate_unit = StatisticUnit::kTime; + big_o.report_label = reports[0].report_label; + big_o.iterations = 0; + big_o.real_accumulated_time = result_real.coef; + big_o.cpu_accumulated_time = result_cpu.coef; + big_o.report_big_o = true; + big_o.complexity = result_cpu.complexity; + + // All the time results are reported after being multiplied by the + // time unit multiplier. But since RMS is a relative quantity it + // should not be multiplied at all. So, here, we _divide_ it by the + // multiplier so that when it is multiplied later the result is the + // correct one. + double multiplier = GetTimeUnitMultiplier(reports[0].time_unit); + + // Only add label to mean/stddev if it is same for all runs + Run rms; + rms.run_name = run_name; + rms.family_index = reports[0].family_index; + rms.per_family_instance_index = reports[0].per_family_instance_index; + rms.run_type = BenchmarkReporter::Run::RT_Aggregate; + rms.aggregate_name = "RMS"; + rms.aggregate_unit = StatisticUnit::kPercentage; + rms.report_label = big_o.report_label; + rms.iterations = 0; + rms.repetition_index = Run::no_repetition_index; + rms.repetitions = reports[0].repetitions; + rms.threads = reports[0].threads; + rms.real_accumulated_time = result_real.rms / multiplier; + rms.cpu_accumulated_time = result_cpu.rms / multiplier; + rms.report_rms = true; + rms.complexity = result_cpu.complexity; + // don't forget to keep the time unit, or we won't be able to + // recover the correct value. + rms.time_unit = reports[0].time_unit; + + results.push_back(big_o); + results.push_back(rms); + return results; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/complexity.h b/bridge/third_party/benchmark/src/complexity.h new file mode 100644 index 0000000000..df29b48d29 --- /dev/null +++ b/bridge/third_party/benchmark/src/complexity.h @@ -0,0 +1,55 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Source project : https://github.com/ismaelJimenez/cpp.leastsq +// Adapted to be used with google benchmark + +#ifndef COMPLEXITY_H_ +#define COMPLEXITY_H_ + +#include +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// Return a vector containing the bigO and RMS information for the specified +// list of reports. If 'reports.size() < 2' an empty vector is returned. +std::vector ComputeBigO( + const std::vector& reports); + +// This data structure will contain the result returned by MinimalLeastSq +// - coef : Estimated coeficient for the high-order term as +// interpolated from data. +// - rms : Normalized Root Mean Squared Error. +// - complexity : Scalability form (e.g. oN, oNLogN). In case a scalability +// form has been provided to MinimalLeastSq this will return +// the same value. In case BigO::oAuto has been selected, this +// parameter will return the best fitting curve detected. + +struct LeastSq { + LeastSq() : coef(0.0), rms(0.0), complexity(oNone) {} + + double coef; + double rms; + BigO complexity; +}; + +// Function to return an string for the calculated complexity +std::string GetBigOString(BigO complexity); + +} // end namespace benchmark + +#endif // COMPLEXITY_H_ diff --git a/bridge/third_party/benchmark/src/console_reporter.cc b/bridge/third_party/benchmark/src/console_reporter.cc new file mode 100644 index 0000000000..04cc0b74e5 --- /dev/null +++ b/bridge/third_party/benchmark/src/console_reporter.cc @@ -0,0 +1,190 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "counter.h" +#include "internal_macros.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { + +bool ConsoleReporter::ReportContext(const Context& context) { + name_field_width_ = context.name_field_width; + printed_header_ = false; + prev_counters_.clear(); + + PrintBasicContext(&GetErrorStream(), context); + +#ifdef BENCHMARK_OS_WINDOWS + if ((output_options_ & OO_Color) && &std::cout != &GetOutputStream()) { + GetErrorStream() + << "Color printing is only supported for stdout on windows." + " Disabling color printing\n"; + output_options_ = static_cast(output_options_ & ~OO_Color); + } +#endif + + return true; +} + +void ConsoleReporter::PrintHeader(const Run& run) { + std::string str = + FormatString("%-*s %13s %15s %12s", static_cast(name_field_width_), + "Benchmark", "Time", "CPU", "Iterations"); + if (!run.counters.empty()) { + if (output_options_ & OO_Tabular) { + for (auto const& c : run.counters) { + str += FormatString(" %10s", c.first.c_str()); + } + } else { + str += " UserCounters..."; + } + } + std::string line = std::string(str.length(), '-'); + GetOutputStream() << line << "\n" << str << "\n" << line << "\n"; +} + +void ConsoleReporter::ReportRuns(const std::vector& reports) { + for (const auto& run : reports) { + // print the header: + // --- if none was printed yet + bool print_header = !printed_header_; + // --- or if the format is tabular and this run + // has different fields from the prev header + print_header |= (output_options_ & OO_Tabular) && + (!internal::SameNames(run.counters, prev_counters_)); + if (print_header) { + printed_header_ = true; + prev_counters_ = run.counters; + PrintHeader(run); + } + // As an alternative to printing the headers like this, we could sort + // the benchmarks by header and then print. But this would require + // waiting for the full results before printing, or printing twice. + PrintRunData(run); + } +} + +static void IgnoreColorPrint(std::ostream& out, LogColor, const char* fmt, + ...) { + va_list args; + va_start(args, fmt); + out << FormatString(fmt, args); + va_end(args); +} + +static std::string FormatTime(double time) { + // Align decimal places... + if (time < 1.0) { + return FormatString("%10.3f", time); + } + if (time < 10.0) { + return FormatString("%10.2f", time); + } + if (time < 100.0) { + return FormatString("%10.1f", time); + } + return FormatString("%10.0f", time); +} + +void ConsoleReporter::PrintRunData(const Run& result) { + typedef void(PrinterFn)(std::ostream&, LogColor, const char*, ...); + auto& Out = GetOutputStream(); + PrinterFn* printer = (output_options_ & OO_Color) + ? static_cast(ColorPrintf) + : IgnoreColorPrint; + auto name_color = + (result.report_big_o || result.report_rms) ? COLOR_BLUE : COLOR_GREEN; + printer(Out, name_color, "%-*s ", name_field_width_, + result.benchmark_name().c_str()); + + if (result.error_occurred) { + printer(Out, COLOR_RED, "ERROR OCCURRED: \'%s\'", + result.error_message.c_str()); + printer(Out, COLOR_DEFAULT, "\n"); + return; + } + + const double real_time = result.GetAdjustedRealTime(); + const double cpu_time = result.GetAdjustedCPUTime(); + const std::string real_time_str = FormatTime(real_time); + const std::string cpu_time_str = FormatTime(cpu_time); + + if (result.report_big_o) { + std::string big_o = GetBigOString(result.complexity); + printer(Out, COLOR_YELLOW, "%10.2f %-4s %10.2f %-4s ", real_time, + big_o.c_str(), cpu_time, big_o.c_str()); + } else if (result.report_rms) { + printer(Out, COLOR_YELLOW, "%10.0f %-4s %10.0f %-4s ", real_time * 100, "%", + cpu_time * 100, "%"); + } else if (result.run_type != Run::RT_Aggregate || + result.aggregate_unit == StatisticUnit::kTime) { + const char* timeLabel = GetTimeUnitString(result.time_unit); + printer(Out, COLOR_YELLOW, "%s %-4s %s %-4s ", real_time_str.c_str(), + timeLabel, cpu_time_str.c_str(), timeLabel); + } else { + assert(result.aggregate_unit == StatisticUnit::kPercentage); + printer(Out, COLOR_YELLOW, "%10.2f %-4s %10.2f %-4s ", + (100. * result.real_accumulated_time), "%", + (100. * result.cpu_accumulated_time), "%"); + } + + if (!result.report_big_o && !result.report_rms) { + printer(Out, COLOR_CYAN, "%10lld", result.iterations); + } + + for (auto& c : result.counters) { + const std::size_t cNameLen = + std::max(std::string::size_type(10), c.first.length()); + std::string s; + const char* unit = ""; + if (result.run_type == Run::RT_Aggregate && + result.aggregate_unit == StatisticUnit::kPercentage) { + s = StrFormat("%.2f", 100. * c.second.value); + unit = "%"; + } else { + s = HumanReadableNumber(c.second.value, c.second.oneK); + if (c.second.flags & Counter::kIsRate) + unit = (c.second.flags & Counter::kInvert) ? "s" : "/s"; + } + if (output_options_ & OO_Tabular) { + printer(Out, COLOR_DEFAULT, " %*s%s", cNameLen - strlen(unit), s.c_str(), + unit); + } else { + printer(Out, COLOR_DEFAULT, " %s=%s%s", c.first.c_str(), s.c_str(), unit); + } + } + + if (!result.report_label.empty()) { + printer(Out, COLOR_DEFAULT, " %s", result.report_label.c_str()); + } + + printer(Out, COLOR_DEFAULT, "\n"); +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/counter.cc b/bridge/third_party/benchmark/src/counter.cc new file mode 100644 index 0000000000..cf5b78ee3a --- /dev/null +++ b/bridge/third_party/benchmark/src/counter.cc @@ -0,0 +1,80 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "counter.h" + +namespace benchmark { +namespace internal { + +double Finish(Counter const& c, IterationCount iterations, double cpu_time, + double num_threads) { + double v = c.value; + if (c.flags & Counter::kIsRate) { + v /= cpu_time; + } + if (c.flags & Counter::kAvgThreads) { + v /= num_threads; + } + if (c.flags & Counter::kIsIterationInvariant) { + v *= iterations; + } + if (c.flags & Counter::kAvgIterations) { + v /= iterations; + } + + if (c.flags & Counter::kInvert) { // Invert is *always* last. + v = 1.0 / v; + } + return v; +} + +void Finish(UserCounters* l, IterationCount iterations, double cpu_time, + double num_threads) { + for (auto& c : *l) { + c.second.value = Finish(c.second, iterations, cpu_time, num_threads); + } +} + +void Increment(UserCounters* l, UserCounters const& r) { + // add counters present in both or just in *l + for (auto& c : *l) { + auto it = r.find(c.first); + if (it != r.end()) { + c.second.value = c.second + it->second; + } + } + // add counters present in r, but not in *l + for (auto const& tc : r) { + auto it = l->find(tc.first); + if (it == l->end()) { + (*l)[tc.first] = tc.second; + } + } +} + +bool SameNames(UserCounters const& l, UserCounters const& r) { + if (&l == &r) return true; + if (l.size() != r.size()) { + return false; + } + for (auto const& c : l) { + if (r.find(c.first) == r.end()) { + return false; + } + } + return true; +} + +} // end namespace internal +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/counter.h b/bridge/third_party/benchmark/src/counter.h new file mode 100644 index 0000000000..1f5a58e31f --- /dev/null +++ b/bridge/third_party/benchmark/src/counter.h @@ -0,0 +1,32 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_COUNTER_H_ +#define BENCHMARK_COUNTER_H_ + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// these counter-related functions are hidden to reduce API surface. +namespace internal { +void Finish(UserCounters* l, IterationCount iterations, double time, + double num_threads); +void Increment(UserCounters* l, UserCounters const& r); +bool SameNames(UserCounters const& l, UserCounters const& r); +} // end namespace internal + +} // end namespace benchmark + +#endif // BENCHMARK_COUNTER_H_ diff --git a/bridge/third_party/benchmark/src/csv_reporter.cc b/bridge/third_party/benchmark/src/csv_reporter.cc new file mode 100644 index 0000000000..1c5e9fa668 --- /dev/null +++ b/bridge/third_party/benchmark/src/csv_reporter.cc @@ -0,0 +1,158 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" +#include "complexity.h" +#include "string_util.h" +#include "timers.h" + +// File format reference: http://edoceo.com/utilitas/csv-file-format. + +namespace benchmark { + +namespace { +std::vector elements = { + "name", "iterations", "real_time", "cpu_time", + "time_unit", "bytes_per_second", "items_per_second", "label", + "error_occurred", "error_message"}; +} // namespace + +std::string CsvEscape(const std::string& s) { + std::string tmp; + tmp.reserve(s.size() + 2); + for (char c : s) { + switch (c) { + case '"': + tmp += "\"\""; + break; + default: + tmp += c; + break; + } + } + return '"' + tmp + '"'; +} + +bool CSVReporter::ReportContext(const Context& context) { + PrintBasicContext(&GetErrorStream(), context); + return true; +} + +void CSVReporter::ReportRuns(const std::vector& reports) { + std::ostream& Out = GetOutputStream(); + + if (!printed_header_) { + // save the names of all the user counters + for (const auto& run : reports) { + for (const auto& cnt : run.counters) { + if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second") + continue; + user_counter_names_.insert(cnt.first); + } + } + + // print the header + for (auto B = elements.begin(); B != elements.end();) { + Out << *B++; + if (B != elements.end()) Out << ","; + } + for (auto B = user_counter_names_.begin(); + B != user_counter_names_.end();) { + Out << ",\"" << *B++ << "\""; + } + Out << "\n"; + + printed_header_ = true; + } else { + // check that all the current counters are saved in the name set + for (const auto& run : reports) { + for (const auto& cnt : run.counters) { + if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second") + continue; + BM_CHECK(user_counter_names_.find(cnt.first) != + user_counter_names_.end()) + << "All counters must be present in each run. " + << "Counter named \"" << cnt.first + << "\" was not in a run after being added to the header"; + } + } + } + + // print results for each run + for (const auto& run : reports) { + PrintRunData(run); + } +} + +void CSVReporter::PrintRunData(const Run& run) { + std::ostream& Out = GetOutputStream(); + Out << CsvEscape(run.benchmark_name()) << ","; + if (run.error_occurred) { + Out << std::string(elements.size() - 3, ','); + Out << "true,"; + Out << CsvEscape(run.error_message) << "\n"; + return; + } + + // Do not print iteration on bigO and RMS report + if (!run.report_big_o && !run.report_rms) { + Out << run.iterations; + } + Out << ","; + + Out << run.GetAdjustedRealTime() << ","; + Out << run.GetAdjustedCPUTime() << ","; + + // Do not print timeLabel on bigO and RMS report + if (run.report_big_o) { + Out << GetBigOString(run.complexity); + } else if (!run.report_rms) { + Out << GetTimeUnitString(run.time_unit); + } + Out << ","; + + if (run.counters.find("bytes_per_second") != run.counters.end()) { + Out << run.counters.at("bytes_per_second"); + } + Out << ","; + if (run.counters.find("items_per_second") != run.counters.end()) { + Out << run.counters.at("items_per_second"); + } + Out << ","; + if (!run.report_label.empty()) { + Out << CsvEscape(run.report_label); + } + Out << ",,"; // for error_occurred and error_message + + // Print user counters + for (const auto& ucn : user_counter_names_) { + auto it = run.counters.find(ucn); + if (it == run.counters.end()) { + Out << ","; + } else { + Out << "," << it->second; + } + } + Out << '\n'; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/cycleclock.h b/bridge/third_party/benchmark/src/cycleclock.h new file mode 100644 index 0000000000..d65d32a39d --- /dev/null +++ b/bridge/third_party/benchmark/src/cycleclock.h @@ -0,0 +1,225 @@ +// ---------------------------------------------------------------------- +// CycleClock +// A CycleClock tells you the current time in Cycles. The "time" +// is actually time since power-on. This is like time() but doesn't +// involve a system call and is much more precise. +// +// NOTE: Not all cpu/platform/kernel combinations guarantee that this +// clock increments at a constant rate or is synchronized across all logical +// cpus in a system. +// +// If you need the above guarantees, please consider using a different +// API. There are efforts to provide an interface which provides a millisecond +// granularity and implemented as a memory read. A memory read is generally +// cheaper than the CycleClock for many architectures. +// +// Also, in some out of order CPU implementations, the CycleClock is not +// serializing. So if you're trying to count at cycles granularity, your +// data might be inaccurate due to out of order instruction execution. +// ---------------------------------------------------------------------- + +#ifndef BENCHMARK_CYCLECLOCK_H_ +#define BENCHMARK_CYCLECLOCK_H_ + +#include + +#include "benchmark/benchmark.h" +#include "internal_macros.h" + +#if defined(BENCHMARK_OS_MACOSX) +#include +#endif +// For MSVC, we want to use '_asm rdtsc' when possible (since it works +// with even ancient MSVC compilers), and when not possible the +// __rdtsc intrinsic, declared in . Unfortunately, in some +// environments, and have conflicting +// declarations of some other intrinsics, breaking compilation. +// Therefore, we simply declare __rdtsc ourselves. See also +// http://connect.microsoft.com/VisualStudio/feedback/details/262047 +#if defined(COMPILER_MSVC) && !defined(_M_IX86) && !defined(_M_ARM64) +extern "C" uint64_t __rdtsc(); +#pragma intrinsic(__rdtsc) +#endif + +#if !defined(BENCHMARK_OS_WINDOWS) || defined(BENCHMARK_OS_MINGW) +#include +#include +#endif + +#ifdef BENCHMARK_OS_EMSCRIPTEN +#include +#endif + +namespace benchmark { +// NOTE: only i386 and x86_64 have been well tested. +// PPC, sparc, alpha, and ia64 are based on +// http://peter.kuscsik.com/wordpress/?p=14 +// with modifications by m3b. See also +// https://setisvn.ssl.berkeley.edu/svn/lib/fftw-3.0.1/kernel/cycle.h +namespace cycleclock { +// This should return the number of cycles since power-on. Thread-safe. +inline BENCHMARK_ALWAYS_INLINE int64_t Now() { +#if defined(BENCHMARK_OS_MACOSX) + // this goes at the top because we need ALL Macs, regardless of + // architecture, to return the number of "mach time units" that + // have passed since startup. See sysinfo.cc where + // InitializeSystemInfo() sets the supposed cpu clock frequency of + // macs to the number of mach time units per second, not actual + // CPU clock frequency (which can change in the face of CPU + // frequency scaling). Also note that when the Mac sleeps, this + // counter pauses; it does not continue counting, nor does it + // reset to zero. + return mach_absolute_time(); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // this goes above x86-specific code because old versions of Emscripten + // define __x86_64__, although they have nothing to do with it. + return static_cast(emscripten_get_now() * 1e+6); +#elif defined(__i386__) + int64_t ret; + __asm__ volatile("rdtsc" : "=A"(ret)); + return ret; +#elif defined(__x86_64__) || defined(__amd64__) + uint64_t low, high; + __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); + return (high << 32) | low; +#elif defined(__powerpc__) || defined(__ppc__) + // This returns a time-base, which is not always precisely a cycle-count. +#if defined(__powerpc64__) || defined(__ppc64__) + int64_t tb; + asm volatile("mfspr %0, 268" : "=r"(tb)); + return tb; +#else + uint32_t tbl, tbu0, tbu1; + asm volatile( + "mftbu %0\n" + "mftb %1\n" + "mftbu %2" + : "=r"(tbu0), "=r"(tbl), "=r"(tbu1)); + tbl &= -static_cast(tbu0 == tbu1); + // high 32 bits in tbu1; low 32 bits in tbl (tbu0 is no longer needed) + return (static_cast(tbu1) << 32) | tbl; +#endif +#elif defined(__sparc__) + int64_t tick; + asm(".byte 0x83, 0x41, 0x00, 0x00"); + asm("mov %%g1, %0" : "=r"(tick)); + return tick; +#elif defined(__ia64__) + int64_t itc; + asm("mov %0 = ar.itc" : "=r"(itc)); + return itc; +#elif defined(COMPILER_MSVC) && defined(_M_IX86) + // Older MSVC compilers (like 7.x) don't seem to support the + // __rdtsc intrinsic properly, so I prefer to use _asm instead + // when I know it will work. Otherwise, I'll use __rdtsc and hope + // the code is being compiled with a non-ancient compiler. + _asm rdtsc +#elif defined(COMPILER_MSVC) && defined(_M_ARM64) + // See // https://docs.microsoft.com/en-us/cpp/intrinsics/arm64-intrinsics + // and https://reviews.llvm.org/D53115 + int64_t virtual_timer_value; + virtual_timer_value = _ReadStatusReg(ARM64_CNTVCT); + return virtual_timer_value; +#elif defined(COMPILER_MSVC) + return __rdtsc(); +#elif defined(BENCHMARK_OS_NACL) + // Native Client validator on x86/x86-64 allows RDTSC instructions, + // and this case is handled above. Native Client validator on ARM + // rejects MRC instructions (used in the ARM-specific sequence below), + // so we handle it here. Portable Native Client compiles to + // architecture-agnostic bytecode, which doesn't provide any + // cycle counter access mnemonics. + + // Native Client does not provide any API to access cycle counter. + // Use clock_gettime(CLOCK_MONOTONIC, ...) instead of gettimeofday + // because is provides nanosecond resolution (which is noticable at + // least for PNaCl modules running on x86 Mac & Linux). + // Initialize to always return 0 if clock_gettime fails. + struct timespec ts = {0, 0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) * 1000000000 + ts.tv_nsec; +#elif defined(__aarch64__) + // System timer of ARMv8 runs at a different frequency than the CPU's. + // The frequency is fixed, typically in the range 1-50MHz. It can be + // read at CNTFRQ special register. We assume the OS has set up + // the virtual timer properly. + int64_t virtual_timer_value; + asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; +#elif defined(__ARM_ARCH) + // V6 is the earliest arch that has a standard cyclecount + // Native Client validator doesn't allow MRC instructions. +#if (__ARM_ARCH >= 6) + uint32_t pmccntr; + uint32_t pmuseren; + uint32_t pmcntenset; + // Read the user mode perf monitor counter access permissions. + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + // The counter is set up to count every 64th cycle + return static_cast(pmccntr) * 64; // Should optimize to << 6 + } + } +#endif + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#elif defined(__mips__) || defined(__m68k__) + // mips apparently only allows rdtsc for superusers, so we fall + // back to gettimeofday. It's possible clock_gettime would be better. + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#elif defined(__loongarch__) + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#elif defined(__s390__) // Covers both s390 and s390x. + // Return the CPU clock. + uint64_t tsc; +#if defined(BENCHMARK_OS_ZOS) && defined(COMPILER_IBMXL) + // z/OS XL compiler HLASM syntax. + asm(" stck %0" : "=m"(tsc) : : "cc"); +#else + asm("stck %0" : "=Q"(tsc) : : "cc"); +#endif + return tsc; +#elif defined(__riscv) // RISC-V + // Use RDCYCLE (and RDCYCLEH on riscv32) +#if __riscv_xlen == 32 + uint32_t cycles_lo, cycles_hi0, cycles_hi1; + // This asm also includes the PowerPC overflow handling strategy, as above. + // Implemented in assembly because Clang insisted on branching. + asm volatile( + "rdcycleh %0\n" + "rdcycle %1\n" + "rdcycleh %2\n" + "sub %0, %0, %2\n" + "seqz %0, %0\n" + "sub %0, zero, %0\n" + "and %1, %1, %0\n" + : "=r"(cycles_hi0), "=r"(cycles_lo), "=r"(cycles_hi1)); + return (static_cast(cycles_hi1) << 32) | cycles_lo; +#else + uint64_t cycles; + asm volatile("rdcycle %0" : "=r"(cycles)); + return cycles; +#endif +#elif defined(__e2k__) || defined(__elbrus__) + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#else +// The soft failover to a generic implementation is automatic only for ARM. +// For other platforms the developer is expected to make an attempt to create +// a fast implementation and use generic version if nothing better is available. +#error You need to define CycleTimer for your OS and CPU +#endif +} +} // end namespace cycleclock +} // end namespace benchmark + +#endif // BENCHMARK_CYCLECLOCK_H_ diff --git a/bridge/third_party/benchmark/src/internal_macros.h b/bridge/third_party/benchmark/src/internal_macros.h new file mode 100644 index 0000000000..91f367b894 --- /dev/null +++ b/bridge/third_party/benchmark/src/internal_macros.h @@ -0,0 +1,102 @@ +#ifndef BENCHMARK_INTERNAL_MACROS_H_ +#define BENCHMARK_INTERNAL_MACROS_H_ + +#include "benchmark/benchmark.h" + +/* Needed to detect STL */ +#include + +// clang-format off + +#ifndef __has_feature +#define __has_feature(x) 0 +#endif + +#if defined(__clang__) + #if defined(__ibmxl__) + #if !defined(COMPILER_IBMXL) + #define COMPILER_IBMXL + #endif + #elif !defined(COMPILER_CLANG) + #define COMPILER_CLANG + #endif +#elif defined(_MSC_VER) + #if !defined(COMPILER_MSVC) + #define COMPILER_MSVC + #endif +#elif defined(__GNUC__) + #if !defined(COMPILER_GCC) + #define COMPILER_GCC + #endif +#endif + +#if __has_feature(cxx_attributes) + #define BENCHMARK_NORETURN [[noreturn]] +#elif defined(__GNUC__) + #define BENCHMARK_NORETURN __attribute__((noreturn)) +#elif defined(COMPILER_MSVC) + #define BENCHMARK_NORETURN __declspec(noreturn) +#else + #define BENCHMARK_NORETURN +#endif + +#if defined(__CYGWIN__) + #define BENCHMARK_OS_CYGWIN 1 +#elif defined(_WIN32) + #define BENCHMARK_OS_WINDOWS 1 + #if defined(__MINGW32__) + #define BENCHMARK_OS_MINGW 1 + #endif +#elif defined(__APPLE__) + #define BENCHMARK_OS_APPLE 1 + #include "TargetConditionals.h" + #if defined(TARGET_OS_MAC) + #define BENCHMARK_OS_MACOSX 1 + #if defined(TARGET_OS_IPHONE) + #define BENCHMARK_OS_IOS 1 + #endif + #endif +#elif defined(__FreeBSD__) + #define BENCHMARK_OS_FREEBSD 1 +#elif defined(__NetBSD__) + #define BENCHMARK_OS_NETBSD 1 +#elif defined(__OpenBSD__) + #define BENCHMARK_OS_OPENBSD 1 +#elif defined(__DragonFly__) + #define BENCHMARK_OS_DRAGONFLY 1 +#elif defined(__linux__) + #define BENCHMARK_OS_LINUX 1 +#elif defined(__native_client__) + #define BENCHMARK_OS_NACL 1 +#elif defined(__EMSCRIPTEN__) + #define BENCHMARK_OS_EMSCRIPTEN 1 +#elif defined(__rtems__) + #define BENCHMARK_OS_RTEMS 1 +#elif defined(__Fuchsia__) +#define BENCHMARK_OS_FUCHSIA 1 +#elif defined (__SVR4) && defined (__sun) +#define BENCHMARK_OS_SOLARIS 1 +#elif defined(__QNX__) +#define BENCHMARK_OS_QNX 1 +#elif defined(__MVS__) +#define BENCHMARK_OS_ZOS 1 +#endif + +#if defined(__ANDROID__) && defined(__GLIBCXX__) +#define BENCHMARK_STL_ANDROID_GNUSTL 1 +#endif + +#if !__has_feature(cxx_exceptions) && !defined(__cpp_exceptions) \ + && !defined(__EXCEPTIONS) + #define BENCHMARK_HAS_NO_EXCEPTIONS +#endif + +#if defined(COMPILER_CLANG) || defined(COMPILER_GCC) + #define BENCHMARK_MAYBE_UNUSED __attribute__((unused)) +#else + #define BENCHMARK_MAYBE_UNUSED +#endif + +// clang-format on + +#endif // BENCHMARK_INTERNAL_MACROS_H_ diff --git a/bridge/third_party/benchmark/src/json_reporter.cc b/bridge/third_party/benchmark/src/json_reporter.cc new file mode 100644 index 0000000000..e84a4ed24f --- /dev/null +++ b/bridge/third_party/benchmark/src/json_reporter.cc @@ -0,0 +1,323 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include // for setprecision +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "complexity.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { +namespace internal { +extern std::map* global_context; +} + +namespace { + +std::string StrEscape(const std::string& s) { + std::string tmp; + tmp.reserve(s.size()); + for (char c : s) { + switch (c) { + case '\b': + tmp += "\\b"; + break; + case '\f': + tmp += "\\f"; + break; + case '\n': + tmp += "\\n"; + break; + case '\r': + tmp += "\\r"; + break; + case '\t': + tmp += "\\t"; + break; + case '\\': + tmp += "\\\\"; + break; + case '"': + tmp += "\\\""; + break; + default: + tmp += c; + break; + } + } + return tmp; +} + +std::string FormatKV(std::string const& key, std::string const& value) { + return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), + StrEscape(value).c_str()); +} + +std::string FormatKV(std::string const& key, const char* value) { + return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), + StrEscape(value).c_str()); +} + +std::string FormatKV(std::string const& key, bool value) { + return StrFormat("\"%s\": %s", StrEscape(key).c_str(), + value ? "true" : "false"); +} + +std::string FormatKV(std::string const& key, int64_t value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": " << value; + return ss.str(); +} + +std::string FormatKV(std::string const& key, IterationCount value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": " << value; + return ss.str(); +} + +std::string FormatKV(std::string const& key, double value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": "; + + if (std::isnan(value)) + ss << (value < 0 ? "-" : "") << "NaN"; + else if (std::isinf(value)) + ss << (value < 0 ? "-" : "") << "Infinity"; + else { + const auto max_digits10 = + std::numeric_limits::max_digits10; + const auto max_fractional_digits10 = max_digits10 - 1; + ss << std::scientific << std::setprecision(max_fractional_digits10) + << value; + } + return ss.str(); +} + +int64_t RoundDouble(double v) { return std::lround(v); } + +} // end namespace + +bool JSONReporter::ReportContext(const Context& context) { + std::ostream& out = GetOutputStream(); + + out << "{\n"; + std::string inner_indent(2, ' '); + + // Open context block and print context information. + out << inner_indent << "\"context\": {\n"; + std::string indent(4, ' '); + + std::string walltime_value = LocalDateTimeString(); + out << indent << FormatKV("date", walltime_value) << ",\n"; + + out << indent << FormatKV("host_name", context.sys_info.name) << ",\n"; + + if (Context::executable_name) { + out << indent << FormatKV("executable", Context::executable_name) << ",\n"; + } + + CPUInfo const& info = context.cpu_info; + out << indent << FormatKV("num_cpus", static_cast(info.num_cpus)) + << ",\n"; + out << indent + << FormatKV("mhz_per_cpu", + RoundDouble(info.cycles_per_second / 1000000.0)) + << ",\n"; + if (CPUInfo::Scaling::UNKNOWN != info.scaling) { + out << indent + << FormatKV("cpu_scaling_enabled", + info.scaling == CPUInfo::Scaling::ENABLED ? true : false) + << ",\n"; + } + + out << indent << "\"caches\": [\n"; + indent = std::string(6, ' '); + std::string cache_indent(8, ' '); + for (size_t i = 0; i < info.caches.size(); ++i) { + auto& CI = info.caches[i]; + out << indent << "{\n"; + out << cache_indent << FormatKV("type", CI.type) << ",\n"; + out << cache_indent << FormatKV("level", static_cast(CI.level)) + << ",\n"; + out << cache_indent << FormatKV("size", static_cast(CI.size)) + << ",\n"; + out << cache_indent + << FormatKV("num_sharing", static_cast(CI.num_sharing)) + << "\n"; + out << indent << "}"; + if (i != info.caches.size() - 1) out << ","; + out << "\n"; + } + indent = std::string(4, ' '); + out << indent << "],\n"; + out << indent << "\"load_avg\": ["; + for (auto it = info.load_avg.begin(); it != info.load_avg.end();) { + out << *it++; + if (it != info.load_avg.end()) out << ","; + } + out << "],\n"; + +#if defined(NDEBUG) + const char build_type[] = "release"; +#else + const char build_type[] = "debug"; +#endif + out << indent << FormatKV("library_build_type", build_type); + + if (internal::global_context != nullptr) { + for (const auto& kv : *internal::global_context) { + out << ",\n"; + out << indent << FormatKV(kv.first, kv.second); + } + } + out << "\n"; + + // Close context block and open the list of benchmarks. + out << inner_indent << "},\n"; + out << inner_indent << "\"benchmarks\": [\n"; + return true; +} + +void JSONReporter::ReportRuns(std::vector const& reports) { + if (reports.empty()) { + return; + } + std::string indent(4, ' '); + std::ostream& out = GetOutputStream(); + if (!first_report_) { + out << ",\n"; + } + first_report_ = false; + + for (auto it = reports.begin(); it != reports.end(); ++it) { + out << indent << "{\n"; + PrintRunData(*it); + out << indent << '}'; + auto it_cp = it; + if (++it_cp != reports.end()) { + out << ",\n"; + } + } +} + +void JSONReporter::Finalize() { + // Close the list of benchmarks and the top level object. + GetOutputStream() << "\n ]\n}\n"; +} + +void JSONReporter::PrintRunData(Run const& run) { + std::string indent(6, ' '); + std::ostream& out = GetOutputStream(); + out << indent << FormatKV("name", run.benchmark_name()) << ",\n"; + out << indent << FormatKV("family_index", run.family_index) << ",\n"; + out << indent + << FormatKV("per_family_instance_index", run.per_family_instance_index) + << ",\n"; + out << indent << FormatKV("run_name", run.run_name.str()) << ",\n"; + out << indent << FormatKV("run_type", [&run]() -> const char* { + switch (run.run_type) { + case BenchmarkReporter::Run::RT_Iteration: + return "iteration"; + case BenchmarkReporter::Run::RT_Aggregate: + return "aggregate"; + } + BENCHMARK_UNREACHABLE(); + }()) << ",\n"; + out << indent << FormatKV("repetitions", run.repetitions) << ",\n"; + if (run.run_type != BenchmarkReporter::Run::RT_Aggregate) { + out << indent << FormatKV("repetition_index", run.repetition_index) + << ",\n"; + } + out << indent << FormatKV("threads", run.threads) << ",\n"; + if (run.run_type == BenchmarkReporter::Run::RT_Aggregate) { + out << indent << FormatKV("aggregate_name", run.aggregate_name) << ",\n"; + out << indent << FormatKV("aggregate_unit", [&run]() -> const char* { + switch (run.aggregate_unit) { + case StatisticUnit::kTime: + return "time"; + case StatisticUnit::kPercentage: + return "percentage"; + } + BENCHMARK_UNREACHABLE(); + }()) << ",\n"; + } + if (run.error_occurred) { + out << indent << FormatKV("error_occurred", run.error_occurred) << ",\n"; + out << indent << FormatKV("error_message", run.error_message) << ",\n"; + } + if (!run.report_big_o && !run.report_rms) { + out << indent << FormatKV("iterations", run.iterations) << ",\n"; + if (run.run_type != Run::RT_Aggregate || + run.aggregate_unit == StatisticUnit::kTime) { + out << indent << FormatKV("real_time", run.GetAdjustedRealTime()) + << ",\n"; + out << indent << FormatKV("cpu_time", run.GetAdjustedCPUTime()); + } else { + assert(run.aggregate_unit == StatisticUnit::kPercentage); + out << indent << FormatKV("real_time", run.real_accumulated_time) + << ",\n"; + out << indent << FormatKV("cpu_time", run.cpu_accumulated_time); + } + out << ",\n" + << indent << FormatKV("time_unit", GetTimeUnitString(run.time_unit)); + } else if (run.report_big_o) { + out << indent << FormatKV("cpu_coefficient", run.GetAdjustedCPUTime()) + << ",\n"; + out << indent << FormatKV("real_coefficient", run.GetAdjustedRealTime()) + << ",\n"; + out << indent << FormatKV("big_o", GetBigOString(run.complexity)) << ",\n"; + out << indent << FormatKV("time_unit", GetTimeUnitString(run.time_unit)); + } else if (run.report_rms) { + out << indent << FormatKV("rms", run.GetAdjustedCPUTime()); + } + + for (auto& c : run.counters) { + out << ",\n" << indent << FormatKV(c.first, c.second); + } + + if (run.memory_result) { + const MemoryManager::Result memory_result = *run.memory_result; + out << ",\n" << indent << FormatKV("allocs_per_iter", run.allocs_per_iter); + out << ",\n" + << indent << FormatKV("max_bytes_used", memory_result.max_bytes_used); + + auto report_if_present = [&out, &indent](const char* label, int64_t val) { + if (val != MemoryManager::TombstoneValue) + out << ",\n" << indent << FormatKV(label, val); + }; + + report_if_present("total_allocated_bytes", + memory_result.total_allocated_bytes); + report_if_present("net_heap_growth", memory_result.net_heap_growth); + } + + if (!run.report_label.empty()) { + out << ",\n" << indent << FormatKV("label", run.report_label); + } + out << '\n'; +} + +const int64_t MemoryManager::TombstoneValue = + std::numeric_limits::max(); + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/log.h b/bridge/third_party/benchmark/src/log.h new file mode 100644 index 0000000000..48c071aded --- /dev/null +++ b/bridge/third_party/benchmark/src/log.h @@ -0,0 +1,74 @@ +#ifndef BENCHMARK_LOG_H_ +#define BENCHMARK_LOG_H_ + +#include +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { +namespace internal { + +typedef std::basic_ostream&(EndLType)(std::basic_ostream&); + +class LogType { + friend LogType& GetNullLogInstance(); + friend LogType& GetErrorLogInstance(); + + // FIXME: Add locking to output. + template + friend LogType& operator<<(LogType&, Tp const&); + friend LogType& operator<<(LogType&, EndLType*); + + private: + LogType(std::ostream* out) : out_(out) {} + std::ostream* out_; + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(LogType); +}; + +template +LogType& operator<<(LogType& log, Tp const& value) { + if (log.out_) { + *log.out_ << value; + } + return log; +} + +inline LogType& operator<<(LogType& log, EndLType* m) { + if (log.out_) { + *log.out_ << m; + } + return log; +} + +inline int& LogLevel() { + static int log_level = 0; + return log_level; +} + +inline LogType& GetNullLogInstance() { + static LogType log(nullptr); + return log; +} + +inline LogType& GetErrorLogInstance() { + static LogType log(&std::clog); + return log; +} + +inline LogType& GetLogInstanceForLevel(int level) { + if (level <= LogLevel()) { + return GetErrorLogInstance(); + } + return GetNullLogInstance(); +} + +} // end namespace internal +} // end namespace benchmark + +// clang-format off +#define BM_VLOG(x) \ + (::benchmark::internal::GetLogInstanceForLevel(x) << "-- LOG(" << x << "):" \ + " ") +// clang-format on +#endif diff --git a/bridge/third_party/benchmark/src/mutex.h b/bridge/third_party/benchmark/src/mutex.h new file mode 100644 index 0000000000..bec78d9e5f --- /dev/null +++ b/bridge/third_party/benchmark/src/mutex.h @@ -0,0 +1,155 @@ +#ifndef BENCHMARK_MUTEX_H_ +#define BENCHMARK_MUTEX_H_ + +#include +#include + +#include "check.h" + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(HAVE_THREAD_SAFETY_ATTRIBUTES) +#define THREAD_ANNOTATION_ATTRIBUTE_(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE_(x) // no-op +#endif + +#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE_(capability(x)) + +#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE_(scoped_lockable) + +#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE_(guarded_by(x)) + +#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE_(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(release_shared_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE_(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE_(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE_(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE_(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE_(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE_(no_thread_safety_analysis) + +namespace benchmark { + +typedef std::condition_variable Condition; + +// NOTE: Wrappers for std::mutex and std::unique_lock are provided so that +// we can annotate them with thread safety attributes and use the +// -Wthread-safety warning with clang. The standard library types cannot be +// used directly because they do not provide the required annotations. +class CAPABILITY("mutex") Mutex { + public: + Mutex() {} + + void lock() ACQUIRE() { mut_.lock(); } + void unlock() RELEASE() { mut_.unlock(); } + std::mutex& native_handle() { return mut_; } + + private: + std::mutex mut_; +}; + +class SCOPED_CAPABILITY MutexLock { + typedef std::unique_lock MutexLockImp; + + public: + MutexLock(Mutex& m) ACQUIRE(m) : ml_(m.native_handle()) {} + ~MutexLock() RELEASE() {} + MutexLockImp& native_handle() { return ml_; } + + private: + MutexLockImp ml_; +}; + +class Barrier { + public: + Barrier(int num_threads) : running_threads_(num_threads) {} + + // Called by each thread + bool wait() EXCLUDES(lock_) { + bool last_thread = false; + { + MutexLock ml(lock_); + last_thread = createBarrier(ml); + } + if (last_thread) phase_condition_.notify_all(); + return last_thread; + } + + void removeThread() EXCLUDES(lock_) { + MutexLock ml(lock_); + --running_threads_; + if (entered_ != 0) phase_condition_.notify_all(); + } + + private: + Mutex lock_; + Condition phase_condition_; + int running_threads_; + + // State for barrier management + int phase_number_ = 0; + int entered_ = 0; // Number of threads that have entered this barrier + + // Enter the barrier and wait until all other threads have also + // entered the barrier. Returns iff this is the last thread to + // enter the barrier. + bool createBarrier(MutexLock& ml) REQUIRES(lock_) { + BM_CHECK_LT(entered_, running_threads_); + entered_++; + if (entered_ < running_threads_) { + // Wait for all threads to enter + int phase_number_cp = phase_number_; + auto cb = [this, phase_number_cp]() { + return this->phase_number_ > phase_number_cp || + entered_ == running_threads_; // A thread has aborted in error + }; + phase_condition_.wait(ml.native_handle(), cb); + if (phase_number_ > phase_number_cp) return false; + // else (running_threads_ == entered_) and we are the last thread. + } + // Last thread has reached the barrier + phase_number_++; + entered_ = 0; + return true; + } +}; + +} // end namespace benchmark + +#endif // BENCHMARK_MUTEX_H_ diff --git a/bridge/third_party/benchmark/src/perf_counters.cc b/bridge/third_party/benchmark/src/perf_counters.cc new file mode 100644 index 0000000000..b2ac7687ef --- /dev/null +++ b/bridge/third_party/benchmark/src/perf_counters.cc @@ -0,0 +1,132 @@ +// Copyright 2021 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "perf_counters.h" + +#include +#include + +#if defined HAVE_LIBPFM +#include "perfmon/pfmlib.h" +#include "perfmon/pfmlib_perf_event.h" +#endif + +namespace benchmark { +namespace internal { + +constexpr size_t PerfCounterValues::kMaxCounters; + +#if defined HAVE_LIBPFM +const bool PerfCounters::kSupported = true; + +bool PerfCounters::Initialize() { return pfm_initialize() == PFM_SUCCESS; } + +PerfCounters PerfCounters::Create( + const std::vector& counter_names) { + if (counter_names.empty()) { + return NoCounters(); + } + if (counter_names.size() > PerfCounterValues::kMaxCounters) { + GetErrorLogInstance() + << counter_names.size() + << " counters were requested. The minimum is 1, the maximum is " + << PerfCounterValues::kMaxCounters << "\n"; + return NoCounters(); + } + std::vector counter_ids(counter_names.size()); + + const int mode = PFM_PLM3; // user mode only + for (size_t i = 0; i < counter_names.size(); ++i) { + const bool is_first = i == 0; + struct perf_event_attr attr {}; + attr.size = sizeof(attr); + const int group_id = !is_first ? counter_ids[0] : -1; + const auto& name = counter_names[i]; + if (name.empty()) { + GetErrorLogInstance() << "A counter name was the empty string\n"; + return NoCounters(); + } + pfm_perf_encode_arg_t arg{}; + arg.attr = &attr; + + const int pfm_get = + pfm_get_os_event_encoding(name.c_str(), mode, PFM_OS_PERF_EVENT, &arg); + if (pfm_get != PFM_SUCCESS) { + GetErrorLogInstance() << "Unknown counter name: " << name << "\n"; + return NoCounters(); + } + attr.disabled = is_first; + // Note: the man page for perf_event_create suggests inerit = true and + // read_format = PERF_FORMAT_GROUP don't work together, but that's not the + // case. + attr.inherit = true; + attr.pinned = is_first; + attr.exclude_kernel = true; + attr.exclude_user = false; + attr.exclude_hv = true; + // Read all counters in one read. + attr.read_format = PERF_FORMAT_GROUP; + + int id = -1; + static constexpr size_t kNrOfSyscallRetries = 5; + // Retry syscall as it was interrupted often (b/64774091). + for (size_t num_retries = 0; num_retries < kNrOfSyscallRetries; + ++num_retries) { + id = perf_event_open(&attr, 0, -1, group_id, 0); + if (id >= 0 || errno != EINTR) { + break; + } + } + if (id < 0) { + GetErrorLogInstance() + << "Failed to get a file descriptor for " << name << "\n"; + return NoCounters(); + } + + counter_ids[i] = id; + } + if (ioctl(counter_ids[0], PERF_EVENT_IOC_ENABLE) != 0) { + GetErrorLogInstance() << "Failed to start counters\n"; + return NoCounters(); + } + + return PerfCounters(counter_names, std::move(counter_ids)); +} + +PerfCounters::~PerfCounters() { + if (counter_ids_.empty()) { + return; + } + ioctl(counter_ids_[0], PERF_EVENT_IOC_DISABLE); + for (int fd : counter_ids_) { + close(fd); + } +} +#else // defined HAVE_LIBPFM +const bool PerfCounters::kSupported = false; + +bool PerfCounters::Initialize() { return false; } + +PerfCounters PerfCounters::Create( + const std::vector& counter_names) { + if (!counter_names.empty()) { + GetErrorLogInstance() << "Performance counters not supported."; + } + return NoCounters(); +} + +PerfCounters::~PerfCounters() = default; +#endif // defined HAVE_LIBPFM +} // namespace internal +} // namespace benchmark diff --git a/bridge/third_party/benchmark/src/perf_counters.h b/bridge/third_party/benchmark/src/perf_counters.h new file mode 100644 index 0000000000..47ca1385e2 --- /dev/null +++ b/bridge/third_party/benchmark/src/perf_counters.h @@ -0,0 +1,172 @@ +// Copyright 2021 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_PERF_COUNTERS_H +#define BENCHMARK_PERF_COUNTERS_H + +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" +#include "log.h" + +#ifndef BENCHMARK_OS_WINDOWS +#include +#endif + +namespace benchmark { +namespace internal { + +// Typically, we can only read a small number of counters. There is also a +// padding preceding counter values, when reading multiple counters with one +// syscall (which is desirable). PerfCounterValues abstracts these details. +// The implementation ensures the storage is inlined, and allows 0-based +// indexing into the counter values. +// The object is used in conjunction with a PerfCounters object, by passing it +// to Snapshot(). The values are populated such that +// perfCounters->names()[i]'s value is obtained at position i (as given by +// operator[]) of this object. +class PerfCounterValues { + public: + explicit PerfCounterValues(size_t nr_counters) : nr_counters_(nr_counters) { + BM_CHECK_LE(nr_counters_, kMaxCounters); + } + + uint64_t operator[](size_t pos) const { return values_[kPadding + pos]; } + + static constexpr size_t kMaxCounters = 3; + + private: + friend class PerfCounters; + // Get the byte buffer in which perf counters can be captured. + // This is used by PerfCounters::Read + std::pair get_data_buffer() { + return {reinterpret_cast(values_.data()), + sizeof(uint64_t) * (kPadding + nr_counters_)}; + } + + static constexpr size_t kPadding = 1; + std::array values_; + const size_t nr_counters_; +}; + +// Collect PMU counters. The object, once constructed, is ready to be used by +// calling read(). PMU counter collection is enabled from the time create() is +// called, to obtain the object, until the object's destructor is called. +class PerfCounters final { + public: + // True iff this platform supports performance counters. + static const bool kSupported; + + bool IsValid() const { return is_valid_; } + static PerfCounters NoCounters() { return PerfCounters(); } + + ~PerfCounters(); + PerfCounters(PerfCounters&&) = default; + PerfCounters(const PerfCounters&) = delete; + + // Platform-specific implementations may choose to do some library + // initialization here. + static bool Initialize(); + + // Return a PerfCounters object ready to read the counters with the names + // specified. The values are user-mode only. The counter name format is + // implementation and OS specific. + // TODO: once we move to C++-17, this should be a std::optional, and then the + // IsValid() boolean can be dropped. + static PerfCounters Create(const std::vector& counter_names); + + // Take a snapshot of the current value of the counters into the provided + // valid PerfCounterValues storage. The values are populated such that: + // names()[i]'s value is (*values)[i] + BENCHMARK_ALWAYS_INLINE bool Snapshot(PerfCounterValues* values) const { +#ifndef BENCHMARK_OS_WINDOWS + assert(values != nullptr); + assert(IsValid()); + auto buffer = values->get_data_buffer(); + auto read_bytes = ::read(counter_ids_[0], buffer.first, buffer.second); + return static_cast(read_bytes) == buffer.second; +#else + (void)values; + return false; +#endif + } + + const std::vector& names() const { return counter_names_; } + size_t num_counters() const { return counter_names_.size(); } + + private: + PerfCounters(const std::vector& counter_names, + std::vector&& counter_ids) + : counter_ids_(std::move(counter_ids)), + counter_names_(counter_names), + is_valid_(true) {} + PerfCounters() : is_valid_(false) {} + + std::vector counter_ids_; + const std::vector counter_names_; + const bool is_valid_; +}; + +// Typical usage of the above primitives. +class PerfCountersMeasurement final { + public: + PerfCountersMeasurement(PerfCounters&& c) + : counters_(std::move(c)), + start_values_(counters_.IsValid() ? counters_.names().size() : 0), + end_values_(counters_.IsValid() ? counters_.names().size() : 0) {} + + bool IsValid() const { return counters_.IsValid(); } + + BENCHMARK_ALWAYS_INLINE void Start() { + assert(IsValid()); + // Tell the compiler to not move instructions above/below where we take + // the snapshot. + ClobberMemory(); + counters_.Snapshot(&start_values_); + ClobberMemory(); + } + + BENCHMARK_ALWAYS_INLINE std::vector> + StopAndGetMeasurements() { + assert(IsValid()); + // Tell the compiler to not move instructions above/below where we take + // the snapshot. + ClobberMemory(); + counters_.Snapshot(&end_values_); + ClobberMemory(); + + std::vector> ret; + for (size_t i = 0; i < counters_.names().size(); ++i) { + double measurement = static_cast(end_values_[i]) - + static_cast(start_values_[i]); + ret.push_back({counters_.names()[i], measurement}); + } + return ret; + } + + private: + PerfCounters counters_; + PerfCounterValues start_values_; + PerfCounterValues end_values_; +}; + +BENCHMARK_UNUSED static bool perf_init_anchor = PerfCounters::Initialize(); + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_PERF_COUNTERS_H diff --git a/bridge/third_party/benchmark/src/re.h b/bridge/third_party/benchmark/src/re.h new file mode 100644 index 0000000000..630046782d --- /dev/null +++ b/bridge/third_party/benchmark/src/re.h @@ -0,0 +1,158 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_RE_H_ +#define BENCHMARK_RE_H_ + +#include "internal_macros.h" + +// clang-format off + +#if !defined(HAVE_STD_REGEX) && \ + !defined(HAVE_GNU_POSIX_REGEX) && \ + !defined(HAVE_POSIX_REGEX) + // No explicit regex selection; detect based on builtin hints. + #if defined(BENCHMARK_OS_LINUX) || defined(BENCHMARK_OS_APPLE) + #define HAVE_POSIX_REGEX 1 + #elif __cplusplus >= 199711L + #define HAVE_STD_REGEX 1 + #endif +#endif + +// Prefer C regex libraries when compiling w/o exceptions so that we can +// correctly report errors. +#if defined(BENCHMARK_HAS_NO_EXCEPTIONS) && \ + defined(BENCHMARK_HAVE_STD_REGEX) && \ + (defined(HAVE_GNU_POSIX_REGEX) || defined(HAVE_POSIX_REGEX)) + #undef HAVE_STD_REGEX +#endif + +#if defined(HAVE_STD_REGEX) + #include +#elif defined(HAVE_GNU_POSIX_REGEX) + #include +#elif defined(HAVE_POSIX_REGEX) + #include +#else +#error No regular expression backend was found! +#endif + +// clang-format on + +#include + +#include "check.h" + +namespace benchmark { + +// A wrapper around the POSIX regular expression API that provides automatic +// cleanup +class Regex { + public: + Regex() : init_(false) {} + + ~Regex(); + + // Compile a regular expression matcher from spec. Returns true on success. + // + // On failure (and if error is not nullptr), error is populated with a human + // readable error message if an error occurs. + bool Init(const std::string& spec, std::string* error); + + // Returns whether str matches the compiled regular expression. + bool Match(const std::string& str); + + private: + bool init_; +// Underlying regular expression object +#if defined(HAVE_STD_REGEX) + std::regex re_; +#elif defined(HAVE_POSIX_REGEX) || defined(HAVE_GNU_POSIX_REGEX) + regex_t re_; +#else +#error No regular expression backend implementation available +#endif +}; + +#if defined(HAVE_STD_REGEX) + +inline bool Regex::Init(const std::string& spec, std::string* error) { +#ifdef BENCHMARK_HAS_NO_EXCEPTIONS + ((void)error); // suppress unused warning +#else + try { +#endif + re_ = std::regex(spec, std::regex_constants::extended); + init_ = true; +#ifndef BENCHMARK_HAS_NO_EXCEPTIONS +} +catch (const std::regex_error& e) { + if (error) { + *error = e.what(); + } +} +#endif +return init_; +} + +inline Regex::~Regex() {} + +inline bool Regex::Match(const std::string& str) { + if (!init_) { + return false; + } + return std::regex_search(str, re_); +} + +#else +inline bool Regex::Init(const std::string& spec, std::string* error) { + int ec = regcomp(&re_, spec.c_str(), REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + if (error) { + size_t needed = regerror(ec, &re_, nullptr, 0); + char* errbuf = new char[needed]; + regerror(ec, &re_, errbuf, needed); + + // regerror returns the number of bytes necessary to null terminate + // the string, so we move that when assigning to error. + BM_CHECK_NE(needed, 0); + error->assign(errbuf, needed - 1); + + delete[] errbuf; + } + + return false; + } + + init_ = true; + return true; +} + +inline Regex::~Regex() { + if (init_) { + regfree(&re_); + } +} + +inline bool Regex::Match(const std::string& str) { + if (!init_) { + return false; + } + return regexec(&re_, str.c_str(), 0, nullptr, 0) == 0; +} +#endif + +} // end namespace benchmark + +#endif // BENCHMARK_RE_H_ diff --git a/bridge/third_party/benchmark/src/reporter.cc b/bridge/third_party/benchmark/src/reporter.cc new file mode 100644 index 0000000000..1d2df17b90 --- /dev/null +++ b/bridge/third_party/benchmark/src/reporter.cc @@ -0,0 +1,114 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { +namespace internal { +extern std::map *global_context; +} + +BenchmarkReporter::BenchmarkReporter() + : output_stream_(&std::cout), error_stream_(&std::cerr) {} + +BenchmarkReporter::~BenchmarkReporter() {} + +void BenchmarkReporter::PrintBasicContext(std::ostream *out, + Context const &context) { + BM_CHECK(out) << "cannot be null"; + auto &Out = *out; + + Out << LocalDateTimeString() << "\n"; + + if (context.executable_name) + Out << "Running " << context.executable_name << "\n"; + + const CPUInfo &info = context.cpu_info; + Out << "Run on (" << info.num_cpus << " X " + << (info.cycles_per_second / 1000000.0) << " MHz CPU " + << ((info.num_cpus > 1) ? "s" : "") << ")\n"; + if (info.caches.size() != 0) { + Out << "CPU Caches:\n"; + for (auto &CInfo : info.caches) { + Out << " L" << CInfo.level << " " << CInfo.type << " " + << (CInfo.size / 1024) << " KiB"; + if (CInfo.num_sharing != 0) + Out << " (x" << (info.num_cpus / CInfo.num_sharing) << ")"; + Out << "\n"; + } + } + if (!info.load_avg.empty()) { + Out << "Load Average: "; + for (auto It = info.load_avg.begin(); It != info.load_avg.end();) { + Out << StrFormat("%.2f", *It++); + if (It != info.load_avg.end()) Out << ", "; + } + Out << "\n"; + } + + if (internal::global_context != nullptr) { + for (const auto &kv : *internal::global_context) { + Out << kv.first << ": " << kv.second << "\n"; + } + } + + if (CPUInfo::Scaling::ENABLED == info.scaling) { + Out << "***WARNING*** CPU scaling is enabled, the benchmark " + "real time measurements may be noisy and will incur extra " + "overhead.\n"; + } + +#ifndef NDEBUG + Out << "***WARNING*** Library was built as DEBUG. Timings may be " + "affected.\n"; +#endif +} + +// No initializer because it's already initialized to NULL. +const char *BenchmarkReporter::Context::executable_name; + +BenchmarkReporter::Context::Context() + : cpu_info(CPUInfo::Get()), sys_info(SystemInfo::Get()) {} + +std::string BenchmarkReporter::Run::benchmark_name() const { + std::string name = run_name.str(); + if (run_type == RT_Aggregate) { + name += "_" + aggregate_name; + } + return name; +} + +double BenchmarkReporter::Run::GetAdjustedRealTime() const { + double new_time = real_accumulated_time * GetTimeUnitMultiplier(time_unit); + if (iterations != 0) new_time /= static_cast(iterations); + return new_time; +} + +double BenchmarkReporter::Run::GetAdjustedCPUTime() const { + double new_time = cpu_accumulated_time * GetTimeUnitMultiplier(time_unit); + if (iterations != 0) new_time /= static_cast(iterations); + return new_time; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/sleep.cc b/bridge/third_party/benchmark/src/sleep.cc new file mode 100644 index 0000000000..ab59000f24 --- /dev/null +++ b/bridge/third_party/benchmark/src/sleep.cc @@ -0,0 +1,66 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sleep.h" + +#include +#include +#include + +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#endif + +#ifdef BENCHMARK_OS_ZOS +#include +#endif + +namespace benchmark { +#ifdef BENCHMARK_OS_WINDOWS +// Window's Sleep takes milliseconds argument. +void SleepForMilliseconds(int milliseconds) { Sleep(milliseconds); } +void SleepForSeconds(double seconds) { + SleepForMilliseconds(static_cast(kNumMillisPerSecond * seconds)); +} +#else // BENCHMARK_OS_WINDOWS +void SleepForMicroseconds(int microseconds) { +#ifdef BENCHMARK_OS_ZOS + // z/OS does not support nanosleep. Instead call sleep() and then usleep() to + // sleep for the remaining microseconds because usleep() will fail if its + // argument is greater than 1000000. + div_t sleepTime = div(microseconds, kNumMicrosPerSecond); + int seconds = sleepTime.quot; + while (seconds != 0) seconds = sleep(seconds); + while (usleep(sleepTime.rem) == -1 && errno == EINTR) + ; +#else + struct timespec sleep_time; + sleep_time.tv_sec = microseconds / kNumMicrosPerSecond; + sleep_time.tv_nsec = (microseconds % kNumMicrosPerSecond) * kNumNanosPerMicro; + while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) + ; // Ignore signals and wait for the full interval to elapse. +#endif +} + +void SleepForMilliseconds(int milliseconds) { + SleepForMicroseconds(milliseconds * kNumMicrosPerMilli); +} + +void SleepForSeconds(double seconds) { + SleepForMicroseconds(static_cast(seconds * kNumMicrosPerSecond)); +} +#endif // BENCHMARK_OS_WINDOWS +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/sleep.h b/bridge/third_party/benchmark/src/sleep.h new file mode 100644 index 0000000000..f98551afe2 --- /dev/null +++ b/bridge/third_party/benchmark/src/sleep.h @@ -0,0 +1,15 @@ +#ifndef BENCHMARK_SLEEP_H_ +#define BENCHMARK_SLEEP_H_ + +namespace benchmark { +const int kNumMillisPerSecond = 1000; +const int kNumMicrosPerMilli = 1000; +const int kNumMicrosPerSecond = kNumMillisPerSecond * 1000; +const int kNumNanosPerMicro = 1000; +const int kNumNanosPerSecond = kNumNanosPerMicro * kNumMicrosPerSecond; + +void SleepForMilliseconds(int milliseconds); +void SleepForSeconds(double seconds); +} // end namespace benchmark + +#endif // BENCHMARK_SLEEP_H_ diff --git a/bridge/third_party/benchmark/src/statistics.cc b/bridge/third_party/benchmark/src/statistics.cc new file mode 100644 index 0000000000..3e5ef09939 --- /dev/null +++ b/bridge/third_party/benchmark/src/statistics.cc @@ -0,0 +1,208 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// Copyright 2017 Roman Lebedev. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "statistics.h" + +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "check.h" + +namespace benchmark { + +auto StatisticsSum = [](const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.0); +}; + +double StatisticsMean(const std::vector& v) { + if (v.empty()) return 0.0; + return StatisticsSum(v) * (1.0 / v.size()); +} + +double StatisticsMedian(const std::vector& v) { + if (v.size() < 3) return StatisticsMean(v); + std::vector copy(v); + + auto center = copy.begin() + v.size() / 2; + std::nth_element(copy.begin(), center, copy.end()); + + // did we have an odd number of samples? + // if yes, then center is the median + // it no, then we are looking for the average between center and the value + // before + if (v.size() % 2 == 1) return *center; + auto center2 = copy.begin() + v.size() / 2 - 1; + std::nth_element(copy.begin(), center2, copy.end()); + return (*center + *center2) / 2.0; +} + +// Return the sum of the squares of this sample set +auto SumSquares = [](const std::vector& v) { + return std::inner_product(v.begin(), v.end(), v.begin(), 0.0); +}; + +auto Sqr = [](const double dat) { return dat * dat; }; +auto Sqrt = [](const double dat) { + // Avoid NaN due to imprecision in the calculations + if (dat < 0.0) return 0.0; + return std::sqrt(dat); +}; + +double StatisticsStdDev(const std::vector& v) { + const auto mean = StatisticsMean(v); + if (v.empty()) return mean; + + // Sample standard deviation is undefined for n = 1 + if (v.size() == 1) return 0.0; + + const double avg_squares = SumSquares(v) * (1.0 / v.size()); + return Sqrt(v.size() / (v.size() - 1.0) * (avg_squares - Sqr(mean))); +} + +double StatisticsCV(const std::vector& v) { + if (v.size() < 2) return 0.0; + + const auto stddev = StatisticsStdDev(v); + const auto mean = StatisticsMean(v); + + return stddev / mean; +} + +std::vector ComputeStats( + const std::vector& reports) { + typedef BenchmarkReporter::Run Run; + std::vector results; + + auto error_count = + std::count_if(reports.begin(), reports.end(), + [](Run const& run) { return run.error_occurred; }); + + if (reports.size() - error_count < 2) { + // We don't report aggregated data if there was a single run. + return results; + } + + // Accumulators. + std::vector real_accumulated_time_stat; + std::vector cpu_accumulated_time_stat; + + real_accumulated_time_stat.reserve(reports.size()); + cpu_accumulated_time_stat.reserve(reports.size()); + + // All repetitions should be run with the same number of iterations so we + // can take this information from the first benchmark. + const IterationCount run_iterations = reports.front().iterations; + // create stats for user counters + struct CounterStat { + Counter c; + std::vector s; + }; + std::map counter_stats; + for (Run const& r : reports) { + for (auto const& cnt : r.counters) { + auto it = counter_stats.find(cnt.first); + if (it == counter_stats.end()) { + counter_stats.insert({cnt.first, {cnt.second, std::vector{}}}); + it = counter_stats.find(cnt.first); + it->second.s.reserve(reports.size()); + } else { + BM_CHECK_EQ(counter_stats[cnt.first].c.flags, cnt.second.flags); + } + } + } + + // Populate the accumulators. + for (Run const& run : reports) { + BM_CHECK_EQ(reports[0].benchmark_name(), run.benchmark_name()); + BM_CHECK_EQ(run_iterations, run.iterations); + if (run.error_occurred) continue; + real_accumulated_time_stat.emplace_back(run.real_accumulated_time); + cpu_accumulated_time_stat.emplace_back(run.cpu_accumulated_time); + // user counters + for (auto const& cnt : run.counters) { + auto it = counter_stats.find(cnt.first); + BM_CHECK_NE(it, counter_stats.end()); + it->second.s.emplace_back(cnt.second); + } + } + + // Only add label if it is same for all runs + std::string report_label = reports[0].report_label; + for (std::size_t i = 1; i < reports.size(); i++) { + if (reports[i].report_label != report_label) { + report_label = ""; + break; + } + } + + const double iteration_rescale_factor = + double(reports.size()) / double(run_iterations); + + for (const auto& Stat : *reports[0].statistics) { + // Get the data from the accumulator to BenchmarkReporter::Run's. + Run data; + data.run_name = reports[0].run_name; + data.family_index = reports[0].family_index; + data.per_family_instance_index = reports[0].per_family_instance_index; + data.run_type = BenchmarkReporter::Run::RT_Aggregate; + data.threads = reports[0].threads; + data.repetitions = reports[0].repetitions; + data.repetition_index = Run::no_repetition_index; + data.aggregate_name = Stat.name_; + data.aggregate_unit = Stat.unit_; + data.report_label = report_label; + + // It is incorrect to say that an aggregate is computed over + // run's iterations, because those iterations already got averaged. + // Similarly, if there are N repetitions with 1 iterations each, + // an aggregate will be computed over N measurements, not 1. + // Thus it is best to simply use the count of separate reports. + data.iterations = reports.size(); + + data.real_accumulated_time = Stat.compute_(real_accumulated_time_stat); + data.cpu_accumulated_time = Stat.compute_(cpu_accumulated_time_stat); + + if (data.aggregate_unit == StatisticUnit::kTime) { + // We will divide these times by data.iterations when reporting, but the + // data.iterations is not necessarily the scale of these measurements, + // because in each repetition, these timers are sum over all the iters. + // And if we want to say that the stats are over N repetitions and not + // M iterations, we need to multiply these by (N/M). + data.real_accumulated_time *= iteration_rescale_factor; + data.cpu_accumulated_time *= iteration_rescale_factor; + } + + data.time_unit = reports[0].time_unit; + + // user counters + for (auto const& kv : counter_stats) { + // Do *NOT* rescale the custom counters. They are already properly scaled. + const auto uc_stat = Stat.compute_(kv.second.s); + auto c = Counter(uc_stat, counter_stats[kv.first].c.flags, + counter_stats[kv.first].c.oneK); + data.counters[kv.first] = c; + } + + results.push_back(data); + } + + return results; +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/statistics.h b/bridge/third_party/benchmark/src/statistics.h new file mode 100644 index 0000000000..a9545a58c6 --- /dev/null +++ b/bridge/third_party/benchmark/src/statistics.h @@ -0,0 +1,38 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// Copyright 2017 Roman Lebedev. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STATISTICS_H_ +#define STATISTICS_H_ + +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// Return a vector containing the mean, median and standard devation information +// (and any user-specified info) for the specified list of reports. If 'reports' +// contains less than two non-errored runs an empty vector is returned +std::vector ComputeStats( + const std::vector& reports); + +double StatisticsMean(const std::vector& v); +double StatisticsMedian(const std::vector& v); +double StatisticsStdDev(const std::vector& v); +double StatisticsCV(const std::vector& v); + +} // end namespace benchmark + +#endif // STATISTICS_H_ diff --git a/bridge/third_party/benchmark/src/string_util.cc b/bridge/third_party/benchmark/src/string_util.cc new file mode 100644 index 0000000000..401fa13df7 --- /dev/null +++ b/bridge/third_party/benchmark/src/string_util.cc @@ -0,0 +1,265 @@ +#include "string_util.h" + +#include +#ifdef BENCHMARK_STL_ANDROID_GNUSTL +#include +#endif +#include +#include +#include +#include +#include + +#include "arraysize.h" + +namespace benchmark { +namespace { + +// kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta. +const char kBigSIUnits[] = "kMGTPEZY"; +// Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi. +const char kBigIECUnits[] = "KMGTPEZY"; +// milli, micro, nano, pico, femto, atto, zepto, yocto. +const char kSmallSIUnits[] = "munpfazy"; + +// We require that all three arrays have the same size. +static_assert(arraysize(kBigSIUnits) == arraysize(kBigIECUnits), + "SI and IEC unit arrays must be the same size"); +static_assert(arraysize(kSmallSIUnits) == arraysize(kBigSIUnits), + "Small SI and Big SI unit arrays must be the same size"); + +static const int64_t kUnitsSize = arraysize(kBigSIUnits); + +void ToExponentAndMantissa(double val, double thresh, int precision, + double one_k, std::string* mantissa, + int64_t* exponent) { + std::stringstream mantissa_stream; + + if (val < 0) { + mantissa_stream << "-"; + val = -val; + } + + // Adjust threshold so that it never excludes things which can't be rendered + // in 'precision' digits. + const double adjusted_threshold = + std::max(thresh, 1.0 / std::pow(10.0, precision)); + const double big_threshold = adjusted_threshold * one_k; + const double small_threshold = adjusted_threshold; + // Values in ]simple_threshold,small_threshold[ will be printed as-is + const double simple_threshold = 0.01; + + if (val > big_threshold) { + // Positive powers + double scaled = val; + for (size_t i = 0; i < arraysize(kBigSIUnits); ++i) { + scaled /= one_k; + if (scaled <= big_threshold) { + mantissa_stream << scaled; + *exponent = i + 1; + *mantissa = mantissa_stream.str(); + return; + } + } + mantissa_stream << val; + *exponent = 0; + } else if (val < small_threshold) { + // Negative powers + if (val < simple_threshold) { + double scaled = val; + for (size_t i = 0; i < arraysize(kSmallSIUnits); ++i) { + scaled *= one_k; + if (scaled >= small_threshold) { + mantissa_stream << scaled; + *exponent = -static_cast(i + 1); + *mantissa = mantissa_stream.str(); + return; + } + } + } + mantissa_stream << val; + *exponent = 0; + } else { + mantissa_stream << val; + *exponent = 0; + } + *mantissa = mantissa_stream.str(); +} + +std::string ExponentToPrefix(int64_t exponent, bool iec) { + if (exponent == 0) return ""; + + const int64_t index = (exponent > 0 ? exponent - 1 : -exponent - 1); + if (index >= kUnitsSize) return ""; + + const char* array = + (exponent > 0 ? (iec ? kBigIECUnits : kBigSIUnits) : kSmallSIUnits); + if (iec) + return array[index] + std::string("i"); + else + return std::string(1, array[index]); +} + +std::string ToBinaryStringFullySpecified(double value, double threshold, + int precision, double one_k = 1024.0) { + std::string mantissa; + int64_t exponent; + ToExponentAndMantissa(value, threshold, precision, one_k, &mantissa, + &exponent); + return mantissa + ExponentToPrefix(exponent, false); +} + +} // end namespace + +void AppendHumanReadable(int n, std::string* str) { + std::stringstream ss; + // Round down to the nearest SI prefix. + ss << ToBinaryStringFullySpecified(n, 1.0, 0); + *str += ss.str(); +} + +std::string HumanReadableNumber(double n, double one_k) { + // 1.1 means that figures up to 1.1k should be shown with the next unit down; + // this softens edge effects. + // 1 means that we should show one decimal place of precision. + return ToBinaryStringFullySpecified(n, 1.1, 1, one_k); +} + +std::string StrFormatImp(const char* msg, va_list args) { + // we might need a second shot at this, so pre-emptivly make a copy + va_list args_cp; + va_copy(args_cp, args); + + // TODO(ericwf): use std::array for first attempt to avoid one memory + // allocation guess what the size might be + std::array local_buff; + std::size_t size = local_buff.size(); + // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation + // in the android-ndk + auto ret = vsnprintf(local_buff.data(), size, msg, args_cp); + + va_end(args_cp); + + // handle empty expansion + if (ret == 0) return std::string{}; + if (static_cast(ret) < size) + return std::string(local_buff.data()); + + // we did not provide a long enough buffer on our first attempt. + // add 1 to size to account for null-byte in size cast to prevent overflow + size = static_cast(ret) + 1; + auto buff_ptr = std::unique_ptr(new char[size]); + // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation + // in the android-ndk + vsnprintf(buff_ptr.get(), size, msg, args); + return std::string(buff_ptr.get()); +} + +std::string StrFormat(const char* format, ...) { + va_list args; + va_start(args, format); + std::string tmp = StrFormatImp(format, args); + va_end(args); + return tmp; +} + +std::vector StrSplit(const std::string& str, char delim) { + if (str.empty()) return {}; + std::vector ret; + size_t first = 0; + size_t next = str.find(delim); + for (; next != std::string::npos; + first = next + 1, next = str.find(delim, first)) { + ret.push_back(str.substr(first, next - first)); + } + ret.push_back(str.substr(first)); + return ret; +} + +#ifdef BENCHMARK_STL_ANDROID_GNUSTL +/* + * GNU STL in Android NDK lacks support for some C++11 functions, including + * stoul, stoi, stod. We reimplement them here using C functions strtoul, + * strtol, strtod. Note that reimplemented functions are in benchmark:: + * namespace, not std:: namespace. + */ +unsigned long stoul(const std::string& str, size_t* pos, int base) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const unsigned long result = strtoul(strStart, &strEnd, base); + + const int strtoulErrno = errno; + /* Restore previous errno */ + errno = oldErrno; + + /* Check for errors and return */ + if (strtoulErrno == ERANGE) { + throw std::out_of_range("stoul failed: " + str + + " is outside of range of unsigned long"); + } else if (strEnd == strStart || strtoulErrno != 0) { + throw std::invalid_argument("stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return result; +} + +int stoi(const std::string& str, size_t* pos, int base) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const long result = strtol(strStart, &strEnd, base); + + const int strtolErrno = errno; + /* Restore previous errno */ + errno = oldErrno; + + /* Check for errors and return */ + if (strtolErrno == ERANGE || long(int(result)) != result) { + throw std::out_of_range("stoul failed: " + str + + " is outside of range of int"); + } else if (strEnd == strStart || strtolErrno != 0) { + throw std::invalid_argument("stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return int(result); +} + +double stod(const std::string& str, size_t* pos) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const double result = strtod(strStart, &strEnd); + + /* Restore previous errno */ + const int strtodErrno = errno; + errno = oldErrno; + + /* Check for errors and return */ + if (strtodErrno == ERANGE) { + throw std::out_of_range("stoul failed: " + str + + " is outside of range of int"); + } else if (strEnd == strStart || strtodErrno != 0) { + throw std::invalid_argument("stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return result; +} +#endif + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/string_util.h b/bridge/third_party/benchmark/src/string_util.h new file mode 100644 index 0000000000..ff3b7da47d --- /dev/null +++ b/bridge/third_party/benchmark/src/string_util.h @@ -0,0 +1,65 @@ +#ifndef BENCHMARK_STRING_UTIL_H_ +#define BENCHMARK_STRING_UTIL_H_ + +#include +#include +#include + +#include "internal_macros.h" + +namespace benchmark { + +void AppendHumanReadable(int n, std::string* str); + +std::string HumanReadableNumber(double n, double one_k = 1024.0); + +#if defined(__MINGW32__) +__attribute__((format(__MINGW_PRINTF_FORMAT, 1, 2))) +#elif defined(__GNUC__) +__attribute__((format(printf, 1, 2))) +#endif +std::string +StrFormat(const char* format, ...); + +inline std::ostream& StrCatImp(std::ostream& out) BENCHMARK_NOEXCEPT { + return out; +} + +template +inline std::ostream& StrCatImp(std::ostream& out, First&& f, Rest&&... rest) { + out << std::forward(f); + return StrCatImp(out, std::forward(rest)...); +} + +template +inline std::string StrCat(Args&&... args) { + std::ostringstream ss; + StrCatImp(ss, std::forward(args)...); + return ss.str(); +} + +std::vector StrSplit(const std::string& str, char delim); + +// Disable lint checking for this block since it re-implements C functions. +// NOLINTBEGIN +#ifdef BENCHMARK_STL_ANDROID_GNUSTL +/* + * GNU STL in Android NDK lacks support for some C++11 functions, including + * stoul, stoi, stod. We reimplement them here using C functions strtoul, + * strtol, strtod. Note that reimplemented functions are in benchmark:: + * namespace, not std:: namespace. + */ +unsigned long stoul(const std::string& str, size_t* pos = nullptr, + int base = 10); +int stoi(const std::string& str, size_t* pos = nullptr, int base = 10); +double stod(const std::string& str, size_t* pos = nullptr); +#else +using std::stod; // NOLINT(misc-unused-using-decls) +using std::stoi; // NOLINT(misc-unused-using-decls) +using std::stoul; // NOLINT(misc-unused-using-decls) +#endif +// NOLINTEND + +} // end namespace benchmark + +#endif // BENCHMARK_STRING_UTIL_H_ diff --git a/bridge/third_party/benchmark/src/sysinfo.cc b/bridge/third_party/benchmark/src/sysinfo.cc new file mode 100644 index 0000000000..1b3458443c --- /dev/null +++ b/bridge/third_party/benchmark/src/sysinfo.cc @@ -0,0 +1,727 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#undef StrCat // Don't let StrCat in string_util.h be renamed to lstrcatA +#include +#include + +#include +#else +#include +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include // this header must be included before 'sys/sysctl.h' to avoid compilation error on FreeBSD +#include +#if defined BENCHMARK_OS_FREEBSD || defined BENCHMARK_OS_MACOSX || \ + defined BENCHMARK_OS_NETBSD || defined BENCHMARK_OS_OPENBSD || \ + defined BENCHMARK_OS_DRAGONFLY +#define BENCHMARK_HAS_SYSCTL +#include +#endif +#endif +#if defined(BENCHMARK_OS_SOLARIS) +#include +#endif +#if defined(BENCHMARK_OS_QNX) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "cycleclock.h" +#include "internal_macros.h" +#include "log.h" +#include "sleep.h" +#include "string_util.h" + +namespace benchmark { +namespace { + +void PrintImp(std::ostream& out) { out << std::endl; } + +template +void PrintImp(std::ostream& out, First&& f, Rest&&... rest) { + out << std::forward(f); + PrintImp(out, std::forward(rest)...); +} + +template +BENCHMARK_NORETURN void PrintErrorAndDie(Args&&... args) { + PrintImp(std::cerr, std::forward(args)...); + std::exit(EXIT_FAILURE); +} + +#ifdef BENCHMARK_HAS_SYSCTL + +/// ValueUnion - A type used to correctly alias the byte-for-byte output of +/// `sysctl` with the result type it's to be interpreted as. +struct ValueUnion { + union DataT { + uint32_t uint32_value; + uint64_t uint64_value; + // For correct aliasing of union members from bytes. + char bytes[8]; + }; + using DataPtr = std::unique_ptr; + + // The size of the data union member + its trailing array size. + size_t Size; + DataPtr Buff; + + public: + ValueUnion() : Size(0), Buff(nullptr, &std::free) {} + + explicit ValueUnion(size_t BuffSize) + : Size(sizeof(DataT) + BuffSize), + Buff(::new (std::malloc(Size)) DataT(), &std::free) {} + + ValueUnion(ValueUnion&& other) = default; + + explicit operator bool() const { return bool(Buff); } + + char* data() const { return Buff->bytes; } + + std::string GetAsString() const { return std::string(data()); } + + int64_t GetAsInteger() const { + if (Size == sizeof(Buff->uint32_value)) + return static_cast(Buff->uint32_value); + else if (Size == sizeof(Buff->uint64_value)) + return static_cast(Buff->uint64_value); + BENCHMARK_UNREACHABLE(); + } + + uint64_t GetAsUnsigned() const { + if (Size == sizeof(Buff->uint32_value)) + return Buff->uint32_value; + else if (Size == sizeof(Buff->uint64_value)) + return Buff->uint64_value; + BENCHMARK_UNREACHABLE(); + } + + template + std::array GetAsArray() { + const int ArrSize = sizeof(T) * N; + BM_CHECK_LE(ArrSize, Size); + std::array Arr; + std::memcpy(Arr.data(), data(), ArrSize); + return Arr; + } +}; + +ValueUnion GetSysctlImp(std::string const& Name) { +#if defined BENCHMARK_OS_OPENBSD + int mib[2]; + + mib[0] = CTL_HW; + if ((Name == "hw.ncpu") || (Name == "hw.cpuspeed")) { + ValueUnion buff(sizeof(int)); + + if (Name == "hw.ncpu") { + mib[1] = HW_NCPU; + } else { + mib[1] = HW_CPUSPEED; + } + + if (sysctl(mib, 2, buff.data(), &buff.Size, nullptr, 0) == -1) { + return ValueUnion(); + } + return buff; + } + return ValueUnion(); +#else + size_t CurBuffSize = 0; + if (sysctlbyname(Name.c_str(), nullptr, &CurBuffSize, nullptr, 0) == -1) + return ValueUnion(); + + ValueUnion buff(CurBuffSize); + if (sysctlbyname(Name.c_str(), buff.data(), &buff.Size, nullptr, 0) == 0) + return buff; + return ValueUnion(); +#endif +} + +BENCHMARK_MAYBE_UNUSED +bool GetSysctl(std::string const& Name, std::string* Out) { + Out->clear(); + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + Out->assign(Buff.data()); + return true; +} + +template ::value>::type> +bool GetSysctl(std::string const& Name, Tp* Out) { + *Out = 0; + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + *Out = static_cast(Buff.GetAsUnsigned()); + return true; +} + +template +bool GetSysctl(std::string const& Name, std::array* Out) { + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + *Out = Buff.GetAsArray(); + return true; +} +#endif + +template +bool ReadFromFile(std::string const& fname, ArgT* arg) { + *arg = ArgT(); + std::ifstream f(fname.c_str()); + if (!f.is_open()) return false; + f >> *arg; + return f.good(); +} + +CPUInfo::Scaling CpuScaling(int num_cpus) { + // We don't have a valid CPU count, so don't even bother. + if (num_cpus <= 0) return CPUInfo::Scaling::UNKNOWN; +#if defined(BENCHMARK_OS_QNX) + return CPUInfo::Scaling::UNKNOWN; +#elif !defined(BENCHMARK_OS_WINDOWS) + // On Linux, the CPUfreq subsystem exposes CPU information as files on the + // local file system. If reading the exported files fails, then we may not be + // running on Linux, so we silently ignore all the read errors. + std::string res; + for (int cpu = 0; cpu < num_cpus; ++cpu) { + std::string governor_file = + StrCat("/sys/devices/system/cpu/cpu", cpu, "/cpufreq/scaling_governor"); + if (ReadFromFile(governor_file, &res) && res != "performance") + return CPUInfo::Scaling::ENABLED; + } + return CPUInfo::Scaling::DISABLED; +#else + return CPUInfo::Scaling::UNKNOWN; +#endif +} + +int CountSetBitsInCPUMap(std::string Val) { + auto CountBits = [](std::string Part) { + using CPUMask = std::bitset; + Part = "0x" + Part; + CPUMask Mask(benchmark::stoul(Part, nullptr, 16)); + return static_cast(Mask.count()); + }; + size_t Pos; + int total = 0; + while ((Pos = Val.find(',')) != std::string::npos) { + total += CountBits(Val.substr(0, Pos)); + Val = Val.substr(Pos + 1); + } + if (!Val.empty()) { + total += CountBits(Val); + } + return total; +} + +BENCHMARK_MAYBE_UNUSED +std::vector GetCacheSizesFromKVFS() { + std::vector res; + std::string dir = "/sys/devices/system/cpu/cpu0/cache/"; + int Idx = 0; + while (true) { + CPUInfo::CacheInfo info; + std::string FPath = StrCat(dir, "index", Idx++, "/"); + std::ifstream f(StrCat(FPath, "size").c_str()); + if (!f.is_open()) break; + std::string suffix; + f >> info.size; + if (f.fail()) + PrintErrorAndDie("Failed while reading file '", FPath, "size'"); + if (f.good()) { + f >> suffix; + if (f.bad()) + PrintErrorAndDie( + "Invalid cache size format: failed to read size suffix"); + else if (f && suffix != "K") + PrintErrorAndDie("Invalid cache size format: Expected bytes ", suffix); + else if (suffix == "K") + info.size *= 1024; + } + if (!ReadFromFile(StrCat(FPath, "type"), &info.type)) + PrintErrorAndDie("Failed to read from file ", FPath, "type"); + if (!ReadFromFile(StrCat(FPath, "level"), &info.level)) + PrintErrorAndDie("Failed to read from file ", FPath, "level"); + std::string map_str; + if (!ReadFromFile(StrCat(FPath, "shared_cpu_map"), &map_str)) + PrintErrorAndDie("Failed to read from file ", FPath, "shared_cpu_map"); + info.num_sharing = CountSetBitsInCPUMap(map_str); + res.push_back(info); + } + + return res; +} + +#ifdef BENCHMARK_OS_MACOSX +std::vector GetCacheSizesMacOSX() { + std::vector res; + std::array CacheCounts{{0, 0, 0, 0}}; + GetSysctl("hw.cacheconfig", &CacheCounts); + + struct { + std::string name; + std::string type; + int level; + uint64_t num_sharing; + } Cases[] = {{"hw.l1dcachesize", "Data", 1, CacheCounts[1]}, + {"hw.l1icachesize", "Instruction", 1, CacheCounts[1]}, + {"hw.l2cachesize", "Unified", 2, CacheCounts[2]}, + {"hw.l3cachesize", "Unified", 3, CacheCounts[3]}}; + for (auto& C : Cases) { + int val; + if (!GetSysctl(C.name, &val)) continue; + CPUInfo::CacheInfo info; + info.type = C.type; + info.level = C.level; + info.size = val; + info.num_sharing = static_cast(C.num_sharing); + res.push_back(std::move(info)); + } + return res; +} +#elif defined(BENCHMARK_OS_WINDOWS) +std::vector GetCacheSizesWindows() { + std::vector res; + DWORD buffer_size = 0; + using PInfo = SYSTEM_LOGICAL_PROCESSOR_INFORMATION; + using CInfo = CACHE_DESCRIPTOR; + + using UPtr = std::unique_ptr; + GetLogicalProcessorInformation(nullptr, &buffer_size); + UPtr buff((PInfo*)malloc(buffer_size), &std::free); + if (!GetLogicalProcessorInformation(buff.get(), &buffer_size)) + PrintErrorAndDie("Failed during call to GetLogicalProcessorInformation: ", + GetLastError()); + + PInfo* it = buff.get(); + PInfo* end = buff.get() + (buffer_size / sizeof(PInfo)); + + for (; it != end; ++it) { + if (it->Relationship != RelationCache) continue; + using BitSet = std::bitset; + BitSet B(it->ProcessorMask); + // To prevent duplicates, only consider caches where CPU 0 is specified + if (!B.test(0)) continue; + CInfo* Cache = &it->Cache; + CPUInfo::CacheInfo C; + C.num_sharing = static_cast(B.count()); + C.level = Cache->Level; + C.size = Cache->Size; + switch (Cache->Type) { + case CacheUnified: + C.type = "Unified"; + break; + case CacheInstruction: + C.type = "Instruction"; + break; + case CacheData: + C.type = "Data"; + break; + case CacheTrace: + C.type = "Trace"; + break; + default: + C.type = "Unknown"; + break; + } + res.push_back(C); + } + return res; +} +#elif BENCHMARK_OS_QNX +std::vector GetCacheSizesQNX() { + std::vector res; + struct cacheattr_entry* cache = SYSPAGE_ENTRY(cacheattr); + uint32_t const elsize = SYSPAGE_ELEMENT_SIZE(cacheattr); + int num = SYSPAGE_ENTRY_SIZE(cacheattr) / elsize; + for (int i = 0; i < num; ++i) { + CPUInfo::CacheInfo info; + switch (cache->flags) { + case CACHE_FLAG_INSTR: + info.type = "Instruction"; + info.level = 1; + break; + case CACHE_FLAG_DATA: + info.type = "Data"; + info.level = 1; + break; + case CACHE_FLAG_UNIFIED: + info.type = "Unified"; + info.level = 2; + break; + case CACHE_FLAG_SHARED: + info.type = "Shared"; + info.level = 3; + break; + default: + continue; + break; + } + info.size = cache->line_size * cache->num_lines; + info.num_sharing = 0; + res.push_back(std::move(info)); + cache = SYSPAGE_ARRAY_ADJ_OFFSET(cacheattr, cache, elsize); + } + return res; +} +#endif + +std::vector GetCacheSizes() { +#ifdef BENCHMARK_OS_MACOSX + return GetCacheSizesMacOSX(); +#elif defined(BENCHMARK_OS_WINDOWS) + return GetCacheSizesWindows(); +#elif defined(BENCHMARK_OS_QNX) + return GetCacheSizesQNX(); +#else + return GetCacheSizesFromKVFS(); +#endif +} + +std::string GetSystemName() { +#if defined(BENCHMARK_OS_WINDOWS) + std::string str; + const unsigned COUNT = MAX_COMPUTERNAME_LENGTH + 1; + TCHAR hostname[COUNT] = {'\0'}; + DWORD DWCOUNT = COUNT; + if (!GetComputerName(hostname, &DWCOUNT)) return std::string(""); +#ifndef UNICODE + str = std::string(hostname, DWCOUNT); +#else + // Using wstring_convert, Is deprecated in C++17 + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + std::wstring wStr(hostname, DWCOUNT); + str = converter.to_bytes(wStr); +#endif + return str; +#else // defined(BENCHMARK_OS_WINDOWS) +#ifndef HOST_NAME_MAX +#ifdef BENCHMARK_HAS_SYSCTL // BSD/Mac Doesnt have HOST_NAME_MAX defined +#define HOST_NAME_MAX 64 +#elif defined(BENCHMARK_OS_NACL) +#define HOST_NAME_MAX 64 +#elif defined(BENCHMARK_OS_QNX) +#define HOST_NAME_MAX 154 +#elif defined(BENCHMARK_OS_RTEMS) +#define HOST_NAME_MAX 256 +#else +#pragma message("HOST_NAME_MAX not defined. using 64") +#define HOST_NAME_MAX 64 +#endif +#endif // def HOST_NAME_MAX + char hostname[HOST_NAME_MAX]; + int retVal = gethostname(hostname, HOST_NAME_MAX); + if (retVal != 0) return std::string(""); + return std::string(hostname); +#endif // Catch-all POSIX block. +} + +int GetNumCPUs() { +#ifdef BENCHMARK_HAS_SYSCTL + int NumCPU = -1; + if (GetSysctl("hw.ncpu", &NumCPU)) return NumCPU; + fprintf(stderr, "Err: %s\n", strerror(errno)); + std::exit(EXIT_FAILURE); +#elif defined(BENCHMARK_OS_WINDOWS) + SYSTEM_INFO sysinfo; + // Use memset as opposed to = {} to avoid GCC missing initializer false + // positives. + std::memset(&sysinfo, 0, sizeof(SYSTEM_INFO)); + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; // number of logical + // processors in the current + // group +#elif defined(BENCHMARK_OS_SOLARIS) + // Returns -1 in case of a failure. + int NumCPU = sysconf(_SC_NPROCESSORS_ONLN); + if (NumCPU < 0) { + fprintf(stderr, "sysconf(_SC_NPROCESSORS_ONLN) failed with error: %s\n", + strerror(errno)); + } + return NumCPU; +#elif defined(BENCHMARK_OS_QNX) + return static_cast(_syspage_ptr->num_cpu); +#else + int NumCPUs = 0; + int MaxID = -1; + std::ifstream f("/proc/cpuinfo"); + if (!f.is_open()) { + std::cerr << "failed to open /proc/cpuinfo\n"; + return -1; + } + const std::string Key = "processor"; + std::string ln; + while (std::getline(f, ln)) { + if (ln.empty()) continue; + size_t SplitIdx = ln.find(':'); + std::string value; +#if defined(__s390__) + // s390 has another format in /proc/cpuinfo + // it needs to be parsed differently + if (SplitIdx != std::string::npos) + value = ln.substr(Key.size() + 1, SplitIdx - Key.size() - 1); +#else + if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1); +#endif + if (ln.size() >= Key.size() && ln.compare(0, Key.size(), Key) == 0) { + NumCPUs++; + if (!value.empty()) { + int CurID = benchmark::stoi(value); + MaxID = std::max(CurID, MaxID); + } + } + } + if (f.bad()) { + std::cerr << "Failure reading /proc/cpuinfo\n"; + return -1; + } + if (!f.eof()) { + std::cerr << "Failed to read to end of /proc/cpuinfo\n"; + return -1; + } + f.close(); + + if ((MaxID + 1) != NumCPUs) { + fprintf(stderr, + "CPU ID assignments in /proc/cpuinfo seem messed up." + " This is usually caused by a bad BIOS.\n"); + } + return NumCPUs; +#endif + BENCHMARK_UNREACHABLE(); +} + +double GetCPUCyclesPerSecond(CPUInfo::Scaling scaling) { + // Currently, scaling is only used on linux path here, + // suppress diagnostics about it being unused on other paths. + (void)scaling; + +#if defined BENCHMARK_OS_LINUX || defined BENCHMARK_OS_CYGWIN + long freq; + + // If the kernel is exporting the tsc frequency use that. There are issues + // where cpuinfo_max_freq cannot be relied on because the BIOS may be + // exporintg an invalid p-state (on x86) or p-states may be used to put the + // processor in a new mode (turbo mode). Essentially, those frequencies + // cannot always be relied upon. The same reasons apply to /proc/cpuinfo as + // well. + if (ReadFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", &freq) + // If CPU scaling is disabled, use the *current* frequency. + // Note that we specifically don't want to read cpuinfo_cur_freq, + // because it is only readable by root. + || (scaling == CPUInfo::Scaling::DISABLED && + ReadFromFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", + &freq)) + // Otherwise, if CPU scaling may be in effect, we want to use + // the *maximum* frequency, not whatever CPU speed some random processor + // happens to be using now. + || ReadFromFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + &freq)) { + // The value is in kHz (as the file name suggests). For example, on a + // 2GHz warpstation, the file contains the value "2000000". + return freq * 1000.0; + } + + const double error_value = -1; + double bogo_clock = error_value; + + std::ifstream f("/proc/cpuinfo"); + if (!f.is_open()) { + std::cerr << "failed to open /proc/cpuinfo\n"; + return error_value; + } + + auto startsWithKey = [](std::string const& Value, std::string const& Key) { + if (Key.size() > Value.size()) return false; + auto Cmp = [&](char X, char Y) { + return std::tolower(X) == std::tolower(Y); + }; + return std::equal(Key.begin(), Key.end(), Value.begin(), Cmp); + }; + + std::string ln; + while (std::getline(f, ln)) { + if (ln.empty()) continue; + size_t SplitIdx = ln.find(':'); + std::string value; + if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1); + // When parsing the "cpu MHz" and "bogomips" (fallback) entries, we only + // accept positive values. Some environments (virtual machines) report zero, + // which would cause infinite looping in WallTime_Init. + if (startsWithKey(ln, "cpu MHz")) { + if (!value.empty()) { + double cycles_per_second = benchmark::stod(value) * 1000000.0; + if (cycles_per_second > 0) return cycles_per_second; + } + } else if (startsWithKey(ln, "bogomips")) { + if (!value.empty()) { + bogo_clock = benchmark::stod(value) * 1000000.0; + if (bogo_clock < 0.0) bogo_clock = error_value; + } + } + } + if (f.bad()) { + std::cerr << "Failure reading /proc/cpuinfo\n"; + return error_value; + } + if (!f.eof()) { + std::cerr << "Failed to read to end of /proc/cpuinfo\n"; + return error_value; + } + f.close(); + // If we found the bogomips clock, but nothing better, we'll use it (but + // we're not happy about it); otherwise, fallback to the rough estimation + // below. + if (bogo_clock >= 0.0) return bogo_clock; + +#elif defined BENCHMARK_HAS_SYSCTL + constexpr auto* FreqStr = +#if defined(BENCHMARK_OS_FREEBSD) || defined(BENCHMARK_OS_NETBSD) + "machdep.tsc_freq"; +#elif defined BENCHMARK_OS_OPENBSD + "hw.cpuspeed"; +#elif defined BENCHMARK_OS_DRAGONFLY + "hw.tsc_frequency"; +#else + "hw.cpufrequency"; +#endif + unsigned long long hz = 0; +#if defined BENCHMARK_OS_OPENBSD + if (GetSysctl(FreqStr, &hz)) return hz * 1000000; +#else + if (GetSysctl(FreqStr, &hz)) return hz; +#endif + fprintf(stderr, "Unable to determine clock rate from sysctl: %s: %s\n", + FreqStr, strerror(errno)); + +#elif defined BENCHMARK_OS_WINDOWS + // In NT, read MHz from the registry. If we fail to do so or we're in win9x + // then make a crude estimate. + DWORD data, data_size = sizeof(data); + if (IsWindowsXPOrGreater() && + SUCCEEDED( + SHGetValueA(HKEY_LOCAL_MACHINE, + "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", + "~MHz", nullptr, &data, &data_size))) + return static_cast((int64_t)data * + (int64_t)(1000 * 1000)); // was mhz +#elif defined(BENCHMARK_OS_SOLARIS) + kstat_ctl_t* kc = kstat_open(); + if (!kc) { + std::cerr << "failed to open /dev/kstat\n"; + return -1; + } + kstat_t* ksp = kstat_lookup(kc, (char*)"cpu_info", -1, (char*)"cpu_info0"); + if (!ksp) { + std::cerr << "failed to lookup in /dev/kstat\n"; + return -1; + } + if (kstat_read(kc, ksp, NULL) < 0) { + std::cerr << "failed to read from /dev/kstat\n"; + return -1; + } + kstat_named_t* knp = + (kstat_named_t*)kstat_data_lookup(ksp, (char*)"current_clock_Hz"); + if (!knp) { + std::cerr << "failed to lookup data in /dev/kstat\n"; + return -1; + } + if (knp->data_type != KSTAT_DATA_UINT64) { + std::cerr << "current_clock_Hz is of unexpected data type: " + << knp->data_type << "\n"; + return -1; + } + double clock_hz = knp->value.ui64; + kstat_close(kc); + return clock_hz; +#elif defined(BENCHMARK_OS_QNX) + return static_cast((int64_t)(SYSPAGE_ENTRY(cpuinfo)->speed) * + (int64_t)(1000 * 1000)); +#endif + // If we've fallen through, attempt to roughly estimate the CPU clock rate. + const int estimate_time_ms = 1000; + const auto start_ticks = cycleclock::Now(); + SleepForMilliseconds(estimate_time_ms); + return static_cast(cycleclock::Now() - start_ticks); +} + +std::vector GetLoadAvg() { +#if (defined BENCHMARK_OS_FREEBSD || defined(BENCHMARK_OS_LINUX) || \ + defined BENCHMARK_OS_MACOSX || defined BENCHMARK_OS_NETBSD || \ + defined BENCHMARK_OS_OPENBSD || defined BENCHMARK_OS_DRAGONFLY) && \ + !defined(__ANDROID__) + constexpr int kMaxSamples = 3; + std::vector res(kMaxSamples, 0.0); + const int nelem = getloadavg(res.data(), kMaxSamples); + if (nelem < 1) { + res.clear(); + } else { + res.resize(nelem); + } + return res; +#else + return {}; +#endif +} + +} // end namespace + +const CPUInfo& CPUInfo::Get() { + static const CPUInfo* info = new CPUInfo(); + return *info; +} + +CPUInfo::CPUInfo() + : num_cpus(GetNumCPUs()), + scaling(CpuScaling(num_cpus)), + cycles_per_second(GetCPUCyclesPerSecond(scaling)), + caches(GetCacheSizes()), + load_avg(GetLoadAvg()) {} + +const SystemInfo& SystemInfo::Get() { + static const SystemInfo* info = new SystemInfo(); + return *info; +} + +SystemInfo::SystemInfo() : name(GetSystemName()) {} +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/thread_manager.h b/bridge/third_party/benchmark/src/thread_manager.h new file mode 100644 index 0000000000..4680285089 --- /dev/null +++ b/bridge/third_party/benchmark/src/thread_manager.h @@ -0,0 +1,63 @@ +#ifndef BENCHMARK_THREAD_MANAGER_H +#define BENCHMARK_THREAD_MANAGER_H + +#include + +#include "benchmark/benchmark.h" +#include "mutex.h" + +namespace benchmark { +namespace internal { + +class ThreadManager { + public: + explicit ThreadManager(int num_threads) + : alive_threads_(num_threads), start_stop_barrier_(num_threads) {} + + Mutex& GetBenchmarkMutex() const RETURN_CAPABILITY(benchmark_mutex_) { + return benchmark_mutex_; + } + + bool StartStopBarrier() EXCLUDES(end_cond_mutex_) { + return start_stop_barrier_.wait(); + } + + void NotifyThreadComplete() EXCLUDES(end_cond_mutex_) { + start_stop_barrier_.removeThread(); + if (--alive_threads_ == 0) { + MutexLock lock(end_cond_mutex_); + end_condition_.notify_all(); + } + } + + void WaitForAllThreads() EXCLUDES(end_cond_mutex_) { + MutexLock lock(end_cond_mutex_); + end_condition_.wait(lock.native_handle(), + [this]() { return alive_threads_ == 0; }); + } + + struct Result { + IterationCount iterations = 0; + double real_time_used = 0; + double cpu_time_used = 0; + double manual_time_used = 0; + int64_t complexity_n = 0; + std::string report_label_; + std::string error_message_; + bool has_error_ = false; + UserCounters counters; + }; + GUARDED_BY(GetBenchmarkMutex()) Result results; + + private: + mutable Mutex benchmark_mutex_; + std::atomic alive_threads_; + Barrier start_stop_barrier_; + Mutex end_cond_mutex_; + Condition end_condition_; +}; + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_THREAD_MANAGER_H diff --git a/bridge/third_party/benchmark/src/thread_timer.h b/bridge/third_party/benchmark/src/thread_timer.h new file mode 100644 index 0000000000..eb23f59561 --- /dev/null +++ b/bridge/third_party/benchmark/src/thread_timer.h @@ -0,0 +1,86 @@ +#ifndef BENCHMARK_THREAD_TIMER_H +#define BENCHMARK_THREAD_TIMER_H + +#include "check.h" +#include "timers.h" + +namespace benchmark { +namespace internal { + +class ThreadTimer { + explicit ThreadTimer(bool measure_process_cpu_time_) + : measure_process_cpu_time(measure_process_cpu_time_) {} + + public: + static ThreadTimer Create() { + return ThreadTimer(/*measure_process_cpu_time_=*/false); + } + static ThreadTimer CreateProcessCpuTime() { + return ThreadTimer(/*measure_process_cpu_time_=*/true); + } + + // Called by each thread + void StartTimer() { + running_ = true; + start_real_time_ = ChronoClockNow(); + start_cpu_time_ = ReadCpuTimerOfChoice(); + } + + // Called by each thread + void StopTimer() { + BM_CHECK(running_); + running_ = false; + real_time_used_ += ChronoClockNow() - start_real_time_; + // Floating point error can result in the subtraction producing a negative + // time. Guard against that. + cpu_time_used_ += + std::max(ReadCpuTimerOfChoice() - start_cpu_time_, 0); + } + + // Called by each thread + void SetIterationTime(double seconds) { manual_time_used_ += seconds; } + + bool running() const { return running_; } + + // REQUIRES: timer is not running + double real_time_used() const { + BM_CHECK(!running_); + return real_time_used_; + } + + // REQUIRES: timer is not running + double cpu_time_used() const { + BM_CHECK(!running_); + return cpu_time_used_; + } + + // REQUIRES: timer is not running + double manual_time_used() const { + BM_CHECK(!running_); + return manual_time_used_; + } + + private: + double ReadCpuTimerOfChoice() const { + if (measure_process_cpu_time) return ProcessCPUUsage(); + return ThreadCPUUsage(); + } + + // should the thread, or the process, time be measured? + const bool measure_process_cpu_time; + + bool running_ = false; // Is the timer running + double start_real_time_ = 0; // If running_ + double start_cpu_time_ = 0; // If running_ + + // Accumulated time so far (does not contain current slice if running_) + double real_time_used_ = 0; + double cpu_time_used_ = 0; + // Manually set iteration time. User sets this with SetIterationTime(seconds). + double manual_time_used_ = 0; +}; + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_THREAD_TIMER_H diff --git a/bridge/third_party/benchmark/src/timers.cc b/bridge/third_party/benchmark/src/timers.cc new file mode 100644 index 0000000000..21d3db20da --- /dev/null +++ b/bridge/third_party/benchmark/src/timers.cc @@ -0,0 +1,259 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "timers.h" + +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#undef StrCat // Don't let StrCat in string_util.h be renamed to lstrcatA +#include +#include +#else +#include +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include // this header must be included before 'sys/sysctl.h' to avoid compilation error on FreeBSD +#include +#if defined BENCHMARK_OS_FREEBSD || defined BENCHMARK_OS_DRAGONFLY || \ + defined BENCHMARK_OS_MACOSX +#include +#endif +#if defined(BENCHMARK_OS_MACOSX) +#include +#include +#include +#endif +#endif + +#ifdef BENCHMARK_OS_EMSCRIPTEN +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "log.h" +#include "sleep.h" +#include "string_util.h" + +namespace benchmark { + +// Suppress unused warnings on helper functions. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +namespace { +#if defined(BENCHMARK_OS_WINDOWS) +double MakeTime(FILETIME const& kernel_time, FILETIME const& user_time) { + ULARGE_INTEGER kernel; + ULARGE_INTEGER user; + kernel.HighPart = kernel_time.dwHighDateTime; + kernel.LowPart = kernel_time.dwLowDateTime; + user.HighPart = user_time.dwHighDateTime; + user.LowPart = user_time.dwLowDateTime; + return (static_cast(kernel.QuadPart) + + static_cast(user.QuadPart)) * + 1e-7; +} +#elif !defined(BENCHMARK_OS_FUCHSIA) +double MakeTime(struct rusage const& ru) { + return (static_cast(ru.ru_utime.tv_sec) + + static_cast(ru.ru_utime.tv_usec) * 1e-6 + + static_cast(ru.ru_stime.tv_sec) + + static_cast(ru.ru_stime.tv_usec) * 1e-6); +} +#endif +#if defined(BENCHMARK_OS_MACOSX) +double MakeTime(thread_basic_info_data_t const& info) { + return (static_cast(info.user_time.seconds) + + static_cast(info.user_time.microseconds) * 1e-6 + + static_cast(info.system_time.seconds) + + static_cast(info.system_time.microseconds) * 1e-6); +} +#endif +#if defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_THREAD_CPUTIME_ID) +double MakeTime(struct timespec const& ts) { + return ts.tv_sec + (static_cast(ts.tv_nsec) * 1e-9); +} +#endif + +BENCHMARK_NORETURN static void DiagnoseAndExit(const char* msg) { + std::cerr << "ERROR: " << msg << std::endl; + std::exit(EXIT_FAILURE); +} + +} // end namespace + +double ProcessCPUUsage() { +#if defined(BENCHMARK_OS_WINDOWS) + HANDLE proc = GetCurrentProcess(); + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + if (GetProcessTimes(proc, &creation_time, &exit_time, &kernel_time, + &user_time)) + return MakeTime(kernel_time, user_time); + DiagnoseAndExit("GetProccessTimes() failed"); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) returns 0 on Emscripten. + // Use Emscripten-specific API. Reported CPU time would be exactly the + // same as total time, but this is ok because there aren't long-latency + // syncronous system calls in Emscripten. + return emscripten_get_now() * 1e-3; +#elif defined(CLOCK_PROCESS_CPUTIME_ID) && !defined(BENCHMARK_OS_MACOSX) + // FIXME We want to use clock_gettime, but its not available in MacOS 10.11. + // See https://github.com/google/benchmark/pull/292 + struct timespec spec; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0) + return MakeTime(spec); + DiagnoseAndExit("clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) failed"); +#else + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) == 0) return MakeTime(ru); + DiagnoseAndExit("getrusage(RUSAGE_SELF, ...) failed"); +#endif +} + +double ThreadCPUUsage() { +#if defined(BENCHMARK_OS_WINDOWS) + HANDLE this_thread = GetCurrentThread(); + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + GetThreadTimes(this_thread, &creation_time, &exit_time, &kernel_time, + &user_time); + return MakeTime(kernel_time, user_time); +#elif defined(BENCHMARK_OS_MACOSX) + // FIXME We want to use clock_gettime, but its not available in MacOS 10.11. + // See https://github.com/google/benchmark/pull/292 + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t info; + mach_port_t thread = pthread_mach_thread_np(pthread_self()); + if (thread_info(thread, THREAD_BASIC_INFO, + reinterpret_cast(&info), + &count) == KERN_SUCCESS) { + return MakeTime(info); + } + DiagnoseAndExit("ThreadCPUUsage() failed when evaluating thread_info"); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // Emscripten doesn't support traditional threads + return ProcessCPUUsage(); +#elif defined(BENCHMARK_OS_RTEMS) + // RTEMS doesn't support CLOCK_THREAD_CPUTIME_ID. See + // https://github.com/RTEMS/rtems/blob/master/cpukit/posix/src/clockgettime.c + return ProcessCPUUsage(); +#elif defined(BENCHMARK_OS_SOLARIS) + struct rusage ru; + if (getrusage(RUSAGE_LWP, &ru) == 0) return MakeTime(ru); + DiagnoseAndExit("getrusage(RUSAGE_LWP, ...) failed"); +#elif defined(CLOCK_THREAD_CPUTIME_ID) + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) return MakeTime(ts); + DiagnoseAndExit("clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) failed"); +#else +#error Per-thread timing is not available on your system. +#endif +} + +std::string LocalDateTimeString() { + // Write the local time in RFC3339 format yyyy-mm-ddTHH:MM:SS+/-HH:MM. + typedef std::chrono::system_clock Clock; + std::time_t now = Clock::to_time_t(Clock::now()); + const std::size_t kTzOffsetLen = 6; + const std::size_t kTimestampLen = 19; + + std::size_t tz_len; + std::size_t timestamp_len; + long int offset_minutes; + char tz_offset_sign = '+'; + // tz_offset is set in one of three ways: + // * strftime with %z - This either returns empty or the ISO 8601 time. The + // maximum length an + // ISO 8601 string can be is 7 (e.g. -03:30, plus trailing zero). + // * snprintf with %c%02li:%02li - The maximum length is 41 (one for %c, up to + // 19 for %02li, + // one for :, up to 19 %02li, plus trailing zero). + // * A fixed string of "-00:00". The maximum length is 7 (-00:00, plus + // trailing zero). + // + // Thus, the maximum size this needs to be is 41. + char tz_offset[41]; + // Long enough buffer to avoid format-overflow warnings + char storage[128]; + +#if defined(BENCHMARK_OS_WINDOWS) + std::tm* timeinfo_p = ::localtime(&now); +#else + std::tm timeinfo; + std::tm* timeinfo_p = &timeinfo; + ::localtime_r(&now, &timeinfo); +#endif + + tz_len = std::strftime(tz_offset, sizeof(tz_offset), "%z", timeinfo_p); + + if (tz_len < kTzOffsetLen && tz_len > 1) { + // Timezone offset was written. strftime writes offset as +HHMM or -HHMM, + // RFC3339 specifies an offset as +HH:MM or -HH:MM. To convert, we parse + // the offset as an integer, then reprint it to a string. + + offset_minutes = ::strtol(tz_offset, NULL, 10); + if (offset_minutes < 0) { + offset_minutes *= -1; + tz_offset_sign = '-'; + } + + tz_len = + ::snprintf(tz_offset, sizeof(tz_offset), "%c%02li:%02li", + tz_offset_sign, offset_minutes / 100, offset_minutes % 100); + BM_CHECK(tz_len == kTzOffsetLen); + ((void)tz_len); // Prevent unused variable warning in optimized build. + } else { + // Unknown offset. RFC3339 specifies that unknown local offsets should be + // written as UTC time with -00:00 timezone. +#if defined(BENCHMARK_OS_WINDOWS) + // Potential race condition if another thread calls localtime or gmtime. + timeinfo_p = ::gmtime(&now); +#else + ::gmtime_r(&now, &timeinfo); +#endif + + strncpy(tz_offset, "-00:00", kTzOffsetLen + 1); + } + + timestamp_len = + std::strftime(storage, sizeof(storage), "%Y-%m-%dT%H:%M:%S", timeinfo_p); + BM_CHECK(timestamp_len == kTimestampLen); + // Prevent unused variable warning in optimized build. + ((void)kTimestampLen); + + std::strncat(storage, tz_offset, sizeof(storage) - timestamp_len - 1); + return std::string(storage); +} + +} // end namespace benchmark diff --git a/bridge/third_party/benchmark/src/timers.h b/bridge/third_party/benchmark/src/timers.h new file mode 100644 index 0000000000..65606ccd93 --- /dev/null +++ b/bridge/third_party/benchmark/src/timers.h @@ -0,0 +1,48 @@ +#ifndef BENCHMARK_TIMERS_H +#define BENCHMARK_TIMERS_H + +#include +#include + +namespace benchmark { + +// Return the CPU usage of the current process +double ProcessCPUUsage(); + +// Return the CPU usage of the children of the current process +double ChildrenCPUUsage(); + +// Return the CPU usage of the current thread +double ThreadCPUUsage(); + +#if defined(HAVE_STEADY_CLOCK) +template +struct ChooseSteadyClock { + typedef std::chrono::high_resolution_clock type; +}; + +template <> +struct ChooseSteadyClock { + typedef std::chrono::steady_clock type; +}; +#endif + +struct ChooseClockType { +#if defined(HAVE_STEADY_CLOCK) + typedef ChooseSteadyClock<>::type type; +#else + typedef std::chrono::high_resolution_clock type; +#endif +}; + +inline double ChronoClockNow() { + typedef ChooseClockType::type ClockType; + using FpSeconds = std::chrono::duration; + return FpSeconds(ClockType::now().time_since_epoch()).count(); +} + +std::string LocalDateTimeString(); + +} // end namespace benchmark + +#endif // BENCHMARK_TIMERS_H diff --git a/bridge/third_party/benchmark/test/AssemblyTests.cmake b/bridge/third_party/benchmark/test/AssemblyTests.cmake new file mode 100644 index 0000000000..3d078586f1 --- /dev/null +++ b/bridge/third_party/benchmark/test/AssemblyTests.cmake @@ -0,0 +1,46 @@ + +include(split_list) + +set(ASM_TEST_FLAGS "") +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +if (BENCHMARK_HAS_O3_FLAG) + list(APPEND ASM_TEST_FLAGS -O3) +endif() + +check_cxx_compiler_flag(-g0 BENCHMARK_HAS_G0_FLAG) +if (BENCHMARK_HAS_G0_FLAG) + list(APPEND ASM_TEST_FLAGS -g0) +endif() + +check_cxx_compiler_flag(-fno-stack-protector BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG) +if (BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG) + list(APPEND ASM_TEST_FLAGS -fno-stack-protector) +endif() + +split_list(ASM_TEST_FLAGS) +string(TOUPPER "${CMAKE_CXX_COMPILER_ID}" ASM_TEST_COMPILER) + +macro(add_filecheck_test name) + cmake_parse_arguments(ARG "" "" "CHECK_PREFIXES" ${ARGV}) + add_library(${name} OBJECT ${name}.cc) + set_target_properties(${name} PROPERTIES COMPILE_FLAGS "-S ${ASM_TEST_FLAGS}") + set(ASM_OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${name}.s") + add_custom_target(copy_${name} ALL + COMMAND ${PROJECT_SOURCE_DIR}/tools/strip_asm.py + $ + ${ASM_OUTPUT_FILE} + BYPRODUCTS ${ASM_OUTPUT_FILE}) + add_dependencies(copy_${name} ${name}) + if (NOT ARG_CHECK_PREFIXES) + set(ARG_CHECK_PREFIXES "CHECK") + endif() + foreach(prefix ${ARG_CHECK_PREFIXES}) + add_test(NAME run_${name}_${prefix} + COMMAND + ${LLVM_FILECHECK_EXE} ${name}.cc + --input-file=${ASM_OUTPUT_FILE} + --check-prefixes=CHECK,CHECK-${ASM_TEST_COMPILER} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endforeach() +endmacro() + diff --git a/bridge/third_party/benchmark/test/CMakeLists.txt b/bridge/third_party/benchmark/test/CMakeLists.txt new file mode 100644 index 0000000000..162af53f80 --- /dev/null +++ b/bridge/third_party/benchmark/test/CMakeLists.txt @@ -0,0 +1,277 @@ +# Enable the tests + +find_package(Threads REQUIRED) +include(CheckCXXCompilerFlag) + +# NOTE: Some tests use `` to perform the test. Therefore we must +# strip -DNDEBUG from the default CMake flags in DEBUG mode. +string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) +if( NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG" ) + add_definitions( -UNDEBUG ) + add_definitions(-DTEST_BENCHMARK_LIBRARY_HAS_NO_ASSERTIONS) + # Also remove /D NDEBUG to avoid MSVC warnings about conflicting defines. + foreach (flags_var_to_scrub + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS_MINSIZEREL) + string (REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " + "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") + endforeach() +endif() + +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +set(BENCHMARK_O3_FLAG "") +if (BENCHMARK_HAS_O3_FLAG) + set(BENCHMARK_O3_FLAG "-O3") +endif() + +# NOTE: These flags must be added after find_package(Threads REQUIRED) otherwise +# they will break the configuration check. +if (DEFINED BENCHMARK_CXX_LINKER_FLAGS) + list(APPEND CMAKE_EXE_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) +endif() + +add_library(output_test_helper STATIC output_test_helper.cc output_test.h) + +macro(compile_benchmark_test name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT}) +endmacro(compile_benchmark_test) + +macro(compile_benchmark_test_with_main name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark::benchmark_main) +endmacro(compile_benchmark_test_with_main) + +macro(compile_output_test name) + add_executable(${name} "${name}.cc" output_test.h) + target_link_libraries(${name} output_test_helper benchmark::benchmark + ${BENCHMARK_CXX_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endmacro(compile_output_test) + +# Demonstration executable +compile_benchmark_test(benchmark_test) +add_test(NAME benchmark COMMAND benchmark_test --benchmark_min_time=0.01) + +compile_benchmark_test(spec_arg_test) +add_test(NAME spec_arg COMMAND spec_arg_test --benchmark_filter=BM_NotChosen) + +compile_benchmark_test(benchmark_setup_teardown_test) +add_test(NAME benchmark_setup_teardown COMMAND benchmark_setup_teardown_test) + +compile_benchmark_test(filter_test) +macro(add_filter_test name filter expect) + add_test(NAME ${name} COMMAND filter_test --benchmark_min_time=0.01 --benchmark_filter=${filter} ${expect}) + add_test(NAME ${name}_list_only COMMAND filter_test --benchmark_list_tests --benchmark_filter=${filter} ${expect}) +endmacro(add_filter_test) + +add_filter_test(filter_simple "Foo" 3) +add_filter_test(filter_simple_negative "-Foo" 2) +add_filter_test(filter_suffix "BM_.*" 4) +add_filter_test(filter_suffix_negative "-BM_.*" 1) +add_filter_test(filter_regex_all ".*" 5) +add_filter_test(filter_regex_all_negative "-.*" 0) +add_filter_test(filter_regex_blank "" 5) +add_filter_test(filter_regex_blank_negative "-" 0) +add_filter_test(filter_regex_none "monkey" 0) +add_filter_test(filter_regex_none_negative "-monkey" 5) +add_filter_test(filter_regex_wildcard ".*Foo.*" 3) +add_filter_test(filter_regex_wildcard_negative "-.*Foo.*" 2) +add_filter_test(filter_regex_begin "^BM_.*" 4) +add_filter_test(filter_regex_begin_negative "-^BM_.*" 1) +add_filter_test(filter_regex_begin2 "^N" 1) +add_filter_test(filter_regex_begin2_negative "-^N" 4) +add_filter_test(filter_regex_end ".*Ba$" 1) +add_filter_test(filter_regex_end_negative "-.*Ba$" 4) + +compile_benchmark_test(options_test) +add_test(NAME options_benchmarks COMMAND options_test --benchmark_min_time=0.01) + +compile_benchmark_test(basic_test) +add_test(NAME basic_benchmark COMMAND basic_test --benchmark_min_time=0.01) + +compile_output_test(repetitions_test) +add_test(NAME repetitions_benchmark COMMAND repetitions_test --benchmark_min_time=0.01 --benchmark_repetitions=3) + +compile_benchmark_test(diagnostics_test) +add_test(NAME diagnostics_test COMMAND diagnostics_test --benchmark_min_time=0.01) + +compile_benchmark_test(skip_with_error_test) +add_test(NAME skip_with_error_test COMMAND skip_with_error_test --benchmark_min_time=0.01) + +compile_benchmark_test(donotoptimize_test) +# Some of the issues with DoNotOptimize only occur when optimization is enabled +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +if (BENCHMARK_HAS_O3_FLAG) + set_target_properties(donotoptimize_test PROPERTIES COMPILE_FLAGS "-O3") +endif() +add_test(NAME donotoptimize_test COMMAND donotoptimize_test --benchmark_min_time=0.01) + +compile_benchmark_test(fixture_test) +add_test(NAME fixture_test COMMAND fixture_test --benchmark_min_time=0.01) + +compile_benchmark_test(register_benchmark_test) +add_test(NAME register_benchmark_test COMMAND register_benchmark_test --benchmark_min_time=0.01) + +compile_benchmark_test(map_test) +add_test(NAME map_test COMMAND map_test --benchmark_min_time=0.01) + +compile_benchmark_test(multiple_ranges_test) +add_test(NAME multiple_ranges_test COMMAND multiple_ranges_test --benchmark_min_time=0.01) + +compile_benchmark_test(args_product_test) +add_test(NAME args_product_test COMMAND args_product_test --benchmark_min_time=0.01) + +compile_benchmark_test_with_main(link_main_test) +add_test(NAME link_main_test COMMAND link_main_test --benchmark_min_time=0.01) + +compile_output_test(reporter_output_test) +add_test(NAME reporter_output_test COMMAND reporter_output_test --benchmark_min_time=0.01) + +compile_output_test(templated_fixture_test) +add_test(NAME templated_fixture_test COMMAND templated_fixture_test --benchmark_min_time=0.01) + +compile_output_test(user_counters_test) +add_test(NAME user_counters_test COMMAND user_counters_test --benchmark_min_time=0.01) + +compile_output_test(perf_counters_test) +add_test(NAME perf_counters_test COMMAND perf_counters_test --benchmark_min_time=0.01 --benchmark_perf_counters=CYCLES,BRANCHES) + +compile_output_test(internal_threading_test) +add_test(NAME internal_threading_test COMMAND internal_threading_test --benchmark_min_time=0.01) + +compile_output_test(report_aggregates_only_test) +add_test(NAME report_aggregates_only_test COMMAND report_aggregates_only_test --benchmark_min_time=0.01) + +compile_output_test(display_aggregates_only_test) +add_test(NAME display_aggregates_only_test COMMAND display_aggregates_only_test --benchmark_min_time=0.01) + +compile_output_test(user_counters_tabular_test) +add_test(NAME user_counters_tabular_test COMMAND user_counters_tabular_test --benchmark_counters_tabular=true --benchmark_min_time=0.01) + +compile_output_test(user_counters_thousands_test) +add_test(NAME user_counters_thousands_test COMMAND user_counters_thousands_test --benchmark_min_time=0.01) + +compile_output_test(memory_manager_test) +add_test(NAME memory_manager_test COMMAND memory_manager_test --benchmark_min_time=0.01) + +check_cxx_compiler_flag(-std=c++03 BENCHMARK_HAS_CXX03_FLAG) +if (BENCHMARK_HAS_CXX03_FLAG) + compile_benchmark_test(cxx03_test) + set_target_properties(cxx03_test + PROPERTIES + CXX_STANDARD 98 + CXX_STANDARD_REQUIRED YES) + # libstdc++ provides different definitions within between dialects. When + # LTO is enabled and -Werror is specified GCC diagnoses this ODR violation + # causing the test to fail to compile. To prevent this we explicitly disable + # the warning. + check_cxx_compiler_flag(-Wno-odr BENCHMARK_HAS_WNO_ODR) + if (BENCHMARK_ENABLE_LTO AND BENCHMARK_HAS_WNO_ODR) + set_target_properties(cxx03_test + PROPERTIES + LINK_FLAGS "-Wno-odr") + endif() + add_test(NAME cxx03 COMMAND cxx03_test --benchmark_min_time=0.01) +endif() + +# Attempt to work around flaky test failures when running on Appveyor servers. +if (DEFINED ENV{APPVEYOR}) + set(COMPLEXITY_MIN_TIME "0.5") +else() + set(COMPLEXITY_MIN_TIME "0.01") +endif() +compile_output_test(complexity_test) +add_test(NAME complexity_benchmark COMMAND complexity_test --benchmark_min_time=${COMPLEXITY_MIN_TIME}) + +############################################################################### +# GoogleTest Unit Tests +############################################################################### + +if (BENCHMARK_ENABLE_GTEST_TESTS) + macro(compile_gtest name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark::benchmark + gmock_main ${CMAKE_THREAD_LIBS_INIT}) + endmacro(compile_gtest) + + macro(add_gtest name) + compile_gtest(${name}) + add_test(NAME ${name} COMMAND ${name}) + endmacro() + + add_gtest(benchmark_gtest) + add_gtest(benchmark_name_gtest) + add_gtest(benchmark_random_interleaving_gtest) + add_gtest(commandlineflags_gtest) + add_gtest(statistics_gtest) + add_gtest(string_util_gtest) + add_gtest(perf_counters_gtest) +endif(BENCHMARK_ENABLE_GTEST_TESTS) + +############################################################################### +# Assembly Unit Tests +############################################################################### + +if (BENCHMARK_ENABLE_ASSEMBLY_TESTS) + if (NOT LLVM_FILECHECK_EXE) + message(FATAL_ERROR "LLVM FileCheck is required when including this file") + endif() + include(AssemblyTests.cmake) + add_filecheck_test(donotoptimize_assembly_test) + add_filecheck_test(state_assembly_test) + add_filecheck_test(clobber_memory_assembly_test) +endif() + + + +############################################################################### +# Code Coverage Configuration +############################################################################### + +# Add the coverage command(s) +if(CMAKE_BUILD_TYPE) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER) +endif() +if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage") + find_program(GCOV gcov) + find_program(LCOV lcov) + find_program(GENHTML genhtml) + find_program(CTEST ctest) + if (GCOV AND LCOV AND GENHTML AND CTEST AND HAVE_CXX_FLAG_COVERAGE) + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/lcov/index.html + COMMAND ${LCOV} -q -z -d . + COMMAND ${LCOV} -q --no-external -c -b "${CMAKE_SOURCE_DIR}" -d . -o before.lcov -i + COMMAND ${CTEST} --force-new-ctest-process + COMMAND ${LCOV} -q --no-external -c -b "${CMAKE_SOURCE_DIR}" -d . -o after.lcov + COMMAND ${LCOV} -q -a before.lcov -a after.lcov --output-file final.lcov + COMMAND ${LCOV} -q -r final.lcov "'${CMAKE_SOURCE_DIR}/test/*'" -o final.lcov + COMMAND ${GENHTML} final.lcov -o lcov --demangle-cpp --sort -p "${CMAKE_BINARY_DIR}" -t benchmark + DEPENDS filter_test benchmark_test options_test basic_test fixture_test cxx03_test complexity_test + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running LCOV" + ) + add_custom_target(coverage + DEPENDS ${CMAKE_BINARY_DIR}/lcov/index.html + COMMENT "LCOV report at lcov/index.html" + ) + message(STATUS "Coverage command added") + else() + if (HAVE_CXX_FLAG_COVERAGE) + set(CXX_FLAG_COVERAGE_MESSAGE supported) + else() + set(CXX_FLAG_COVERAGE_MESSAGE unavailable) + endif() + message(WARNING + "Coverage not available:\n" + " gcov: ${GCOV}\n" + " lcov: ${LCOV}\n" + " genhtml: ${GENHTML}\n" + " ctest: ${CTEST}\n" + " --coverage flag: ${CXX_FLAG_COVERAGE_MESSAGE}") + endif() +endif() diff --git a/bridge/third_party/benchmark/test/args_product_test.cc b/bridge/third_party/benchmark/test/args_product_test.cc new file mode 100644 index 0000000000..d44f391f74 --- /dev/null +++ b/bridge/third_party/benchmark/test/args_product_test.cc @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +class ArgsProductFixture : public ::benchmark::Fixture { + public: + ArgsProductFixture() + : expectedValues({{0, 100, 2000, 30000}, + {1, 15, 3, 8}, + {1, 15, 3, 9}, + {1, 15, 7, 8}, + {1, 15, 7, 9}, + {1, 15, 10, 8}, + {1, 15, 10, 9}, + {2, 15, 3, 8}, + {2, 15, 3, 9}, + {2, 15, 7, 8}, + {2, 15, 7, 9}, + {2, 15, 10, 8}, + {2, 15, 10, 9}, + {4, 5, 6, 11}}) {} + + void SetUp(const ::benchmark::State& state) BENCHMARK_OVERRIDE { + std::vector ranges = {state.range(0), state.range(1), + state.range(2), state.range(3)}; + + assert(expectedValues.find(ranges) != expectedValues.end()); + + actualValues.insert(ranges); + } + + // NOTE: This is not TearDown as we want to check after _all_ runs are + // complete. + virtual ~ArgsProductFixture() { + if (actualValues != expectedValues) { + std::cout << "EXPECTED\n"; + for (const auto& v : expectedValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + std::cout << "ACTUAL\n"; + for (const auto& v : actualValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + } + } + + std::set> expectedValues; + std::set> actualValues; +}; + +BENCHMARK_DEFINE_F(ArgsProductFixture, Empty)(benchmark::State& state) { + for (auto _ : state) { + int64_t product = + state.range(0) * state.range(1) * state.range(2) * state.range(3); + for (int64_t x = 0; x < product; x++) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK_REGISTER_F(ArgsProductFixture, Empty) + ->Args({0, 100, 2000, 30000}) + ->ArgsProduct({{1, 2}, {15}, {3, 7, 10}, {8, 9}}) + ->Args({4, 5, 6, 11}); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/basic_test.cc b/bridge/third_party/benchmark/test/basic_test.cc new file mode 100644 index 0000000000..3a8fd42a8c --- /dev/null +++ b/bridge/third_party/benchmark/test/basic_test.cc @@ -0,0 +1,179 @@ + +#include "benchmark/benchmark.h" + +#define BASIC_BENCHMARK_TEST(x) BENCHMARK(x)->Arg(8)->Arg(512)->Arg(8192) + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); +BENCHMARK(BM_empty)->ThreadPerCpu(); + +void BM_spin_empty(benchmark::State& state) { + for (auto _ : state) { + for (auto x = 0; x < state.range(0); ++x) { + benchmark::DoNotOptimize(x); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_empty); +BASIC_BENCHMARK_TEST(BM_spin_empty)->ThreadPerCpu(); + +void BM_spin_pause_before(benchmark::State& state) { + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + for (auto _ : state) { + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_before); +BASIC_BENCHMARK_TEST(BM_spin_pause_before)->ThreadPerCpu(); + +void BM_spin_pause_during(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + state.ResumeTiming(); + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_during); +BASIC_BENCHMARK_TEST(BM_spin_pause_during)->ThreadPerCpu(); + +void BM_pause_during(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + state.ResumeTiming(); + } +} +BENCHMARK(BM_pause_during); +BENCHMARK(BM_pause_during)->ThreadPerCpu(); +BENCHMARK(BM_pause_during)->UseRealTime(); +BENCHMARK(BM_pause_during)->UseRealTime()->ThreadPerCpu(); + +void BM_spin_pause_after(benchmark::State& state) { + for (auto _ : state) { + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_after); +BASIC_BENCHMARK_TEST(BM_spin_pause_after)->ThreadPerCpu(); + +void BM_spin_pause_before_and_after(benchmark::State& state) { + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + for (auto _ : state) { + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } + for (auto i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_before_and_after); +BASIC_BENCHMARK_TEST(BM_spin_pause_before_and_after)->ThreadPerCpu(); + +void BM_empty_stop_start(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_empty_stop_start); +BENCHMARK(BM_empty_stop_start)->ThreadPerCpu(); + +void BM_KeepRunning(benchmark::State& state) { + benchmark::IterationCount iter_count = 0; + assert(iter_count == state.iterations()); + while (state.KeepRunning()) { + ++iter_count; + } + assert(iter_count == state.iterations()); +} +BENCHMARK(BM_KeepRunning); + +void BM_KeepRunningBatch(benchmark::State& state) { + // Choose a batch size >1000 to skip the typical runs with iteration + // targets of 10, 100 and 1000. If these are not actually skipped the + // bug would be detectable as consecutive runs with the same iteration + // count. Below we assert that this does not happen. + const benchmark::IterationCount batch_size = 1009; + + static benchmark::IterationCount prior_iter_count = 0; + benchmark::IterationCount iter_count = 0; + while (state.KeepRunningBatch(batch_size)) { + iter_count += batch_size; + } + assert(state.iterations() == iter_count); + + // Verify that the iteration count always increases across runs (see + // comment above). + assert(iter_count == batch_size // max_iterations == 1 + || iter_count > prior_iter_count); // max_iterations > batch_size + prior_iter_count = iter_count; +} +// Register with a fixed repetition count to establish the invariant that +// the iteration count should always change across runs. This overrides +// the --benchmark_repetitions command line flag, which would otherwise +// cause this test to fail if set > 1. +BENCHMARK(BM_KeepRunningBatch)->Repetitions(1); + +void BM_RangedFor(benchmark::State& state) { + benchmark::IterationCount iter_count = 0; + for (auto _ : state) { + ++iter_count; + } + assert(iter_count == state.max_iterations); +} +BENCHMARK(BM_RangedFor); + +#ifdef BENCHMARK_HAS_CXX11 +template +void BM_OneTemplateFunc(benchmark::State& state) { + auto arg = state.range(0); + T sum = 0; + for (auto _ : state) { + sum += arg; + } +} +BENCHMARK(BM_OneTemplateFunc)->Arg(1); +BENCHMARK(BM_OneTemplateFunc)->Arg(1); + +template +void BM_TwoTemplateFunc(benchmark::State& state) { + auto arg = state.range(0); + A sum = 0; + B prod = 1; + for (auto _ : state) { + sum += arg; + prod *= arg; + } +} +BENCHMARK(BM_TwoTemplateFunc)->Arg(1); +BENCHMARK(BM_TwoTemplateFunc)->Arg(1); + +#endif // BENCHMARK_HAS_CXX11 + +// Ensure that StateIterator provides all the necessary typedefs required to +// instantiate std::iterator_traits. +static_assert( + std::is_same::value_type, + typename benchmark::State::StateIterator::value_type>::value, + ""); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/benchmark_gtest.cc b/bridge/third_party/benchmark/test/benchmark_gtest.cc new file mode 100644 index 0000000000..14a885ba46 --- /dev/null +++ b/bridge/third_party/benchmark/test/benchmark_gtest.cc @@ -0,0 +1,165 @@ +#include +#include +#include + +#include "../src/benchmark_register.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace benchmark { +namespace internal { +extern std::map* global_context; + +namespace { + +TEST(AddRangeTest, Simple) { + std::vector dst; + AddRange(&dst, 1, 2, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2)); +} + +TEST(AddRangeTest, Simple64) { + std::vector dst; + AddRange(&dst, static_cast(1), static_cast(2), 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2)); +} + +TEST(AddRangeTest, Advanced) { + std::vector dst; + AddRange(&dst, 5, 15, 2); + EXPECT_THAT(dst, testing::ElementsAre(5, 8, 15)); +} + +TEST(AddRangeTest, Advanced64) { + std::vector dst; + AddRange(&dst, static_cast(5), static_cast(15), 2); + EXPECT_THAT(dst, testing::ElementsAre(5, 8, 15)); +} + +TEST(AddRangeTest, FullRange8) { + std::vector dst; + AddRange(&dst, int8_t{1}, std::numeric_limits::max(), 8); + EXPECT_THAT(dst, testing::ElementsAre(1, 8, 64, 127)); +} + +TEST(AddRangeTest, FullRange64) { + std::vector dst; + AddRange(&dst, int64_t{1}, std::numeric_limits::max(), 1024); + EXPECT_THAT( + dst, testing::ElementsAre(1LL, 1024LL, 1048576LL, 1073741824LL, + 1099511627776LL, 1125899906842624LL, + 1152921504606846976LL, 9223372036854775807LL)); +} + +TEST(AddRangeTest, NegativeRanges) { + std::vector dst; + AddRange(&dst, -8, 0, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1, 0)); +} + +TEST(AddRangeTest, StrictlyNegative) { + std::vector dst; + AddRange(&dst, -8, -1, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1)); +} + +TEST(AddRangeTest, SymmetricNegativeRanges) { + std::vector dst; + AddRange(&dst, -8, 8, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1, 0, 1, 2, 4, 8)); +} + +TEST(AddRangeTest, SymmetricNegativeRangesOddMult) { + std::vector dst; + AddRange(&dst, -30, 32, 5); + EXPECT_THAT(dst, testing::ElementsAre(-30, -25, -5, -1, 0, 1, 5, 25, 32)); +} + +TEST(AddRangeTest, NegativeRangesAsymmetric) { + std::vector dst; + AddRange(&dst, -3, 5, 2); + EXPECT_THAT(dst, testing::ElementsAre(-3, -2, -1, 0, 1, 2, 4, 5)); +} + +TEST(AddRangeTest, NegativeRangesLargeStep) { + // Always include -1, 0, 1 when crossing zero. + std::vector dst; + AddRange(&dst, -8, 8, 10); + EXPECT_THAT(dst, testing::ElementsAre(-8, -1, 0, 1, 8)); +} + +TEST(AddRangeTest, ZeroOnlyRange) { + std::vector dst; + AddRange(&dst, 0, 0, 2); + EXPECT_THAT(dst, testing::ElementsAre(0)); +} + +TEST(AddRangeTest, ZeroStartingRange) { + std::vector dst; + AddRange(&dst, 0, 2, 2); + EXPECT_THAT(dst, testing::ElementsAre(0, 1, 2)); +} + +TEST(AddRangeTest, NegativeRange64) { + std::vector dst; + AddRange(&dst, -4, 4, 2); + EXPECT_THAT(dst, testing::ElementsAre(-4, -2, -1, 0, 1, 2, 4)); +} + +TEST(AddRangeTest, NegativeRangePreservesExistingOrder) { + // If elements already exist in the range, ensure we don't change + // their ordering by adding negative values. + std::vector dst = {1, 2, 3}; + AddRange(&dst, -2, 2, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2, 3, -2, -1, 0, 1, 2)); +} + +TEST(AddRangeTest, FullNegativeRange64) { + std::vector dst; + const auto min = std::numeric_limits::min(); + const auto max = std::numeric_limits::max(); + AddRange(&dst, min, max, 1024); + EXPECT_THAT( + dst, testing::ElementsAreArray(std::vector{ + min, -1152921504606846976LL, -1125899906842624LL, + -1099511627776LL, -1073741824LL, -1048576LL, -1024LL, -1LL, 0LL, + 1LL, 1024LL, 1048576LL, 1073741824LL, 1099511627776LL, + 1125899906842624LL, 1152921504606846976LL, max})); +} + +TEST(AddRangeTest, Simple8) { + std::vector dst; + AddRange(&dst, 1, 8, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2, 4, 8)); +} + +TEST(AddCustomContext, Simple) { + EXPECT_THAT(global_context, nullptr); + + AddCustomContext("foo", "bar"); + AddCustomContext("baz", "qux"); + + EXPECT_THAT(*global_context, + testing::UnorderedElementsAre(testing::Pair("foo", "bar"), + testing::Pair("baz", "qux"))); + + delete global_context; + global_context = nullptr; +} + +TEST(AddCustomContext, DuplicateKey) { + EXPECT_THAT(global_context, nullptr); + + AddCustomContext("foo", "bar"); + AddCustomContext("foo", "qux"); + + EXPECT_THAT(*global_context, + testing::UnorderedElementsAre(testing::Pair("foo", "bar"))); + + delete global_context; + global_context = nullptr; +} + +} // namespace +} // namespace internal +} // namespace benchmark diff --git a/bridge/third_party/benchmark/test/benchmark_name_gtest.cc b/bridge/third_party/benchmark/test/benchmark_name_gtest.cc new file mode 100644 index 0000000000..afb401c1f5 --- /dev/null +++ b/bridge/third_party/benchmark/test/benchmark_name_gtest.cc @@ -0,0 +1,74 @@ +#include "benchmark/benchmark.h" +#include "gtest/gtest.h" + +namespace { + +using namespace benchmark; +using namespace benchmark::internal; + +TEST(BenchmarkNameTest, Empty) { + const auto name = BenchmarkName(); + EXPECT_EQ(name.str(), std::string()); +} + +TEST(BenchmarkNameTest, FunctionName) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + EXPECT_EQ(name.str(), "function_name"); +} + +TEST(BenchmarkNameTest, FunctionNameAndArgs) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4/5"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/5"); +} + +TEST(BenchmarkNameTest, MinTime) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4"; + name.min_time = "min_time:3.4s"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/min_time:3.4s"); +} + +TEST(BenchmarkNameTest, Iterations) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.iterations = "iterations:42"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/iterations:42"); +} + +TEST(BenchmarkNameTest, Repetitions) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.repetitions = "repetitions:24"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/repetitions:24"); +} + +TEST(BenchmarkNameTest, TimeType) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.time_type = "hammer_time"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/hammer_time"); +} + +TEST(BenchmarkNameTest, Threads) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.threads = "threads:256"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/threads:256"); +} + +TEST(BenchmarkNameTest, TestEmptyFunctionName) { + auto name = BenchmarkName(); + name.args = "first:3/second:4"; + name.threads = "threads:22"; + EXPECT_EQ(name.str(), "first:3/second:4/threads:22"); +} + +} // end namespace diff --git a/bridge/third_party/benchmark/test/benchmark_random_interleaving_gtest.cc b/bridge/third_party/benchmark/test/benchmark_random_interleaving_gtest.cc new file mode 100644 index 0000000000..d04befa8e3 --- /dev/null +++ b/bridge/third_party/benchmark/test/benchmark_random_interleaving_gtest.cc @@ -0,0 +1,127 @@ +#include +#include +#include + +#include "../src/commandlineflags.h" +#include "../src/string_util.h" +#include "benchmark/benchmark.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace benchmark { + +BM_DECLARE_bool(benchmark_enable_random_interleaving); +BM_DECLARE_string(benchmark_filter); +BM_DECLARE_int32(benchmark_repetitions); + +namespace internal { +namespace { + +class EventQueue : public std::queue { + public: + void Put(const std::string& event) { push(event); } + + void Clear() { + while (!empty()) { + pop(); + } + } + + std::string Get() { + std::string event = front(); + pop(); + return event; + } +}; + +EventQueue* queue = new EventQueue(); + +class NullReporter : public BenchmarkReporter { + public: + bool ReportContext(const Context& /*context*/) override { return true; } + void ReportRuns(const std::vector& /* report */) override {} +}; + +class BenchmarkTest : public testing::Test { + public: + static void SetupHook(int /* num_threads */) { queue->push("Setup"); } + + static void TeardownHook(int /* num_threads */) { queue->push("Teardown"); } + + void Execute(const std::string& pattern) { + queue->Clear(); + + BenchmarkReporter* reporter = new NullReporter; + FLAGS_benchmark_filter = pattern; + RunSpecifiedBenchmarks(reporter); + delete reporter; + + queue->Put("DONE"); // End marker + } +}; + +void BM_Match1(benchmark::State& state) { + const int64_t arg = state.range(0); + + for (auto _ : state) { + } + queue->Put(StrFormat("BM_Match1/%d", static_cast(arg))); +} +BENCHMARK(BM_Match1) + ->Iterations(100) + ->Arg(1) + ->Arg(2) + ->Arg(3) + ->Range(10, 80) + ->Args({90}) + ->Args({100}); + +TEST_F(BenchmarkTest, Match1) { + Execute("BM_Match1"); + ASSERT_EQ("BM_Match1/1", queue->Get()); + ASSERT_EQ("BM_Match1/2", queue->Get()); + ASSERT_EQ("BM_Match1/3", queue->Get()); + ASSERT_EQ("BM_Match1/10", queue->Get()); + ASSERT_EQ("BM_Match1/64", queue->Get()); + ASSERT_EQ("BM_Match1/80", queue->Get()); + ASSERT_EQ("BM_Match1/90", queue->Get()); + ASSERT_EQ("BM_Match1/100", queue->Get()); + ASSERT_EQ("DONE", queue->Get()); +} + +TEST_F(BenchmarkTest, Match1WithRepetition) { + FLAGS_benchmark_repetitions = 2; + + Execute("BM_Match1/(64|80)"); + ASSERT_EQ("BM_Match1/64", queue->Get()); + ASSERT_EQ("BM_Match1/64", queue->Get()); + ASSERT_EQ("BM_Match1/80", queue->Get()); + ASSERT_EQ("BM_Match1/80", queue->Get()); + ASSERT_EQ("DONE", queue->Get()); +} + +TEST_F(BenchmarkTest, Match1WithRandomInterleaving) { + FLAGS_benchmark_enable_random_interleaving = true; + FLAGS_benchmark_repetitions = 100; + + std::map element_count; + std::map interleaving_count; + Execute("BM_Match1/(64|80)"); + for (int i = 0; i < 100; ++i) { + std::vector interleaving; + interleaving.push_back(queue->Get()); + interleaving.push_back(queue->Get()); + element_count[interleaving[0]]++; + element_count[interleaving[1]]++; + interleaving_count[StrFormat("%s,%s", interleaving[0].c_str(), + interleaving[1].c_str())]++; + } + EXPECT_EQ(element_count["BM_Match1/64"], 100) << "Unexpected repetitions."; + EXPECT_EQ(element_count["BM_Match1/80"], 100) << "Unexpected repetitions."; + EXPECT_GE(interleaving_count.size(), 2) << "Interleaving was not randomized."; + ASSERT_EQ("DONE", queue->Get()); +} + +} // namespace +} // namespace internal +} // namespace benchmark diff --git a/bridge/third_party/benchmark/test/benchmark_setup_teardown_test.cc b/bridge/third_party/benchmark/test/benchmark_setup_teardown_test.cc new file mode 100644 index 0000000000..efa34e15c1 --- /dev/null +++ b/bridge/third_party/benchmark/test/benchmark_setup_teardown_test.cc @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +// Test that Setup() and Teardown() are called exactly once +// for each benchmark run (single-threaded). +namespace single { +static int setup_call = 0; +static int teardown_call = 0; +} // namespace single +static void DoSetup1(const benchmark::State& state) { + ++single::setup_call; + + // Setup/Teardown should never be called with any thread_idx != 0. + assert(state.thread_index() == 0); +} + +static void DoTeardown1(const benchmark::State& state) { + ++single::teardown_call; + assert(state.thread_index() == 0); +} + +static void BM_with_setup(benchmark::State& state) { + for (auto s : state) { + } +} +BENCHMARK(BM_with_setup) + ->Arg(1) + ->Arg(3) + ->Arg(5) + ->Arg(7) + ->Iterations(100) + ->Setup(DoSetup1) + ->Teardown(DoTeardown1); + +// Test that Setup() and Teardown() are called once for each group of threads. +namespace concurrent { +static std::atomic setup_call(0); +static std::atomic teardown_call(0); +static std::atomic func_call(0); +} // namespace concurrent + +static void DoSetup2(const benchmark::State& state) { + concurrent::setup_call.fetch_add(1, std::memory_order_acquire); + assert(state.thread_index() == 0); +} + +static void DoTeardown2(const benchmark::State& state) { + concurrent::teardown_call.fetch_add(1, std::memory_order_acquire); + assert(state.thread_index() == 0); +} + +static void BM_concurrent(benchmark::State& state) { + for (auto s : state) { + } + concurrent::func_call.fetch_add(1, std::memory_order_acquire); +} + +BENCHMARK(BM_concurrent) + ->Setup(DoSetup2) + ->Teardown(DoTeardown2) + ->Iterations(100) + ->Threads(5) + ->Threads(10) + ->Threads(15); + +// Testing interaction with Fixture::Setup/Teardown +namespace fixture_interaction { +int setup = 0; +int fixture_setup = 0; +} // namespace fixture_interaction + +#define FIXTURE_BECHMARK_NAME MyFixture + +class FIXTURE_BECHMARK_NAME : public ::benchmark::Fixture { + public: + void SetUp(const ::benchmark::State&) BENCHMARK_OVERRIDE { + fixture_interaction::fixture_setup++; + } + + ~FIXTURE_BECHMARK_NAME() {} +}; + +BENCHMARK_F(FIXTURE_BECHMARK_NAME, BM_WithFixture)(benchmark::State& st) { + for (auto _ : st) { + } +} + +static void DoSetupWithFixture(const benchmark::State&) { + fixture_interaction::setup++; +} + +BENCHMARK_REGISTER_F(FIXTURE_BECHMARK_NAME, BM_WithFixture) + ->Arg(1) + ->Arg(3) + ->Arg(5) + ->Arg(7) + ->Setup(DoSetupWithFixture) + ->Repetitions(1) + ->Iterations(100); + +// Testing repetitions. +namespace repetitions { +int setup = 0; +} + +static void DoSetupWithRepetitions(const benchmark::State&) { + repetitions::setup++; +} +static void BM_WithRep(benchmark::State& state) { + for (auto _ : state) { + } +} + +BENCHMARK(BM_WithRep) + ->Arg(1) + ->Arg(3) + ->Arg(5) + ->Arg(7) + ->Setup(DoSetupWithRepetitions) + ->Iterations(100) + ->Repetitions(4); + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + + size_t ret = benchmark::RunSpecifiedBenchmarks("."); + assert(ret > 0); + + // Setup/Teardown is called once for each arg group (1,3,5,7). + assert(single::setup_call == 4); + assert(single::teardown_call == 4); + + // 3 group of threads calling this function (3,5,10). + assert(concurrent::setup_call.load(std::memory_order_relaxed) == 3); + assert(concurrent::teardown_call.load(std::memory_order_relaxed) == 3); + assert((5 + 10 + 15) == + concurrent::func_call.load(std::memory_order_relaxed)); + + // Setup is called 4 times, once for each arg group (1,3,5,7) + assert(fixture_interaction::setup == 4); + // Fixture::Setup is called everytime the bm routine is run. + // The exact number is indeterministic, so we just assert that + // it's more than setup. + assert(fixture_interaction::fixture_setup > fixture_interaction::setup); + + // Setup is call once for each repetition * num_arg = 4 * 4 = 16. + assert(repetitions::setup == 16); + + return 0; +} diff --git a/bridge/third_party/benchmark/test/benchmark_test.cc b/bridge/third_party/benchmark/test/benchmark_test.cc new file mode 100644 index 0000000000..2906cdcde9 --- /dev/null +++ b/bridge/third_party/benchmark/test/benchmark_test.cc @@ -0,0 +1,247 @@ +#include "benchmark/benchmark.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUC__) +#define BENCHMARK_NOINLINE __attribute__((noinline)) +#else +#define BENCHMARK_NOINLINE +#endif + +namespace { + +int BENCHMARK_NOINLINE Factorial(uint32_t n) { + return (n == 1) ? 1 : n * Factorial(n - 1); +} + +double CalculatePi(int depth) { + double pi = 0.0; + for (int i = 0; i < depth; ++i) { + double numerator = static_cast(((i % 2) * 2) - 1); + double denominator = static_cast((2 * i) - 1); + pi += numerator / denominator; + } + return (pi - 1.0) * 4; +} + +std::set ConstructRandomSet(int64_t size) { + std::set s; + for (int i = 0; i < size; ++i) s.insert(s.end(), i); + return s; +} + +std::mutex test_vector_mu; +std::vector* test_vector = nullptr; + +} // end namespace + +static void BM_Factorial(benchmark::State& state) { + int fac_42 = 0; + for (auto _ : state) fac_42 = Factorial(8); + // Prevent compiler optimizations + std::stringstream ss; + ss << fac_42; + state.SetLabel(ss.str()); +} +BENCHMARK(BM_Factorial); +BENCHMARK(BM_Factorial)->UseRealTime(); + +static void BM_CalculatePiRange(benchmark::State& state) { + double pi = 0.0; + for (auto _ : state) pi = CalculatePi(static_cast(state.range(0))); + std::stringstream ss; + ss << pi; + state.SetLabel(ss.str()); +} +BENCHMARK_RANGE(BM_CalculatePiRange, 1, 1024 * 1024); + +static void BM_CalculatePi(benchmark::State& state) { + static const int depth = 1024; + for (auto _ : state) { + benchmark::DoNotOptimize(CalculatePi(static_cast(depth))); + } +} +BENCHMARK(BM_CalculatePi)->Threads(8); +BENCHMARK(BM_CalculatePi)->ThreadRange(1, 32); +BENCHMARK(BM_CalculatePi)->ThreadPerCpu(); + +static void BM_SetInsert(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) data.insert(rand()); + } + state.SetItemsProcessed(state.iterations() * state.range(1)); + state.SetBytesProcessed(state.iterations() * state.range(1) * sizeof(int)); +} + +// Test many inserts at once to reduce the total iterations needed. Otherwise, +// the slower, non-timed part of each iteration will make the benchmark take +// forever. +BENCHMARK(BM_SetInsert)->Ranges({{1 << 10, 8 << 10}, {128, 512}}); + +template +static void BM_Sequential(benchmark::State& state) { + ValueType v = 42; + for (auto _ : state) { + Container c; + for (int64_t i = state.range(0); --i;) c.push_back(v); + } + const int64_t items_processed = state.iterations() * state.range(0); + state.SetItemsProcessed(items_processed); + state.SetBytesProcessed(items_processed * sizeof(v)); +} +BENCHMARK_TEMPLATE2(BM_Sequential, std::vector, int) + ->Range(1 << 0, 1 << 10); +BENCHMARK_TEMPLATE(BM_Sequential, std::list)->Range(1 << 0, 1 << 10); +// Test the variadic version of BENCHMARK_TEMPLATE in C++11 and beyond. +#ifdef BENCHMARK_HAS_CXX11 +BENCHMARK_TEMPLATE(BM_Sequential, std::vector, int)->Arg(512); +#endif + +static void BM_StringCompare(benchmark::State& state) { + size_t len = static_cast(state.range(0)); + std::string s1(len, '-'); + std::string s2(len, '-'); + for (auto _ : state) benchmark::DoNotOptimize(s1.compare(s2)); +} +BENCHMARK(BM_StringCompare)->Range(1, 1 << 20); + +static void BM_SetupTeardown(benchmark::State& state) { + if (state.thread_index() == 0) { + // No need to lock test_vector_mu here as this is running single-threaded. + test_vector = new std::vector(); + } + int i = 0; + for (auto _ : state) { + std::lock_guard l(test_vector_mu); + if (i % 2 == 0) + test_vector->push_back(i); + else + test_vector->pop_back(); + ++i; + } + if (state.thread_index() == 0) { + delete test_vector; + } +} +BENCHMARK(BM_SetupTeardown)->ThreadPerCpu(); + +static void BM_LongTest(benchmark::State& state) { + double tracker = 0.0; + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + benchmark::DoNotOptimize(tracker += i); + } +} +BENCHMARK(BM_LongTest)->Range(1 << 16, 1 << 28); + +static void BM_ParallelMemset(benchmark::State& state) { + int64_t size = state.range(0) / static_cast(sizeof(int)); + int thread_size = static_cast(size) / state.threads(); + int from = thread_size * state.thread_index(); + int to = from + thread_size; + + if (state.thread_index() == 0) { + test_vector = new std::vector(static_cast(size)); + } + + for (auto _ : state) { + for (int i = from; i < to; i++) { + // No need to lock test_vector_mu as ranges + // do not overlap between threads. + benchmark::DoNotOptimize(test_vector->at(i) = 1); + } + } + + if (state.thread_index() == 0) { + delete test_vector; + } +} +BENCHMARK(BM_ParallelMemset)->Arg(10 << 20)->ThreadRange(1, 4); + +static void BM_ManualTiming(benchmark::State& state) { + int64_t slept_for = 0; + int64_t microseconds = state.range(0); + std::chrono::duration sleep_duration{ + static_cast(microseconds)}; + + for (auto _ : state) { + auto start = std::chrono::high_resolution_clock::now(); + // Simulate some useful workload with a sleep + std::this_thread::sleep_for( + std::chrono::duration_cast(sleep_duration)); + auto end = std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast>(end - start); + + state.SetIterationTime(elapsed.count()); + slept_for += microseconds; + } + state.SetItemsProcessed(slept_for); +} +BENCHMARK(BM_ManualTiming)->Range(1, 1 << 14)->UseRealTime(); +BENCHMARK(BM_ManualTiming)->Range(1, 1 << 14)->UseManualTime(); + +#ifdef BENCHMARK_HAS_CXX11 + +template +void BM_with_args(benchmark::State& state, Args&&...) { + for (auto _ : state) { + } +} +BENCHMARK_CAPTURE(BM_with_args, int_test, 42, 43, 44); +BENCHMARK_CAPTURE(BM_with_args, string_and_pair_test, std::string("abc"), + std::pair(42, 3.8)); + +void BM_non_template_args(benchmark::State& state, int, double) { + while (state.KeepRunning()) { + } +} +BENCHMARK_CAPTURE(BM_non_template_args, basic_test, 0, 0); + +#endif // BENCHMARK_HAS_CXX11 + +static void BM_DenseThreadRanges(benchmark::State& st) { + switch (st.range(0)) { + case 1: + assert(st.threads() == 1 || st.threads() == 2 || st.threads() == 3); + break; + case 2: + assert(st.threads() == 1 || st.threads() == 3 || st.threads() == 4); + break; + case 3: + assert(st.threads() == 5 || st.threads() == 8 || st.threads() == 11 || + st.threads() == 14); + break; + default: + assert(false && "Invalid test case number"); + } + while (st.KeepRunning()) { + } +} +BENCHMARK(BM_DenseThreadRanges)->Arg(1)->DenseThreadRange(1, 3); +BENCHMARK(BM_DenseThreadRanges)->Arg(2)->DenseThreadRange(1, 4, 2); +BENCHMARK(BM_DenseThreadRanges)->Arg(3)->DenseThreadRange(5, 14, 3); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/clobber_memory_assembly_test.cc b/bridge/third_party/benchmark/test/clobber_memory_assembly_test.cc new file mode 100644 index 0000000000..ab269130cd --- /dev/null +++ b/bridge/third_party/benchmark/test/clobber_memory_assembly_test.cc @@ -0,0 +1,63 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +extern "C" { + +extern int ExternInt; +extern int ExternInt2; +extern int ExternInt3; +} + +// CHECK-LABEL: test_basic: +extern "C" void test_basic() { + int x; + benchmark::DoNotOptimize(&x); + x = 101; + benchmark::ClobberMemory(); + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: movl $101, [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_redundant_store: +extern "C" void test_redundant_store() { + ExternInt = 3; + benchmark::ClobberMemory(); + ExternInt = 51; + // CHECK-DAG: ExternInt + // CHECK-DAG: movl $3 + // CHECK: movl $51 +} + +// CHECK-LABEL: test_redundant_read: +extern "C" void test_redundant_read() { + int x; + benchmark::DoNotOptimize(&x); + x = ExternInt; + benchmark::ClobberMemory(); + x = ExternInt2; + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK-NOT: ExternInt2 + // CHECK: ret +} + +// CHECK-LABEL: test_redundant_read2: +extern "C" void test_redundant_read2() { + int x; + benchmark::DoNotOptimize(&x); + x = ExternInt; + benchmark::ClobberMemory(); + x = ExternInt2; + benchmark::ClobberMemory(); + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK: ExternInt2(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK: ret +} diff --git a/bridge/third_party/benchmark/test/commandlineflags_gtest.cc b/bridge/third_party/benchmark/test/commandlineflags_gtest.cc new file mode 100644 index 0000000000..8412008ffe --- /dev/null +++ b/bridge/third_party/benchmark/test/commandlineflags_gtest.cc @@ -0,0 +1,228 @@ +#include + +#include "../src/commandlineflags.h" +#include "../src/internal_macros.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace benchmark { +namespace { + +#if defined(BENCHMARK_OS_WINDOWS) +int setenv(const char* name, const char* value, int overwrite) { + if (!overwrite) { + // NOTE: getenv_s is far superior but not available under mingw. + char* env_value = getenv(name); + if (env_value == nullptr) { + return -1; + } + } + return _putenv_s(name, value); +} + +int unsetenv(const char* name) { return _putenv_s(name, ""); } + +#endif // BENCHMARK_OS_WINDOWS + +TEST(BoolFromEnv, Default) { + ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0); + EXPECT_EQ(BoolFromEnv("not_in_env", true), true); +} + +TEST(BoolFromEnv, False) { + ASSERT_EQ(setenv("IN_ENV", "0", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "N", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "n", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "NO", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "No", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "no", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "F", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "f", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "FALSE", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "False", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "false", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "OFF", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "Off", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "off", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("IN_ENV"); +} + +TEST(BoolFromEnv, True) { + ASSERT_EQ(setenv("IN_ENV", "1", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "Y", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "y", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "YES", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "Yes", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "yes", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "T", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "t", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "TRUE", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "True", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "true", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "ON", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "On", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + + ASSERT_EQ(setenv("IN_ENV", "on", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); + +#ifndef BENCHMARK_OS_WINDOWS + ASSERT_EQ(setenv("IN_ENV", "", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("IN_ENV"); +#endif +} + +TEST(Int32FromEnv, NotInEnv) { + ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0); + EXPECT_EQ(Int32FromEnv("not_in_env", 42), 42); +} + +TEST(Int32FromEnv, InvalidInteger) { + ASSERT_EQ(setenv("IN_ENV", "foo", 1), 0); + EXPECT_EQ(Int32FromEnv("in_env", 42), 42); + unsetenv("IN_ENV"); +} + +TEST(Int32FromEnv, ValidInteger) { + ASSERT_EQ(setenv("IN_ENV", "42", 1), 0); + EXPECT_EQ(Int32FromEnv("in_env", 64), 42); + unsetenv("IN_ENV"); +} + +TEST(DoubleFromEnv, NotInEnv) { + ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0); + EXPECT_EQ(DoubleFromEnv("not_in_env", 0.51), 0.51); +} + +TEST(DoubleFromEnv, InvalidReal) { + ASSERT_EQ(setenv("IN_ENV", "foo", 1), 0); + EXPECT_EQ(DoubleFromEnv("in_env", 0.51), 0.51); + unsetenv("IN_ENV"); +} + +TEST(DoubleFromEnv, ValidReal) { + ASSERT_EQ(setenv("IN_ENV", "0.51", 1), 0); + EXPECT_EQ(DoubleFromEnv("in_env", 0.71), 0.51); + unsetenv("IN_ENV"); +} + +TEST(StringFromEnv, Default) { + ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0); + EXPECT_STREQ(StringFromEnv("not_in_env", "foo"), "foo"); +} + +TEST(StringFromEnv, Valid) { + ASSERT_EQ(setenv("IN_ENV", "foo", 1), 0); + EXPECT_STREQ(StringFromEnv("in_env", "bar"), "foo"); + unsetenv("IN_ENV"); +} + +TEST(KvPairsFromEnv, Default) { + ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0); + EXPECT_THAT(KvPairsFromEnv("not_in_env", {{"foo", "bar"}}), + testing::ElementsAre(testing::Pair("foo", "bar"))); +} + +TEST(KvPairsFromEnv, MalformedReturnsDefault) { + ASSERT_EQ(setenv("IN_ENV", "foo", 1), 0); + EXPECT_THAT(KvPairsFromEnv("in_env", {{"foo", "bar"}}), + testing::ElementsAre(testing::Pair("foo", "bar"))); + unsetenv("IN_ENV"); +} + +TEST(KvPairsFromEnv, Single) { + ASSERT_EQ(setenv("IN_ENV", "foo=bar", 1), 0); + EXPECT_THAT(KvPairsFromEnv("in_env", {}), + testing::ElementsAre(testing::Pair("foo", "bar"))); + unsetenv("IN_ENV"); +} + +TEST(KvPairsFromEnv, Multiple) { + ASSERT_EQ(setenv("IN_ENV", "foo=bar,baz=qux", 1), 0); + EXPECT_THAT(KvPairsFromEnv("in_env", {}), + testing::UnorderedElementsAre(testing::Pair("foo", "bar"), + testing::Pair("baz", "qux"))); + unsetenv("IN_ENV"); +} + +} // namespace +} // namespace benchmark diff --git a/bridge/third_party/benchmark/test/complexity_test.cc b/bridge/third_party/benchmark/test/complexity_test.cc new file mode 100644 index 0000000000..1251cd44f5 --- /dev/null +++ b/bridge/third_party/benchmark/test/complexity_test.cc @@ -0,0 +1,226 @@ +#undef NDEBUG +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +namespace { + +#define ADD_COMPLEXITY_CASES(...) \ + int CONCAT(dummy, __LINE__) = AddComplexityTest(__VA_ARGS__) + +int AddComplexityTest(const std::string &test_name, + const std::string &big_o_test_name, + const std::string &rms_test_name, + const std::string &big_o, int family_index) { + SetSubstitutions({{"%name", test_name}, + {"%bigo_name", big_o_test_name}, + {"%rms_name", rms_test_name}, + {"%bigo_str", "[ ]* %float " + big_o}, + {"%bigo", big_o}, + {"%rms", "[ ]*[0-9]+ %"}}); + AddCases( + TC_ConsoleOut, + {{"^%bigo_name %bigo_str %bigo_str[ ]*$"}, + {"^%bigo_name", MR_Not}, // Assert we we didn't only matched a name. + {"^%rms_name %rms %rms[ ]*$", MR_Next}}); + AddCases( + TC_JSONOut, + {{"\"name\": \"%bigo_name\",$"}, + {"\"family_index\": " + std::to_string(family_index) + ",$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"%name\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": %int,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"BigO\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"cpu_coefficient\": %float,$", MR_Next}, + {"\"real_coefficient\": %float,$", MR_Next}, + {"\"big_o\": \"%bigo\",$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}, + {"\"name\": \"%rms_name\",$"}, + {"\"family_index\": " + std::to_string(family_index) + ",$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"%name\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": %int,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"RMS\",$", MR_Next}, + {"\"aggregate_unit\": \"percentage\",$", MR_Next}, + {"\"rms\": %float$", MR_Next}, + {"}", MR_Next}}); + AddCases(TC_CSVOut, {{"^\"%bigo_name\",,%float,%float,%bigo,,,,,$"}, + {"^\"%bigo_name\"", MR_Not}, + {"^\"%rms_name\",,%float,%float,,,,,,$", MR_Next}}); + return 0; +} + +} // end namespace + +// ========================================================================= // +// --------------------------- Testing BigO O(1) --------------------------- // +// ========================================================================= // + +void BM_Complexity_O1(benchmark::State &state) { + for (auto _ : state) { + for (int i = 0; i < 1024; ++i) { + benchmark::DoNotOptimize(&i); + } + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(benchmark::o1); +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(); +BENCHMARK(BM_Complexity_O1) + ->Range(1, 1 << 18) + ->Complexity([](benchmark::IterationCount) { return 1.0; }); + +const char *one_test_name = "BM_Complexity_O1"; +const char *big_o_1_test_name = "BM_Complexity_O1_BigO"; +const char *rms_o_1_test_name = "BM_Complexity_O1_RMS"; +const char *enum_big_o_1 = "\\([0-9]+\\)"; +// FIXME: Tolerate both '(1)' and 'lgN' as output when the complexity is auto +// deduced. +// See https://github.com/google/benchmark/issues/272 +const char *auto_big_o_1 = "(\\([0-9]+\\))|(lgN)"; +const char *lambda_big_o_1 = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + enum_big_o_1, /*family_index=*/0); + +// Add auto enum tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + auto_big_o_1, /*family_index=*/1); + +// Add lambda tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + lambda_big_o_1, /*family_index=*/2); + +// ========================================================================= // +// --------------------------- Testing BigO O(N) --------------------------- // +// ========================================================================= // + +std::vector ConstructRandomVector(int64_t size) { + std::vector v; + v.reserve(static_cast(size)); + for (int i = 0; i < size; ++i) { + v.push_back(static_cast(std::rand() % size)); + } + return v; +} + +void BM_Complexity_O_N(benchmark::State &state) { + auto v = ConstructRandomVector(state.range(0)); + // Test worst case scenario (item not in vector) + const int64_t item_not_in_vector = state.range(0) * 2; + for (auto _ : state) { + benchmark::DoNotOptimize(std::find(v.begin(), v.end(), item_not_in_vector)); + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(benchmark::oN); +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity([](benchmark::IterationCount n) -> double { + return static_cast(n); + }); +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(); + +const char *n_test_name = "BM_Complexity_O_N"; +const char *big_o_n_test_name = "BM_Complexity_O_N_BigO"; +const char *rms_o_n_test_name = "BM_Complexity_O_N_RMS"; +const char *enum_auto_big_o_n = "N"; +const char *lambda_big_o_n = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name, + enum_auto_big_o_n, /*family_index=*/3); + +// Add lambda tests +ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name, + lambda_big_o_n, /*family_index=*/4); + +// ========================================================================= // +// ------------------------- Testing BigO O(N*lgN) ------------------------- // +// ========================================================================= // + +static void BM_Complexity_O_N_log_N(benchmark::State &state) { + auto v = ConstructRandomVector(state.range(0)); + for (auto _ : state) { + std::sort(v.begin(), v.end()); + } + state.SetComplexityN(state.range(0)); +} +static const double kLog2E = 1.44269504088896340736; +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(benchmark::oNLogN); +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity([](benchmark::IterationCount n) { + return kLog2E * n * log(static_cast(n)); + }); +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(); + +const char *n_lg_n_test_name = "BM_Complexity_O_N_log_N"; +const char *big_o_n_lg_n_test_name = "BM_Complexity_O_N_log_N_BigO"; +const char *rms_o_n_lg_n_test_name = "BM_Complexity_O_N_log_N_RMS"; +const char *enum_auto_big_o_n_lg_n = "NlgN"; +const char *lambda_big_o_n_lg_n = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, + rms_o_n_lg_n_test_name, enum_auto_big_o_n_lg_n, + /*family_index=*/6); + +// Add lambda tests +ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, + rms_o_n_lg_n_test_name, lambda_big_o_n_lg_n, + /*family_index=*/7); + +// ========================================================================= // +// -------- Testing formatting of Complexity with captured args ------------ // +// ========================================================================= // + +void BM_ComplexityCaptureArgs(benchmark::State &state, int n) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + state.SetComplexityN(n); +} + +BENCHMARK_CAPTURE(BM_ComplexityCaptureArgs, capture_test, 100) + ->Complexity(benchmark::oN) + ->Ranges({{1, 2}, {3, 4}}); + +const std::string complexity_capture_name = + "BM_ComplexityCaptureArgs/capture_test"; + +ADD_COMPLEXITY_CASES(complexity_capture_name, complexity_capture_name + "_BigO", + complexity_capture_name + "_RMS", "N", /*family_index=*/9); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char *argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/cxx03_test.cc b/bridge/third_party/benchmark/test/cxx03_test.cc new file mode 100644 index 0000000000..9711c1bd4a --- /dev/null +++ b/bridge/third_party/benchmark/test/cxx03_test.cc @@ -0,0 +1,62 @@ +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" + +#if __cplusplus >= 201103L +#error C++11 or greater detected. Should be C++03. +#endif + +#ifdef BENCHMARK_HAS_CXX11 +#error C++11 or greater detected by the library. BENCHMARK_HAS_CXX11 is defined. +#endif + +void BM_empty(benchmark::State& state) { + while (state.KeepRunning()) { + volatile benchmark::IterationCount x = state.iterations(); + ((void)x); + } +} +BENCHMARK(BM_empty); + +// The new C++11 interface for args/ranges requires initializer list support. +// Therefore we provide the old interface to support C++03. +void BM_old_arg_range_interface(benchmark::State& state) { + assert((state.range(0) == 1 && state.range(1) == 2) || + (state.range(0) == 5 && state.range(1) == 6)); + while (state.KeepRunning()) { + } +} +BENCHMARK(BM_old_arg_range_interface)->ArgPair(1, 2)->RangePair(5, 5, 6, 6); + +template +void BM_template2(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE2(BM_template2, int, long); + +template +void BM_template1(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE(BM_template1, long); +BENCHMARK_TEMPLATE1(BM_template1, int); + +template +struct BM_Fixture : public ::benchmark::Fixture {}; + +BENCHMARK_TEMPLATE_F(BM_Fixture, BM_template1, long)(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE1_F(BM_Fixture, BM_template2, int)(benchmark::State& state) { + BM_empty(state); +} + +void BM_counters(benchmark::State& state) { + BM_empty(state); + state.counters["Foo"] = 2; +} +BENCHMARK(BM_counters); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/diagnostics_test.cc b/bridge/third_party/benchmark/test/diagnostics_test.cc new file mode 100644 index 0000000000..c54d5b0d70 --- /dev/null +++ b/bridge/third_party/benchmark/test/diagnostics_test.cc @@ -0,0 +1,80 @@ +// Testing: +// State::PauseTiming() +// State::ResumeTiming() +// Test that CHECK's within these function diagnose when they are called +// outside of the KeepRunning() loop. +// +// NOTE: Users should NOT include or use src/check.h. This is only done in +// order to test library internals. + +#include +#include + +#include "../src/check.h" +#include "benchmark/benchmark.h" + +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +#define TEST_HAS_NO_EXCEPTIONS +#endif + +void TestHandler() { +#ifndef TEST_HAS_NO_EXCEPTIONS + throw std::logic_error(""); +#else + std::abort(); +#endif +} + +void try_invalid_pause_resume(benchmark::State& state) { +#if !defined(TEST_BENCHMARK_LIBRARY_HAS_NO_ASSERTIONS) && \ + !defined(TEST_HAS_NO_EXCEPTIONS) + try { + state.PauseTiming(); + std::abort(); + } catch (std::logic_error const&) { + } + try { + state.ResumeTiming(); + std::abort(); + } catch (std::logic_error const&) { + } +#else + (void)state; // avoid unused warning +#endif +} + +void BM_diagnostic_test(benchmark::State& state) { + static bool called_once = false; + + if (called_once == false) try_invalid_pause_resume(state); + + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } + + if (called_once == false) try_invalid_pause_resume(state); + + called_once = true; +} +BENCHMARK(BM_diagnostic_test); + +void BM_diagnostic_test_keep_running(benchmark::State& state) { + static bool called_once = false; + + if (called_once == false) try_invalid_pause_resume(state); + + while (state.KeepRunning()) { + benchmark::DoNotOptimize(state.iterations()); + } + + if (called_once == false) try_invalid_pause_resume(state); + + called_once = true; +} +BENCHMARK(BM_diagnostic_test_keep_running); + +int main(int argc, char* argv[]) { + benchmark::internal::GetAbortHandler() = &TestHandler; + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/bridge/third_party/benchmark/test/display_aggregates_only_test.cc b/bridge/third_party/benchmark/test/display_aggregates_only_test.cc new file mode 100644 index 0000000000..6ad65e7f51 --- /dev/null +++ b/bridge/third_party/benchmark/test/display_aggregates_only_test.cc @@ -0,0 +1,45 @@ + +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// Ok this test is super ugly. We want to check what happens with the file +// reporter in the presence of DisplayAggregatesOnly(). +// We do not care about console output, the normal tests check that already. + +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->DisplayAggregatesOnly(); + +int main(int argc, char* argv[]) { + const std::string output = GetFileReporterOutput(argc, argv); + + if (SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3") != 7 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3\"") != 3 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_mean\"") != 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_median\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_cv\"") != 1) { + std::cout << "Precondition mismatch. Expected to only find 8 " + "occurrences of \"BM_SummaryRepeat/repeats:3\" substring:\n" + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_mean\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_median\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_cv\"\nThe entire " + "output:\n"; + std::cout << output; + return 1; + } + + return 0; +} diff --git a/bridge/third_party/benchmark/test/donotoptimize_assembly_test.cc b/bridge/third_party/benchmark/test/donotoptimize_assembly_test.cc new file mode 100644 index 0000000000..2e86a51e22 --- /dev/null +++ b/bridge/third_party/benchmark/test/donotoptimize_assembly_test.cc @@ -0,0 +1,161 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +extern "C" { + +extern int ExternInt; +extern int ExternInt2; +extern int ExternInt3; + +inline int Add42(int x) { return x + 42; } + +struct NotTriviallyCopyable { + NotTriviallyCopyable(); + explicit NotTriviallyCopyable(int x) : value(x) {} + NotTriviallyCopyable(NotTriviallyCopyable const &); + int value; +}; + +struct Large { + int value; + int data[2]; +}; +} +// CHECK-LABEL: test_with_rvalue: +extern "C" void test_with_rvalue() { + benchmark::DoNotOptimize(Add42(0)); + // CHECK: movl $42, %eax + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_rvalue: +extern "C" void test_with_large_rvalue() { + benchmark::DoNotOptimize(Large{ExternInt, {ExternInt, ExternInt}}); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]] + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_rvalue: +extern "C" void test_with_non_trivial_rvalue() { + benchmark::DoNotOptimize(NotTriviallyCopyable(ExternInt)); + // CHECK: mov{{l|q}} ExternInt(%rip) + // CHECK: ret +} + +// CHECK-LABEL: test_with_lvalue: +extern "C" void test_with_lvalue() { + int x = 101; + benchmark::DoNotOptimize(x); + // CHECK-GNU: movl $101, %eax + // CHECK-CLANG: movl $101, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_lvalue: +extern "C" void test_with_large_lvalue() { + Large L{ExternInt, {ExternInt, ExternInt}}; + benchmark::DoNotOptimize(L); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_lvalue: +extern "C" void test_with_non_trivial_lvalue() { + NotTriviallyCopyable NTC(ExternInt); + benchmark::DoNotOptimize(NTC); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_const_lvalue: +extern "C" void test_with_const_lvalue() { + const int x = 123; + benchmark::DoNotOptimize(x); + // CHECK: movl $123, %eax + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_const_lvalue: +extern "C" void test_with_large_const_lvalue() { + const Large L{ExternInt, {ExternInt, ExternInt}}; + benchmark::DoNotOptimize(L); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_const_lvalue: +extern "C" void test_with_non_trivial_const_lvalue() { + const NotTriviallyCopyable Obj(ExternInt); + benchmark::DoNotOptimize(Obj); + // CHECK: mov{{q|l}} ExternInt(%rip) + // CHECK: ret +} + +// CHECK-LABEL: test_div_by_two: +extern "C" int test_div_by_two(int input) { + int divisor = 2; + benchmark::DoNotOptimize(divisor); + return input / divisor; + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_inc_integer: +extern "C" int test_inc_integer() { + int x = 0; + for (int i = 0; i < 5; ++i) benchmark::DoNotOptimize(++x); + // CHECK: movl $1, [[DEST:.*]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK-CLANG: movl [[DEST]], %eax + // CHECK: ret + return x; +} + +// CHECK-LABEL: test_pointer_rvalue +extern "C" void test_pointer_rvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret + int x = 42; + benchmark::DoNotOptimize(&x); +} + +// CHECK-LABEL: test_pointer_const_lvalue: +extern "C" void test_pointer_const_lvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret + int x = 42; + int *const xp = &x; + benchmark::DoNotOptimize(xp); +} + +// CHECK-LABEL: test_pointer_lvalue: +extern "C" void test_pointer_lvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z+]+]]) + // CHECK: ret + int x = 42; + int *xp = &x; + benchmark::DoNotOptimize(xp); +} diff --git a/bridge/third_party/benchmark/test/donotoptimize_test.cc b/bridge/third_party/benchmark/test/donotoptimize_test.cc new file mode 100644 index 0000000000..c321f156a1 --- /dev/null +++ b/bridge/third_party/benchmark/test/donotoptimize_test.cc @@ -0,0 +1,53 @@ +#include + +#include "benchmark/benchmark.h" + +namespace { +#if defined(__GNUC__) +std::uint64_t double_up(const std::uint64_t x) __attribute__((const)); +#endif +std::uint64_t double_up(const std::uint64_t x) { return x * 2; } +} // namespace + +// Using DoNotOptimize on types like BitRef seem to cause a lot of problems +// with the inline assembly on both GCC and Clang. +struct BitRef { + int index; + unsigned char& byte; + + public: + static BitRef Make() { + static unsigned char arr[2] = {}; + BitRef b(1, arr[0]); + return b; + } + + private: + BitRef(int i, unsigned char& b) : index(i), byte(b) {} +}; + +int main(int, char*[]) { + // this test verifies compilation of DoNotOptimize() for some types + + char buffer8[8] = ""; + benchmark::DoNotOptimize(buffer8); + + char buffer20[20] = ""; + benchmark::DoNotOptimize(buffer20); + + char buffer1024[1024] = ""; + benchmark::DoNotOptimize(buffer1024); + benchmark::DoNotOptimize(&buffer1024[0]); + + int x = 123; + benchmark::DoNotOptimize(x); + benchmark::DoNotOptimize(&x); + benchmark::DoNotOptimize(x += 42); + + benchmark::DoNotOptimize(double_up(x)); + + // These tests are to e + benchmark::DoNotOptimize(BitRef::Make()); + BitRef lval = BitRef::Make(); + benchmark::DoNotOptimize(lval); +} diff --git a/bridge/third_party/benchmark/test/filter_test.cc b/bridge/third_party/benchmark/test/filter_test.cc new file mode 100644 index 0000000000..a567de2dd5 --- /dev/null +++ b/bridge/third_party/benchmark/test/filter_test.cc @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE { + return ConsoleReporter::ReportContext(context); + }; + + virtual void ReportRuns(const std::vector& report) BENCHMARK_OVERRIDE { + ++count_; + max_family_index_ = + std::max(max_family_index_, report[0].family_index); + ConsoleReporter::ReportRuns(report); + }; + + TestReporter() : count_(0), max_family_index_(0) {} + + virtual ~TestReporter() {} + + size_t GetCount() const { return count_; } + + size_t GetMaxFamilyIndex() const { return max_family_index_; } + + private: + mutable size_t count_; + mutable size_t max_family_index_; +}; + +} // end namespace + +static void NoPrefix(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(NoPrefix); + +static void BM_Foo(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_Foo); + +static void BM_Bar(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_Bar); + +static void BM_FooBar(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_FooBar); + +static void BM_FooBa(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_FooBa); + +int main(int argc, char** argv) { + bool list_only = false; + for (int i = 0; i < argc; ++i) + list_only |= std::string(argv[i]).find("--benchmark_list_tests") != + std::string::npos; + + benchmark::Initialize(&argc, argv); + + TestReporter test_reporter; + const size_t returned_count = + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + if (argc == 2) { + // Make sure we ran all of the tests + std::stringstream ss(argv[1]); + size_t expected_return; + ss >> expected_return; + + if (returned_count != expected_return) { + std::cerr << "ERROR: Expected " << expected_return + << " tests to match the filter but returned_count = " + << returned_count << std::endl; + return -1; + } + + const size_t expected_reports = list_only ? 0 : expected_return; + const size_t reports_count = test_reporter.GetCount(); + if (reports_count != expected_reports) { + std::cerr << "ERROR: Expected " << expected_reports + << " tests to be run but reported_count = " << reports_count + << std::endl; + return -1; + } + + const size_t max_family_index = test_reporter.GetMaxFamilyIndex(); + const size_t num_families = reports_count == 0 ? 0 : 1 + max_family_index; + if (num_families != expected_reports) { + std::cerr << "ERROR: Expected " << expected_reports + << " test families to be run but num_families = " + << num_families << std::endl; + return -1; + } + } + + return 0; +} diff --git a/bridge/third_party/benchmark/test/fixture_test.cc b/bridge/third_party/benchmark/test/fixture_test.cc new file mode 100644 index 0000000000..af650dbd06 --- /dev/null +++ b/bridge/third_party/benchmark/test/fixture_test.cc @@ -0,0 +1,51 @@ + +#include +#include + +#include "benchmark/benchmark.h" + +#define FIXTURE_BECHMARK_NAME MyFixture + +class FIXTURE_BECHMARK_NAME : public ::benchmark::Fixture { + public: + void SetUp(const ::benchmark::State& state) BENCHMARK_OVERRIDE { + if (state.thread_index() == 0) { + assert(data.get() == nullptr); + data.reset(new int(42)); + } + } + + void TearDown(const ::benchmark::State& state) BENCHMARK_OVERRIDE { + if (state.thread_index() == 0) { + assert(data.get() != nullptr); + data.reset(); + } + } + + ~FIXTURE_BECHMARK_NAME() { assert(data == nullptr); } + + std::unique_ptr data; +}; + +BENCHMARK_F(FIXTURE_BECHMARK_NAME, Foo)(benchmark::State& st) { + assert(data.get() != nullptr); + assert(*data == 42); + for (auto _ : st) { + } +} + +BENCHMARK_DEFINE_F(FIXTURE_BECHMARK_NAME, Bar)(benchmark::State& st) { + if (st.thread_index() == 0) { + assert(data.get() != nullptr); + assert(*data == 42); + } + for (auto _ : st) { + assert(data.get() != nullptr); + assert(*data == 42); + } + st.SetItemsProcessed(st.range(0)); +} +BENCHMARK_REGISTER_F(FIXTURE_BECHMARK_NAME, Bar)->Arg(42); +BENCHMARK_REGISTER_F(FIXTURE_BECHMARK_NAME, Bar)->Arg(42)->ThreadPerCpu(); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/internal_threading_test.cc b/bridge/third_party/benchmark/test/internal_threading_test.cc new file mode 100644 index 0000000000..62b5b955a9 --- /dev/null +++ b/bridge/third_party/benchmark/test/internal_threading_test.cc @@ -0,0 +1,185 @@ + +#undef NDEBUG + +#include +#include + +#include "../src/timers.h" +#include "benchmark/benchmark.h" +#include "output_test.h" + +static const std::chrono::duration time_frame(50); +static const double time_frame_in_sec( + std::chrono::duration_cast>>( + time_frame) + .count()); + +void MyBusySpinwait() { + const auto start = benchmark::ChronoClockNow(); + + while (true) { + const auto now = benchmark::ChronoClockNow(); + const auto elapsed = now - start; + + if (std::chrono::duration(elapsed) >= + time_frame) + return; + } +} + +// ========================================================================= // +// --------------------------- TEST CASES BEGIN ---------------------------- // +// ========================================================================= // + +// ========================================================================= // +// BM_MainThread + +void BM_MainThread(benchmark::State& state) { + for (auto _ : state) { + MyBusySpinwait(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->UseRealTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->UseManualTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->UseRealTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->UseManualTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// BM_WorkerThread + +void BM_WorkerThread(benchmark::State& state) { + for (auto _ : state) { + std::thread Worker(&MyBusySpinwait); + Worker.join(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->UseRealTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->UseManualTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->MeasureProcessCPUTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->UseRealTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->UseManualTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->MeasureProcessCPUTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// BM_MainThreadAndWorkerThread + +void BM_MainThreadAndWorkerThread(benchmark::State& state) { + for (auto _ : state) { + std::thread Worker(&MyBusySpinwait); + MyBusySpinwait(); + Worker.join(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_MainThreadAndWorkerThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->UseManualTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_MainThreadAndWorkerThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->UseManualTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// ---------------------------- TEST CASES END ----------------------------- // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/link_main_test.cc b/bridge/third_party/benchmark/test/link_main_test.cc new file mode 100644 index 0000000000..241ad5c390 --- /dev/null +++ b/bridge/third_party/benchmark/test/link_main_test.cc @@ -0,0 +1,8 @@ +#include "benchmark/benchmark.h" + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); diff --git a/bridge/third_party/benchmark/test/map_test.cc b/bridge/third_party/benchmark/test/map_test.cc new file mode 100644 index 0000000000..509613457c --- /dev/null +++ b/bridge/third_party/benchmark/test/map_test.cc @@ -0,0 +1,57 @@ +#include +#include + +#include "benchmark/benchmark.h" + +namespace { + +std::map ConstructRandomMap(int size) { + std::map m; + for (int i = 0; i < size; ++i) { + m.insert(std::make_pair(std::rand() % size, std::rand() % size)); + } + return m; +} + +} // namespace + +// Basic version. +static void BM_MapLookup(benchmark::State& state) { + const int size = static_cast(state.range(0)); + std::map m; + for (auto _ : state) { + state.PauseTiming(); + m = ConstructRandomMap(size); + state.ResumeTiming(); + for (int i = 0; i < size; ++i) { + benchmark::DoNotOptimize(m.find(std::rand() % size)); + } + } + state.SetItemsProcessed(state.iterations() * size); +} +BENCHMARK(BM_MapLookup)->Range(1 << 3, 1 << 12); + +// Using fixtures. +class MapFixture : public ::benchmark::Fixture { + public: + void SetUp(const ::benchmark::State& st) BENCHMARK_OVERRIDE { + m = ConstructRandomMap(static_cast(st.range(0))); + } + + void TearDown(const ::benchmark::State&) BENCHMARK_OVERRIDE { m.clear(); } + + std::map m; +}; + +BENCHMARK_DEFINE_F(MapFixture, Lookup)(benchmark::State& state) { + const int size = static_cast(state.range(0)); + for (auto _ : state) { + for (int i = 0; i < size; ++i) { + benchmark::DoNotOptimize(m.find(std::rand() % size)); + } + } + state.SetItemsProcessed(state.iterations() * size); +} +BENCHMARK_REGISTER_F(MapFixture, Lookup)->Range(1 << 3, 1 << 12); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/memory_manager_test.cc b/bridge/third_party/benchmark/test/memory_manager_test.cc new file mode 100644 index 0000000000..f0c192fcbd --- /dev/null +++ b/bridge/third_party/benchmark/test/memory_manager_test.cc @@ -0,0 +1,46 @@ +#include + +#include "../src/check.h" +#include "benchmark/benchmark.h" +#include "output_test.h" + +class TestMemoryManager : public benchmark::MemoryManager { + void Start() BENCHMARK_OVERRIDE {} + void Stop(Result* result) BENCHMARK_OVERRIDE { + result->num_allocs = 42; + result->max_bytes_used = 42000; + } +}; + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); + +ADD_CASES(TC_ConsoleOut, {{"^BM_empty %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_empty\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_empty\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"allocs_per_iter\": %float,$", MR_Next}, + {"\"max_bytes_used\": 42000$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_empty\",%csv_report$"}}); + +int main(int argc, char* argv[]) { + std::unique_ptr mm(new TestMemoryManager()); + + benchmark::RegisterMemoryManager(mm.get()); + RunOutputTests(argc, argv); + benchmark::RegisterMemoryManager(nullptr); +} diff --git a/bridge/third_party/benchmark/test/multiple_ranges_test.cc b/bridge/third_party/benchmark/test/multiple_ranges_test.cc new file mode 100644 index 0000000000..7618c4da08 --- /dev/null +++ b/bridge/third_party/benchmark/test/multiple_ranges_test.cc @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +class MultipleRangesFixture : public ::benchmark::Fixture { + public: + MultipleRangesFixture() + : expectedValues({{1, 3, 5}, + {1, 3, 8}, + {1, 3, 15}, + {2, 3, 5}, + {2, 3, 8}, + {2, 3, 15}, + {1, 4, 5}, + {1, 4, 8}, + {1, 4, 15}, + {2, 4, 5}, + {2, 4, 8}, + {2, 4, 15}, + {1, 7, 5}, + {1, 7, 8}, + {1, 7, 15}, + {2, 7, 5}, + {2, 7, 8}, + {2, 7, 15}, + {7, 6, 3}}) {} + + void SetUp(const ::benchmark::State& state) BENCHMARK_OVERRIDE { + std::vector ranges = {state.range(0), state.range(1), + state.range(2)}; + + assert(expectedValues.find(ranges) != expectedValues.end()); + + actualValues.insert(ranges); + } + + // NOTE: This is not TearDown as we want to check after _all_ runs are + // complete. + virtual ~MultipleRangesFixture() { + if (actualValues != expectedValues) { + std::cout << "EXPECTED\n"; + for (const auto& v : expectedValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + std::cout << "ACTUAL\n"; + for (const auto& v : actualValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + } + } + + std::set> expectedValues; + std::set> actualValues; +}; + +BENCHMARK_DEFINE_F(MultipleRangesFixture, Empty)(benchmark::State& state) { + for (auto _ : state) { + int64_t product = state.range(0) * state.range(1) * state.range(2); + for (int64_t x = 0; x < product; x++) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK_REGISTER_F(MultipleRangesFixture, Empty) + ->RangeMultiplier(2) + ->Ranges({{1, 2}, {3, 7}, {5, 15}}) + ->Args({7, 6, 3}); + +void BM_CheckDefaultArgument(benchmark::State& state) { + // Test that the 'range()' without an argument is the same as 'range(0)'. + assert(state.range() == state.range(0)); + assert(state.range() != state.range(1)); + for (auto _ : state) { + } +} +BENCHMARK(BM_CheckDefaultArgument)->Ranges({{1, 5}, {6, 10}}); + +static void BM_MultipleRanges(benchmark::State& st) { + for (auto _ : st) { + } +} +BENCHMARK(BM_MultipleRanges)->Ranges({{5, 5}, {6, 6}}); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/options_test.cc b/bridge/third_party/benchmark/test/options_test.cc new file mode 100644 index 0000000000..d424d40b95 --- /dev/null +++ b/bridge/third_party/benchmark/test/options_test.cc @@ -0,0 +1,75 @@ +#include +#include + +#include "benchmark/benchmark.h" + +#if defined(NDEBUG) +#undef NDEBUG +#endif +#include + +void BM_basic(benchmark::State& state) { + for (auto _ : state) { + } +} + +void BM_basic_slow(benchmark::State& state) { + std::chrono::milliseconds sleep_duration(state.range(0)); + for (auto _ : state) { + std::this_thread::sleep_for( + std::chrono::duration_cast(sleep_duration)); + } +} + +BENCHMARK(BM_basic); +BENCHMARK(BM_basic)->Arg(42); +BENCHMARK(BM_basic_slow)->Arg(10)->Unit(benchmark::kNanosecond); +BENCHMARK(BM_basic_slow)->Arg(100)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_basic_slow)->Arg(1000)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_basic_slow)->Arg(1000)->Unit(benchmark::kSecond); +BENCHMARK(BM_basic)->Range(1, 8); +BENCHMARK(BM_basic)->RangeMultiplier(2)->Range(1, 8); +BENCHMARK(BM_basic)->DenseRange(10, 15); +BENCHMARK(BM_basic)->Args({42, 42}); +BENCHMARK(BM_basic)->Ranges({{64, 512}, {64, 512}}); +BENCHMARK(BM_basic)->MinTime(0.7); +BENCHMARK(BM_basic)->UseRealTime(); +BENCHMARK(BM_basic)->ThreadRange(2, 4); +BENCHMARK(BM_basic)->ThreadPerCpu(); +BENCHMARK(BM_basic)->Repetitions(3); +BENCHMARK(BM_basic) + ->RangeMultiplier(std::numeric_limits::max()) + ->Range(std::numeric_limits::min(), + std::numeric_limits::max()); + +// Negative ranges +BENCHMARK(BM_basic)->Range(-64, -1); +BENCHMARK(BM_basic)->RangeMultiplier(4)->Range(-8, 8); +BENCHMARK(BM_basic)->DenseRange(-2, 2, 1); +BENCHMARK(BM_basic)->Ranges({{-64, 1}, {-8, -1}}); + +void CustomArgs(benchmark::internal::Benchmark* b) { + for (int i = 0; i < 10; ++i) { + b->Arg(i); + } +} + +BENCHMARK(BM_basic)->Apply(CustomArgs); + +void BM_explicit_iteration_count(benchmark::State& state) { + // Test that benchmarks specified with an explicit iteration count are + // only run once. + static bool invoked_before = false; + assert(!invoked_before); + invoked_before = true; + + // Test that the requested iteration count is respected. + assert(state.max_iterations == 42); + size_t actual_iterations = 0; + for (auto _ : state) ++actual_iterations; + assert(state.iterations() == state.max_iterations); + assert(state.iterations() == 42); +} +BENCHMARK(BM_explicit_iteration_count)->Iterations(42); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/output_test.h b/bridge/third_party/benchmark/test/output_test.h new file mode 100644 index 0000000000..c6ff8ef2d3 --- /dev/null +++ b/bridge/third_party/benchmark/test/output_test.h @@ -0,0 +1,211 @@ +#ifndef TEST_OUTPUT_TEST_H +#define TEST_OUTPUT_TEST_H + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include + +#include "../src/re.h" +#include "benchmark/benchmark.h" + +#define CONCAT2(x, y) x##y +#define CONCAT(x, y) CONCAT2(x, y) + +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = ::AddCases(__VA_ARGS__) + +#define SET_SUBSTITUTIONS(...) \ + int CONCAT(dummy, __LINE__) = ::SetSubstitutions(__VA_ARGS__) + +enum MatchRules { + MR_Default, // Skip non-matching lines until a match is found. + MR_Next, // Match must occur on the next line. + MR_Not // No line between the current position and the next match matches + // the regex +}; + +struct TestCase { + TestCase(std::string re, int rule = MR_Default); + + std::string regex_str; + int match_rule; + std::string substituted_regex; + std::shared_ptr regex; +}; + +enum TestCaseID { + TC_ConsoleOut, + TC_ConsoleErr, + TC_JSONOut, + TC_JSONErr, + TC_CSVOut, + TC_CSVErr, + + TC_NumID // PRIVATE +}; + +// Add a list of test cases to be run against the output specified by +// 'ID' +int AddCases(TestCaseID ID, std::initializer_list il); + +// Add or set a list of substitutions to be performed on constructed regex's +// See 'output_test_helper.cc' for a list of default substitutions. +int SetSubstitutions( + std::initializer_list> il); + +// Run all output tests. +void RunOutputTests(int argc, char* argv[]); + +// Count the number of 'pat' substrings in the 'haystack' string. +int SubstrCnt(const std::string& haystack, const std::string& pat); + +// Run registered benchmarks with file reporter enabled, and return the content +// outputted by the file reporter. +std::string GetFileReporterOutput(int argc, char* argv[]); + +// ========================================================================= // +// ------------------------- Results checking ------------------------------ // +// ========================================================================= // + +// Call this macro to register a benchmark for checking its results. This +// should be all that's needed. It subscribes a function to check the (CSV) +// results of a benchmark. This is done only after verifying that the output +// strings are really as expected. +// bm_name_pattern: a name or a regex pattern which will be matched against +// all the benchmark names. Matching benchmarks +// will be the subject of a call to checker_function +// checker_function: should be of type ResultsCheckFn (see below) +#define CHECK_BENCHMARK_RESULTS(bm_name_pattern, checker_function) \ + size_t CONCAT(dummy, __LINE__) = AddChecker(bm_name_pattern, checker_function) + +struct Results; +typedef std::function ResultsCheckFn; + +size_t AddChecker(const char* bm_name_pattern, const ResultsCheckFn& fn); + +// Class holding the results of a benchmark. +// It is passed in calls to checker functions. +struct Results { + // the benchmark name + std::string name; + // the benchmark fields + std::map values; + + Results(const std::string& n) : name(n) {} + + int NumThreads() const; + + double NumIterations() const; + + typedef enum { kCpuTime, kRealTime } BenchmarkTime; + + // get cpu_time or real_time in seconds + double GetTime(BenchmarkTime which) const; + + // get the real_time duration of the benchmark in seconds. + // it is better to use fuzzy float checks for this, as the float + // ASCII formatting is lossy. + double DurationRealTime() const { + return NumIterations() * GetTime(kRealTime); + } + // get the cpu_time duration of the benchmark in seconds + double DurationCPUTime() const { return NumIterations() * GetTime(kCpuTime); } + + // get the string for a result by name, or nullptr if the name + // is not found + const std::string* Get(const char* entry_name) const { + auto it = values.find(entry_name); + if (it == values.end()) return nullptr; + return &it->second; + } + + // get a result by name, parsed as a specific type. + // NOTE: for counters, use GetCounterAs instead. + template + T GetAs(const char* entry_name) const; + + // counters are written as doubles, so they have to be read first + // as a double, and only then converted to the asked type. + template + T GetCounterAs(const char* entry_name) const { + double dval = GetAs(entry_name); + T tval = static_cast(dval); + return tval; + } +}; + +template +T Results::GetAs(const char* entry_name) const { + auto* sv = Get(entry_name); + BM_CHECK(sv != nullptr && !sv->empty()); + std::stringstream ss; + ss << *sv; + T out; + ss >> out; + BM_CHECK(!ss.fail()); + return out; +} + +//---------------------------------- +// Macros to help in result checking. Do not use them with arguments causing +// side-effects. + +// clang-format off + +#define CHECK_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value) \ + CONCAT(BM_CHECK_, relationship) \ + (entry.getfn< var_type >(var_name), (value)) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "expected (" << #var_type << ")" << (var_name) \ + << "=" << (entry).getfn< var_type >(var_name) \ + << " to be " #relationship " to " << (value) << "\n" + +// check with tolerance. eps_factor is the tolerance window, which is +// interpreted relative to value (eg, 0.1 means 10% of value). +#define CHECK_FLOAT_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value, eps_factor) \ + CONCAT(BM_CHECK_FLOAT_, relationship) \ + (entry.getfn< var_type >(var_name), (value), (eps_factor) * (value)) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "expected (" << #var_type << ")" << (var_name) \ + << "=" << (entry).getfn< var_type >(var_name) \ + << " to be " #relationship " to " << (value) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "with tolerance of " << (eps_factor) * (value) \ + << " (" << (eps_factor)*100. << "%), " \ + << "but delta was " << ((entry).getfn< var_type >(var_name) - (value)) \ + << " (" << (((entry).getfn< var_type >(var_name) - (value)) \ + / \ + ((value) > 1.e-5 || value < -1.e-5 ? value : 1.e-5)*100.) \ + << "%)" + +#define CHECK_RESULT_VALUE(entry, var_type, var_name, relationship, value) \ + CHECK_RESULT_VALUE_IMPL(entry, GetAs, var_type, var_name, relationship, value) + +#define CHECK_COUNTER_VALUE(entry, var_type, var_name, relationship, value) \ + CHECK_RESULT_VALUE_IMPL(entry, GetCounterAs, var_type, var_name, relationship, value) + +#define CHECK_FLOAT_RESULT_VALUE(entry, var_name, relationship, value, eps_factor) \ + CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetAs, double, var_name, relationship, value, eps_factor) + +#define CHECK_FLOAT_COUNTER_VALUE(entry, var_name, relationship, value, eps_factor) \ + CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetCounterAs, double, var_name, relationship, value, eps_factor) + +// clang-format on + +// ========================================================================= // +// --------------------------- Misc Utilities ------------------------------ // +// ========================================================================= // + +namespace { + +const char* const dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; + +} // end namespace + +#endif // TEST_OUTPUT_TEST_H diff --git a/bridge/third_party/benchmark/test/output_test_helper.cc b/bridge/third_party/benchmark/test/output_test_helper.cc new file mode 100644 index 0000000000..81584cbf77 --- /dev/null +++ b/bridge/third_party/benchmark/test/output_test_helper.cc @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/benchmark_api_internal.h" +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "../src/log.h" // NOTE: log.h is for internal use only +#include "../src/re.h" // NOTE: re.h is for internal use only +#include "output_test.h" + +// ========================================================================= // +// ------------------------------ Internals -------------------------------- // +// ========================================================================= // +namespace internal { +namespace { + +using TestCaseList = std::vector; + +// Use a vector because the order elements are added matters during iteration. +// std::map/unordered_map don't guarantee that. +// For example: +// SetSubstitutions({{"%HelloWorld", "Hello"}, {"%Hello", "Hi"}}); +// Substitute("%HelloWorld") // Always expands to Hello. +using SubMap = std::vector>; + +TestCaseList& GetTestCaseList(TestCaseID ID) { + // Uses function-local statics to ensure initialization occurs + // before first use. + static TestCaseList lists[TC_NumID]; + return lists[ID]; +} + +SubMap& GetSubstitutions() { + // Don't use 'dec_re' from header because it may not yet be initialized. + // clang-format off + static std::string safe_dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; + static std::string time_re = "([0-9]+[.])?[0-9]+"; + static std::string percentage_re = "[0-9]+[.][0-9]{2}"; + static SubMap map = { + {"%float", "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"}, + // human-readable float + {"%hrfloat", "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?[kMGTPEZYmunpfazy]?"}, + {"%percentage", percentage_re}, + {"%int", "[ ]*[0-9]+"}, + {" %s ", "[ ]+"}, + {"%time", "[ ]*" + time_re + "[ ]+ns"}, + {"%console_report", "[ ]*" + time_re + "[ ]+ns [ ]*" + time_re + "[ ]+ns [ ]*[0-9]+"}, + {"%console_percentage_report", "[ ]*" + percentage_re + "[ ]+% [ ]*" + percentage_re + "[ ]+% [ ]*[0-9]+"}, + {"%console_us_report", "[ ]*" + time_re + "[ ]+us [ ]*" + time_re + "[ ]+us [ ]*[0-9]+"}, + {"%console_ms_report", "[ ]*" + time_re + "[ ]+ms [ ]*" + time_re + "[ ]+ms [ ]*[0-9]+"}, + {"%console_s_report", "[ ]*" + time_re + "[ ]+s [ ]*" + time_re + "[ ]+s [ ]*[0-9]+"}, + {"%console_time_only_report", "[ ]*" + time_re + "[ ]+ns [ ]*" + time_re + "[ ]+ns"}, + {"%console_us_report", "[ ]*" + time_re + "[ ]+us [ ]*" + time_re + "[ ]+us [ ]*[0-9]+"}, + {"%console_us_time_only_report", "[ ]*" + time_re + "[ ]+us [ ]*" + time_re + "[ ]+us"}, + {"%csv_header", + "name,iterations,real_time,cpu_time,time_unit,bytes_per_second," + "items_per_second,label,error_occurred,error_message"}, + {"%csv_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,,,,,"}, + {"%csv_us_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",us,,,,,"}, + {"%csv_ms_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ms,,,,,"}, + {"%csv_s_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",s,,,,,"}, + {"%csv_bytes_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns," + safe_dec_re + ",,,,"}, + {"%csv_items_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,," + safe_dec_re + ",,,"}, + {"%csv_bytes_items_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns," + safe_dec_re + + "," + safe_dec_re + ",,,"}, + {"%csv_label_report_begin", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,,,"}, + {"%csv_label_report_end", ",,"}}; + // clang-format on + return map; +} + +std::string PerformSubstitutions(std::string source) { + SubMap const& subs = GetSubstitutions(); + using SizeT = std::string::size_type; + for (auto const& KV : subs) { + SizeT pos; + SizeT next_start = 0; + while ((pos = source.find(KV.first, next_start)) != std::string::npos) { + next_start = pos + KV.second.size(); + source.replace(pos, KV.first.size(), KV.second); + } + } + return source; +} + +void CheckCase(std::stringstream& remaining_output, TestCase const& TC, + TestCaseList const& not_checks) { + std::string first_line; + bool on_first = true; + std::string line; + while (remaining_output.eof() == false) { + BM_CHECK(remaining_output.good()); + std::getline(remaining_output, line); + if (on_first) { + first_line = line; + on_first = false; + } + for (const auto& NC : not_checks) { + BM_CHECK(!NC.regex->Match(line)) + << "Unexpected match for line \"" << line << "\" for MR_Not regex \"" + << NC.regex_str << "\"" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; + } + if (TC.regex->Match(line)) return; + BM_CHECK(TC.match_rule != MR_Next) + << "Expected line \"" << line << "\" to match regex \"" << TC.regex_str + << "\"" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; + } + BM_CHECK(remaining_output.eof() == false) + << "End of output reached before match for regex \"" << TC.regex_str + << "\" was found" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; +} + +void CheckCases(TestCaseList const& checks, std::stringstream& output) { + std::vector not_checks; + for (size_t i = 0; i < checks.size(); ++i) { + const auto& TC = checks[i]; + if (TC.match_rule == MR_Not) { + not_checks.push_back(TC); + continue; + } + CheckCase(output, TC, not_checks); + not_checks.clear(); + } +} + +class TestReporter : public benchmark::BenchmarkReporter { + public: + TestReporter(std::vector reps) + : reporters_(std::move(reps)) {} + + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE { + bool last_ret = false; + bool first = true; + for (auto rep : reporters_) { + bool new_ret = rep->ReportContext(context); + BM_CHECK(first || new_ret == last_ret) + << "Reports return different values for ReportContext"; + first = false; + last_ret = new_ret; + } + (void)first; + return last_ret; + } + + void ReportRuns(const std::vector& report) BENCHMARK_OVERRIDE { + for (auto rep : reporters_) rep->ReportRuns(report); + } + void Finalize() BENCHMARK_OVERRIDE { + for (auto rep : reporters_) rep->Finalize(); + } + + private: + std::vector reporters_; +}; +} // namespace + +} // end namespace internal + +// ========================================================================= // +// -------------------------- Results checking ----------------------------- // +// ========================================================================= // + +namespace internal { + +// Utility class to manage subscribers for checking benchmark results. +// It works by parsing the CSV output to read the results. +class ResultsChecker { + public: + struct PatternAndFn : public TestCase { // reusing TestCase for its regexes + PatternAndFn(const std::string& rx, ResultsCheckFn fn_) + : TestCase(rx), fn(std::move(fn_)) {} + ResultsCheckFn fn; + }; + + std::vector check_patterns; + std::vector results; + std::vector field_names; + + void Add(const std::string& entry_pattern, const ResultsCheckFn& fn); + + void CheckResults(std::stringstream& output); + + private: + void SetHeader_(const std::string& csv_header); + void SetValues_(const std::string& entry_csv_line); + + std::vector SplitCsv_(const std::string& line); +}; + +// store the static ResultsChecker in a function to prevent initialization +// order problems +ResultsChecker& GetResultsChecker() { + static ResultsChecker rc; + return rc; +} + +// add a results checker for a benchmark +void ResultsChecker::Add(const std::string& entry_pattern, + const ResultsCheckFn& fn) { + check_patterns.emplace_back(entry_pattern, fn); +} + +// check the results of all subscribed benchmarks +void ResultsChecker::CheckResults(std::stringstream& output) { + // first reset the stream to the start + { + auto start = std::stringstream::pos_type(0); + // clear before calling tellg() + output.clear(); + // seek to zero only when needed + if (output.tellg() > start) output.seekg(start); + // and just in case + output.clear(); + } + // now go over every line and publish it to the ResultsChecker + std::string line; + bool on_first = true; + while (output.eof() == false) { + BM_CHECK(output.good()); + std::getline(output, line); + if (on_first) { + SetHeader_(line); // this is important + on_first = false; + continue; + } + SetValues_(line); + } + // finally we can call the subscribed check functions + for (const auto& p : check_patterns) { + BM_VLOG(2) << "--------------------------------\n"; + BM_VLOG(2) << "checking for benchmarks matching " << p.regex_str << "...\n"; + for (const auto& r : results) { + if (!p.regex->Match(r.name)) { + BM_VLOG(2) << p.regex_str << " is not matched by " << r.name << "\n"; + continue; + } else { + BM_VLOG(2) << p.regex_str << " is matched by " << r.name << "\n"; + } + BM_VLOG(1) << "Checking results of " << r.name << ": ... \n"; + p.fn(r); + BM_VLOG(1) << "Checking results of " << r.name << ": OK.\n"; + } + } +} + +// prepare for the names in this header +void ResultsChecker::SetHeader_(const std::string& csv_header) { + field_names = SplitCsv_(csv_header); +} + +// set the values for a benchmark +void ResultsChecker::SetValues_(const std::string& entry_csv_line) { + if (entry_csv_line.empty()) return; // some lines are empty + BM_CHECK(!field_names.empty()); + auto vals = SplitCsv_(entry_csv_line); + BM_CHECK_EQ(vals.size(), field_names.size()); + results.emplace_back(vals[0]); // vals[0] is the benchmark name + auto& entry = results.back(); + for (size_t i = 1, e = vals.size(); i < e; ++i) { + entry.values[field_names[i]] = vals[i]; + } +} + +// a quick'n'dirty csv splitter (eliminating quotes) +std::vector ResultsChecker::SplitCsv_(const std::string& line) { + std::vector out; + if (line.empty()) return out; + if (!field_names.empty()) out.reserve(field_names.size()); + size_t prev = 0, pos = line.find_first_of(','), curr = pos; + while (pos != line.npos) { + BM_CHECK(curr > 0); + if (line[prev] == '"') ++prev; + if (line[curr - 1] == '"') --curr; + out.push_back(line.substr(prev, curr - prev)); + prev = pos + 1; + pos = line.find_first_of(',', pos + 1); + curr = pos; + } + curr = line.size(); + if (line[prev] == '"') ++prev; + if (line[curr - 1] == '"') --curr; + out.push_back(line.substr(prev, curr - prev)); + return out; +} + +} // end namespace internal + +size_t AddChecker(const char* bm_name, const ResultsCheckFn& fn) { + auto& rc = internal::GetResultsChecker(); + rc.Add(bm_name, fn); + return rc.results.size(); +} + +int Results::NumThreads() const { + auto pos = name.find("/threads:"); + if (pos == name.npos) return 1; + auto end = name.find('/', pos + 9); + std::stringstream ss; + ss << name.substr(pos + 9, end); + int num = 1; + ss >> num; + BM_CHECK(!ss.fail()); + return num; +} + +double Results::NumIterations() const { return GetAs("iterations"); } + +double Results::GetTime(BenchmarkTime which) const { + BM_CHECK(which == kCpuTime || which == kRealTime); + const char* which_str = which == kCpuTime ? "cpu_time" : "real_time"; + double val = GetAs(which_str); + auto unit = Get("time_unit"); + BM_CHECK(unit); + if (*unit == "ns") { + return val * 1.e-9; + } else if (*unit == "us") { + return val * 1.e-6; + } else if (*unit == "ms") { + return val * 1.e-3; + } else if (*unit == "s") { + return val; + } else { + BM_CHECK(1 == 0) << "unknown time unit: " << *unit; + return 0; + } +} + +// ========================================================================= // +// -------------------------- Public API Definitions------------------------ // +// ========================================================================= // + +TestCase::TestCase(std::string re, int rule) + : regex_str(std::move(re)), + match_rule(rule), + substituted_regex(internal::PerformSubstitutions(regex_str)), + regex(std::make_shared()) { + std::string err_str; + regex->Init(substituted_regex, &err_str); + BM_CHECK(err_str.empty()) + << "Could not construct regex \"" << substituted_regex << "\"" + << "\n originally \"" << regex_str << "\"" + << "\n got error: " << err_str; +} + +int AddCases(TestCaseID ID, std::initializer_list il) { + auto& L = internal::GetTestCaseList(ID); + L.insert(L.end(), il); + return 0; +} + +int SetSubstitutions( + std::initializer_list> il) { + auto& subs = internal::GetSubstitutions(); + for (auto KV : il) { + bool exists = false; + KV.second = internal::PerformSubstitutions(KV.second); + for (auto& EKV : subs) { + if (EKV.first == KV.first) { + EKV.second = std::move(KV.second); + exists = true; + break; + } + } + if (!exists) subs.push_back(std::move(KV)); + } + return 0; +} + +// Disable deprecated warnings temporarily because we need to reference +// CSVReporter but don't want to trigger -Werror=-Wdeprecated-declarations +BENCHMARK_DISABLE_DEPRECATED_WARNING + +void RunOutputTests(int argc, char* argv[]) { + using internal::GetTestCaseList; + benchmark::Initialize(&argc, argv); + auto options = benchmark::internal::GetOutputOptions(/*force_no_color*/ true); + benchmark::ConsoleReporter CR(options); + benchmark::JSONReporter JR; + benchmark::CSVReporter CSVR; + struct ReporterTest { + const char* name; + std::vector& output_cases; + std::vector& error_cases; + benchmark::BenchmarkReporter& reporter; + std::stringstream out_stream; + std::stringstream err_stream; + + ReporterTest(const char* n, std::vector& out_tc, + std::vector& err_tc, + benchmark::BenchmarkReporter& br) + : name(n), output_cases(out_tc), error_cases(err_tc), reporter(br) { + reporter.SetOutputStream(&out_stream); + reporter.SetErrorStream(&err_stream); + } + } TestCases[] = { + {"ConsoleReporter", GetTestCaseList(TC_ConsoleOut), + GetTestCaseList(TC_ConsoleErr), CR}, + {"JSONReporter", GetTestCaseList(TC_JSONOut), GetTestCaseList(TC_JSONErr), + JR}, + {"CSVReporter", GetTestCaseList(TC_CSVOut), GetTestCaseList(TC_CSVErr), + CSVR}, + }; + + // Create the test reporter and run the benchmarks. + std::cout << "Running benchmarks...\n"; + internal::TestReporter test_rep({&CR, &JR, &CSVR}); + benchmark::RunSpecifiedBenchmarks(&test_rep); + + for (auto& rep_test : TestCases) { + std::string msg = std::string("\nTesting ") + rep_test.name + " Output\n"; + std::string banner(msg.size() - 1, '-'); + std::cout << banner << msg << banner << "\n"; + + std::cerr << rep_test.err_stream.str(); + std::cout << rep_test.out_stream.str(); + + internal::CheckCases(rep_test.error_cases, rep_test.err_stream); + internal::CheckCases(rep_test.output_cases, rep_test.out_stream); + + std::cout << "\n"; + } + + // now that we know the output is as expected, we can dispatch + // the checks to subscribees. + auto& csv = TestCases[2]; + // would use == but gcc spits a warning + BM_CHECK(std::strcmp(csv.name, "CSVReporter") == 0); + internal::GetResultsChecker().CheckResults(csv.out_stream); +} + +BENCHMARK_RESTORE_DEPRECATED_WARNING + +int SubstrCnt(const std::string& haystack, const std::string& pat) { + if (pat.length() == 0) return 0; + int count = 0; + for (size_t offset = haystack.find(pat); offset != std::string::npos; + offset = haystack.find(pat, offset + pat.length())) + ++count; + return count; +} + +static char ToHex(int ch) { + return ch < 10 ? static_cast('0' + ch) + : static_cast('a' + (ch - 10)); +} + +static char RandomHexChar() { + static std::mt19937 rd{std::random_device{}()}; + static std::uniform_int_distribution mrand{0, 15}; + return ToHex(mrand(rd)); +} + +static std::string GetRandomFileName() { + std::string model = "test.%%%%%%"; + for (auto& ch : model) { + if (ch == '%') ch = RandomHexChar(); + } + return model; +} + +static bool FileExists(std::string const& name) { + std::ifstream in(name.c_str()); + return in.good(); +} + +static std::string GetTempFileName() { + // This function attempts to avoid race conditions where two tests + // create the same file at the same time. However, it still introduces races + // similar to tmpnam. + int retries = 3; + while (--retries) { + std::string name = GetRandomFileName(); + if (!FileExists(name)) return name; + } + std::cerr << "Failed to create unique temporary file name" << std::endl; + std::abort(); +} + +std::string GetFileReporterOutput(int argc, char* argv[]) { + std::vector new_argv(argv, argv + argc); + assert(static_cast(argc) == new_argv.size()); + + std::string tmp_file_name = GetTempFileName(); + std::cout << "Will be using this as the tmp file: " << tmp_file_name << '\n'; + + std::string tmp = "--benchmark_out="; + tmp += tmp_file_name; + new_argv.emplace_back(const_cast(tmp.c_str())); + + argc = int(new_argv.size()); + + benchmark::Initialize(&argc, new_argv.data()); + benchmark::RunSpecifiedBenchmarks(); + + // Read the output back from the file, and delete the file. + std::ifstream tmp_stream(tmp_file_name); + std::string output = std::string((std::istreambuf_iterator(tmp_stream)), + std::istreambuf_iterator()); + std::remove(tmp_file_name.c_str()); + + return output; +} diff --git a/bridge/third_party/benchmark/test/perf_counters_gtest.cc b/bridge/third_party/benchmark/test/perf_counters_gtest.cc new file mode 100644 index 0000000000..3eac62463b --- /dev/null +++ b/bridge/third_party/benchmark/test/perf_counters_gtest.cc @@ -0,0 +1,145 @@ +#include + +#include "../src/perf_counters.h" +#include "gtest/gtest.h" + +#ifndef GTEST_SKIP +struct MsgHandler { + void operator=(std::ostream&) {} +}; +#define GTEST_SKIP() return MsgHandler() = std::cout +#endif + +using benchmark::internal::PerfCounters; +using benchmark::internal::PerfCounterValues; + +namespace { +const char kGenericPerfEvent1[] = "CYCLES"; +const char kGenericPerfEvent2[] = "BRANCHES"; +const char kGenericPerfEvent3[] = "INSTRUCTIONS"; + +TEST(PerfCountersTest, Init) { + EXPECT_EQ(PerfCounters::Initialize(), PerfCounters::kSupported); +} + +TEST(PerfCountersTest, OneCounter) { + if (!PerfCounters::kSupported) { + GTEST_SKIP() << "Performance counters not supported.\n"; + } + EXPECT_TRUE(PerfCounters::Initialize()); + EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1}).IsValid()); +} + +TEST(PerfCountersTest, NegativeTest) { + if (!PerfCounters::kSupported) { + EXPECT_FALSE(PerfCounters::Initialize()); + return; + } + EXPECT_TRUE(PerfCounters::Initialize()); + EXPECT_FALSE(PerfCounters::Create({}).IsValid()); + EXPECT_FALSE(PerfCounters::Create({""}).IsValid()); + EXPECT_FALSE(PerfCounters::Create({"not a counter name"}).IsValid()); + { + EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, + kGenericPerfEvent3}) + .IsValid()); + } + EXPECT_FALSE( + PerfCounters::Create({kGenericPerfEvent2, "", kGenericPerfEvent1}) + .IsValid()); + EXPECT_FALSE(PerfCounters::Create({kGenericPerfEvent3, "not a counter name", + kGenericPerfEvent1}) + .IsValid()); + { + EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, + kGenericPerfEvent3}) + .IsValid()); + } + EXPECT_FALSE( + PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, + kGenericPerfEvent3, "MISPREDICTED_BRANCH_RETIRED"}) + .IsValid()); +} + +TEST(PerfCountersTest, Read1Counter) { + if (!PerfCounters::kSupported) { + GTEST_SKIP() << "Test skipped because libpfm is not supported.\n"; + } + EXPECT_TRUE(PerfCounters::Initialize()); + auto counters = PerfCounters::Create({kGenericPerfEvent1}); + EXPECT_TRUE(counters.IsValid()); + PerfCounterValues values1(1); + EXPECT_TRUE(counters.Snapshot(&values1)); + EXPECT_GT(values1[0], 0); + PerfCounterValues values2(1); + EXPECT_TRUE(counters.Snapshot(&values2)); + EXPECT_GT(values2[0], 0); + EXPECT_GT(values2[0], values1[0]); +} + +TEST(PerfCountersTest, Read2Counters) { + if (!PerfCounters::kSupported) { + GTEST_SKIP() << "Test skipped because libpfm is not supported.\n"; + } + EXPECT_TRUE(PerfCounters::Initialize()); + auto counters = + PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2}); + EXPECT_TRUE(counters.IsValid()); + PerfCounterValues values1(2); + EXPECT_TRUE(counters.Snapshot(&values1)); + EXPECT_GT(values1[0], 0); + EXPECT_GT(values1[1], 0); + PerfCounterValues values2(2); + EXPECT_TRUE(counters.Snapshot(&values2)); + EXPECT_GT(values2[0], 0); + EXPECT_GT(values2[1], 0); +} + +size_t do_work() { + size_t res = 0; + for (size_t i = 0; i < 100000000; ++i) res += i * i; + return res; +} + +void measure(size_t threadcount, PerfCounterValues* values1, + PerfCounterValues* values2) { + BM_CHECK_NE(values1, nullptr); + BM_CHECK_NE(values2, nullptr); + std::vector threads(threadcount); + auto work = [&]() { BM_CHECK(do_work() > 1000); }; + + // We need to first set up the counters, then start the threads, so the + // threads would inherit the counters. But later, we need to first destroy the + // thread pool (so all the work finishes), then measure the counters. So the + // scopes overlap, and we need to explicitly control the scope of the + // threadpool. + auto counters = + PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent3}); + for (auto& t : threads) t = std::thread(work); + counters.Snapshot(values1); + for (auto& t : threads) t.join(); + counters.Snapshot(values2); +} + +TEST(PerfCountersTest, MultiThreaded) { + if (!PerfCounters::kSupported) { + GTEST_SKIP() << "Test skipped because libpfm is not supported."; + } + EXPECT_TRUE(PerfCounters::Initialize()); + PerfCounterValues values1(2); + PerfCounterValues values2(2); + + measure(2, &values1, &values2); + std::vector D1{static_cast(values2[0] - values1[0]), + static_cast(values2[1] - values1[1])}; + + measure(4, &values1, &values2); + std::vector D2{static_cast(values2[0] - values1[0]), + static_cast(values2[1] - values1[1])}; + + // Some extra work will happen on the main thread - like joining the threads + // - so the ratio won't be quite 2.0, but very close. + EXPECT_GE(D2[0], 1.9 * D1[0]); + EXPECT_GE(D2[1], 1.9 * D1[1]); +} +} // namespace diff --git a/bridge/third_party/benchmark/test/perf_counters_test.cc b/bridge/third_party/benchmark/test/perf_counters_test.cc new file mode 100644 index 0000000000..3017a452fe --- /dev/null +++ b/bridge/third_party/benchmark/test/perf_counters_test.cc @@ -0,0 +1,27 @@ +#undef NDEBUG + +#include "../src/perf_counters.h" + +#include "benchmark/benchmark.h" +#include "output_test.h" + +static void BM_Simple(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_Simple); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Simple\",$"}}); + +static void CheckSimple(Results const& e) { + CHECK_COUNTER_VALUE(e, double, "CYCLES", GT, 0); + CHECK_COUNTER_VALUE(e, double, "BRANCHES", GT, 0.0); +} +CHECK_BENCHMARK_RESULTS("BM_Simple", &CheckSimple); + +int main(int argc, char* argv[]) { + if (!benchmark::internal::PerfCounters::kSupported) { + return 0; + } + RunOutputTests(argc, argv); +} diff --git a/bridge/third_party/benchmark/test/register_benchmark_test.cc b/bridge/third_party/benchmark/test/register_benchmark_test.cc new file mode 100644 index 0000000000..602405b67e --- /dev/null +++ b/bridge/third_party/benchmark/test/register_benchmark_test.cc @@ -0,0 +1,184 @@ + +#undef NDEBUG +#include +#include + +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "benchmark/benchmark.h" + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual void ReportRuns(const std::vector& report) BENCHMARK_OVERRIDE { + all_runs_.insert(all_runs_.end(), begin(report), end(report)); + ConsoleReporter::ReportRuns(report); + } + + std::vector all_runs_; +}; + +struct TestCase { + std::string name; + const char* label; + // Note: not explicit as we rely on it being converted through ADD_CASES. + TestCase(const char* xname) : TestCase(xname, nullptr) {} + TestCase(const char* xname, const char* xlabel) + : name(xname), label(xlabel) {} + + typedef benchmark::BenchmarkReporter::Run Run; + + void CheckRun(Run const& run) const { + // clang-format off + BM_CHECK(name == run.benchmark_name()) << "expected " << name << " got " + << run.benchmark_name(); + if (label) { + BM_CHECK(run.report_label == label) << "expected " << label << " got " + << run.report_label; + } else { + BM_CHECK(run.report_label.empty()); + } + // clang-format on + } +}; + +std::vector ExpectedResults; + +int AddCases(std::initializer_list const& v) { + for (const auto& N : v) { + ExpectedResults.push_back(N); + } + return 0; +} + +#define CONCAT(x, y) CONCAT2(x, y) +#define CONCAT2(x, y) x##y +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = AddCases({__VA_ARGS__}) + +} // end namespace + +typedef benchmark::internal::Benchmark* ReturnVal; + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with no additional arguments +//----------------------------------------------------------------------------// +void BM_function(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_function); +ReturnVal dummy = benchmark::RegisterBenchmark( + "BM_function_manual_registration", BM_function); +ADD_CASES({"BM_function"}, {"BM_function_manual_registration"}); + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with additional arguments +// Note: GCC <= 4.8 do not support this form of RegisterBenchmark because they +// reject the variadic pack expansion of lambda captures. +//----------------------------------------------------------------------------// +#ifndef BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + +void BM_extra_args(benchmark::State& st, const char* label) { + for (auto _ : st) { + } + st.SetLabel(label); +} +int RegisterFromFunction() { + std::pair cases[] = { + {"test1", "One"}, {"test2", "Two"}, {"test3", "Three"}}; + for (auto const& c : cases) + benchmark::RegisterBenchmark(c.first, &BM_extra_args, c.second); + return 0; +} +int dummy2 = RegisterFromFunction(); +ADD_CASES({"test1", "One"}, {"test2", "Two"}, {"test3", "Three"}); + +#endif // BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with different callable types +//----------------------------------------------------------------------------// + +struct CustomFixture { + void operator()(benchmark::State& st) { + for (auto _ : st) { + } + } +}; + +void TestRegistrationAtRuntime() { +#ifdef BENCHMARK_HAS_CXX11 + { + CustomFixture fx; + benchmark::RegisterBenchmark("custom_fixture", fx); + AddCases({"custom_fixture"}); + } +#endif +#ifndef BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + { + const char* x = "42"; + auto capturing_lam = [=](benchmark::State& st) { + for (auto _ : st) { + } + st.SetLabel(x); + }; + benchmark::RegisterBenchmark("lambda_benchmark", capturing_lam); + AddCases({{"lambda_benchmark", x}}); + } +#endif +} + +// Test that all benchmarks, registered at either during static init or runtime, +// are run and the results are passed to the reported. +void RunTestOne() { + TestRegistrationAtRuntime(); + + TestReporter test_reporter; + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); +} + +// Test that ClearRegisteredBenchmarks() clears all previously registered +// benchmarks. +// Also test that new benchmarks can be registered and ran afterwards. +void RunTestTwo() { + assert(ExpectedResults.size() != 0 && + "must have at least one registered benchmark"); + ExpectedResults.clear(); + benchmark::ClearRegisteredBenchmarks(); + + TestReporter test_reporter; + size_t num_ran = benchmark::RunSpecifiedBenchmarks(&test_reporter); + assert(num_ran == 0); + assert(test_reporter.all_runs_.begin() == test_reporter.all_runs_.end()); + + TestRegistrationAtRuntime(); + num_ran = benchmark::RunSpecifiedBenchmarks(&test_reporter); + assert(num_ran == ExpectedResults.size()); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); +} + +int main(int argc, char* argv[]) { + benchmark::Initialize(&argc, argv); + + RunTestOne(); + RunTestTwo(); +} diff --git a/bridge/third_party/benchmark/test/repetitions_test.cc b/bridge/third_party/benchmark/test/repetitions_test.cc new file mode 100644 index 0000000000..569777d5f9 --- /dev/null +++ b/bridge/third_party/benchmark/test/repetitions_test.cc @@ -0,0 +1,214 @@ + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ------------------------ Testing Basic Output --------------------------- // +// ========================================================================= // + +static void BM_ExplicitRepetitions(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_ExplicitRepetitions)->Repetitions(2); + +ADD_CASES(TC_ConsoleOut, + {{"^BM_ExplicitRepetitions/repeats:2 %console_report$"}}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_ExplicitRepetitions/repeats:2 %console_report$"}}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_ExplicitRepetitions/repeats:2_mean %console_report$"}}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_ExplicitRepetitions/repeats:2_median %console_report$"}}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_ExplicitRepetitions/repeats:2_stddev %console_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_ExplicitRepetitions/repeats:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ExplicitRepetitions/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_ExplicitRepetitions/repeats:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ExplicitRepetitions/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_ExplicitRepetitions/repeats:2_mean\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ExplicitRepetitions/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_ExplicitRepetitions/repeats:2_median\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ExplicitRepetitions/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_ExplicitRepetitions/repeats:2_stddev\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ExplicitRepetitions/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ExplicitRepetitions/repeats:2\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ExplicitRepetitions/repeats:2\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_ExplicitRepetitions/repeats:2_mean\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_ExplicitRepetitions/repeats:2_median\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_ExplicitRepetitions/repeats:2_stddev\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Basic Output --------------------------- // +// ========================================================================= // + +static void BM_ImplicitRepetitions(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_ImplicitRepetitions); + +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions %console_report$"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions %console_report$"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions %console_report$"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions_mean %console_report$"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions_median %console_report$"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_ImplicitRepetitions_stddev %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions_mean\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions_median\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_ImplicitRepetitions_stddev\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_ImplicitRepetitions\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ImplicitRepetitions\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ImplicitRepetitions\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ImplicitRepetitions_mean\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ImplicitRepetitions_median\",%csv_report$"}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_ImplicitRepetitions_stddev\",%csv_report$"}}); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/report_aggregates_only_test.cc b/bridge/third_party/benchmark/test/report_aggregates_only_test.cc new file mode 100644 index 0000000000..47da503588 --- /dev/null +++ b/bridge/third_party/benchmark/test/report_aggregates_only_test.cc @@ -0,0 +1,41 @@ + +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// Ok this test is super ugly. We want to check what happens with the file +// reporter in the presence of ReportAggregatesOnly(). +// We do not care about console output, the normal tests check that already. + +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->ReportAggregatesOnly(); + +int main(int argc, char* argv[]) { + const std::string output = GetFileReporterOutput(argc, argv); + + if (SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3") != 4 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_mean\"") != 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_median\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_cv\"") != 1) { + std::cout << "Precondition mismatch. Expected to only find four " + "occurrences of \"BM_SummaryRepeat/repeats:3\" substring:\n" + "\"name\": \"BM_SummaryRepeat/repeats:3_mean\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_median\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_cv\"\nThe entire " + "output:\n"; + std::cout << output; + return 1; + } + + return 0; +} diff --git a/bridge/third_party/benchmark/test/reporter_output_test.cc b/bridge/third_party/benchmark/test/reporter_output_test.cc new file mode 100644 index 0000000000..2b6e6543dd --- /dev/null +++ b/bridge/third_party/benchmark/test/reporter_output_test.cc @@ -0,0 +1,1127 @@ + +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ---------------------- Testing Prologue Output -------------------------- // +// ========================================================================= // + +ADD_CASES(TC_ConsoleOut, {{"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations$", MR_Next}, + {"^[-]+$", MR_Next}}); +static int AddContextCases() { + AddCases(TC_ConsoleErr, + { + {"^%int-%int-%intT%int:%int:%int[-+]%int:%int$", MR_Default}, + {"Running .*/reporter_output_test(\\.exe)?$", MR_Next}, + {"Run on \\(%int X %float MHz CPU s?\\)", MR_Next}, + }); + AddCases(TC_JSONOut, + {{"^\\{", MR_Default}, + {"\"context\":", MR_Next}, + {"\"date\": \"", MR_Next}, + {"\"host_name\":", MR_Next}, + {"\"executable\": \".*(/|\\\\)reporter_output_test(\\.exe)?\",", + MR_Next}, + {"\"num_cpus\": %int,$", MR_Next}, + {"\"mhz_per_cpu\": %float,$", MR_Next}, + {"\"caches\": \\[$", MR_Default}}); + auto const& Info = benchmark::CPUInfo::Get(); + auto const& Caches = Info.caches; + if (!Caches.empty()) { + AddCases(TC_ConsoleErr, {{"CPU Caches:$", MR_Next}}); + } + for (size_t I = 0; I < Caches.size(); ++I) { + std::string num_caches_str = + Caches[I].num_sharing != 0 ? " \\(x%int\\)$" : "$"; + AddCases(TC_ConsoleErr, + {{"L%int (Data|Instruction|Unified) %int KiB" + num_caches_str, + MR_Next}}); + AddCases(TC_JSONOut, {{"\\{$", MR_Next}, + {"\"type\": \"", MR_Next}, + {"\"level\": %int,$", MR_Next}, + {"\"size\": %int,$", MR_Next}, + {"\"num_sharing\": %int$", MR_Next}, + {"}[,]{0,1}$", MR_Next}}); + } + AddCases(TC_JSONOut, {{"],$"}}); + auto const& LoadAvg = Info.load_avg; + if (!LoadAvg.empty()) { + AddCases(TC_ConsoleErr, + {{"Load Average: (%float, ){0,2}%float$", MR_Next}}); + } + AddCases(TC_JSONOut, {{"\"load_avg\": \\[(%float,?){0,3}],$", MR_Next}}); + return 0; +} +int dummy_register = AddContextCases(); +ADD_CASES(TC_CSVOut, {{"%csv_header"}}); + +// ========================================================================= // +// ------------------------ Testing Basic Output --------------------------- // +// ========================================================================= // + +void BM_basic(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_basic); + +ADD_CASES(TC_ConsoleOut, {{"^BM_basic %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_basic\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_basic\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_basic\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Bytes per Second Output ---------------- // +// ========================================================================= // + +void BM_bytes_per_second(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + state.SetBytesProcessed(1); +} +BENCHMARK(BM_bytes_per_second); + +ADD_CASES(TC_ConsoleOut, {{"^BM_bytes_per_second %console_report " + "bytes_per_second=%float[kM]{0,1}/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_bytes_per_second\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_bytes_per_second\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bytes_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_bytes_per_second\",%csv_bytes_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Items per Second Output ---------------- // +// ========================================================================= // + +void BM_items_per_second(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + state.SetItemsProcessed(1); +} +BENCHMARK(BM_items_per_second); + +ADD_CASES(TC_ConsoleOut, {{"^BM_items_per_second %console_report " + "items_per_second=%float[kM]{0,1}/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_items_per_second\",$"}, + {"\"family_index\": 2,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_items_per_second\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"items_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_items_per_second\",%csv_items_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Label Output --------------------------- // +// ========================================================================= // + +void BM_label(benchmark::State& state) { + for (auto _ : state) { + } + state.SetLabel("some label"); +} +BENCHMARK(BM_label); + +ADD_CASES(TC_ConsoleOut, {{"^BM_label %console_report some label$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_label\",$"}, + {"\"family_index\": 3,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_label\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"label\": \"some label\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_label\",%csv_label_report_begin\"some " + "label\"%csv_label_report_end$"}}); + +// ========================================================================= // +// ------------------------ Testing Time Label Output ---------------------- // +// ========================================================================= // + +void BM_time_label_nanosecond(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_time_label_nanosecond)->Unit(benchmark::kNanosecond); + +ADD_CASES(TC_ConsoleOut, {{"^BM_time_label_nanosecond %console_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_time_label_nanosecond\",$"}, + {"\"family_index\": 4,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_time_label_nanosecond\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_time_label_nanosecond\",%csv_report$"}}); + +void BM_time_label_microsecond(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_time_label_microsecond)->Unit(benchmark::kMicrosecond); + +ADD_CASES(TC_ConsoleOut, {{"^BM_time_label_microsecond %console_us_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_time_label_microsecond\",$"}, + {"\"family_index\": 5,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_time_label_microsecond\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"us\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_time_label_microsecond\",%csv_us_report$"}}); + +void BM_time_label_millisecond(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_time_label_millisecond)->Unit(benchmark::kMillisecond); + +ADD_CASES(TC_ConsoleOut, {{"^BM_time_label_millisecond %console_ms_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_time_label_millisecond\",$"}, + {"\"family_index\": 6,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_time_label_millisecond\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ms\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_time_label_millisecond\",%csv_ms_report$"}}); + +void BM_time_label_second(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_time_label_second)->Unit(benchmark::kSecond); + +ADD_CASES(TC_ConsoleOut, {{"^BM_time_label_second %console_s_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_time_label_second\",$"}, + {"\"family_index\": 7,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_time_label_second\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"s\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_time_label_second\",%csv_s_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Error Output --------------------------- // +// ========================================================================= // + +void BM_error(benchmark::State& state) { + state.SkipWithError("message"); + for (auto _ : state) { + } +} +BENCHMARK(BM_error); +ADD_CASES(TC_ConsoleOut, {{"^BM_error[ ]+ERROR OCCURRED: 'message'$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_error\",$"}, + {"\"family_index\": 8,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_error\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"error_occurred\": true,$", MR_Next}, + {"\"error_message\": \"message\",$", MR_Next}}); + +ADD_CASES(TC_CSVOut, {{"^\"BM_error\",,,,,,,,true,\"message\"$"}}); + +// ========================================================================= // +// ------------------------ Testing No Arg Name Output ----------------------- +// // +// ========================================================================= // + +void BM_no_arg_name(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_no_arg_name)->Arg(3); +ADD_CASES(TC_ConsoleOut, {{"^BM_no_arg_name/3 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_no_arg_name/3\",$"}, + {"\"family_index\": 9,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_no_arg_name/3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_no_arg_name/3\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Arg Name Output ----------------------- // +// ========================================================================= // + +void BM_arg_name(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_arg_name)->ArgName("first")->Arg(3); +ADD_CASES(TC_ConsoleOut, {{"^BM_arg_name/first:3 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_arg_name/first:3\",$"}, + {"\"family_index\": 10,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_arg_name/first:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_arg_name/first:3\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Arg Names Output ----------------------- // +// ========================================================================= // + +void BM_arg_names(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_arg_names)->Args({2, 5, 4})->ArgNames({"first", "", "third"}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_arg_names/first:2/5/third:4 %console_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_arg_names/first:2/5/third:4\",$"}, + {"\"family_index\": 11,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_arg_names/first:2/5/third:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_arg_names/first:2/5/third:4\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Name Output ---------------------------- // +// ========================================================================= // + +void BM_name(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_name)->Name("BM_custom_name"); + +ADD_CASES(TC_ConsoleOut, {{"^BM_custom_name %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_custom_name\",$"}, + {"\"family_index\": 12,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_custom_name\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_custom_name\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Big Args Output ------------------------ // +// ========================================================================= // + +void BM_BigArgs(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_BigArgs)->RangeMultiplier(2)->Range(1U << 30U, 1U << 31U); +ADD_CASES(TC_ConsoleOut, {{"^BM_BigArgs/1073741824 %console_report$"}, + {"^BM_BigArgs/2147483648 %console_report$"}}); + +// ========================================================================= // +// ----------------------- Testing Complexity Output ----------------------- // +// ========================================================================= // + +void BM_Complexity_O1(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(benchmark::o1); +SET_SUBSTITUTIONS({{"%bigOStr", "[ ]* %float \\([0-9]+\\)"}, + {"%RMS", "[ ]*[0-9]+ %"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_Complexity_O1_BigO %bigOStr %bigOStr[ ]*$"}, + {"^BM_Complexity_O1_RMS %RMS %RMS[ ]*$"}}); + +// ========================================================================= // +// ----------------------- Testing Aggregate Output ------------------------ // +// ========================================================================= // + +// Test that non-aggregate data is printed by default +void BM_Repeat(benchmark::State& state) { + for (auto _ : state) { + } +} +// need two repetitions min to be able to output any aggregate output +BENCHMARK(BM_Repeat)->Repetitions(2); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:2 %console_report$"}, + {"^BM_Repeat/repeats:2 %console_report$"}, + {"^BM_Repeat/repeats:2_mean %console_time_only_report [ ]*2$"}, + {"^BM_Repeat/repeats:2_median %console_time_only_report [ ]*2$"}, + {"^BM_Repeat/repeats:2_stddev %console_time_only_report [ ]*2$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:2\",$"}, + {"\"family_index\": 15,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:2\"", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2\",$"}, + {"\"family_index\": 15,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_mean\",$"}, + {"\"family_index\": 15,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_median\",$"}, + {"\"family_index\": 15,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_stddev\",$"}, + {"\"family_index\": 15,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:2\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_stddev\",%csv_report$"}}); +// but for two repetitions, mean and median is the same, so let's repeat.. +BENCHMARK(BM_Repeat)->Repetitions(3); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3_mean %console_time_only_report [ ]*3$"}, + {"^BM_Repeat/repeats:3_median %console_time_only_report [ ]*3$"}, + {"^BM_Repeat/repeats:3_stddev %console_time_only_report [ ]*3$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_mean\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_median\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_stddev\",$"}, + {"\"family_index\": 16,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_stddev\",%csv_report$"}}); +// median differs between even/odd number of repetitions, so just to be sure +BENCHMARK(BM_Repeat)->Repetitions(4); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4_mean %console_time_only_report [ ]*4$"}, + {"^BM_Repeat/repeats:4_median %console_time_only_report [ ]*4$"}, + {"^BM_Repeat/repeats:4_stddev %console_time_only_report [ ]*4$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_mean\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_median\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_stddev\",$"}, + {"\"family_index\": 17,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_stddev\",%csv_report$"}}); + +// Test that a non-repeated test still prints non-aggregate results even when +// only-aggregate reports have been requested +void BM_RepeatOnce(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_RepeatOnce)->Repetitions(1)->ReportAggregatesOnly(); +ADD_CASES(TC_ConsoleOut, {{"^BM_RepeatOnce/repeats:1 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_RepeatOnce/repeats:1\",$"}, + {"\"family_index\": 18,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_RepeatOnce/repeats:1\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_RepeatOnce/repeats:1\",%csv_report$"}}); + +// Test that non-aggregate data is not reported +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->ReportAggregatesOnly(); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"^BM_SummaryRepeat/repeats:3_mean %console_time_only_report [ ]*3$"}, + {"^BM_SummaryRepeat/repeats:3_median %console_time_only_report [ ]*3$"}, + {"^BM_SummaryRepeat/repeats:3_stddev %console_time_only_report [ ]*3$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_mean\",$"}, + {"\"family_index\": 19,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_median\",$"}, + {"\"family_index\": 19,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_stddev\",$"}, + {"\"family_index\": 19,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"^\"BM_SummaryRepeat/repeats:3_mean\",%csv_report$"}, + {"^\"BM_SummaryRepeat/repeats:3_median\",%csv_report$"}, + {"^\"BM_SummaryRepeat/repeats:3_stddev\",%csv_report$"}}); + +// Test that non-aggregate data is not displayed. +// NOTE: this test is kinda bad. we are only testing the display output. +// But we don't check that the file output still contains everything... +void BM_SummaryDisplay(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryDisplay)->Repetitions(2)->DisplayAggregatesOnly(); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"^BM_SummaryDisplay/repeats:2_mean %console_time_only_report [ ]*2$"}, + {"^BM_SummaryDisplay/repeats:2_median %console_time_only_report [ ]*2$"}, + {"^BM_SummaryDisplay/repeats:2_stddev %console_time_only_report [ ]*2$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_mean\",$"}, + {"\"family_index\": 20,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_median\",$"}, + {"\"family_index\": 20,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_stddev\",$"}, + {"\"family_index\": 20,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"^\"BM_SummaryDisplay/repeats:2_mean\",%csv_report$"}, + {"^\"BM_SummaryDisplay/repeats:2_median\",%csv_report$"}, + {"^\"BM_SummaryDisplay/repeats:2_stddev\",%csv_report$"}}); + +// Test repeats with custom time unit. +void BM_RepeatTimeUnit(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_RepeatTimeUnit) + ->Repetitions(3) + ->ReportAggregatesOnly() + ->Unit(benchmark::kMicrosecond); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"^BM_RepeatTimeUnit/repeats:3_mean %console_us_time_only_report [ ]*3$"}, + {"^BM_RepeatTimeUnit/repeats:3_median %console_us_time_only_report [ " + "]*3$"}, + {"^BM_RepeatTimeUnit/repeats:3_stddev %console_us_time_only_report [ " + "]*3$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_mean\",$"}, + {"\"family_index\": 21,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_median\",$"}, + {"\"family_index\": 21,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_stddev\",$"}, + {"\"family_index\": 21,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}}); +ADD_CASES(TC_CSVOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"^\"BM_RepeatTimeUnit/repeats:3_mean\",%csv_us_report$"}, + {"^\"BM_RepeatTimeUnit/repeats:3_median\",%csv_us_report$"}, + {"^\"BM_RepeatTimeUnit/repeats:3_stddev\",%csv_us_report$"}}); + +// ========================================================================= // +// -------------------- Testing user-provided statistics ------------------- // +// ========================================================================= // + +const auto UserStatistics = [](const std::vector& v) { + return v.back(); +}; +void BM_UserStats(benchmark::State& state) { + for (auto _ : state) { + state.SetIterationTime(150 / 10e8); + } +} +// clang-format off +BENCHMARK(BM_UserStats) + ->Repetitions(3) + ->Iterations(5) + ->UseManualTime() + ->ComputeStatistics("", UserStatistics); +// clang-format on + +// check that user-provided stats is calculated, and is after the default-ones +// empty string as name is intentional, it would sort before anything else +ADD_CASES(TC_ConsoleOut, {{"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_mean [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_median [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_stddev [ ]* 0.000 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time_ " + "[ ]* 150 ns %time [ ]*3$"}}); +ADD_CASES( + TC_JSONOut, + {{"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_mean\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_median\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_stddev\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_\",$"}, + {"\"family_index\": 22,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time_mean\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/" + "manual_time_median\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/" + "manual_time_stddev\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}}); + +// ========================================================================= // +// ------------- Testing relative standard deviation statistics ------------ // +// ========================================================================= // + +const auto UserPercentStatistics = [](const std::vector&) { + return 1. / 100.; +}; +void BM_UserPercentStats(benchmark::State& state) { + for (auto _ : state) { + state.SetIterationTime(150 / 10e8); + } +} +// clang-format off +BENCHMARK(BM_UserPercentStats) + ->Repetitions(3) + ->Iterations(5) + ->UseManualTime() + ->Unit(benchmark::TimeUnit::kNanosecond) + ->ComputeStatistics("", UserPercentStatistics, benchmark::StatisticUnit::kPercentage); +// clang-format on + +// check that UserPercent-provided stats is calculated, and is after the +// default-ones empty string as name is intentional, it would sort before +// anything else +ADD_CASES(TC_ConsoleOut, + {{"^BM_UserPercentStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_mean [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_median [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_stddev [ ]* 0.000 ns %time [ ]*3$"}, + {"^BM_UserPercentStats/iterations:5/repeats:3/manual_time_ " + "[ ]* 1.00 % [ ]* 1.00 %[ ]*3$"}}); +ADD_CASES( + TC_JSONOut, + {{"\"name\": \"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time_mean\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time_median\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time_stddev\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time_\",$"}, + {"\"family_index\": 23,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": " + "\"BM_UserPercentStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"\",$", MR_Next}, + {"\"aggregate_unit\": \"percentage\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.(0)*e-(0)*2,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_mean\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_median\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_stddev\",%csv_report$"}, + {"^\"BM_UserPercentStats/iterations:5/repeats:3/" + "manual_time_\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------- Testing StrEscape JSON ------------------------ // +// ========================================================================= // +#if 0 // enable when csv testing code correctly handles multi-line fields +void BM_JSON_Format(benchmark::State& state) { + state.SkipWithError("val\b\f\n\r\t\\\"with\"es,capes"); + for (auto _ : state) { + } +} +BENCHMARK(BM_JSON_Format); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_JSON_Format\",$"}, + {"\"family_index\": 23,$", MR_Next}, +{"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_JSON_Format\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"error_occurred\": true,$", MR_Next}, + {R"("error_message": "val\\b\\f\\n\\r\\t\\\\\\"with\\"es,capes",$)", MR_Next}}); +#endif +// ========================================================================= // +// -------------------------- Testing CsvEscape ---------------------------- // +// ========================================================================= // + +void BM_CSV_Format(benchmark::State& state) { + state.SkipWithError("\"freedom\""); + for (auto _ : state) { + } +} +BENCHMARK(BM_CSV_Format); +ADD_CASES(TC_CSVOut, {{"^\"BM_CSV_Format\",,,,,,,,true,\"\"\"freedom\"\"\"$"}}); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/skip_with_error_test.cc b/bridge/third_party/benchmark/test/skip_with_error_test.cc new file mode 100644 index 0000000000..026d479133 --- /dev/null +++ b/bridge/third_party/benchmark/test/skip_with_error_test.cc @@ -0,0 +1,196 @@ + +#undef NDEBUG +#include +#include + +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "benchmark/benchmark.h" + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE { + return ConsoleReporter::ReportContext(context); + }; + + virtual void ReportRuns(const std::vector& report) BENCHMARK_OVERRIDE { + all_runs_.insert(all_runs_.end(), begin(report), end(report)); + ConsoleReporter::ReportRuns(report); + } + + TestReporter() {} + virtual ~TestReporter() {} + + mutable std::vector all_runs_; +}; + +struct TestCase { + std::string name; + bool error_occurred; + std::string error_message; + + typedef benchmark::BenchmarkReporter::Run Run; + + void CheckRun(Run const& run) const { + BM_CHECK(name == run.benchmark_name()) + << "expected " << name << " got " << run.benchmark_name(); + BM_CHECK(error_occurred == run.error_occurred); + BM_CHECK(error_message == run.error_message); + if (error_occurred) { + // BM_CHECK(run.iterations == 0); + } else { + BM_CHECK(run.iterations != 0); + } + } +}; + +std::vector ExpectedResults; + +int AddCases(const char* base_name, std::initializer_list const& v) { + for (auto TC : v) { + TC.name = base_name + TC.name; + ExpectedResults.push_back(std::move(TC)); + } + return 0; +} + +#define CONCAT(x, y) CONCAT2(x, y) +#define CONCAT2(x, y) x##y +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = AddCases(__VA_ARGS__) + +} // end namespace + +void BM_error_no_running(benchmark::State& state) { + state.SkipWithError("error message"); +} +BENCHMARK(BM_error_no_running); +ADD_CASES("BM_error_no_running", {{"", true, "error message"}}); + +void BM_error_before_running(benchmark::State& state) { + state.SkipWithError("error message"); + while (state.KeepRunning()) { + assert(false); + } +} +BENCHMARK(BM_error_before_running); +ADD_CASES("BM_error_before_running", {{"", true, "error message"}}); + +void BM_error_before_running_batch(benchmark::State& state) { + state.SkipWithError("error message"); + while (state.KeepRunningBatch(17)) { + assert(false); + } +} +BENCHMARK(BM_error_before_running_batch); +ADD_CASES("BM_error_before_running_batch", {{"", true, "error message"}}); + +void BM_error_before_running_range_for(benchmark::State& state) { + state.SkipWithError("error message"); + for (auto _ : state) { + assert(false); + } +} +BENCHMARK(BM_error_before_running_range_for); +ADD_CASES("BM_error_before_running_range_for", {{"", true, "error message"}}); + +void BM_error_during_running(benchmark::State& state) { + int first_iter = true; + while (state.KeepRunning()) { + if (state.range(0) == 1 && state.thread_index() <= (state.threads() / 2)) { + assert(first_iter); + first_iter = false; + state.SkipWithError("error message"); + } else { + state.PauseTiming(); + state.ResumeTiming(); + } + } +} +BENCHMARK(BM_error_during_running)->Arg(1)->Arg(2)->ThreadRange(1, 8); +ADD_CASES("BM_error_during_running", {{"/1/threads:1", true, "error message"}, + {"/1/threads:2", true, "error message"}, + {"/1/threads:4", true, "error message"}, + {"/1/threads:8", true, "error message"}, + {"/2/threads:1", false, ""}, + {"/2/threads:2", false, ""}, + {"/2/threads:4", false, ""}, + {"/2/threads:8", false, ""}}); + +void BM_error_during_running_ranged_for(benchmark::State& state) { + assert(state.max_iterations > 3 && "test requires at least a few iterations"); + bool first_iter = true; + // NOTE: Users should not write the for loop explicitly. + for (auto It = state.begin(), End = state.end(); It != End; ++It) { + if (state.range(0) == 1) { + assert(first_iter); + first_iter = false; + (void)first_iter; + state.SkipWithError("error message"); + // Test the unfortunate but documented behavior that the ranged-for loop + // doesn't automatically terminate when SkipWithError is set. + assert(++It != End); + break; // Required behavior + } + } +} +BENCHMARK(BM_error_during_running_ranged_for)->Arg(1)->Arg(2)->Iterations(5); +ADD_CASES("BM_error_during_running_ranged_for", + {{"/1/iterations:5", true, "error message"}, + {"/2/iterations:5", false, ""}}); + +void BM_error_after_running(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } + if (state.thread_index() <= (state.threads() / 2)) + state.SkipWithError("error message"); +} +BENCHMARK(BM_error_after_running)->ThreadRange(1, 8); +ADD_CASES("BM_error_after_running", {{"/threads:1", true, "error message"}, + {"/threads:2", true, "error message"}, + {"/threads:4", true, "error message"}, + {"/threads:8", true, "error message"}}); + +void BM_error_while_paused(benchmark::State& state) { + bool first_iter = true; + while (state.KeepRunning()) { + if (state.range(0) == 1 && state.thread_index() <= (state.threads() / 2)) { + assert(first_iter); + first_iter = false; + state.PauseTiming(); + state.SkipWithError("error message"); + } else { + state.PauseTiming(); + state.ResumeTiming(); + } + } +} +BENCHMARK(BM_error_while_paused)->Arg(1)->Arg(2)->ThreadRange(1, 8); +ADD_CASES("BM_error_while_paused", {{"/1/threads:1", true, "error message"}, + {"/1/threads:2", true, "error message"}, + {"/1/threads:4", true, "error message"}, + {"/1/threads:8", true, "error message"}, + {"/2/threads:1", false, ""}, + {"/2/threads:2", false, ""}, + {"/2/threads:4", false, ""}, + {"/2/threads:8", false, ""}}); + +int main(int argc, char* argv[]) { + benchmark::Initialize(&argc, argv); + + TestReporter test_reporter; + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); + + return 0; +} diff --git a/bridge/third_party/benchmark/test/spec_arg_test.cc b/bridge/third_party/benchmark/test/spec_arg_test.cc new file mode 100644 index 0000000000..043db1be47 --- /dev/null +++ b/bridge/third_party/benchmark/test/spec_arg_test.cc @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +// Tests that we can override benchmark-spec value from FLAGS_benchmark_filter +// with argument to RunSpecifiedBenchmarks(...). + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual bool ReportContext(const Context& context) BENCHMARK_OVERRIDE { + return ConsoleReporter::ReportContext(context); + }; + + virtual void ReportRuns(const std::vector& report) BENCHMARK_OVERRIDE { + assert(report.size() == 1); + matched_functions.push_back(report[0].run_name.function_name); + ConsoleReporter::ReportRuns(report); + }; + + TestReporter() {} + + virtual ~TestReporter() {} + + const std::vector& GetMatchedFunctions() const { + return matched_functions; + } + + private: + std::vector matched_functions; +}; + +} // end namespace + +static void BM_NotChosen(benchmark::State& state) { + assert(false && "SHOULD NOT BE CALLED"); + for (auto _ : state) { + } +} +BENCHMARK(BM_NotChosen); + +static void BM_Chosen(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_Chosen); + +int main(int argc, char** argv) { + const std::string flag = "BM_NotChosen"; + + // Verify that argv specify --benchmark_filter=BM_NotChosen. + bool found = false; + for (int i = 0; i < argc; ++i) { + if (strcmp("--benchmark_filter=BM_NotChosen", argv[i]) == 0) { + found = true; + break; + } + } + assert(found); + + benchmark::Initialize(&argc, argv); + + // Check that the current flag value is reported accurately via the + // GetBenchmarkFilter() function. + if (flag != benchmark::GetBenchmarkFilter()) { + std::cerr + << "Seeing different value for flags. GetBenchmarkFilter() returns [" + << benchmark::GetBenchmarkFilter() << "] expected flag=[" << flag + << "]\n"; + return 1; + } + TestReporter test_reporter; + const char* const spec = "BM_Chosen"; + const size_t returned_count = + benchmark::RunSpecifiedBenchmarks(&test_reporter, spec); + assert(returned_count == 1); + const std::vector matched_functions = + test_reporter.GetMatchedFunctions(); + assert(matched_functions.size() == 1); + if (strcmp(spec, matched_functions.front().c_str()) != 0) { + std::cerr << "Expected benchmark [" << spec << "] to run, but got [" + << matched_functions.front() << "]\n"; + return 2; + } + return 0; +} diff --git a/bridge/third_party/benchmark/test/state_assembly_test.cc b/bridge/third_party/benchmark/test/state_assembly_test.cc new file mode 100644 index 0000000000..7ddbb3b2a9 --- /dev/null +++ b/bridge/third_party/benchmark/test/state_assembly_test.cc @@ -0,0 +1,68 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +// clang-format off +extern "C" { + extern int ExternInt; + benchmark::State& GetState(); + void Fn(); +} +// clang-format on + +using benchmark::State; + +// CHECK-LABEL: test_for_auto_loop: +extern "C" int test_for_auto_loop() { + State& S = GetState(); + int x = 42; + // CHECK: [[CALL:call(q)*]] _ZN9benchmark5State16StartKeepRunningEv + // CHECK-NEXT: testq %rbx, %rbx + // CHECK-NEXT: je [[LOOP_END:.*]] + + for (auto _ : S) { + // CHECK: .L[[LOOP_HEAD:[a-zA-Z0-9_]+]]: + // CHECK-GNU-NEXT: subq $1, %rbx + // CHECK-CLANG-NEXT: {{(addq \$1, %rax|incq %rax|addq \$-1, %rbx)}} + // CHECK-NEXT: jne .L[[LOOP_HEAD]] + benchmark::DoNotOptimize(x); + } + // CHECK: [[LOOP_END]]: + // CHECK: [[CALL]] _ZN9benchmark5State17FinishKeepRunningEv + + // CHECK: movl $101, %eax + // CHECK: ret + return 101; +} + +// CHECK-LABEL: test_while_loop: +extern "C" int test_while_loop() { + State& S = GetState(); + int x = 42; + + // CHECK: j{{(e|mp)}} .L[[LOOP_HEADER:[a-zA-Z0-9_]+]] + // CHECK-NEXT: .L[[LOOP_BODY:[a-zA-Z0-9_]+]]: + while (S.KeepRunning()) { + // CHECK-GNU-NEXT: subq $1, %[[IREG:[a-z]+]] + // CHECK-CLANG-NEXT: {{(addq \$-1,|decq)}} %[[IREG:[a-z]+]] + // CHECK: movq %[[IREG]], [[DEST:.*]] + benchmark::DoNotOptimize(x); + } + // CHECK-DAG: movq [[DEST]], %[[IREG]] + // CHECK-DAG: testq %[[IREG]], %[[IREG]] + // CHECK-DAG: jne .L[[LOOP_BODY]] + // CHECK-DAG: .L[[LOOP_HEADER]]: + + // CHECK: cmpb $0 + // CHECK-NEXT: jne .L[[LOOP_END:[a-zA-Z0-9_]+]] + // CHECK: [[CALL:call(q)*]] _ZN9benchmark5State16StartKeepRunningEv + + // CHECK: .L[[LOOP_END]]: + // CHECK: [[CALL]] _ZN9benchmark5State17FinishKeepRunningEv + + // CHECK: movl $101, %eax + // CHECK: ret + return 101; +} diff --git a/bridge/third_party/benchmark/test/statistics_gtest.cc b/bridge/third_party/benchmark/test/statistics_gtest.cc new file mode 100644 index 0000000000..1de2d87d4b --- /dev/null +++ b/bridge/third_party/benchmark/test/statistics_gtest.cc @@ -0,0 +1,35 @@ +//===---------------------------------------------------------------------===// +// statistics_test - Unit tests for src/statistics.cc +//===---------------------------------------------------------------------===// + +#include "../src/statistics.h" +#include "gtest/gtest.h" + +namespace { +TEST(StatisticsTest, Mean) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({42, 42, 42, 42}), 42.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({1, 2, 3, 4}), 2.5); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({1, 2, 5, 10, 10, 14}), 7.0); +} + +TEST(StatisticsTest, Median) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({42, 42, 42, 42}), 42.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({1, 2, 3, 4}), 2.5); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({1, 2, 5, 10, 10}), 5.0); +} + +TEST(StatisticsTest, StdDev) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsStdDev({101, 101, 101, 101}), 0.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsStdDev({1, 2, 3}), 1.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsStdDev({2.5, 2.4, 3.3, 4.2, 5.1}), + 1.151086443322134); +} + +TEST(StatisticsTest, CV) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsCV({101, 101, 101, 101}), 0.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsCV({1, 2, 3}), 1. / 2.); + EXPECT_DOUBLE_EQ(benchmark::StatisticsCV({2.5, 2.4, 3.3, 4.2, 5.1}), + 0.32888184094918121); +} + +} // end namespace diff --git a/bridge/third_party/benchmark/test/string_util_gtest.cc b/bridge/third_party/benchmark/test/string_util_gtest.cc new file mode 100644 index 0000000000..698f2d43eb --- /dev/null +++ b/bridge/third_party/benchmark/test/string_util_gtest.cc @@ -0,0 +1,152 @@ +//===---------------------------------------------------------------------===// +// statistics_test - Unit tests for src/statistics.cc +//===---------------------------------------------------------------------===// + +#include "../src/internal_macros.h" +#include "../src/string_util.h" +#include "gtest/gtest.h" + +namespace { +TEST(StringUtilTest, stoul) { + { + size_t pos = 0; + EXPECT_EQ(0ul, benchmark::stoul("0", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(7ul, benchmark::stoul("7", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(135ul, benchmark::stoul("135", &pos)); + EXPECT_EQ(3ul, pos); + } +#if ULONG_MAX == 0xFFFFFFFFul + { + size_t pos = 0; + EXPECT_EQ(0xFFFFFFFFul, benchmark::stoul("4294967295", &pos)); + EXPECT_EQ(10ul, pos); + } +#elif ULONG_MAX == 0xFFFFFFFFFFFFFFFFul + { + size_t pos = 0; + EXPECT_EQ(0xFFFFFFFFFFFFFFFFul, + benchmark::stoul("18446744073709551615", &pos)); + EXPECT_EQ(20ul, pos); + } +#endif + { + size_t pos = 0; + EXPECT_EQ(10ul, benchmark::stoul("1010", &pos, 2)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(520ul, benchmark::stoul("1010", &pos, 8)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1010ul, benchmark::stoul("1010", &pos, 10)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(4112ul, benchmark::stoul("1010", &pos, 16)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(0xBEEFul, benchmark::stoul("BEEF", &pos, 16)); + EXPECT_EQ(4ul, pos); + } +#ifndef BENCHMARK_HAS_NO_EXCEPTIONS + { ASSERT_THROW(benchmark::stoul("this is a test"), std::invalid_argument); } +#endif +} + +TEST(StringUtilTest, stoi){{size_t pos = 0; +EXPECT_EQ(0, benchmark::stoi("0", &pos)); +EXPECT_EQ(1ul, pos); +} // namespace +{ + size_t pos = 0; + EXPECT_EQ(-17, benchmark::stoi("-17", &pos)); + EXPECT_EQ(3ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(1357, benchmark::stoi("1357", &pos)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(10, benchmark::stoi("1010", &pos, 2)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(520, benchmark::stoi("1010", &pos, 8)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(1010, benchmark::stoi("1010", &pos, 10)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(4112, benchmark::stoi("1010", &pos, 16)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(0xBEEF, benchmark::stoi("BEEF", &pos, 16)); + EXPECT_EQ(4ul, pos); +} +#ifndef BENCHMARK_HAS_NO_EXCEPTIONS +{ ASSERT_THROW(benchmark::stoi("this is a test"), std::invalid_argument); } +#endif +} + +TEST(StringUtilTest, stod){{size_t pos = 0; +EXPECT_EQ(0.0, benchmark::stod("0", &pos)); +EXPECT_EQ(1ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(-84.0, benchmark::stod("-84", &pos)); + EXPECT_EQ(3ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(1234.0, benchmark::stod("1234", &pos)); + EXPECT_EQ(4ul, pos); +} +{ + size_t pos = 0; + EXPECT_EQ(1.5, benchmark::stod("1.5", &pos)); + EXPECT_EQ(3ul, pos); +} +{ + size_t pos = 0; + /* Note: exactly representable as double */ + EXPECT_EQ(-1.25e+9, benchmark::stod("-1.25e+9", &pos)); + EXPECT_EQ(8ul, pos); +} +#ifndef BENCHMARK_HAS_NO_EXCEPTIONS +{ ASSERT_THROW(benchmark::stod("this is a test"), std::invalid_argument); } +#endif +} + +TEST(StringUtilTest, StrSplit) { + EXPECT_EQ(benchmark::StrSplit("", ','), std::vector{}); + EXPECT_EQ(benchmark::StrSplit("hello", ','), + std::vector({"hello"})); + EXPECT_EQ(benchmark::StrSplit("hello,there,is,more", ','), + std::vector({"hello", "there", "is", "more"})); +} + +} // end namespace diff --git a/bridge/third_party/benchmark/test/templated_fixture_test.cc b/bridge/third_party/benchmark/test/templated_fixture_test.cc new file mode 100644 index 0000000000..af239c3a72 --- /dev/null +++ b/bridge/third_party/benchmark/test/templated_fixture_test.cc @@ -0,0 +1,28 @@ + +#include +#include + +#include "benchmark/benchmark.h" + +template +class MyFixture : public ::benchmark::Fixture { + public: + MyFixture() : data(0) {} + + T data; +}; + +BENCHMARK_TEMPLATE_F(MyFixture, Foo, int)(benchmark::State& st) { + for (auto _ : st) { + data += 1; + } +} + +BENCHMARK_TEMPLATE_DEFINE_F(MyFixture, Bar, double)(benchmark::State& st) { + for (auto _ : st) { + data += 1.0; + } +} +BENCHMARK_REGISTER_F(MyFixture, Bar); + +BENCHMARK_MAIN(); diff --git a/bridge/third_party/benchmark/test/user_counters_tabular_test.cc b/bridge/third_party/benchmark/test/user_counters_tabular_test.cc new file mode 100644 index 0000000000..45ac043d51 --- /dev/null +++ b/bridge/third_party/benchmark/test/user_counters_tabular_test.cc @@ -0,0 +1,557 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// @todo: this checks the full output at once; the rule for +// CounterSet1 was failing because it was not matching "^[-]+$". +// @todo: check that the counters are vertically aligned. +ADD_CASES(TC_ConsoleOut, + { + // keeping these lines long improves readability, so: + // clang-format off + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bar %s Bat %s Baz %s Foo %s Frob %s Lob$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1 %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1 %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1_mean %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1_median %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1_stddev %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:1_cv %console_percentage_report [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*%$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2 %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2 %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2_mean %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2_median %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2_stddev %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/repeats:2/threads:2_cv %console_percentage_report [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*% [ ]*%percentage[ ]*%$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bar %s Baz %s Foo$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bat %s Baz %s Foo$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$"}, + // clang-format on + }); +ADD_CASES(TC_CSVOut, {{"%csv_header," + "\"Bar\",\"Bat\",\"Baz\",\"Foo\",\"Frob\",\"Lob\""}}); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +void BM_Counters_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {1, bm::Counter::kAvgThreads}}, + {"Bar", {2, bm::Counter::kAvgThreads}}, + {"Baz", {4, bm::Counter::kAvgThreads}}, + {"Bat", {8, bm::Counter::kAvgThreads}}, + {"Frob", {16, bm::Counter::kAvgThreads}}, + {"Lob", {32, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_Counters_Tabular)->ThreadRange(1, 2)->Repetitions(2); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1_mean\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1_median\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1_stddev\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:1_cv\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:1\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"cv\",$", MR_Next}, + {"\"aggregate_unit\": \"percentage\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); + +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 1,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 2,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 1,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 2,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:2_median\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 1,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 2,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:2_stddev\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 1,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 2,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/repeats:2/threads:2_cv\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 1,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Tabular/repeats:2/threads:2\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 2,$", MR_Next}, + {"\"aggregate_name\": \"cv\",$", MR_Next}, + {"\"aggregate_unit\": \"percentage\",$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1_mean\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1_median\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1_stddev\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:1_cv\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2_mean\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2_median\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2_stddev\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_Tabular/repeats:2/threads:2_cv\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckTabular(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 2); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 4); + CHECK_COUNTER_VALUE(e, int, "Bat", EQ, 8); + CHECK_COUNTER_VALUE(e, int, "Frob", EQ, 16); + CHECK_COUNTER_VALUE(e, int, "Lob", EQ, 32); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Tabular/repeats:2/threads:1$", + &CheckTabular); +CHECK_BENCHMARK_RESULTS("BM_Counters_Tabular/repeats:2/threads:2$", + &CheckTabular); + +// ========================================================================= // +// -------------------- Tabular+Rate Counters Output ----------------------- // +// ========================================================================= // + +void BM_CounterRates_Tabular(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {1, bm::Counter::kAvgThreadsRate}}, + {"Bar", {2, bm::Counter::kAvgThreadsRate}}, + {"Baz", {4, bm::Counter::kAvgThreadsRate}}, + {"Bat", {8, bm::Counter::kAvgThreadsRate}}, + {"Frob", {16, bm::Counter::kAvgThreadsRate}}, + {"Lob", {32, bm::Counter::kAvgThreadsRate}}, + }); +} +BENCHMARK(BM_CounterRates_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterRates_Tabular/threads:%int\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_CounterRates_Tabular/threads:%int\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterRates_Tabular/threads:%int\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckTabularRate(Results const& e) { + double t = e.DurationCPUTime(); + CHECK_FLOAT_COUNTER_VALUE(e, "Foo", EQ, 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Bar", EQ, 2. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Baz", EQ, 4. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Bat", EQ, 8. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Frob", EQ, 16. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Lob", EQ, 32. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_CounterRates_Tabular/threads:%int", + &CheckTabularRate); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +// set only some of the counters +void BM_CounterSet0_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {10, bm::Counter::kAvgThreads}}, + {"Bar", {20, bm::Counter::kAvgThreads}}, + {"Baz", {40, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet0_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet0_Tabular/threads:%int\",$"}, + {"\"family_index\": 2,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_CounterSet0_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet0_Tabular/threads:%int\",%csv_report," + "%float,,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet0(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 10); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 20); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 40); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet0_Tabular", &CheckSet0); + +// again. +void BM_CounterSet1_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {15, bm::Counter::kAvgThreads}}, + {"Bar", {25, bm::Counter::kAvgThreads}}, + {"Baz", {45, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet1_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet1_Tabular/threads:%int\",$"}, + {"\"family_index\": 3,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_CounterSet1_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet1_Tabular/threads:%int\",%csv_report," + "%float,,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet1(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 15); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 25); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 45); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet1_Tabular/threads:%int", &CheckSet1); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +// set only some of the counters, different set now. +void BM_CounterSet2_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {10, bm::Counter::kAvgThreads}}, + {"Bat", {30, bm::Counter::kAvgThreads}}, + {"Baz", {40, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet2_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet2_Tabular/threads:%int\",$"}, + {"\"family_index\": 4,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_CounterSet2_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet2_Tabular/threads:%int\",%csv_report," + ",%float,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet2(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 10); + CHECK_COUNTER_VALUE(e, int, "Bat", EQ, 30); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 40); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet2_Tabular", &CheckSet2); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/user_counters_test.cc b/bridge/third_party/benchmark/test/user_counters_test.cc new file mode 100644 index 0000000000..1cc74552a1 --- /dev/null +++ b/bridge/third_party/benchmark/test/user_counters_test.cc @@ -0,0 +1,555 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ---------------------- Testing Prologue Output -------------------------- // +// ========================================================================= // + +// clang-format off + +ADD_CASES(TC_ConsoleOut, + {{"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations UserCounters...$", MR_Next}, + {"^[-]+$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"%csv_header,\"bar\",\"foo\""}}); + +// clang-format on + +// ========================================================================= // +// ------------------------- Simple Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_Simple(benchmark::State& state) { + for (auto _ : state) { + } + state.counters["foo"] = 1; + state.counters["bar"] = 2 * static_cast(state.iterations()); +} +BENCHMARK(BM_Counters_Simple); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Counters_Simple %console_report bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_Simple\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Simple\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Simple\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSimple(Results const& e) { + double its = e.NumIterations(); + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + // check that the value of bar is within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Simple", &CheckSimple); + +// ========================================================================= // +// --------------------- Counters+Items+Bytes/s Output --------------------- // +// ========================================================================= // + +namespace { +int num_calls1 = 0; +} +void BM_Counters_WithBytesAndItemsPSec(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + state.counters["foo"] = 1; + state.counters["bar"] = ++num_calls1; + state.SetBytesProcessed(364); + state.SetItemsProcessed(150); +} +BENCHMARK(BM_Counters_WithBytesAndItemsPSec); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_WithBytesAndItemsPSec %console_report " + "bar=%hrfloat bytes_per_second=%hrfloat/s " + "foo=%hrfloat items_per_second=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_WithBytesAndItemsPSec\",$"}, + {"\"family_index\": 1,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_WithBytesAndItemsPSec\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"bytes_per_second\": %float,$", MR_Next}, + {"\"foo\": %float,$", MR_Next}, + {"\"items_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_WithBytesAndItemsPSec\"," + "%csv_bytes_items_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckBytesAndItemsPSec(Results const& e) { + double t = e.DurationCPUTime(); // this (and not real time) is the time used + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, num_calls1); + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_RESULT_VALUE(e, "bytes_per_second", EQ, 364. / t, 0.001); + CHECK_FLOAT_RESULT_VALUE(e, "items_per_second", EQ, 150. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_WithBytesAndItemsPSec", + &CheckBytesAndItemsPSec); + +// ========================================================================= // +// ------------------------- Rate Counters Output -------------------------- // +// ========================================================================= // + +void BM_Counters_Rate(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kIsRate}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kIsRate}; +} +BENCHMARK(BM_Counters_Rate); +ADD_CASES( + TC_ConsoleOut, + {{"^BM_Counters_Rate %console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_Rate\",$"}, + {"\"family_index\": 2,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Rate\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Rate\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckRate(Results const& e) { + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Rate", &CheckRate); + +// ========================================================================= // +// ----------------------- Inverted Counters Output ------------------------ // +// ========================================================================= // + +void BM_Invert(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{0.0001, bm::Counter::kInvert}; + state.counters["bar"] = bm::Counter{10000, bm::Counter::kInvert}; +} +BENCHMARK(BM_Invert); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Invert %console_report bar=%hrfloatu foo=%hrfloatk$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Invert\",$"}, + {"\"family_index\": 3,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Invert\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Invert\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckInvert(Results const& e) { + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 10000, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 0.0001, 0.0001); +} +CHECK_BENCHMARK_RESULTS("BM_Invert", &CheckInvert); + +// ========================================================================= // +// ------------------------- InvertedRate Counters Output +// -------------------------- // +// ========================================================================= // + +void BM_Counters_InvertedRate(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = + bm::Counter{1, bm::Counter::kIsRate | bm::Counter::kInvert}; + state.counters["bar"] = + bm::Counter{8192, bm::Counter::kIsRate | bm::Counter::kInvert}; +} +BENCHMARK(BM_Counters_InvertedRate); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_InvertedRate %console_report " + "bar=%hrfloats foo=%hrfloats$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_InvertedRate\",$"}, + {"\"family_index\": 4,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_InvertedRate\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_InvertedRate\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckInvertedRate(Results const& e) { + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, t / 8192.0, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_InvertedRate", &CheckInvertedRate); + +// ========================================================================= // +// ------------------------- Thread Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_Threads(benchmark::State& state) { + for (auto _ : state) { + } + state.counters["foo"] = 1; + state.counters["bar"] = 2; +} +BENCHMARK(BM_Counters_Threads)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_Threads/threads:%int %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Threads/threads:%int\",$"}, + {"\"family_index\": 5,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Threads/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_Threads/threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckThreads(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "foo", EQ, e.NumThreads()); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, 2 * e.NumThreads()); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Threads/threads:%int", &CheckThreads); + +// ========================================================================= // +// ---------------------- ThreadAvg Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_AvgThreads(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgThreads}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgThreads}; +} +BENCHMARK(BM_Counters_AvgThreads)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgThreads/threads:%int " + "%console_report bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgThreads/threads:%int\",$"}, + {"\"family_index\": 6,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_AvgThreads/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_AvgThreads/threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgThreads(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, 2); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreads/threads:%int", + &CheckAvgThreads); + +// ========================================================================= // +// ---------------------- ThreadAvg Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_AvgThreadsRate(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgThreadsRate}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgThreadsRate}; +} +BENCHMARK(BM_Counters_AvgThreadsRate)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgThreadsRate/threads:%int " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgThreadsRate/threads:%int\",$"}, + {"\"family_index\": 7,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_AvgThreadsRate/threads:%int\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_AvgThreadsRate/" + "threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgThreadsRate(Results const& e) { + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / e.DurationCPUTime(), 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / e.DurationCPUTime(), 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreadsRate/threads:%int", + &CheckAvgThreadsRate); + +// ========================================================================= // +// ------------------- IterationInvariant Counters Output ------------------ // +// ========================================================================= // + +void BM_Counters_IterationInvariant(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kIsIterationInvariant}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kIsIterationInvariant}; +} +BENCHMARK(BM_Counters_IterationInvariant); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_IterationInvariant %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_IterationInvariant\",$"}, + {"\"family_index\": 8,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_IterationInvariant\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_IterationInvariant\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckIterationInvariant(Results const& e) { + double its = e.NumIterations(); + // check that the values are within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_IterationInvariant", + &CheckIterationInvariant); + +// ========================================================================= // +// ----------------- IterationInvariantRate Counters Output ---------------- // +// ========================================================================= // + +void BM_Counters_kIsIterationInvariantRate(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = + bm::Counter{1, bm::Counter::kIsIterationInvariantRate}; + state.counters["bar"] = + bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kIsIterationInvariant}; +} +BENCHMARK(BM_Counters_kIsIterationInvariantRate); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kIsIterationInvariantRate " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_kIsIterationInvariantRate\",$"}, + {"\"family_index\": 9,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_kIsIterationInvariantRate\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kIsIterationInvariantRate\",%csv_report," + "%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckIsIterationInvariantRate(Results const& e) { + double its = e.NumIterations(); + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its * 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, its * 2. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_kIsIterationInvariantRate", + &CheckIsIterationInvariantRate); + +// ========================================================================= // +// ------------------- AvgIterations Counters Output ------------------ // +// ========================================================================= // + +void BM_Counters_AvgIterations(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterations}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgIterations}; +} +BENCHMARK(BM_Counters_AvgIterations); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgIterations %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgIterations\",$"}, + {"\"family_index\": 10,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_AvgIterations\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_AvgIterations\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgIterations(Results const& e) { + double its = e.NumIterations(); + // check that the values are within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgIterations", &CheckAvgIterations); + +// ========================================================================= // +// ----------------- AvgIterationsRate Counters Output ---------------- // +// ========================================================================= // + +void BM_Counters_kAvgIterationsRate(benchmark::State& state) { + for (auto _ : state) { + // This test requires a non-zero CPU time to avoid divide-by-zero + benchmark::DoNotOptimize(state.iterations()); + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterationsRate}; + state.counters["bar"] = + bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kAvgIterations}; +} +BENCHMARK(BM_Counters_kAvgIterationsRate); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kAvgIterationsRate " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_kAvgIterationsRate\",$"}, + {"\"family_index\": 11,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_kAvgIterationsRate\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kAvgIterationsRate\",%csv_report," + "%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgIterationsRate(Results const& e) { + double its = e.NumIterations(); + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_kAvgIterationsRate", + &CheckAvgIterationsRate); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/test/user_counters_thousands_test.cc b/bridge/third_party/benchmark/test/user_counters_thousands_test.cc new file mode 100644 index 0000000000..a42683b32f --- /dev/null +++ b/bridge/third_party/benchmark/test/user_counters_thousands_test.cc @@ -0,0 +1,186 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ------------------------ Thousands Customisation ------------------------ // +// ========================================================================= // + +void BM_Counters_Thousands(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"t0_1000000DefaultBase", + bm::Counter(1000 * 1000, bm::Counter::kDefaults)}, + {"t1_1000000Base1000", bm::Counter(1000 * 1000, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1000)}, + {"t2_1000000Base1024", bm::Counter(1000 * 1000, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1024)}, + {"t3_1048576Base1000", bm::Counter(1024 * 1024, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1000)}, + {"t4_1048576Base1024", bm::Counter(1024 * 1024, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1024)}, + }); +} +BENCHMARK(BM_Counters_Thousands)->Repetitions(2); +ADD_CASES( + TC_ConsoleOut, + { + {"^BM_Counters_Thousands/repeats:2 %console_report " + "t0_1000000DefaultBase=1000k " + "t1_1000000Base1000=1000k t2_1000000Base1024=976.56[23]k " + "t3_1048576Base1000=1048.58k t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2 %console_report " + "t0_1000000DefaultBase=1000k " + "t1_1000000Base1000=1000k t2_1000000Base1024=976.56[23]k " + "t3_1048576Base1000=1048.58k t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_mean %console_report " + "t0_1000000DefaultBase=1000k t1_1000000Base1000=1000k " + "t2_1000000Base1024=976.56[23]k t3_1048576Base1000=1048.58k " + "t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_median %console_report " + "t0_1000000DefaultBase=1000k t1_1000000Base1000=1000k " + "t2_1000000Base1024=976.56[23]k t3_1048576Base1000=1048.58k " + "t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_stddev %console_time_only_report [ " + "]*2 t0_1000000DefaultBase=0 t1_1000000Base1000=0 " + "t2_1000000Base1024=0 t3_1048576Base1000=0 t4_1048576Base1024=0$"}, + }); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_mean\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_median\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_stddev\",$"}, + {"\"family_index\": 0,$", MR_Next}, + {"\"per_family_instance_index\": 0,$", MR_Next}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"aggregate_unit\": \"time\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t1_1000000Base1000\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t2_1000000Base1024\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t3_1048576Base1000\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t4_1048576Base1024\": 0\\.(0)*e\\+(0)*$", MR_Next}, + {"}", MR_Next}}); + +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_Thousands/" + "repeats:2\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\.04858e\\+(" + "0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\.04858e\\+(" + "0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2_mean\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\." + "04858e\\+(0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2_median\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\." + "04858e\\+(0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/repeats:2_stddev\",%csv_report,0,0,0,0,0$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckThousands(Results const& e) { + if (e.name != "BM_Counters_Thousands/repeats:2") + return; // Do not check the aggregates! + + // check that the values are within 0.01% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "t0_1000000DefaultBase", EQ, 1000 * 1000, + 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t1_1000000Base1000", EQ, 1000 * 1000, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t2_1000000Base1024", EQ, 1000 * 1000, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t3_1048576Base1000", EQ, 1024 * 1024, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t4_1048576Base1024", EQ, 1024 * 1024, 0.0001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Thousands", &CheckThousands); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/bridge/third_party/benchmark/tools/BUILD.bazel b/bridge/third_party/benchmark/tools/BUILD.bazel new file mode 100644 index 0000000000..5895883a2e --- /dev/null +++ b/bridge/third_party/benchmark/tools/BUILD.bazel @@ -0,0 +1,19 @@ +load("@py_deps//:requirements.bzl", "requirement") + +py_library( + name = "gbench", + srcs = glob(["gbench/*.py"]), + deps = [ + requirement("numpy"), + requirement("scipy"), + ], +) + +py_binary( + name = "compare", + srcs = ["compare.py"], + python_version = "PY2", + deps = [ + ":gbench", + ], +) diff --git a/bridge/third_party/benchmark/tools/compare.py b/bridge/third_party/benchmark/tools/compare.py new file mode 100755 index 0000000000..01d2c89f50 --- /dev/null +++ b/bridge/third_party/benchmark/tools/compare.py @@ -0,0 +1,429 @@ +#!/usr/bin/env python + +import unittest +""" +compare.py - versatile benchmark output compare tool +""" + +import argparse +from argparse import ArgumentParser +import json +import sys +import gbench +from gbench import util, report +from gbench.util import * + + +def check_inputs(in1, in2, flags): + """ + Perform checking on the user provided inputs and diagnose any abnormalities + """ + in1_kind, in1_err = classify_input_file(in1) + in2_kind, in2_err = classify_input_file(in2) + output_file = find_benchmark_flag('--benchmark_out=', flags) + output_type = find_benchmark_flag('--benchmark_out_format=', flags) + if in1_kind == IT_Executable and in2_kind == IT_Executable and output_file: + print(("WARNING: '--benchmark_out=%s' will be passed to both " + "benchmarks causing it to be overwritten") % output_file) + if in1_kind == IT_JSON and in2_kind == IT_JSON and len(flags) > 0: + print("WARNING: passing optional flags has no effect since both " + "inputs are JSON") + if output_type is not None and output_type != 'json': + print(("ERROR: passing '--benchmark_out_format=%s' to 'compare.py`" + " is not supported.") % output_type) + sys.exit(1) + + +def create_parser(): + parser = ArgumentParser( + description='versatile benchmark output compare tool') + + parser.add_argument( + '-a', + '--display_aggregates_only', + dest='display_aggregates_only', + action="store_true", + help="If there are repetitions, by default, we display everything - the" + " actual runs, and the aggregates computed. Sometimes, it is " + "desirable to only view the aggregates. E.g. when there are a lot " + "of repetitions. Do note that only the display is affected. " + "Internally, all the actual runs are still used, e.g. for U test.") + + parser.add_argument( + '--no-color', + dest='color', + default=True, + action="store_false", + help="Do not use colors in the terminal output" + ) + + parser.add_argument( + '-d', + '--dump_to_json', + dest='dump_to_json', + help="Additionally, dump benchmark comparison output to this file in JSON format.") + + utest = parser.add_argument_group() + utest.add_argument( + '--no-utest', + dest='utest', + default=True, + action="store_false", + help="The tool can do a two-tailed Mann-Whitney U test with the null hypothesis that it is equally likely that a randomly selected value from one sample will be less than or greater than a randomly selected value from a second sample.\nWARNING: requires **LARGE** (no less than {}) number of repetitions to be meaningful!\nThe test is being done by default, if at least {} repetitions were done.\nThis option can disable the U Test.".format(report.UTEST_OPTIMAL_REPETITIONS, report.UTEST_MIN_REPETITIONS)) + alpha_default = 0.05 + utest.add_argument( + "--alpha", + dest='utest_alpha', + default=alpha_default, + type=float, + help=("significance level alpha. if the calculated p-value is below this value, then the result is said to be statistically significant and the null hypothesis is rejected.\n(default: %0.4f)") % + alpha_default) + + subparsers = parser.add_subparsers( + help='This tool has multiple modes of operation:', + dest='mode') + + parser_a = subparsers.add_parser( + 'benchmarks', + help='The most simple use-case, compare all the output of these two benchmarks') + baseline = parser_a.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test_baseline', + metavar='test_baseline', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + contender = parser_a.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'test_contender', + metavar='test_contender', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + parser_a.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + parser_b = subparsers.add_parser( + 'filters', help='Compare filter one with the filter two of benchmark') + baseline = parser_b.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test', + metavar='test', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + baseline.add_argument( + 'filter_baseline', + metavar='filter_baseline', + type=str, + nargs=1, + help='The first filter, that will be used as baseline') + contender = parser_b.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'filter_contender', + metavar='filter_contender', + type=str, + nargs=1, + help='The second filter, that will be compared against the baseline') + parser_b.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + parser_c = subparsers.add_parser( + 'benchmarksfiltered', + help='Compare filter one of first benchmark with filter two of the second benchmark') + baseline = parser_c.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test_baseline', + metavar='test_baseline', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + baseline.add_argument( + 'filter_baseline', + metavar='filter_baseline', + type=str, + nargs=1, + help='The first filter, that will be used as baseline') + contender = parser_c.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'test_contender', + metavar='test_contender', + type=argparse.FileType('r'), + nargs=1, + help='The second benchmark executable or JSON output file, that will be compared against the baseline') + contender.add_argument( + 'filter_contender', + metavar='filter_contender', + type=str, + nargs=1, + help='The second filter, that will be compared against the baseline') + parser_c.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + return parser + + +def main(): + # Parse the command line flags + parser = create_parser() + args, unknown_args = parser.parse_known_args() + if args.mode is None: + parser.print_help() + exit(1) + assert not unknown_args + benchmark_options = args.benchmark_options + + if args.mode == 'benchmarks': + test_baseline = args.test_baseline[0].name + test_contender = args.test_contender[0].name + filter_baseline = '' + filter_contender = '' + + # NOTE: if test_baseline == test_contender, you are analyzing the stdev + + description = 'Comparing %s to %s' % (test_baseline, test_contender) + elif args.mode == 'filters': + test_baseline = args.test[0].name + test_contender = args.test[0].name + filter_baseline = args.filter_baseline[0] + filter_contender = args.filter_contender[0] + + # NOTE: if filter_baseline == filter_contender, you are analyzing the + # stdev + + description = 'Comparing %s to %s (from %s)' % ( + filter_baseline, filter_contender, args.test[0].name) + elif args.mode == 'benchmarksfiltered': + test_baseline = args.test_baseline[0].name + test_contender = args.test_contender[0].name + filter_baseline = args.filter_baseline[0] + filter_contender = args.filter_contender[0] + + # NOTE: if test_baseline == test_contender and + # filter_baseline == filter_contender, you are analyzing the stdev + + description = 'Comparing %s (from %s) to %s (from %s)' % ( + filter_baseline, test_baseline, filter_contender, test_contender) + else: + # should never happen + print("Unrecognized mode of operation: '%s'" % args.mode) + parser.print_help() + exit(1) + + check_inputs(test_baseline, test_contender, benchmark_options) + + if args.display_aggregates_only: + benchmark_options += ['--benchmark_display_aggregates_only=true'] + + options_baseline = [] + options_contender = [] + + if filter_baseline and filter_contender: + options_baseline = ['--benchmark_filter=%s' % filter_baseline] + options_contender = ['--benchmark_filter=%s' % filter_contender] + + # Run the benchmarks and report the results + json1 = json1_orig = gbench.util.sort_benchmark_results(gbench.util.run_or_load_benchmark( + test_baseline, benchmark_options + options_baseline)) + json2 = json2_orig = gbench.util.sort_benchmark_results(gbench.util.run_or_load_benchmark( + test_contender, benchmark_options + options_contender)) + + # Now, filter the benchmarks so that the difference report can work + if filter_baseline and filter_contender: + replacement = '[%s vs. %s]' % (filter_baseline, filter_contender) + json1 = gbench.report.filter_benchmark( + json1_orig, filter_baseline, replacement) + json2 = gbench.report.filter_benchmark( + json2_orig, filter_contender, replacement) + + diff_report = gbench.report.get_difference_report( + json1, json2, args.utest) + output_lines = gbench.report.print_difference_report( + diff_report, + args.display_aggregates_only, + args.utest, args.utest_alpha, args.color) + print(description) + for ln in output_lines: + print(ln) + + # Optionally, diff and output to JSON + if args.dump_to_json is not None: + with open(args.dump_to_json, 'w') as f_json: + json.dump(diff_report, f_json) + +class TestParser(unittest.TestCase): + def setUp(self): + self.parser = create_parser() + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'gbench', + 'Inputs') + self.testInput0 = os.path.join(testInputs, 'test1_run1.json') + self.testInput1 = os.path.join(testInputs, 'test1_run2.json') + + def test_benchmarks_basic(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_without_utest(self): + parsed = self.parser.parse_args( + ['--no-utest', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertFalse(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.05) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_display_aggregates_only(self): + parsed = self.parser.parse_args( + ['-a', 'benchmarks', self.testInput0, self.testInput1]) + self.assertTrue(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_with_utest_alpha(self): + parsed = self.parser.parse_args( + ['--alpha=0.314', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.314) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_without_utest_with_utest_alpha(self): + parsed = self.parser.parse_args( + ['--no-utest', '--alpha=0.314', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertFalse(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.314) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_with_remainder(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1, 'd']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.benchmark_options, ['d']) + + def test_benchmarks_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1, '--', 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.benchmark_options, ['e']) + + def test_filters_basic(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertFalse(parsed.benchmark_options) + + def test_filters_with_remainder(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd', 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertEqual(parsed.benchmark_options, ['e']) + + def test_filters_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd', '--', 'f']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertEqual(parsed.benchmark_options, ['f']) + + def test_benchmarksfiltered_basic(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertFalse(parsed.benchmark_options) + + def test_benchmarksfiltered_with_remainder(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', 'f']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertEqual(parsed.benchmark_options[0], 'f') + + def test_benchmarksfiltered_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', '--', 'g']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertEqual(parsed.benchmark_options[0], 'g') + + +if __name__ == '__main__': + # unittest.main() + main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run1.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run1.json new file mode 100644 index 0000000000..601e327aef --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run1.json @@ -0,0 +1,119 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_SameTimes", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_2xFaster", + "iterations": 1000, + "real_time": 50, + "cpu_time": 50, + "time_unit": "ns" + }, + { + "name": "BM_2xSlower", + "iterations": 1000, + "real_time": 50, + "cpu_time": 50, + "time_unit": "ns" + }, + { + "name": "BM_1PercentFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_1PercentSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_100xSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_100xFaster", + "iterations": 1000, + "real_time": 10000, + "cpu_time": 10000, + "time_unit": "ns" + }, + { + "name": "BM_10PercentCPUToTime", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_ThirdFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_BigO", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "BigO", + "cpu_coefficient": 4.2749856294592886e+00, + "real_coefficient": 6.4789275289789780e+00, + "big_o": "N", + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_RMS", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "RMS", + "rms": 4.5097802512472874e-03 + }, + { + "name": "BM_NotBadTimeUnit", + "iterations": 1000, + "real_time": 0.4, + "cpu_time": 0.5, + "time_unit": "s" + }, + { + "name": "BM_DifferentTimeUnit", + "iterations": 1, + "real_time": 1, + "cpu_time": 1, + "time_unit": "s" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run2.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run2.json new file mode 100644 index 0000000000..3cbcf39b0c --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test1_run2.json @@ -0,0 +1,119 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_SameTimes", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_2xFaster", + "iterations": 1000, + "real_time": 25, + "cpu_time": 25, + "time_unit": "ns" + }, + { + "name": "BM_2xSlower", + "iterations": 20833333, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_1PercentFaster", + "iterations": 1000, + "real_time": 98.9999999, + "cpu_time": 98.9999999, + "time_unit": "ns" + }, + { + "name": "BM_1PercentSlower", + "iterations": 1000, + "real_time": 100.9999999, + "cpu_time": 100.9999999, + "time_unit": "ns" + }, + { + "name": "BM_10PercentFaster", + "iterations": 1000, + "real_time": 90, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_10PercentSlower", + "iterations": 1000, + "real_time": 110, + "cpu_time": 110, + "time_unit": "ns" + }, + { + "name": "BM_100xSlower", + "iterations": 1000, + "real_time": 1.0000e+04, + "cpu_time": 1.0000e+04, + "time_unit": "ns" + }, + { + "name": "BM_100xFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentCPUToTime", + "iterations": 1000, + "real_time": 110, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_ThirdFaster", + "iterations": 1000, + "real_time": 66.665, + "cpu_time": 66.664, + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_BigO", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "BigO", + "cpu_coefficient": 5.6215779594361486e+00, + "real_coefficient": 5.6288314793554610e+00, + "big_o": "N", + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_RMS", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "RMS", + "rms": 3.3128901852342174e-03 + }, + { + "name": "BM_NotBadTimeUnit", + "iterations": 1000, + "real_time": 0.04, + "cpu_time": 0.6, + "time_unit": "s" + }, + { + "name": "BM_DifferentTimeUnit", + "iterations": 1, + "real_time": 1, + "cpu_time": 1, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test2_run.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test2_run.json new file mode 100644 index 0000000000..15bc698030 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test2_run.json @@ -0,0 +1,81 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_Hi", + "iterations": 1234, + "real_time": 42, + "cpu_time": 24, + "time_unit": "ms" + }, + { + "name": "BM_Zero", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_Zero/4", + "iterations": 4000, + "real_time": 40, + "cpu_time": 40, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_Zero", + "iterations": 2000, + "real_time": 20, + "cpu_time": 20, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_Zero/3", + "iterations": 3000, + "real_time": 30, + "cpu_time": 30, + "time_unit": "ns" + }, + { + "name": "BM_One", + "iterations": 5000, + "real_time": 5, + "cpu_time": 5, + "time_unit": "ns" + }, + { + "name": "BM_One/4", + "iterations": 2000, + "real_time": 20, + "cpu_time": 20, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_One", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_One/3", + "iterations": 1500, + "real_time": 15, + "cpu_time": 15, + "time_unit": "ns" + }, + { + "name": "BM_Bye", + "iterations": 5321, + "real_time": 11, + "cpu_time": 63, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run0.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run0.json new file mode 100644 index 0000000000..49f8b06143 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run0.json @@ -0,0 +1,65 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_One", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 10, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 9, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 8, + "cpu_time": 86, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 8, + "cpu_time": 80, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 8, + "cpu_time": 77, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1000, + "real_time": 8, + "cpu_time": 80, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1000, + "real_time": 9, + "cpu_time": 82, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run1.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run1.json new file mode 100644 index 0000000000..acc5ba17ae --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test3_run1.json @@ -0,0 +1,65 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_One", + "iterations": 1000, + "real_time": 9, + "cpu_time": 110, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 10, + "cpu_time": 89, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 7, + "cpu_time": 72, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 7, + "cpu_time": 75, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 762, + "real_time": 4.54, + "cpu_time": 66.6, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "iteration", + "iterations": 1000, + "real_time": 800, + "cpu_time": 1, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1200, + "real_time": 5, + "cpu_time": 53, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run.json new file mode 100644 index 0000000000..eaa005f3a9 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run.json @@ -0,0 +1,96 @@ +{ + "benchmarks": [ + { + "name": "99 family 0 instance 0 repetition 0", + "run_type": "iteration", + "family_index": 0, + "per_family_instance_index": 0, + "repetition_index": 0 + }, + { + "name": "98 family 0 instance 0 repetition 1", + "run_type": "iteration", + "family_index": 0, + "per_family_instance_index": 0, + "repetition_index": 1 + }, + { + "name": "97 family 0 instance 0 aggregate", + "run_type": "aggregate", + "family_index": 0, + "per_family_instance_index": 0, + "aggregate_name": "9 aggregate" + }, + + + { + "name": "96 family 0 instance 1 repetition 0", + "run_type": "iteration", + "family_index": 0, + "per_family_instance_index": 1, + "repetition_index": 0 + }, + { + "name": "95 family 0 instance 1 repetition 1", + "run_type": "iteration", + "family_index": 0, + "per_family_instance_index": 1, + "repetition_index": 1 + }, + { + "name": "94 family 0 instance 1 aggregate", + "run_type": "aggregate", + "family_index": 0, + "per_family_instance_index": 1, + "aggregate_name": "9 aggregate" + }, + + + + + { + "name": "93 family 1 instance 0 repetition 0", + "run_type": "iteration", + "family_index": 1, + "per_family_instance_index": 0, + "repetition_index": 0 + }, + { + "name": "92 family 1 instance 0 repetition 1", + "run_type": "iteration", + "family_index": 1, + "per_family_instance_index": 0, + "repetition_index": 1 + }, + { + "name": "91 family 1 instance 0 aggregate", + "run_type": "aggregate", + "family_index": 1, + "per_family_instance_index": 0, + "aggregate_name": "9 aggregate" + }, + + + { + "name": "90 family 1 instance 1 repetition 0", + "run_type": "iteration", + "family_index": 1, + "per_family_instance_index": 1, + "repetition_index": 0 + }, + { + "name": "89 family 1 instance 1 repetition 1", + "run_type": "iteration", + "family_index": 1, + "per_family_instance_index": 1, + "repetition_index": 1 + }, + { + "name": "88 family 1 instance 1 aggregate", + "run_type": "aggregate", + "family_index": 1, + "per_family_instance_index": 1, + "aggregate_name": "9 aggregate" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run0.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run0.json new file mode 100644 index 0000000000..54cf127585 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run0.json @@ -0,0 +1,21 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "whocares", + "run_type": "aggregate", + "aggregate_name": "zz", + "aggregate_unit": "percentage", + "iterations": 1000, + "real_time": 0.01, + "cpu_time": 0.10, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run1.json b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run1.json new file mode 100644 index 0000000000..25d56050c9 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/Inputs/test4_run1.json @@ -0,0 +1,21 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "whocares", + "run_type": "aggregate", + "aggregate_name": "zz", + "aggregate_unit": "percentage", + "iterations": 1000, + "real_time": 0.005, + "cpu_time": 0.15, + "time_unit": "ns" + } + ] +} diff --git a/bridge/third_party/benchmark/tools/gbench/__init__.py b/bridge/third_party/benchmark/tools/gbench/__init__.py new file mode 100644 index 0000000000..fce1a1acfb --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/__init__.py @@ -0,0 +1,8 @@ +"""Google Benchmark tooling""" + +__author__ = 'Eric Fiselier' +__email__ = 'eric@efcs.ca' +__versioninfo__ = (0, 5, 0) +__version__ = '.'.join(str(v) for v in __versioninfo__) + 'dev' + +__all__ = [] diff --git a/bridge/third_party/benchmark/tools/gbench/report.py b/bridge/third_party/benchmark/tools/gbench/report.py new file mode 100644 index 0000000000..4c798baf69 --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/report.py @@ -0,0 +1,1144 @@ +"""report.py - Utilities for reporting statistics about benchmark results +""" + +import unittest +import os +import re +import copy +import random + +from scipy.stats import mannwhitneyu, gmean +from numpy import array +from pandas import Timedelta + + +class BenchmarkColor(object): + def __init__(self, name, code): + self.name = name + self.code = code + + def __repr__(self): + return '%s%r' % (self.__class__.__name__, + (self.name, self.code)) + + def __format__(self, format): + return self.code + + +# Benchmark Colors Enumeration +BC_NONE = BenchmarkColor('NONE', '') +BC_MAGENTA = BenchmarkColor('MAGENTA', '\033[95m') +BC_CYAN = BenchmarkColor('CYAN', '\033[96m') +BC_OKBLUE = BenchmarkColor('OKBLUE', '\033[94m') +BC_OKGREEN = BenchmarkColor('OKGREEN', '\033[32m') +BC_HEADER = BenchmarkColor('HEADER', '\033[92m') +BC_WARNING = BenchmarkColor('WARNING', '\033[93m') +BC_WHITE = BenchmarkColor('WHITE', '\033[97m') +BC_FAIL = BenchmarkColor('FAIL', '\033[91m') +BC_ENDC = BenchmarkColor('ENDC', '\033[0m') +BC_BOLD = BenchmarkColor('BOLD', '\033[1m') +BC_UNDERLINE = BenchmarkColor('UNDERLINE', '\033[4m') + +UTEST_MIN_REPETITIONS = 2 +UTEST_OPTIMAL_REPETITIONS = 9 # Lowest reasonable number, More is better. +UTEST_COL_NAME = "_pvalue" + + +def color_format(use_color, fmt_str, *args, **kwargs): + """ + Return the result of 'fmt_str.format(*args, **kwargs)' after transforming + 'args' and 'kwargs' according to the value of 'use_color'. If 'use_color' + is False then all color codes in 'args' and 'kwargs' are replaced with + the empty string. + """ + assert use_color is True or use_color is False + if not use_color: + args = [arg if not isinstance(arg, BenchmarkColor) else BC_NONE + for arg in args] + kwargs = {key: arg if not isinstance(arg, BenchmarkColor) else BC_NONE + for key, arg in kwargs.items()} + return fmt_str.format(*args, **kwargs) + + +def find_longest_name(benchmark_list): + """ + Return the length of the longest benchmark name in a given list of + benchmark JSON objects + """ + longest_name = 1 + for bc in benchmark_list: + if len(bc['name']) > longest_name: + longest_name = len(bc['name']) + return longest_name + + +def calculate_change(old_val, new_val): + """ + Return a float representing the decimal change between old_val and new_val. + """ + if old_val == 0 and new_val == 0: + return 0.0 + if old_val == 0: + return float(new_val - old_val) / (float(old_val + new_val) / 2) + return float(new_val - old_val) / abs(old_val) + + +def filter_benchmark(json_orig, family, replacement=""): + """ + Apply a filter to the json, and only leave the 'family' of benchmarks. + """ + regex = re.compile(family) + filtered = {} + filtered['benchmarks'] = [] + for be in json_orig['benchmarks']: + if not regex.search(be['name']): + continue + filteredbench = copy.deepcopy(be) # Do NOT modify the old name! + filteredbench['name'] = regex.sub(replacement, filteredbench['name']) + filtered['benchmarks'].append(filteredbench) + return filtered + + +def get_unique_benchmark_names(json): + """ + While *keeping* the order, give all the unique 'names' used for benchmarks. + """ + seen = set() + uniqued = [x['name'] for x in json['benchmarks'] + if x['name'] not in seen and + (seen.add(x['name']) or True)] + return uniqued + + +def intersect(list1, list2): + """ + Given two lists, get a new list consisting of the elements only contained + in *both of the input lists*, while preserving the ordering. + """ + return [x for x in list1 if x in list2] + + +def is_potentially_comparable_benchmark(x): + return ('time_unit' in x and 'real_time' in x and 'cpu_time' in x) + + +def partition_benchmarks(json1, json2): + """ + While preserving the ordering, find benchmarks with the same names in + both of the inputs, and group them. + (i.e. partition/filter into groups with common name) + """ + json1_unique_names = get_unique_benchmark_names(json1) + json2_unique_names = get_unique_benchmark_names(json2) + names = intersect(json1_unique_names, json2_unique_names) + partitions = [] + for name in names: + time_unit = None + # Pick the time unit from the first entry of the lhs benchmark. + # We should be careful not to crash with unexpected input. + for x in json1['benchmarks']: + if (x['name'] == name and is_potentially_comparable_benchmark(x)): + time_unit = x['time_unit'] + break + if time_unit is None: + continue + # Filter by name and time unit. + # All the repetitions are assumed to be comparable. + lhs = [x for x in json1['benchmarks'] if x['name'] == name and + x['time_unit'] == time_unit] + rhs = [x for x in json2['benchmarks'] if x['name'] == name and + x['time_unit'] == time_unit] + partitions.append([lhs, rhs]) + return partitions + + +def get_timedelta_field_as_seconds(benchmark, field_name): + """ + Get value of field_name field of benchmark, which is time with time unit + time_unit, as time in seconds. + """ + time_unit = benchmark['time_unit'] if 'time_unit' in benchmark else 's' + dt = Timedelta(benchmark[field_name], time_unit) + return dt / Timedelta(1, 's') + + +def calculate_geomean(json): + """ + Extract all real/cpu times from all the benchmarks as seconds, + and calculate their geomean. + """ + times = [] + for benchmark in json['benchmarks']: + if 'run_type' in benchmark and benchmark['run_type'] == 'aggregate': + continue + times.append([get_timedelta_field_as_seconds(benchmark, 'real_time'), + get_timedelta_field_as_seconds(benchmark, 'cpu_time')]) + return gmean(times) if times else array([]) + + +def extract_field(partition, field_name): + # The count of elements may be different. We want *all* of them. + lhs = [x[field_name] for x in partition[0]] + rhs = [x[field_name] for x in partition[1]] + return [lhs, rhs] + + +def calc_utest(timings_cpu, timings_time): + min_rep_cnt = min(len(timings_time[0]), + len(timings_time[1]), + len(timings_cpu[0]), + len(timings_cpu[1])) + + # Does *everything* has at least UTEST_MIN_REPETITIONS repetitions? + if min_rep_cnt < UTEST_MIN_REPETITIONS: + return False, None, None + + time_pvalue = mannwhitneyu( + timings_time[0], timings_time[1], alternative='two-sided').pvalue + cpu_pvalue = mannwhitneyu( + timings_cpu[0], timings_cpu[1], alternative='two-sided').pvalue + + return (min_rep_cnt >= UTEST_OPTIMAL_REPETITIONS), cpu_pvalue, time_pvalue + + +def print_utest(bc_name, utest, utest_alpha, first_col_width, use_color=True): + def get_utest_color(pval): + return BC_FAIL if pval >= utest_alpha else BC_OKGREEN + + # Check if we failed miserably with minimum required repetitions for utest + if not utest['have_optimal_repetitions'] and utest['cpu_pvalue'] is None and utest['time_pvalue'] is None: + return [] + + dsc = "U Test, Repetitions: {} vs {}".format( + utest['nr_of_repetitions'], utest['nr_of_repetitions_other']) + dsc_color = BC_OKGREEN + + # We still got some results to show but issue a warning about it. + if not utest['have_optimal_repetitions']: + dsc_color = BC_WARNING + dsc += ". WARNING: Results unreliable! {}+ repetitions recommended.".format( + UTEST_OPTIMAL_REPETITIONS) + + special_str = "{}{:<{}s}{endc}{}{:16.4f}{endc}{}{:16.4f}{endc}{} {}" + + return [color_format(use_color, + special_str, + BC_HEADER, + "{}{}".format(bc_name, UTEST_COL_NAME), + first_col_width, + get_utest_color( + utest['time_pvalue']), utest['time_pvalue'], + get_utest_color( + utest['cpu_pvalue']), utest['cpu_pvalue'], + dsc_color, dsc, + endc=BC_ENDC)] + + +def get_difference_report( + json1, + json2, + utest=False): + """ + Calculate and report the difference between each test of two benchmarks + runs specified as 'json1' and 'json2'. Output is another json containing + relevant details for each test run. + """ + assert utest is True or utest is False + + diff_report = [] + partitions = partition_benchmarks(json1, json2) + for partition in partitions: + benchmark_name = partition[0][0]['name'] + time_unit = partition[0][0]['time_unit'] + measurements = [] + utest_results = {} + # Careful, we may have different repetition count. + for i in range(min(len(partition[0]), len(partition[1]))): + bn = partition[0][i] + other_bench = partition[1][i] + measurements.append({ + 'real_time': bn['real_time'], + 'cpu_time': bn['cpu_time'], + 'real_time_other': other_bench['real_time'], + 'cpu_time_other': other_bench['cpu_time'], + 'time': calculate_change(bn['real_time'], other_bench['real_time']), + 'cpu': calculate_change(bn['cpu_time'], other_bench['cpu_time']) + }) + + # After processing the whole partition, if requested, do the U test. + if utest: + timings_cpu = extract_field(partition, 'cpu_time') + timings_time = extract_field(partition, 'real_time') + have_optimal_repetitions, cpu_pvalue, time_pvalue = calc_utest( + timings_cpu, timings_time) + if cpu_pvalue and time_pvalue: + utest_results = { + 'have_optimal_repetitions': have_optimal_repetitions, + 'cpu_pvalue': cpu_pvalue, + 'time_pvalue': time_pvalue, + 'nr_of_repetitions': len(timings_cpu[0]), + 'nr_of_repetitions_other': len(timings_cpu[1]) + } + + # Store only if we had any measurements for given benchmark. + # E.g. partition_benchmarks will filter out the benchmarks having + # time units which are not compatible with other time units in the + # benchmark suite. + if measurements: + run_type = partition[0][0]['run_type'] if 'run_type' in partition[0][0] else '' + aggregate_name = partition[0][0]['aggregate_name'] if run_type == 'aggregate' and 'aggregate_name' in partition[0][0] else '' + diff_report.append({ + 'name': benchmark_name, + 'measurements': measurements, + 'time_unit': time_unit, + 'run_type': run_type, + 'aggregate_name': aggregate_name, + 'utest': utest_results + }) + + lhs_gmean = calculate_geomean(json1) + rhs_gmean = calculate_geomean(json2) + if lhs_gmean.any() and rhs_gmean.any(): + diff_report.append({ + 'name': 'OVERALL_GEOMEAN', + 'measurements': [{ + 'real_time': lhs_gmean[0], + 'cpu_time': lhs_gmean[1], + 'real_time_other': rhs_gmean[0], + 'cpu_time_other': rhs_gmean[1], + 'time': calculate_change(lhs_gmean[0], rhs_gmean[0]), + 'cpu': calculate_change(lhs_gmean[1], rhs_gmean[1]) + }], + 'time_unit': 's', + 'run_type': 'aggregate', + 'aggregate_name': 'geomean', + 'utest': {} + }) + + return diff_report + + +def print_difference_report( + json_diff_report, + include_aggregates_only=False, + utest=False, + utest_alpha=0.05, + use_color=True): + """ + Calculate and report the difference between each test of two benchmarks + runs specified as 'json1' and 'json2'. + """ + assert utest is True or utest is False + + def get_color(res): + if res > 0.05: + return BC_FAIL + elif res > -0.07: + return BC_WHITE + else: + return BC_CYAN + + first_col_width = find_longest_name(json_diff_report) + first_col_width = max( + first_col_width, + len('Benchmark')) + first_col_width += len(UTEST_COL_NAME) + first_line = "{:<{}s}Time CPU Time Old Time New CPU Old CPU New".format( + 'Benchmark', 12 + first_col_width) + output_strs = [first_line, '-' * len(first_line)] + + fmt_str = "{}{:<{}s}{endc}{}{:+16.4f}{endc}{}{:+16.4f}{endc}{:14.0f}{:14.0f}{endc}{:14.0f}{:14.0f}" + for benchmark in json_diff_report: + # *If* we were asked to only include aggregates, + # and if it is non-aggregate, then don't print it. + if not include_aggregates_only or not 'run_type' in benchmark or benchmark['run_type'] == 'aggregate': + for measurement in benchmark['measurements']: + output_strs += [color_format(use_color, + fmt_str, + BC_HEADER, + benchmark['name'], + first_col_width, + get_color(measurement['time']), + measurement['time'], + get_color(measurement['cpu']), + measurement['cpu'], + measurement['real_time'], + measurement['real_time_other'], + measurement['cpu_time'], + measurement['cpu_time_other'], + endc=BC_ENDC)] + + # After processing the measurements, if requested and + # if applicable (e.g. u-test exists for given benchmark), + # print the U test. + if utest and benchmark['utest']: + output_strs += print_utest(benchmark['name'], + benchmark['utest'], + utest_alpha=utest_alpha, + first_col_width=first_col_width, + use_color=use_color) + + return output_strs + + +############################################################################### +# Unit tests + + +class TestGetUniqueBenchmarkNames(unittest.TestCase): + def load_results(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput = os.path.join(testInputs, 'test3_run0.json') + with open(testOutput, 'r') as f: + json = json.load(f) + return json + + def test_basic(self): + expect_lines = [ + 'BM_One', + 'BM_Two', + 'short', # These two are not sorted + 'medium', # These two are not sorted + ] + json = self.load_results() + output_lines = get_unique_benchmark_names(json) + print("\n") + print("\n".join(output_lines)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + self.assertEqual(expect_lines[i], output_lines[i]) + + +class TestReportDifference(unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_results(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test1_run1.json') + testOutput2 = os.path.join(testInputs, 'test1_run2.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + json1, json2 = load_results() + cls.json_diff_report = get_difference_report(json1, json2) + + def test_json_diff_report_pretty_printing(self): + expect_lines = [ + ['BM_SameTimes', '+0.0000', '+0.0000', '10', '10', '10', '10'], + ['BM_2xFaster', '-0.5000', '-0.5000', '50', '25', '50', '25'], + ['BM_2xSlower', '+1.0000', '+1.0000', '50', '100', '50', '100'], + ['BM_1PercentFaster', '-0.0100', '-0.0100', '100', '99', '100', '99'], + ['BM_1PercentSlower', '+0.0100', '+0.0100', '100', '101', '100', '101'], + ['BM_10PercentFaster', '-0.1000', '-0.1000', '100', '90', '100', '90'], + ['BM_10PercentSlower', '+0.1000', '+0.1000', '100', '110', '100', '110'], + ['BM_100xSlower', '+99.0000', '+99.0000', + '100', '10000', '100', '10000'], + ['BM_100xFaster', '-0.9900', '-0.9900', + '10000', '100', '10000', '100'], + ['BM_10PercentCPUToTime', '+0.1000', + '-0.1000', '100', '110', '100', '90'], + ['BM_ThirdFaster', '-0.3333', '-0.3334', '100', '67', '100', '67'], + ['BM_NotBadTimeUnit', '-0.9000', '+0.2000', '0', '0', '0', '1'], + ['OVERALL_GEOMEAN', '-0.8344', '-0.8026', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(len(parts), 7) + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report_output(self): + expected_output = [ + { + 'name': 'BM_SameTimes', + 'measurements': [{'time': 0.0000, 'cpu': 0.0000, 'real_time': 10, 'real_time_other': 10, 'cpu_time': 10, 'cpu_time_other': 10}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_2xFaster', + 'measurements': [{'time': -0.5000, 'cpu': -0.5000, 'real_time': 50, 'real_time_other': 25, 'cpu_time': 50, 'cpu_time_other': 25}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_2xSlower', + 'measurements': [{'time': 1.0000, 'cpu': 1.0000, 'real_time': 50, 'real_time_other': 100, 'cpu_time': 50, 'cpu_time_other': 100}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_1PercentFaster', + 'measurements': [{'time': -0.0100, 'cpu': -0.0100, 'real_time': 100, 'real_time_other': 98.9999999, 'cpu_time': 100, 'cpu_time_other': 98.9999999}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_1PercentSlower', + 'measurements': [{'time': 0.0100, 'cpu': 0.0100, 'real_time': 100, 'real_time_other': 101, 'cpu_time': 100, 'cpu_time_other': 101}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_10PercentFaster', + 'measurements': [{'time': -0.1000, 'cpu': -0.1000, 'real_time': 100, 'real_time_other': 90, 'cpu_time': 100, 'cpu_time_other': 90}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_10PercentSlower', + 'measurements': [{'time': 0.1000, 'cpu': 0.1000, 'real_time': 100, 'real_time_other': 110, 'cpu_time': 100, 'cpu_time_other': 110}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_100xSlower', + 'measurements': [{'time': 99.0000, 'cpu': 99.0000, 'real_time': 100, 'real_time_other': 10000, 'cpu_time': 100, 'cpu_time_other': 10000}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_100xFaster', + 'measurements': [{'time': -0.9900, 'cpu': -0.9900, 'real_time': 10000, 'real_time_other': 100, 'cpu_time': 10000, 'cpu_time_other': 100}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_10PercentCPUToTime', + 'measurements': [{'time': 0.1000, 'cpu': -0.1000, 'real_time': 100, 'real_time_other': 110, 'cpu_time': 100, 'cpu_time_other': 90}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_ThirdFaster', + 'measurements': [{'time': -0.3333, 'cpu': -0.3334, 'real_time': 100, 'real_time_other': 67, 'cpu_time': 100, 'cpu_time_other': 67}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'BM_NotBadTimeUnit', + 'measurements': [{'time': -0.9000, 'cpu': 0.2000, 'real_time': 0.4, 'real_time_other': 0.04, 'cpu_time': 0.5, 'cpu_time_other': 0.6}], + 'time_unit': 's', + 'utest': {} + }, + { + 'name': 'OVERALL_GEOMEAN', + 'measurements': [{'real_time': 1.193776641714438e-06, 'cpu_time': 1.2144445585302297e-06, + 'real_time_other': 1.9768988699420897e-07, 'cpu_time_other': 2.397447755209533e-07, + 'time': -0.834399601997324, 'cpu': -0.8025889499549471}], + 'time_unit': 's', + 'run_type': 'aggregate', + 'aggregate_name': 'geomean', 'utest': {} + }, + ] + self.assertEqual(len(self.json_diff_report), len(expected_output)) + for out, expected in zip( + self.json_diff_report, expected_output): + self.assertEqual(out['name'], expected['name']) + self.assertEqual(out['time_unit'], expected['time_unit']) + assert_utest(self, out, expected) + assert_measurements(self, out, expected) + + +class TestReportDifferenceBetweenFamilies(unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_result(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput = os.path.join(testInputs, 'test2_run.json') + with open(testOutput, 'r') as f: + json = json.load(f) + return json + + json = load_result() + json1 = filter_benchmark(json, "BM_Z.ro", ".") + json2 = filter_benchmark(json, "BM_O.e", ".") + cls.json_diff_report = get_difference_report(json1, json2) + + def test_json_diff_report_pretty_printing(self): + expect_lines = [ + ['.', '-0.5000', '-0.5000', '10', '5', '10', '5'], + ['./4', '-0.5000', '-0.5000', '40', '20', '40', '20'], + ['Prefix/.', '-0.5000', '-0.5000', '20', '10', '20', '10'], + ['Prefix/./3', '-0.5000', '-0.5000', '30', '15', '30', '15'], + ['OVERALL_GEOMEAN', '-0.5000', '-0.5000', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(len(parts), 7) + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report(self): + expected_output = [ + { + 'name': u'.', + 'measurements': [{'time': -0.5, 'cpu': -0.5, 'real_time': 10, 'real_time_other': 5, 'cpu_time': 10, 'cpu_time_other': 5}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': u'./4', + 'measurements': [{'time': -0.5, 'cpu': -0.5, 'real_time': 40, 'real_time_other': 20, 'cpu_time': 40, 'cpu_time_other': 20}], + 'time_unit': 'ns', + 'utest': {}, + }, + { + 'name': u'Prefix/.', + 'measurements': [{'time': -0.5, 'cpu': -0.5, 'real_time': 20, 'real_time_other': 10, 'cpu_time': 20, 'cpu_time_other': 10}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': u'Prefix/./3', + 'measurements': [{'time': -0.5, 'cpu': -0.5, 'real_time': 30, 'real_time_other': 15, 'cpu_time': 30, 'cpu_time_other': 15}], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'OVERALL_GEOMEAN', + 'measurements': [{'real_time': 2.213363839400641e-08, 'cpu_time': 2.213363839400641e-08, + 'real_time_other': 1.1066819197003185e-08, 'cpu_time_other': 1.1066819197003185e-08, + 'time': -0.5000000000000009, 'cpu': -0.5000000000000009}], + 'time_unit': 's', + 'run_type': 'aggregate', + 'aggregate_name': 'geomean', + 'utest': {} + } + ] + self.assertEqual(len(self.json_diff_report), len(expected_output)) + for out, expected in zip( + self.json_diff_report, expected_output): + self.assertEqual(out['name'], expected['name']) + self.assertEqual(out['time_unit'], expected['time_unit']) + assert_utest(self, out, expected) + assert_measurements(self, out, expected) + + +class TestReportDifferenceWithUTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_results(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test3_run0.json') + testOutput2 = os.path.join(testInputs, 'test3_run1.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + json1, json2 = load_results() + cls.json_diff_report = get_difference_report( + json1, json2, utest=True) + + def test_json_diff_report_pretty_printing(self): + expect_lines = [ + ['BM_One', '-0.1000', '+0.1000', '10', '9', '100', '110'], + ['BM_Two', '+0.1111', '-0.0111', '9', '10', '90', '89'], + ['BM_Two', '-0.1250', '-0.1628', '8', '7', '86', '72'], + ['BM_Two_pvalue', + '1.0000', + '0.6667', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '2.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['short', '-0.1250', '-0.0625', '8', '7', '80', '75'], + ['short', '-0.4325', '-0.1351', '8', '5', '77', '67'], + ['short_pvalue', + '0.7671', + '0.2000', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '3.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['medium', '-0.3750', '-0.3375', '8', '5', '80', '53'], + ['OVERALL_GEOMEAN', '+1.6405', '-0.6985', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report_pretty_printing_aggregates_only(self): + expect_lines = [ + ['BM_One', '-0.1000', '+0.1000', '10', '9', '100', '110'], + ['BM_Two_pvalue', + '1.0000', + '0.6667', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '2.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['short', '-0.1250', '-0.0625', '8', '7', '80', '75'], + ['short', '-0.4325', '-0.1351', '8', '5', '77', '67'], + ['short_pvalue', + '0.7671', + '0.2000', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '3.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['OVERALL_GEOMEAN', '+1.6405', '-0.6985', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, include_aggregates_only=True, utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report(self): + expected_output = [ + { + 'name': u'BM_One', + 'measurements': [ + {'time': -0.1, + 'cpu': 0.1, + 'real_time': 10, + 'real_time_other': 9, + 'cpu_time': 100, + 'cpu_time_other': 110} + ], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': u'BM_Two', + 'measurements': [ + {'time': 0.1111111111111111, + 'cpu': -0.011111111111111112, + 'real_time': 9, + 'real_time_other': 10, + 'cpu_time': 90, + 'cpu_time_other': 89}, + {'time': -0.125, 'cpu': -0.16279069767441862, 'real_time': 8, + 'real_time_other': 7, 'cpu_time': 86, 'cpu_time_other': 72} + ], + 'time_unit': 'ns', + 'utest': { + 'have_optimal_repetitions': False, 'cpu_pvalue': 0.6666666666666666, 'time_pvalue': 1.0 + } + }, + { + 'name': u'short', + 'measurements': [ + {'time': -0.125, + 'cpu': -0.0625, + 'real_time': 8, + 'real_time_other': 7, + 'cpu_time': 80, + 'cpu_time_other': 75}, + {'time': -0.4325, + 'cpu': -0.13506493506493514, + 'real_time': 8, + 'real_time_other': 4.54, + 'cpu_time': 77, + 'cpu_time_other': 66.6} + ], + 'time_unit': 'ns', + 'utest': { + 'have_optimal_repetitions': False, 'cpu_pvalue': 0.2, 'time_pvalue': 0.7670968684102772 + } + }, + { + 'name': u'medium', + 'measurements': [ + {'time': -0.375, + 'cpu': -0.3375, + 'real_time': 8, + 'real_time_other': 5, + 'cpu_time': 80, + 'cpu_time_other': 53} + ], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': 'OVERALL_GEOMEAN', + 'measurements': [{'real_time': 8.48528137423858e-09, 'cpu_time': 8.441336246629233e-08, + 'real_time_other': 2.2405267593145244e-08, 'cpu_time_other': 2.5453661413660466e-08, + 'time': 1.6404861082353634, 'cpu': -0.6984640740519662}], + 'time_unit': 's', + 'run_type': 'aggregate', + 'aggregate_name': 'geomean', + 'utest': {} + } + ] + self.assertEqual(len(self.json_diff_report), len(expected_output)) + for out, expected in zip( + self.json_diff_report, expected_output): + self.assertEqual(out['name'], expected['name']) + self.assertEqual(out['time_unit'], expected['time_unit']) + assert_utest(self, out, expected) + assert_measurements(self, out, expected) + + +class TestReportDifferenceWithUTestWhileDisplayingAggregatesOnly( + unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_results(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test3_run0.json') + testOutput2 = os.path.join(testInputs, 'test3_run1.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + json1, json2 = load_results() + cls.json_diff_report = get_difference_report( + json1, json2, utest=True) + + def test_json_diff_report_pretty_printing(self): + expect_lines = [ + ['BM_One', '-0.1000', '+0.1000', '10', '9', '100', '110'], + ['BM_Two', '+0.1111', '-0.0111', '9', '10', '90', '89'], + ['BM_Two', '-0.1250', '-0.1628', '8', '7', '86', '72'], + ['BM_Two_pvalue', + '1.0000', + '0.6667', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '2.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['short', '-0.1250', '-0.0625', '8', '7', '80', '75'], + ['short', '-0.4325', '-0.1351', '8', '5', '77', '67'], + ['short_pvalue', + '0.7671', + '0.2000', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '3.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['medium', '-0.3750', '-0.3375', '8', '5', '80', '53'], + ['OVERALL_GEOMEAN', '+1.6405', '-0.6985', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, + utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report(self): + expected_output = [ + { + 'name': u'BM_One', + 'measurements': [ + {'time': -0.1, + 'cpu': 0.1, + 'real_time': 10, + 'real_time_other': 9, + 'cpu_time': 100, + 'cpu_time_other': 110} + ], + 'time_unit': 'ns', + 'utest': {} + }, + { + 'name': u'BM_Two', + 'measurements': [ + {'time': 0.1111111111111111, + 'cpu': -0.011111111111111112, + 'real_time': 9, + 'real_time_other': 10, + 'cpu_time': 90, + 'cpu_time_other': 89}, + {'time': -0.125, 'cpu': -0.16279069767441862, 'real_time': 8, + 'real_time_other': 7, 'cpu_time': 86, 'cpu_time_other': 72} + ], + 'time_unit': 'ns', + 'utest': { + 'have_optimal_repetitions': False, 'cpu_pvalue': 0.6666666666666666, 'time_pvalue': 1.0 + } + }, + { + 'name': u'short', + 'measurements': [ + {'time': -0.125, + 'cpu': -0.0625, + 'real_time': 8, + 'real_time_other': 7, + 'cpu_time': 80, + 'cpu_time_other': 75}, + {'time': -0.4325, + 'cpu': -0.13506493506493514, + 'real_time': 8, + 'real_time_other': 4.54, + 'cpu_time': 77, + 'cpu_time_other': 66.6} + ], + 'time_unit': 'ns', + 'utest': { + 'have_optimal_repetitions': False, 'cpu_pvalue': 0.2, 'time_pvalue': 0.7670968684102772 + } + }, + { + 'name': u'medium', + 'measurements': [ + {'real_time_other': 5, + 'cpu_time': 80, + 'time': -0.375, + 'real_time': 8, + 'cpu_time_other': 53, + 'cpu': -0.3375 + } + ], + 'utest': {}, + 'time_unit': u'ns', + 'aggregate_name': '' + }, + { + 'name': 'OVERALL_GEOMEAN', + 'measurements': [{'real_time': 8.48528137423858e-09, 'cpu_time': 8.441336246629233e-08, + 'real_time_other': 2.2405267593145244e-08, 'cpu_time_other': 2.5453661413660466e-08, + 'time': 1.6404861082353634, 'cpu': -0.6984640740519662}], + 'time_unit': 's', + 'run_type': 'aggregate', + 'aggregate_name': 'geomean', + 'utest': {} + } + ] + self.assertEqual(len(self.json_diff_report), len(expected_output)) + for out, expected in zip( + self.json_diff_report, expected_output): + self.assertEqual(out['name'], expected['name']) + self.assertEqual(out['time_unit'], expected['time_unit']) + assert_utest(self, out, expected) + assert_measurements(self, out, expected) + + +class TestReportDifferenceForPercentageAggregates( + unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_results(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test4_run0.json') + testOutput2 = os.path.join(testInputs, 'test4_run1.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + json1, json2 = load_results() + cls.json_diff_report = get_difference_report( + json1, json2, utest=True) + + def test_json_diff_report_pretty_printing(self): + expect_lines = [ + ['whocares', '-0.5000', '+0.5000', '0', '0', '0', '0'] + ] + output_lines_with_header = print_difference_report( + self.json_diff_report, + utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + def test_json_diff_report(self): + expected_output = [ + { + 'name': u'whocares', + 'measurements': [ + {'time': -0.5, + 'cpu': 0.5, + 'real_time': 0.01, + 'real_time_other': 0.005, + 'cpu_time': 0.10, + 'cpu_time_other': 0.15} + ], + 'time_unit': 'ns', + 'utest': {} + } + ] + self.assertEqual(len(self.json_diff_report), len(expected_output)) + for out, expected in zip( + self.json_diff_report, expected_output): + self.assertEqual(out['name'], expected['name']) + self.assertEqual(out['time_unit'], expected['time_unit']) + assert_utest(self, out, expected) + assert_measurements(self, out, expected) + + +class TestReportSorting(unittest.TestCase): + @classmethod + def setUpClass(cls): + def load_result(): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput = os.path.join(testInputs, 'test4_run.json') + with open(testOutput, 'r') as f: + json = json.load(f) + return json + + cls.json = load_result() + + def test_json_diff_report_pretty_printing(self): + import util + + expected_names = [ + "99 family 0 instance 0 repetition 0", + "98 family 0 instance 0 repetition 1", + "97 family 0 instance 0 aggregate", + "96 family 0 instance 1 repetition 0", + "95 family 0 instance 1 repetition 1", + "94 family 0 instance 1 aggregate", + "93 family 1 instance 0 repetition 0", + "92 family 1 instance 0 repetition 1", + "91 family 1 instance 0 aggregate", + "90 family 1 instance 1 repetition 0", + "89 family 1 instance 1 repetition 1", + "88 family 1 instance 1 aggregate" + ] + + for n in range(len(self.json['benchmarks']) ** 2): + random.shuffle(self.json['benchmarks']) + sorted_benchmarks = util.sort_benchmark_results(self.json)[ + 'benchmarks'] + self.assertEqual(len(expected_names), len(sorted_benchmarks)) + for out, expected in zip(sorted_benchmarks, expected_names): + self.assertEqual(out['name'], expected) + + +def assert_utest(unittest_instance, lhs, rhs): + if lhs['utest']: + unittest_instance.assertAlmostEqual( + lhs['utest']['cpu_pvalue'], + rhs['utest']['cpu_pvalue']) + unittest_instance.assertAlmostEqual( + lhs['utest']['time_pvalue'], + rhs['utest']['time_pvalue']) + unittest_instance.assertEqual( + lhs['utest']['have_optimal_repetitions'], + rhs['utest']['have_optimal_repetitions']) + else: + # lhs is empty. assert if rhs is not. + unittest_instance.assertEqual(lhs['utest'], rhs['utest']) + + +def assert_measurements(unittest_instance, lhs, rhs): + for m1, m2 in zip(lhs['measurements'], rhs['measurements']): + unittest_instance.assertEqual(m1['real_time'], m2['real_time']) + unittest_instance.assertEqual(m1['cpu_time'], m2['cpu_time']) + # m1['time'] and m1['cpu'] hold values which are being calculated, + # and therefore we must use almost-equal pattern. + unittest_instance.assertAlmostEqual(m1['time'], m2['time'], places=4) + unittest_instance.assertAlmostEqual(m1['cpu'], m2['cpu'], places=4) + + +if __name__ == '__main__': + unittest.main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/bridge/third_party/benchmark/tools/gbench/util.py b/bridge/third_party/benchmark/tools/gbench/util.py new file mode 100644 index 0000000000..5d0012c0cb --- /dev/null +++ b/bridge/third_party/benchmark/tools/gbench/util.py @@ -0,0 +1,181 @@ +"""util.py - General utilities for running, loading, and processing benchmarks +""" +import json +import os +import tempfile +import subprocess +import sys +import functools + +# Input file type enumeration +IT_Invalid = 0 +IT_JSON = 1 +IT_Executable = 2 + +_num_magic_bytes = 2 if sys.platform.startswith('win') else 4 + + +def is_executable_file(filename): + """ + Return 'True' if 'filename' names a valid file which is likely + an executable. A file is considered an executable if it starts with the + magic bytes for a EXE, Mach O, or ELF file. + """ + if not os.path.isfile(filename): + return False + with open(filename, mode='rb') as f: + magic_bytes = f.read(_num_magic_bytes) + if sys.platform == 'darwin': + return magic_bytes in [ + b'\xfe\xed\xfa\xce', # MH_MAGIC + b'\xce\xfa\xed\xfe', # MH_CIGAM + b'\xfe\xed\xfa\xcf', # MH_MAGIC_64 + b'\xcf\xfa\xed\xfe', # MH_CIGAM_64 + b'\xca\xfe\xba\xbe', # FAT_MAGIC + b'\xbe\xba\xfe\xca' # FAT_CIGAM + ] + elif sys.platform.startswith('win'): + return magic_bytes == b'MZ' + else: + return magic_bytes == b'\x7FELF' + + +def is_json_file(filename): + """ + Returns 'True' if 'filename' names a valid JSON output file. + 'False' otherwise. + """ + try: + with open(filename, 'r') as f: + json.load(f) + return True + except BaseException: + pass + return False + + +def classify_input_file(filename): + """ + Return a tuple (type, msg) where 'type' specifies the classified type + of 'filename'. If 'type' is 'IT_Invalid' then 'msg' is a human readable + string represeting the error. + """ + ftype = IT_Invalid + err_msg = None + if not os.path.exists(filename): + err_msg = "'%s' does not exist" % filename + elif not os.path.isfile(filename): + err_msg = "'%s' does not name a file" % filename + elif is_executable_file(filename): + ftype = IT_Executable + elif is_json_file(filename): + ftype = IT_JSON + else: + err_msg = "'%s' does not name a valid benchmark executable or JSON file" % filename + return ftype, err_msg + + +def check_input_file(filename): + """ + Classify the file named by 'filename' and return the classification. + If the file is classified as 'IT_Invalid' print an error message and exit + the program. + """ + ftype, msg = classify_input_file(filename) + if ftype == IT_Invalid: + print("Invalid input file: %s" % msg) + sys.exit(1) + return ftype + + +def find_benchmark_flag(prefix, benchmark_flags): + """ + Search the specified list of flags for a flag matching `` and + if it is found return the arg it specifies. If specified more than once the + last value is returned. If the flag is not found None is returned. + """ + assert prefix.startswith('--') and prefix.endswith('=') + result = None + for f in benchmark_flags: + if f.startswith(prefix): + result = f[len(prefix):] + return result + + +def remove_benchmark_flags(prefix, benchmark_flags): + """ + Return a new list containing the specified benchmark_flags except those + with the specified prefix. + """ + assert prefix.startswith('--') and prefix.endswith('=') + return [f for f in benchmark_flags if not f.startswith(prefix)] + + +def load_benchmark_results(fname): + """ + Read benchmark output from a file and return the JSON object. + REQUIRES: 'fname' names a file containing JSON benchmark output. + """ + with open(fname, 'r') as f: + return json.load(f) + + +def sort_benchmark_results(result): + benchmarks = result['benchmarks'] + + # From inner key to the outer key! + benchmarks = sorted( + benchmarks, key=lambda benchmark: benchmark['repetition_index'] if 'repetition_index' in benchmark else -1) + benchmarks = sorted( + benchmarks, key=lambda benchmark: 1 if 'run_type' in benchmark and benchmark['run_type'] == "aggregate" else 0) + benchmarks = sorted( + benchmarks, key=lambda benchmark: benchmark['per_family_instance_index'] if 'per_family_instance_index' in benchmark else -1) + benchmarks = sorted( + benchmarks, key=lambda benchmark: benchmark['family_index'] if 'family_index' in benchmark else -1) + + result['benchmarks'] = benchmarks + return result + + +def run_benchmark(exe_name, benchmark_flags): + """ + Run a benchmark specified by 'exe_name' with the specified + 'benchmark_flags'. The benchmark is run directly as a subprocess to preserve + real time console output. + RETURNS: A JSON object representing the benchmark output + """ + output_name = find_benchmark_flag('--benchmark_out=', + benchmark_flags) + is_temp_output = False + if output_name is None: + is_temp_output = True + thandle, output_name = tempfile.mkstemp() + os.close(thandle) + benchmark_flags = list(benchmark_flags) + \ + ['--benchmark_out=%s' % output_name] + + cmd = [exe_name] + benchmark_flags + print("RUNNING: %s" % ' '.join(cmd)) + exitCode = subprocess.call(cmd) + if exitCode != 0: + print('TEST FAILED...') + sys.exit(exitCode) + json_res = load_benchmark_results(output_name) + if is_temp_output: + os.unlink(output_name) + return json_res + + +def run_or_load_benchmark(filename, benchmark_flags): + """ + Get the results for a specified benchmark. If 'filename' specifies + an executable benchmark then the results are generated by running the + benchmark. Otherwise 'filename' must name a valid JSON output file, + which is loaded and the result returned. + """ + ftype = check_input_file(filename) + if ftype == IT_JSON: + return load_benchmark_results(filename) + if ftype == IT_Executable: + return run_benchmark(filename, benchmark_flags) + raise ValueError('Unknown file type %s' % ftype) diff --git a/bridge/third_party/benchmark/tools/requirements.txt b/bridge/third_party/benchmark/tools/requirements.txt new file mode 100644 index 0000000000..3b3331b5af --- /dev/null +++ b/bridge/third_party/benchmark/tools/requirements.txt @@ -0,0 +1 @@ +scipy>=1.5.0 \ No newline at end of file diff --git a/bridge/third_party/benchmark/tools/strip_asm.py b/bridge/third_party/benchmark/tools/strip_asm.py new file mode 100755 index 0000000000..9030550b43 --- /dev/null +++ b/bridge/third_party/benchmark/tools/strip_asm.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python + +""" +strip_asm.py - Cleanup ASM output for the specified file +""" + +from argparse import ArgumentParser +import sys +import os +import re + +def find_used_labels(asm): + found = set() + label_re = re.compile("\s*j[a-z]+\s+\.L([a-zA-Z0-9][a-zA-Z0-9_]*)") + for l in asm.splitlines(): + m = label_re.match(l) + if m: + found.add('.L%s' % m.group(1)) + return found + + +def normalize_labels(asm): + decls = set() + label_decl = re.compile("^[.]{0,1}L([a-zA-Z0-9][a-zA-Z0-9_]*)(?=:)") + for l in asm.splitlines(): + m = label_decl.match(l) + if m: + decls.add(m.group(0)) + if len(decls) == 0: + return asm + needs_dot = next(iter(decls))[0] != '.' + if not needs_dot: + return asm + for ld in decls: + asm = re.sub("(^|\s+)" + ld + "(?=:|\s)", '\\1.' + ld, asm) + return asm + + +def transform_labels(asm): + asm = normalize_labels(asm) + used_decls = find_used_labels(asm) + new_asm = '' + label_decl = re.compile("^\.L([a-zA-Z0-9][a-zA-Z0-9_]*)(?=:)") + for l in asm.splitlines(): + m = label_decl.match(l) + if not m or m.group(0) in used_decls: + new_asm += l + new_asm += '\n' + return new_asm + + +def is_identifier(tk): + if len(tk) == 0: + return False + first = tk[0] + if not first.isalpha() and first != '_': + return False + for i in range(1, len(tk)): + c = tk[i] + if not c.isalnum() and c != '_': + return False + return True + +def process_identifiers(l): + """ + process_identifiers - process all identifiers and modify them to have + consistent names across all platforms; specifically across ELF and MachO. + For example, MachO inserts an additional understore at the beginning of + names. This function removes that. + """ + parts = re.split(r'([a-zA-Z0-9_]+)', l) + new_line = '' + for tk in parts: + if is_identifier(tk): + if tk.startswith('__Z'): + tk = tk[1:] + elif tk.startswith('_') and len(tk) > 1 and \ + tk[1].isalpha() and tk[1] != 'Z': + tk = tk[1:] + new_line += tk + return new_line + + +def process_asm(asm): + """ + Strip the ASM of unwanted directives and lines + """ + new_contents = '' + asm = transform_labels(asm) + + # TODO: Add more things we want to remove + discard_regexes = [ + re.compile("\s+\..*$"), # directive + re.compile("\s*#(NO_APP|APP)$"), #inline ASM + re.compile("\s*#.*$"), # comment line + re.compile("\s*\.globa?l\s*([.a-zA-Z_][a-zA-Z0-9$_.]*)"), #global directive + re.compile("\s*\.(string|asciz|ascii|[1248]?byte|short|word|long|quad|value|zero)"), + ] + keep_regexes = [ + + ] + fn_label_def = re.compile("^[a-zA-Z_][a-zA-Z0-9_.]*:") + for l in asm.splitlines(): + # Remove Mach-O attribute + l = l.replace('@GOTPCREL', '') + add_line = True + for reg in discard_regexes: + if reg.match(l) is not None: + add_line = False + break + for reg in keep_regexes: + if reg.match(l) is not None: + add_line = True + break + if add_line: + if fn_label_def.match(l) and len(new_contents) != 0: + new_contents += '\n' + l = process_identifiers(l) + new_contents += l + new_contents += '\n' + return new_contents + +def main(): + parser = ArgumentParser( + description='generate a stripped assembly file') + parser.add_argument( + 'input', metavar='input', type=str, nargs=1, + help='An input assembly file') + parser.add_argument( + 'out', metavar='output', type=str, nargs=1, + help='The output file') + args, unknown_args = parser.parse_known_args() + input = args.input[0] + output = args.out[0] + if not os.path.isfile(input): + print(("ERROR: input file '%s' does not exist") % input) + sys.exit(1) + contents = None + with open(input, 'r') as f: + contents = f.read() + new_contents = process_asm(contents) + with open(output, 'w') as f: + f.write(new_contents) + + +if __name__ == '__main__': + main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 1fe245764c..fae7647b00 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -20,7 +20,6 @@ import 'package:flutter/services.dart'; import 'package:test/test.dart'; import 'test_input.dart'; -import 'platform.dart'; import 'match_snapshots.dart'; // Steps for using dart:ffi to call a Dart function from C: @@ -146,7 +145,7 @@ typedef Native_RegisterTestEnvDartMethods = Void Function(Pointer method typedef Dart_RegisterTestEnvDartMethods = void Function(Pointer methodBytes, int length); final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = -nativeDynamicLibrary.lookup>('registerTestEnvDartMethods').asFunction(); +KrakenDynamicLibrary.ref.lookup>('registerTestEnvDartMethods').asFunction(); void registerDartTestMethodsToCpp() { diff --git a/integration_tests/lib/bridge/platform.dart b/integration_tests/lib/bridge/platform.dart deleted file mode 100644 index f0e7442f97..0000000000 --- a/integration_tests/lib/bridge/platform.dart +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ -// ignore_for_file: unused_import, undefined_function - -import 'dart:ffi'; -import 'dart:io' show Platform; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; -import 'package:path/path.dart'; - -/// Search dynamic lib from env.KRAKEN_LIBRARY_PATH or /usr/lib -const String KRAKEN_LIBRARY_PATH = 'KRAKEN_LIBRARY_PATH'; -const String KRAKEN_JS_ENGINE = 'KRAKEN_JS_ENGINE'; -final String? kkLibraryPath = Platform.environment[KRAKEN_LIBRARY_PATH]; -final String libName = 'libkraken_test'; -final String nativeDynamicLibraryName = Platform.isMacOS || Platform.isIOS - ? '$libName.dylib' - : Platform.isWindows ? '$libName.dll' : '$libName.so'; -DynamicLibrary nativeDynamicLibrary = DynamicLibrary.open(join( - kkLibraryPath ?? (Platform.isLinux ? '\$ORIGIN' : ''), - nativeDynamicLibraryName)); diff --git a/integration_tests/lib/bridge/to_native.dart b/integration_tests/lib/bridge/to_native.dart index 61d00f4d50..aa3dc2b5f6 100644 --- a/integration_tests/lib/bridge/to_native.dart +++ b/integration_tests/lib/bridge/to_native.dart @@ -9,8 +9,6 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:kraken/bridge.dart'; -import 'platform.dart'; - // Steps for using dart:ffi to call a C function from Dart: // 1. Import dart:ffi. // 2. Create a typedef with the FFI type signature of the C function. @@ -24,7 +22,7 @@ typedef Native_InitTestFramework = Void Function(Int32 contextId); typedef Dart_InitTestFramework = void Function(int contextId); final Dart_InitTestFramework _initTestFramework = - nativeDynamicLibrary.lookup>('initTestFramework').asFunction(); + KrakenDynamicLibrary.ref.lookup>('initTestFramework').asFunction(); void initTestFramework(int contextId) { _initTestFramework(contextId); @@ -35,7 +33,7 @@ typedef Native_EvaluateTestScripts = Int8 Function(Int32 contextId, Pointer, Pointer, int); final Dart_EvaluateTestScripts _evaluateTestScripts = - nativeDynamicLibrary.lookup>('evaluateTestScripts').asFunction(); +KrakenDynamicLibrary.ref.lookup>('evaluateTestScripts').asFunction(); void evaluateTestScripts(int contextId, String code, {String url = 'test://', int line = 0}) { Pointer _url = (url).toNativeUtf8(); @@ -48,7 +46,7 @@ typedef Native_ExecuteTest = Void Function(Int32 contextId, Pointer>); final Dart_ExecuteTest _executeTest = - nativeDynamicLibrary.lookup>('executeTest').asFunction(); +KrakenDynamicLibrary.ref.lookup>('executeTest').asFunction(); List?> completerList = List.filled(10, null); diff --git a/integration_tests/lib/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index 5758536cf0..33a79e77ae 100644 --- a/integration_tests/lib/custom/custom_element.dart +++ b/integration_tests/lib/custom/custom_element.dart @@ -1,45 +1,80 @@ -import 'dart:ffi'; import 'dart:async'; -import 'package:kraken/bridge.dart'; -import 'package:kraken/dom.dart'; +import 'package:kraken/dom.dart' as dom; import 'package:kraken/widget.dart'; -import 'package:flutter/material.dart' show TextDirection, TextStyle, Color, Image, Text, AssetImage, Widget, BuildContext hide Element; +import 'package:waterfall_flow/waterfall_flow.dart'; +import 'package:flutter/material.dart'; + +class WaterfallFlowWidgetElement extends WidgetElement { + WaterfallFlowWidgetElement(dom.EventTargetContext? context) : + super(context); + + List _children = []; + + Widget _func (BuildContext context, int index) { + return _children[index]; + } + + @override + Widget build(BuildContext context, Map properties, List children) { + _children = children; + + return WaterfallFlow.builder( + itemBuilder: _func, + padding: EdgeInsets.all(5.0), + gridDelegate: SliverWaterfallFlowDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 5.0, + mainAxisSpacing: 5.0, + lastChildLayoutTypeBuilder: (index) => index == children.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none, + ), + ); + } +} class TextWidgetElement extends WidgetElement { - TextWidgetElement(int targetId, Pointer nativeEventTarget, ElementManager elementManager) : - super(targetId, nativeEventTarget, elementManager); + TextWidgetElement(dom.EventTargetContext? context) : + super(context); @override - Widget build(BuildContext context, Map properties) { + Widget build(BuildContext context, Map properties, List children) { return Text(properties['value'] ?? '', textDirection: TextDirection.ltr, style: TextStyle(color: Color.fromARGB(255, 100, 100, 100))); } } class ImageWidgetElement extends WidgetElement { - ImageWidgetElement(int targetId, Pointer nativeEventTarget, ElementManager elementManager) : - super(targetId, nativeEventTarget, elementManager); + ImageWidgetElement(dom.EventTargetContext? context) : + super(context); @override - Widget build(BuildContext context, Map properties) { + Widget build(BuildContext context, Map properties, List children) { return Image(image: AssetImage(properties['src'])); } } -void defineKrakenCustomElements() { - Kraken.defineCustomElement('sample-element', (int targetId, Pointer nativeEventTarget, ElementManager elementManager) { - return SampleElement(targetId, nativeEventTarget, elementManager); - }); - Kraken.defineCustomElement('flutter-text', (targetId, nativeEventTarget, elementManager) { - return TextWidgetElement(targetId, nativeEventTarget, elementManager); - }); - Kraken.defineCustomElement('flutter-asset-image', (targetId, nativeEventTarget, elementManager) { - return ImageWidgetElement(targetId, nativeEventTarget, elementManager); - }); +class ContainerWidgetElement extends WidgetElement { + ContainerWidgetElement(dom.EventTargetContext? context) : + super(context); + + @override + Widget build(BuildContext context, Map properties, List children) { + return Container( + width: 200, + height: 200, + decoration: const BoxDecoration( + border: Border( top: BorderSide( width: 5, color: Colors.red ), bottom: BorderSide( width: 5, color: Colors.red ), left: BorderSide( width: 5, color: Colors.red ), right: BorderSide( width: 5, color: Colors.red )), + ), + child: Column( + children: children, + ), + ); + } } -class SampleElement extends Element { - SampleElement(int targetId, Pointer nativeEventTarget, ElementManager elementManager) - : super(targetId, nativeEventTarget, elementManager); +class SampleElement extends dom.Element { + SampleElement(dom.EventTargetContext? context) + : super(context); @override getProperty(String key) { @@ -76,4 +111,20 @@ class SampleElement extends Element { } } - +void defineKrakenCustomElements() { + Kraken.defineCustomElement('waterfall-flow', (dom.EventTargetContext? context) { + return WaterfallFlowWidgetElement(context); + }); + Kraken.defineCustomElement('flutter-container', (dom.EventTargetContext? context) { + return ContainerWidgetElement(context); + }); + Kraken.defineCustomElement('sample-element', (dom.EventTargetContext? context) { + return SampleElement(context); + }); + Kraken.defineCustomElement('flutter-text', (dom.EventTargetContext? context) { + return TextWidgetElement(context); + }); + Kraken.defineCustomElement('flutter-asset-image', (dom.EventTargetContext? context) { + return ImageWidgetElement(context); + }); +} diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index 01df5cff28..29de9ffc88 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -6,6 +6,7 @@ import 'package:kraken/css.dart'; import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/foundation.dart'; +import 'package:kraken/kraken.dart'; import 'package:kraken/module.dart'; import 'package:kraken/widget.dart'; import 'package:ansicolor/ansicolor.dart'; @@ -41,6 +42,8 @@ class IntegrationTestUriParser extends UriParser { // By CLI: `KRAKEN_ENABLE_TEST=true flutter run` void main() async { + // Overrides library name. + KrakenDynamicLibrary.libName = 'libkraken_test'; defineKrakenCustomElements(); // FIXME: This is a workaround for testcase @@ -57,7 +60,6 @@ void main() async { LOCAL_HTTP_SERVER = '${httpServer.getUri().toString()}'; '''; - // Set render font family AlibabaPuHuiTi to resolve rendering difference. CSSText.DEFAULT_FONT_FAMILY_FALLBACK = ['AlibabaPuHuiTi']; @@ -82,7 +84,7 @@ void main() async { var kraken = krakenMap[i] = Kraken( viewportWidth: 360, viewportHeight: 640, - bundleContent: 'console.log("Starting integration tests...")', + bundle: KrakenBundle.fromContent('console.log("Starting integration tests...")'), disableViewportWidthAssertion: true, disableViewportHeightAssertion: true, javaScriptChannel: javaScriptChannel, @@ -90,7 +92,7 @@ void main() async { onDrag: (GestureEvent gestureEvent) { if (gestureEvent.state == EVENT_STATE_START) { var event = CustomEvent('nativegesture', CustomEventInit(detail: 'nativegesture')); - krakenMap[i]!.controller!.view.document!.documentElement.dispatchEvent(event); + krakenMap[i]!.controller!.view.document.documentElement?.dispatchEvent(event); } }, ), @@ -117,7 +119,6 @@ void main() async { }); testTextInput = TestTextInput(); - testTextInput.register(); WidgetsBinding.instance!.addPostFrameCallback((_) async { registerDartTestMethodsToCpp(); @@ -145,7 +146,7 @@ void main() async { // Manual dispose context for memory leak check. krakenMap.forEach((key, kraken) { - disposeContext(kraken.controller!.view.contextId); + disposePage(kraken.controller!.view.contextId); }); for (int i = 0; i < results.length; i ++) { diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index ed611d4488..474176debc 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -1,21 +1,24 @@ import 'dart:async'; import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/foundation.dart'; -import 'package:kraken/module.dart'; import 'package:kraken/widget.dart'; +import 'package:kraken/launcher.dart'; import 'package:ansicolor/ansicolor.dart'; import 'package:path/path.dart' as path; -import 'bridge/from_native.dart'; -import 'bridge/to_native.dart'; -import 'custom/custom_object_element.dart'; import 'package:kraken_websocket/kraken_websocket.dart'; import 'package:kraken_video_player/kraken_video_player.dart'; import 'package:kraken_webview/kraken_webview.dart'; +import 'bridge/from_native.dart'; +import 'bridge/to_native.dart'; +import 'custom/custom_object_element.dart'; + String? pass = (AnsiPen()..green())('[TEST PASS]'); String? err = (AnsiPen()..red())('[TEST FAILED]'); @@ -40,6 +43,9 @@ class IntegrationTestUriParser extends UriParser { // By CLI: `KRAKEN_ENABLE_TEST=true flutter run` void main() async { + // Overrides library name. + KrakenDynamicLibrary.libName = 'libkraken_test'; + KrakenWebsocket.initialize(); KrakenVideoPlayer.initialize(); KrakenWebView.initialize(); @@ -68,7 +74,7 @@ void main() async { var kraken = krakenMap[i] = Kraken( viewportWidth: 360, viewportHeight: 640, - bundleContent: 'console.log("Starting Plugin tests...")', + bundle: KrakenBundle.fromContent('console.log("Starting Plugin tests...")'), disableViewportWidthAssertion: true, disableViewportHeightAssertion: true, uriParser: IntegrationTestUriParser(), diff --git a/integration_tests/package.json b/integration_tests/package.json index 35c40d7eb2..51bc4a31f0 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -2,8 +2,9 @@ "private": true, "scripts": { "pretest": "npm install", - "test": "flutter pub get && flutter clean && npm run clean && npm run lint && npm run integration", - "plugin_test": "npm run pretest && flutter pub get && flutter clean && npm run clean && npm run lint && npm run plugin_integration", + "test": "flutter clean && flutter pub get && npm run clean && npm run integration", + "posttest": "npm run lint", + "plugin_test": "npm run pretest && flutter clean && flutter pub get && npm run clean && npm run lint && npm run plugin_integration", "lint": "cd ../ && npm run lint", "clean": "rm -rf ./.specs && git clean -xfd ./snapshots", "specs": "webpack --config ./webpack.config.js", @@ -31,6 +32,7 @@ }, "dependencies": { "core-js": "^3.8.2", - "lodash.flattendeep": "^4.4.0" + "lodash.flattendeep": "^4.4.0", + "node-html-parser": "^5.1.0" } } diff --git a/integration_tests/pubspec.yaml b/integration_tests/pubspec.yaml index 588519bcb6..bc8898bd43 100644 --- a/integration_tests/pubspec.yaml +++ b/integration_tests/pubspec.yaml @@ -27,9 +27,10 @@ dependencies: sdk: flutter kraken: 0.8.0-dev.1 image: ^3.0.2 - kraken_video_player: ^1.1.0 + kraken_video_player: ^2.0.0-dev.0 kraken_websocket: ^1.0.0 - kraken_webview: ^1.1.1 + kraken_webview: ^2.0.0-dev.0 + waterfall_flow: ^3.0.1 dev_dependencies: flutter_test: diff --git a/integration_tests/runtime/global.ts b/integration_tests/runtime/global.ts index bad3119eb0..ed9f5b845a 100644 --- a/integration_tests/runtime/global.ts +++ b/integration_tests/runtime/global.ts @@ -176,7 +176,6 @@ async function simulateSwipe(startX: number, startY: number, endX: number, endY: let progress = i / totalCount; let diffX = diffXPerSecond * 100 * ease.transformInternal(progress); let diffY = diffYPerSecond * 100 * ease.transformInternal(progress); - await sleep(pointerMoveDelay); params.push([startX + diffX, startY + diffY, PointerChange.move]) } @@ -192,7 +191,7 @@ async function simulatePointDown(x: number, y: number, pointer: number = 0) { } // Simulate an point up action. -async function simulatePoinrUp(x: number, y: number, pointer: number = 0) { +async function simulatePointUp(x: number, y: number, pointer: number = 0) { await simulatePointer([ [x, y, PointerChange.up], ], pointer); @@ -228,5 +227,5 @@ Object.assign(global, { sleep, snapshot, simulatePointDown, - simulatePoinrUp, + simulatePointUp, }); diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js new file mode 100644 index 0000000000..29f5414630 --- /dev/null +++ b/integration_tests/scripts/html_loader.js @@ -0,0 +1,74 @@ +const path = require('path'); +const HTMLParser = require('node-html-parser'); + +const SCRIPT = 'script'; + +let filename = ''; +const scripts = []; + +const traverseParseHTML = (ele) => { + ele.childNodes && ele.childNodes.forEach(e => { + if (e.rawTagName === SCRIPT) { + e.childNodes.forEach(item => { + // TextNode of script element. + if (item.nodeType === 3) { + scripts.push(item._rawText); + } + // Delete content of script element for avoid to script repetition. + item._rawText = ''; + }) + } + traverseParseHTML(e); + }); +} + +const loader = function(source) { + const opts = this.query || {}; + const snapshotFilepath = path.relative( + opts.workspacePath, + path.join( + opts.snapshotPath, + path.relative(opts.testPath, filename), + ) + ); + + let root = HTMLParser.parse(source); + traverseParseHTML(root); + + // Set attr of HTML can let the case use fit. For example: xxx . + let isFit = false; + root.childNodes && root.childNodes.forEach(ele => { + if (ele.rawAttrs && ele.rawAttrs.indexOf('fit') >= 0) { + isFit = true; + } + }) + + const htmlString = root.toString().replace(/\n/g, ''); + + return ` + describe('html-${path.basename(filename)}', () => { + // Use html_snapshot to snapshot in html file. + const html_snapshot = async (...argv) => { + if (argv.length === 0) { + await snapshot(null, '${snapshotFilepath}'); + } else if (argv.length === 1) { + await snapshot(argv[0], '${snapshotFilepath}'); + } + }; + + // Use html_parse to parser html in html file. + const html_parse = () => __kraken_parse_html__('${htmlString}'); + + ${isFit ? 'fit' : 'it'}("should work", async () => {\ + html_parse();\ + ${scripts.length === 0 ? 'await html_snapshot();' : scripts.join('\n')} + }) + }); + `; +}; + +loader.pitch = (f) => { + filename = f; +}; + +module.exports = loader; diff --git a/integration_tests/snapshots/css/css-display/block-in-inline.ts.9fe720c71.png b/integration_tests/snapshots/css/css-display/block-in-inline.ts.9fe720c71.png index 804712f588..3e153c6bb7 100644 Binary files a/integration_tests/snapshots/css/css-display/block-in-inline.ts.9fe720c71.png and b/integration_tests/snapshots/css/css-display/block-in-inline.ts.9fe720c71.png differ diff --git a/integration_tests/snapshots/css/css-display/block-in.ts.18e00a921.png b/integration_tests/snapshots/css/css-display/block-in.ts.18e00a921.png index fbf4ac94db..12e6a08a24 100644 Binary files a/integration_tests/snapshots/css/css-display/block-in.ts.18e00a921.png and b/integration_tests/snapshots/css/css-display/block-in.ts.18e00a921.png differ diff --git a/integration_tests/snapshots/css/css-display/block-in.ts.39bde26f1.png b/integration_tests/snapshots/css/css-display/block-in.ts.39bde26f1.png index 964824584f..71ab057d99 100644 Binary files a/integration_tests/snapshots/css/css-display/block-in.ts.39bde26f1.png and b/integration_tests/snapshots/css/css-display/block-in.ts.39bde26f1.png differ diff --git a/integration_tests/snapshots/css/css-display/block-in.ts.fde04d101.png b/integration_tests/snapshots/css/css-display/block-in.ts.fde04d101.png index d4ea85613a..88401869d0 100644 Binary files a/integration_tests/snapshots/css/css-display/block-in.ts.fde04d101.png and b/integration_tests/snapshots/css/css-display/block-in.ts.fde04d101.png differ diff --git a/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e1.png b/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e1.png new file mode 100644 index 0000000000..8c6aac0480 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e1.png differ diff --git a/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e2.png b/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e2.png new file mode 100644 index 0000000000..a6ff3a3263 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/sliver.ts.68990a0e2.png differ diff --git a/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e31.png b/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e31.png new file mode 100644 index 0000000000..3969f2b5f5 Binary files /dev/null and b/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e31.png differ diff --git a/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e32.png b/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e32.png new file mode 100644 index 0000000000..7169a15efa Binary files /dev/null and b/integration_tests/snapshots/css/css-flexbox/align-items.ts.e81143e32.png differ diff --git a/integration_tests/snapshots/css/css-flexbox/flex_shrink.ts.911a58ee1.png b/integration_tests/snapshots/css/css-flexbox/flex_shrink.ts.911a58ee1.png new file mode 100644 index 0000000000..2a2e2ffc2b Binary files /dev/null and b/integration_tests/snapshots/css/css-flexbox/flex_shrink.ts.911a58ee1.png differ diff --git a/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.784e156d1.png b/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.784e156d1.png new file mode 100644 index 0000000000..0ae405b160 Binary files /dev/null and b/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.784e156d1.png differ diff --git a/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.dc55f15a1.png b/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.dc55f15a1.png new file mode 100644 index 0000000000..ee3fb2f8f2 Binary files /dev/null and b/integration_tests/snapshots/css/css-flexbox/position-absolute.ts.dc55f15a1.png differ diff --git a/integration_tests/snapshots/css/css-flow/block-in.ts.18e00a921.png b/integration_tests/snapshots/css/css-flow/block-in.ts.18e00a921.png index fbf4ac94db..12e6a08a24 100644 Binary files a/integration_tests/snapshots/css/css-flow/block-in.ts.18e00a921.png and b/integration_tests/snapshots/css/css-flow/block-in.ts.18e00a921.png differ diff --git a/integration_tests/snapshots/css/css-flow/block-in.ts.39bde26f1.png b/integration_tests/snapshots/css/css-flow/block-in.ts.39bde26f1.png index 964824584f..71ab057d99 100644 Binary files a/integration_tests/snapshots/css/css-flow/block-in.ts.39bde26f1.png and b/integration_tests/snapshots/css/css-flow/block-in.ts.39bde26f1.png differ diff --git a/integration_tests/snapshots/css/css-flow/block-in.ts.fde04d101.png b/integration_tests/snapshots/css/css-flow/block-in.ts.fde04d101.png index d4ea85613a..88401869d0 100644 Binary files a/integration_tests/snapshots/css/css-flow/block-in.ts.fde04d101.png and b/integration_tests/snapshots/css/css-flow/block-in.ts.fde04d101.png differ diff --git a/integration_tests/snapshots/css/css-position/absolute.ts.09aad6c31.png b/integration_tests/snapshots/css/css-position/absolute.ts.09aad6c31.png new file mode 100644 index 0000000000..befa19de83 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/absolute.ts.09aad6c31.png differ diff --git a/integration_tests/snapshots/css/css-position/absolute.ts.389330e81.png b/integration_tests/snapshots/css/css-position/absolute.ts.389330e81.png new file mode 100644 index 0000000000..326752cae0 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/absolute.ts.389330e81.png differ diff --git a/integration_tests/snapshots/css/css-position/absolute.ts.3efa54b21.png b/integration_tests/snapshots/css/css-position/absolute.ts.3efa54b21.png new file mode 100644 index 0000000000..e5c78fbc46 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/absolute.ts.3efa54b21.png differ diff --git a/integration_tests/snapshots/css/css-position/absolute.ts.75dd44031.png b/integration_tests/snapshots/css/css-position/absolute.ts.75dd44031.png new file mode 100644 index 0000000000..326752cae0 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/absolute.ts.75dd44031.png differ diff --git a/integration_tests/snapshots/css/css-position/absolute.ts.dffe41a91.png b/integration_tests/snapshots/css/css-position/absolute.ts.dffe41a91.png new file mode 100644 index 0000000000..aab11d5bcb Binary files /dev/null and b/integration_tests/snapshots/css/css-position/absolute.ts.dffe41a91.png differ diff --git a/integration_tests/snapshots/css/css-position/fixed.ts.52dcf5d71.png b/integration_tests/snapshots/css/css-position/fixed.ts.52dcf5d71.png new file mode 100644 index 0000000000..648790c377 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/fixed.ts.52dcf5d71.png differ diff --git a/integration_tests/snapshots/css/css-position/fixed.ts.7693463d1.png b/integration_tests/snapshots/css/css-position/fixed.ts.7693463d1.png new file mode 100644 index 0000000000..befa19de83 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/fixed.ts.7693463d1.png differ diff --git a/integration_tests/snapshots/css/css-position/fixed.ts.ad01a2041.png b/integration_tests/snapshots/css/css-position/fixed.ts.ad01a2041.png new file mode 100644 index 0000000000..648790c377 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/fixed.ts.ad01a2041.png differ diff --git a/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png b/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png new file mode 100644 index 0000000000..c851065325 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png differ diff --git a/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png b/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png new file mode 100644 index 0000000000..20734c78ef Binary files /dev/null and b/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png differ diff --git a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.13c9173d1.png b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.13c9173d1.png index a500e1c41b..0c9655f7a4 100644 Binary files a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.13c9173d1.png and b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.13c9173d1.png differ diff --git a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e41.png b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e41.png index 501bedfa22..93baf7d9c7 100644 Binary files a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e41.png and b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e41.png differ diff --git a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e42.png b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e42.png index a172b950ab..8d6283d8a9 100644 Binary files a/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e42.png and b/integration_tests/snapshots/css/css-text-decor/text-shadow.ts.3b9aa4e42.png differ diff --git a/integration_tests/snapshots/css/css-transforms/translateX.ts.b5c3bf8d1.png b/integration_tests/snapshots/css/css-transforms/translateX.ts.b5c3bf8d1.png new file mode 100644 index 0000000000..78638d318b Binary files /dev/null and b/integration_tests/snapshots/css/css-transforms/translateX.ts.b5c3bf8d1.png differ diff --git a/integration_tests/snapshots/css/css-transforms/translateY.ts.bda289211.png b/integration_tests/snapshots/css/css-transforms/translateY.ts.bda289211.png new file mode 100644 index 0000000000..6d8fe4d29c Binary files /dev/null and b/integration_tests/snapshots/css/css-transforms/translateY.ts.bda289211.png differ diff --git a/integration_tests/snapshots/css/css-transitions/transition-property.ts.1a5e3ec22.png b/integration_tests/snapshots/css/css-transitions/transition-property.ts.1a5e3ec22.png index 28252c95c5..1dea083ed6 100644 Binary files a/integration_tests/snapshots/css/css-transitions/transition-property.ts.1a5e3ec22.png and b/integration_tests/snapshots/css/css-transitions/transition-property.ts.1a5e3ec22.png differ diff --git a/integration_tests/snapshots/css/css-values/px.ts.e6b7b8b21.png b/integration_tests/snapshots/css/css-values/px.ts.e6b7b8b21.png new file mode 100644 index 0000000000..c4874bc9b1 Binary files /dev/null and b/integration_tests/snapshots/css/css-values/px.ts.e6b7b8b21.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.37d8f5d81.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.37d8f5d81.png new file mode 100644 index 0000000000..48a23161c6 Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.37d8f5d81.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.5dde2c731.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.5dde2c731.png new file mode 100644 index 0000000000..ca498ffc08 Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.5dde2c731.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.7111cb3c1.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.7111cb3c1.png new file mode 100644 index 0000000000..48a23161c6 Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.7111cb3c1.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png new file mode 100644 index 0000000000..e63609372a Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.9e1c9bc01.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.9e1c9bc01.png new file mode 100644 index 0000000000..cd460c6f6b Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.9e1c9bc01.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.af596f8a1.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.af596f8a1.png new file mode 100644 index 0000000000..92744ca18d Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.af596f8a1.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.e0b274c01.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.e0b274c01.png new file mode 100644 index 0000000000..443df22e4e Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.e0b274c01.png differ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png new file mode 100644 index 0000000000..a55c5eb4eb Binary files /dev/null and b/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png differ diff --git a/integration_tests/snapshots/dom/elements/br.ts.2334eb651.png b/integration_tests/snapshots/dom/elements/br.ts.2334eb651.png new file mode 100644 index 0000000000..6a3b631d96 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/br.ts.2334eb651.png differ diff --git a/integration_tests/snapshots/dom/elements/br.ts.2437dbca1.png b/integration_tests/snapshots/dom/elements/br.ts.2437dbca1.png new file mode 100644 index 0000000000..e0425e1dc9 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/br.ts.2437dbca1.png differ diff --git a/integration_tests/snapshots/dom/elements/br.ts.674727a91.png b/integration_tests/snapshots/dom/elements/br.ts.674727a91.png new file mode 100644 index 0000000000..8dec842d95 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/br.ts.674727a91.png differ diff --git a/integration_tests/snapshots/dom/elements/br.ts.8d7589671.png b/integration_tests/snapshots/dom/elements/br.ts.8d7589671.png new file mode 100644 index 0000000000..8dec842d95 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/br.ts.8d7589671.png differ diff --git a/integration_tests/snapshots/dom/elements/br.ts.ed7c61d61.png b/integration_tests/snapshots/dom/elements/br.ts.ed7c61d61.png new file mode 100644 index 0000000000..2c04f0face Binary files /dev/null and b/integration_tests/snapshots/dom/elements/br.ts.ed7c61d61.png differ diff --git a/integration_tests/snapshots/dom/elements/canvas/context2d.ts.05fab60c1.png b/integration_tests/snapshots/dom/elements/canvas/context2d.ts.05fab60c1.png index 3ecf2757a7..d2b958d095 100644 Binary files a/integration_tests/snapshots/dom/elements/canvas/context2d.ts.05fab60c1.png and b/integration_tests/snapshots/dom/elements/canvas/context2d.ts.05fab60c1.png differ diff --git a/integration_tests/snapshots/dom/elements/canvas/context2d.ts.3e9e6a8e1.png b/integration_tests/snapshots/dom/elements/canvas/context2d.ts.3e9e6a8e1.png index e471c6c27a..0edd7c1856 100644 Binary files a/integration_tests/snapshots/dom/elements/canvas/context2d.ts.3e9e6a8e1.png and b/integration_tests/snapshots/dom/elements/canvas/context2d.ts.3e9e6a8e1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.400d17681.png b/integration_tests/snapshots/dom/elements/custom-element.ts.400d17681.png new file mode 100644 index 0000000000..b77ea8e249 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.400d17681.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.4056c9991.png b/integration_tests/snapshots/dom/elements/custom-element.ts.4056c9991.png new file mode 100644 index 0000000000..b4f10ba85c Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.4056c9991.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.5c5ea81b1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.5c5ea81b1.png new file mode 100644 index 0000000000..8678ef35ca Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.5c5ea81b1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.633308271.png b/integration_tests/snapshots/dom/elements/custom-element.ts.633308271.png new file mode 100644 index 0000000000..00965cc9aa Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.633308271.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.7122b2171.png b/integration_tests/snapshots/dom/elements/custom-element.ts.7122b2171.png new file mode 100644 index 0000000000..f39b9c2de3 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.7122b2171.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.7253dbaf1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.7253dbaf1.png new file mode 100644 index 0000000000..deaa789156 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.7253dbaf1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.88f9bec61.png b/integration_tests/snapshots/dom/elements/custom-element.ts.88f9bec61.png new file mode 100644 index 0000000000..f88013d6e7 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.88f9bec61.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.9319a30e1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.9319a30e1.png new file mode 100644 index 0000000000..cb8fecab8b Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.9319a30e1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.ae130e951.png b/integration_tests/snapshots/dom/elements/custom-element.ts.ae130e951.png new file mode 100644 index 0000000000..0fb902b0b2 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.ae130e951.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.ece784ea1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.ece784ea1.png new file mode 100644 index 0000000000..2fa76f7234 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.ece784ea1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.f789134a1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.f789134a1.png new file mode 100644 index 0000000000..840dcc8c7d Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.f789134a1.png differ diff --git a/integration_tests/snapshots/dom/elements/custom-element.ts.f8441f1b1.png b/integration_tests/snapshots/dom/elements/custom-element.ts.f8441f1b1.png new file mode 100644 index 0000000000..8678ef35ca Binary files /dev/null and b/integration_tests/snapshots/dom/elements/custom-element.ts.f8441f1b1.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.56441b221.png b/integration_tests/snapshots/dom/elements/img.ts.56441b221.png new file mode 100644 index 0000000000..b30d241138 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/img.ts.56441b221.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.56fc70fa1.png b/integration_tests/snapshots/dom/elements/img.ts.56fc70fa1.png index 1711ff5635..091b2aa0be 100644 Binary files a/integration_tests/snapshots/dom/elements/img.ts.56fc70fa1.png and b/integration_tests/snapshots/dom/elements/img.ts.56fc70fa1.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.7bcfa1081.png b/integration_tests/snapshots/dom/elements/img.ts.7bcfa1081.png new file mode 100644 index 0000000000..ac7c517411 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/img.ts.7bcfa1081.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.7bcfa1082.png b/integration_tests/snapshots/dom/elements/img.ts.7bcfa1082.png new file mode 100644 index 0000000000..271381af58 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/img.ts.7bcfa1082.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.8ea40d5a1.png b/integration_tests/snapshots/dom/elements/img.ts.8ea40d5a1.png new file mode 100644 index 0000000000..b30d241138 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/img.ts.8ea40d5a1.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.ea354b921.png b/integration_tests/snapshots/dom/elements/img.ts.ea354b921.png index 1711ff5635..091b2aa0be 100644 Binary files a/integration_tests/snapshots/dom/elements/img.ts.ea354b921.png and b/integration_tests/snapshots/dom/elements/img.ts.ea354b921.png differ diff --git a/integration_tests/snapshots/dom/elements/img.ts.ff2142cd1.png b/integration_tests/snapshots/dom/elements/img.ts.ff2142cd1.png new file mode 100644 index 0000000000..af50889cc5 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/img.ts.ff2142cd1.png differ diff --git a/integration_tests/snapshots/dom/elements/pre.ts.3745c23b1.png b/integration_tests/snapshots/dom/elements/pre.ts.3745c23b1.png index 3c78da9e8f..f54e0e88a5 100644 Binary files a/integration_tests/snapshots/dom/elements/pre.ts.3745c23b1.png and b/integration_tests/snapshots/dom/elements/pre.ts.3745c23b1.png differ diff --git a/integration_tests/snapshots/dom/events/event.ts.a64bc6611.png b/integration_tests/snapshots/dom/events/event.ts.a64bc6611.png index 17b606eabd..94afc21bbb 100644 Binary files a/integration_tests/snapshots/dom/events/event.ts.a64bc6611.png and b/integration_tests/snapshots/dom/events/event.ts.a64bc6611.png differ diff --git a/integration_tests/snapshots/dom/nodes/insert-before.ts.1a0a72251.png b/integration_tests/snapshots/dom/nodes/insert-before.ts.1a0a72251.png new file mode 100644 index 0000000000..035183ef79 Binary files /dev/null and b/integration_tests/snapshots/dom/nodes/insert-before.ts.1a0a72251.png differ diff --git a/integration_tests/snapshots/dom/nodes/insert-before.ts.370933ef1.png b/integration_tests/snapshots/dom/nodes/insert-before.ts.370933ef1.png new file mode 100644 index 0000000000..73319fcceb Binary files /dev/null and b/integration_tests/snapshots/dom/nodes/insert-before.ts.370933ef1.png differ diff --git a/integration_tests/snapshots/dom/nodes/insert-before.ts.b4c2022b1.png b/integration_tests/snapshots/dom/nodes/insert-before.ts.b4c2022b1.png new file mode 100644 index 0000000000..b0ab11ceb1 Binary files /dev/null and b/integration_tests/snapshots/dom/nodes/insert-before.ts.b4c2022b1.png differ diff --git a/integration_tests/snapshots/dom/nodes/textNode.ts.587c301b1.png b/integration_tests/snapshots/dom/nodes/textNode.ts.587c301b1.png new file mode 100644 index 0000000000..8876f826d9 Binary files /dev/null and b/integration_tests/snapshots/dom/nodes/textNode.ts.587c301b1.png differ diff --git a/integration_tests/snapshots/dom/nodes/textNode.ts.9f4d71e21.png b/integration_tests/snapshots/dom/nodes/textNode.ts.9f4d71e21.png new file mode 100644 index 0000000000..8876f826d9 Binary files /dev/null and b/integration_tests/snapshots/dom/nodes/textNode.ts.9f4d71e21.png differ diff --git a/integration_tests/specs/css/css-display/sliver.ts b/integration_tests/specs/css/css-display/sliver.ts index da7093e96a..2477a69cde 100644 --- a/integration_tests/specs/css/css-display/sliver.ts +++ b/integration_tests/specs/css/css-display/sliver.ts @@ -153,6 +153,54 @@ describe('display sliver', () => { await snapshot(); }); + it('should works with height of sliver child changes', async (done) => { + let div; + let div1; + let div2; + div = createElement( + 'div', + { + style: { + display: 'sliver', + width: '200px', + height: '200px', + backgroundColor: 'red' + }, + }, [ + (div1 = createElement('div', { + style: { + positive: 'relative', + width: '200px', + height: '100px', + backgroundColor: 'green' + } + }, [ + createText('1') + ])), + (div2 = createElement('div', { + style: { + positive: 'relative', + width: '200px', + height: '100px', + backgroundColor: 'yellow' + } + }, [ + createText('2') + ])) + ] + ); + BODY.appendChild(div); + + await snapshot(); + + requestAnimationFrame(async () => { + div1.style.height = '50px'; + div2.style.height = '50px'; + await snapshot(); + done(); + }); + }); + it('sliver child is text or comment', async () => { var comment = document.createComment('HelloWorld'); var text = document.createTextNode('HelloWorld'); @@ -199,4 +247,17 @@ describe('display sliver', () => { await simulateClick(50, 20); // Will trigger done. }); + + it('sliver child with none-static position not throw errors', async () => { + var container = createSliverBasicCase(); + var firstChild = container.firstChild; // should be element. + firstChild.style.position = 'relative'; + + var innerChild = document.createElement('div'); + innerChild.appendChild(document.createTextNode('helloworld')); + innerChild.style.position = 'relative'; + innerChild.style.top = innerChild.style.left = '15px'; + firstChild?.appendChild(innerChild); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flexbox/align-items.ts b/integration_tests/specs/css/css-flexbox/align-items.ts index 44a02074fd..aa157166ae 100644 --- a/integration_tests/specs/css/css-flexbox/align-items.ts +++ b/integration_tests/specs/css/css-flexbox/align-items.ts @@ -1130,6 +1130,46 @@ describe('align-items', () => { await snapshot(); }); + it('does not work with stretch when align-self of flex item changed from auto to flex-start', async (done) => { + let flexbox; + let flexitem; + + flexbox = createElement( + 'div', + { + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + flexDirection: 'column', + width: '200px', + height: '120px', + 'box-sizing': 'border-box', + }, + }, + [ + (flexitem = createElement('div', { + style: { + 'height': '50px', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + // 'align-self': 'flex-start', + }, + })), + ] + ); + + BODY.appendChild(flexbox); + + await snapshot(); + + requestAnimationFrame(async () => { + flexitem.style.alignSelf = 'flex-start'; + await snapshot(); + done(); + }); + }); + it('should works with img with no size set', async () => { const container = createElement( 'div', diff --git a/integration_tests/specs/css/css-flexbox/auto-margins.ts b/integration_tests/specs/css/css-flexbox/auto-margins.ts index 1d32bad8d3..0bb1abbdbf 100644 --- a/integration_tests/specs/css/css-flexbox/auto-margins.ts +++ b/integration_tests/specs/css/css-flexbox/auto-margins.ts @@ -52,8 +52,6 @@ describe('auto-margins', () => { }, [ img = createElement('img', { - src: - 'https://kraken.oss-cn-hangzhou.aliyuncs.com/images/300x150-green.png', style: { 'box-sizing': 'border-box', margin: 'auto', @@ -67,6 +65,7 @@ describe('auto-margins', () => { document.body.appendChild(div_1); await snapshot(); + img.src = 'assets/300x150-green.png'; img.onload = async () => { await snapshot(); diff --git a/integration_tests/specs/css/css-flexbox/flex_shrink.ts b/integration_tests/specs/css/css-flexbox/flex_shrink.ts index fc4a6f8ba0..ab6370ae66 100644 --- a/integration_tests/specs/css/css-flexbox/flex_shrink.ts +++ b/integration_tests/specs/css/css-flexbox/flex_shrink.ts @@ -1060,5 +1060,35 @@ describe('flexbox flex-shrink', () => { await snapshot(); }); + it('should work with flex item containing only text not overflow flex container', async () => { + const div = createElement('div',{ + style: { + display: 'flex', + alignContent: 'flex-start', + alignItems: 'flex-start', + width: '300px' + } + }, [ + createElement('div', { + style: { + width: '100px', + height: '100px', + background: 'red', + flexShrink: 0 + } + }), + createElement('div', { + style: { + height: '100px', + background: 'green', + } + }, [ + createText('Flex item should not overflow container.') + ]), + ]); + document.body.appendChild(div); + await snapshot(); + }); + }); diff --git a/integration_tests/specs/css/css-flexbox/flexbox_flex-0.ts b/integration_tests/specs/css/css-flexbox/flexbox_flex-0.ts index dc28b49b91..4e3c7ee257 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox_flex-0.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox_flex-0.ts @@ -2399,7 +2399,10 @@ describe('flexbox_flex-0', () => { await snapshot(); }); - it('1-auto-shrink', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('1-auto-shrink', async () => { let div; div = createElement( 'div', @@ -3635,7 +3638,9 @@ describe('flexbox_flex-0', () => { await snapshot(); }); - it('N-auto-shrink', async () => { + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('N-auto-shrink', async () => { let div; div = createElement( 'div', @@ -3869,7 +3874,10 @@ describe('flexbox_flex-0', () => { await snapshot(); }); - it('auto', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('auto', async () => { let div; let flex; div = createElement( diff --git a/integration_tests/specs/css/css-flexbox/flexbox_flex-1.ts b/integration_tests/specs/css/css-flexbox/flexbox_flex-1.ts index d3644222a0..c2b32bc1a8 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox_flex-1.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox_flex-1.ts @@ -2251,7 +2251,10 @@ describe('flexbox_flex-1', () => { await snapshot(); }); - it('1-auto-shrink', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('1-auto-shrink', async () => { let div; div = createElement( 'div', @@ -3492,7 +3495,10 @@ describe('flexbox_flex-1', () => { await snapshot(); }); - it('N-auto-shrink', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('N-auto-shrink', async () => { let div; div = createElement( 'div', diff --git a/integration_tests/specs/css/css-flexbox/flexbox_flex-n.ts b/integration_tests/specs/css/css-flexbox/flexbox_flex-n.ts index df01d43e31..e0756f3259 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox_flex-n.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox_flex-n.ts @@ -2251,7 +2251,10 @@ describe('flexbox_flex-N', () => { await snapshot(); }); - it('1-auto-shrink', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('1-auto-shrink', async () => { let div; div = createElement( 'div', @@ -3492,7 +3495,10 @@ describe('flexbox_flex-N', () => { await snapshot(); }); - it('N-auto-shrink', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('N-auto-shrink', async () => { let div; div = createElement( 'div', diff --git a/integration_tests/specs/css/css-flexbox/flexbox_flex.ts b/integration_tests/specs/css/css-flexbox/flexbox_flex.ts index 7881aab14f..a3bb8663c0 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox_flex.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox_flex.ts @@ -239,7 +239,10 @@ describe('flexbox_flex', () => { await snapshot(); }); - it('initial', async () => { + + // @TODO: Impl setting longest words width as the minimum size of text. + // https://github.com/openkraken/kraken/issues/401 + xit('initial', async () => { let div; let flex; div = createElement( diff --git a/integration_tests/specs/css/css-flexbox/position-absolute.ts b/integration_tests/specs/css/css-flexbox/position-absolute.ts index 226be3da32..294ead281a 100644 --- a/integration_tests/specs/css/css-flexbox/position-absolute.ts +++ b/integration_tests/specs/css/css-flexbox/position-absolute.ts @@ -191,4 +191,65 @@ describe('flexbox-position-absolute', () => { await snapshot(); }); + + it('should works with image of position absolute and self no height', async () => { + const div = createElement('div', { + style: { + display: 'flex', + backgroundColor: 'yellow', + position: 'relative', + overflow: 'hidden', + } + }, [ + createElement('img', { + src: 'assets/100x100-green.png', + style: { + position: 'absolute', + height: '200px' + } + }), + createElement('div', { + style: { + display: 'flex', + width: '50px', + padding: '20px 0', + position: 'relative' + } + }) + ]); + document.body.appendChild(div); + + await snapshot(0.1); + }); + + it('should works with child of position absolute and self no height', async () => { + const div = createElement('div', { + style: { + display: 'flex', + alignItems: 'center', + backgroundColor: 'yellow', + position: 'relative', + overflow: 'hidden', + } + }, [ + createElement('div', { + style: { + position: 'absolute', + width: '200px', + height: '200px', + backgroundColor: 'green' + } + }), + createElement('div', { + style: { + width: '200px', + padding: '150px 0', + position: 'relative' + } + }) + ]); + document.body.appendChild(div); + + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-position/absolute.ts b/integration_tests/specs/css/css-position/absolute.ts index 99faa802ba..b0e238a452 100644 --- a/integration_tests/specs/css/css-position/absolute.ts +++ b/integration_tests/specs/css/css-position/absolute.ts @@ -726,6 +726,59 @@ describe('Position absolute', () => { await snapshot(); }); + it('should work with percentage offset of containing block which is not parent', async () => { + let div1 = createElement( + 'div', + { + style: { + position: 'absolute', + top: '50%', + left: '50%', + width: '100px', + height: '100px', + backgroundColor: 'green' + }, + }); + + BODY.appendChild(div1); + await snapshot(); + }); + + it('should work with percentage offset of containing block which is not parent in scroll container', async () => { + let div = createElement('div', { + style: { + width: '200px', + height: '200px', + borderTop: '40px solid black', + backgroundColor: 'yellow', + position: 'relative', + overflow: 'scroll' + } + }, [ + createElement('div', { + style: { + height: '200px', + } + }, [ + createElement( + 'div', + { + style: { + position: 'absolute', + top: '30%', + width: '100px', + height: '100px', + backgroundColor: 'green' + }, + }) + ]) + ]); + + BODY.appendChild(div); + + await snapshot(); + }); + it('should work with percentage after element is attached', async (done) => { let div2; let div; @@ -855,4 +908,103 @@ describe('Position absolute', () => { await snapshot(); }); + + it('child of positioned element with no width stretch to parent in flow layout', async () => { + var div = createElement('div', { + style: { + width: '200px', + height: '200px', + position: 'relative', + } + }, [ + createElement('div', { + style: { + backgroundColor: '#999', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + } + }, [ + createElement('div', { + style: { + backgroundColor: 'green', + height: '100px', + } + }) + ]) + ]); + + append(BODY, div); + + await snapshot(); + }); + + it('child of positioned element with no width stretch to parent in flex layout', async () => { + var div = createElement('div', { + style: { + width: '200px', + height: '200px', + position: 'relative', + } + }, [ + createElement('div', { + style: { + backgroundColor: '#999', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + display: 'flex', + flexDirection: 'column', + } + }, [ + createElement('div', { + style: { + backgroundColor: 'green', + height: '100px', + } + }) + ]) + ]); + + append(BODY, div); + + await snapshot(); + }); + + it('child of positioned element with no height stretch to parent in flex layout', async () => { + var div = createElement('div', { + style: { + width: '200px', + height: '200px', + position: 'relative', + } + }, [ + createElement('div', { + style: { + backgroundColor: '#999', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + display: 'flex', + } + }, [ + createElement('div', { + style: { + backgroundColor: 'green', + width: '100px', + } + }) + ]) + ]); + + append(BODY, div); + + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-position/fixed.ts b/integration_tests/specs/css/css-position/fixed.ts index 4448c75e46..a059bd20af 100644 --- a/integration_tests/specs/css/css-position/fixed.ts +++ b/integration_tests/specs/css/css-position/fixed.ts @@ -287,4 +287,90 @@ describe('Position fixed', () => { cont.style.position = 'static'; await snapshot(); }); + + it('should work with parent zIndex of parent fixed element larger than zIndex of child fixed element', async () => { + let div; + div = createElement( + 'div', + { + style: { + position: 'fixed', + width: '200px', + height: '200px', + background: 'yellow', + zIndex: 1000, + }, + }, + [ + createElement('div', { + style: { + position: 'fixed', + width: '100px', + height: '100px', + 'background-color': 'green', + zIndex: 100, + }, + }), + ] + ); + + document.body.appendChild(div); + + await snapshot(); + }); + + it('should work with parent zIndex of parent fixed element larger than zIndex of child fixed element in nested container', async () => { + let div; + div = createElement('div', { + style: { + } + }, [ + createElement( + 'div', + { + style: { + position: 'fixed', + width: '200px', + height: '200px', + background: 'yellow', + zIndex: 1000, + }, + }, + [ + createElement('div', { + style: { + position: 'fixed', + width: '100px', + height: '100px', + display: 'flex', + 'background-color': 'green', + zIndex: 100, + }, + }), + ] + ) + ]); + + document.body.appendChild(div); + + await snapshot(); + }); + + it('should work with percentage offset', async () => { + let div1 = createElement( + 'div', + { + style: { + position: 'fixed', + top: '50%', + left: '50%', + width: '100px', + height: '100px', + backgroundColor: 'green' + }, + }); + + BODY.appendChild(div1); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-position/nonstatic-to-static.ts b/integration_tests/specs/css/css-position/nonstatic-to-static.ts index 038b779808..8805820d22 100644 --- a/integration_tests/specs/css/css-position/nonstatic-to-static.ts +++ b/integration_tests/specs/css/css-position/nonstatic-to-static.ts @@ -66,4 +66,57 @@ describe('Position non-static', () => { await snapshot(); }); + + it('children should reposition when parent position changed from relative to static', async (done) => { + let div; + let item1; + let item2; + div = createElement( + 'div', + { + style: { + position: 'relative', + width: '200px', + height: '100px', + display: 'flex', + flexDirection: 'row', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + position: 'relative', + margin: '30px', + width: '100px', + height: '50px', + backgroundColor: 'yellow', + } + }, [ + createElement('div', { + style: {} + }, [ + (item2 = createElement('div', { + style: { + position: 'absolute', + top: 0, + left: 0, + width: '30px', + height: '30px', + backgroundColor: 'red' + } + })), + ]) + ])), + ] + ); + + BODY.appendChild(div); + + requestAnimationFrame(async () => { + item1.style.position = 'static'; + await snapshot(); + done(); + }); + }); }); diff --git a/integration_tests/specs/css/css-position/static-to-nostatic.ts b/integration_tests/specs/css/css-position/static-to-nostatic.ts index b0156fbdd9..f255f5f7bd 100644 --- a/integration_tests/specs/css/css-position/static-to-nostatic.ts +++ b/integration_tests/specs/css/css-position/static-to-nostatic.ts @@ -59,4 +59,56 @@ describe('Position static', () => { await snapshot(); }); + + it('children should reposition when parent position changed from static to relative', async (done) => { + let div; + let item1; + let item2; + div = createElement( + 'div', + { + style: { + position: 'relative', + width: '200px', + height: '100px', + display: 'flex', + flexDirection: 'row', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + margin: '30px', + width: '100px', + height: '50px', + backgroundColor: 'yellow', + } + }, [ + createElement('div', { + style: {} + }, [ + (item2 = createElement('div', { + style: { + position: 'absolute', + top: 0, + left: 0, + width: '30px', + height: '30px', + backgroundColor: 'red' + } + })), + ]) + ])), + ] + ); + + BODY.appendChild(div); + + requestAnimationFrame(async () => { + item1.style.position = 'relative'; + await snapshot(); + done(); + }); + }); }); diff --git a/integration_tests/specs/css/css-transforms/translateX.ts b/integration_tests/specs/css/css-transforms/translateX.ts index ee0bc9720b..220e5bf5ba 100644 --- a/integration_tests/specs/css/css-transforms/translateX.ts +++ b/integration_tests/specs/css/css-transforms/translateX.ts @@ -67,4 +67,24 @@ describe('Transform translateX', () => { BODY.appendChild(div); await snapshot(); }); + + it('should work with percentage of positioned element', async () => { + let div1 = createElement( + 'div', + { + style: { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + color: 'rgb(255, 255, 255)', + padding: '8px 16px', + position: 'absolute', + textAlign: 'center', + transform: 'translateX(50%)' + }, + }, [ + createText('foo bar') + ]); + + BODY.appendChild(div1); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-transforms/translateY.ts b/integration_tests/specs/css/css-transforms/translateY.ts index 4d596092ec..f60c04b2ef 100644 --- a/integration_tests/specs/css/css-transforms/translateY.ts +++ b/integration_tests/specs/css/css-transforms/translateY.ts @@ -67,4 +67,24 @@ describe('Transform translate3d', () => { BODY.appendChild(div); await snapshot(); }); + + it('should work with percentage of positioned element', async () => { + let div1 = createElement( + 'div', + { + style: { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + color: 'rgb(255, 255, 255)', + padding: '8px 16px', + position: 'absolute', + textAlign: 'center', + transform: 'translateY(50%)' + }, + }, [ + createText('foo bar') + ]); + + BODY.appendChild(div1); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-values/px.ts b/integration_tests/specs/css/css-values/px.ts index 89ea66911f..d184a400ed 100644 --- a/integration_tests/specs/css/css-values/px.ts +++ b/integration_tests/specs/css/css-values/px.ts @@ -46,4 +46,26 @@ describe('px', () => { }, 600); }); }); + + it('negative css length value should not work', async () => { + const container = createElement('div', { + style: { + background: 'yellow', + width: '-100px', + height: '-100px', + minWidth: '-100px', + maxWidth: '-200px', + minHeight: '-100px', + maxHeight: '-200px', + padding: '-50px', + border: '-10px solid green', + } + }, [ + createText('foo') + ]); + + document.body.appendChild(container); + + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-values/rem.ts b/integration_tests/specs/css/css-values/rem.ts index 83bd6ec913..449dda3f6b 100644 --- a/integration_tests/specs/css/css-values/rem.ts +++ b/integration_tests/specs/css/css-values/rem.ts @@ -47,6 +47,8 @@ describe("rem", () => { }); it("should works with style other than font-size of html", async () => { + document.documentElement.style.fontSize = '2rem'; + let div; let div2; let div3; diff --git a/integration_tests/specs/css/css-variables/css-variables.ts b/integration_tests/specs/css/css-variables/css-variables.ts new file mode 100644 index 0000000000..0121972b6d --- /dev/null +++ b/integration_tests/specs/css/css-variables/css-variables.ts @@ -0,0 +1,189 @@ +describe('CSS Variables', () => { + // https://github.com/web-platform-tests/wpt/blob/master/css/css-variables/css-variable-change-style-001.html + it('change-style-001', async () => { + + document.body.appendChild(createStyle(` + .outer { + --x: red; + --y: green; + --z: 28px; + } + `)); + document.head.appendChild(createStyle(` + .inner { + font-size: var(--z); + } + `)); + + document.body.appendChild( +
+
+
FontSize should be 28px.
+
+
+ ); + + await snapshot(); + }); + + it('change-style-002', async () => { + document.head.appendChild(createStyle(` + .inner { + + --x: red; + --y: green; + --z: 28px; + font-size: var(--z); + } + `)); + + document.body.appendChild( +
+
+
FontSize should be 28px.
+
+
+ ); + + await snapshot(); + }); + + + it('variable resolve color', async () => { + document.head.appendChild(createStyle(` + .inner { + --x: red; + --y: green; + --z: 28px; + background-color: var(--x); + } + `)); + + document.body.appendChild( +
+
+
Background should be red.
+
+
+ ); + + await snapshot(); + }); + + it('nested variables', async () => { + document.head.appendChild(createStyle(` + .inner { + color: var(--x); + } + .outer { + --y: red; + --x: var(--y); + } + `)); + + document.body.appendChild( +
+
+
Color should be red.
+
+
+ ); + + await snapshot(); + }); + + describe('Shorthand CSS properties', () => { + it('background', async () => { + document.head.appendChild(createStyle(` + .inner { + --x: red; + --y: green; + --z: 28px; + background: var(--y); + } + `)); + + document.body.appendChild( +
+
+
Background should be green.
+
+
+ ); + + await snapshot(); + }); + + it('margin', async () => { + document.head.appendChild(createStyle(` + .inner { + --x: red; + --y: green; + --z: 28px; + margin: var(--z); + background: red; + } + `)); + + document.body.appendChild( +
+
+
Background should be red with 28px margin.
+
+
+ ); + + await snapshot(); + }); + + it('padding', async () => { + document.head.appendChild(createStyle(` + .inner { + --x: red; + --y: green; + --z: 28px; + padding: var(--z); + background: red; + } + `)); + + document.body.appendChild( +
+
+
Background should be red with 28px padding.
+
+
+ ); + + await snapshot(); + }); + + it('border', async () => { + document.head.appendChild(createStyle(` + .inner { + --x: 4px; + --y: solid; + --z: green; + border: var(--x) var(--y) var(--z); + background: red; + } + `)); + + document.body.appendChild( +
+
+
Background should be red with 4px green solid border.
+
+
+ ); + + await snapshot(); + }); + }); + + function createStyle(text) { + const style = document.createElement('style'); + style.appendChild(document.createTextNode(text)); + return style; + } +}); diff --git a/integration_tests/specs/dom/elements/br.ts b/integration_tests/specs/dom/elements/br.ts index 5d05a0299f..fa36452fc2 100644 --- a/integration_tests/specs/dom/elements/br.ts +++ b/integration_tests/specs/dom/elements/br.ts @@ -4,4 +4,108 @@ describe('br-element', () => { document.body.appendChild(p); await snapshot(); }); + + it('should work with one BR element follows a display block element', async () => { + const div = createElement('div',{ + style: { + fontSize: '24px' + } + }, [ + createElement('div', { + style: {} + }, [ + createText('Hello'), + ]), + createElement('br', { + style: {} + }), + createText('world'), + ]); + document.body.appendChild(div); + await snapshot(); + }); + + it('should work with one BR element follows a text node in flow layout', async () => { + const div = createElement('div',{ + style: { + fontSize: '24px' + } + }, [ + createText('Hello'), + createElement('br', { + style: {} + }), + createText('world'), + ]); + document.body.appendChild(div); + await snapshot(); + }); + + it('should work with one BR element in flex layout', async () => { + const div = createElement('div',{ + style: { + fontSize: '24px', + display: 'flex', + flexDirection: 'column' + } + }, [ + createElement('span', { + style: {} + }, [ + createText('Hello'), + ]), + createElement('br', { + style: {} + }), + createText('world'), + ]); + document.body.appendChild(div); + await snapshot(); + }); + + it('should work with multiple BR elements follows a text node', async () => { + const div = createElement('div',{ + style: { + fontSize: '24px' + } + }, [ + createText('Hello'), + createElement('br', { + style: {} + }), + createElement('br', { + style: {} + }), + createElement('br', { + style: {} + }), + createElement('br', { + style: {} + }), + createText('world'), + ]); + document.body.appendChild(div); + await snapshot(); + }); + + it('should not work with styles on BR', async () => { + const div = createElement('div',{ + style: { + fontSize: '24px' + } + }, [ + createElement('br', { + style: { + width: '100px', + height: '100px', + margin: '100px', + backgroundColor: 'green' + } + }), + createText('Hello '), + createText('world'), + ]); + document.body.appendChild(div); + await snapshot(); + }); }); diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index e0144ef591..fa1de71b44 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -39,6 +39,165 @@ describe('custom widget element', () => { simulateClick(20, 20); }); + + it('text node should be child of flutter container', async () => { + const container = document.createElement('flutter-container'); + const text = document.createTextNode('text'); + document.body.appendChild(container); + container.appendChild(text); + await snapshot(); + }); + + it('element should be child of flutter container', async () => { + const container = document.createElement('flutter-container'); + const element = document.createElement('div'); + element.style.width = '30px'; + element.style.height = '30px'; + element.style.backgroundColor = 'red'; + container.appendChild(element); + document.body.appendChild(container); + await snapshot(); + }); + + it('flutter widget should be child of flutter container', async () => { + const container = document.createElement('flutter-container'); + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'text'); + container.appendChild(fluttetText); + document.body.appendChild(container); + + await snapshot(); + }); + + it('flutter widget and dom node should be child of flutter container', async () => { + const container = document.createElement('flutter-container'); + document.body.appendChild(container); + + const element = document.createElement('div'); + element.style.backgroundColor = 'red'; + element.appendChild(document.createTextNode('div element')); + container.appendChild(element); + + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'text'); + container.appendChild(fluttetText); + + const text = document.createTextNode('text'); + container.appendChild(text); + + await snapshot(); + }); + + it('flutter widget should be child of element', async () => { + const container = document.createElement('div'); + container.style.width = '100px'; + container.style.height = '100px'; + container.style.backgroundColor = 'red'; + const element = document.createElement('flutter-text'); + element.setAttribute('value', 'text'); + container.appendChild(element); + document.body.appendChild(container); + + await snapshot(); + }); + + it('flutter widget should be child of element and the element should be child of flutter widget', async () => { + const container = document.createElement('flutter-container'); + document.body.appendChild(container); + + const childContainer = document.createElement('div'); + container.appendChild(childContainer); + + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'text'); + childContainer.appendChild(fluttetText); + + await snapshot(); + }); + + it('should work with waterfall-flow', async () => { + const flutterContainer = document.createElement('waterfall-flow'); + flutterContainer.style.height = '100vh'; + flutterContainer.style.display = 'block'; + + document.body.appendChild(flutterContainer); + + const colors = ['red', 'yellow', 'black', 'blue', 'green']; + + for (let i = 0; i < 10; i++) { + const div = document.createElement('div'); + div.style.width = '100%'; + div.style.border = `1px solid ${colors[i % colors.length]}`; + div.appendChild(document.createTextNode(`${i}`)); + + const img = document.createElement('img'); + img.src = 'https://gw.alicdn.com/tfs/TB1CxCYq5_1gK0jSZFqXXcpaXXa-128-90.png'; + div.appendChild(img); + img.style.width = '100px'; + + flutterContainer.appendChild(div); + } + + await snapshot(); + }); + + it('flutter widget should spread out the parent node when parent node is line-block', async () => { + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'flutter text'); + fluttetText.style.display = 'inline-block'; + document.body.appendChild(fluttetText); + document.body.appendChild(document.createTextNode('dom text')); + + await snapshot(); + }); + + it('flutter widget should spread out the parent node when parent node is line', async () => { + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'flutter text'); + fluttetText.style.display = 'inline'; + document.body.appendChild(fluttetText); + document.body.appendChild(document.createTextNode('dom text')); + + await snapshot(); + }); + + it('flutter widget should spread out the parent node when parent node is block', async () => { + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'flutter text'); + fluttetText.style.display = 'block'; + document.body.appendChild(fluttetText); + document.body.appendChild(document.createTextNode('dom text')); + + await snapshot(); + }); + + it('flutter widget should spread out the parent node when parent node is flex', async () => { + const div = document.createElement('div'); + div.style.display = 'flex'; + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'flutter text'); + fluttetText.style.display = 'block'; + div.appendChild(fluttetText); + div.appendChild(document.createTextNode('111')); + document.body.appendChild(div); + + await snapshot(); + }); + + it('flutter widget should spread out the parent node when parent node is sliver', async () => { + const div = document.createElement('div'); + div.style.display = 'sliver'; + div.style.height = '500px'; + div.appendChild(document.createTextNode('111')); + const fluttetText = document.createElement('flutter-text'); + fluttetText.setAttribute('value', 'flutter text'); + fluttetText.style.display = 'block'; + div.appendChild(fluttetText); + div.appendChild(document.createTextNode('111')); + document.body.appendChild(div); + + await snapshot(); + }); }); describe('custom html element', () => { diff --git a/integration_tests/specs/dom/elements/img.ts b/integration_tests/specs/dom/elements/img.ts index 21f58f33d7..0b3fc32a3a 100644 --- a/integration_tests/specs/dom/elements/img.ts +++ b/integration_tests/specs/dom/elements/img.ts @@ -8,7 +8,7 @@ describe('Tags img', () => { img.style.width = '60px'; img.setAttribute( 'src', - '//gw.alicdn.com/tfs/TB1MRC_cvb2gK0jSZK9XXaEgFXa-1701-1535.png' + 'assets/100x100-green.png' ); document.body.appendChild(img); @@ -158,7 +158,7 @@ describe('Tags img', () => { var img = document.createElement('img'); img.onload = function() { expect(img.naturalWidth).toEqual(200); - expect(img.naturalWidth).toEqual(200); + expect(img.naturalHeight).toEqual(200); done(); }; img.src = imageURL; @@ -174,14 +174,14 @@ describe('Tags img', () => { expect(img.height).toEqual(20); // Image has not been loaded. expect(img.naturalWidth).toEqual(0); - expect(img.naturalWidth).toEqual(0); + expect(img.naturalHeight).toEqual(0); }); it('should work with loading=lazy', (done) => { const img = document.createElement('img'); // Make image loading=lazy. img.setAttribute('loading', 'lazy'); - img.src = '//gw.alicdn.com/tfs/TB1MRC_cvb2gK0jSZK9XXaEgFXa-1701-1535.png'; + img.src = 'assets/100x100-green.png'; img.style.width = '60px'; document.body.appendChild(img); @@ -303,7 +303,7 @@ describe('Tags img', () => { }, 100); }); - it('gif can replay', async (done) => { + it('gif can not replay by remove nodes', async (done) => { const imageURL = 'assets/sample-gif-40k.gif'; const img = document.createElement('img'); @@ -311,14 +311,11 @@ describe('Tags img', () => { await snapshot(img); document.body.removeChild(img); - setTimeout(() => { + setTimeout(async () => { + // When img re-append to document, to Gif image will continue to play. document.body.appendChild(img); - // After next frame that image has shown. - requestAnimationFrame(async () => { - // When replay, the image should be same as first frame. - await snapshot(img); - done(); - }); + await snapshot(img); + done(); // Delay 200ms to play gif. }, 200); }; @@ -326,4 +323,72 @@ describe('Tags img', () => { document.body.appendChild(img); img.src = imageURL; }); + + it('width property change should work when width of style is not set', async (done) => { + let img = createElement('img', { + src: 'assets/300x150-green.png', + width: 100, + height: 100, + }); + BODY.appendChild(img); + + requestAnimationFrame(async () => { + img.width = 200; + await snapshot(0.1); + done(); + }); + }); + + it('width property should not work when width of style is auto', async () => { + let img = createElement('img', { + src: 'assets/300x150-green.png', + width: 100, + height: 100, + style: { + width: 'auto' + } + }); + BODY.appendChild(img); + + await snapshot(0.1); + }); + + it('can get natualSize from repeat image url', async (done) => { + const flutterContainer = document.createElement('div'); + flutterContainer.style.height = '100vh'; + flutterContainer.style.display = 'block'; + document.body.appendChild(flutterContainer); + + const colors = ['red', 'yellow', 'black', 'blue', 'green']; + const images = [ + 'assets/100x100-green.png', + 'assets/200x200-green.png', + 'assets/60x60-gg-rr.png', + ]; + + let loadedCount = 0; + let imgCount = 10; + + for (let i = 0; i < imgCount; i++) { + const div = document.createElement('div'); + div.style.width = '100px'; + div.style.height = '100px'; + div.style.border = `3px solid ${colors[i % colors.length]}` + div.appendChild(document.createTextNode(i)); + + const img = document.createElement('img'); + img.src = images[i % images.length]; + div.appendChild(img); + img.style.width = '80px'; + img.onload = async () => { + loadedCount++; + if (loadedCount == imgCount) { + await snapshot(); + done(); + } + }; + + flutterContainer.appendChild(div); + } + }); }); diff --git a/integration_tests/specs/dom/elements/script.ts b/integration_tests/specs/dom/elements/script.ts index c65a8e34cf..4a3722b6a9 100644 --- a/integration_tests/specs/dom/elements/script.ts +++ b/integration_tests/specs/dom/elements/script.ts @@ -4,7 +4,7 @@ describe('script element', () => { const p =

Should see hello below:

; document.body.appendChild(p); var x = document.createElement('script'); - x.src = 'assets/hello.js'; + x.src = 'assets://assets/hello.js'; document.head.appendChild(x); x.onload = async () => { await snapshot(); diff --git a/integration_tests/specs/dom/elements/template.ts b/integration_tests/specs/dom/elements/template.ts index 2a892fb70c..e47403ecc0 100644 --- a/integration_tests/specs/dom/elements/template.ts +++ b/integration_tests/specs/dom/elements/template.ts @@ -12,7 +12,7 @@ describe('Tags template', () => { const t = document.createElement('template') t.innerHTML = '
template text
'; document.body.appendChild(t.content); - + expect(t.innerHTML).toBe(''); await snapshot(); }); }); diff --git a/integration_tests/specs/dom/events/touchEvent.ts b/integration_tests/specs/dom/events/touchEvent.ts index f27fd5e04f..00f7f6980a 100644 --- a/integration_tests/specs/dom/events/touchEvent.ts +++ b/integration_tests/specs/dom/events/touchEvent.ts @@ -77,7 +77,7 @@ describe('TouchEvent', () => { const func = async (e: TouchEvent) => { expect(e.touches.length).toBe(2); div.removeEventListener('touchend', func); - await simulatePoinrUp(20, 20); + await simulatePointUp(20, 20); done(); }; @@ -86,7 +86,7 @@ describe('TouchEvent', () => { document.body.appendChild(div); await simulatePointDown(20, 20); - + await simulateClick(10, 10, 1); }); @@ -107,7 +107,7 @@ describe('TouchEvent', () => { const func = async (e: TouchEvent) => { expect(e.targetTouches.length).toBe(1); - await simulatePoinrUp(20, 20); + await simulatePointUp(20, 20); div2.removeEventListener('touchend', func); done(); }; @@ -115,8 +115,8 @@ describe('TouchEvent', () => { div2.addEventListener('touchend', func); await simulatePointDown(20, 20); - - await simulateClick(5, 5 , 1); + + await simulateClick(5, 5, 1); }); it('touchend should work with changedTouches', async (done) => { @@ -134,7 +134,7 @@ describe('TouchEvent', () => { div.addEventListener('touchend', func) document.body.appendChild(div); - + await simulateClick(10, 10); }); @@ -153,7 +153,7 @@ describe('TouchEvent', () => { div.addEventListener('touchstart', func) document.body.appendChild(div); - + await simulateClick(10, 10); }); @@ -172,7 +172,20 @@ describe('TouchEvent', () => { div.addEventListener('touchmove', func) document.body.appendChild(div); - + + await simulateSwipe(0, 0, 0, 100, 0.5); + }); + + it('touchmove should work on body when element with position', async (done) => { + const div = document.createElement('div'); + div.style.width = '100px'; + div.style.height = '100px'; + div.style.backgroundColor = 'red'; + div.style.position = 'fixed'; + document.body.appendChild(div); + + document.body.addEventListener('touchmove', () => done()) + await simulateSwipe(0, 0, 0, 100, 0.5); }); }); diff --git a/integration_tests/specs/dom/nodes/insert-before.ts b/integration_tests/specs/dom/nodes/insert-before.ts index 2e0587eb2a..ba06cc5c2c 100644 --- a/integration_tests/specs/dom/nodes/insert-before.ts +++ b/integration_tests/specs/dom/nodes/insert-before.ts @@ -129,4 +129,87 @@ describe('Insert before', () => { await snapshot(); }); + it('insert before position fixed element', async () => { + let child1 = createElement('div', { + style: { + position: 'fixed', + top: '100px', + width: '100px', + height: '100px', + background: 'red' + } + }); + let child2; + const container = createElement('div', { + style: { + width: '200px', + height: '200px', + background: 'yellow' + } + }, [ + (child2 = createElement('div', { + style: { + position: 'fixed', + width: '100px', + height: '100px', + background: 'green' + } + })) + ]); + + document.body.appendChild(container); + + container.insertBefore(child1, child2); + + await snapshot(); + }); + + it('insert before position absolute element', async () => { + let child1 = createElement('div', { + style: { + width: '100px', + height: '100px', + background: 'red' + } + }); + let child2; + const container = createElement('div', { + style: { + width: '200px', + height: '200px', + background: 'yellow' + } + }, [ + (child2 = createElement('div', { + style: { + position: 'absolute', + width: '100px', + height: '100px', + background: 'green' + } + })) + ]); + + document.body.appendChild(container); + + container.insertBefore(child1, child2); + + await snapshot(); + }); + + it('insert before referenceNode is comment', async () => { + var container = document.createElement('div'); + document.body.appendChild(container); + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + + var ref; + container.appendChild(document.createTextNode('text1')); + container.appendChild(ref = document.createComment('comment1')); + container.appendChild(document.createTextNode('text2')); + + container.insertBefore(document.createTextNode('This line should between text1 and text2'), ref); + + await snapshot(); + }); }); diff --git a/integration_tests/specs/dom/nodes/offset.ts b/integration_tests/specs/dom/nodes/offset.ts index 0c6701dd77..6320efeac4 100644 --- a/integration_tests/specs/dom/nodes/offset.ts +++ b/integration_tests/specs/dom/nodes/offset.ts @@ -47,4 +47,93 @@ describe('Offset api', () => { await snapshot(); }); + + it('offsetTop and offsetLeft works when positioned parent found', async () => { + let item1; + let div1 = createElement( + 'div', + { + style: { + width: '100px', + height: '100px', + backgroundColor: 'coral', + }, + }); + let div2 = createElement( + 'div', + { + style: { + position: 'relative', + width: '100px', + height: '100px', + marginLeft: '100px', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + width: '50px', + height: '50px', + backgroundColor: 'yellow', + } + })), + ] + ); + + BODY.appendChild(div1); + BODY.appendChild(div2); + + expect(item1.offsetTop).toBe(0); + expect(item1.offsetLeft).toBe(0); + }); + + it('offsetTop and offsetLeft works when positioned parent not found', async () => { + let item1; + let div1 = createElement( + 'div', + { + style: { + width: '100px', + height: '100px', + backgroundColor: 'coral', + }, + }); + let div2 = createElement( + 'div', + { + style: { + width: '100px', + height: '100px', + marginLeft: '100px', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + width: '50px', + height: '50px', + backgroundColor: 'yellow', + } + })), + (createElement('div', { + style: { + width: '50px', + height: '1000px', + backgroundColor: 'red', + } + })), + ] + ); + + BODY.appendChild(div1); + BODY.appendChild(div2); + + document.documentElement.scrollTo(0, 80); + + expect(item1.offsetTop).toBe(100); + expect(item1.offsetLeft).toBe(100); + }); + }); diff --git a/integration_tests/specs/dom/nodes/textNode.ts b/integration_tests/specs/dom/nodes/textNode.ts index 0b6da3fee3..95e41691c0 100644 --- a/integration_tests/specs/dom/nodes/textNode.ts +++ b/integration_tests/specs/dom/nodes/textNode.ts @@ -55,6 +55,28 @@ describe('TextNode', () => { await snapshot(); }); + it('the previous sibling is block, the left space of this textnode is hidden', async () => { + const div = document.createElement('div'); + div.appendChild(document.createTextNode('text1')); + document.body.appendChild(div); + + const text = document.createTextNode(' text2'); + document.body.appendChild(text); + + await snapshot(); + }); + + it('the next sibling is block, the right space of this textnode is hidden', async () => { + const text = document.createTextNode('text1 '); + document.body.appendChild(text); + + const div = document.createElement('div'); + div.appendChild(document.createTextNode('text2')); + document.body.appendChild(div); + + await snapshot(); + }); + it('should work with set textContent', async () => { const div = document.createElement('div'); const text = document.createTextNode('before modified'); diff --git a/integration_tests/specs/window/global.ts b/integration_tests/specs/window/global.ts index 6329c8ab92..85fb17804b 100644 --- a/integration_tests/specs/window/global.ts +++ b/integration_tests/specs/window/global.ts @@ -1,4 +1,4 @@ -describe('windowisglobal', () => { +describe('window', () => { it('window equal to globalThis', () => { expect(window).toBe(globalThis as any); }); @@ -7,6 +7,7 @@ describe('windowisglobal', () => { // @ts-ignore expect(typeof window.kraken).toBe('object'); }); + it('equal to this', () => { function f() { // @ts-ignore diff --git a/integration_tests/webpack.config.js b/integration_tests/webpack.config.js index 002742f34e..61813e5dd8 100644 --- a/integration_tests/webpack.config.js +++ b/integration_tests/webpack.config.js @@ -1,7 +1,6 @@ const path = require('path'); const glob = require('glob'); const bableTransformSnapshotPlugin = require('./scripts/babel_transform_snapshot'); -const quickjsSyntaxFixLoader = require('./scripts/quickjs_syntax_fix_loader'); const context = path.join(__dirname); const runtimePath = path.join(context, 'runtime'); @@ -10,7 +9,7 @@ const resetRuntimePath = path.join(context, 'runtime/reset'); const buildPath = path.join(context, '.specs'); const testPath = path.join(context, 'specs'); const snapshotPath = path.join(context, 'snapshots'); -const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx}', { +const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx,html}', { cwd: context, ignore: ['node_modules/**'], }).map((file) => './' + file).filter(name => name.indexOf('plugins') < 0); @@ -50,6 +49,20 @@ module.exports = { test: /\.css$/i, use: require.resolve('stylesheet-loader'), }, + { + test: /\.(html?)$/i, + exclude: /node_modules/, + use: [ + { + loader: path.resolve('./scripts/html_loader'), + options: { + workspacePath: context, + testPath, + snapshotPath, + } + } + ] + }, { test: /\.(jsx?|tsx?)$/i, exclude: /node_modules/, diff --git a/kraken/.gitignore b/kraken/.gitignore index 035019a252..7055d72bb6 100644 --- a/kraken/.gitignore +++ b/kraken/.gitignore @@ -15,6 +15,10 @@ *.iws .idea/ +# Patch original files +*.orig +*.rej + # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. diff --git a/kraken/android/CMakeLists.txt b/kraken/android/CMakeLists.txt new file mode 100644 index 0000000000..bc6eae54fa --- /dev/null +++ b/kraken/android/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.6) + +project(app) diff --git a/kraken/android/build.gradle b/kraken/android/build.gradle index 38c83fb20a..6cd648cb3c 100644 --- a/kraken/android/build.gradle +++ b/kraken/android/build.gradle @@ -47,4 +47,15 @@ android { jniLibs.srcDirs = ['jniLibs'] } } + + // Encapsulates your external native build configurations. + externalNativeBuild { + + // Encapsulates your CMake build configurations. + cmake { + + // Provides a relative path to your CMake build script. + path "CMakeLists.txt" + } + } } diff --git a/kraken/android/src/main/java/com/openkraken/kraken/Kraken.java b/kraken/android/src/main/java/com/openkraken/kraken/Kraken.java index f1b1a43e6c..be90c77683 100644 --- a/kraken/android/src/main/java/com/openkraken/kraken/Kraken.java +++ b/kraken/android/src/main/java/com/openkraken/kraken/Kraken.java @@ -14,6 +14,7 @@ public class Kraken { private String url; + private String dynamicLibraryPath; private FlutterEngine flutterEngine; private MethodChannel.MethodCallHandler handler; @@ -24,7 +25,7 @@ public Kraken(FlutterEngine flutterEngine) { this.flutterEngine = flutterEngine; sdkMap.put(flutterEngine, this); } else { - throw new IllegalArgumentException("flutter engine must not be null!"); + throw new IllegalArgumentException("flutter engine must not be null."); } } @@ -36,17 +37,29 @@ public void registerMethodCallHandler(MethodChannel.MethodCallHandler handler) { this.handler = handler; } /** - * page load form + * Load url. * @param url */ public void loadUrl(String url) { this.url = url; } + /** + * Set the dynamic library path. + * @param value + */ + public void setDynamicLibraryPath(String value) { + this.dynamicLibraryPath = value; + } + public String getUrl() { return url; } + public String getDynamicLibraryPath() { + return dynamicLibraryPath != null ? dynamicLibraryPath : ""; + } + public void _handleMethodCall(MethodCall call, MethodChannel.Result result) { if (this.handler != null) { this.handler.onMethodCall(call, result); diff --git a/kraken/android/src/main/java/com/openkraken/kraken/KrakenPlugin.java b/kraken/android/src/main/java/com/openkraken/kraken/KrakenPlugin.java index 34371f40f0..70d8a5e33c 100644 --- a/kraken/android/src/main/java/com/openkraken/kraken/KrakenPlugin.java +++ b/kraken/android/src/main/java/com/openkraken/kraken/KrakenPlugin.java @@ -68,6 +68,12 @@ public void onMethodCall(MethodCall call, Result result) { break; } + case "getDynamicLibraryPath": { + Kraken kraken = getKraken(); + result.success(kraken == null ? "" : kraken.getDynamicLibraryPath()); + break; + } + case "invokeMethod": { Kraken kraken = getKraken(); if (kraken != null) { diff --git a/kraken/example/ios/Flutter/AppFrameworkInfo.plist b/kraken/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a7..f2872cf474 100644 --- a/kraken/example/ios/Flutter/AppFrameworkInfo.plist +++ b/kraken/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/kraken/example/ios/Runner.xcodeproj/project.pbxproj b/kraken/example/ios/Runner.xcodeproj/project.pbxproj index 066ff7c5cf..b9f75b08c9 100644 --- a/kraken/example/ios/Runner.xcodeproj/project.pbxproj +++ b/kraken/example/ios/Runner.xcodeproj/project.pbxproj @@ -355,7 +355,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ANRD47DNBX; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -492,7 +492,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ANRD47DNBX; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -523,7 +523,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ANRD47DNBX; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/kraken/example/lib/main.dart b/kraken/example/lib/main.dart index af85d22ca3..120cdc3a16 100644 --- a/kraken/example/lib/main.dart +++ b/kraken/example/lib/main.dart @@ -1,11 +1,12 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:kraken/kraken.dart'; -// import 'package:kraken_websocket/kraken_websocket.dart'; -// import 'package:kraken_devtools/kraken_devtools.dart'; -import 'dart:ui'; +import 'package:kraken/devtools.dart'; +import 'package:kraken_websocket/kraken_websocket.dart'; void main() { - // KrakenWebsocket.initialize(); + KrakenWebsocket.initialize(); runApp(MyApp()); } @@ -63,7 +64,7 @@ class _MyHomePageState extends State { controller: textEditingController, onSubmitted: (value) { textEditingController.text = value; - _kraken?.loadURL(value); + _kraken?.loadBundle(KrakenBundle.fromUrl(value)); }, decoration: InputDecoration( hintText: 'Enter a app url', @@ -89,10 +90,10 @@ class _MyHomePageState extends State { // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: _kraken = Kraken( - // devToolsService: ChromeDevToolsService(), + devToolsService: ChromeDevToolsService(), viewportWidth: viewportSize.width - queryData.padding.horizontal, viewportHeight: viewportSize.height - appBar.preferredSize.height - queryData.padding.vertical, - bundleURL: 'assets/bundle.js', + bundle: KrakenBundle.fromUrl('assets://assets/bundle.js'), ), )); } diff --git a/kraken/example/macos/Flutter/GeneratedPluginRegistrant.swift b/kraken/example/macos/Flutter/GeneratedPluginRegistrant.swift index e51fee510b..65844a0f73 100644 --- a/kraken/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/kraken/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,14 +7,12 @@ import Foundation import connectivity_macos import kraken -import kraken_devtools import kraken_websocket import shared_preferences_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) KrakenPlugin.register(with: registry.registrar(forPlugin: "KrakenPlugin")) - KrakenDevtoolsPlugin.register(with: registry.registrar(forPlugin: "KrakenDevtoolsPlugin")) KrakenWebsocketPlugin.register(with: registry.registrar(forPlugin: "KrakenWebsocketPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/kraken/example/macos/Runner.xcodeproj/project.pbxproj b/kraken/example/macos/Runner.xcodeproj/project.pbxproj index f07f94af20..a4aea51fd4 100644 --- a/kraken/example/macos/Runner.xcodeproj/project.pbxproj +++ b/kraken/example/macos/Runner.xcodeproj/project.pbxproj @@ -266,7 +266,6 @@ "${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/kraken/macos/libkraken.dylib", "${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/kraken/macos/libquickjs.dylib", "${BUILT_PRODUCTS_DIR}/kraken/kraken.framework", - "${BUILT_PRODUCTS_DIR}/kraken_devtools/kraken_devtools.framework", "${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/kraken_websocket/macos/libkraken_websocket.dylib", "${BUILT_PRODUCTS_DIR}/kraken_websocket/kraken_websocket.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework", @@ -278,7 +277,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libkraken.dylib", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libquickjs.dylib", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/kraken.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/kraken_devtools.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libkraken_websocket.dylib", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/kraken_websocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework", diff --git a/kraken/example/pubspec.yaml b/kraken/example/pubspec.yaml index afbf77c35c..d467155053 100644 --- a/kraken/example/pubspec.yaml +++ b/kraken/example/pubspec.yaml @@ -12,8 +12,7 @@ dependencies: flutter: sdk: flutter kraken: ^0.7.2 - # kraken_websocket: ^1.0.5 - # kraken_devtools: ^0.4.0-dev.0 + kraken_websocket: ^2.0.0-rc.1 # When depending on this package from a real application, # you should remove the following overrides. diff --git a/kraken/include/kraken_bridge_jsc.h b/kraken/include/kraken_bridge_jsc.h deleted file mode 120000 index 41024ae73b..0000000000 --- a/kraken/include/kraken_bridge_jsc.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/kraken_bridge_jsc.h \ No newline at end of file diff --git a/kraken/include/kraken_bridge_jsc_config.h b/kraken/include/kraken_bridge_jsc_config.h deleted file mode 120000 index 84a806fdda..0000000000 --- a/kraken/include/kraken_bridge_jsc_config.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/kraken_bridge_jsc_config.h \ No newline at end of file diff --git a/kraken/ios/kraken.podspec b/kraken/ios/kraken.podspec index 9dd25aa47c..0e1da7e9f5 100644 --- a/kraken/ios/kraken.podspec +++ b/kraken/ios/kraken.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'kraken' - s.version = '0.6.3' + s.version = '0.10.0' s.summary = 'A high-performance, web standards-compliant rendering engine.' s.description = <<-DESC A high-performance, web standards-compliant rendering engine. diff --git a/kraken/ios/prepare.sh b/kraken/ios/prepare.sh index 85f161545f..bed3c608e6 100644 --- a/kraken/ios/prepare.sh +++ b/kraken/ios/prepare.sh @@ -1,3 +1,5 @@ +ROOT=$(pwd) + if [ -L "kraken_bridge.xcframework" ]; then ROOT=$(pwd) rm kraken_bridge.xcframework diff --git a/kraken/lib/bridge.dart b/kraken/lib/bridge.dart index 139f83040d..f27cd65548 100644 --- a/kraken/lib/bridge.dart +++ b/kraken/lib/bridge.dart @@ -4,6 +4,7 @@ */ export 'src/bridge/bridge.dart'; +export 'src/bridge/dynamic_library.dart'; export 'src/bridge/to_native.dart'; export 'src/bridge/from_native.dart'; export 'src/bridge/native_types.dart'; diff --git a/kraken/lib/css.dart b/kraken/lib/css.dart index 0691dd583c..bdb0595e9e 100644 --- a/kraken/lib/css.dart +++ b/kraken/lib/css.dart @@ -42,6 +42,7 @@ export 'src/css/object_fit.dart'; export 'src/css/object_position.dart'; export 'src/css/sliver.dart'; export 'src/css/value.dart'; +export 'src/css/variable.dart'; export 'src/css/keywords.dart'; // CSS Values @@ -54,6 +55,6 @@ export 'src/css/values/number.dart'; export 'src/css/values/integer.dart'; export 'src/css/values/position.dart'; export 'src/css/values/time.dart'; -export 'src/css/values/url.dart'; export 'src/css/values/percentage.dart'; export 'src/css/values/textual.dart'; +export 'src/css/values/variable.dart'; diff --git a/kraken/lib/devtools.dart b/kraken/lib/devtools.dart new file mode 100644 index 0000000000..431deebefb --- /dev/null +++ b/kraken/lib/devtools.dart @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +export 'src/devtools/server.dart'; +export 'src/devtools/service.dart'; +export 'src/devtools/module.dart'; +export 'src/devtools/overlay.dart'; +export 'src/devtools/inspector.dart'; + +// DevTools Modules +export 'src/devtools/modules/css.dart'; +export 'src/devtools/modules/debugger.dart'; +export 'src/devtools/modules/dom.dart'; +export 'src/devtools/modules/log.dart'; +export 'src/devtools/modules/network.dart'; +export 'src/devtools/modules/overlay.dart'; +export 'src/devtools/modules/page.dart'; +export 'src/devtools/modules/profiler.dart'; +export 'src/devtools/modules/runtime.dart'; diff --git a/kraken/lib/dom.dart b/kraken/lib/dom.dart index aef5507442..48426d3d41 100644 --- a/kraken/lib/dom.dart +++ b/kraken/lib/dom.dart @@ -5,8 +5,6 @@ export 'src/dom/binding.dart'; export 'src/dom/element.dart'; -export 'src/dom/element_manager.dart'; -export 'src/dom/event_handler.dart'; export 'src/dom/event.dart'; export 'src/dom/event_target.dart'; export 'src/dom/object_element_client.dart'; @@ -15,8 +13,10 @@ export 'src/dom/node.dart'; export 'src/dom/text_node.dart'; export 'src/dom/window.dart'; export 'src/dom/document.dart'; +export 'src/dom/comment.dart'; export 'src/dom/document_fragment.dart'; export 'src/dom/sliver_manager.dart'; +export 'src/dom/element_registry.dart'; // Elements export 'src/dom/elements/semantics_text.dart'; diff --git a/kraken/lib/rendering.dart b/kraken/lib/rendering.dart index 3e19a7ca1a..469766da16 100644 --- a/kraken/lib/rendering.dart +++ b/kraken/lib/rendering.dart @@ -23,3 +23,5 @@ export 'src/rendering/opacity.dart'; export 'src/rendering/transform.dart'; export 'src/rendering/viewport.dart'; export 'src/rendering/paragraph.dart'; +export 'src/rendering/line_break.dart'; +export 'src/rendering/image.dart'; diff --git a/kraken/lib/src/bridge/bridge.dart b/kraken/lib/src/bridge/bridge.dart index e0bdf79ea7..b76c3cdaca 100644 --- a/kraken/lib/src/bridge/bridge.dart +++ b/kraken/lib/src/bridge/bridge.dart @@ -12,8 +12,9 @@ import 'package:kraken/module.dart'; import 'from_native.dart'; import 'to_native.dart'; -/// the Kraken JS Bridge Size -int kKrakenJSBridgePoolSize = 8; +/// The maximum kraken pages running in the same times. +/// Can be upgrade to larger amount if you have enough memory spaces. +int kKrakenJSPagePoolSize = 1024; bool _firstView = true; @@ -38,8 +39,6 @@ int initBridge() { Future.microtask(() { // Port flutter's frame callback into bridge. SchedulerBinding.instance!.addPersistentFrameCallback((_) { - - assert(contextId != -1); flushUICommand(); flushUICommandCallback(); }); @@ -47,11 +46,11 @@ int initBridge() { } if (_firstView) { - initJSContextPool(kKrakenJSBridgePoolSize); + initJSPagePool(kKrakenJSPagePoolSize); _firstView = false; contextId = 0; } else { - contextId = allocateNewContext(); + contextId = allocateNewPage(); if (contextId == -1) { throw Exception('Can\' allocate new kraken bridge: bridge count had reach the maximum size.'); } diff --git a/kraken/lib/src/bridge/dynamic_library.dart b/kraken/lib/src/bridge/dynamic_library.dart new file mode 100644 index 0000000000..9f35970cb4 --- /dev/null +++ b/kraken/lib/src/bridge/dynamic_library.dart @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:ffi'; +import 'dart:io' show Platform; + +import 'package:path/path.dart'; + +abstract class KrakenDynamicLibrary { + static final String _defaultLibraryPath = Platform.isLinux ? '\$ORIGIN' : ''; + + /// The search path that dynamic library be load, if null using default. + static String _dynamicLibraryPath = _defaultLibraryPath; + static String get dynamicLibraryPath => _dynamicLibraryPath; + static set dynamicLibraryPath(String value) { + _dynamicLibraryPath = value; + } + + // The kraken library name. + static String libName = 'libkraken'; + + static String get _nativeDynamicLibraryName { + if (Platform.isMacOS) { + return '$libName.dylib'; + } else if (Platform.isIOS) { + return 'kraken_bridge.framework/kraken_bridge'; + } else if (Platform.isWindows) { + return '$libName.dll'; + } else if (Platform.isAndroid || Platform.isLinux) { + return '$libName.so'; + } else { + throw UnimplementedError('Not supported platform.'); + } + } + + static DynamicLibrary? _ref; + static DynamicLibrary get ref { + DynamicLibrary? nativeDynamicLibrary = _ref; + _ref = nativeDynamicLibrary ??= DynamicLibrary.open(join(_dynamicLibraryPath, _nativeDynamicLibraryName)); + return nativeDynamicLibrary; + } +} diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index 1e350abb59..a901b9d524 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -13,19 +13,18 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/services.dart'; import 'package:kraken/bridge.dart'; -import 'package:kraken/dom.dart'; import 'package:kraken/launcher.dart'; import 'package:kraken/module.dart'; import 'package:kraken/src/module/performance_timing.dart'; import 'native_types.dart'; -import 'platform.dart'; +import 'dynamic_library.dart'; // An native struct can be directly convert to javaScript String without any conversion cost. class NativeString extends Struct { external Pointer string; - @Int32() + @Uint32() external int length; } @@ -158,8 +157,8 @@ typedef DartRAFAsyncCallback = void Function( typedef NativeRequestBatchUpdate = Void Function(Int32 contextId); void _requestBatchUpdate(int contextId) { - KrakenController controller = KrakenController.getControllerOfJSContextId(contextId)!; - return controller.module.requestBatchUpdate(); + KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + return controller?.module.requestBatchUpdate(); } final Pointer> _nativeRequestBatchUpdate = @@ -175,12 +174,22 @@ int _setTimeout(Pointer callbackContext, int contextId, return controller.module.setTimeout(timeout, () { DartAsyncCallback func = callback.asFunction(); - try { - func(callbackContext, contextId, nullptr); - } catch (e, stack) { - Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); - func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); + + void _runCallback() { + try { + func(callbackContext, contextId, nullptr); + } catch (e, stack) { + Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); + func(callbackContext, contextId, nativeErrorMessage); + malloc.free(nativeErrorMessage); + } + } + + // Pause if kraken page paused. + if (controller.paused) { + controller.pushPendingCallbacks(_runCallback); + } else { + _runCallback(); } }); } @@ -196,13 +205,22 @@ int _setInterval(Pointer callbackContext, int contextId, Pointer> callback, int timeout) { KrakenController controller = KrakenController.getControllerOfJSContextId(contextId)!; return controller.module.setInterval(timeout, () { - DartAsyncCallback func = callback.asFunction(); - try { - func(callbackContext, contextId, nullptr); - } catch (e, stack) { - Pointer nativeErrorMessage = ('Dart Error: $e\n$stack').toNativeUtf8(); - func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); + void _runCallbacks() { + DartAsyncCallback func = callback.asFunction(); + try { + func(callbackContext, contextId, nullptr); + } catch (e, stack) { + Pointer nativeErrorMessage = ('Dart Error: $e\n$stack').toNativeUtf8(); + func(callbackContext, contextId, nativeErrorMessage); + malloc.free(nativeErrorMessage); + } + } + + // Pause if kraken page paused. + if (controller.paused) { + controller.pushPendingCallbacks(_runCallbacks); + } else { + _runCallbacks(); } }); } @@ -229,14 +247,24 @@ int _requestAnimationFrame(Pointer callbackContext, int contextId, Pointer> callback) { KrakenController controller = KrakenController.getControllerOfJSContextId(contextId)!; return controller.module.requestAnimationFrame((double highResTimeStamp) { - DartRAFAsyncCallback func = callback.asFunction(); - try { - func(callbackContext, contextId, highResTimeStamp, nullptr); - } catch (e, stack) { - Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); - func(callbackContext, contextId, highResTimeStamp, nativeErrorMessage); - malloc.free(nativeErrorMessage); + void _runCallback() { + DartRAFAsyncCallback func = callback.asFunction(); + try { + func(callbackContext, contextId, highResTimeStamp, nullptr); + } catch (e, stack) { + Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); + func(callbackContext, contextId, highResTimeStamp, nativeErrorMessage); + malloc.free(nativeErrorMessage); + } + } + + // Pause if kraken page paused. + if (controller.paused) { + controller.pushPendingCallbacks(_runCallback); + } else { + _runCallback(); } + }); } @@ -327,18 +355,11 @@ void _flushUICommand() { final Pointer> _nativeFlushUICommand = Pointer.fromFunction(_flushUICommand); -// HTML Element is special element which created at initialize time, so we can't use UICommandQueue to init. -typedef NativeInitHTML = Void Function(Int32 contextId, Pointer nativePtr); -void _initHTML(int contextId, Pointer nativePtr) { - ElementManager.htmlNativePtrMap[contextId] = nativePtr; -} -final Pointer> _nativeInitHTML = Pointer.fromFunction(_initHTML); - typedef NativeInitWindow = Void Function(Int32 contextId, Pointer nativePtr); typedef DartInitWindow = void Function(int contextId, Pointer nativePtr); void _initWindow(int contextId, Pointer nativePtr) { - ElementManager.windowNativePtrMap[contextId] = nativePtr; + KrakenViewController.windowNativePtrMap[contextId] = nativePtr; } final Pointer> _nativeInitWindow = Pointer.fromFunction(_initWindow); @@ -347,7 +368,7 @@ typedef NativeInitDocument = Void Function(Int32 contextId, Pointer nativePtr); void _initDocument(int contextId, Pointer nativePtr) { - ElementManager.documentNativePtrMap[contextId] = nativePtr; + KrakenViewController.documentNativePtrMap[contextId] = nativePtr; } final Pointer> _nativeInitDocument = Pointer.fromFunction(_initDocument); @@ -391,7 +412,6 @@ final List _dartNativeMethods = [ _nativePlatformBrightness.address, _nativeToBlob.address, _nativeFlushUICommand.address, - _nativeInitHTML.address, _nativeInitWindow.address, _nativeInitDocument.address, _nativeGetEntries.address, @@ -401,8 +421,10 @@ final List _dartNativeMethods = [ typedef NativeRegisterDartMethods = Void Function(Pointer methodBytes, Int32 length); typedef DartRegisterDartMethods = void Function(Pointer methodBytes, int length); -final DartRegisterDartMethods _registerDartMethods = - nativeDynamicLibrary.lookup>('registerDartMethods').asFunction(); +final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary + .ref + .lookup>('registerDartMethods') + .asFunction(); void registerDartMethodsToCpp() { Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); diff --git a/kraken/lib/src/bridge/native_types.dart b/kraken/lib/src/bridge/native_types.dart index 12731bde5e..196935ffb8 100644 --- a/kraken/lib/src/bridge/native_types.dart +++ b/kraken/lib/src/bridge/native_types.dart @@ -273,6 +273,7 @@ class NativeBoundingClientRect extends Struct { typedef NativeDispatchEvent = Void Function( + Int32 contextId, Pointer nativeEventTarget, Pointer eventType, Pointer nativeEvent, diff --git a/kraken/lib/src/bridge/native_value.dart b/kraken/lib/src/bridge/native_value.dart index f585802531..2357b728f3 100644 --- a/kraken/lib/src/bridge/native_value.dart +++ b/kraken/lib/src/bridge/native_value.dart @@ -70,7 +70,10 @@ dynamic fromNativeValue(Pointer nativeValue) { JSValueType type = JSValueType.values[nativeValue.ref.tag]; switch(type) { case JSValueType.TAG_STRING: - return nativeStringToString(Pointer.fromAddress(nativeValue.ref.u)); + Pointer nativeString = Pointer.fromAddress(nativeValue.ref.u); + String result = nativeStringToString(nativeString); + freeNativeString(nativeString); + return result; case JSValueType.TAG_INT: return nativeValue.ref.u; case JSValueType.TAG_BOOL: @@ -95,7 +98,10 @@ dynamic fromNativeValue(Pointer nativeValue) { case JSValueType.TAG_ASYNC_FUNCTION: break; case JSValueType.TAG_JSON: - return jsonDecode(nativeStringToString(Pointer.fromAddress(nativeValue.ref.u))); + Pointer nativeString = Pointer.fromAddress(nativeValue.ref.u); + dynamic value = jsonDecode(nativeStringToString(nativeString)); + freeNativeString(nativeString); + return value; } } diff --git a/kraken/lib/src/bridge/platform.dart b/kraken/lib/src/bridge/platform.dart deleted file mode 100644 index cfe3a23364..0000000000 --- a/kraken/lib/src/bridge/platform.dart +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2019-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ -// ignore_for_file: unused_import, undefined_function - -import 'dart:ffi'; -import 'dart:io' show Platform; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; -import 'package:path/path.dart'; - -/// Search dynamic lib from env.KRAKEN_LIBRARY_PATH or /usr/lib -const String KRAKEN_LIBRARY_PATH = 'KRAKEN_LIBRARY_PATH'; -const String KRAKEN_JS_ENGINE = 'KRAKEN_JS_ENGINE'; -const String KRAKEN_ENABLE_TEST = 'KRAKEN_ENABLE_TEST'; -final String kkLibraryPath = Platform.environment[KRAKEN_LIBRARY_PATH] ?? (Platform.isLinux ? '\$ORIGIN' : ''); -final String libName = Platform.environment[KRAKEN_ENABLE_TEST] == 'true' ? 'libkraken_test' : 'libkraken'; -final String nativeDynamicLibraryName = Platform.isMacOS - ? '$libName.dylib' - : Platform.isIOS ? 'kraken_bridge.framework/kraken_bridge' : Platform.isWindows ? '$libName.dll' : '$libName.so'; -DynamicLibrary nativeDynamicLibrary = - DynamicLibrary.open(join(kkLibraryPath, nativeDynamicLibraryName)); diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index 540f2388b6..30df7d09da 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -17,7 +17,7 @@ import 'package:kraken/module.dart'; import 'from_native.dart'; import 'native_types.dart'; -import 'platform.dart'; +import 'dynamic_library.dart'; // Steps for using dart:ffi to call a C function from Dart: // 1. Import dart:ffi. @@ -34,7 +34,6 @@ void setKrakenUserAgent(String userAgent) { } class KrakenInfo { - final Pointer _nativeKrakenInfo; KrakenInfo(Pointer info) : _nativeKrakenInfo = info; @@ -60,15 +59,17 @@ class KrakenInfo { } String get userAgent { - return _krakenUserAgent ?? '$appName/$appVersion ($systemName; $appName/$appRevision)'; + return _krakenUserAgent ?? + '$appName/$appVersion ($systemName; $appName/$appRevision)'; } } typedef NativeGetKrakenInfo = Pointer Function(); typedef DartGetKrakenInfo = Pointer Function(); -final DartGetKrakenInfo _getKrakenInfo = - nativeDynamicLibrary.lookup>('getKrakenInfo').asFunction(); +final DartGetKrakenInfo _getKrakenInfo = KrakenDynamicLibrary.ref + .lookup>('getKrakenInfo') + .asFunction(); final KrakenInfo _cachedInfo = KrakenInfo(_getKrakenInfo()); @@ -77,38 +78,64 @@ KrakenInfo getKrakenInfo() { } // Register invokeEventListener -typedef NativeInvokeEventListener = Void Function(Int32 contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); -typedef DartInvokeEventListener = void Function(int contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); - -final DartInvokeEventListener _invokeModuleEvent = - nativeDynamicLibrary.lookup>('invokeModuleEvent').asFunction(); - -void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { +typedef NativeInvokeEventListener = Void Function( + Int32 contextId, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer); +typedef DartInvokeEventListener = void Function( + int contextId, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer); + +final DartInvokeEventListener _invokeModuleEvent = KrakenDynamicLibrary + .ref + .lookup>('invokeModuleEvent') + .asFunction(); + +void invokeModuleEvent( + int contextId, String moduleName, Event? event, String extra) { if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } Pointer nativeModuleName = stringToNativeString(moduleName); Pointer rawEvent = event == null ? nullptr : event.toRaw().cast(); - _invokeModuleEvent(contextId, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, stringToNativeString(extra)); + _invokeModuleEvent( + contextId, + nativeModuleName, + event == null ? nullptr : event.type.toNativeUtf8(), + rawEvent, + stringToNativeString(extra)); freeNativeString(nativeModuleName); } typedef DartDispatchEvent = void Function( - Pointer nativeEventTarget, Pointer eventType, Pointer nativeEvent, int isCustomEvent); - -void emitUIEvent(int contextId, Pointer nativeEventTarget, Event event) { - if(KrakenController.getControllerOfJSContextId(contextId) == null) { + int contextId, + Pointer nativeEventTarget, + Pointer eventType, + Pointer nativeEvent, + int isCustomEvent +); + +void emitUIEvent( + int contextId, Pointer nativeEventTarget, Event event) { + if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } - DartDispatchEvent dispatchEvent = nativeEventTarget.ref.dispatchEvent.asFunction(); + DartDispatchEvent dispatchEvent = + nativeEventTarget.ref.dispatchEvent.asFunction(); Pointer rawEvent = event.toRaw().cast(); bool isCustomEvent = event is CustomEvent; Pointer eventTypeString = stringToNativeString(event.type); - dispatchEvent(nativeEventTarget, eventTypeString, rawEvent, isCustomEvent ? 1 : 0); + dispatchEvent(contextId, nativeEventTarget, eventTypeString, rawEvent, isCustomEvent ? 1 : 0); freeNativeString(eventTypeString); } -void emitModuleEvent(int contextId, String moduleName, Event? event, String extra) { +void emitModuleEvent( + int contextId, String moduleName, Event? event, String extra) { invokeModuleEvent(contextId, moduleName, event, extra); } @@ -116,18 +143,19 @@ void emitModuleEvent(int contextId, String moduleName, Event? event, String extr typedef NativeCreateScreen = Pointer Function(Double, Double); typedef DartCreateScreen = Pointer Function(double, double); -final DartCreateScreen _createScreen = - nativeDynamicLibrary.lookup>('createScreen').asFunction(); +final DartCreateScreen _createScreen = KrakenDynamicLibrary.ref + .lookup>('createScreen') + .asFunction(); Pointer createScreen(double width, double height) { return _createScreen(width, height); } // Register evaluateScripts -typedef NativeEvaluateScripts = Void Function( - Int32 contextId, Pointer code, Pointer url, Int32 startLine); -typedef DartEvaluateScripts = void Function( - int contextId, Pointer code, Pointer url, int startLine); +typedef NativeEvaluateScripts = Void Function(Int32 contextId, + Pointer code, Pointer url, Int32 startLine); +typedef DartEvaluateScripts = void Function(int contextId, + Pointer code, Pointer url, int startLine); // Register parseHTML typedef NativeParseHTML = Void Function( @@ -135,14 +163,16 @@ typedef NativeParseHTML = Void Function( typedef DartParseHTML = void Function( int contextId, Pointer code, int length); -final DartEvaluateScripts _evaluateScripts = -nativeDynamicLibrary.lookup>('evaluateScripts').asFunction(); +final DartEvaluateScripts _evaluateScripts = KrakenDynamicLibrary.ref + .lookup>('evaluateScripts') + .asFunction(); -final DartParseHTML _parseHTML = -nativeDynamicLibrary.lookup>('parseHTML').asFunction(); +final DartParseHTML _parseHTML = KrakenDynamicLibrary.ref + .lookup>('parseHTML') + .asFunction(); void evaluateScripts(int contextId, String code, String url, [int line = 0]) { - if(KrakenController.getControllerOfJSContextId(contextId) == null) { + if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } Pointer nativeString = stringToNativeString(code); @@ -155,13 +185,18 @@ void evaluateScripts(int contextId, String code, String url, [int line = 0]) { freeNativeString(nativeString); } -typedef NativeEvaluateQuickjsByteCode = Void Function(Int32 contextId, Pointer bytes, Int32 byteLen); -typedef DartEvaluateQuickjsByteCode = void Function(int contextId, Pointer bytes, int byteLen); +typedef NativeEvaluateQuickjsByteCode = Void Function( + Int32 contextId, Pointer bytes, Int32 byteLen); +typedef DartEvaluateQuickjsByteCode = void Function( + int contextId, Pointer bytes, int byteLen); -final DartEvaluateQuickjsByteCode _evaluateQuickjsByteCode = nativeDynamicLibrary.lookup>('evaluateQuickjsByteCode').asFunction(); +final DartEvaluateQuickjsByteCode _evaluateQuickjsByteCode = KrakenDynamicLibrary + .ref + .lookup>('evaluateQuickjsByteCode') + .asFunction(); void evaluateQuickjsByteCode(int contextId, Uint8List bytes) { - if(KrakenController.getControllerOfJSContextId(contextId) == null) { + if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } Pointer byteData = malloc.allocate(sizeOf() * bytes.length); @@ -171,7 +206,7 @@ void evaluateQuickjsByteCode(int contextId, Uint8List bytes) { } void parseHTML(int contextId, String code) { - if(KrakenController.getControllerOfJSContextId(contextId) == null) { + if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } Pointer nativeCode = code.toNativeUtf8(); @@ -184,63 +219,77 @@ void parseHTML(int contextId, String code) { } // Register initJsEngine -typedef NativeInitJSContextPool = Void Function(Int32 poolSize); -typedef DartInitJSContextPool = void Function(int poolSize); +typedef NativeInitJSPagePool = Void Function(Int32 poolSize); +typedef DartInitJSPagePool = void Function(int poolSize); -final DartInitJSContextPool _initJSContextPool = - nativeDynamicLibrary.lookup>('initJSContextPool').asFunction(); +final DartInitJSPagePool _initJSPagePool = KrakenDynamicLibrary.ref + .lookup>('initJSPagePool') + .asFunction(); -void initJSContextPool(int poolSize) { - _initJSContextPool(poolSize); +void initJSPagePool(int poolSize) { + _initJSPagePool(poolSize); } -typedef NativeDisposeContext = Void Function(Int32 contextId); -typedef DartDisposeContext = void Function(int contextId); +typedef NativeDisposePage = Void Function(Int32 contextId); +typedef DartDisposePage = void Function(int contextId); -final DartDisposeContext _disposeContext = - nativeDynamicLibrary.lookup>('disposeContext').asFunction(); +final DartDisposePage _disposePage = KrakenDynamicLibrary.ref + .lookup>('disposePage') + .asFunction(); -void disposeContext(int contextId) { - _disposeContext(contextId); +void disposePage(int contextId) { + _disposePage(contextId); } -typedef NativeAllocateNewContext = Int32 Function(Int32); -typedef DartAllocateNewContext = int Function(int); +typedef NativeAllocateNewPage = Int32 Function(Int32); +typedef DartAllocateNewPage = int Function(int); -final DartAllocateNewContext _allocateNewContext = - nativeDynamicLibrary.lookup>('allocateNewContext').asFunction(); +final DartAllocateNewPage _allocateNewPage = KrakenDynamicLibrary.ref + .lookup>('allocateNewPage') + .asFunction(); -int allocateNewContext([int targetContextId = -1]) { - return _allocateNewContext(targetContextId); +int allocateNewPage([int targetContextId = -1]) { + return _allocateNewPage(targetContextId); } -typedef NativeRegisterPluginByteCode = Void Function(Pointer bytes, Int32 length, Pointer pluginName); -typedef DartRegisterPluginByteCode = void Function(Pointer bytes, int length, Pointer pluginName); +typedef NativeRegisterPluginByteCode = Void Function( + Pointer bytes, Int32 length, Pointer pluginName); +typedef DartRegisterPluginByteCode = void Function( + Pointer bytes, int length, Pointer pluginName); -final DartRegisterPluginByteCode _registerPluginByteCode = - nativeDynamicLibrary.lookup>('registerPluginByteCode').asFunction(); +final DartRegisterPluginByteCode _registerPluginByteCode = KrakenDynamicLibrary + .ref + .lookup>( + 'registerPluginByteCode') + .asFunction(); void registerPluginByteCode(Uint8List bytecode, String name) { Pointer bytes = malloc.allocate(sizeOf() * bytecode.length); + bytes.asTypedList(bytecode.length).setAll(0, bytecode); _registerPluginByteCode(bytes, bytecode.length, name.toNativeUtf8()); } typedef NativeProfileModeEnabled = Int32 Function(); typedef DartProfileModeEnabled = int Function(); -final DartProfileModeEnabled _profileModeEnabled = -nativeDynamicLibrary.lookup>('profileModeEnabled').asFunction(); +final DartProfileModeEnabled _profileModeEnabled = KrakenDynamicLibrary + .ref + .lookup>('profileModeEnabled') + .asFunction(); + +const _CODE_ENABLED = 1; bool profileModeEnabled() { - return _profileModeEnabled() == 1 ? true : false; + return _profileModeEnabled() == _CODE_ENABLED; } // Regisdster reloadJsContext typedef NativeReloadJSContext = Void Function(Int32 contextId); typedef DartReloadJSContext = void Function(int contextId); -final DartReloadJSContext _reloadJSContext = - nativeDynamicLibrary.lookup>('reloadJsContext').asFunction(); +final DartReloadJSContext _reloadJSContext = KrakenDynamicLibrary.ref + .lookup>('reloadJsContext') + .asFunction(); Future reloadJSContext(int contextId) async { Completer completer = Completer(); @@ -254,20 +303,27 @@ Future reloadJSContext(int contextId) async { typedef NativeFlushUICommandCallback = Void Function(); typedef DartFlushUICommandCallback = void Function(); -final DartFlushUICommandCallback _flushUICommandCallback = -nativeDynamicLibrary.lookup>('flushUICommandCallback').asFunction(); +final DartFlushUICommandCallback _flushUICommandCallback = KrakenDynamicLibrary + .ref + .lookup>( + 'flushUICommandCallback') + .asFunction(); void flushUICommandCallback() { _flushUICommandCallback(); } -typedef NativeDispatchUITask = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef DartDispatchUITask = void Function(int contextId, Pointer context, Pointer callback); +typedef NativeDispatchUITask = Void Function( + Int32 contextId, Pointer context, Pointer callback); +typedef DartDispatchUITask = void Function( + int contextId, Pointer context, Pointer callback); -final DartDispatchUITask _dispatchUITask = - nativeDynamicLibrary.lookup>('dispatchUITask').asFunction(); +final DartDispatchUITask _dispatchUITask = KrakenDynamicLibrary.ref + .lookup>('dispatchUITask') + .asFunction(); -void dispatchUITask(int contextId, Pointer context, Pointer callback) { +void dispatchUITask( + int contextId, Pointer context, Pointer callback) { _dispatchUITask(contextId, context, callback); } @@ -305,20 +361,26 @@ class UICommandItem extends Struct { typedef NativeGetUICommandItems = Pointer Function(Int32 contextId); typedef DartGetUICommandItems = Pointer Function(int contextId); -final DartGetUICommandItems _getUICommandItems = - nativeDynamicLibrary.lookup>('getUICommandItems').asFunction(); +final DartGetUICommandItems _getUICommandItems = KrakenDynamicLibrary + .ref + .lookup>('getUICommandItems') + .asFunction(); typedef NativeGetUICommandItemSize = Int64 Function(Int64 contextId); typedef DartGetUICommandItemSize = int Function(int contextId); -final DartGetUICommandItemSize _getUICommandItemSize = - nativeDynamicLibrary.lookup>('getUICommandItemSize').asFunction(); +final DartGetUICommandItemSize _getUICommandItemSize = KrakenDynamicLibrary + .ref + .lookup>('getUICommandItemSize') + .asFunction(); typedef NativeClearUICommandItems = Void Function(Int32 contextId); typedef DartClearUICommandItems = void Function(int contextId); -final DartClearUICommandItems _clearUICommandItems = - nativeDynamicLibrary.lookup>('clearUICommandItems').asFunction(); +final DartClearUICommandItems _clearUICommandItems = KrakenDynamicLibrary + .ref + .lookup>('clearUICommandItems') + .asFunction(); class UICommand { late final UICommandType type; @@ -348,14 +410,18 @@ const int args01StringMemOffset = 2; const int args02StringMemOffset = 3; const int nativePtrMemOffset = 4; -final bool isEnabledLog = kDebugMode && Platform.environment['ENABLE_KRAKEN_JS_LOG'] == 'true'; +final bool isEnabledLog = + kDebugMode && Platform.environment['ENABLE_KRAKEN_JS_LOG'] == 'true'; // We found there are performance bottleneck of reading native memory with Dart FFI API. // So we align all UI instructions to a whole block of memory, and then convert them into a dart array at one time, // To ensure the fastest subsequent random access. -List readNativeUICommandToDart(Pointer nativeCommandItems, int commandLength, int contextId) { - List rawMemory = nativeCommandItems.asTypedList(commandLength * nativeCommandSize).toList(growable: false); - +List readNativeUICommandToDart( + Pointer nativeCommandItems, int commandLength, int contextId) { + List rawMemory = nativeCommandItems + .cast() + .asTypedList(commandLength * nativeCommandSize) + .toList(growable: false); List results = List.generate(commandLength, (int _i) { int i = _i * nativeCommandSize; UICommand command = UICommand(); @@ -366,13 +432,15 @@ List readNativeUICommandToDart(Pointer nativeCommandItems, in // +-------+-------+ // | id | type | // +-------+-------+ - int id = typeIdCombine >> 32; - int type = typeIdCombine ^ (id << 32); + int id = (typeIdCombine >> 32).toSigned(32); + int type = (typeIdCombine ^ (id << 32)).toSigned(32); command.type = UICommandType.values[type]; command.id = id; int nativePtrValue = rawMemory[i + nativePtrMemOffset]; - command.nativePtr = nativePtrValue != 0 ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) : nullptr; + command.nativePtr = nativePtrValue != 0 + ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) + : nullptr; command.args = List.empty(growable: true); int args01And02Length = rawMemory[i + args01And02LengthMemOffset]; @@ -382,8 +450,8 @@ List readNativeUICommandToDart(Pointer nativeCommandItems, in if (args01And02Length == 0) { args01Length = args02Length = 0; } else { - args02Length = args01And02Length >> 32; - args01Length = args01And02Length ^ (args02Length << 32); + args02Length = (args01And02Length >> 32).toSigned(32); + args01Length = (args01And02Length ^ (args02Length << 32)).toSigned(32); } int args01StringMemory = rawMemory[i + args01StringMemOffset]; @@ -400,7 +468,7 @@ List readNativeUICommandToDart(Pointer nativeCommandItems, in if (isEnabledLog) { String printMsg = '${command.type}, id: ${command.id}'; - for (int i = 0; i < command.args.length; i ++) { + for (int i = 0; i < command.args.length; i++) { printMsg += ' args[$i]: ${command.args[i]}'; } printMsg += ' nativePtr: ${command.nativePtr}'; @@ -420,13 +488,15 @@ void clearUICommand(int contextId) { } void flushUICommand() { - Map controllerMap = KrakenController.getControllerMap(); + Map controllerMap = + KrakenController.getControllerMap(); for (KrakenController? controller in controllerMap.values) { if (controller == null) continue; - Pointer nativeCommandItems = _getUICommandItems(controller.view.contextId); + Pointer nativeCommandItems = + _getUICommandItems(controller.view.contextId); int commandLength = _getUICommandItemSize(controller.view.contextId); - if (commandLength == 0) { + if (commandLength == 0 || nativeCommandItems == nullptr) { continue; } @@ -434,7 +504,8 @@ void flushUICommand() { PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); } - List commands = readNativeUICommandToDart(nativeCommandItems, commandLength, controller.view.contextId); + List commands = readNativeUICommandToDart( + nativeCommandItems, commandLength, controller.view.contextId); SchedulerBinding.instance!.scheduleFrame(); @@ -454,16 +525,19 @@ void flushUICommand() { try { switch (commandType) { case UICommandType.createElement: - controller.view.createElement(id, nativePtr.cast(), command.args[0]); + controller.view.createElement( + id, nativePtr.cast(), command.args[0]); break; case UICommandType.createTextNode: - controller.view.createTextNode(id, nativePtr.cast(), command.args[0]); + controller.view.createTextNode( + id, nativePtr.cast(), command.args[0]); break; case UICommandType.createComment: - controller.view.createComment(id, nativePtr.cast()); + controller.view + .createComment(id, nativePtr.cast()); break; case UICommandType.disposeEventTarget: - ElementManager.disposeEventTarget(controller.view.contextId, id); + controller.view.disposeEventTarget(id); break; case UICommandType.addEvent: controller.view.addEvent(id, command.args[0]); @@ -499,7 +573,8 @@ void flushUICommand() { controller.view.removeProperty(id, key); break; case UICommandType.createDocumentFragment: - controller.view.createDocumentFragment(id, nativePtr.cast()); + controller.view.createDocumentFragment( + id, nativePtr.cast()); break; default: break; diff --git a/kraken/lib/src/css/animation.dart b/kraken/lib/src/css/animation.dart index 928dc1d34e..e2f96a70e2 100644 --- a/kraken/lib/src/css/animation.dart +++ b/kraken/lib/src/css/animation.dart @@ -501,7 +501,7 @@ class KeyframeEffect extends AnimationEffect { if (left == right) continue; - List? handlers = CSSTranstionHandlers[property]; + List? handlers = CSSTransitionHandlers[property]; handlers ??= [_defaultParse, _defaultLerp]; Function parseProperty = handlers[0]; diff --git a/kraken/lib/src/css/background.dart b/kraken/lib/src/css/background.dart index e1fd43e19d..aa3fea5cde 100644 --- a/kraken/lib/src/css/background.dart +++ b/kraken/lib/src/css/background.dart @@ -8,6 +8,7 @@ import 'dart:ui'; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; +import 'package:kraken/painting.dart'; import 'package:kraken/css.dart'; import 'package:kraken/launcher.dart'; import 'package:kraken/rendering.dart'; @@ -15,10 +16,6 @@ import 'package:kraken/rendering.dart'; // CSS Backgrounds: https://drafts.csswg.org/css-backgrounds/ // CSS Images: https://drafts.csswg.org/css-images-3/ -/// The [CSSBackgroundMixin] mixin used to handle background shorthand and compute -/// to single value of background -/// - final RegExp _splitRegExp = RegExp(r'\s+'); const String _singleQuote = '\''; const String _doubleQuote = '"'; @@ -81,7 +78,9 @@ enum CSSBackgroundImageType { image, } -mixin CSSBackgroundMixin on RenderStyleBase { +/// The [CSSBackgroundMixin] mixin used to handle background shorthand and compute +/// to single value of background. +mixin CSSBackgroundMixin on RenderStyle { static CSSBackgroundPosition DEFAULT_BACKGROUND_POSITION = CSSBackgroundPosition(percentage: -1); static CSSBackgroundSize DEFAULT_BACKGROUND_SIZE = CSSBackgroundSize(fit: BoxFit.none); @@ -91,7 +90,7 @@ mixin CSSBackgroundMixin on RenderStyleBase { set backgroundClip(BackgroundBoundary? value) { if (value == _backgroundClip) return; _backgroundClip = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-origin @@ -100,42 +99,46 @@ mixin CSSBackgroundMixin on RenderStyleBase { set backgroundOrigin(BackgroundBoundary? value) { if (value == _backgroundOrigin) return; _backgroundOrigin = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override Color? get backgroundColor => _backgroundColor; Color? _backgroundColor; set backgroundColor(Color? value) { if (value == _backgroundColor) return; _backgroundColor = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-image + @override CSSBackgroundImage? get backgroundImage => _backgroundImage; CSSBackgroundImage? _backgroundImage; set backgroundImage(CSSBackgroundImage? value) { if (value == _backgroundImage) return; _backgroundImage = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-position-x + @override CSSBackgroundPosition get backgroundPositionX => _backgroundPositionX ?? DEFAULT_BACKGROUND_POSITION; CSSBackgroundPosition? _backgroundPositionX; set backgroundPositionX(CSSBackgroundPosition? value) { if (value == _backgroundPositionX) return; _backgroundPositionX = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-position-y + @override CSSBackgroundPosition get backgroundPositionY => _backgroundPositionY ?? DEFAULT_BACKGROUND_POSITION; CSSBackgroundPosition? _backgroundPositionY; set backgroundPositionY(CSSBackgroundPosition? value) { if (value == _backgroundPositionY) return; _backgroundPositionY = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-size @@ -144,7 +147,7 @@ mixin CSSBackgroundMixin on RenderStyleBase { set backgroundSize(CSSBackgroundSize? value) { if (value == _backgroundSize) return; _backgroundSize = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-attachment @@ -153,16 +156,17 @@ mixin CSSBackgroundMixin on RenderStyleBase { set backgroundAttachment(CSSBackgroundAttachmentType? value) { if (value == _backgroundAttachment) return; _backgroundAttachment = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Background-repeat + @override ImageRepeat get backgroundRepeat => _backgroundRepeat ?? ImageRepeat.repeat; ImageRepeat? _backgroundRepeat; set backgroundRepeat(ImageRepeat? value) { if (value == _backgroundRepeat) return; _backgroundRepeat = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } } @@ -191,7 +195,7 @@ class CSSBackgroundImage { Uri uri = Uri.parse(url); if (url.isNotEmpty) { uri = controller.uriParser!.resolve(Uri.parse(controller.href), uri); - return CSSUrl.parseUrl(uri, contextId: controller.view.contextId); + return getImageProvider(uri, contextId: controller.view.contextId); } } } @@ -405,8 +409,8 @@ class CSSBackground { value == FIT_HEIGTH || value == SCALE_DOWN || value == FILL || - CSSLength.isLength(value) || - CSSPercentage.isPercentage(value); + CSSLength.isNonNegativeLength(value) || + CSSPercentage.isNonNegativePercentage(value); } static bool isValidBackgroundAttachmentValue(String value) { diff --git a/kraken/lib/src/css/border.dart b/kraken/lib/src/css/border.dart index 04078b940b..074b100cf5 100644 --- a/kraken/lib/src/css/border.dart +++ b/kraken/lib/src/css/border.dart @@ -11,6 +11,9 @@ import 'package:kraken/css.dart'; final RegExp _splitRegExp = RegExp(r'\s+'); +// Initial border value: medium +final CSSLengthValue _mediumWidth = CSSLengthValue(3, CSSLengthType.PX); + enum CSSBorderStyleType { none, hidden, @@ -24,10 +27,11 @@ enum CSSBorderStyleType { outset, } -mixin CSSBorderMixin on RenderStyleBase { +mixin CSSBorderMixin on RenderStyle { // Effective border widths. These are used to calculate the // dimensions of the border box. + @override EdgeInsets get border { // If has border, render padding should subtracting the edge of the border return EdgeInsets.fromLTRB( @@ -47,12 +51,12 @@ mixin CSSBorderMixin on RenderStyleBase { return constraints.deflate(border); } + @override List? get borderSides { - RenderStyle renderStyle = this as RenderStyle; - BorderSide? leftSide = CSSBorderSide.getBorderSide(renderStyle, CSSBorderSide.LEFT); - BorderSide? topSide = CSSBorderSide.getBorderSide(renderStyle, CSSBorderSide.TOP); - BorderSide? rightSide = CSSBorderSide.getBorderSide(renderStyle, CSSBorderSide.RIGHT); - BorderSide? bottomSide = CSSBorderSide.getBorderSide(renderStyle, CSSBorderSide.BOTTOM); + BorderSide? leftSide = CSSBorderSide._getBorderSide(this, CSSBorderSide.LEFT); + BorderSide? topSide = CSSBorderSide._getBorderSide(this, CSSBorderSide.TOP); + BorderSide? rightSide = CSSBorderSide._getBorderSide(this, CSSBorderSide.RIGHT); + BorderSide? bottomSide = CSSBorderSide._getBorderSide(this, CSSBorderSide.BOTTOM); bool hasBorder = leftSide != null || topSide != null || @@ -77,109 +81,131 @@ mixin CSSBorderMixin on RenderStyleBase { /// Border-width = | thin | medium | thick - // Initial value: medium - final CSSLengthValue _mediumWidth = CSSLengthValue(3, CSSLengthType.PX); - CSSLengthValue? _borderTopWidth; set borderTopWidth(CSSLengthValue? value) { if (value == _borderTopWidth) return; _borderTopWidth = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } + @override CSSLengthValue? get borderTopWidth => _borderTopWidth; + + @override CSSLengthValue get effectiveBorderTopWidth => borderTopStyle == BorderStyle.none ? CSSLengthValue.zero : (_borderTopWidth ?? _mediumWidth); CSSLengthValue? _borderRightWidth; set borderRightWidth(CSSLengthValue? value) { if (value == _borderRightWidth) return; _borderRightWidth = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } + + @override CSSLengthValue? get borderRightWidth => _borderRightWidth; + + @override CSSLengthValue get effectiveBorderRightWidth => borderRightStyle == BorderStyle.none ? CSSLengthValue.zero : (_borderRightWidth ?? _mediumWidth); CSSLengthValue? _borderBottomWidth; set borderBottomWidth(CSSLengthValue? value) { if (value == _borderBottomWidth) return; _borderBottomWidth = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } + + @override CSSLengthValue? get borderBottomWidth => _borderBottomWidth; + + @override CSSLengthValue get effectiveBorderBottomWidth => borderBottomStyle == BorderStyle.none ? CSSLengthValue.zero : (_borderBottomWidth ?? _mediumWidth); CSSLengthValue? _borderLeftWidth; set borderLeftWidth(CSSLengthValue? value) { if (value == _borderLeftWidth) return; _borderLeftWidth = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } + + @override CSSLengthValue? get borderLeftWidth => _borderLeftWidth; + + @override CSSLengthValue get effectiveBorderLeftWidth => borderLeftStyle == BorderStyle.none ? CSSLengthValue.zero : (_borderLeftWidth ?? _mediumWidth); /// Border-color + @override Color get borderTopColor => _borderTopColor ?? currentColor; Color? _borderTopColor; set borderTopColor(Color? value) { if (value == _borderTopColor) return; _borderTopColor = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override Color get borderRightColor => _borderRightColor ?? currentColor; Color? _borderRightColor; set borderRightColor(Color? value) { if (value == _borderRightColor) return; _borderRightColor = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override Color get borderBottomColor => _borderBottomColor ?? currentColor; Color? _borderBottomColor; set borderBottomColor(Color? value) { if (value == _borderBottomColor) return; _borderBottomColor = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override Color get borderLeftColor => _borderLeftColor ?? currentColor; Color? _borderLeftColor; set borderLeftColor(Color? value) { if (value == _borderLeftColor) return; _borderLeftColor = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } /// Border-style + @override BorderStyle get borderTopStyle => _borderTopStyle ?? BorderStyle.none; BorderStyle? _borderTopStyle; set borderTopStyle(BorderStyle? value) { if (value == _borderTopStyle) return; _borderTopStyle = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override BorderStyle get borderRightStyle => _borderRightStyle ?? BorderStyle.none; BorderStyle? _borderRightStyle; set borderRightStyle(BorderStyle? value) { if (value == _borderRightStyle) return; _borderRightStyle = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override BorderStyle get borderBottomStyle => _borderBottomStyle ?? BorderStyle.none; BorderStyle? _borderBottomStyle; set borderBottomStyle(BorderStyle? value) { if (value == _borderBottomStyle) return; _borderBottomStyle = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } - BorderStyle get borderLeftStyle => _borderLeftStyle ?? BorderStyle.none; BorderStyle? _borderLeftStyle; + + @override + BorderStyle get borderLeftStyle => _borderLeftStyle ?? BorderStyle.none; + set borderLeftStyle(BorderStyle? value) { if (value == _borderLeftStyle) return; _borderLeftStyle = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } } @@ -237,12 +263,12 @@ class CSSBorderSide { } static bool isValidBorderWidthValue(String value) { - return CSSLength.isLength(value) || value == THIN || value == MEDIUM || value == THICK; + return CSSLength.isNonNegativeLength(value) || value == THIN || value == MEDIUM || value == THICK; } static BorderSide none = BorderSide(color: defaultBorderColor, width: 0.0, style: BorderStyle.none); - static BorderSide? getBorderSide(RenderStyle renderStyle, String side) { + static BorderSide? _getBorderSide(RenderStyle renderStyle, String side) { BorderStyle? borderStyle; CSSLengthValue? borderWidth; Color? borderColor; diff --git a/kraken/lib/src/css/border_radius.dart b/kraken/lib/src/css/border_radius.dart index 56a95ecc22..5340944f56 100644 --- a/kraken/lib/src/css/border_radius.dart +++ b/kraken/lib/src/css/border_radius.dart @@ -2,39 +2,44 @@ import 'dart:ui'; import 'package:kraken/css.dart'; -mixin CSSBorderRadiusMixin on RenderStyleBase { +mixin CSSBorderRadiusMixin on RenderStyle { CSSBorderRadius? _borderTopLeftRadius; set borderTopLeftRadius(CSSBorderRadius? value) { if (value == _borderTopLeftRadius) return; _borderTopLeftRadius = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override CSSBorderRadius get borderTopLeftRadius => _borderTopLeftRadius ?? CSSBorderRadius.zero; CSSBorderRadius? _borderTopRightRadius; set borderTopRightRadius(CSSBorderRadius? value) { if (value == _borderTopRightRadius) return; _borderTopRightRadius = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override CSSBorderRadius get borderTopRightRadius => _borderTopRightRadius ?? CSSBorderRadius.zero; CSSBorderRadius? _borderBottomRightRadius; set borderBottomRightRadius(CSSBorderRadius? value) { if (value == _borderBottomRightRadius) return; _borderBottomRightRadius = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override CSSBorderRadius get borderBottomRightRadius => _borderBottomRightRadius ?? CSSBorderRadius.zero; CSSBorderRadius? _borderBottomLeftRadius; set borderBottomLeftRadius(CSSBorderRadius? value) { if (value == _borderBottomLeftRadius) return; _borderBottomLeftRadius = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override CSSBorderRadius get borderBottomLeftRadius => _borderBottomLeftRadius ?? CSSBorderRadius.zero; + @override List? get borderRadius { bool hasBorderRadius = borderTopLeftRadius != CSSBorderRadius.zero || borderTopRightRadius != CSSBorderRadius.zero || diff --git a/kraken/lib/src/css/box.dart b/kraken/lib/src/css/box.dart index 585a5ee628..5ea710c727 100644 --- a/kraken/lib/src/css/box.dart +++ b/kraken/lib/src/css/box.dart @@ -11,21 +11,16 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; // CSS Box Model: https://drafts.csswg.org/css-box-4/ -mixin CSSBoxMixin on RenderStyleBase { +mixin CSSBoxMixin on RenderStyle { final DecorationPosition decorationPosition = DecorationPosition.background; final ImageConfiguration imageConfiguration = ImageConfiguration.empty; /// What decoration to paint, should get value after layout. CSSBoxDecoration? get decoration { - RenderStyle renderStyle = this as RenderStyle; - List? radius = renderStyle.borderRadius; - List? borderSides = renderStyle.borderSides; - List? shadows = renderStyle.shadows; - var backgroundColor = renderStyle.backgroundColor; - var backgroundImage = renderStyle.backgroundImage; - var backgroundRepeat = renderStyle.backgroundRepeat; - + List? radius = this.borderRadius; + List? borderSides = this.borderSides; + if (backgroundColor == null && backgroundImage == null && borderSides == null && diff --git a/kraken/lib/src/css/box_shadow.dart b/kraken/lib/src/css/box_shadow.dart index f6943ca1ad..43732a601c 100644 --- a/kraken/lib/src/css/box_shadow.dart +++ b/kraken/lib/src/css/box_shadow.dart @@ -1,14 +1,15 @@ import 'package:kraken/css.dart'; -mixin CSSBoxShadowMixin on RenderStyleBase { +mixin CSSBoxShadowMixin on RenderStyle { List? _boxShadow; set boxShadow(List? value) { if (value == _boxShadow) return; _boxShadow = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } List? get boxShadow => _boxShadow; + @override List? get shadows { if (boxShadow == null) { return null; diff --git a/kraken/lib/src/css/content_visibility.dart b/kraken/lib/src/css/content_visibility.dart index d5c582ef1d..495368d3e9 100644 --- a/kraken/lib/src/css/content_visibility.dart +++ b/kraken/lib/src/css/content_visibility.dart @@ -13,7 +13,7 @@ enum ContentVisibility { visible } -mixin CSSContentVisibilityMixin on RenderStyleBase { +mixin CSSContentVisibilityMixin on RenderStyle { /// Whether the child is hidden from the rest of the tree. /// @@ -25,13 +25,14 @@ mixin CSSContentVisibilityMixin on RenderStyleBase { /// /// If ContentVisibility.auto, the framework will compute the intersection bounds and not to paint when child renderObject /// are no longer intersection with this renderObject. - ContentVisibility? _contentVisibility; + @override ContentVisibility? get contentVisibility => _contentVisibility; + ContentVisibility? _contentVisibility; set contentVisibility(ContentVisibility? value) { if (value == null) return; if (value == _contentVisibility) return; _contentVisibility = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } static ContentVisibility resolveContentVisibility(String value) { diff --git a/kraken/lib/src/css/display.dart b/kraken/lib/src/css/display.dart index 8d7c1b8a11..eb83aa79de 100644 --- a/kraken/lib/src/css/display.dart +++ b/kraken/lib/src/css/display.dart @@ -14,20 +14,24 @@ enum CSSDisplay { flex, inlineFlex, - sliver, // @TODO temp name. + sliver, none } -mixin CSSDisplayMixin on RenderStyleBase { +mixin CSSDisplayMixin on RenderStyle { CSSDisplay? _display; + + @override CSSDisplay get display => _display ?? CSSDisplay.inline; set display(CSSDisplay value) { if (_display != value) { _display = value; - renderBoxModel?.markNeedsLayout(); + + // The display changes of the node may affect the whitespace of the nextSibling and previousSibling text node so prev and next node require layout. + renderBoxModel?.markAdjacentRenderParagraphNeedsLayout(); } } @@ -58,13 +62,11 @@ mixin CSSDisplayMixin on RenderStyleBase { /// Some layout effects require blockification or inlinification of the box type /// https://www.w3.org/TR/css-display-3/#transformations - CSSDisplay? get effectiveDisplay { - RenderStyle renderStyle = this as RenderStyle; - CSSDisplay? transformedDisplay = renderStyle.display; - - // Must take from style because it inited before flush pending properties. - CSSPositionType position = renderStyle.position; + @override + CSSDisplay get effectiveDisplay { + CSSDisplay transformedDisplay = display; + // Must take `position` from style because it inited before flush pending properties. // Display as inline-block when element is positioned if (position == CSSPositionType.absolute || position == CSSPositionType.fixed) { return CSSDisplay.inlineBlock; @@ -82,17 +84,16 @@ mixin CSSDisplayMixin on RenderStyleBase { RenderBoxModel parent = renderBoxModel!.parent as RenderBoxModel; RenderStyle parentRenderStyle = parent.renderStyle; - CSSLengthValue marginLeft = renderStyle.marginLeft; - CSSLengthValue marginRight = renderStyle.marginRight; - bool isVerticalDirection = parentRenderStyle.flexDirection == FlexDirection.column || parentRenderStyle.flexDirection == FlexDirection.columnReverse; // Flex item will not stretch in stretch alignment when flex wrap is set to wrap or wrap-reverse bool isFlexNoWrap = parentRenderStyle.flexWrap == FlexWrap.nowrap; - bool isAlignItemsStretch = parentRenderStyle.effectiveAlignItems == AlignItems.stretch; + bool isStretchSelf = alignSelf != AlignSelf.auto + ? alignSelf == AlignSelf.stretch + : parentRenderStyle.effectiveAlignItems == AlignItems.stretch; // Display as block if flex vertical layout children and stretch children - if (!marginLeft.isAuto && !marginRight.isAuto && isVerticalDirection && isFlexNoWrap && isAlignItemsStretch) { + if (!marginLeft.isAuto && !marginRight.isAuto && isVerticalDirection && isFlexNoWrap && isStretchSelf) { transformedDisplay = CSSDisplay.block; } } diff --git a/kraken/lib/src/css/filter.dart b/kraken/lib/src/css/filter.dart index e396be3add..47515faa0d 100644 --- a/kraken/lib/src/css/filter.dart +++ b/kraken/lib/src/css/filter.dart @@ -91,7 +91,7 @@ List _multiplyMatrix5(List? a, List b) { /// Impl W3C Filter Effects Spec: /// https://www.w3.org/TR/filter-effects-1/#definitions -mixin CSSFilterEffectsMixin on RenderStyleBase { +mixin CSSFilterEffectsMixin on RenderStyle { // Get the color filter. // eg: 'grayscale(1) grayscale(0.5)' -> matrix5(grayscale(1)) · matrix5(grayscale(0.5)) @@ -138,13 +138,12 @@ mixin CSSFilterEffectsMixin on RenderStyleBase { // Get the image filter. ImageFilter? _parseImageFilters(List functions) { - RenderStyle renderStyle = this as RenderStyle; if (functions.isNotEmpty) { for (int i = 0; i < functions.length; i ++) { CSSFunctionalNotation f = functions[i]; switch (f.name.toLowerCase()) { case BLUR: - CSSLengthValue length = CSSLength.parseLength(f.args.first, renderStyle, FILTER); + CSSLengthValue length = CSSLength.parseLength(f.args.first, this, FILTER); double amount = length.computedValue; ImageFilter imageFilter = ImageFilter.blur(sigmaX: amount, sigmaY: amount); // Only length is not relative value will cached the image filter. @@ -159,6 +158,8 @@ mixin CSSFilterEffectsMixin on RenderStyleBase { } ColorFilter? _cachedColorFilter; + + @override ColorFilter? get colorFilter { if (_filter == null) { return null; @@ -170,6 +171,8 @@ mixin CSSFilterEffectsMixin on RenderStyleBase { } ImageFilter? _cachedImageFilter; + + @override ImageFilter? get imageFilter { if (_filter == null) { return null; @@ -180,8 +183,9 @@ mixin CSSFilterEffectsMixin on RenderStyleBase { } } - List? _filter; + @override List? get filter => _filter; + List? _filter; set filter(List? functions) { _filter = functions; // Clear cache when filter changed. @@ -189,12 +193,12 @@ mixin CSSFilterEffectsMixin on RenderStyleBase { _cachedImageFilter = null; // Filter effect the stacking context. - RenderBoxModel? parentRenderer = (this as RenderStyle).parent?.renderBoxModel; + RenderBoxModel? parentRenderer = parent?.renderBoxModel; if (parentRenderer is RenderLayoutBox) { parentRenderer.markChildrenNeedsSort(); } - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); if (!kReleaseMode && functions != null) { ColorFilter? colorFilter = _parseColorFilters(functions); diff --git a/kraken/lib/src/css/flexbox.dart b/kraken/lib/src/css/flexbox.dart index db65e30e61..1562a8e6f2 100644 --- a/kraken/lib/src/css/flexbox.dart +++ b/kraken/lib/src/css/flexbox.dart @@ -173,10 +173,11 @@ enum AlignSelf { baseline } -mixin CSSFlexboxMixin on RenderStyleBase { +mixin CSSFlexboxMixin on RenderStyle { - FlexDirection? _flexDirection; + @override FlexDirection get flexDirection => _flexDirection ?? FlexDirection.row; + FlexDirection? _flexDirection; set flexDirection(FlexDirection? value) { if (value == _flexDirection) return; _flexDirection = value; @@ -185,8 +186,9 @@ mixin CSSFlexboxMixin on RenderStyleBase { } } - FlexWrap? _flexWrap; + @override FlexWrap get flexWrap => _flexWrap ?? FlexWrap.nowrap; + FlexWrap? _flexWrap; set flexWrap(FlexWrap? value) { if (_flexWrap == value) return; _flexWrap = value; @@ -195,9 +197,9 @@ mixin CSSFlexboxMixin on RenderStyleBase { } } - - JustifyContent? _justifyContent; + @override JustifyContent get justifyContent => _justifyContent ?? JustifyContent.flexStart; + JustifyContent? _justifyContent; set justifyContent(JustifyContent? value) { if (_justifyContent == value) return; _justifyContent = value; @@ -207,8 +209,9 @@ mixin CSSFlexboxMixin on RenderStyleBase { } - AlignItems? _alignItems; + @override AlignItems get alignItems => _alignItems ?? AlignItems.stretch; + AlignItems? _alignItems; set alignItems(AlignItems? value) { if (_alignItems == value) return; _alignItems = value; @@ -217,9 +220,9 @@ mixin CSSFlexboxMixin on RenderStyleBase { } } + @override AlignItems get effectiveAlignItems { if (CSSFlex.isVerticalFlexDirection(flexDirection)) { - TextAlign textAlign = (this as RenderStyle).textAlign; if (textAlign == TextAlign.right) { return AlignItems.flexEnd; } else if (textAlign == TextAlign.center) { @@ -229,8 +232,9 @@ mixin CSSFlexboxMixin on RenderStyleBase { return alignItems; } - AlignContent? _alignContent; + @override AlignContent get alignContent => _alignContent ?? AlignContent.stretch; + AlignContent? _alignContent; set alignContent(AlignContent? value) { if (_alignContent == value) return; _alignContent = value; @@ -239,18 +243,20 @@ mixin CSSFlexboxMixin on RenderStyleBase { } } - AlignSelf? _alignSelf; + @override AlignSelf get alignSelf => _alignSelf ?? AlignSelf.auto; + AlignSelf? _alignSelf; set alignSelf(AlignSelf value) { if (_alignSelf == value) return; _alignSelf = value; - if (renderBoxModel!.parent is RenderFlexLayout) { + if (renderBoxModel?.parent is RenderFlexLayout) { renderBoxModel!.markNeedsLayout(); } } - CSSLengthValue? _flexBasis; + @override CSSLengthValue? get flexBasis => _flexBasis; + CSSLengthValue? _flexBasis; set flexBasis(CSSLengthValue? value) { // Negative value is invalid. if ((value != null && ((value.value != null && value.value! < 0))) || @@ -259,27 +265,29 @@ mixin CSSFlexboxMixin on RenderStyleBase { return; } _flexBasis = value; - if (renderBoxModel!.parent is RenderFlexLayout) { + if (renderBoxModel?.parent is RenderFlexLayout) { renderBoxModel!.markNeedsLayout(); } } - double? _flexGrow; + @override double get flexGrow => _flexGrow ?? 0.0; + double? _flexGrow; set flexGrow(double? value) { if (_flexGrow == value) return; _flexGrow = value; - if (renderBoxModel!.parent is RenderFlexLayout) { + if (renderBoxModel?.parent is RenderFlexLayout) { renderBoxModel!.markNeedsLayout(); } } - double? _flexShrink; + @override double get flexShrink => _flexShrink ?? 1.0; + double? _flexShrink; set flexShrink(double? value) { if (_flexShrink == value) return; _flexShrink = value; - if (renderBoxModel!.parent is RenderFlexLayout) { + if (renderBoxModel?.parent is RenderFlexLayout) { renderBoxModel!.markNeedsLayout(); } } diff --git a/kraken/lib/src/css/inline.dart b/kraken/lib/src/css/inline.dart index 456ca25fb8..e30e4e114e 100644 --- a/kraken/lib/src/css/inline.dart +++ b/kraken/lib/src/css/inline.dart @@ -24,13 +24,14 @@ enum VerticalAlign { /// middle, } -mixin CSSInlineMixin on RenderStyleBase { - VerticalAlign _verticalAlign = VerticalAlign.baseline; +mixin CSSInlineMixin on RenderStyle { + @override VerticalAlign get verticalAlign => _verticalAlign; + VerticalAlign _verticalAlign = VerticalAlign.baseline; set verticalAlign(VerticalAlign value) { if (_verticalAlign != value) { - renderBoxModel!.markNeedsLayout(); _verticalAlign = value; + renderBoxModel?.markNeedsLayout(); } } diff --git a/kraken/lib/src/css/margin.dart b/kraken/lib/src/css/margin.dart index 339cc5df8f..a0863e75ec 100644 --- a/kraken/lib/src/css/margin.dart +++ b/kraken/lib/src/css/margin.dart @@ -9,12 +9,13 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; import 'package:kraken/rendering.dart'; -mixin CSSMarginMixin on RenderStyleBase { +mixin CSSMarginMixin on RenderStyle { /// The amount to margin the child in each dimension. /// /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] /// must not be null. + @override EdgeInsets get margin { EdgeInsets insets = EdgeInsets.only( left: marginLeft.computedValue, @@ -22,7 +23,6 @@ mixin CSSMarginMixin on RenderStyleBase { bottom: marginBottom.computedValue, top: marginTop.computedValue ).resolve(TextDirection.ltr); - assert(insets.isNonNegative); return insets; } @@ -32,6 +32,8 @@ mixin CSSMarginMixin on RenderStyleBase { _marginLeft = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get marginLeft => _marginLeft ?? CSSLengthValue.zero; CSSLengthValue? _marginRight; @@ -40,6 +42,8 @@ mixin CSSMarginMixin on RenderStyleBase { _marginRight = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get marginRight => _marginRight ?? CSSLengthValue.zero; CSSLengthValue? _marginBottom; @@ -48,6 +52,8 @@ mixin CSSMarginMixin on RenderStyleBase { _marginBottom = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get marginBottom => _marginBottom ?? CSSLengthValue.zero; CSSLengthValue? _marginTop; @@ -56,6 +62,8 @@ mixin CSSMarginMixin on RenderStyleBase { _marginTop = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get marginTop => _marginTop ?? CSSLengthValue.zero; void _markSelfAndParentNeedsLayout() { diff --git a/kraken/lib/src/css/matrix.dart b/kraken/lib/src/css/matrix.dart index e6895b6d7c..1988534fa9 100644 --- a/kraken/lib/src/css/matrix.dart +++ b/kraken/lib/src/css/matrix.dart @@ -659,20 +659,32 @@ class CSSMatrix { if (method.args.length == 1) { CSSLengthValue x = CSSLength.parseLength(method.args[0].trim(), renderStyle, TRANSLATE, Axis.horizontal); x.renderStyle = renderStyle; - return Matrix4.identity()..translate(x.computedValue); + double computedValue = x.computedValue; + // Double.infinity indicates translate not resolved due to renderBox not layout yet + // in percentage case. + if (computedValue == double.infinity) return null; + return Matrix4.identity()..translate(computedValue); } break; case TRANSLATE_Y: if (method.args.length == 1) { CSSLengthValue y = CSSLength.parseLength(method.args[0].trim(), renderStyle, TRANSLATE, Axis.vertical); y.renderStyle = renderStyle; - return Matrix4.identity()..translate(0.0, y.computedValue); + double computedValue = y.computedValue; + // Double.infinity indicates translate not resolved due to renderBox not layout yet + // in percentage case. + if (computedValue == double.infinity) return null; + return Matrix4.identity()..translate(0.0, computedValue); } break; case TRANSLATE_Z: if (method.args.length == 1) { CSSLengthValue z = CSSLength.parseLength(method.args[0].trim(), renderStyle, TRANSLATE); - return Matrix4.identity()..translate(0.0, 0.0, z.computedValue); + double computedValue = z.computedValue; + // Double.infinity indicates translate not resolved due to renderBox not layout yet + // in percentage case. + if (computedValue == double.infinity) return null; + return Matrix4.identity()..translate(0.0, 0.0, computedValue); } break; // https://drafts.csswg.org/css-transforms-2/#individual-transforms diff --git a/kraken/lib/src/css/object_fit.dart b/kraken/lib/src/css/object_fit.dart index f2f9fd5dd7..adcf037f79 100644 --- a/kraken/lib/src/css/object_fit.dart +++ b/kraken/lib/src/css/object_fit.dart @@ -6,15 +6,15 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; -mixin CSSObjectFitMixin on RenderStyleBase { +mixin CSSObjectFitMixin on RenderStyle { + + @override + BoxFit get objectFit => _objectFit; BoxFit _objectFit = BoxFit.fill; - BoxFit get objectFit { - return _objectFit; - } set objectFit(BoxFit value) { if (_objectFit == value) return; _objectFit = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } static BoxFit resolveBoxFit(String fit) { diff --git a/kraken/lib/src/css/object_position.dart b/kraken/lib/src/css/object_position.dart index 87cce4cc65..b6ca1c7898 100644 --- a/kraken/lib/src/css/object_position.dart +++ b/kraken/lib/src/css/object_position.dart @@ -6,15 +6,14 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; -mixin CSSObjectPositionMixin on RenderStyleBase { +mixin CSSObjectPositionMixin on RenderStyle { + @override + Alignment get objectPosition => _objectPosition; Alignment _objectPosition = Alignment.center; - Alignment get objectPosition { - return _objectPosition; - } set objectPosition(Alignment value) { if (_objectPosition == value) return; _objectPosition = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } static Alignment resolveObjectPosition(String? position) { diff --git a/kraken/lib/src/css/opacity.dart b/kraken/lib/src/css/opacity.dart index a08cfdab6e..23bb9172cc 100644 --- a/kraken/lib/src/css/opacity.dart +++ b/kraken/lib/src/css/opacity.dart @@ -8,7 +8,7 @@ import 'dart:ui' as ui; import 'package:kraken/css.dart'; import 'package:kraken/rendering.dart'; -mixin CSSOpacityMixin on RenderStyleBase { +mixin CSSOpacityMixin on RenderStyle { /// The fraction to scale the child's alpha value. /// @@ -20,6 +20,7 @@ mixin CSSOpacityMixin on RenderStyleBase { /// Values 1.0 and 0.0 are painted with a fast path. Other values /// require painting the child into an intermediate buffer, which is /// expensive. + @override double get opacity => _opacity; double _opacity = 1.0; set opacity(double? value) { @@ -30,16 +31,17 @@ mixin CSSOpacityMixin on RenderStyleBase { _opacity = value; int alpha = ui.Color.getAlphaFromOpacity(_opacity); renderBoxModel!.alpha = alpha; - if (alpha != 0 && alpha != 255) - renderBoxModel!.markNeedsCompositingBitsUpdate(); + if (alpha != 0 && alpha != 255) { + renderBoxModel?.markNeedsCompositingBitsUpdate(); + } // Opacity effect the stacking context. - RenderBoxModel? parentRenderer = (this as RenderStyle).parent?.renderBoxModel; + RenderBoxModel? parentRenderer = parent?.renderBoxModel; if (parentRenderer is RenderLayoutBox) { parentRenderer.markChildrenNeedsSort(); } - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } static double? resolveOpacity(String value) { diff --git a/kraken/lib/src/css/overflow.dart b/kraken/lib/src/css/overflow.dart index c8bd91249b..403c3d30da 100644 --- a/kraken/lib/src/css/overflow.dart +++ b/kraken/lib/src/css/overflow.dart @@ -48,25 +48,24 @@ List _scrollingContentBoxCopyStyles = [ LINE_CLAMP, ]; -mixin CSSOverflowMixin on RenderStyleBase { +mixin CSSOverflowMixin on RenderStyle { + @override + CSSOverflowType get overflowX => _overflowX ?? CSSOverflowType.visible; CSSOverflowType? _overflowX; - CSSOverflowType get overflowX { - return _overflowX ?? CSSOverflowType.visible; - } set overflowX(CSSOverflowType value) { if (_overflowX == value) return; _overflowX = value; } + @override + CSSOverflowType get overflowY => _overflowY ?? CSSOverflowType.visible; CSSOverflowType? _overflowY; - CSSOverflowType get overflowY { - return _overflowY ?? CSSOverflowType.visible; - } set overflowY(CSSOverflowType value) { if (_overflowY == value) return; _overflowY = value; } + @override CSSOverflowType get effectiveOverflowX { if (overflowX == CSSOverflowType.visible && overflowY != CSSOverflowType.visible) { return CSSOverflowType.auto; @@ -74,6 +73,7 @@ mixin CSSOverflowMixin on RenderStyleBase { return overflowX; } + @override CSSOverflowType get effectiveOverflowY { if (overflowY == CSSOverflowType.visible && overflowX != CSSOverflowType.visible) { return CSSOverflowType.auto; @@ -217,8 +217,11 @@ mixin ElementOverflowMixin on ElementBase { } void scrollingContentBoxStyleListener(String property, String? original, String present) { - RenderLayoutBox scrollingContentBox = (renderBoxModel as RenderLayoutBox).renderScrollingContent!; - RenderStyle scrollingContentRenderStyle = scrollingContentBox.renderStyle; + RenderLayoutBox? scrollingContentBox = (renderBoxModel as RenderLayoutBox).renderScrollingContent; + // Sliver content has no multi scroll content box. + if (scrollingContentBox == null) return; + + CSSRenderStyle scrollingContentRenderStyle = scrollingContentBox.renderStyle; switch (property) { case DISPLAY: diff --git a/kraken/lib/src/css/padding.dart b/kraken/lib/src/css/padding.dart index 7d5ea4df7b..a9a6c2fe67 100644 --- a/kraken/lib/src/css/padding.dart +++ b/kraken/lib/src/css/padding.dart @@ -9,11 +9,12 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; import 'package:kraken/rendering.dart'; -mixin CSSPaddingMixin on RenderStyleBase { +mixin CSSPaddingMixin on RenderStyle { /// The amount to pad the child in each dimension. /// /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] /// must not be null. + @override EdgeInsets get padding { EdgeInsets insets = EdgeInsets.only( left: paddingLeft.computedValue, @@ -31,6 +32,8 @@ mixin CSSPaddingMixin on RenderStyleBase { _paddingLeft = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get paddingLeft => _paddingLeft ?? CSSLengthValue.zero; CSSLengthValue? _paddingRight; @@ -39,6 +42,8 @@ mixin CSSPaddingMixin on RenderStyleBase { _paddingRight = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get paddingRight => _paddingRight ?? CSSLengthValue.zero; CSSLengthValue? _paddingBottom; @@ -47,6 +52,8 @@ mixin CSSPaddingMixin on RenderStyleBase { _paddingBottom = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get paddingBottom => _paddingBottom ?? CSSLengthValue.zero; CSSLengthValue? _paddingTop; @@ -55,6 +62,8 @@ mixin CSSPaddingMixin on RenderStyleBase { _paddingTop = value; _markSelfAndParentNeedsLayout(); } + + @override CSSLengthValue get paddingTop => _paddingTop ?? CSSLengthValue.zero; void _markSelfAndParentNeedsLayout() { diff --git a/kraken/lib/src/css/position.dart b/kraken/lib/src/css/position.dart index 0a5c153cd5..bf91a899f7 100644 --- a/kraken/lib/src/css/position.dart +++ b/kraken/lib/src/css/position.dart @@ -3,6 +3,8 @@ * Author: Kraken Team. */ +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; import 'package:kraken/rendering.dart'; @@ -14,7 +16,7 @@ enum CSSPositionType { sticky, } -mixin CSSPositionMixin on RenderStyleBase { +mixin CSSPositionMixin on RenderStyle { static const CSSPositionType DEFAULT_POSITION_TYPE = CSSPositionType.static; @@ -28,10 +30,9 @@ mixin CSSPositionMixin on RenderStyleBase { // Computed value: the keyword auto or a computed value // Canonical order: per grammar // Animation type: by computed value type + @override + CSSLengthValue get top => _top ?? CSSLengthValue.auto; CSSLengthValue? _top; - CSSLengthValue get top { - return _top ?? CSSLengthValue.auto; - } set top(CSSLengthValue? value) { if (_top == value) { return; @@ -40,10 +41,9 @@ mixin CSSPositionMixin on RenderStyleBase { _markParentNeedsLayout(); } + @override + CSSLengthValue get bottom => _bottom ?? CSSLengthValue.auto; CSSLengthValue? _bottom; - CSSLengthValue get bottom { - return _bottom ?? CSSLengthValue.auto; - } set bottom(CSSLengthValue? value) { if (_bottom == value) { return; @@ -52,10 +52,9 @@ mixin CSSPositionMixin on RenderStyleBase { _markParentNeedsLayout(); } + @override + CSSLengthValue get left => _left ?? CSSLengthValue.auto; CSSLengthValue? _left; - CSSLengthValue get left { - return _left ?? CSSLengthValue.auto; - } set left(CSSLengthValue? value) { if (_left == value) { return; @@ -64,10 +63,9 @@ mixin CSSPositionMixin on RenderStyleBase { _markParentNeedsLayout(); } + @override + CSSLengthValue get right => _right ?? CSSLengthValue.auto; CSSLengthValue? _right; - CSSLengthValue get right { - return _right ?? CSSLengthValue.auto; - } set right(CSSLengthValue? value) { if (_right == value) { return; @@ -78,9 +76,10 @@ mixin CSSPositionMixin on RenderStyleBase { // The z-index property specifies the stack order of an element. // Only works on positioned elements(position: absolute/relative/fixed). int? _zIndex; - int? get zIndex { - return _zIndex; - } + + @override + int? get zIndex => _zIndex; + set zIndex(int? value) { if (_zIndex == value) return; _zIndex = value; @@ -89,24 +88,30 @@ mixin CSSPositionMixin on RenderStyleBase { } CSSPositionType _position = DEFAULT_POSITION_TYPE; - CSSPositionType get position { - return _position; - } + + @override + CSSPositionType get position => _position; + set position(CSSPositionType value) { if (_position == value) return; _position = value; - + // Position effect the stacking context. _markNeedsSort(); _markParentNeedsLayout(); // Position change may affect transformed display // https://www.w3.org/TR/css-display-3/#transformations + + // The position changes of the node may affect the whitespace of the nextSibling and previousSibling text node so prev and next node require layout. + renderBoxModel?.markAdjacentRenderParagraphNeedsLayout(); } void _markNeedsSort() { - if (renderBoxModel!.parentData is RenderLayoutParentData) { - RenderLayoutBox parent = renderBoxModel!.parent as RenderLayoutBox; - parent.markChildrenNeedsSort(); + if (renderBoxModel?.parentData is RenderLayoutParentData) { + AbstractNode? parent = renderBoxModel!.parent; + if (parent is RenderLayoutBox) { + parent.markChildrenNeedsSort(); + } } } @@ -114,11 +119,13 @@ mixin CSSPositionMixin on RenderStyleBase { // Should mark positioned element's containing block needs layout directly // cause RelayoutBoundary of positioned element will prevent the needsLayout flag // to bubble up in the RenderObject tree. - if (renderBoxModel!.parentData is RenderLayoutParentData) { + if (renderBoxModel?.parentData is RenderLayoutParentData) { RenderStyle renderStyle = renderBoxModel!.renderStyle; if (renderStyle.position != DEFAULT_POSITION_TYPE) { - RenderBoxModel parent = renderBoxModel!.parent as RenderBoxModel; - parent.markNeedsLayout(); + AbstractNode? parent = renderBoxModel!.parent; + if (parent is RenderObject) { + parent.markNeedsLayout(); + } } } } @@ -134,8 +141,11 @@ mixin CSSPositionMixin on RenderStyleBase { if (renderStyle.position != DEFAULT_POSITION_TYPE || parentRenderStyle?.effectiveDisplay == CSSDisplay.flex || parentRenderStyle?.effectiveDisplay == CSSDisplay.inlineFlex) { - RenderBoxModel parent = renderBoxModel!.parent as RenderBoxModel; - parent.markNeedsPaint(); + + AbstractNode? parent = renderBoxModel!.parent; + if (parent is RenderObject) { + parent.markNeedsPaint(); + } } } } @@ -154,5 +164,4 @@ mixin CSSPositionMixin on RenderStyleBase { return CSSPositionType.static; } } - } diff --git a/kraken/lib/src/css/positioned.dart b/kraken/lib/src/css/positioned.dart index 3932227c3c..1ebf0179ea 100644 --- a/kraken/lib/src/css/positioned.dart +++ b/kraken/lib/src/css/positioned.dart @@ -11,39 +11,27 @@ import 'package:kraken/rendering.dart'; // CSS Positioned Layout: https://drafts.csswg.org/css-position/ -BoxSizeType? _getChildWidthSizeType(RenderBox child) { - if (child is RenderTextBox) { - return child.widthSizeType; - } else if (child is RenderBoxModel) { - return child.widthSizeType; - } - return null; -} - -BoxSizeType? _getChildHeightSizeType(RenderBox child) { - if (child is RenderTextBox) { - return child.heightSizeType; - } else if (child is RenderBoxModel) { - return child.heightSizeType; - } - return null; -} - // RenderPositionHolder may be affected by overflow: scroller offset. // We need to reset these offset to keep positioned elements render at their original position. +// @NOTE: Attention that renderObjects in tree may not all subtype of RenderBoxModel, use `is` to identify. Offset? _getRenderPositionHolderScrollOffset(RenderPositionPlaceholder holder, RenderObject root) { - RenderBoxModel? parent = holder.parent as RenderBoxModel?; - while (parent != null && parent != root) { - if (parent.clipX || parent.clipY) { - return Offset(parent.scrollLeft, parent.scrollTop); + AbstractNode? current = holder.parent; + while (current != null && current != root) { + if (current is RenderBoxModel) { + if (current.clipX || current.clipY) { + return Offset(current.scrollLeft, current.scrollTop); + } } - parent = parent.parent as RenderBoxModel?; + current = current.parent; } return null; } // Get the offset of the RenderPlaceholder of positioned element to its parent RenderBoxModel. Offset _getPlaceholderToParentOffset(RenderPositionPlaceholder placeholder, RenderBoxModel parent) { + if (!placeholder.attached) { + return Offset.zero; + } Offset positionHolderScrollOffset = _getRenderPositionHolderScrollOffset(placeholder, parent) ?? Offset.zero; Offset placeholderOffset = placeholder.localToGlobal(positionHolderScrollOffset, ancestor: parent); return placeholderOffset; @@ -332,73 +320,6 @@ class CSSPositionedLayout { ) { BoxConstraints childConstraints = child.getConstraints(); - // Scrolling element has two repaint boundary box, positioned element is positioned - // relative to the outer renderBox. - RenderBoxModel containerBox = parent.isScrollingContentBox ? parent.parent as RenderBoxModel : parent; - Size trySize = containerBox.constraints.biggest; - Size parentSize = trySize.isInfinite ? containerBox.constraints.smallest : trySize; - - // Positioned element's size stretch start at the padding-box of its parent in cases like - // `height: 0; top: 0; bottom: 0`. - double borderLeft = parent.renderStyle.effectiveBorderLeftWidth.computedValue; - double borderRight = parent.renderStyle.effectiveBorderRightWidth.computedValue; - double borderTop = parent.renderStyle.effectiveBorderTopWidth.computedValue; - double borderBottom = parent.renderStyle.effectiveBorderBottomWidth.computedValue; - Size parentPaddingBoxSize = Size( - parentSize.width - borderLeft - borderRight, - parentSize.height - borderTop - borderBottom, - ); - - BoxSizeType? widthType = _getChildWidthSizeType(child); - BoxSizeType? heightType = _getChildHeightSizeType(child); - RenderStyle childRenderStyle = child.renderStyle; - - // If child has no width, calculate width by left and right. - // Element with intrinsic size such as image will not stretch - if (childRenderStyle.width.isAuto && - widthType != BoxSizeType.intrinsic && - childRenderStyle.left.isNotAuto && - childRenderStyle.right.isNotAuto) { - double childMarginLeft = childRenderStyle.marginLeft.computedValue; - double childMarginRight = childRenderStyle.marginRight.computedValue; - // Child width calculation should subtract its horizontal margin. - double constraintWidth = parentPaddingBoxSize.width - - childRenderStyle.left.computedValue - childRenderStyle.right.computedValue - - childMarginLeft - childMarginRight; - double? maxWidth = childRenderStyle.maxWidth.isNone ? null : childRenderStyle.maxWidth.computedValue; - double? minWidth = childRenderStyle.minWidth.isAuto ? null : childRenderStyle.minWidth.computedValue; - // Constrain to min-width or max-width if width not exists - if (maxWidth != null) { - constraintWidth = constraintWidth > maxWidth ? maxWidth : constraintWidth; - } else if (minWidth != null) { - constraintWidth = constraintWidth < minWidth ? minWidth : constraintWidth; - } - childConstraints = childConstraints.tighten(width: constraintWidth); - } - // If child has not height, should be calculate height by top and bottom - if (childRenderStyle.height.isAuto && - heightType != BoxSizeType.intrinsic && - childRenderStyle.top.isNotAuto && - childRenderStyle.bottom.isNotAuto) { - double childMarginTop = childRenderStyle.marginTop.computedValue; - double childMarginBottom = childRenderStyle.marginBottom.computedValue; - // Child height calculation should subtract its vertical margin. - double constraintHeight = parentPaddingBoxSize.height - - childRenderStyle.top.computedValue - childRenderStyle.bottom.computedValue - - childMarginTop - childMarginBottom; - CSSLengthValue maxHeightLength = childRenderStyle.maxHeight; - CSSLengthValue minHeightLength = childRenderStyle.minHeight; - // Constrain to min-height or max-height if width not exists - if (maxHeightLength.isNotNone) { - double maxHeight = maxHeightLength.computedValue; - constraintHeight = constraintHeight > maxHeight ? maxHeight : constraintHeight; - } else if (minHeightLength.isNotAuto) { - double minHeight = minHeightLength.computedValue; - constraintHeight = constraintHeight < minHeight ? minHeight : constraintHeight; - } - childConstraints = childConstraints.tighten(height: constraintHeight); - } - // Whether child need to layout bool isChildNeedsLayout = true; if (child.hasSize && diff --git a/kraken/lib/src/css/render_style.dart b/kraken/lib/src/css/render_style.dart index 05f146f28c..b65cc14659 100644 --- a/kraken/lib/src/css/render_style.dart +++ b/kraken/lib/src/css/render_style.dart @@ -11,19 +11,183 @@ import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/rendering.dart'; -mixin RenderStyleBase { +typedef RenderStyleVisitor = void Function(T renderStyle); + +/// The abstract class for render-style, declare the +/// getter interface for all available CSS rule. +abstract class RenderStyle { + // Common + Element get target; + RenderStyle? get parent; + dynamic getProperty(String key); + /// Resolve the style value. + dynamic resolveValue(String property, String present); + + // CSSVariable + String? getCSSVariable(String identifier, String propertyName); + void setCSSVariable(String identifier, String value); + + // Geometry + CSSLengthValue get top; + CSSLengthValue get right; + CSSLengthValue get bottom; + CSSLengthValue get left; + int? get zIndex; + CSSLengthValue get width; + CSSLengthValue get height; + CSSLengthValue get minWidth; + CSSLengthValue get minHeight; + CSSLengthValue get maxWidth; + CSSLengthValue get maxHeight; + EdgeInsets get margin; + CSSLengthValue get marginLeft; + CSSLengthValue get marginRight; + CSSLengthValue get marginTop; + CSSLengthValue get marginBottom; + EdgeInsets get padding; + CSSLengthValue get paddingLeft; + CSSLengthValue get paddingRight; + CSSLengthValue get paddingBottom; + CSSLengthValue get paddingTop; + + // Border + EdgeInsets get border; + CSSLengthValue? get borderTopWidth; + CSSLengthValue? get borderRightWidth; + CSSLengthValue? get borderBottomWidth; + CSSLengthValue? get borderLeftWidth; + BorderStyle get borderLeftStyle; + BorderStyle get borderRightStyle; + BorderStyle get borderTopStyle; + BorderStyle get borderBottomStyle; + CSSLengthValue get effectiveBorderLeftWidth; + CSSLengthValue get effectiveBorderRightWidth; + CSSLengthValue get effectiveBorderTopWidth; + CSSLengthValue get effectiveBorderBottomWidth; + double get contentMaxConstraintsWidth; + Color get borderLeftColor; + Color get borderRightColor; + Color get borderTopColor; + Color get borderBottomColor; + List? get borderRadius; + CSSBorderRadius get borderTopLeftRadius; + CSSBorderRadius get borderTopRightRadius; + CSSBorderRadius get borderBottomRightRadius; + CSSBorderRadius get borderBottomLeftRadius; + List? get borderSides; + List? get shadows; + + // Decorations + Color? get backgroundColor; + CSSBackgroundImage? get backgroundImage; + ImageRepeat get backgroundRepeat; + CSSBackgroundPosition get backgroundPositionX; + CSSBackgroundPosition get backgroundPositionY; + + // Text + CSSLengthValue get fontSize; + FontWeight get fontWeight; + FontStyle get fontStyle; + List? get fontFamily; + List? get textShadow; + WhiteSpace get whiteSpace; + TextOverflow get textOverflow; + TextAlign get textAlign; + int? get lineClamp; + CSSLengthValue get lineHeight; + CSSLengthValue? get letterSpacing; + CSSLengthValue? get wordSpacing; + + // BoxModel + double? get borderBoxLogicalWidth; + double? get borderBoxLogicalHeight; + double? get borderBoxConstraintsWidth; + double? get borderBoxConstraintsHeight; + double? get borderBoxWidth; + double? get borderBoxHeight; + double? get paddingBoxLogicalWidth; + double? get paddingBoxLogicalHeight; + double? get paddingBoxConstraintsWidth; + double? get paddingBoxConstraintsHeight; + double? get paddingBoxWidth; + double? get paddingBoxHeight; + double? get contentBoxLogicalWidth; + double? get contentBoxLogicalHeight; + double? get contentBoxConstraintsWidth; + double? get contentBoxConstraintsHeight; + double? get contentBoxWidth; + double? get contentBoxHeight; + CSSPositionType get position; + CSSDisplay get display; + CSSDisplay get effectiveDisplay; + Alignment get objectPosition; + CSSOverflowType get overflowX; + CSSOverflowType get overflowY; + CSSOverflowType get effectiveOverflowX; + CSSOverflowType get effectiveOverflowY; + double? get intrinsicRatio; + double? get intrinsicWidth; + double? get intrinsicHeight; + + // Flex + FlexDirection get flexDirection; + FlexWrap get flexWrap; + JustifyContent get justifyContent; + AlignItems get alignItems; + AlignItems get effectiveAlignItems; + AlignContent get alignContent; + AlignSelf get alignSelf; + CSSLengthValue? get flexBasis; + double get flexGrow; + double get flexShrink; + + // Color + Color get color; + Color get currentColor; + + // Filter + ColorFilter? get colorFilter; + ImageFilter? get imageFilter; + List? get filter; + + // Misc + double get opacity; + Visibility get visibility; + ContentVisibility? get contentVisibility; + VerticalAlign get verticalAlign; + BoxFit get objectFit; + bool get isHeightStretch; + + // Transition + List get transitionProperty; + List get transitionDuration; + List get transitionTimingFunction; + List get transitionDelay; + + // Sliver + Axis get sliverDirection; + + void addFontRelativeProperty(String propertyName); + void addRootFontRelativeProperty(String propertyName); + void addColorRelativeProperty(String propertyName); + String? removeAnimationProperty(String propertyName); + double getWidthByIntrinsicRatio(); + double getHeightByIntrinsicRatio(); + // Following properties used for exposing APIs - // for class that extends [RenderStyleBase]. - late Element target; + // for class that extends [AbstractRenderStyle]. RenderBoxModel? get renderBoxModel => target.renderBoxModel; - Size get viewportSize => target.elementManager.viewport.viewportSize; - double get rootFontSize => target.elementManager.getRootFontSize(); - Color get currentColor => (this as RenderStyle).color; + + Size get viewportSize => target.ownerDocument.viewport.viewportSize; + + double get rootFontSize => target.ownerDocument.documentElement!.renderStyle.fontSize.computedValue; + + void visitChildren(RenderStyleVisitor visitor); } -class RenderStyle +class CSSRenderStyle + extends RenderStyle with - RenderStyleBase, CSSSizingMixin, CSSPaddingMixin, CSSBorderMixin, @@ -46,258 +210,444 @@ class RenderStyle CSSOverflowMixin, CSSFilterEffectsMixin, CSSOpacityMixin, - CSSTransitionMixin { + CSSTransitionMixin, + CSSVariableMixin { + CSSRenderStyle({ required this.target }); @override Element target; - RenderStyle? parent; - - RenderStyle({ - required this.target, - }); + @override + CSSRenderStyle? parent; - dynamic getProperty(String name) { - RenderStyle renderStyle = this; + @override + getProperty(String name) { switch (name) { case DISPLAY: - return renderStyle.display; + return display; case Z_INDEX: - return renderStyle.zIndex; + return zIndex; case OVERFLOW_X: - return renderStyle.overflowX; + return overflowX; case OVERFLOW_Y: - return renderStyle.overflowY; + return overflowY; case OPACITY: - return renderStyle.opacity; + return opacity; case VISIBILITY: - return renderStyle.visibility; + return visibility; case CONTENT_VISIBILITY: - return renderStyle.contentVisibility; + return contentVisibility; case POSITION: - return renderStyle.position; + return position; case TOP: - return renderStyle.top; + return top; case LEFT: - return renderStyle.left; + return left; case BOTTOM: - return renderStyle.bottom; + return bottom; case RIGHT: - return renderStyle.right; + return right; // Size case WIDTH: - return renderStyle.width; + return width; case MIN_WIDTH: - return renderStyle.minWidth; + return minWidth; case MAX_WIDTH: - return renderStyle.maxWidth; + return maxWidth; case HEIGHT: - return renderStyle.height; + return height; case MIN_HEIGHT: - return renderStyle.minHeight; + return minHeight; case MAX_HEIGHT: - return renderStyle.maxHeight; + return maxHeight; // Flex case FLEX_DIRECTION: - return renderStyle.flexDirection; + return flexDirection; case FLEX_WRAP: - return renderStyle.flexWrap; + return flexWrap; case ALIGN_CONTENT: - return renderStyle.alignContent; + return alignContent; case ALIGN_ITEMS: - return renderStyle.alignItems; + return alignItems; case JUSTIFY_CONTENT: - return renderStyle.justifyContent; + return justifyContent; case ALIGN_SELF: - return renderStyle.alignSelf; + return alignSelf; case FLEX_GROW: - return renderStyle.flexGrow; + return flexGrow; case FLEX_SHRINK: - return renderStyle.flexShrink; + return flexShrink; case FLEX_BASIS: - return renderStyle.flexBasis; + return flexBasis; // Background case BACKGROUND_COLOR: - return renderStyle.backgroundColor; + return backgroundColor; case BACKGROUND_ATTACHMENT: - return renderStyle.backgroundAttachment; + return backgroundAttachment; case BACKGROUND_IMAGE: - return renderStyle.backgroundImage; + return backgroundImage; case BACKGROUND_REPEAT: - return renderStyle.backgroundRepeat; + return backgroundRepeat; case BACKGROUND_POSITION_X: - return renderStyle.backgroundPositionX; + return backgroundPositionX; case BACKGROUND_POSITION_Y: - return renderStyle.backgroundPositionY; + return backgroundPositionY; case BACKGROUND_SIZE: - return renderStyle.backgroundSize; + return backgroundSize; case BACKGROUND_CLIP: - return renderStyle.backgroundClip; + return backgroundClip; case BACKGROUND_ORIGIN: - return renderStyle.backgroundOrigin; + return backgroundOrigin; // Padding case PADDING_TOP: - return renderStyle.paddingTop; + return paddingTop; case PADDING_RIGHT: - return renderStyle.paddingRight; + return paddingRight; case PADDING_BOTTOM: - return renderStyle.paddingBottom; + return paddingBottom; case PADDING_LEFT: - return renderStyle.paddingLeft; + return paddingLeft; // Border case BORDER_LEFT_WIDTH: - return renderStyle.borderLeftWidth; + return borderLeftWidth; case BORDER_TOP_WIDTH: - return renderStyle.borderTopWidth; + return borderTopWidth; case BORDER_RIGHT_WIDTH: - return renderStyle.borderRightWidth; + return borderRightWidth; case BORDER_BOTTOM_WIDTH: - return renderStyle.borderBottomWidth; + return borderBottomWidth; case BORDER_LEFT_STYLE: - return renderStyle.borderLeftStyle; + return borderLeftStyle; case BORDER_TOP_STYLE: - return renderStyle.borderTopStyle; + return borderTopStyle; case BORDER_RIGHT_STYLE: - return renderStyle.borderRightStyle; + return borderRightStyle; case BORDER_BOTTOM_STYLE: - return renderStyle.borderBottomStyle; + return borderBottomStyle; case BORDER_LEFT_COLOR: - return renderStyle.borderLeftColor; + return borderLeftColor; case BORDER_TOP_COLOR: - return renderStyle.borderTopColor; + return borderTopColor; case BORDER_RIGHT_COLOR: - return renderStyle.borderRightColor; + return borderRightColor; case BORDER_BOTTOM_COLOR: - return renderStyle.borderBottomColor; + return borderBottomColor; case BOX_SHADOW: - return renderStyle.boxShadow; + return boxShadow; case BORDER_TOP_LEFT_RADIUS: - return renderStyle.borderTopLeftRadius; + return borderTopLeftRadius; case BORDER_TOP_RIGHT_RADIUS: - return renderStyle.borderTopRightRadius; + return borderTopRightRadius; case BORDER_BOTTOM_LEFT_RADIUS: - return renderStyle.borderBottomLeftRadius; + return borderBottomLeftRadius; case BORDER_BOTTOM_RIGHT_RADIUS: - return renderStyle.borderBottomRightRadius; + return borderBottomRightRadius; // Margin case MARGIN_LEFT: - return renderStyle.marginLeft; + return marginLeft; case MARGIN_TOP: - return renderStyle.marginTop; + return marginTop; case MARGIN_RIGHT: - return renderStyle.marginRight; + return marginRight; case MARGIN_BOTTOM: - return renderStyle.marginBottom; + return marginBottom; // Text case COLOR: - return renderStyle.color; + return color; case TEXT_DECORATION_LINE: - return renderStyle.textDecorationLine; + return textDecorationLine; case TEXT_DECORATION_STYLE: - return renderStyle.textDecorationStyle; + return textDecorationStyle; case TEXT_DECORATION_COLOR: - return renderStyle.textDecorationColor; + return textDecorationColor; case FONT_WEIGHT: - return renderStyle.fontWeight; + return fontWeight; case FONT_STYLE: - return renderStyle.fontStyle; + return fontStyle; case FONT_FAMILY: - return renderStyle.fontFamily; + return fontFamily; case FONT_SIZE: - return renderStyle.fontSize; + return fontSize; case LINE_HEIGHT: - return renderStyle.lineHeight; + return lineHeight; case LETTER_SPACING: - return renderStyle.letterSpacing; + return letterSpacing; case WORD_SPACING: - return renderStyle.wordSpacing; + return wordSpacing; case TEXT_SHADOW: - return renderStyle.textShadow; + return textShadow; case WHITE_SPACE: - return renderStyle.whiteSpace; + return whiteSpace; case TEXT_OVERFLOW: - return renderStyle.textOverflow; + return textOverflow; case LINE_CLAMP: - return renderStyle.lineClamp; + return lineClamp; case VERTICAL_ALIGN: - return renderStyle.verticalAlign; + return verticalAlign; case TEXT_ALIGN: - return renderStyle.textAlign; + return textAlign; // Transform case TRANSFORM: - return renderStyle.transform; + return transform; case TRANSFORM_ORIGIN: - return renderStyle.transformOrigin; + return transformOrigin; case SLIVER_DIRECTION: - return renderStyle.sliverDirection; + return sliverDirection; case OBJECT_FIT: - return renderStyle.objectFit; + return objectFit; case OBJECT_POSITION: - return renderStyle.objectPosition; + return objectPosition; case FILTER: - return renderStyle.filter; + return filter; } } - // Content width of render box model calculated from style. - double? getLogicalContentWidth() { + @override + dynamic resolveValue(String propertyName, String propertyValue) { RenderStyle renderStyle = this; - double? intrinsicRatio = renderBoxModel!.intrinsicRatio; - CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay; - double? width = renderStyle.width.isAuto ? null : renderStyle.width.computedValue; - double? minWidth = renderStyle.minWidth.isAuto ? null : renderStyle.minWidth.computedValue; - double? maxWidth = renderStyle.maxWidth.isNone ? null : renderStyle.maxWidth.computedValue; - double cropWidth = 0; + // Process CSSVariable. + dynamic value = CSSVariable.tryParse(renderStyle, propertyName, propertyValue); + if (value != null) { + return value; + } + + switch (propertyName) { + case DISPLAY: + value = CSSDisplayMixin.resolveDisplay(propertyValue); + break; + case OVERFLOW_X: + case OVERFLOW_Y: + value = CSSOverflowMixin.resolveOverflowType(propertyValue); + break; + case POSITION: + value = CSSPositionMixin.resolvePositionType(propertyValue); + break; + case Z_INDEX: + value = int.tryParse(propertyValue); + break; + case TOP: + case LEFT: + case BOTTOM: + case RIGHT: + case FLEX_BASIS: + case PADDING_TOP: + case PADDING_RIGHT: + case PADDING_BOTTOM: + case PADDING_LEFT: + case WIDTH: + case MIN_WIDTH: + case MAX_WIDTH: + case HEIGHT: + case MIN_HEIGHT: + case MAX_HEIGHT: + case MARGIN_LEFT: + case MARGIN_TOP: + case MARGIN_RIGHT: + case MARGIN_BOTTOM: + case FONT_SIZE: + value = CSSLength.resolveLength(propertyValue, renderStyle, propertyName); + break; + case FLEX_DIRECTION: + value = CSSFlexboxMixin.resolveFlexDirection(propertyValue); + break; + case FLEX_WRAP: + value = CSSFlexboxMixin.resolveFlexWrap(propertyValue); + break; + case ALIGN_CONTENT: + value = CSSFlexboxMixin.resolveAlignContent(propertyValue); + break; + case ALIGN_ITEMS: + value = CSSFlexboxMixin.resolveAlignItems(propertyValue); + break; + case JUSTIFY_CONTENT: + value = CSSFlexboxMixin.resolveJustifyContent(propertyValue); + break; + case ALIGN_SELF: + value = CSSFlexboxMixin.resolveAlignSelf(propertyValue); + break; + case FLEX_GROW: + value = CSSFlexboxMixin.resolveFlexGrow(propertyValue); + break; + case FLEX_SHRINK: + value = CSSFlexboxMixin.resolveFlexShrink(propertyValue); + break; + case SLIVER_DIRECTION: + value = CSSSliverMixin.resolveAxis(propertyValue); + break; + case TEXT_ALIGN: + value = CSSTextMixin.resolveTextAlign(propertyValue); + break; + case BACKGROUND_ATTACHMENT: + value = CSSBackground.resolveBackgroundAttachment(propertyValue); + break; + case BACKGROUND_IMAGE: + value = CSSBackground.resolveBackgroundImage(propertyValue, renderStyle, propertyName, renderStyle.target.ownerDocument.controller); + break; + case BACKGROUND_REPEAT: + value = CSSBackground.resolveBackgroundRepeat(propertyValue); + break; + case BACKGROUND_POSITION_X: + value = CSSPosition.resolveBackgroundPosition(propertyValue, renderStyle, propertyName, true); + break; + case BACKGROUND_POSITION_Y: + value = CSSPosition.resolveBackgroundPosition(propertyValue, renderStyle, propertyName, false); + break; + case BACKGROUND_SIZE: + value = CSSBackground.resolveBackgroundSize(propertyValue, renderStyle, propertyName); + break; + case BACKGROUND_CLIP: + value = CSSBackground.resolveBackgroundClip(propertyValue); + break; + case BACKGROUND_ORIGIN: + value = CSSBackground.resolveBackgroundOrigin(propertyValue); + break; + case BORDER_LEFT_WIDTH: + case BORDER_TOP_WIDTH: + case BORDER_RIGHT_WIDTH: + case BORDER_BOTTOM_WIDTH: + value = CSSBorderSide.resolveBorderWidth(propertyValue, renderStyle, propertyName); + break; + case BORDER_LEFT_STYLE: + case BORDER_TOP_STYLE: + case BORDER_RIGHT_STYLE: + case BORDER_BOTTOM_STYLE: + value = CSSBorderSide.resolveBorderStyle(propertyValue); + break; + case COLOR: + case BACKGROUND_COLOR: + case TEXT_DECORATION_COLOR: + case BORDER_LEFT_COLOR: + case BORDER_TOP_COLOR: + case BORDER_RIGHT_COLOR: + case BORDER_BOTTOM_COLOR: + value = CSSColor.resolveColor(propertyValue, renderStyle, propertyName); + break; + case BOX_SHADOW: + value = CSSBoxShadow.parseBoxShadow(propertyValue, renderStyle, propertyName); + break; + case BORDER_TOP_LEFT_RADIUS: + case BORDER_TOP_RIGHT_RADIUS: + case BORDER_BOTTOM_LEFT_RADIUS: + case BORDER_BOTTOM_RIGHT_RADIUS: + value = CSSBorderRadius.parseBorderRadius(propertyValue, renderStyle, propertyName); + break; + case OPACITY: + value = CSSOpacityMixin.resolveOpacity(propertyValue); + break; + case VISIBILITY: + value = CSSVisibilityMixin.resolveVisibility(propertyValue); + break; + case CONTENT_VISIBILITY: + value = CSSContentVisibilityMixin.resolveContentVisibility(propertyValue); + break; + case TRANSFORM: + value = CSSTransformMixin.resolveTransform(propertyValue); + break; + case FILTER: + value = CSSFunction.parseFunction(propertyValue); + break; + case TRANSFORM_ORIGIN: + value = CSSOrigin.parseOrigin(propertyValue, renderStyle, propertyName); + break; + case OBJECT_FIT: + value = CSSObjectFitMixin.resolveBoxFit(propertyValue); + break; + case OBJECT_POSITION: + value = CSSObjectPositionMixin.resolveObjectPosition(propertyValue); + break; + case TEXT_DECORATION_LINE: + value = CSSText.resolveTextDecorationLine(propertyValue); + break; + case TEXT_DECORATION_STYLE: + value = CSSText.resolveTextDecorationStyle(propertyValue); + break; + case FONT_WEIGHT: + value = CSSText.resolveFontWeight(propertyValue); + break; + case FONT_STYLE: + value = CSSText.resolveFontStyle(propertyValue); + break; + case FONT_FAMILY: + value = CSSText.resolveFontFamilyFallback(propertyValue); + break; + case LINE_HEIGHT: + value = CSSText.resolveLineHeight(propertyValue, renderStyle, propertyName); + break; + case LETTER_SPACING: + value = CSSText.resolveSpacing(propertyValue, renderStyle, propertyName); + break; + case WORD_SPACING: + value = CSSText.resolveSpacing(propertyValue, renderStyle, propertyName); + break; + case TEXT_SHADOW: + value = CSSText.resolveTextShadow(propertyValue, renderStyle, propertyName); + break; + case WHITE_SPACE: + value = CSSText.resolveWhiteSpace(propertyValue); + break; + case TEXT_OVERFLOW: + // Overflow will affect text-overflow ellipsis taking effect + value = CSSText.resolveTextOverflow(propertyValue); + break; + case LINE_CLAMP: + value = CSSText.parseLineClamp(propertyValue); + break; + case VERTICAL_ALIGN: + value = CSSInlineMixin.resolveVerticalAlign(propertyValue); + break; + // Transition + case TRANSITION_DELAY: + case TRANSITION_DURATION: + case TRANSITION_TIMING_FUNCTION: + case TRANSITION_PROPERTY: + value = CSSStyleProperty.getMultipleValues(propertyValue); + break; + } + + // --x: foo; + // Directly passing the value, not to resolve now. + if (CSSVariable.isVariable(propertyName)) { + return propertyValue; + } + + return value; + } + + + // Compute the content box width from render style. + void computeContentBoxLogicalWidth() { + RenderBoxModel current = renderBoxModel!; + RenderStyle renderStyle = this; + double? logicalWidth; + + CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay; switch (effectiveDisplay) { case CSSDisplay.block: case CSSDisplay.flex: case CSSDisplay.sliver: - // Get own width if exists else get the width of nearest ancestor width width - if (!renderStyle.width.isAuto) { - cropWidth = _getCropWidthByPaddingBorder(renderStyle, cropWidth); - } else { - // @TODO: flexbox stretch alignment will stretch replaced element in the cross axis - // Block level element will spread to its parent's width except for replaced element - if (renderBoxModel is! RenderIntrinsic) { - RenderStyle currentRenderStyle = renderStyle; - - while (true) { - RenderStyle? parentRenderStyle = renderStyle.parent; - - if (parentRenderStyle != null) { - cropWidth += currentRenderStyle.margin.horizontal; - cropWidth = _getCropWidthByPaddingBorder(currentRenderStyle, cropWidth); - parentRenderStyle = currentRenderStyle.parent; - } else { - break; - } + // Use width directly if defined. + if (renderStyle.width.isNotAuto) { + logicalWidth = renderStyle.width.computedValue; + } else if (renderStyle.parent != null) { + RenderStyle parentRenderStyle = renderStyle.parent!; + RenderBoxModel parent = parentRenderStyle.renderBoxModel!; - CSSDisplay? parentEffectiveDisplay = parentRenderStyle!.effectiveDisplay; - RenderBoxModel parentRenderBoxModel = parentRenderStyle.renderBoxModel!; - // Set width of element according to parent display - if (parentEffectiveDisplay != CSSDisplay.inline) { - // Skip to find upper parent - if (parentRenderStyle.width.isNotAuto) { - // Use style width - width = parentRenderStyle.width.computedValue; - cropWidth = _getCropWidthByPaddingBorder(parentRenderStyle, cropWidth); - break; - } else if (parentRenderBoxModel.hasSize && parentRenderBoxModel.constraints.hasTightWidth) { - // Cases like flex item with flex-grow and no width in flex row direction. - width = parentRenderBoxModel.constraints.maxWidth; - cropWidth = _getCropWidthByPaddingBorder(parentRenderStyle, cropWidth); - break; - } else if (parentEffectiveDisplay == CSSDisplay.inlineBlock || - parentEffectiveDisplay == CSSDisplay.inlineFlex || - parentEffectiveDisplay == CSSDisplay.sliver) { - // Collapse width to children - width = null; - break; - } - } + // Use parent's tight constraints if constraints is tight and width not exist. + if (parent.hasSize && parent.constraints.hasTightWidth) { + logicalWidth = parent.constraints.maxWidth; - currentRenderStyle = parentRenderStyle; + // Block element (except replaced element) will stretch to the content width of its parent in flow layout. + // Replaced element also stretch in flex layout if align-items is stretch. + } else if (current is! RenderIntrinsic || parent is RenderFlexLayout) { + RenderStyle? ancestorRenderStyle = _findAncestorWithNoDisplayInline(); + // Should ignore renderStyle of display inline when searching for ancestors to stretch width. + if (ancestorRenderStyle != null) { + logicalWidth = ancestorRenderStyle.contentBoxLogicalWidth; + // Should subtract horizontal margin of own from its parent content width. + if (logicalWidth != null) { + logicalWidth -= renderStyle.margin.horizontal; + } } } } @@ -305,179 +655,236 @@ class RenderStyle case CSSDisplay.inlineBlock: case CSSDisplay.inlineFlex: if (renderStyle.width.isNotAuto) { - width = renderStyle.width.computedValue; - cropWidth = _getCropWidthByPaddingBorder(renderStyle, cropWidth); - } else { - width = null; + logicalWidth = renderStyle.width.computedValue; + + // The width of positioned, non-replaced element is determined as following algorithm. + // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width + } else if ((renderStyle.position == CSSPositionType.absolute || + renderStyle.position == CSSPositionType.fixed) + && current is! RenderIntrinsic + && renderStyle.width.isAuto + && renderStyle.left.isNotAuto + && renderStyle.right.isNotAuto + ) { + if (current.parent is! RenderBoxModel) { + logicalWidth = null; + } + // Should access the renderStyle of renderBoxModel parent but not renderStyle parent + // cause the element of renderStyle parent may not equal to containing block. + RenderBoxModel parent = current.parent as RenderBoxModel; + // Get the renderStyle of outer scrolling box cause the renderStyle of scrolling + // content box is only a fraction of the complete renderStyle. + RenderStyle parentRenderStyle = parent.isScrollingContentBox + ? (parent.parent as RenderBoxModel).renderStyle + : parent.renderStyle; + // Width of positioned element should subtract its horizontal margin. + logicalWidth = (parentRenderStyle.paddingBoxLogicalWidth ?? 0) + - renderStyle.left.computedValue - renderStyle.right.computedValue + - renderStyle.marginLeft.computedValue - renderStyle.marginRight.computedValue; + + } else if (current.hasSize && current.constraints.hasTightWidth) { + logicalWidth = current.constraints.maxWidth; } break; case CSSDisplay.inline: - width = null; break; - default: + case CSSDisplay.none: break; } - // Get height by intrinsic ratio for replaced element if height is not defined - if (width == null && intrinsicRatio != null) { - width = renderStyle.getWidthByIntrinsicRatio() + cropWidth; + + // Get width by intrinsic ratio for replaced element if width is auto. + if (logicalWidth == null && intrinsicRatio != null) { + logicalWidth = renderStyle.getWidthByIntrinsicRatio(); } - if (minWidth != null) { - if (width != null && width < minWidth) { - width = minWidth; + // Constrain width by min-width and max-width. + if (renderStyle.minWidth.isNotAuto) { + double minWidth = renderStyle.minWidth.computedValue; + if (logicalWidth != null && logicalWidth < minWidth) { + logicalWidth = minWidth; } } - if (maxWidth != null) { - if (width != null && width > maxWidth) { - width = maxWidth; + if (renderStyle.maxWidth.isNotNone) { + double maxWidth = renderStyle.maxWidth.computedValue; + if (logicalWidth != null && logicalWidth > maxWidth) { + logicalWidth = maxWidth; } } - if (width != null) { - return math.max(0, width - cropWidth); - } else { - return null; + double? logicalContentWidth; + // Subtract padding and border width to get content width. + if (logicalWidth != null) { + logicalContentWidth = logicalWidth - + renderStyle.border.horizontal - + renderStyle.padding.horizontal; + // Logical width may be smaller than its border and padding width, + // in this case, content width will be negative which is illegal. + logicalContentWidth = math.max(0, logicalContentWidth); } + + _contentBoxLogicalWidth = logicalContentWidth; } - // Content height of render box model calculated from style. - double? getLogicalContentHeight() { + // Compute the content box height from render style. + void computeContentBoxLogicalHeight() { + RenderBoxModel current = renderBoxModel!; RenderStyle renderStyle = this; + double? logicalHeight; + CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay; - double? height = renderStyle.height.isAuto ? null : renderStyle.height.computedValue; - double cropHeight = 0; - double? maxHeight = renderStyle.maxHeight.isNone ? null : renderStyle.maxHeight.computedValue; - double? minHeight = renderStyle.minHeight.isAuto ? null : renderStyle.minHeight.computedValue; - double? intrinsicRatio = renderBoxModel!.intrinsicRatio; // Inline element has no height. - if (effectiveDisplay == CSSDisplay.inline) { - return null; - } else if (height != null) { - cropHeight = _getCropHeightByPaddingBorder(renderStyle, cropHeight); - } else { - RenderStyle currentRenderStyle = renderStyle; - - while (true) { - RenderStyle? parentRenderStyle = currentRenderStyle.parent; - - if (parentRenderStyle != null) { - cropHeight += currentRenderStyle.margin.vertical; - cropHeight = _getCropHeightByPaddingBorder(currentRenderStyle, cropHeight); - parentRenderStyle = currentRenderStyle.parent; - } else { - break; + if (effectiveDisplay != CSSDisplay.inline) { + if (renderStyle.height.isNotAuto) { + logicalHeight = renderStyle.height.computedValue; + + // The height of positioned, non-replaced element is determined as following algorithm. + // https://www.w3.org/TR/css-position-3/#abs-non-replaced-height + } else if ((renderStyle.position == CSSPositionType.absolute || + renderStyle.position == CSSPositionType.fixed) + && current is! RenderIntrinsic + && renderStyle.height.isAuto + && renderStyle.top.isNotAuto + && renderStyle.bottom.isNotAuto + ) { + if (current.parent is! RenderBoxModel) { + logicalHeight = null; } + // Should access the renderStyle of renderBoxModel parent but not renderStyle parent + // cause the element of renderStyle parent may not equal to containing block. + RenderBoxModel parent = current.parent as RenderBoxModel; + // Get the renderStyle of outer scrolling box cause the renderStyle of scrolling + // content box is only a fraction of the complete renderStyle. + RenderStyle parentRenderStyle = parent.isScrollingContentBox + ? (parent.parent as RenderBoxModel).renderStyle + : parent.renderStyle; + // Height of positioned element should subtract its vertical margin. + logicalHeight = (parentRenderStyle.paddingBoxLogicalHeight ?? 0) + - renderStyle.top.computedValue - renderStyle.bottom.computedValue + - renderStyle.marginTop.computedValue - renderStyle.marginBottom.computedValue; - RenderBoxModel parentRenderBoxModel = parentRenderStyle!.renderBoxModel!; - if (CSSSizingMixin.isStretchChildHeight(parentRenderStyle, currentRenderStyle)) { - if (parentRenderStyle.height.isNotAuto) { - height = parentRenderStyle.height.computedValue; - cropHeight = _getCropHeightByPaddingBorder(parentRenderStyle, cropHeight); - break; - } else if (parentRenderBoxModel.hasSize && parentRenderBoxModel.constraints.hasTightHeight) { - // Cases like flex item with flex-grow and no height in flex column direction. - height = parentRenderBoxModel.constraints.maxHeight; - cropHeight = _getCropHeightByPaddingBorder(parentRenderStyle, cropHeight); - break; + } else { + if (renderStyle.parent != null) { + RenderStyle parentRenderStyle = renderStyle.parent!; + RenderBoxModel parent = parentRenderStyle.renderBoxModel!; + + if (renderStyle.isHeightStretch) { + // Use parent's tight constraints if constraints is tight and height not exist. + if (parent.hasSize && parent.constraints.hasTightHeight) { + logicalHeight = parent.constraints.maxHeight; + } else { + logicalHeight = parentRenderStyle.contentBoxLogicalHeight; + // Should subtract vertical margin of own from its parent content height. + if (logicalHeight != null) { + logicalHeight -= renderStyle.margin.vertical; + } + } } - } else { - break; } - - currentRenderStyle = parentRenderStyle; } } - // Get height by intrinsic ratio for replaced element if height is not defined. - if (height == null && intrinsicRatio != null) { - height = renderStyle.getHeightByIntrinsicRatio() + cropHeight; + // Get height by intrinsic ratio for replaced element if height is auto. + if (logicalHeight == null && intrinsicRatio != null) { + logicalHeight = renderStyle.getHeightByIntrinsicRatio(); } - if (minHeight != null) { - if (height != null && height < minHeight) { - height = minHeight; + // Constrain height by min-height and max-height. + if (renderStyle.minHeight.isNotAuto) { + double minHeight = renderStyle.minHeight.computedValue; + if (logicalHeight != null && logicalHeight < minHeight) { + logicalHeight = minHeight; } } - if (maxHeight != null) { - if (height != null && height > maxHeight) { - height = maxHeight; + if (renderStyle.maxHeight.isNotNone) { + double maxHeight = renderStyle.maxHeight.computedValue; + if (logicalHeight != null && logicalHeight > maxHeight) { + logicalHeight = maxHeight; } } - if (height != null) { - return math.max(0, height - cropHeight); - } else { - return null; + double? logicalContentHeight; + // Subtract padding and border width to get content width. + if (logicalHeight != null) { + logicalContentHeight = logicalHeight - + renderStyle.border.vertical - + renderStyle.padding.vertical; + // Logical height may be smaller than its border and padding width, + // in this case, content height will be negative which is illegal. + logicalContentHeight = math.max(0, logicalContentHeight); } + + _contentBoxLogicalHeight = logicalContentHeight; } - // Max constraints width of content, used in calculating the remaining space for line wrapping - // in the stage of layout. - double get contentMaxConstraintsWidth { - // If renderBoxModel definite content constraints, use it as max constrains width of content. - BoxConstraints? contentConstraints = renderBoxModel!.contentConstraints; - if (contentConstraints != null && contentConstraints.maxWidth != double.infinity) { - return contentConstraints.maxWidth; + // Whether height is stretched to fill its parent's content height. + @override + bool get isHeightStretch { + RenderStyle renderStyle = this; + if (renderStyle.parent == null) { + return false; } + bool isStretch = false; + RenderStyle parentRenderStyle = renderStyle.parent!; - // If renderBoxModel has no logical content width (eg display is inline-block/inline-flex and - // has no width), find its ancestors with logical width set to calculate the remaining space. - double contentMaxConstraintsWidth = double.infinity; - double cropWidth = 0; + bool isParentFlex = parentRenderStyle.display == CSSDisplay.flex || + parentRenderStyle.display == CSSDisplay.inlineFlex; + bool isHorizontalDirection = false; + bool isFlexNoWrap = false; + bool isChildStretchSelf = false; + if (isParentFlex) { + isHorizontalDirection = CSSFlex.isHorizontalFlexDirection(parentRenderStyle.flexDirection); + isFlexNoWrap = parentRenderStyle.flexWrap != FlexWrap.wrap && + parentRenderStyle.flexWrap != FlexWrap.wrapReverse; + isChildStretchSelf = renderStyle.alignSelf != AlignSelf.auto + ? renderStyle.alignSelf == AlignSelf.stretch + : parentRenderStyle.effectiveAlignItems == AlignItems.stretch; + } - RenderStyle currentRenderStyle = this; + CSSLengthValue marginTop = renderStyle.marginTop; + CSSLengthValue marginBottom = renderStyle.marginBottom; - // Get the nearest width of ancestor with width - while (true) { - RenderStyle? parentRenderStyle = currentRenderStyle.parent; - CSSDisplay? effectiveDisplay = currentRenderStyle.effectiveDisplay; + // Display as block if flex vertical layout children and stretch children + if (marginTop.isNotAuto && marginBottom.isNotAuto && + isParentFlex && isHorizontalDirection && isFlexNoWrap && isChildStretchSelf) { + isStretch = true; + } - // Flex item with flex-shrink 0 and no width/max-width will have infinity constraints - // even if parents have width - if (parentRenderStyle != null && (parentRenderStyle.display == CSSDisplay.flex || - parentRenderStyle.display == CSSDisplay.inlineFlex) - ) { - if (currentRenderStyle.flexShrink == 0 && - currentRenderStyle.width.isAuto && - currentRenderStyle.maxWidth.isNone) { - break; - } - } + return isStretch; + } - // Get width if width exists and element is not inline - if (effectiveDisplay != CSSDisplay.inline && - (currentRenderStyle.width.isNotAuto || currentRenderStyle.maxWidth.isNotNone)) { - // Get the min width between width and max-width - contentMaxConstraintsWidth = math.min( - (currentRenderStyle.width.isAuto ? null : currentRenderStyle.width.computedValue) ?? double.infinity, - (currentRenderStyle.maxWidth.isNone ? null : currentRenderStyle.maxWidth.computedValue) ?? double.infinity - ); - cropWidth = _getCropWidthByPaddingBorder(currentRenderStyle, cropWidth); - break; - } - if (parentRenderStyle != null) { - cropWidth += currentRenderStyle.margin.horizontal; - cropWidth = _getCropWidthByPaddingBorder(currentRenderStyle, cropWidth); - currentRenderStyle = parentRenderStyle; - } else { - break; - } + // Max width to constrain its children, used in deciding the line wrapping timing of layout. + @override + double get contentMaxConstraintsWidth { + // If renderBoxModel definite content constraints, use it as max constrains width of content. + BoxConstraints? contentConstraints = renderBoxModel!.contentConstraints; + if (contentConstraints != null && contentConstraints.maxWidth != double.infinity) { + return contentConstraints.maxWidth; } - if (contentMaxConstraintsWidth != double.infinity) { - contentMaxConstraintsWidth = contentMaxConstraintsWidth - cropWidth; + double contentMaxConstraintsWidth = double.infinity; + RenderStyle renderStyle = this; + double? borderBoxLogicalWidth; + RenderStyle? ancestorRenderStyle = _findAncestorWithContentBoxLogicalWidth(); + + // If renderBoxModel has no logical width (eg. display is inline-block/inline-flex and + // has no width), the child width is constrained by its closest ancestor who has definite logical content box width. + if (ancestorRenderStyle != null) { + borderBoxLogicalWidth = ancestorRenderStyle.contentBoxLogicalWidth; } - // Set contentMaxConstraintsWidth to 0 when it is negative in the case of - // renderBoxModel's width exceeds its ancestors. - //
- //
- //
- //
- if (contentMaxConstraintsWidth < 0) { - contentMaxConstraintsWidth = 0; + if (borderBoxLogicalWidth != null) { + contentMaxConstraintsWidth = borderBoxLogicalWidth - + renderStyle.border.horizontal - + renderStyle.padding.horizontal; + // Logical width may be smaller than its border and padding width, + // in this case, content width will be negative which is illegal. + //
+ //
+ //
+ //
+ contentMaxConstraintsWidth = math.max(0, contentMaxConstraintsWidth); } return contentMaxConstraintsWidth; @@ -485,131 +892,218 @@ class RenderStyle // Content width calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-content-box - // @TODO: add cache to avoid recalculate every time. + // Use double.infinity refers to the value is not computed yet. + double? _contentBoxLogicalWidth = double.infinity; + @override double? get contentBoxLogicalWidth { // If renderBox has tight width, its logical size equals max size. - if (renderBoxModel != null && - renderBoxModel!.hasSize && - renderBoxModel!.constraints.hasTightWidth - ) { - return renderBoxModel!.constraints.maxWidth - - effectiveBorderLeftWidth.computedValue - effectiveBorderRightWidth.computedValue - - paddingLeft.computedValue - paddingRight.computedValue; + // Compute logical width directly in case as renderBoxModel is not layouted yet, + // eg. compute percentage length before layout. + if (_contentBoxLogicalWidth == double.infinity) { + computeContentBoxLogicalWidth(); } - return getLogicalContentWidth(); + return _contentBoxLogicalWidth; } // Content height calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-content-box - // @TODO: add cache to avoid recalculate every time. + // Use double.infinity refers to the value is not computed yet. + double? _contentBoxLogicalHeight = double.infinity; + @override double? get contentBoxLogicalHeight { - // If renderBox has tight height, its logical size equals max size. - if (renderBoxModel != null && - renderBoxModel!.hasSize && - renderBoxModel!.constraints.hasTightHeight - ) { - return renderBoxModel!.constraints.maxHeight - - effectiveBorderTopWidth.computedValue - effectiveBorderBottomWidth.computedValue - - paddingTop.computedValue - paddingBottom.computedValue; + // Compute logical height directly in case as renderBoxModel is not layouted yet, + // eg. compute percentage length before layout. + if (_contentBoxLogicalHeight == double.infinity) { + computeContentBoxLogicalHeight(); } - return getLogicalContentHeight(); + return _contentBoxLogicalHeight; } // Padding box width calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-padding-box + @override double? get paddingBoxLogicalWidth { if (contentBoxLogicalWidth == null) { return null; } - return contentBoxLogicalWidth! + paddingLeft.computedValue + paddingRight.computedValue; + return contentBoxLogicalWidth! + + paddingLeft.computedValue + + paddingRight.computedValue; } // Padding box height calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-padding-box + @override double? get paddingBoxLogicalHeight { if (contentBoxLogicalHeight == null) { return null; } - return contentBoxLogicalHeight! + paddingTop.computedValue + paddingBottom.computedValue; + return contentBoxLogicalHeight! + + paddingTop.computedValue + + paddingBottom.computedValue; } // Border box width calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-border-box + @override double? get borderBoxLogicalWidth { if (paddingBoxLogicalWidth == null) { return null; } - return paddingBoxLogicalWidth! + effectiveBorderLeftWidth.computedValue + effectiveBorderRightWidth.computedValue; + return paddingBoxLogicalWidth! + + effectiveBorderLeftWidth.computedValue + + effectiveBorderRightWidth.computedValue; } // Border box height calculated from renderStyle tree. // https://www.w3.org/TR/css-box-3/#valdef-box-border-box + @override double? get borderBoxLogicalHeight { if (paddingBoxLogicalHeight == null) { return null; } - return paddingBoxLogicalHeight! + effectiveBorderTopWidth.computedValue + effectiveBorderBottomWidth.computedValue; + return paddingBoxLogicalHeight! + + effectiveBorderTopWidth.computedValue + + effectiveBorderBottomWidth.computedValue; } - // Content box width of renderBoxModel after it was rendered. - // https://www.w3.org/TR/css-box-3/#valdef-box-content-box - double? get contentBoxWidth { - if (paddingBoxWidth == null) { - return null; + // Border box width of renderBoxModel after it was rendered. + // https://www.w3.org/TR/css-box-3/#valdef-box-border-box + @override + double? get borderBoxWidth { + if (renderBoxModel!.hasSize && renderBoxModel!.boxSize != null) { + return renderBoxModel!.boxSize!.width; } - return paddingBoxWidth! - paddingLeft.computedValue - paddingRight.computedValue; + return null; } - // Content box height of renderBoxModel after it was rendered. - // https://www.w3.org/TR/css-box-3/#valdef-box-content-box - double? get contentBoxHeight { - if (paddingBoxHeight == null) { - return null; + // Border box height of renderBoxModel after it was rendered. + // https://www.w3.org/TR/css-box-3/#valdef-box-border-box + @override + double? get borderBoxHeight { + if (renderBoxModel!.hasSize && renderBoxModel!.boxSize != null) { + return renderBoxModel!.boxSize!.height; } - return paddingBoxHeight! - paddingTop.computedValue - paddingBottom.computedValue; + return null; } // Padding box width of renderBoxModel after it was rendered. // https://www.w3.org/TR/css-box-3/#valdef-box-padding-box + @override double? get paddingBoxWidth { if (borderBoxWidth == null) { return null; } - return borderBoxWidth! - effectiveBorderLeftWidth.computedValue - effectiveBorderRightWidth.computedValue; + return borderBoxWidth! + - effectiveBorderLeftWidth.computedValue + - effectiveBorderRightWidth.computedValue; } // Padding box height of renderBoxModel after it was rendered. // https://www.w3.org/TR/css-box-3/#valdef-box-padding-box + @override double? get paddingBoxHeight { if (borderBoxHeight == null) { return null; } - return borderBoxHeight! - effectiveBorderTopWidth.computedValue - effectiveBorderBottomWidth.computedValue; + return borderBoxHeight! + - effectiveBorderTopWidth.computedValue + - effectiveBorderBottomWidth.computedValue; } - // Border box width of renderBoxModel after it was rendered. - // https://www.w3.org/TR/css-box-3/#valdef-box-border-box - double? get borderBoxWidth { - if (renderBoxModel!.hasSize && renderBoxModel!.boxSize != null) { - return renderBoxModel!.boxSize!.width; + // Content box width of renderBoxModel after it was rendered. + // https://www.w3.org/TR/css-box-3/#valdef-box-content-box + @override + double? get contentBoxWidth { + if (paddingBoxWidth == null) { + return null; + } + return paddingBoxWidth! + - paddingLeft.computedValue + - paddingRight.computedValue; + } + + // Content box height of renderBoxModel after it was rendered. + // https://www.w3.org/TR/css-box-3/#valdef-box-content-box + @override + double? get contentBoxHeight { + if (paddingBoxHeight == null) { + return null; + } + return paddingBoxHeight! + - paddingTop.computedValue + - paddingBottom.computedValue; + } + + // Border box width of renderBoxModel calculated from tight width constraints. + @override + double? get borderBoxConstraintsWidth { + if (renderBoxModel!.hasSize && + renderBoxModel!.constraints.hasTightWidth + ) { + return renderBoxModel!.constraints.maxWidth; } return null; } - // Border box height of renderBoxModel after it was rendered. - // https://www.w3.org/TR/css-box-3/#valdef-box-border-box - double? get borderBoxHeight { - if (renderBoxModel!.hasSize && renderBoxModel!.boxSize != null) { - return renderBoxModel!.boxSize!.height; + // Border box height of renderBoxModel calculated from tight height constraints. + @override + double? get borderBoxConstraintsHeight { + if (renderBoxModel!.hasSize && + renderBoxModel!.constraints.hasTightHeight + ) { + return renderBoxModel!.constraints.maxHeight; } return null; } - /// Get height of replaced element by intrinsic ratio if height is not defined + // Padding box width of renderBoxModel calculated from tight width constraints. + @override + double? get paddingBoxConstraintsWidth { + if (borderBoxConstraintsWidth == null) { + return null; + } + return borderBoxConstraintsWidth! + - effectiveBorderLeftWidth.computedValue + - effectiveBorderRightWidth.computedValue; + } + + // Padding box height of renderBoxModel calculated from tight height constraints. + @override + double? get paddingBoxConstraintsHeight { + if (borderBoxConstraintsHeight == null) { + return null; + } + return borderBoxConstraintsHeight! + - effectiveBorderTopWidth.computedValue + - effectiveBorderBottomWidth.computedValue; + } + + // Content box width of renderBoxModel calculated from tight width constraints. + @override + double? get contentBoxConstraintsWidth { + if (paddingBoxConstraintsWidth == null) { + return null; + } + return paddingBoxConstraintsWidth! + - paddingLeft.computedValue + - paddingRight.computedValue; + } + + // Content box height of renderBoxModel calculated from tight height constraints. + @override + double? get contentBoxConstraintsHeight { + if (paddingBoxConstraintsHeight == null) { + return null; + } + return paddingBoxConstraintsHeight! + - paddingTop.computedValue + - paddingBottom.computedValue; + } + + // Get height of replaced element by intrinsic ratio if height is not defined + @override double getHeightByIntrinsicRatio() { - // @TODO: move intrinsic width/height to renderStyle - double? intrinsicWidth = renderBoxModel!.intrinsicWidth; - double intrinsicRatio = renderBoxModel!.intrinsicRatio!; double? realWidth = width.isAuto ? intrinsicWidth : width.computedValue; if (minWidth.isNotAuto && realWidth! < minWidth.computedValue) { realWidth = minWidth.computedValue; @@ -617,16 +1111,13 @@ class RenderStyle if (maxWidth.isNotNone && realWidth! > maxWidth.computedValue) { realWidth = maxWidth.computedValue; } - double realHeight = realWidth! * intrinsicRatio; + double realHeight = realWidth! * intrinsicRatio!; return realHeight; } - /// Get width of replaced element by intrinsic ratio if width is not defined + // Get width of replaced element by intrinsic ratio if width is not defined + @override double getWidthByIntrinsicRatio() { - // @TODO: move intrinsic width/height to renderStyle - double? intrinsicHeight = renderBoxModel!.intrinsicHeight; - double intrinsicRatio = renderBoxModel!.intrinsicRatio!; - double? realHeight = height.isAuto ? intrinsicHeight : height.computedValue; if (!minHeight.isAuto && realHeight! < minHeight.computedValue) { realHeight = minHeight.computedValue; @@ -634,25 +1125,73 @@ class RenderStyle if (!maxHeight.isNone && realHeight! > maxHeight.computedValue) { realHeight = maxHeight.computedValue; } - double realWidth = realHeight! / intrinsicRatio; + double realWidth = realHeight! / intrinsicRatio!; return realWidth; } + + @override + void visitChildren(RenderStyleVisitor visitor) { + target.children.forEach((Element childElement) { + visitor(childElement.renderStyle as T); + }); + } + // Mark this node as detached. void detach() { // Clear reference to it's parent. parent = null; } -} -double _getCropWidthByPaddingBorder(RenderStyle renderStyle, double cropWidth) { - cropWidth += renderStyle.border.horizontal; - cropWidth += renderStyle.padding.horizontal; - return cropWidth; -} + // Find ancestor render style with display of not inline. + RenderStyle? _findAncestorWithNoDisplayInline() { + RenderStyle renderStyle = this; + RenderStyle? parentRenderStyle = renderStyle.parent; + while(parentRenderStyle != null) { + if (parentRenderStyle.effectiveDisplay != CSSDisplay.inline) { + break; + } + parentRenderStyle = parentRenderStyle.parent; + } + return parentRenderStyle; + } + + // Find ancestor render style with definite content box logical width. + RenderStyle? _findAncestorWithContentBoxLogicalWidth() { + RenderStyle renderStyle = this; + RenderStyle? parentRenderStyle = renderStyle.parent; + + while(parentRenderStyle != null) { + RenderStyle? grandParentRenderStyle = parentRenderStyle.parent; + // Flex item with flex-shrink 0 and no width/max-width will have infinity constraints + // even if parents have width. + if (grandParentRenderStyle != null) { + bool isGrandParentFlex = grandParentRenderStyle.display == CSSDisplay.flex || + grandParentRenderStyle.display == CSSDisplay.inlineFlex; + if (isGrandParentFlex && parentRenderStyle.flexShrink == 0) { + return null; + } + } -double _getCropHeightByPaddingBorder(RenderStyle renderStyle, double cropHeight) { - cropHeight += renderStyle.border.vertical; - cropHeight += renderStyle.padding.vertical; - return cropHeight; + if (parentRenderStyle.contentBoxLogicalWidth != null) { + break; + } + + parentRenderStyle = grandParentRenderStyle; + } + return parentRenderStyle; + } + + // Whether current renderStyle is ancestor for child renderStyle in the renderStyle tree. + bool isAncestorOf(RenderStyle childRenderStyle) { + RenderStyle? parentRenderStyle = childRenderStyle.parent; + while(parentRenderStyle != null) { + if (parentRenderStyle == this) { + return true; + } + parentRenderStyle = parentRenderStyle.parent; + } + return false; + } } + diff --git a/kraken/lib/src/css/sizing.dart b/kraken/lib/src/css/sizing.dart index 42b8de38ab..3b31e048c8 100644 --- a/kraken/lib/src/css/sizing.dart +++ b/kraken/lib/src/css/sizing.dart @@ -15,7 +15,7 @@ import 'package:kraken/rendering.dart'; /// - min-width /// - min-height -mixin CSSSizingMixin on RenderStyleBase { +mixin CSSSizingMixin on RenderStyle { // https://drafts.csswg.org/css-sizing-3/#preferred-size-properties // Name: width, height @@ -28,9 +28,10 @@ mixin CSSSizingMixin on RenderStyleBase { // Canonical order: per grammar // Animation type: by computed value type, recursing into fit-content() CSSLengthValue? _width; - CSSLengthValue get width { - return _width ?? CSSLengthValue.auto; - } + + @override + CSSLengthValue get width => _width ?? CSSLengthValue.auto; + set width(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || width == value) { @@ -41,9 +42,10 @@ mixin CSSSizingMixin on RenderStyleBase { } CSSLengthValue? _height; - CSSLengthValue get height { - return _height ?? CSSLengthValue.auto; - } + + @override + CSSLengthValue get height => _height ?? CSSLengthValue.auto; + set height(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || height == value) { @@ -64,9 +66,10 @@ mixin CSSSizingMixin on RenderStyleBase { // Canonical order: per grammar // Animatable: by computed value, recursing into fit-content() CSSLengthValue? _minWidth; - CSSLengthValue get minWidth { - return _minWidth ?? CSSLengthValue.auto; - } + + @override + CSSLengthValue get minWidth => _minWidth ?? CSSLengthValue.auto; + set minWidth(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || minWidth == value) { @@ -77,9 +80,10 @@ mixin CSSSizingMixin on RenderStyleBase { } CSSLengthValue? _minHeight; - CSSLengthValue get minHeight { - return _minHeight ?? CSSLengthValue.auto; - } + + @override + CSSLengthValue get minHeight => _minHeight ?? CSSLengthValue.auto; + set minHeight(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || minHeight == value) { @@ -100,9 +104,10 @@ mixin CSSSizingMixin on RenderStyleBase { // Canonical order: per grammar // Animatable: by computed value, recursing into fit-content() CSSLengthValue? _maxWidth; - CSSLengthValue get maxWidth { - return _maxWidth ?? CSSLengthValue.none; - } + + @override + CSSLengthValue get maxWidth => _maxWidth ?? CSSLengthValue.none; + set maxWidth(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || maxWidth == value) { @@ -113,9 +118,12 @@ mixin CSSSizingMixin on RenderStyleBase { } CSSLengthValue? _maxHeight; + + @override CSSLengthValue get maxHeight { return _maxHeight ?? CSSLengthValue.none; } + set maxHeight(CSSLengthValue? value) { // Negative value is invalid, auto value is parsed at layout stage. if ((value != null && value.value != null && value.value! < 0) || maxHeight == value) { @@ -125,6 +133,42 @@ mixin CSSSizingMixin on RenderStyleBase { _markSelfAndParentNeedsLayout(); } + // Intrinsic width of replaced element. + double? _intrinsicWidth; + @override + double? get intrinsicWidth { + return _intrinsicWidth; + } + set intrinsicWidth(double? value) { + if (_intrinsicWidth == value) return; + _intrinsicWidth = value; + _markSelfAndParentNeedsLayout(); + } + + // Intrinsic height of replaced element. + double? _intrinsicHeight; + @override + double? get intrinsicHeight { + return _intrinsicHeight; + } + set intrinsicHeight(double? value) { + if (_intrinsicHeight == value) return; + _intrinsicHeight = value; + _markSelfAndParentNeedsLayout(); + } + + // Aspect ratio of replaced element. + double? _intrinsicRatio; + @override + double? get intrinsicRatio { + return _intrinsicRatio; + } + set intrinsicRatio(double? value) { + if (_intrinsicRatio == value) return; + _intrinsicRatio = value; + _markSelfAndParentNeedsLayout(); + } + void _markSelfAndParentNeedsLayout() { if (renderBoxModel == null) return; RenderBoxModel boxModel = renderBoxModel!; @@ -136,34 +180,4 @@ mixin CSSSizingMixin on RenderStyleBase { } } - // Whether current node should stretch children's height - static bool isStretchChildHeight(RenderStyle renderStyle, RenderStyle childRenderStyle) { - bool isStretch = false; - bool isFlex = renderStyle.renderBoxModel is RenderFlexLayout; - bool isHorizontalDirection = false; - bool isAlignItemsStretch = false; - bool isFlexNoWrap = false; - bool isChildAlignSelfStretch = false; - bool isChildStretchSelf = false; - if (isFlex) { - isHorizontalDirection = CSSFlex.isHorizontalFlexDirection(renderStyle.flexDirection); - isAlignItemsStretch = renderStyle.effectiveAlignItems == AlignItems.stretch; - isFlexNoWrap = renderStyle.flexWrap != FlexWrap.wrap && - childRenderStyle.flexWrap != FlexWrap.wrapReverse; - isChildAlignSelfStretch = childRenderStyle.alignSelf == AlignSelf.stretch; - isChildStretchSelf = childRenderStyle.alignSelf != AlignSelf.auto ? - isChildAlignSelfStretch : isAlignItemsStretch; - } - - CSSLengthValue marginTop = childRenderStyle.marginTop; - CSSLengthValue marginBottom = childRenderStyle.marginBottom; - - // Display as block if flex vertical layout children and stretch children - if (!marginTop.isAuto && !marginBottom.isAuto && - isFlex && isHorizontalDirection && isFlexNoWrap && isChildStretchSelf) { - isStretch = true; - } - - return isStretch; - } } diff --git a/kraken/lib/src/css/sliver.dart b/kraken/lib/src/css/sliver.dart index 17ade19482..983d39e2fa 100644 --- a/kraken/lib/src/css/sliver.dart +++ b/kraken/lib/src/css/sliver.dart @@ -6,14 +6,15 @@ import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; -mixin CSSSliverMixin on RenderStyleBase { +mixin CSSSliverMixin on RenderStyle { - Axis _sliverDirection = Axis.vertical; + @override Axis get sliverDirection => _sliverDirection; + Axis _sliverDirection = Axis.vertical; set sliverDirection(Axis value) { if (_sliverDirection == value) return; _sliverDirection = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsLayout(); } static Axis resolveAxis(String sliverDirection) { diff --git a/kraken/lib/src/css/style_declaration.dart b/kraken/lib/src/css/style_declaration.dart index 09864ecbc7..5fd07cbff1 100644 --- a/kraken/lib/src/css/style_declaration.dart +++ b/kraken/lib/src/css/style_declaration.dart @@ -6,6 +6,7 @@ import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/rendering.dart'; +import 'package:quiver/collection.dart'; typedef StyleChangeListener = void Function(String property, String? original, String present); @@ -45,6 +46,8 @@ List _propertyOrders = [ RegExp _kebabCaseReg = RegExp(r'[A-Z]'); +final LinkedLruHashMap> _cachedExpandedShorthand = LinkedLruHashMap(maximumSize: 500); + // CSS Object Model: https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface /// The [CSSStyleDeclaration] interface represents an object that is a CSS @@ -169,51 +172,59 @@ class CSSStyleDeclaration { } void _expandShorthand(String propertyName, String normalizedValue, bool? isImportant) { - Map longhandProperties = {}; - switch(propertyName) { - case PADDING: - CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); - break; - case MARGIN: - CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); - break; - case BACKGROUND: - CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); - break; - case BACKGROUND_POSITION: - CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); - break; - case BORDER_RADIUS: - CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); - break; - case OVERFLOW: - CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); - break; - case FONT: - CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); - break; - case FLEX: - CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); - break; - case FLEX_FLOW: - CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); - break; - case BORDER: - case BORDER_TOP: - case BORDER_RIGHT: - case BORDER_BOTTOM: - case BORDER_LEFT: - case BORDER_COLOR: - case BORDER_STYLE: - case BORDER_WIDTH: - CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); - break; - case TRANSITION: - CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); - break; - case TEXT_DECORATION: - CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); - break; + Map longhandProperties; + String cacheKey = '$propertyName:$normalizedValue'; + if (_cachedExpandedShorthand.containsKey(cacheKey)) { + longhandProperties = _cachedExpandedShorthand[cacheKey]!; + } else { + longhandProperties = {}; + + switch(propertyName) { + case PADDING: + CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); + break; + case MARGIN: + CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); + break; + case BACKGROUND: + CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); + break; + case BACKGROUND_POSITION: + CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); + break; + case BORDER_RADIUS: + CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); + break; + case OVERFLOW: + CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); + break; + case FONT: + CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); + break; + case FLEX: + CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); + break; + case FLEX_FLOW: + CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); + break; + case BORDER: + case BORDER_TOP: + case BORDER_RIGHT: + case BORDER_BOTTOM: + case BORDER_LEFT: + case BORDER_COLOR: + case BORDER_STYLE: + case BORDER_WIDTH: + CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); + break; + case TRANSITION: + CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); + break; + case TEXT_DECORATION: + CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); + break; + } + _cachedExpandedShorthand[cacheKey] = longhandProperties; } if (longhandProperties.isNotEmpty) { @@ -256,13 +267,26 @@ class CSSStyleDeclaration { bool _isValidValue(String propertyName, String normalizedValue) { - // Illegal value like ' ' after trim is '' should do nothing. + // Illegal value like ' ' after trimming is '' should do nothing. if (normalizedValue.isEmpty) return false; + // Always return true if is CSS function notation, for value is + // lazy calculated. + // Eg. var(--x), calc(1 + 1) + if (CSSFunction.isFunction(normalizedValue)) return true; + // Validate value. switch (propertyName) { case WIDTH: case HEIGHT: + // Validation length type + if (!CSSLength.isNonNegativeLength(normalizedValue) && + !CSSLength.isAuto(normalizedValue) && + !CSSPercentage.isNonNegativePercentage(normalizedValue) + ) { + return false; + } + break; case TOP: case LEFT: case RIGHT: @@ -282,8 +306,8 @@ class CSSStyleDeclaration { case MAX_WIDTH: case MAX_HEIGHT: if (normalizedValue != NONE && - !CSSLength.isLength(normalizedValue) && - !CSSPercentage.isPercentage(normalizedValue) + !CSSLength.isNonNegativeLength(normalizedValue) && + !CSSPercentage.isNonNegativePercentage(normalizedValue) ) { return false; } @@ -294,8 +318,8 @@ class CSSStyleDeclaration { case PADDING_LEFT: case PADDING_BOTTOM: case PADDING_RIGHT: - if (!CSSLength.isLength(normalizedValue) && - !CSSPercentage.isPercentage(normalizedValue) + if (!CSSLength.isNonNegativeLength(normalizedValue) && + !CSSPercentage.isNonNegativePercentage(normalizedValue) ) { return false; } @@ -304,7 +328,7 @@ class CSSStyleDeclaration { case BORDER_TOP_WIDTH: case BORDER_LEFT_WIDTH: case BORDER_RIGHT_WIDTH: - if (!CSSLength.isLength(normalizedValue)) { + if (!CSSLength.isNonNegativeLength(normalizedValue)) { return false; } break; @@ -330,7 +354,7 @@ class CSSStyleDeclaration { /// Modifies an existing CSS property or creates a new CSS property in /// the declaration block. - void setProperty(String propertyName, value, [bool? isImportant]) { + void setProperty(String propertyName, String? value, [bool? isImportant]) { // Null or empty value means should be removed. if (isNullOrEmptyValue(value)) { removeProperty(propertyName, isImportant); @@ -367,10 +391,13 @@ class CSSStyleDeclaration { } void flushPendingProperties() { - if (target?.parentNode?.renderer == null) return; + Element? _target = target; + // If style target element not exists, no need to do flush operation. + if (_target == null) return; // Display change from none to other value that the renderBoxModel is null. - if (_pendingProperties.containsKey(DISPLAY) && target!.isConnected) { + if (_pendingProperties.containsKey(DISPLAY) && _target.isConnected && + _target.parentElement?.renderStyle.display != CSSDisplay.sliver) { String? prevValue = _properties[DISPLAY]; String currentValue = _pendingProperties[DISPLAY]!; _properties[DISPLAY] = currentValue; @@ -378,7 +405,10 @@ class CSSStyleDeclaration { _emitPropertyChanged(DISPLAY, prevValue, currentValue); } - RenderBoxModel? renderBoxModel = target!.renderBoxModel; + // If target has no renderer attached, no need to flush. + if (!_target.isRendererAttached) return; + + RenderBoxModel? renderBoxModel = _target.renderBoxModel; if (_pendingProperties.isEmpty || renderBoxModel == null) { return; } @@ -406,6 +436,10 @@ class CSSStyleDeclaration { for (String propertyName in propertyNames) { String? prevValue = prevValues[propertyName]; String currentValue = pendingProperties[propertyName]!; + + // Return if value has not changed. + if (currentValue == prevValue) return; + _emitPropertyChanged(propertyName, prevValue, currentValue); } } diff --git a/kraken/lib/src/css/style_property.dart b/kraken/lib/src/css/style_property.dart index 25e22b4740..cdf2cf277c 100644 --- a/kraken/lib/src/css/style_property.dart +++ b/kraken/lib/src/css/style_property.dart @@ -67,7 +67,7 @@ List _splitBySpace(String value) { class CSSStyleProperty { static void setShorthandPadding(Map properties, String shorthandValue) { - List? values = _getEdgeValues(shorthandValue); + List? values = _getEdgeValues(shorthandValue, isNonNegativeLengthOrPercentage: true); if (values == null) return; properties[PADDING_TOP] = values[0]; @@ -84,7 +84,7 @@ class CSSStyleProperty { } static void setShorthandMargin(Map properties, String shorthandValue) { - List? values = _getEdgeValues(shorthandValue, isLengthOrPercentage: false); + List? values = _getEdgeValues(shorthandValue); if (values == null) return; properties[MARGIN_TOP] = values[0]; @@ -156,7 +156,7 @@ class CSSStyleProperty { } static void setShorthandOverflow(Map properties, String shorthandValue) { - List values = shorthandValue.split(_spaceRegExp); + List values = _splitBySpace(shorthandValue); if (values.length == 1) { properties[OVERFLOW_Y] = properties[OVERFLOW_X] = values[0]; } else if (values.length == 2) { @@ -289,7 +289,7 @@ class CSSStyleProperty { borderLeftColor = values[2]; } } else if (property == BORDER_WIDTH) { - List? values = _getEdgeValues(shorthandValue); + List? values = _getEdgeValues(shorthandValue, isNonNegativeLength: true); if (values == null) return; borderTopWidth = values[0]; @@ -298,7 +298,7 @@ class CSSStyleProperty { borderLeftWidth = values[3]; } else if (property == BORDER_STYLE) { // @TODO: validate value - List? values = _getEdgeValues(shorthandValue, isLengthOrPercentage: false); + List? values = _getEdgeValues(shorthandValue); if (values == null) return; borderTopStyle = values[0]; @@ -307,7 +307,7 @@ class CSSStyleProperty { borderLeftStyle = values[3]; } else if (property == BORDER_COLOR) { // @TODO: validate value - List? values = _getEdgeValues(shorthandValue, isLengthOrPercentage: false); + List? values = _getEdgeValues(shorthandValue); if (values == null) return; borderTopColor = values[0]; @@ -395,7 +395,7 @@ class CSSStyleProperty { if (shadow == NONE) { continue; } - List parts = shadow.trim().split(_spaceRegExp); + List parts = _splitBySpace(shadow.trim()); String? inset; String? color; @@ -429,7 +429,7 @@ class CSSStyleProperty { static List? _getBorderRaidusValues(String shorthandProperty) { if (!shorthandProperty.contains('/')) { - return _getEdgeValues(shorthandProperty); + return _getEdgeValues(shorthandProperty, isNonNegativeLengthOrPercentage: true); } List radius = shorthandProperty.split(_slashRegExp); @@ -446,8 +446,8 @@ class CSSStyleProperty { String firstRadius = radius[0]; String secondRadius = radius[1]; - List firstValues = _getEdgeValues(firstRadius)!; - List secondValues = _getEdgeValues(secondRadius)!; + List firstValues = _getEdgeValues(firstRadius, isNonNegativeLengthOrPercentage: true)!; + List secondValues = _getEdgeValues(secondRadius, isNonNegativeLengthOrPercentage: true)!; return [ '${firstValues[0]} ${secondValues[0]}', @@ -461,7 +461,7 @@ class CSSStyleProperty { static List? _getBackgroundValues(String shorthandProperty) { // Convert 40%/10em -> 40% / 10em shorthandProperty = shorthandProperty.replaceAll(_slashRegExp, ' / '); - List values = shorthandProperty.split(_spaceRegExp); + List values = shorthandProperty.split(_spaceRegExp); String? color; String? image; @@ -476,29 +476,30 @@ class CSSStyleProperty { String? size; bool isPositionEndAndSizeStart = false; - for (String value in values as Iterable) { - if (color == null && CSSColor.isColor(value)) { + for (String value in values) { + final bool isValueVariableFunction = CSSFunction.isFunction(value, functionName: VAR); + if (color == null && (isValueVariableFunction || CSSColor.isColor(value))) { color = value; - } else if (image == null && CSSBackground.isValidBackgroundImageValue(value)) { + } else if (image == null && (isValueVariableFunction || CSSBackground.isValidBackgroundImageValue(value))) { image = value; - } else if (repeat == null && CSSBackground.isValidBackgroundRepeatValue(value)) { + } else if (repeat == null && (isValueVariableFunction || CSSBackground.isValidBackgroundRepeatValue(value))) { repeat = value; - } else if (attachment == null && CSSBackground.isValidBackgroundAttachmentValue(value)) { + } else if (attachment == null && (isValueVariableFunction || CSSBackground.isValidBackgroundAttachmentValue(value))) { attachment = value; } else if (positionX == null && !isPositionEndAndSizeStart && - CSSBackground.isValidBackgroundPositionValue(value)) { + (isValueVariableFunction || CSSBackground.isValidBackgroundPositionValue(value))) { positionX = value; } else if (positionY == null && !isPositionEndAndSizeStart && - CSSBackground.isValidBackgroundPositionValue(value)) { + (isValueVariableFunction || CSSBackground.isValidBackgroundPositionValue(value))) { positionY = value; } else if (value == '/') { isPositionEndAndSizeStart = true; continue; - } else if (sizeWidth == null && CSSBackground.isValidBackgroundSizeValue(value)) { + } else if (sizeWidth == null && (isValueVariableFunction || CSSBackground.isValidBackgroundSizeValue(value))) { sizeWidth = value; - } else if (sizeHeight == null && CSSBackground.isValidBackgroundSizeValue(value)) { + } else if (sizeHeight == null && (isValueVariableFunction || CSSBackground.isValidBackgroundSizeValue(value))) { sizeHeight = value; } else { return null; @@ -535,7 +536,7 @@ class CSSStyleProperty { shorthandProperty = shorthandProperty.replaceAll(_slashRegExp, ' / '); // Convert "Goudy Bookletter 1911", sans-serif => "Goudy Bookletter 1911",sans-serif shorthandProperty = shorthandProperty.replaceAll(_replaceCommaRegExp, ','); - List values = _splitBySpace(shorthandProperty); + List values = _splitBySpace(shorthandProperty); String? style; String? weight; @@ -545,17 +546,18 @@ class CSSStyleProperty { bool isSizeEndAndLineHeightStart = false; - for (String value in values as Iterable) { - if (style == null && CSSText.isValidFontStyleValue(value)) { + for (String value in values) { + final bool isValueVariableFunction = CSSFunction.isFunction(value, functionName: VAR); + if (style == null && (isValueVariableFunction || CSSText.isValidFontStyleValue(value))) { style = value; - } else if (weight == null && CSSText.isValidFontWeightValue(value)) { + } else if (weight == null && (isValueVariableFunction || CSSText.isValidFontWeightValue(value))) { weight = value; - } else if (size == null && CSSLength.isLength(value)) { + } else if (size == null && (isValueVariableFunction || CSSLength.isNonNegativeLength(value))) { size = value; } else if (value == '/') { isSizeEndAndLineHeightStart = true; continue; - } else if (lineHeight == null && CSSText.isValidLineHeightValue(value)) { + } else if (lineHeight == null && (isValueVariableFunction || CSSText.isValidLineHeightValue(value))) { lineHeight = value; } else if (family == null) { // The font-family must be the last value specified. @@ -574,7 +576,7 @@ class CSSStyleProperty { } static List? _getTextDecorationValues(String shorthandProperty) { - List values = shorthandProperty.split(_spaceRegExp); + List values = _splitBySpace(shorthandProperty); String? line; String? color; String? style; @@ -595,15 +597,15 @@ class CSSStyleProperty { } static List? _getTransitionValues(String shorthandProperty) { - List transitions = shorthandProperty.split(_commaRegExp); + List transitions = shorthandProperty.split(_commaRegExp); List values = List.filled(4, null); - for (String transition in transitions as Iterable) { - List parts = transition.trim().split(_spaceRegExp); + for (String transition in transitions) { + List parts = _splitBySpace(transition.trim()); String? property; String? duration; - String? timingFuction; + String? timingFunction; String? delay; for (String part in parts) { @@ -611,8 +613,8 @@ class CSSStyleProperty { property = part; } else if (duration == null && CSSTime.isTime(part)) { duration = part; - } else if (timingFuction == null && CSSTransitionMixin.isValidTransitionTimingFunctionValue(part)) { - timingFuction = part; + } else if (timingFunction == null && CSSTransitionMixin.isValidTransitionTimingFunctionValue(part)) { + timingFunction = part; } else if (delay == null && CSSTime.isTime(part)) { delay = part; } else { @@ -622,12 +624,12 @@ class CSSStyleProperty { property = property ?? ALL; duration = duration ?? _0s; - timingFuction = timingFuction ?? EASE; + timingFunction = timingFunction ?? EASE; delay = delay ?? _0s; values[0] == null ? values[0] = property : values[0] = values[0]! + (_comma + property); values[1] == null ? values[1] = duration : values[1] = values[1]! + (_comma + duration); - values[2] == null ? values[2] = timingFuction : values[2] = values[2]! + (_comma + timingFuction); + values[2] == null ? values[2] = timingFunction : values[2] = values[2]! + (_comma + timingFunction); values[3] == null ? values[3] = delay : values[3] = values[3]! + (_comma + delay); } @@ -635,15 +637,16 @@ class CSSStyleProperty { } static List? _getFlexFlowValues(String shorthandProperty) { - List values = shorthandProperty.split(_spaceRegExp); + List values = _splitBySpace(shorthandProperty); String? direction; String? wrap; for (String value in values) { - if (direction == null && CSSFlex.isValidFlexDirectionValue(value)) { + final bool isValueVariableFunction = CSSFunction.isFunction(value, functionName: VAR); + if (direction == null && (isValueVariableFunction || CSSFlex.isValidFlexDirectionValue(value))) { direction = value; - } else if (wrap == null && CSSFlex.isValidFlexWrapValue(value)) { + } else if (wrap == null && (isValueVariableFunction || CSSFlex.isValidFlexWrapValue(value))) { wrap = value; } else { return null; @@ -654,7 +657,7 @@ class CSSStyleProperty { } static List? _getFlexValues(String shorthandProperty) { - List values = shorthandProperty.split(_spaceRegExp); + List values = _splitBySpace(shorthandProperty); // In flex shorthand case it is interpreted as flex: 1 0; String? grow; @@ -681,11 +684,12 @@ class CSSStyleProperty { } } - if (grow == null && CSSNumber.isNumber(value)) { + final bool isValueVariableFunction = CSSFunction.isFunction(value, functionName: VAR); + if (grow == null && (isValueVariableFunction || CSSNumber.isNumber(value))) { grow = value; - } else if (shrink == null && CSSNumber.isNumber(value)) { + } else if (shrink == null && (isValueVariableFunction || CSSNumber.isNumber(value))) { shrink = value; - } else if (basis == null && ((CSSLength.isLength(value) || value == AUTO))) { + } else if (basis == null && ((isValueVariableFunction || CSSLength.isNonNegativeLength(value) || value == AUTO))) { basis = value; } else { return null; @@ -696,7 +700,7 @@ class CSSStyleProperty { } static List? _getBorderValues(String shorthandProperty) { - List values = shorthandProperty.split(_spaceRegExp); + List values = _splitBySpace(shorthandProperty); String? width; String? style; @@ -704,11 +708,12 @@ class CSSStyleProperty { // NOTE: if one of token is wrong like `1pxxx solid red` that all should not work for (String value in values) { - if (width == null && CSSBorderSide.isValidBorderWidthValue(value)) { + final bool isValueVariableFunction = CSSFunction.isFunction(value, functionName: VAR); + if (width == null && (isValueVariableFunction || CSSBorderSide.isValidBorderWidthValue(value))) { width = value; - } else if (style == null && CSSBorderSide.isValidBorderStyleValue(value)) { + } else if (style == null && (isValueVariableFunction || CSSBorderSide.isValidBorderStyleValue(value))) { style = value; - } else if (color == null && CSSColor.isColor(value)) { + } else if (color == null && (isValueVariableFunction || CSSColor.isColor(value))) { color = value; } else { return null; @@ -718,8 +723,12 @@ class CSSStyleProperty { return [width, style, color]; } - static List? _getEdgeValues(String shorthandProperty, {bool isLengthOrPercentage = true}) { - var properties = shorthandProperty.split(_spaceRegExp); + static List? _getEdgeValues(String shorthandProperty, { + bool isLengthOrPercentage = false, + bool isNonNegativeLengthOrPercentage = false, + bool isNonNegativeLength = false, + }) { + List properties = _splitBySpace(shorthandProperty); String? topValue; String? rightValue; @@ -742,6 +751,13 @@ class CSSStyleProperty { leftValue = properties[3]; } + if (topValue != null && CSSFunction.isFunction(topValue) && + rightValue != null && CSSFunction.isFunction(rightValue) && + bottomValue != null && CSSFunction.isFunction(bottomValue) && + leftValue != null && CSSFunction.isFunction(leftValue)) { + return [topValue, rightValue, bottomValue, leftValue]; + } + if (isLengthOrPercentage) { if ((!CSSLength.isLength(topValue) && !CSSPercentage.isPercentage(topValue)) || (!CSSLength.isLength(rightValue) && !CSSPercentage.isPercentage(rightValue)) || @@ -749,6 +765,20 @@ class CSSStyleProperty { (!CSSLength.isLength(leftValue) && !CSSPercentage.isPercentage(leftValue))) { return null; } + } else if (isNonNegativeLengthOrPercentage) { + if ((!CSSLength.isNonNegativeLength(topValue) && !CSSPercentage.isNonNegativePercentage(topValue)) || + (!CSSLength.isNonNegativeLength(rightValue) && !CSSPercentage.isNonNegativePercentage(rightValue)) || + (!CSSLength.isNonNegativeLength(bottomValue) && !CSSPercentage.isNonNegativePercentage(bottomValue))|| + (!CSSLength.isNonNegativeLength(leftValue) && !CSSPercentage.isNonNegativePercentage(leftValue))) { + return null; + } + } else if (isNonNegativeLength) { + if ((!CSSLength.isNonNegativeLength(topValue)) || + (!CSSLength.isNonNegativeLength(rightValue)) || + (!CSSLength.isNonNegativeLength(bottomValue))|| + (!CSSLength.isNonNegativeLength(leftValue))) { + return null; + } } // Assume the properties are in the usual order top, right, bottom, left. @@ -757,7 +787,7 @@ class CSSStyleProperty { // https://drafts.csswg.org/css-values-4/#typedef-position static List getPositionValues(String shorthandProperty) { - var properties = shorthandProperty.trim().split(_spaceRegExp); + List properties = _splitBySpace(shorthandProperty.trim()); String? x; String? y; diff --git a/kraken/lib/src/css/text.dart b/kraken/lib/src/css/text.dart index 83804fe1e9..7df89838c9 100644 --- a/kraken/lib/src/css/text.dart +++ b/kraken/lib/src/css/text.dart @@ -12,19 +12,20 @@ final RegExp _commaRegExp = RegExp(r'\s*,\s*'); // CSS Text: https://drafts.csswg.org/css-text-3/ // CSS Text Decoration: https://drafts.csswg.org/css-text-decor-3/ // CSS Box Alignment: https://drafts.csswg.org/css-align/ -mixin CSSTextMixin on RenderStyleBase { - +mixin CSSTextMixin on RenderStyle { bool get hasColor => _color != null; + @override + Color get currentColor => color; + Color? _color; + + @override Color get color { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_color == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.color; - } + if (_color == null && parent != null) { + return parent!.color; } // The root element has no color, and the color is initial. @@ -39,18 +40,18 @@ mixin CSSTextMixin on RenderStyleBase { } // Current not update the dependent property relative to the color. - final Map _colorRealativeProperties = {}; + final Map _colorRelativeProperties = {}; + @override void addColorRelativeProperty(String propertyName) { - _colorRealativeProperties[propertyName] = true; + _colorRelativeProperties[propertyName] = true; } void updateColorRelativeProperty() { - if (_colorRealativeProperties.isEmpty) return; - RenderStyle renderStyle = this as RenderStyle; - _colorRealativeProperties.forEach((String propertyName, _) { + if (_colorRelativeProperties.isEmpty) return; + _colorRelativeProperties.forEach((String propertyName, _) { // TODO: use css color abstraction avoid re-parse the property string. - renderStyle.target.setRenderStyle(propertyName, renderStyle.target.style.getPropertyValue(propertyName)); + target.setRenderStyle(propertyName, target.style.getPropertyValue(propertyName)); }); } @@ -86,14 +87,12 @@ mixin CSSTextMixin on RenderStyleBase { } FontWeight? _fontWeight; + @override FontWeight get fontWeight { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_fontWeight == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.fontWeight; - } + if (_fontWeight == null && parent != null) { + return parent!.fontWeight; } // The root element has no fontWeight, and the fontWeight is initial. @@ -107,14 +106,13 @@ mixin CSSTextMixin on RenderStyleBase { } FontStyle? _fontStyle; + + @override FontStyle get fontStyle { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_fontStyle == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.fontStyle; - } + if (_fontStyle == null && parent != null) { + return parent!.fontStyle; } // The root element has no fontWeight, and the fontWeight is initial. @@ -128,14 +126,13 @@ mixin CSSTextMixin on RenderStyleBase { } List? _fontFamily; + + @override List? get fontFamily { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_fontFamily == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.fontFamily; - } + if (_fontFamily == null && parent != null) { + return parent!.fontFamily; } return _fontFamily ?? CSSText.DEFAULT_FONT_FAMILY_FALLBACK; } @@ -149,14 +146,13 @@ mixin CSSTextMixin on RenderStyleBase { bool get hasFontSize => _fontSize != null; CSSLengthValue? _fontSize; + + @override CSSLengthValue get fontSize { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_fontSize == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.fontSize; - } + if (_fontSize == null && parent != null) { + return parent!.fontSize; } return _fontSize ?? CSSText.DEFAULT_FONT_SIZE; } @@ -172,34 +168,35 @@ mixin CSSTextMixin on RenderStyleBase { } // Current not update the dependent property relative to the font-size. - final Map _fontRealativeProperties = {}; - final Map _rootFontRealativeProperties = {}; + final Map _fontRelativeProperties = {}; + final Map _rootFontRelativeProperties = {}; + @override void addFontRelativeProperty(String propertyName) { - _fontRealativeProperties[propertyName] = true; + _fontRelativeProperties[propertyName] = true; } void updateFontRelativeLength() { - if (_fontRealativeProperties.isEmpty) return; + if (_fontRelativeProperties.isEmpty) return; renderBoxModel?.markNeedsLayout(); } + @override void addRootFontRelativeProperty(String propertyName) { - _rootFontRealativeProperties[propertyName] = true; + _rootFontRelativeProperties[propertyName] = true; } void updateRootFontRelativeLength() { - if (_rootFontRealativeProperties.isEmpty) return; + if (_rootFontRelativeProperties.isEmpty) return; renderBoxModel?.markNeedsLayout(); } CSSLengthValue? _lineHeight; + + @override CSSLengthValue get lineHeight { - if (_lineHeight == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.lineHeight; - } + if (_lineHeight == null && parent != null) { + return parent!.lineHeight; } return _lineHeight ?? CSSText.DEFAULT_LINE_HEIGHT; @@ -213,14 +210,13 @@ mixin CSSTextMixin on RenderStyleBase { } CSSLengthValue? _letterSpacing; + + @override CSSLengthValue? get letterSpacing { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_letterSpacing == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.letterSpacing; - } + if (_letterSpacing == null && parent != null) { + return parent!.letterSpacing; } return _letterSpacing; } @@ -232,14 +228,13 @@ mixin CSSTextMixin on RenderStyleBase { } CSSLengthValue? _wordSpacing; + + @override CSSLengthValue? get wordSpacing { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_wordSpacing == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.wordSpacing; - } + if (_wordSpacing == null && parent != null) { + return parent!.wordSpacing; } return _wordSpacing; } @@ -251,14 +246,13 @@ mixin CSSTextMixin on RenderStyleBase { } List? _textShadow; + + @override List? get textShadow { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_textShadow == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.textShadow; - } + if (_textShadow == null && parent != null) { + return parent!.textShadow; } return _textShadow; } @@ -270,14 +264,13 @@ mixin CSSTextMixin on RenderStyleBase { } WhiteSpace? _whiteSpace; + + @override WhiteSpace get whiteSpace { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_whiteSpace == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.whiteSpace; - } + if (_whiteSpace == null && parent != null) { + return parent!.whiteSpace; } return _whiteSpace ?? WhiteSpace.normal; } @@ -289,6 +282,8 @@ mixin CSSTextMixin on RenderStyleBase { } TextOverflow _textOverflow = TextOverflow.clip; + + @override TextOverflow get textOverflow { return _textOverflow; } @@ -299,7 +294,27 @@ mixin CSSTextMixin on RenderStyleBase { _markTextNeedsLayout(); } + // text-overflow is affect by the value of line-clamp,overflow and white-space styles, + // get the real working style of text-overflow after other style is set. + TextOverflow get effectiveTextOverflow { + // Set line-clamp to number makes text-overflow ellipsis which takes priority over text-overflow style. + if (lineClamp != null && lineClamp! > 0) { + return TextOverflow.ellipsis; + } + + // text-overflow only works when overflow is not visible and white-space is nowrap. + if (effectiveOverflowX == CSSOverflowType.visible || whiteSpace != WhiteSpace.nowrap) { + // TextOverflow.visible value does not exist in CSS spec, use it to specify the case + // when text overflow its container and not clipped. + return TextOverflow.visible; + } + + return textOverflow; + } + int? _lineClamp; + + @override int? get lineClamp { return _lineClamp; } @@ -311,14 +326,13 @@ mixin CSSTextMixin on RenderStyleBase { } TextAlign? _textAlign; + + @override TextAlign get textAlign { // Get style from self or closest parent if specified style property is not set // due to style inheritance. - if (_textAlign == null) { - RenderStyle renderStyle = this as RenderStyle; - if (renderStyle.parent != null) { - return renderStyle.parent!.textAlign; - } + if (_textAlign == null && parent != null) { + return parent!.textAlign; } return _textAlign ?? TextAlign.start; } @@ -417,7 +431,9 @@ mixin CSSTextMixin on RenderStyleBase { return alignment; } - static TextSpan createTextSpan(String? text, RenderStyle renderStyle) { + static TextSpan createTextSpan(String? text, CSSRenderStyle renderStyle, { + Color? color, + }) { /// Creates a new TextStyle object. /// color: The color to use when painting the text. If this is specified, foreground must be null. /// decoration: The decorations to paint near the text (e.g., an underline). @@ -434,7 +450,7 @@ mixin CSSTextMixin on RenderStyleBase { /// background: The paint drawn as a background for the text. /// foreground: The paint used to draw the text. If this is specified, color must be null. TextStyle textStyle = TextStyle( - color: renderStyle.color, + color: color ?? renderStyle.color, decoration: renderStyle.textDecorationLine, decorationColor: renderStyle.textDecorationColor, decorationStyle: renderStyle.textDecorationStyle, @@ -474,7 +490,7 @@ class CSSText { } static bool isValidLineHeightValue(String value) { - return CSSLength.isLength(value) || CSSPercentage.isPercentage(value) || + return CSSLength.isNonNegativeLength(value) || CSSPercentage.isNonNegativePercentage(value) || value == 'normal' || double.tryParse(value) != null; } @@ -489,7 +505,7 @@ class CSSText { static CSSLengthValue DEFAULT_LINE_HEIGHT = CSSLengthValue.normal; static CSSLengthValue? resolveLineHeight(String value, RenderStyle renderStyle, String propertyName) { if (value.isNotEmpty) { - if (CSSLength.isLength(value) || CSSPercentage.isPercentage(value)) { + if (CSSLength.isNonNegativeLength(value) || CSSPercentage.isNonNegativePercentage(value)) { CSSLengthValue lineHeight = CSSLength.parseLength(value, renderStyle, propertyName); // Line-height 0 and negative value is considered invalid. if (lineHeight.computedValue != double.infinity && lineHeight.computedValue > 0) { diff --git a/kraken/lib/src/css/transform.dart b/kraken/lib/src/css/transform.dart index f55fe68ce9..d7139ab177 100644 --- a/kraken/lib/src/css/transform.dart +++ b/kraken/lib/src/css/transform.dart @@ -9,7 +9,7 @@ import 'package:kraken/rendering.dart'; import 'package:vector_math/vector_math_64.dart'; // CSS Transforms: https://drafts.csswg.org/css-transforms/ -mixin CSSTransformMixin on RenderStyleBase { +mixin CSSTransformMixin on RenderStyle { static Offset DEFAULT_TRANSFORM_OFFSET = Offset(0, 0); static Alignment DEFAULT_TRANSFORM_ALIGNMENT = Alignment.center; @@ -35,12 +35,13 @@ mixin CSSTransformMixin on RenderStyleBase { _transformMatrix = null; // Transform effect the stacking context. - RenderBoxModel? parentRenderer = (this as RenderStyle).parent?.renderBoxModel; + RenderBoxModel? parentRenderer = parent?.renderBoxModel; if (parentRenderer is RenderLayoutBox) { parentRenderer.markChildrenNeedsSort(); } - renderBoxModel!.markNeedsLayout(); + // Transform stage are applied at paint stage, should avoid re-layout. + renderBoxModel?.markNeedsPaint(); } static List? resolveTransform(String present) { @@ -52,7 +53,7 @@ mixin CSSTransformMixin on RenderStyleBase { Matrix4? get transformMatrix { if (_transformMatrix == null && _transform != null) { // Illegal transform syntax will return null. - _transformMatrix = CSSMatrix.computeTransformMatrix(_transform!, this as RenderStyle); + _transformMatrix = CSSMatrix.computeTransformMatrix(_transform!, this); } return _transformMatrix; } @@ -60,7 +61,7 @@ mixin CSSTransformMixin on RenderStyleBase { set transformMatrix(Matrix4? value) { if (value == null || _transformMatrix == value) return; _transformMatrix = value; - renderBoxModel!.markNeedsLayout(); + renderBoxModel?.markNeedsPaint(); } Offset get transformOffset => _transformOffset; @@ -68,7 +69,7 @@ mixin CSSTransformMixin on RenderStyleBase { set transformOffset(Offset value) { if (_transformOffset == value) return; _transformOffset = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } Alignment get transformAlignment => _transformAlignment; @@ -76,7 +77,7 @@ mixin CSSTransformMixin on RenderStyleBase { set transformAlignment(Alignment value) { if (_transformAlignment == value) return; _transformAlignment = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } CSSOrigin? _transformOrigin; diff --git a/kraken/lib/src/css/transition.dart b/kraken/lib/src/css/transition.dart index fdd87d66b0..403ba21ba2 100644 --- a/kraken/lib/src/css/transition.dart +++ b/kraken/lib/src/css/transition.dart @@ -58,7 +58,7 @@ double? _parseLength(String length, RenderStyle renderStyle, String property) { return CSSLength.parseLength(length, renderStyle, property).computedValue; } -void _updateLength(double oldLengthValue, double newLengthValue, double progress, String property, RenderStyle renderStyle) { +void _updateLength(double oldLengthValue, double newLengthValue, double progress, String property, CSSRenderStyle renderStyle) { double value = oldLengthValue * (1 - progress) + newLengthValue * progress; renderStyle.target.setRenderStyleProperty(property, CSSLengthValue(value, CSSLengthType.PX)); } @@ -67,7 +67,7 @@ FontWeight _parseFontWeight(String fontWeight, RenderStyle renderStyle, String p return CSSText.resolveFontWeight(fontWeight); } -void _updateFontWeight(FontWeight oldValue, FontWeight newValue, double progress, String property, RenderStyle renderStyle) { +void _updateFontWeight(FontWeight oldValue, FontWeight newValue, double progress, String property, CSSRenderStyle renderStyle) { FontWeight? fontWeight = FontWeight.lerp(oldValue, newValue, progress); switch (property) { case FONT_WEIGHT: @@ -96,7 +96,7 @@ double _parseLineHeight(String lineHeight, RenderStyle renderStyle, String prope return CSSLength.parseLength(lineHeight, renderStyle, LINE_HEIGHT).computedValue; } -void _updateLineHeight(double oldValue, double newValue, double progress, String property, RenderStyle renderStyle) { +void _updateLineHeight(double oldValue, double newValue, double progress, String property, CSSRenderStyle renderStyle) { renderStyle.lineHeight = CSSLengthValue(_getNumber(oldValue, newValue, progress), CSSLengthType.PX); } @@ -104,7 +104,7 @@ Matrix4? _parseTransform(String value, RenderStyle renderStyle, String property) return CSSMatrix.computeTransformMatrix(CSSFunction.parseFunction(value), renderStyle); } -void _updateTransform(Matrix4 begin, Matrix4 end, double t, String property, RenderStyle renderStyle) { +void _updateTransform(Matrix4 begin, Matrix4 end, double t, String property, CSSRenderStyle renderStyle) { Matrix4 newMatrix4 = CSSMatrix.lerpMatrix(begin, end, t); renderStyle.transformMatrix = newMatrix4; } @@ -116,7 +116,7 @@ const List _numberHandler = [_parseNumber, _updateNumber]; const List _lineHeightHandler = [_parseLineHeight, _updateLineHeight]; const List _transformHandler = [_parseTransform, _updateTransform]; -Map> CSSTranstionHandlers = { +Map> CSSTransitionHandlers = { COLOR: _colorHandler, BACKGROUND_COLOR: _colorHandler, BORDER_BOTTOM_COLOR: _colorHandler, @@ -179,7 +179,7 @@ enum CSSTransitionEvent { cancel, } -mixin CSSTransitionMixin on RenderStyleBase { +mixin CSSTransitionMixin on RenderStyle { // https://drafts.csswg.org/css-transitions/#transition-property-property // Name: transition-property @@ -199,8 +199,9 @@ mixin CSSTransitionMixin on RenderStyleBase { // Any animation found in previousAnimations but not found in newAnimations is not longer current and should be canceled. // @HACK: There are no way to get animationList from styles(Webkit will create an new Style object when style changes, but Kraken not). // Therefore we should cancel all running transition to get thing works. - finishRunningTransiton(); + finishRunningTransition(); } + @override List get transitionProperty => _transitionProperty ?? const [ALL]; // https://drafts.csswg.org/css-transitions/#transition-duration-property @@ -218,6 +219,7 @@ mixin CSSTransitionMixin on RenderStyleBase { _transitionDuration = value; _effectiveTransitions = null; } + @override List get transitionDuration => _transitionDuration ?? const [_0s]; // https://drafts.csswg.org/css-transitions/#transition-timing-function-property @@ -235,6 +237,7 @@ mixin CSSTransitionMixin on RenderStyleBase { _transitionTimingFunction = value; _effectiveTransitions = null; } + @override List get transitionTimingFunction => _transitionTimingFunction ?? const [EASE]; // https://drafts.csswg.org/css-transitions/#transition-delay-property @@ -252,6 +255,7 @@ mixin CSSTransitionMixin on RenderStyleBase { _transitionDelay = value; _effectiveTransitions = null; } + @override List get transitionDelay => _transitionDelay ?? const [_0s]; Map? _effectiveTransitions; @@ -277,7 +281,7 @@ mixin CSSTransitionMixin on RenderStyleBase { } // Transition does not work when renderBoxModel has not been layout yet. - if (renderBoxModel != null && renderBoxModel!.hasSize && CSSTranstionHandlers[property] != null && + if (renderBoxModel != null && renderBoxModel!.hasSize && CSSTransitionHandlers[property] != null && (effectiveTransitions.containsKey(property) || effectiveTransitions.containsKey(ALL))) { bool shouldTransition = false; // Transition will be disabled when all transition has transitionDuration as 0. @@ -299,6 +303,7 @@ mixin CSSTransitionMixin on RenderStyleBase { return _propertyRunningTransition.containsKey(property); } + @override String? removeAnimationProperty(String propertyName) { String? prevValue = EMPTY_STRING; @@ -317,7 +322,7 @@ mixin CSSTransitionMixin on RenderStyleBase { // An Event fired when a CSS transition has been cancelled. target.dispatchEvent(Event(EVENT_TRANSITION_CANCEL)); - + // Maybe set transition twice in a same frame. should check animationProperties has contains propertyName. if (_animationProperties.containsKey(propertyName)) { begin = _animationProperties[propertyName]; @@ -337,7 +342,7 @@ mixin CSSTransitionMixin on RenderStyleBase { Keyframe(propertyName, begin, 0, LINEAR), Keyframe(propertyName, end, 1, LINEAR), ]; - KeyframeEffect effect = KeyframeEffect(this as RenderStyle, target, keyframes, options); + KeyframeEffect effect = KeyframeEffect(this, target, keyframes, options); Animation animation = Animation(effect); _propertyRunningTransition[propertyName] = animation; @@ -356,11 +361,11 @@ mixin CSSTransitionMixin on RenderStyleBase { }; target.dispatchEvent(Event(EVENT_TRANSITION_RUN)); - + animation.play(); } - void cancelRunningTransiton() { + void cancelRunningTransition() { if (_propertyRunningTransition.isNotEmpty) { for (String property in _propertyRunningTransition.keys) { _propertyRunningTransition[property]!.cancel(); @@ -369,7 +374,7 @@ mixin CSSTransitionMixin on RenderStyleBase { } } - void finishRunningTransiton() { + void finishRunningTransition() { if (_propertyRunningTransition.isNotEmpty) { for (String property in _propertyRunningTransition.keys) { _propertyRunningTransition[property]!.finish(); diff --git a/kraken/lib/src/css/values/function.dart b/kraken/lib/src/css/values/function.dart index a2153ef77c..7a94833c06 100644 --- a/kraken/lib/src/css/values/function.dart +++ b/kraken/lib/src/css/values/function.dart @@ -7,7 +7,8 @@ import 'package:quiver/collection.dart'; -final _functionRegExp = RegExp(r'^[a-zA-Z_]+\(.+\)$', caseSensitive: false); +// DotAll means accept line terminators like `\n`. +final _functionRegExp = RegExp(r'^[a-zA-Z-_]+\(.+\)$', dotAll: true); final _functionStart = '('; final _functionEnd = ')'; final _functionNotationUrl = 'url'; @@ -20,30 +21,49 @@ final LinkedLruHashMap> _cachedParsedFunctio // ignore: public_member_api_docs class CSSFunction { - static bool isFunction(String value) { + static bool isFunction(String value, { String? functionName }) { + if (functionName != null) { + bool isMatch; + final int functionNameLength = functionName.length; + + if (value.length < functionNameLength) { + return false; + } + + for (int i = 0; i < functionNameLength; i++) { + isMatch = functionName.codeUnitAt(i) == value.codeUnitAt(i); + if (!isMatch) { + return false; + } + } + } + return _functionRegExp.hasMatch(value); } - static List parseFunction(String value) { + static List parseFunction(final String value) { if (_cachedParsedFunction.containsKey(value)) { return _cachedParsedFunction[value]!; } - var start = 0; - var left = value.indexOf(_functionStart, start); - List notations = []; - // function may contain function, should handle this situation + final int valueLength = value.length; + final List notations = []; + + int start = 0; + int left = value.indexOf(_functionStart, start); + + // Function may contain function, should handle this situation. while (left != -1 && start < left) { String fn = value.substring(start, left); int argsBeginIndex = left + 1; List argList = []; int argBeginIndex = argsBeginIndex; - // contains function count + // Contain function count. int containLeftCount = 0; bool match = false; - // find all args in this function - while (argsBeginIndex < value.length) { - // url() function notation should not be splitted cause it only accept one URL. + // Find all args in this function. + while (argsBeginIndex < valueLength) { + // url() function notation should not be split causing it only accept one URL. // https://drafts.csswg.org/css-values-3/#urls if (fn != _functionNotationUrl && value[argsBeginIndex] == FUNCTION_ARGS_SPLIT) { if (containLeftCount == 0 && argBeginIndex < argsBeginIndex) { @@ -60,7 +80,7 @@ class CSSFunction { argList.add(value.substring(argBeginIndex, argsBeginIndex)); argBeginIndex = argsBeginIndex + 1; } - // function parse success when find the matched right parenthesis + // Function parse success when find the matched right parenthesis. match = true; break; } @@ -68,7 +88,7 @@ class CSSFunction { argsBeginIndex++; } if (match) { - // only add the right function + // Only add the right function. fn = fn.trim(); if (fn.startsWith(FUNCTION_SPLIT)) { fn = fn.substring(1, ).trim(); diff --git a/kraken/lib/src/css/values/length.dart b/kraken/lib/src/css/values/length.dart index 14bcacc90d..8671cfbdbc 100644 --- a/kraken/lib/src/css/values/length.dart +++ b/kraken/lib/src/css/values/length.dart @@ -18,7 +18,10 @@ const _1Q = _1cm / 40; // 1Q = 1/40th of 1cm const _1pc = _1in / 6; // 1pc = 1/6th of 1in const _1pt = _1in / 72; // 1pt = 1/72th of 1in -final _lengthRegExp = RegExp(r'^[+-]?(\d+)?(\.\d+)?px|rpx|vw|vh|vmin|vmax|rem|em|in|cm|mm|pc|pt$', caseSensitive: false); +final String _unitRegStr = '(px|rpx|vw|vh|vmin|vmax|rem|em|in|cm|mm|pc|pt)'; +final _lengthRegExp = RegExp(r'^[+-]?(\d+)?(\.\d+)?' + _unitRegStr + r'$', caseSensitive: false); +final _negativeZeroRegExp = RegExp(r'^-(0+)?(\.0+)?' + _unitRegStr + r'$', caseSensitive: false); +final _nonNegativeLengthRegExp = RegExp(r'^[+]?(\d+)?(\.\d+)?' + _unitRegStr + r'$', caseSensitive: false); enum CSSLengthType { // absolute units @@ -121,14 +124,46 @@ class CSSLengthValue { bool isPositioned = positionType == CSSPositionType.absolute || positionType == CSSPositionType.fixed; - RenderStyle? parentRenderStyle = renderStyle!.parent; - - // Percentage relative width priority: logical width > renderer width - double? relativeParentWidth = isPositioned ? - parentRenderStyle?.paddingBoxLogicalWidth ?? parentRenderStyle?.paddingBoxWidth : - parentRenderStyle?.contentBoxLogicalWidth ?? parentRenderStyle?.contentBoxWidth; - RenderBoxModel? renderBoxModel = renderStyle!.renderBoxModel; + // Should access the renderStyle of renderBoxModel parent but not renderStyle parent + // cause the element of renderStyle parent may not equal to containing block. + RenderStyle? parentRenderStyle; + if (renderBoxModel?.parent is RenderBoxModel) { + RenderBoxModel parentRenderBoxModel = renderBoxModel?.parent as RenderBoxModel; + // Get the renderStyle of outer scrolling box cause the renderStyle of scrolling + // content box is only a fraction of the complete renderStyle. + parentRenderStyle = parentRenderBoxModel.isScrollingContentBox + ? (parentRenderBoxModel.parent as RenderBoxModel).renderStyle + : parentRenderBoxModel.renderStyle; + } + + // Constraints is calculated before layout, the layouted size is identical to the tight constraints + // if constraints is tight, so it's safe to use the tight constraints as the parent size to resolve + // the child percentage length to save one extra layout to wait for parent layout complete. + + // Percentage relative width priority: tight constraints width > renderer width > logical width + double? parentPaddingBoxWidth = parentRenderStyle?.paddingBoxConstraintsWidth + ?? parentRenderStyle?.paddingBoxWidth + ?? parentRenderStyle?.paddingBoxLogicalWidth; + double? parentContentBoxWidth = parentRenderStyle?.contentBoxConstraintsWidth + ?? parentRenderStyle?.contentBoxWidth + ?? parentRenderStyle?.contentBoxLogicalWidth; + // Percentage relative height priority: tight constraints height > renderer height > logical height + double? parentPaddingBoxHeight = parentRenderStyle?.paddingBoxConstraintsHeight + ?? parentRenderStyle?.paddingBoxHeight + ?? parentRenderStyle?.paddingBoxLogicalHeight; + double? parentContentBoxHeight = parentRenderStyle?.contentBoxConstraintsHeight + ?? parentRenderStyle?.contentBoxHeight + ?? parentRenderStyle?.contentBoxLogicalHeight; + + // Positioned element is positioned relative to the padding box of its containing block + // while the others relative to the content box. + double? relativeParentWidth = isPositioned + ? parentPaddingBoxWidth + : parentContentBoxWidth; + double? relativeParentHeight = isPositioned + ? parentPaddingBoxHeight + : parentContentBoxHeight; switch (propertyName) { case FONT_SIZE: @@ -173,11 +208,6 @@ class CSSLengthValue { // The percentage height of positioned element and flex item resolves against the rendered height // of parent, mark parent as needs relayout if rendered height is not ready yet. if (isPositioned || isGrandParentFlexLayout) { - // Percentage relative height priority: logical height > renderer height - double? relativeParentHeight = isPositioned ? - parentRenderStyle?.paddingBoxLogicalHeight ?? parentRenderStyle?.paddingBoxHeight : - parentRenderStyle?.contentBoxLogicalHeight ?? parentRenderStyle?.contentBoxHeight; - if (relativeParentHeight != null) { _computedValue = value! * relativeParentHeight; } else { @@ -240,8 +270,6 @@ class CSSLengthValue { case TOP: case BOTTOM: // Offset of positioned element starts from the edge of padding box of containing block. - double? parentPaddingBoxHeight = parentRenderStyle?.paddingBoxHeight ?? - parentRenderStyle?.paddingBoxLogicalHeight; if (parentPaddingBoxHeight != null) { _computedValue = value! * parentPaddingBoxHeight; } else { @@ -249,14 +277,13 @@ class CSSLengthValue { if (renderBoxModel != null) { renderBoxModel.markParentNeedsRelayout(); } + // Set as initial value, use infinity as auto value. _computedValue = double.infinity; } break; case LEFT: case RIGHT: // Offset of positioned element starts from the edge of padding box of containing block. - double? parentPaddingBoxWidth = parentRenderStyle?.paddingBoxWidth ?? - parentRenderStyle?.paddingBoxLogicalWidth; if (parentPaddingBoxWidth != null) { _computedValue = value! * parentPaddingBoxWidth; } else { @@ -266,7 +293,8 @@ class CSSLengthValue { } _computedValue = double.infinity; } - break; + break; + case TRANSLATE: case BACKGROUND_SIZE: case BORDER_TOP_LEFT_RADIUS: @@ -277,26 +305,16 @@ class CSSLengthValue { // Percentages for the vertical axis refer to the height of the box. double? borderBoxWidth = renderStyle!.borderBoxWidth ?? renderStyle!.borderBoxLogicalWidth; double? borderBoxHeight = renderStyle!.borderBoxHeight ?? renderStyle!.borderBoxLogicalHeight; - if (axisType == Axis.horizontal) { - if (borderBoxWidth != null) { - _computedValue = value! * borderBoxWidth; - } else { - // Mark parent to relayout to get renderer height of parent. - if (renderBoxModel != null) { - renderBoxModel.markParentNeedsRelayout(); - } - _computedValue = 0; - } - } else if (axisType == Axis.vertical) { - if (borderBoxHeight != null) { - _computedValue = value! * borderBoxHeight; - } else { - // Mark parent to relayout to get renderer height of parent. - if (renderBoxModel != null) { - renderBoxModel.markParentNeedsRelayout(); - } - _computedValue = 0; - } + double? borderBoxDimension = axisType == Axis.horizontal ? borderBoxWidth : borderBoxHeight; + + if (borderBoxDimension != null) { + _computedValue = value! * borderBoxDimension; + } else { + _computedValue = propertyName == TRANSLATE + // Transform will be cached once resolved, so avoid resolve if width not defined. + // Use double.infinity to indicate percentage not resolved. + ? double.infinity + : 0; } break; } @@ -335,7 +353,7 @@ class CSSLengthValue { } bool get isNotAuto { - return type != CSSLengthType.AUTO; + return !isAuto; } bool get isNone { @@ -404,7 +422,18 @@ class CSSLength { } static bool isLength(String? value) { - return value != null && (value == ZERO || _lengthRegExp.hasMatch(value)); + return value != null && ( + value == ZERO + || _lengthRegExp.hasMatch(value) + ); + } + + static bool isNonNegativeLength(String? value) { + return value != null && ( + value == ZERO + || _negativeZeroRegExp.hasMatch(value) // Negative zero is considered to be equal to zero. + || _nonNegativeLengthRegExp.hasMatch(value) + ); } static CSSLengthValue? resolveLength(String text, RenderStyle? renderStyle, String propertyName) { @@ -503,9 +532,7 @@ class CSSLength { // Using fallback value if not match user agent-defined environment variable: env(xxx, 50px). return parseLength(notations[0].args[1], renderStyle, propertyName, axisType); } - } - // TODO: impl CSS Variables. } if (value == 0) { diff --git a/kraken/lib/src/css/values/percentage.dart b/kraken/lib/src/css/values/percentage.dart index 95eea282a3..d935db17fa 100644 --- a/kraken/lib/src/css/values/percentage.dart +++ b/kraken/lib/src/css/values/percentage.dart @@ -8,6 +8,7 @@ import 'package:quiver/collection.dart'; final _percentageRegExp = RegExp(r'^[+-]?\d+[\.]?\d*\%$', caseSensitive: false); +final _nonNegativePercentageRegExp = RegExp(r'^[+]?\d+[\.]?\d*\%$', caseSensitive: false); final LinkedLruHashMap _cachedParsedPercentage = LinkedLruHashMap(maximumSize: 100); class CSSPercentage { @@ -27,4 +28,8 @@ class CSSPercentage { static bool isPercentage(String? percentageValue) { return percentageValue != null && _percentageRegExp.hasMatch(percentageValue); } + + static bool isNonNegativePercentage(String? percentageValue) { + return percentageValue != null && _nonNegativePercentageRegExp.hasMatch(percentageValue); + } } diff --git a/kraken/lib/src/css/values/url.dart b/kraken/lib/src/css/values/url.dart deleted file mode 100644 index 8c475c0fbe..0000000000 --- a/kraken/lib/src/css/values/url.dart +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -import 'dart:io'; - -import 'package:flutter/rendering.dart'; -import 'package:kraken/painting.dart'; - -// CSS Values and Units: https://drafts.csswg.org/css-values-3/#urls -class CSSUrl { - static ImageProvider? parseUrl(Uri resolvedUri, { cache = 'auto', int? contextId }) { - - ImageProvider? imageProvider; - if (resolvedUri.isScheme('HTTP') || resolvedUri.isScheme('HTTPS')) { - // @TODO: Caching also works after image downloaded. - ImageType cacheType = (cache == 'store' || cache == 'auto') - ? ImageType.cached - : ImageType.network; - imageProvider = getImageProviderFactory(cacheType)(resolvedUri, [contextId]); - } else if (resolvedUri.isScheme('FILE')) { - File file = File.fromUri(resolvedUri); - imageProvider = getImageProviderFactory(ImageType.file)(resolvedUri, file); - } else if (resolvedUri.isScheme('DATA')) { - // Data URL: https://tools.ietf.org/html/rfc2397 - // dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - UriData data = UriData.fromUri(resolvedUri); - if (data.isBase64) { - imageProvider = getImageProviderFactory(ImageType.dataUrl)(resolvedUri, data.contentAsBytes()); - } - } else if (resolvedUri.isScheme('BLOB')) { - // @TODO: support blob file url - imageProvider = getImageProviderFactory(ImageType.blob)(resolvedUri); - } else { - // Fallback to asset image - imageProvider = getImageProviderFactory(ImageType.assets)(resolvedUri); - } - - return imageProvider; - } -} diff --git a/kraken/lib/src/css/values/variable.dart b/kraken/lib/src/css/values/variable.dart new file mode 100644 index 0000000000..067d7edf40 --- /dev/null +++ b/kraken/lib/src/css/values/variable.dart @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/css.dart'; + +const int _HYPHEN_CODE = 45; // - + +// https://www.w3.org/TR/css-variables-1/#defining-variables +class CSSVariable { + + static bool isVariable(String? value) { + if (value == null) { + return false; + } + return value.length > 2 && value.codeUnitAt(0) == _HYPHEN_CODE && value.codeUnitAt(1) == _HYPHEN_CODE; + } + + // Try to parse CSSVariable. + static CSSVariable? tryParse(RenderStyle renderStyle, String propertyName, String propertyValue) { + // font-size: var(--x); + // font-size: var(--x, 28px); + if (CSSFunction.isFunction(propertyValue, functionName: VAR)) { + List fns = CSSFunction.parseFunction(propertyValue); + if (fns.first.args.isNotEmpty) { + if (fns.first.args.length > 1) { + // Has default value for CSS Variable. + return CSSVariable(fns.first.args.first, renderStyle, + defaultValue: fns.first.args.last); + } else { + return CSSVariable(fns.first.args.first, renderStyle); + } + } + } + } + + final String identifier; + final String? defaultValue; + final RenderStyle _renderStyle; + + CSSVariable(this.identifier, this._renderStyle, { this.defaultValue }); + + // Get the lazy calculated CSS resolved value. + dynamic computedValue(String propertyName) { + String? unsolvedValue = _renderStyle.getCSSVariable(identifier, propertyName) ?? defaultValue; + if (unsolvedValue == null) { + return null; + } + + var resolved = _renderStyle.resolveValue(propertyName, unsolvedValue); + if (resolved is CSSVariable) { + return resolved.computedValue(propertyName); + } else { + return resolved; + } + } + + @override + String toString() { + return 'var($identifier${defaultValue != null ? ', $defaultValue': ''})'; + } +} diff --git a/kraken/lib/src/css/variable.dart b/kraken/lib/src/css/variable.dart new file mode 100644 index 0000000000..c203147544 --- /dev/null +++ b/kraken/lib/src/css/variable.dart @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:collection'; +import 'render_style.dart'; + +mixin CSSVariableMixin on RenderStyle { + Map? _storage; + final Map> _propertyDependencies = {}; + + void _addDependency(String identifier, String propertyName) { + List? dep = _propertyDependencies[identifier]; + if (dep == null) { + _propertyDependencies[identifier] = [propertyName]; + } else if (!dep.contains(propertyName)) { + dep.add(propertyName); + } + } + + @override + String? getCSSVariable(String identifier, String propertyName) { + Map? storage = _storage; + _addDependency(identifier, propertyName); + + if (storage != null && storage.containsKey(identifier)) { + return storage[identifier]; + } else { + // Inherits from renderStyle tree. + return parent?.getCSSVariable(identifier, propertyName); + } + } + + // --x: red + // key: --x + // value: red + @override + void setCSSVariable(String identifier, String value) { + _storage ??= HashMap(); + _storage![identifier] = value; + + if (_propertyDependencies.containsKey(identifier)) { + _notifyCSSVariableChanged(_propertyDependencies[identifier]!, value); + } + } + + void _notifyCSSVariableChanged(List propertyNames, String value) { + propertyNames.forEach((String propertyName) { + target.setRenderStyle(propertyName, value); + visitChildren((CSSRenderStyle childRenderStyle) { + childRenderStyle._notifyCSSVariableChanged(propertyNames, value); + }); + }); + } +} diff --git a/kraken/lib/src/css/visibility.dart b/kraken/lib/src/css/visibility.dart index 67cfcc21db..d8758519a2 100644 --- a/kraken/lib/src/css/visibility.dart +++ b/kraken/lib/src/css/visibility.dart @@ -9,15 +9,16 @@ enum Visibility { hidden, } -mixin CSSVisibilityMixin on RenderStyleBase { +mixin CSSVisibilityMixin on RenderStyle { Visibility _visibility = Visibility.visible; void set visibility(Visibility value) { if (_visibility == value) return; _visibility = value; - renderBoxModel!.markNeedsPaint(); + renderBoxModel?.markNeedsPaint(); } + @override Visibility get visibility => _visibility; bool get isVisibilityHidden { diff --git a/kraken/lib/src/devtools/inspector.dart b/kraken/lib/src/devtools/inspector.dart new file mode 100644 index 0000000000..355ef3e000 --- /dev/null +++ b/kraken/lib/src/devtools/inspector.dart @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'dart:convert'; +import 'dart:io'; + +import 'package:kraken/devtools.dart'; + +const String INSPECTOR_URL = 'devtools://devtools/bundled/inspector.html'; +const int INSPECTOR_DEFAULT_PORT = 9222; +const String INSPECTOR_DEFAULT_ADDRESS = '127.0.0.1'; + +typedef NativeInspectorMessageHandler = void Function(String message); + +class DOMUpdatedEvent extends InspectorEvent { + @override + String get method => 'DOM.documentUpdated'; + + @override + JSONEncodable? get params => null; +} + +class InspectorServerInit { + final int port; + final String address; + final String bundleURL; + final int contextId; + + InspectorServerInit(this.contextId, this.port, this.address, this.bundleURL); +} + +class InspectorServerStart { } + +class InspectorFrontEndMessage { + InspectorFrontEndMessage(this.id, this.module, this.method, this.params); + int? id; + String module; + String method; + final Map? params; +} + +class InspectorMethodResult { + final int? id; + final Map? result; + InspectorMethodResult(this.id, this.result); +} + +class InspectorPostTaskMessage { + int context; + int callback; + InspectorPostTaskMessage(this.context, this.callback); +} + +class InspectorReload { + int contextId; + InspectorReload(this.contextId); +} + +class UIInspector { + final ChromeDevToolsService devtoolsService; + final Map moduleRegistrar = {}; + + UIInspector(this.devtoolsService) { + registerModule(InspectDOMModule(devtoolsService)); + registerModule(InspectOverlayModule(devtoolsService)); + registerModule(InspectPageModule(devtoolsService)); + registerModule(InspectCSSModule(devtoolsService)); + registerModule(InspectNetworkModule(devtoolsService)); + } + + void registerModule(UIInspectorModule module) { + moduleRegistrar[module.name] = module; + } + + void onServerStart(int port) async { + String remoteAddress = await UIInspector.getConnectedLocalNetworkAddress(); + String inspectorURL = '$INSPECTOR_URL?ws=$remoteAddress:$port'; + + print('Kraken DevTool listening at ws://$remoteAddress:$port'); + print('Open Chrome/Edge and enter following url to your navigator:'); + print(' $inspectorURL'); + } + + void messageRouter(int? id, String module, String method, Map? params) { + if (moduleRegistrar.containsKey(module)) { + moduleRegistrar[module]!.invoke(id, method, params); + } + } + + void onDOMTreeChanged() { + devtoolsService.isolateServerPort?.send(DOMUpdatedEvent()); + } + + void dispose() { + moduleRegistrar.clear(); + } + + static Future getConnectedLocalNetworkAddress() async { + List interfaces = await NetworkInterface.list( + includeLoopback: false, type: InternetAddressType.IPv4); + + String result = INSPECTOR_DEFAULT_ADDRESS; + for (NetworkInterface interface in interfaces) { + if (interface.name == 'en0' || interface.name == 'eth0' || interface.name == 'wlan0') { + result = interface.addresses.first.address; + break; + } + } + + return result; + } +} + +abstract class JSONEncodable { + Map toJson(); + + @override + String toString() { + return jsonEncode(toJson()); + } +} + +abstract class InspectorEvent extends JSONEncodable { + String get method; + JSONEncodable? get params; + InspectorEvent(); + + @override + Map toJson() { + return { + 'method': method, + 'params': params?.toJson() ?? {}, + }; + } +} + +class JSONEncodableMap extends JSONEncodable { + Map map; + JSONEncodableMap(this.map); + + @override + Map toJson() => map; +} diff --git a/kraken/lib/src/devtools/module.dart b/kraken/lib/src/devtools/module.dart new file mode 100644 index 0000000000..e908161138 --- /dev/null +++ b/kraken/lib/src/devtools/module.dart @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:convert'; + +import 'package:kraken/devtools.dart'; + +abstract class _InspectorModule { + String get name; + + bool _enable = false; + + void invoke(int? id, String method, Map? params) { + switch (method) { + case 'enable': + _enable = true; + sendToFrontend(id, null); + break; + case 'disable': + _enable = false; + sendToFrontend(id, null); + break; + + default: + if (_enable) receiveFromFrontend(id, method, params); + } + } + + void sendToFrontend(int? id, JSONEncodable? result); + void sendEventToFrontend(InspectorEvent event); + void receiveFromFrontend(int? id, String method, Map? params); +} + +// Inspector modules working on flutter.ui thread. +abstract class UIInspectorModule extends _InspectorModule { + final ChromeDevToolsService devtoolsService; + UIInspectorModule(this.devtoolsService); + + @override + void sendToFrontend(int? id, JSONEncodable? result) { + devtoolsService.isolateServerPort!.send(InspectorMethodResult(id, result?.toJson())); + } + + @override + void sendEventToFrontend(InspectorEvent event) { + devtoolsService.isolateServerPort!.send(event); + } +} + +// Inspector modules working on dart isolates +abstract class IsolateInspectorModule extends _InspectorModule { + IsolateInspectorModule(this.server); + + final IsolateInspectorServer server; + + @override + void sendToFrontend(int? id, JSONEncodable? result) { + server.sendToFrontend(id, result?.toJson()); + } + + @override + void sendEventToFrontend(InspectorEvent event) { + server.sendEventToFrontend(event); + } + + void callNativeInspectorMethod(int? id, String method, Map? params) { + assert(server.nativeInspectorMessageHandler != null); + server.nativeInspectorMessageHandler!(jsonEncode({'id': id, 'method': name + '.' + method, 'params': params})); + } +} diff --git a/kraken/lib/src/devtools/modules/css.dart b/kraken/lib/src/devtools/modules/css.dart new file mode 100644 index 0000000000..fe02c8acae --- /dev/null +++ b/kraken/lib/src/devtools/modules/css.dart @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/css.dart'; +import 'package:kraken/devtools.dart'; +import 'package:kraken/dom.dart'; + +const int INLINED_STYLESHEET_ID = 1; +const String ZERO_PX = '0px'; +RegExp _kebabCaseReg = RegExp(r'[A-Z]'); +RegExp _camelCaseReg = RegExp(r'-(\w)'); + +class InspectCSSModule extends UIInspectorModule { + Document get document => devtoolsService.controller!.view.document; + InspectCSSModule(ChromeDevToolsService devtoolsService): super(devtoolsService); + + @override + String get name => 'CSS'; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + switch (method) { + case 'getMatchedStylesForNode': + handleGetMatchedStylesForNode(id, params!); + break; + case 'getComputedStyleForNode': + handleGetComputedStyleForNode(id, params!); + break; + case 'getInlineStylesForNode': + handleGetInlineStylesForNode(id, params!); + break; + case 'setStyleTexts': + handleSetStyleTexts(id, params!); + break; + } + } + + void handleGetMatchedStylesForNode(int? id, Map params) { + int nodeId = params['nodeId']; + Element? element = document.controller.view.getEventTargetById(nodeId); + if (element != null) { + MatchedStyles matchedStyles = MatchedStyles( + inlineStyle: buildInlineStyle(element), + ); + sendToFrontend(id, matchedStyles); + } + } + + void handleGetComputedStyleForNode(int? id, Map params) { + int nodeId = params['nodeId']; + Element? element = document.controller.view.getEventTargetById(nodeId); + + if (element != null) { + ComputedStyle computedStyle = ComputedStyle( + computedStyle: buildComputedStyle(element), + ); + sendToFrontend(id, computedStyle); + } + } + + // Returns the styles defined inline (explicitly in the "style" attribute and + // implicitly, using DOM attributes) for a DOM node identified by nodeId. + void handleGetInlineStylesForNode(int? id, Map params) { + int nodeId = params['nodeId']; + Element? element = document.controller.view.getEventTargetById(nodeId); + + if (element != null) { + InlinedStyle inlinedStyle = InlinedStyle( + inlineStyle: buildInlineStyle(element), + attributesStyle: buildAttributesStyle(element.properties), + ); + sendToFrontend(id, inlinedStyle); + } + } + + void handleSetStyleTexts(int? id, Map params) { + List edits = params['edits']; + List styles = []; + + // @TODO: diff the inline style edits. + // @TODO: support comments for inline style. + for (Map edit in edits) { + // Use styleSheetId to identity element. + int nodeId = edit['styleSheetId']; + String text = edit['text'] ?? ''; + List texts = text.split(';'); + Element? element = document.controller.view.getEventTargetById(nodeId); + if (element != null) { + for (String kv in texts) { + kv = kv.trim(); + List _kv = kv.split(':'); + if (_kv.length == 2) { + String name = _kv[0].trim(); + String value = _kv[1].trim(); + element.setInlineStyle(_camelize(name), value); + } + } + styles.add(buildInlineStyle(element)); + } else { + styles.add(null); + } + } + + sendToFrontend(id, JSONEncodableMap({ + 'styles': styles, + })); + } + + static CSSStyle? buildInlineStyle(Element element) { + List cssProperties = []; + String cssText = ''; + element.inlineStyle.forEach((key, value) { + String kebabName = _kebabize(key); + String propertyValue = value.toString(); + String _cssText = '$kebabName: $propertyValue'; + CSSProperty cssProperty = CSSProperty( + name: kebabName, + value: value, + range: SourceRange( + startLine: 0, + startColumn: cssText.length, + endLine: 0, + endColumn: cssText.length + _cssText.length + 1, + ), + ); + cssText += '$_cssText; '; + cssProperties.add(cssProperty); + }); + + return CSSStyle( + // Absent for user agent stylesheet and user-specified stylesheet rules. + // Use hash code id to identity which element the rule belongs to. + styleSheetId: element.ownerDocument.controller.view.getTargetIdByEventTarget(element), + cssProperties: cssProperties, + shorthandEntries: [], + cssText: cssText, + range: SourceRange(startLine: 0, startColumn: 0, endLine: 0, endColumn: cssText.length) + ); + } + + static String resolveCSSDisplayString(CSSDisplay display) { + switch (display) { + case CSSDisplay.none: + return 'none'; + case CSSDisplay.sliver: + return 'sliver'; + case CSSDisplay.block: + return 'block'; + case CSSDisplay.inlineBlock: + return 'inline-block'; + case CSSDisplay.flex: + return 'flex'; + case CSSDisplay.inlineFlex: + return 'inline-flex'; + case CSSDisplay.inline: + default: + return 'inline'; + } + } + + static String _resolveCSSLengthType(CSSLengthType type) { + switch (type) { + case CSSLengthType.PX: + return 'px'; + case CSSLengthType.EM: + return 'em'; + case CSSLengthType.REM: + return 'rem'; + case CSSLengthType.VH: + return 'vh'; + case CSSLengthType.VW: + return 'vw'; + case CSSLengthType.VMIN: + return 'vmin'; + case CSSLengthType.VMAX: + return 'vmax'; + case CSSLengthType.PERCENTAGE: + return '%'; + case CSSLengthType.UNKNOWN: + return ''; + case CSSLengthType.AUTO: + return 'auto'; + case CSSLengthType.NONE: + return 'none'; + case CSSLengthType.NORMAL: + return 'normal'; + case CSSLengthType.INITIAL: + return 'initial'; + } + } + + static List buildComputedStyle(Element element) { + List computedStyle = []; + CSSStyleDeclaration style = element.style; + RenderStyle? renderStyle = element.renderStyle; + + for (int i = 0; i < style.length; i++) { + String propertyName = style.item(i); + String propertyValue = style.getPropertyValue(propertyName); + propertyName = _kebabize(propertyName); + + if (CSSLength.isLength(propertyValue)) { + CSSLengthValue? len = CSSLength.resolveLength( + propertyValue, + renderStyle, + propertyName, + ); + + propertyValue = len == null ? '0' : '${len.computedValue}${_resolveCSSLengthType(len.type)}'; + } + + if (propertyName == DISPLAY) { + propertyValue = resolveCSSDisplayString(element.renderStyle.display); + } + + computedStyle.add(CSSComputedStyleProperty(name: propertyName, value: propertyValue)); + } + + if (!style.contains(BORDER_TOP_STYLE)) { + computedStyle.add(CSSComputedStyleProperty(name: _kebabize(BORDER_TOP_STYLE), value: ZERO_PX)); + } + if (!style.contains(BORDER_RIGHT_STYLE)) { + computedStyle.add(CSSComputedStyleProperty(name: _kebabize(BORDER_RIGHT_STYLE), value: ZERO_PX)); + } + if (!style.contains(BORDER_BOTTOM_STYLE)) { + computedStyle.add(CSSComputedStyleProperty(name: _kebabize(BORDER_BOTTOM_STYLE), value: ZERO_PX)); + } + if (!style.contains(BORDER_LEFT_STYLE)) { + computedStyle.add(CSSComputedStyleProperty(name: _kebabize(BORDER_LEFT_STYLE), value: ZERO_PX)); + } + + // Calc computed size. + Map boundingClientRect = element.boundingClientRect.toJSON(); + boundingClientRect.forEach((String name, value) { + computedStyle.add(CSSComputedStyleProperty(name: name, value: '${value}px')); + }); + + return computedStyle; + } + + // Kraken not supports attribute style for now. + static CSSStyle? buildAttributesStyle(Map properties) { + return null; + } +} + +class MatchedStyles extends JSONEncodable { + + MatchedStyles({ + this.inlineStyle, + this.attributesStyle, + this.matchedCSSRules, + this.pseudoElements, + this.inherited, + this.cssKeyframesRules, + }); + + CSSStyle? inlineStyle; + CSSStyle? attributesStyle; + List? matchedCSSRules; + List? pseudoElements; + List? inherited; + List? cssKeyframesRules; + + @override + Map toJson() { + return { + if (inlineStyle != null) 'inlineStyle': inlineStyle, + if (attributesStyle != null) 'attributesStyle': attributesStyle, + if (matchedCSSRules != null) 'matchedCSSRules': matchedCSSRules, + if (pseudoElements != null) 'pseudoElements': pseudoElements, + if (inherited != null) 'inherited': inherited, + if (cssKeyframesRules != null) 'cssKeyframesRules': cssKeyframesRules, + }; + } +} + +class CSSStyle extends JSONEncodable { + int? styleSheetId; + List cssProperties; + List shorthandEntries; + String? cssText; + SourceRange? range; + + CSSStyle({ + this.styleSheetId, + required this.cssProperties, + required this.shorthandEntries, + this.cssText, + this.range, + }); + + @override + Map toJson() { + return { + if (styleSheetId != null) 'styleSheetId': styleSheetId, + 'cssProperties': cssProperties, + 'shorthandEntries': shorthandEntries, + if (cssText != null) 'cssText': cssText, + if (range != null) 'range': range, + }; + } +} + +class RuleMatch extends JSONEncodable { + @override + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} + +class PseudoElementMatches extends JSONEncodable { + @override + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} + +class InheritedStyleEntry extends JSONEncodable { + @override + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} + +class CSSKeyframesRule extends JSONEncodable { + @override + Map toJson() { + // TODO: implement toJson + throw UnimplementedError(); + } +} + +class CSSProperty extends JSONEncodable { + String name; + String value; + bool important; + bool implicit; + String? text; + bool parsedOk; + bool? disabled; + SourceRange? range; + + CSSProperty({ + required this.name, + required this.value, + this.important = false, + this.implicit = false, + this.text, + this.parsedOk = true, + this.disabled, + this.range, + }); + + @override + Map toJson() { + return { + 'name': name, + 'value': value, + 'important': important, + 'implicit': implicit, + 'text': text, + 'parsedOk': parsedOk, + if (disabled != null) 'disabled': disabled, + if (range != null) 'range': range, + }; + } +} + +class SourceRange extends JSONEncodable { + int startLine; + int startColumn; + int endLine; + int endColumn; + + SourceRange({ + required this.startLine, + required this.startColumn, + required this.endLine, + required this.endColumn, + }); + + @override + Map toJson() { + return { + 'startLine': startLine, + 'startColumn': startColumn, + 'endLine': endLine, + 'endColumn': endColumn, + }; + } +} + + +class ShorthandEntry extends JSONEncodable { + String name; + String value; + bool important; + + ShorthandEntry({ + required this.name, + required this.value, + this.important = false, + }); + + @override + Map toJson() { + return { + 'name': name, + 'value': value, + 'important': important, + }; + } +} + +/// https://chromedevtools.github.io/devtools-protocol/tot/CSS/#method-getComputedStyleForNode +class ComputedStyle extends JSONEncodable { + List computedStyle; + + ComputedStyle({ required this.computedStyle }); + + @override + Map toJson() { + return { + 'computedStyle': computedStyle, + }; + } +} + +/// https://chromedevtools.github.io/devtools-protocol/tot/CSS/#type-CSSComputedStyleProperty +class CSSComputedStyleProperty extends JSONEncodable { + String name; + String value; + + CSSComputedStyleProperty({ required this.name, required this.value }); + + @override + Map toJson() { + return { + 'name': name, + 'value': value, + }; + } +} + +class InlinedStyle extends JSONEncodable { + CSSStyle? inlineStyle; + CSSStyle? attributesStyle; + + InlinedStyle({ this.inlineStyle, this.attributesStyle }); + + @override + Map toJson() { + return { + if (inlineStyle != null) 'inlineStyle': inlineStyle, + if (attributesStyle != null) 'attributesStyle': attributesStyle, + }; + } +} + +// aB to a-b +String _kebabize(String str) { + return str.replaceAllMapped(_kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); +} + +// a-b to aB +String _camelize(String str) { + return str.replaceAllMapped(_camelCaseReg, (match) { + String subStr = match[0]!.substring(1); + return subStr.isNotEmpty ? subStr.toUpperCase() : ''; + }); +} diff --git a/kraken/lib/src/devtools/modules/debugger.dart b/kraken/lib/src/devtools/modules/debugger.dart new file mode 100644 index 0000000000..91ac4ca3c8 --- /dev/null +++ b/kraken/lib/src/devtools/modules/debugger.dart @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/devtools.dart'; + +class InspectDebuggerModule extends IsolateInspectorModule { + InspectDebuggerModule(IsolateInspectorServer server): super(server); + + @override + String get name => 'Debugger'; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + callNativeInspectorMethod(id, method, params); + } +} diff --git a/kraken/lib/src/devtools/modules/dom.dart b/kraken/lib/src/devtools/modules/dom.dart new file mode 100644 index 0000000000..864f9f7f63 --- /dev/null +++ b/kraken/lib/src/devtools/modules/dom.dart @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'dart:ui' as ui; + +import 'package:kraken/devtools.dart'; +import 'package:kraken/dom.dart'; +import 'package:kraken/rendering.dart'; +import 'package:flutter/rendering.dart'; + +const int DOCUMENT_NODE_ID = 0; +const String DEFAULT_FRAME_ID = 'main_frame'; + +class InspectDOMModule extends UIInspectorModule { + @override + String get name => 'DOM'; + + Document get document => devtoolsService.controller!.view.document; + InspectDOMModule(ChromeDevToolsService devtoolsService): super(devtoolsService); + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + switch (method) { + case 'getDocument': + onGetDocument(id, method, params); + break; + case 'getBoxModel': + onGetBoxModel(id, params!); + break; + case 'setInspectedNode': + onSetInspectedNode(id, params!); + break; + case 'getNodeForLocation': + onGetNodeForLocation(id, params!); + break; + } + } + + void onGetNodeForLocation(int? id, Map params) { + int x = params['x']; + int y = params['y']; + + RenderBox rootRenderObject = document.renderer!; + BoxHitTestResult result = BoxHitTestResult(); + rootRenderObject.hitTest(result, position: Offset(x.toDouble(), y.toDouble())); + if (result.path.first.target is RenderBoxModel) { + RenderBoxModel lastHitRenderBoxModel = result.path.first.target as RenderBoxModel; + int? targetId = document.controller.view.getTargetIdByEventTarget(lastHitRenderBoxModel.renderStyle.target); + sendToFrontend(id, JSONEncodableMap({ + 'backendId': targetId, + 'frameId': DEFAULT_FRAME_ID, + 'nodeId': targetId, + })); + } else { + sendToFrontend(id, null); + } + } + + /// Enables console to refer to the node with given id via $x + /// (see Command Line API for more details $x functions). + Node? inspectedNode; + + void onSetInspectedNode(int? id, Map params) { + int nodeId = params['nodeId']; + Node? node = document.controller.view.getEventTargetById(nodeId); + if (node != null) { + inspectedNode = node; + } + sendToFrontend(id, null); + } + + /// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getDocument + void onGetDocument(int? id, String method, Map? params) { + Node root = this.document.documentElement!; + InspectorDocument document = InspectorDocument( + InspectorNode(root) + ); + + sendToFrontend(id, document); + } + + void onGetBoxModel(int? id, Map params) { + int nodeId = params['nodeId']; + Element? element = document.controller.view.getEventTargetById(nodeId); + + // BoxModel design to BorderBox in kraken. + if (element != null && element.renderBoxModel != null && element.renderBoxModel!.hasSize) { + ui.Offset contentBoxOffset = element.renderBoxModel!.localToGlobal(ui.Offset.zero); + + int widthWithinBorder = element.renderBoxModel!.size.width.toInt(); + int heightWithinBorder = element.renderBoxModel!.size.height.toInt(); + List border = [ + contentBoxOffset.dx, contentBoxOffset.dy, + contentBoxOffset.dx + widthWithinBorder, contentBoxOffset.dy, + contentBoxOffset.dx + widthWithinBorder, contentBoxOffset.dy + heightWithinBorder, + contentBoxOffset.dx, contentBoxOffset.dy + heightWithinBorder, + ]; + List padding = [ + border[0] + (element.renderBoxModel!.renderStyle.borderLeftWidth?.computedValue ?? 0), border[1] + (element.renderBoxModel!.renderStyle.borderTopWidth?.computedValue ?? 0), + border[2] - (element.renderBoxModel!.renderStyle.borderRightWidth?.computedValue ?? 0), border[3] + (element.renderBoxModel!.renderStyle.borderTopWidth?.computedValue ?? 0), + border[4] - (element.renderBoxModel!.renderStyle.borderRightWidth?.computedValue ?? 0), border[5] - (element.renderBoxModel!.renderStyle.borderBottomWidth?.computedValue ?? 0), + border[6] + (element.renderBoxModel!.renderStyle.borderLeftWidth?.computedValue ?? 0), border[7] - (element.renderBoxModel!.renderStyle.borderBottomWidth?.computedValue ?? 0), + ]; + List content = [ + padding[0] + element.renderBoxModel!.renderStyle.paddingLeft.computedValue, padding[1] + element.renderBoxModel!.renderStyle.paddingTop.computedValue, + padding[2] - element.renderBoxModel!.renderStyle.paddingRight.computedValue, padding[3] + element.renderBoxModel!.renderStyle.paddingTop.computedValue, + padding[4] - element.renderBoxModel!.renderStyle.paddingRight.computedValue, padding[5] - element.renderBoxModel!.renderStyle.paddingBottom.computedValue, + padding[6] + element.renderBoxModel!.renderStyle.paddingLeft.computedValue, padding[7] - element.renderBoxModel!.renderStyle.paddingBottom.computedValue, + ]; + List margin = [ + border[0] - element.renderBoxModel!.renderStyle.marginLeft.computedValue, border[1] - element.renderBoxModel!.renderStyle.marginTop.computedValue, + border[2] + element.renderBoxModel!.renderStyle.marginRight.computedValue, border[3] - element.renderBoxModel!.renderStyle.marginTop.computedValue, + border[4] + element.renderBoxModel!.renderStyle.marginRight.computedValue, border[5] + element.renderBoxModel!.renderStyle.marginBottom.computedValue, + border[6] - element.renderBoxModel!.renderStyle.marginLeft.computedValue, border[7] + element.renderBoxModel!.renderStyle.marginBottom.computedValue, + ]; + + BoxModel boxModel = BoxModel( + content: content, + padding: padding, + border: border, + margin: margin, + width: widthWithinBorder, + height: heightWithinBorder, + ); + sendToFrontend(id, JSONEncodableMap({ + 'model': boxModel, + })); + } else { + sendToFrontend(id, null); + } + } +} + +class InspectorDocument extends JSONEncodable { + InspectorNode child; + + InspectorDocument(this.child); + + @override + Map toJson() { + var owner = child.referencedNode.ownerDocument; + return { + 'depth': 0, + 'root': { + 'nodeId': DOCUMENT_NODE_ID, + 'backendNodeId': DOCUMENT_NODE_ID, + 'nodeType': 9, + 'nodeName': '#document', + 'childNodeCount': 1, + 'children': [child.toJson()], + 'baseURL': owner.controller.href, + 'documentURL': owner.controller.href, + }, + }; + } +} + +/// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-Node +class InspectorNode extends JSONEncodable { + /// DOM interaction is implemented in terms of mirror objects that represent the actual + /// DOM nodes. DOMNode is a base node mirror type. + InspectorNode(this.referencedNode); + + /// Reference backend Kraken DOM Node. + Node referencedNode; + + /// Node identifier that is passed into the rest of the DOM messages as the nodeId. + /// Backend will only push node with given id once. It is aware of all requested nodes + /// and will only fire DOM events for nodes known to the client. + int? get nodeId => referencedNode.ownerDocument.controller.view.getTargetIdByEventTarget(referencedNode); + + /// Optional. The id of the parent node if any. + int get parentId { + if (referencedNode.parentNode != null) { + return referencedNode.ownerDocument.controller.view.getTargetIdByEventTarget(referencedNode.parentNode!) ?? 0; + } else { + return 0; + } + } + + /// The BackendNodeId for this node. + /// Unique DOM node identifier used to reference a node that may not have been pushed to + /// the front-end. + int backendNodeId = 0; + + /// [Node]'s nodeType. + int get nodeType => getNodeTypeValue(referencedNode.nodeType); + + /// Node's nodeName. + String get nodeName => referencedNode.nodeName.toLowerCase(); + + /// Node's localName. + String? localName; + + /// Node's nodeValue. + String get nodeValue { + if (referencedNode.nodeType == NodeType.TEXT_NODE) { + TextNode textNode = referencedNode as TextNode; + return textNode.data; + } else if (referencedNode.nodeType == NodeType.COMMENT_NODE) { + Comment comment = referencedNode as Comment; + return comment.data; + } else { + return ''; + } + } + + int get childNodeCount => referencedNode.childNodes.length; + + List? get attributes { + if (referencedNode.nodeType == NodeType.ELEMENT_NODE) { + List attrs = []; + Element el = referencedNode as Element; + el.properties.forEach((key, value) { + attrs.add(key); + attrs.add(value.toString()); + }); + return attrs; + } else { + return null; + } + } + + @override + Map toJson() { + return { + 'nodeId': nodeId, + 'backendNodeId': backendNodeId, + 'nodeType': nodeType, + 'localName': localName, + 'nodeName': nodeName, + 'nodeValue': nodeValue, + 'parentId': parentId, + 'childNodeCount': childNodeCount, + 'attributes': attributes, + if (childNodeCount > 0) + 'children': referencedNode.childNodes.map((Node node) => InspectorNode(node).toJson()).toList(), + }; + } +} + +class BoxModel extends JSONEncodable { + List? content; + List? padding; + List? border; + List? margin; + int? width; + int? height; + + BoxModel({ this.content, this.padding, this.border, this.margin, this.width, this.height }); + + @override + Map toJson() { + return { + 'content': content, + 'padding': padding, + 'border': border, + 'margin': content, + 'width': width, + 'height': height, + }; + } +} + +class Rect extends JSONEncodable { + num? x; + num? y; + num? width; + num? height; + + Rect({ this.x, this.y, this.width, this.height }); + + @override + Map toJson() { + return { + 'x': x, + 'y': y, + 'width': width, + 'height': height, + }; + } +} + diff --git a/kraken/lib/src/devtools/modules/log.dart b/kraken/lib/src/devtools/modules/log.dart new file mode 100644 index 0000000000..61c06461b1 --- /dev/null +++ b/kraken/lib/src/devtools/modules/log.dart @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/devtools.dart'; + +class InspectorLogModule extends IsolateInspectorModule { + InspectorLogModule(IsolateInspectorServer server): super(server); + + @override + String get name => 'Log'; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + // callNativeInspectorMethod(id, method, params); + } +} + + +class LogEntryEvent extends InspectorEvent { + // Allowed Values: xml, javascript, network, storage, appcache, + // rendering, security, deprecation, worker, violation, intervention, + // recommendation, other + String source; + + // Allowed Values: verbose, info, warning, error + String level; + + // The output text. + String text; + + String? url; + + LogEntryEvent({ + required this.level, + required this.text, + this.source = 'javascript', + this.url, + }); + + @override + String get method => 'Log.entryAdded'; + + @override + JSONEncodable? get params => JSONEncodableMap({ + 'entry': { + 'source': source, + 'level': level, + 'text': text, + 'timestamp': DateTime.now().millisecondsSinceEpoch, + if (url != null) 'url': url, + }, + }); +} diff --git a/kraken/lib/src/devtools/modules/network.dart b/kraken/lib/src/devtools/modules/network.dart new file mode 100644 index 0000000000..48f123bc4b --- /dev/null +++ b/kraken/lib/src/devtools/modules/network.dart @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:kraken/devtools.dart'; +import 'package:kraken/foundation.dart'; + +class InspectNetworkModule extends UIInspectorModule implements HttpClientInterceptor { + InspectNetworkModule(ChromeDevToolsService devtoolsService) : super(devtoolsService) { + _registerHttpClientInterceptor(); + } + + void _registerHttpClientInterceptor() { + setupHttpOverrides(this, contextId: devtoolsService.controller!.view.contextId); + } + + HttpClientInterceptor? get _customHttpClientInterceptor => devtoolsService.controller?.httpClientInterceptor; + + @override + String get name => 'Network'; + + final HttpCacheMode _httpCacheOriginalMode = HttpCacheController.mode; + final int _initialTimestamp = DateTime.now().millisecondsSinceEpoch; + // RequestId to data buffer. + final Map _responseBuffers = {}; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + switch (method) { + case 'setCacheDisabled': + bool cacheDisabled = params?['cacheDisabled']; + if (cacheDisabled) { + HttpCacheController.mode = HttpCacheMode.NO_CACHE; + } else { + HttpCacheController.mode = _httpCacheOriginalMode; + } + sendToFrontend(id, null); + break; + case 'getResponseBody': + String requestId = params!['requestId']; + Uint8List? buffer = _responseBuffers[requestId]; + sendToFrontend(id, JSONEncodableMap({ + if (buffer != null) 'body': utf8.decode(buffer), + // True, if content was sent as base64. + 'base64Encoded': false, + })); + break; + } + } + + @override + Future beforeRequest(HttpClientRequest request) { + sendEventToFrontend(NetworkRequestWillBeSentEvent( + requestId: _getRequestId(request), + loaderId: devtoolsService.controller!.view.contextId.toString(), + requestMethod: request.method, + url: request.uri.toString(), + headers: _getHttpHeaders(request.headers), + timestamp: (DateTime.now().millisecondsSinceEpoch - _initialTimestamp) ~/ 1000, + )); + HttpClientInterceptor? customHttpClientInterceptor = _customHttpClientInterceptor; + if (customHttpClientInterceptor != null) { + return customHttpClientInterceptor.beforeRequest(request); + } else { + return Future.value(null); + } + } + + @override + Future afterResponse(HttpClientRequest request, HttpClientResponse response) async { + String requestId = _getRequestId(request); + sendEventToFrontend(NetworkResponseReceivedEvent( + requestId: requestId, + loaderId: devtoolsService.controller!.view.contextId.toString(), + url: request.uri.toString(), + headers: _getHttpHeaders(request.headers), + status: response.statusCode, + statusText: response.reasonPhrase, + mimeType: response.headers.value(HttpHeaders.contentTypeHeader) ?? 'text/plain', + remoteIPAddress: response.connectionInfo!.remoteAddress.address, + remotePort: response.connectionInfo!.remotePort, + // HttpClientStreamResponse is the internal implementation for disk cache. + fromDiskCache: response is HttpClientStreamResponse, + encodedDataLength: response.contentLength, + protocol: request.uri.scheme, + type: _getRequestType(request), + timestamp: (DateTime.now().millisecondsSinceEpoch - _initialTimestamp) ~/ 1000, + )); + sendEventToFrontend(NetworkLoadingFinishedEvent( + requestId: requestId, + contentLength: response.contentLength, + timestamp: (DateTime.now().millisecondsSinceEpoch - _initialTimestamp) ~/ 1000, + )); + Uint8List data = await consolidateHttpClientResponseBytes(response); + _responseBuffers[requestId] = data; + + HttpClientStreamResponse proxyResponse = HttpClientStreamResponse( + Stream.value(data), + statusCode: response.statusCode, + reasonPhrase: response.reasonPhrase, + responseHeaders: _getHttpHeaders(response.headers)); + + HttpClientInterceptor? customHttpClientInterceptor = _customHttpClientInterceptor; + if (customHttpClientInterceptor != null) { + return customHttpClientInterceptor.afterResponse(request, proxyResponse); + } else { + return Future.value(proxyResponse); + } + } + + @override + Future shouldInterceptRequest(HttpClientRequest request) { + HttpClientInterceptor? customHttpClientInterceptor = _customHttpClientInterceptor; + if (customHttpClientInterceptor != null) { + return customHttpClientInterceptor.shouldInterceptRequest(request); + } else { + return Future.value(null); + } + } +} + +class NetworkRequestWillBeSentEvent extends InspectorEvent { + + final String requestId; + final String loaderId; + final String url; + final String requestMethod; + final Map headers; + final int timestamp; + + NetworkRequestWillBeSentEvent({ + required this.requestId, + required this.loaderId, + required this.requestMethod, + required this.url, + required this.headers, + required this.timestamp, + }); + + @override + String get method => 'Network.requestWillBeSent'; + + @override + JSONEncodable? get params => JSONEncodableMap({ + 'requestId': requestId, + 'loaderId': loaderId, + 'documentURL': '', + 'request': { + 'url': url, + 'method': requestMethod, + 'headers': headers, + 'initialPriority': 'Medium', + 'referrerPolicy': '', + }, + 'timestamp': timestamp, + 'initiator': { + 'type': 'script', + 'lineNumber': 0, + 'columnNumber': 0, + }, + 'redirectHasExtraInfo': false, + }); +} + +class NetworkResponseReceivedEvent extends InspectorEvent { + final String requestId; + final String loaderId; + final String url; + final Map headers; + final int status; + final String statusText; + final String mimeType; + final String remoteIPAddress; + final int remotePort; + final bool fromDiskCache; + final int encodedDataLength; + final String protocol; + final String type; + final int timestamp; + + NetworkResponseReceivedEvent({ + required this.requestId, + required this.loaderId, + required this.url, + required this.headers, + required this.status, + required this.statusText, + required this.mimeType, + required this.remoteIPAddress, + required this.remotePort, + required this.fromDiskCache, + required this.encodedDataLength, + required this.protocol, + required this.type, + required this.timestamp, + }); + + @override + String get method => 'Network.responseReceived'; + + @override + JSONEncodable? get params => JSONEncodableMap({ + 'requestId': requestId, + 'loaderId': loaderId, + 'timestamp': timestamp, + 'type': type, + 'response': { + 'url': url, + 'status': status, + 'statusText': statusText, + 'headers': headers, + 'mimeType': mimeType, + 'connectionReused': false, + 'connectionId': 0, + 'remoteIPAddress': remoteIPAddress, + 'remotePort': remotePort, + 'fromDiskCache': fromDiskCache, + 'encodedDataLength': encodedDataLength, + 'protocol': protocol, + 'securityState': 'secure', + }, + 'hasExtraInfo': false, + }); +} + +class NetworkLoadingFinishedEvent extends InspectorEvent { + final String requestId; + final int contentLength; + final int timestamp; + + NetworkLoadingFinishedEvent({ required this.requestId, required this.contentLength, required this.timestamp }); + + @override + String get method => 'Network.loadingFinished'; + + @override + JSONEncodable? get params => JSONEncodableMap({ + 'requestId': requestId, + 'timestamp': timestamp, + 'encodedDataLength': contentLength, + }); + +} + +Map _getHttpHeaders(HttpHeaders headers) { + Map map = {}; + headers.forEach((String name, values) { + map[name] = headers.value(name) ?? ''; + }); + return map; +} + +String _getRequestId(HttpClientRequest request) { + // @NOTE: For creating backend request, only uri is the same object reference. + // See http_client_request.dart [_createBackendClientRequest] + return request.uri.hashCode.toString(); +} + +// Allowed Values: Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR, Fetch, EventSource, WebSocket, +// Manifest, SignedExchange, Ping, CSPViolationReport, Preflight, Other +String _getRequestType(HttpClientRequest request) { + String urlPath = request.uri.path; + if (urlPath.endsWith('.js')) { + return 'Script'; + } else if (urlPath.endsWith('.css')) { + return 'Stylesheet'; + } else if (urlPath.endsWith('.jpg') || urlPath.endsWith('.png') + || urlPath.endsWith('.gif') || urlPath.endsWith('.webp')) { + return 'Image'; + } else if (urlPath.endsWith('.html') || urlPath.endsWith('.htm')) { + return 'Document'; + } else { + return 'Fetch'; + } +} diff --git a/kraken/lib/src/devtools/modules/overlay.dart b/kraken/lib/src/devtools/modules/overlay.dart new file mode 100644 index 0000000000..941dfafc30 --- /dev/null +++ b/kraken/lib/src/devtools/modules/overlay.dart @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/dom.dart'; +import 'package:kraken/devtools.dart'; + +class InspectOverlayModule extends UIInspectorModule { + @override + String get name => 'Overlay'; + + Document get document => devtoolsService.controller!.view.document; + InspectOverlayModule(ChromeDevToolsService devtoolsService): super(devtoolsService); + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + switch (method) { + case 'highlightNode': + onHighlightNode(id, params!); + break; + case 'hideHighlight': + onHideHighlight(id); + break; + } + } + + Element? _highlightElement; + /// https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-highlightNode + void onHighlightNode(int? id, Map params) { + _highlightElement?.debugHideHighlight(); + + int nodeId = params['nodeId']; + Element? element = document.controller.view.getEventTargetById(nodeId); + + if (element != null) { + element.debugHighlight(); + _highlightElement = element; + } + sendToFrontend(id, null); + } + + void onHideHighlight(int? id) { + _highlightElement?.debugHideHighlight(); + _highlightElement = null; + sendToFrontend(id, null); + } +} + diff --git a/kraken/lib/src/devtools/modules/page.dart b/kraken/lib/src/devtools/modules/page.dart new file mode 100644 index 0000000000..0a8540f4b9 --- /dev/null +++ b/kraken/lib/src/devtools/modules/page.dart @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:meta/meta.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:kraken/dom.dart'; +import 'package:kraken/devtools.dart'; + + +String enumKey(String key) { + return key.split('.').last; +} + +class PageScreenCastFrameEvent extends InspectorEvent { + @override + String get method => 'Page.screencastFrame'; + + @override + JSONEncodable get params => _screenCastFrame; + + final ScreenCastFrame _screenCastFrame; + + PageScreenCastFrameEvent(this._screenCastFrame); +} + +// Information about the Frame on the page. +class Frame extends JSONEncodable { + // Frame unique identifier. + final String id; + + // Parent frame identifier. + String? parentId; + + // Identifier of the loader associated with this frame. + final String loaderId; + + // Frame's name as specified in the tag. + String? name; + + // Frame document's URL without fragment. + final String url; + + // Frame document's URL fragment including the '#'. + String? urlFragment; + + // Frame document's registered domain, taking the public suffixes list into account. Extracted from the Frame's url. Example URLs: http://www.google.com/file.html -> "google.com" http://a.b.co.uk/file.html -> "b.co.uk" + final String domainAndRegistry; + + // Frame document's security origin. + final String securityOrigin; + + // Frame document's mimeType as determined by the browser. + final String mimeType; + + // If the frame failed to load, this contains the URL that could not be loaded. Note that unlike url above, this URL may contain a fragment. + String? unreachableUrl; + + // Indicates whether this frame was tagged as an ad. + String? AdFrameType; + + // Indicates whether the main document is a secure context and explains why that is the case. + final String secureContextType; + + // Indicates whether this is a cross origin isolated context. + final String crossOriginIsolatedContextType; + + // Indicated which gated APIs / features are available. + final List gatedAPIFeatures; + + Frame( + this.id, + this.loaderId, + this.url, + this.domainAndRegistry, + this.securityOrigin, + this.mimeType, + this.secureContextType, + this.crossOriginIsolatedContextType, + this.gatedAPIFeatures, + {this.parentId, + this.name, + this.urlFragment, + this.unreachableUrl, + this.AdFrameType}); + + @override + Map toJson() { + Map map = { + 'id': id, + 'loaderId': loaderId, + 'url': url, + 'domainAndRegistry': domainAndRegistry, + 'securityOrigin': securityOrigin, + 'mimeType': mimeType, + 'secureContextType': secureContextType, + 'crossOriginIsolatedContextType': crossOriginIsolatedContextType, + 'gatedAPIFeatures': gatedAPIFeatures + }; + + if (parentId != null) map['parentId'] = parentId; + if (name != null) map['name'] = name; + if (urlFragment != null) map['urlFragment'] = urlFragment; + if (unreachableUrl != null) map['unreachableUrl'] = unreachableUrl; + if (AdFrameType != null) map['AdFrameType'] = AdFrameType; + return map; + } +} + +class FrameResource extends JSONEncodable { + // Resource URL. + final String url; + + // Type of this resource. + final String type; + + // Resource mimeType as determined by the browser. + final String mimeType; + + // last-modified timestamp as reported by server. + int? lastModified; + + // Resource content size. + int? contentSize; + + // True if the resource failed to load. + bool? failed; + + // True if the resource was canceled during loading. + bool? canceled; + + FrameResource(this.url, this.type, this.mimeType, + {this.lastModified, this.contentSize, this.failed, this.canceled}); + + @override + Map toJson() { + Map map = {'url': url, 'type': type, 'mimeType': mimeType}; + if (lastModified != null) map['lastModified'] = lastModified; + if (contentSize != null) map['contentSize'] = contentSize; + if (failed != null) map['failed'] = failed; + if (canceled != null) map['canceled'] = canceled; + return map; + } +} + +// Information about the Frame hierarchy along with their cached resources. +class FrameResourceTree extends JSONEncodable { + // Frame information for this tree item. + final Frame frame; + + // Child frames. + List? childFrames; + + // Information about frame resources. + final List resources; + + FrameResourceTree(this.frame, this.resources, {this.childFrames}); + + @override + Map toJson() { + Map map = {'frame': frame, 'resources': resources}; + if (childFrames != null) map['childFrames'] = childFrames; + return map; + } +} + +enum ResourceType { + Document, + Stylesheet, + Image, + Media, + Font, + Script, + TextTrack, + XHR, + Fetch, + EventSource, + WebSocket, + Manifest, + SignedExchange, + Ping, + CSPViolationReport, + Preflight, + Other +} + +class InspectPageModule extends UIInspectorModule { + + Document get document => devtoolsService.controller!.view.document; + + InspectPageModule(ChromeDevToolsService devtoolsService): super(devtoolsService); + + @override + String get name => 'Page'; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + switch (method) { + case 'startScreencast': + sendToFrontend(id, null); + startScreenCast(); + break; + case 'stopScreencast': + sendToFrontend(id, null); + stopScreenCast(); + break; + case 'screencastFrameAck': + sendToFrontend(id, null); + handleScreencastFrameAck(params!); + break; + case 'getResourceContent': + sendToFrontend(id, JSONEncodableMap({ + 'content': devtoolsService.controller?.bundle?.content, + 'base64Encoded': false + })); + break; + case 'reload': + sendToFrontend(id, null); + handleReloadPage(); + break; + default: + sendToFrontend(id, null); + } + } + + void handleReloadPage() async { + try { + await document.controller.reload(); + } catch (e, stack) { + print('Dart Error: $e\n$stack'); + } + } + + int? _lastSentSessionID; + bool _isFramingScreenCast = false; + + void _frameScreenCast(Duration timeStamp) { + Element root = document.documentElement!; + root.toBlob().then((Uint8List screenShot) { + String encodedImage = base64Encode(screenShot); + _lastSentSessionID = timeStamp.inMilliseconds; + InspectorEvent event = PageScreenCastFrameEvent(ScreenCastFrame( + encodedImage, + ScreencastFrameMetadata( + 0, + 1, + document.viewport.viewportSize.width, + document.viewport.viewportSize.height, + root.offsetLeft, + root.offsetTop, + timestamp: timeStamp.inMilliseconds, + ), + _lastSentSessionID!)); + + sendEventToFrontend(event); + }); + } + + void startScreenCast() { + _isFramingScreenCast = true; + SchedulerBinding.instance!.addPostFrameCallback(_frameScreenCast); + SchedulerBinding.instance!.scheduleFrame(); + } + + void stopScreenCast() { + _isFramingScreenCast = false; + } + + /// Avoiding frame blocking, confirm frontend has ack last frame, + /// and then send next frame. + void handleScreencastFrameAck(Map params) { + int? ackSessionID = params['sessionId']; + if (ackSessionID == _lastSentSessionID && _isFramingScreenCast) { + SchedulerBinding.instance!.addPostFrameCallback(_frameScreenCast); + } + } +} + +@immutable +class ScreenCastFrame implements JSONEncodable { + final String data; + final ScreencastFrameMetadata metadata; + final int sessionId; + + ScreenCastFrame(this.data, this.metadata, this.sessionId); + + @override + Map toJson() { + return { + 'data': data, + 'metadata': metadata.toJson(), + 'sessionId': sessionId, + }; + } +} + +@immutable +class ScreencastFrameMetadata implements JSONEncodable { + final num offsetTop; + final num pageScaleFactor; + final num deviceWidth; + final num deviceHeight; + final num scrollOffsetX; + final num scrollOffsetY; + final num? timestamp; + + ScreencastFrameMetadata( + this.offsetTop, + this.pageScaleFactor, + this.deviceWidth, + this.deviceHeight, + this.scrollOffsetX, + this.scrollOffsetY, + {this.timestamp}); + + @override + Map toJson() { + return { + 'offsetTop': offsetTop, + 'pageScaleFactor': pageScaleFactor, + 'deviceWidth': deviceWidth, + 'deviceHeight': deviceHeight, + 'scrollOffsetX': scrollOffsetX, + 'scrollOffsetY': scrollOffsetY, + 'timestamp': timestamp + }; + } +} diff --git a/kraken/lib/src/devtools/modules/profiler.dart b/kraken/lib/src/devtools/modules/profiler.dart new file mode 100644 index 0000000000..d833210358 --- /dev/null +++ b/kraken/lib/src/devtools/modules/profiler.dart @@ -0,0 +1,4 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ diff --git a/kraken/lib/src/devtools/modules/runtime.dart b/kraken/lib/src/devtools/modules/runtime.dart new file mode 100644 index 0000000000..69cb4d21c5 --- /dev/null +++ b/kraken/lib/src/devtools/modules/runtime.dart @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:kraken/devtools.dart'; + +class InspectRuntimeModule extends IsolateInspectorModule { + InspectRuntimeModule(IsolateInspectorServer server): super(server); + + @override + String get name => 'Runtime'; + + @override + void receiveFromFrontend(int? id, String method, Map? params) { + callNativeInspectorMethod(id, method, params); + } +} diff --git a/kraken/lib/src/devtools/overlay.dart b/kraken/lib/src/devtools/overlay.dart new file mode 100644 index 0000000000..cab0d71a6e --- /dev/null +++ b/kraken/lib/src/devtools/overlay.dart @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'dart:ui'; + +import 'package:flutter/rendering.dart'; +import 'package:flutter/foundation.dart'; + +const Color _kHighlightedRenderObjectFillColor = Color.fromARGB(128, 128, 128, 255); +const Color _kHighlightedRenderObjectBorderColor = Color.fromARGB(128, 64, 64, 128); + +class InspectorOverlayLayer extends Layer { + /// Creates a layer that displays the inspector overlay. + InspectorOverlayLayer({ required this.overlayRect }) { + bool inDebugMode = kDebugMode || kProfileMode; + if (inDebugMode == false) { + throw FlutterError.fromParts([ + ErrorSummary( + 'The inspector should never be used in production mode due to the ' + 'negative performance impact.' + ), + ]); + } + } + + /// The rectangle in this layer's coordinate system that the overlay should + /// occupy. + /// + /// The scene must be explicitly recomposited after this property is changed + /// (as described at [Layer]). + final Rect overlayRect; + + late Picture _picture; + + @override + void addToScene(SceneBuilder builder, [Offset layerOffset = Offset.zero]) { + _picture = _buildPicture(); + builder.addPicture(layerOffset, _picture); + } + + Picture _buildPicture() { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder, overlayRect); + + final Paint fillPaint = Paint() + ..style = PaintingStyle.fill + ..color = _kHighlightedRenderObjectFillColor; + + final Paint borderPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0 + ..color = _kHighlightedRenderObjectBorderColor; + + // Highlight the selected renderObject. + canvas + ..save() + // ..transform(state.selected.transform.storage) + ..drawRect(overlayRect, fillPaint) + ..drawRect(overlayRect, borderPaint) + ..restore(); + + return recorder.endRecording(); + } +} diff --git a/kraken/lib/src/devtools/server.dart b/kraken/lib/src/devtools/server.dart new file mode 100644 index 0000000000..6f9a8a609c --- /dev/null +++ b/kraken/lib/src/devtools/server.dart @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:ffi'; +import 'dart:typed_data'; + +import 'package:kraken/module.dart'; +import 'package:kraken/bridge.dart'; +import 'package:kraken/kraken.dart'; +import 'package:kraken/devtools.dart'; +import 'package:ffi/ffi.dart'; +import '../bridge/dynamic_library.dart'; + +const String CONTENT_TYPE = 'Content-Type'; +const String CONTENT_LENGTH = 'Content-Length'; +const String FAVICON = 'https://gw.alicdn.com/tfs/TB1tTwGAAL0gK0jSZFxXXXWHVXa-144-144.png'; + +typedef MessageCallback = void Function(Map?); + +Map _inspectorServerMap = {}; + +typedef NativeInspectorMessageCallback = Void Function(Pointer rpcSession, Pointer message); +typedef DartInspectorMessageCallback = void Function(Pointer rpcSession, Pointer message); +typedef NativeRegisterInspectorMessageCallback = Void Function( + Int32 contextId, + Pointer rpcSession, + Pointer> inspectorMessageCallback); +typedef NativeAttachInspector = Void Function(Int32); +typedef DartAttachInspector = void Function(int); +typedef NativeInspectorMessage = Void Function(Int32 contextId, Pointer); +typedef NativePostTaskToUIThread = Void Function(Int32 contextId, Pointer context, Pointer callback); +typedef NativeDispatchInspectorTask = Void Function(Int32 contextId, Pointer context, Pointer callback); +typedef DartDispatchInspectorTask = void Function(int? contextId, Pointer context, Pointer callback); + +void _registerInspectorMessageCallback( + int contextId, + Pointer rpcSession, + Pointer> inspectorMessageCallback) { + IsolateInspectorServer? server = _inspectorServerMap[contextId]; + if (server == null) { + print('Internal error: can not get inspector server from contextId: $contextId'); + return; + } + DartInspectorMessageCallback nativeCallback = inspectorMessageCallback.asFunction(); + server.nativeInspectorMessageHandler = (String message) { + nativeCallback(rpcSession, (message).toNativeUtf8()); + }; +} + +void _onInspectorMessage(int contextId, Pointer message) { + IsolateInspectorServer? server = _inspectorServerMap[contextId]; + if (server == null) { + print('Internal error: can not get inspector server from contextId: $contextId'); + return; + } + String data = (message).toDartString(); + server.sendRawJSONToFrontend(data); +} + +void _postTaskToUIThread(int contextId, Pointer context, Pointer callback) { + IsolateInspectorServer? server = _inspectorServerMap[contextId]; + if (server == null) { + print('Internal error: can not get inspector server from contextId: $contextId'); + return; + } + server.isolateToMainStream!.send(InspectorPostTaskMessage(context.address, callback.address)); +} + +void attachInspector(int contextId) { + final DartAttachInspector _attachInspector = KrakenDynamicLibrary.ref + .lookup>('attachInspector') + .asFunction(); + _attachInspector(contextId); +} + +void initInspectorServerNativeBinding(int contextId) { + final DartRegisterDartMethods _registerInspectorServerDartMethods = + KrakenDynamicLibrary.ref + .lookup>( + 'registerInspectorDartMethods') + .asFunction(); + final Pointer> + _nativeInspectorMessage = Pointer.fromFunction(_onInspectorMessage); + final Pointer> + _nativeRegisterInspectorMessageCallback = Pointer.fromFunction(_registerInspectorMessageCallback); + final Pointer> _nativePostTaskToUIThread = Pointer.fromFunction(_postTaskToUIThread); + + final List _dartNativeMethods = [ + _nativeInspectorMessage.address, + _nativeRegisterInspectorMessageCallback.address, + _nativePostTaskToUIThread.address + ]; + + Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); + Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); + nativeMethodList.setAll(0, _dartNativeMethods); + + _registerInspectorServerDartMethods(bytes, _dartNativeMethods.length); +} + +void serverIsolateEntryPoint(SendPort isolateToMainStream) { + ReceivePort mainToIsolateStream = ReceivePort(); + isolateToMainStream.send(mainToIsolateStream.sendPort); + IsolateInspectorServer? server; + int? mainIsolateJSContextId; + + mainToIsolateStream.listen((data) { + if (data is InspectorServerInit) { + server = IsolateInspectorServer(data.port, data.address, data.bundleURL); + server!._isolateToMainStream = isolateToMainStream; + server!.onStarted = () { + isolateToMainStream.send(InspectorServerStart()); + }; + server!.onFrontendMessage = (Map? frontEndMessage) { + int? id = frontEndMessage!['id']; + String _method = frontEndMessage['method']; + Map? params = frontEndMessage['params']; + + List moduleMethod = _method.split('.'); + String module = moduleMethod[0]; + String method = moduleMethod[1]; + + // Runtime、Log、Debugger methods should handled on inspector isolate. + if (module == 'Runtime' || module == 'Log' || module == 'Debugger') { + server!.messageRouter(id, module, method, params); + } else { + isolateToMainStream.send(InspectorFrontEndMessage(id, module, method, params)); + } + }; + server!.start(); + _inspectorServerMap[data.contextId] = server; + mainIsolateJSContextId = data.contextId; + // @TODO + // initInspectorServerNativeBinding(data.contextId); + // attachInspector(data.contextId); + } else if (server != null && server!.connected) { + if (data is InspectorEvent) { + server!.sendEventToFrontend(data); + } else if (data is InspectorMethodResult) { + server!.sendToFrontend(data.id, data.result); + } else if (data is InspectorPostTaskMessage) { + final DartDispatchInspectorTask _dispatchInspectorTask = KrakenDynamicLibrary.ref + .lookup>('dispatchInspectorTask') + .asFunction(); + _dispatchInspectorTask(mainIsolateJSContextId, Pointer.fromAddress(data.context), Pointer.fromAddress(data.callback)); + } else if (data is InspectorReload) { + // @TODO + // attachInspector(data.contextId); + } + } + }); +} + +class IsolateInspectorServer { + IsolateInspectorServer(this.port, this.address, this.bundleURL) { + // registerModule(InspectRuntimeModule(this)); + // registerModule(InspectDebuggerModule(this)); + registerModule(InspectorLogModule(this)); + } + + // final Inspector inspector; + final String address; + final String bundleURL; + int port; + + VoidCallback? onStarted; + MessageCallback? onFrontendMessage; + late HttpServer _httpServer; + WebSocket? _ws; + + SendPort? _isolateToMainStream; + SendPort? get isolateToMainStream => _isolateToMainStream; + + NativeInspectorMessageHandler? nativeInspectorMessageHandler; + + final Map moduleRegistrar = {}; + + void messageRouter(int? id, String module, String method, Map? params) { + if (moduleRegistrar.containsKey(module)) { + moduleRegistrar[module]!.invoke(id, method, params); + } + } + + void registerModule(IsolateInspectorModule module) { + moduleRegistrar[module.name] = module; + } + + /// InspectServer has connected frontend. + bool get connected => _ws?.readyState == WebSocket.open; + + int _bindServerRetryTime = 0; + + Future _bindServer(int port) async { + try { + _httpServer = await HttpServer.bind(address, port); + this.port = port; + } on SocketException { + if (_bindServerRetryTime < 10) { + _bindServerRetryTime++; + await _bindServer(port + 1); + } else { + rethrow; + } + } + } + + Future start() async { + await _bindServer(port); + + if (onStarted != null) { + onStarted!(); + } + + _httpServer.listen((HttpRequest request) { + if (WebSocketTransformer.isUpgradeRequest(request)) { + WebSocketTransformer + .upgrade(request, compression: CompressionOptions.compressionOff) + .then((WebSocket webSocket) { + _ws = webSocket; + webSocket.listen(onWebSocketRequest, onDone: () { + _ws = null; + }, onError: (obj, stack) { + _ws = null; + }); + }); + } else { + onHTTPRequest(request); + } + }); + } + + void sendToFrontend(int? id, Map? result) { + String data = jsonEncode({ + if (id != null) 'id': id, + // Give an empty object for response. + 'result': result ?? {}, + }); + _ws?.add(data); + } + + void sendEventToFrontend(InspectorEvent event) { + _ws?.add(jsonEncode(event)); + } + + void sendRawJSONToFrontend(String message) { + _ws?.add(message); + } + + Map? _parseMessage(message) { + try { + Map? data = jsonDecode(message); + return data; + } catch (err) { + print('Error while decoding frontend message: $message'); + rethrow; + } + } + + void onWebSocketRequest(message) { + if (message is String) { + Map? data = _parseMessage(message); + if (onFrontendMessage != null) { + onFrontendMessage!(data); + } + } + } + + Future onHTTPRequest(HttpRequest request) async { + switch (request.requestedUri.path) { + case '/json/version': + onRequestVersion(request); + break; + + case '/json': + case '/json/list': + onRequestList(request); + break; + + case '/json/new': + onRequestNew(request); + break; + + case '/json/close': + onRequestClose(request); + break; + + case '/json/protocol': + onRequestProtocol(request); + break; + + default: + onRequestFallback(request); + break; + } + await request.response.close(); + } + + void _writeJSONObject(HttpRequest request, Object obj) { + String body = jsonEncode(obj); + // Must preserve header case, or chrome devtools inspector will drop data. + request.response.headers + .set(CONTENT_TYPE, 'application/json; charset=UTF-8', preserveHeaderCase: true); + request.response.headers.set(CONTENT_LENGTH, body.length, preserveHeaderCase: true); + request.response.write(body); + } + + void onRequestVersion(HttpRequest request) { + request.response.headers.clear(); + KrakenInfo krakenInfo = getKrakenInfo(); + _writeJSONObject(request, { + 'Browser': 'Kraken/${krakenInfo.appVersion}', + 'Protocol-Version': '1.3', + 'User-Agent': krakenInfo.userAgent, + }); + } + + void onRequestList(HttpRequest request) { + request.response.headers.clear(); + String pageId = hashCode.toString(); + String entryURL = '$address:$port/devtools/page/$pageId'; + _writeJSONObject(request, [ + { + 'faviconUrl': FAVICON, + 'devtoolsFrontendUrl': '$INSPECTOR_URL?ws=$entryURL', + 'title': 'Kraken App', + 'id': pageId, + 'type': 'page', + 'url': bundleURL, + 'webSocketDebuggerUrl': 'ws://$entryURL' + } + ]); + } + + void onRequestClose(HttpRequest request) { + onRequestFallback(request); + } + + void onRequestActivate(HttpRequest request) { + onRequestFallback(request); + } + + void onRequestNew(HttpRequest request) { + onRequestFallback(request); + } + + void onRequestProtocol(HttpRequest request) { + onRequestFallback(request); + } + + void onRequestFallback(HttpRequest request) { + request.response.statusCode = 404; + request.response.write('Unknown request.'); + } + + void dispose() async { + onStarted = null; + onFrontendMessage = null; + + await _ws?.close(); + await _httpServer.close(); + } +} diff --git a/kraken/lib/src/devtools/service.dart b/kraken/lib/src/devtools/service.dart new file mode 100644 index 0000000000..b81b2b8bd4 --- /dev/null +++ b/kraken/lib/src/devtools/service.dart @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'dart:isolate'; +import 'dart:ffi'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; +import 'package:kraken/kraken.dart'; +import 'package:kraken/devtools.dart'; +import '../bridge/dynamic_library.dart'; + +typedef NativePostTaskToInspectorThread = Void Function(Int32 contextId, Pointer context, Pointer callback); +typedef DartPostTaskToInspectorThread = void Function(int contextId, Pointer context, Pointer callback); + +void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { + ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); + if (devTool != null) { + devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); + } +} + +final Pointer> _nativePostTaskToInspectorThread = Pointer.fromFunction(_postTaskToInspectorThread); + +final List _dartNativeMethods = [ + _nativePostTaskToInspectorThread.address +]; + +void spawnIsolateInspectorServer(ChromeDevToolsService devTool, KrakenController controller, { int port = INSPECTOR_DEFAULT_PORT, String? address }) { + ReceivePort serverIsolateReceivePort = ReceivePort(); + + serverIsolateReceivePort.listen((data) { + if (data is SendPort) { + devTool._isolateServerPort = data; + String bundleURL = controller.bundle?.uri.toString() ?? ''; + devTool._isolateServerPort!.send(InspectorServerInit(controller.view.contextId, port, '0.0.0.0', bundleURL)); + } else if (data is InspectorFrontEndMessage) { + devTool.uiInspector!.messageRouter(data.id, data.module, data.method, data.params); + } else if (data is InspectorServerStart) { + devTool.uiInspector!.onServerStart(port); + } else if (data is InspectorPostTaskMessage) { + if (devTool.isReloading) return; + dispatchUITask(controller.view.contextId, Pointer.fromAddress(data.context), Pointer.fromAddress(data.callback)); + } + }); + + Isolate.spawn(serverIsolateEntryPoint, serverIsolateReceivePort.sendPort).then((Isolate isolate) { + devTool._isolateServerIsolate = isolate; + }); +} + +class ChromeDevToolsService extends DevToolsService { + /// Design prevDevTool for reload page, + /// do not use it in any other place. + /// More detail see [InspectPageModule.handleReloadPage]. + static ChromeDevToolsService? prevDevTools; + + static final Map _contextDevToolMap = {}; + static ChromeDevToolsService? getDevToolOfContextId(int contextId) { + return _contextDevToolMap[contextId]; + } + + late Isolate _isolateServerIsolate; + SendPort? _isolateServerPort; + SendPort? get isolateServerPort => _isolateServerPort; + + /// Used for debugger inspector. + UIInspector? _uiInspector; + UIInspector? get uiInspector => _uiInspector; + + KrakenController? _controller; + KrakenController? get controller => _controller; + + bool get isReloading => _reloading; + bool _reloading = false; + + @override + void dispose() { + _uiInspector?.dispose(); + _contextDevToolMap.remove(controller!.view.contextId); + _controller = null; + _isolateServerPort = null; + _isolateServerIsolate.kill(); + } + + @override + void init(KrakenController controller) { + _contextDevToolMap[controller.view.contextId] = this; + _controller = controller; + // @TODO: Add JS debug support for QuickJS. + // bool nativeInited = _registerUIDartMethodsToCpp(); + // if (!nativeInited) { + // print('Warning: kraken_devtools is not supported on your platform.'); + // return; + // } + spawnIsolateInspectorServer(this, controller); + _uiInspector = UIInspector(this); + controller.view.debugDOMTreeChanged = uiInspector!.onDOMTreeChanged; + } + + @override + void willReload() { + _reloading = true; + } + + @override + void didReload() { + _reloading = false; + controller!.view.debugDOMTreeChanged = _uiInspector!.onDOMTreeChanged; + _isolateServerPort!.send(InspectorReload(_controller!.view.contextId)); + } + + // @TODO: Implement and remove. + // ignore: unused_element + static bool _registerUIDartMethodsToCpp() { + final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary.ref.lookup>('registerUIDartMethods').asFunction(); + Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); + Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); + nativeMethodList.setAll(0, _dartNativeMethods); + _registerDartMethods(bytes, _dartNativeMethods.length); + return true; + } +} diff --git a/kraken/lib/src/dom/comment.dart b/kraken/lib/src/dom/comment.dart new file mode 100644 index 0000000000..1bc747bc65 --- /dev/null +++ b/kraken/lib/src/dom/comment.dart @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'package:flutter/rendering.dart'; +import 'package:kraken/dom.dart'; + +class Comment extends Node { + Comment(EventTargetContext? context) + : super(NodeType.COMMENT_NODE, context); + + @override + String get nodeName => '#comment'; + + @override + RenderBox? get renderer => null; + + // @TODO: Get data from bridge side. + String get data => ''; + + int get length => data.length; +} diff --git a/kraken/lib/src/dom/document.dart b/kraken/lib/src/dom/document.dart index 5505f1f2bd..836f480fc9 100644 --- a/kraken/lib/src/dom/document.dart +++ b/kraken/lib/src/dom/document.dart @@ -2,24 +2,97 @@ * Copyright (C) 2021-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - import 'package:flutter/rendering.dart'; -import 'package:kraken/bridge.dart'; +import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; +import 'package:kraken/gesture.dart'; +import 'package:kraken/launcher.dart'; +import 'package:kraken/rendering.dart'; +import 'package:kraken/src/dom/element_registry.dart' as element_registry; +import 'package:kraken/widget.dart'; class Document extends Node { - final HTMLElement documentElement; - Document(int targetId, Pointer nativeEventTarget, ElementManager elementManager, this.documentElement) - : super(NodeType.DOCUMENT_NODE, targetId, nativeEventTarget, elementManager); + final RenderViewportBox viewport; + KrakenController controller; + GestureListener? gestureListener; + WidgetDelegate? widgetDelegate; + + Document(EventTargetContext? context, + { + required this.viewport, + required this.controller, + this.gestureListener, + this.widgetDelegate, + }) + : super(NodeType.DOCUMENT_NODE, context); @override String get nodeName => '#document'; @override - RenderBox? get renderer => elementManager.viewport; + RenderBox? get renderer => viewport; + + Element? _documentElement; + Element? get documentElement { + return _documentElement; + } + set documentElement(Element? element) { + if (_documentElement == element) { + return; + } + + if (element != null) { + element.attachTo(this); + // Should scrollable. + element.setRenderStyleProperty(OVERFLOW_X, CSSOverflowType.scroll); + element.setRenderStyleProperty(OVERFLOW_Y, CSSOverflowType.scroll); + // Init with viewport size. + element.renderStyle.width = CSSLengthValue(viewport.viewportSize.width, CSSLengthType.PX); + element.renderStyle.height = CSSLengthValue(viewport.viewportSize.height, CSSLengthType.PX); + } else { + // Detach document element. + viewport.child = null; + } + + _documentElement = element; + } + + @override + Node appendChild(Node child) { + if (child is Element) { + documentElement ??= child; + } else { + throw UnsupportedError('Only Element can be appended to Document'); + } + return super.appendChild(child); + } + + @override + Node insertBefore(Node child, Node referenceNode) { + if (child is Element) { + documentElement ??= child; + } else { + throw UnsupportedError('Only Element can be inserted to Document'); + } + return super.insertBefore(child, referenceNode); + } + + @override + Node removeChild(Node child) { + if (documentElement == child) { + documentElement = null; + } + return super.removeChild(child); + } + + @override + Node? replaceChild(Node newNode, Node oldNode) { + if (documentElement == oldNode) { + documentElement = newNode is Element ? newNode : null; + } + return super.replaceChild(newNode, oldNode); + } addEvent(String eventType) { if (eventHandlers.containsKey(eventType)) return; // Only listen once. @@ -27,12 +100,65 @@ class Document extends Node { switch (eventType) { case EVENT_SCROLL: // Fired at the Document or element when the viewport or element is scrolled, respectively. - return documentElement.addEventListener(eventType, dispatchEvent); + return documentElement?.addEventListener(eventType, dispatchEvent); default: // Events listened on the Window need to be proxied to the Document, because there is a RenderView on the Document, which can handle hitTest. // https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/VisualViewport.cpp#L61 - documentElement.addEvent(eventType); + documentElement?.addEvent(eventType); break; } } + + Element createElement(String type, EventTargetContext? context) { + Element element = element_registry.createElement(type, context); + element.ownerDocument = this; + return element; + } + + TextNode createTextNode(String data, EventTargetContext? context) { + TextNode textNode = TextNode(data, context); + textNode.ownerDocument = this; + return textNode; + } + + DocumentFragment createDocumentFragment(EventTargetContext? context) { + DocumentFragment documentFragment = DocumentFragment(context); + documentFragment.ownerDocument = this; + return documentFragment; + } + + Comment createComment(EventTargetContext? context) { + Comment comment = Comment(context); + comment.ownerDocument = this; + return comment; + } + + // TODO: https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets + List adoptedStyleSheets = []; + // The styleSheets attribute is readonly attribute. + final List styleSheets = []; + + void addStyleSheet(CSSStyleSheet sheet) { + styleSheets.add(sheet); + recalculateDocumentStyle(); + } + + void removeStyleSheet(CSSStyleSheet sheet) { + styleSheets.remove(sheet); + recalculateDocumentStyle(); + } + + void recalculateDocumentStyle() { + // Recalculate style for all nodes sync. + documentElement?.recalculateNestedStyle(); + } + + @override + void dispose() { + gestureListener = null; + widgetDelegate = null; + styleSheets.clear(); + adoptedStyleSheets.clear(); + super.dispose(); + } } diff --git a/kraken/lib/src/dom/document_fragment.dart b/kraken/lib/src/dom/document_fragment.dart index 3a99c2b462..148a3a81b6 100644 --- a/kraken/lib/src/dom/document_fragment.dart +++ b/kraken/lib/src/dom/document_fragment.dart @@ -9,8 +9,8 @@ import 'package:kraken/dom.dart'; const String DOCUMENT_FRAGMENT = 'DOCUMENTFRAGMENT'; class DocumentFragment extends Node { - DocumentFragment(int targetId, nativeNodePtr, ElementManager elementManager) - : super(NodeType.COMMENT_NODE, targetId, nativeNodePtr, elementManager); + DocumentFragment(EventTargetContext? context) + : super(NodeType.COMMENT_NODE, context); @override String get nodeName => '#documentfragment'; diff --git a/kraken/lib/src/dom/element.dart b/kraken/lib/src/dom/element.dart index 4b06742a5e..7ccfb2678b 100644 --- a/kraken/lib/src/dom/element.dart +++ b/kraken/lib/src/dom/element.dart @@ -4,7 +4,6 @@ */ import 'dart:async'; -import 'dart:ffi'; import 'dart:typed_data'; import 'dart:ui'; @@ -13,14 +12,14 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/rendering.dart'; +import 'package:kraken/widget.dart'; +import 'package:kraken/src/dom/element_event.dart'; +import 'package:kraken/src/dom/element_view.dart'; import 'package:meta/meta.dart'; -import 'element_native_methods.dart'; - final RegExp _splitRegExp = RegExp(r'\s+'); const String _ONE_SPACE = ' '; const String _STYLE_PROPERTY = 'style'; @@ -31,6 +30,7 @@ const String _CLASS_NAME = 'class'; /// height is 150 in pixel. const String ELEMENT_DEFAULT_WIDTH = '300px'; const String ELEMENT_DEFAULT_HEIGHT = '150px'; +const String UNKNOWN = 'UNKNOWN'; typedef TestElement = bool Function(Element element); @@ -67,7 +67,7 @@ mixin ElementBase on Node { } } - late RenderStyle renderStyle; + late CSSRenderStyle renderStyle; } typedef BeforeRendererAttach = RenderObject Function(); @@ -82,7 +82,7 @@ typedef GetRenderBoxModel = RenderBoxModel? Function(); class Element extends Node with ElementBase, - ElementNativeMethods, + ElementViewMixin, ElementEventMixin, ElementOverflowMixin { @@ -146,21 +146,24 @@ class Element extends Node _updateRenderBoxModel(); } - Element(int targetId, Pointer nativeEventTarget, ElementManager elementManager, - { Map defaultStyle = const {}, - // Whether element allows children. - bool isIntrinsicBox = false, - bool isDefaultRepaintBoundary = false}) - : _defaultStyle = defaultStyle, - _isIntrinsicBox = isIntrinsicBox, - _isDefaultRepaintBoundary = isDefaultRepaintBoundary, - super(NodeType.ELEMENT_NODE, targetId, nativeEventTarget, elementManager) { + Element( + EventTargetContext? context, + { + Map? defaultStyle, + // Whether element allows children. + bool isIntrinsicBox = false, + bool isDefaultRepaintBoundary = false + }) + : _defaultStyle = defaultStyle ?? const {}, + _isIntrinsicBox = isIntrinsicBox, + _isDefaultRepaintBoundary = isDefaultRepaintBoundary, + super(NodeType.ELEMENT_NODE, context) { // Init style and add change listener. style = CSSStyleDeclaration.computedStyle(this, _defaultStyle, _onStyleChanged); // Init render style. - renderStyle = RenderStyle(target: this); + renderStyle = CSSRenderStyle(target: this); } @override @@ -188,15 +191,19 @@ class Element extends Node RenderBox? previousRenderBoxModel = renderBoxModel; if (nextRenderBoxModel != previousRenderBoxModel) { - RenderBox? parentRenderBox; + RenderObject? parentRenderObject; RenderBox? after; if (previousRenderBoxModel != null) { - parentRenderBox = previousRenderBoxModel.parent as RenderBox?; - after = (previousRenderBoxModel.parentData as ContainerParentDataMixin?)?.previousSibling; - _detachRenderBoxModel(previousRenderBoxModel); + parentRenderObject = previousRenderBoxModel.parent as RenderObject?; + + if (previousRenderBoxModel.parentData is ContainerParentDataMixin) { + after = (previousRenderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + } + + RenderBoxModel.detachRenderBox(previousRenderBoxModel); - if (parentRenderBox != null) { - _attachRenderBoxModel(parentRenderBox, nextRenderBoxModel, after: after); + if (parentRenderObject != null) { + RenderBoxModel.attachRenderBox(parentRenderObject, nextRenderBoxModel, after: after); } } renderBoxModel = nextRenderBoxModel; @@ -246,7 +253,7 @@ class Element extends Node // Create renderLayoutBox if type changed and copy children if there has previous renderLayoutBox. RenderLayoutBox _createRenderLayout({ RenderLayoutBox? previousRenderLayoutBox, - RenderStyle? renderStyle, + CSSRenderStyle? renderStyle, bool isRepaintBoundary = false }) { renderStyle = renderStyle ?? this.renderStyle; @@ -397,22 +404,27 @@ class Element extends Node @override void didAttachRenderer() { + super.didAttachRenderer(); // Ensure that the child is attached. ensureChildAttached(); } @override void willDetachRenderer() { + super.willDetachRenderer(); // Cancel running transition. - renderStyle.cancelRunningTransiton(); + renderStyle.cancelRunningTransition(); + + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Remove all intersection change listeners. - renderBoxModel!.clearIntersectionChangeListeners(); + _renderBoxModel.clearIntersectionChangeListeners(); // Remove fixed children from root when element disposed. - _removeFixedChild(renderBoxModel!, elementManager.viewportElement._renderLayoutBox!); + _removeFixedChild(_renderBoxModel, ownerDocument.documentElement!._renderLayoutBox!); // Remove renderBox. - _removeRenderBoxModel(renderBoxModel!); + _renderBoxModel.detachFromContainingBlock(); // Remove pointer listener removeEventResponder(renderBoxModel!); @@ -437,6 +449,7 @@ class Element extends Node } void _handleScroll(double scrollOffset, AxisDirection axisDirection) { + if (renderBoxModel == null) return; _applyStickyChildrenOffset(); _applyFixedChildrenOffset(scrollOffset, axisDirection); @@ -452,7 +465,7 @@ class Element extends Node /// So it needs to manually mark element needs paint and add scroll offset in paint stage void _applyFixedChildrenOffset(double scrollOffset, AxisDirection axisDirection) { // Only root element has fixed children - if (this == elementManager.viewportElement && renderBoxModel != null) { + if (this == ownerDocument.documentElement && renderBoxModel != null) { RenderBoxModel layoutBox = (renderBoxModel as RenderLayoutBox).renderScrollingContent ?? renderBoxModel!; for (RenderBoxModel child in layoutBox.fixedChildren) { // Save scrolling offset for paint @@ -473,56 +486,145 @@ class Element extends Node } } - void _updateRenderBoxModelWithPosition() { - RenderBoxModel _renderBoxModel = renderBoxModel!; - CSSPositionType currentPosition = renderStyle.position; + // Find all the nested position absolute elements which need to change the containing block + // from other element to this element when element's position is changed from static to relative. + // Take following html for example, div of id=4 should reposition from div of id=1 to div of id=2. + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ List findNestedPositionAbsoluteChildren() { + List positionAbsoluteChildren = []; - // Remove fixed children before convert to non repaint boundary renderObject - if (currentPosition != CSSPositionType.fixed) { - _removeFixedChild(_renderBoxModel, elementManager.viewportElement._renderLayoutBox!); - } - - RenderBox? previousSibling; - RenderPositionPlaceholder? renderPositionPlaceholder = _renderBoxModel.renderPositionPlaceholder; - // It needs to find the previous sibling of the previous sibling if the placeholder of - // positioned element exists and follows renderObject at the same time, eg. - //
- if (renderPositionPlaceholder != null) { - previousSibling = (renderPositionPlaceholder.parentData as ContainerParentDataMixin).previousSibling; - // The placeholder's previousSibling maybe the origin renderBox. - if (previousSibling == _renderBoxModel) { - previousSibling = (_renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; - } - _detachRenderBoxModel(renderPositionPlaceholder); - _renderBoxModel.renderPositionPlaceholder = null; - } else { - previousSibling = (_renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; - } + if (!isRendererAttached) return positionAbsoluteChildren; - // Detach renderBoxModel from original parent. - _detachRenderBoxModel(_renderBoxModel); + children.forEach((Element child) { + if (!child.isRendererAttached) return; - _updateRenderBoxModel(); - _addToContainingBlock(after: previousSibling); + RenderBoxModel childRenderBoxModel = child.renderBoxModel!; + RenderStyle childRenderStyle = childRenderBoxModel.renderStyle; + if (childRenderStyle.position == CSSPositionType.absolute) { + positionAbsoluteChildren.add(child); + } + // No need to loop layout box whose position is not static. + if (childRenderStyle.position != CSSPositionType.static) { + return; + } + if (childRenderBoxModel is RenderLayoutBox) { + List mergedChildren = child.findNestedPositionAbsoluteChildren(); + for (Element child in mergedChildren) { + positionAbsoluteChildren.add(child); + } + } + }); - // Add fixed children after convert to repaint boundary renderObject. - if (currentPosition == CSSPositionType.fixed) { - _addFixedChild(renderBoxModel!, elementManager.viewportElement._renderLayoutBox!); + return positionAbsoluteChildren; + } + + // Find all the direct position absolute elements which need to change the containing block + // from this element to other element when element's position is changed from relative to static. + // Take following html for example, div of id=4 should reposition from div of id=2 to div of id=1. + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ List findDirectPositionAbsoluteChildren() { + List directPositionAbsoluteChildren = []; + + if (!isRendererAttached) return directPositionAbsoluteChildren; + + RenderBox? child = (renderBoxModel as RenderLayoutBox).firstChild; + + while (child != null) { + final ContainerParentDataMixin? childParentData = + child.parentData as ContainerParentDataMixin?; + if (child is! RenderLayoutBox) { + child = childParentData!.nextSibling; + continue; + } + if (child.renderStyle.position == CSSPositionType.absolute) { + directPositionAbsoluteChildren.add(child.renderStyle.target); + } + child = childParentData!.nextSibling; } + + return directPositionAbsoluteChildren; } - Element? getElementById(Element parentElement, int targetId) { - Element? result; - List childNodes = parentElement.childNodes; + void _updateRenderBoxModelWithPosition(CSSPositionType oldPosition) { + CSSPositionType currentPosition = renderStyle.position; - for (int i = 0; i < childNodes.length; i++) { - Element element = childNodes[i]; - if (element.targetId == targetId) { - result = element; - break; + // No need to detach and reattach renderBoxMode when its position + // changes between static and relative. + if (!(oldPosition == CSSPositionType.static && currentPosition == CSSPositionType.relative) + && !(oldPosition == CSSPositionType.relative && currentPosition == CSSPositionType.static) + ) { + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Remove fixed children before convert to non repaint boundary renderObject + if (currentPosition != CSSPositionType.fixed) { + _removeFixedChild(_renderBoxModel, ownerDocument.documentElement!._renderLayoutBox!); + } + + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? previousSibling = _renderBoxModel.getPreviousSibling(); + // Detach renderBoxModel from its original parent. + _renderBoxModel.detachFromContainingBlock(); + // Change renderBoxModel type in cases such as position changes to fixed which + // need to create repaintBoundary. + _updateRenderBoxModel(); + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + // Attach renderBoxModel to its containing block. + renderBoxModel!.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: previousSibling); + + // Add fixed children after convert to repaint boundary renderObject. + if (currentPosition == CSSPositionType.fixed) { + _addFixedChild(renderBoxModel!, ownerDocument.documentElement!._renderLayoutBox!); } } - return result; + + // Need to change the containing block of nested position absolute children from its upper parent + // to this element when element's position is changed from static to relative. + if (oldPosition == CSSPositionType.static) { + List positionAbsoluteChildren = findNestedPositionAbsoluteChildren(); + positionAbsoluteChildren.forEach((Element child) { + child.addToContainingBlock(); + }); + + // Need to change the containing block of direct position absolute children from this element + // to its upper parent when element's position is changed from relative to static. + } else if (currentPosition == CSSPositionType.static) { + List directPositionAbsoluteChildren = findDirectPositionAbsoluteChildren(); + directPositionAbsoluteChildren.forEach((Element child) { + child.addToContainingBlock(); + }); + } + } + + // Add element to its containing block which includes the steps of detach the renderBoxModel + // from its original parent and attach to its new containing block. + void addToContainingBlock() { + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? previousSibling = _renderBoxModel.getPreviousSibling(); + // Detach renderBoxModel from its original parent. + _renderBoxModel.detachFromContainingBlock(); + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + // Attach renderBoxModel of to its containing block. + _renderBoxModel.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: previousSibling); } void addChild(RenderBox child) { @@ -576,16 +678,20 @@ class Element extends Node void attachTo(Node parent, {RenderBox? after}) { _applyStyle(style); - willAttachRenderer(); + if (parentElement?.renderStyle.display == CSSDisplay.sliver) { + // Sliver should not create renderer here, but need to trigger + // render sliver list dynamical rebuild child by element tree. + parentElement?._renderLayoutBox?.markNeedsLayout(); + } else { + willAttachRenderer(); + } if (renderer != null) { - // HTML element override attachTo method to attach renderObject to viewportBox. - if (parent is Element) { - RenderLayoutBox? parentRenderLayoutBox = parentElement?._renderLayoutBox?.renderScrollingContent ?? parentElement?._renderLayoutBox; - parentRenderLayoutBox!.insert(renderBoxModel!, after: after); - } else if (parent is Document) { - parent.appendChild(this); + // If element attach WidgetElement, render obeject should be attach to render tree when mount. + if (parent is! WidgetElement) { + RenderBoxModel.attachRenderBox(parent.renderer!, renderer!, after: after); } + // Flush pending style before child attached. style.flushPendingProperties(); @@ -639,15 +745,17 @@ class Element extends Node if (child is Element) { child.renderStyle.parent = renderStyle; } + + RenderLayoutBox? renderLayoutBox = _renderLayoutBox; if (isRendererAttached) { // Only append child renderer when which is not attached. - if (!child.isRendererAttached && _renderLayoutBox != null) { + if (!child.isRendererAttached && renderLayoutBox != null && this is! WidgetElement) { RenderBox? after; - RenderLayoutBox? scrollingContentBox = _renderLayoutBox!.renderScrollingContent; + RenderLayoutBox? scrollingContentBox = renderLayoutBox.renderScrollingContent; if (scrollingContentBox != null) { after = scrollingContentBox.lastChild; } else { - after = _renderLayoutBox!.lastChild; + after = renderLayoutBox.lastChild; } child.attachTo(this, after: after); @@ -688,13 +796,33 @@ class Element extends Node } if (isRendererAttached) { + // If afterRenderObject is null, which means insert child at the head of parent. + RenderBox? afterRenderObject; + // Only append child renderer when which is not attached. if (!child.isRendererAttached) { - RenderBox? afterRenderObject; - // `referenceNode` should not be null, or `referenceIndex` can only be -1. - if (referenceIndex != -1 && referenceNode.isRendererAttached) { - afterRenderObject = (referenceNode.renderer!.parentData as ContainerParentDataMixin).previousSibling; + if (referenceIndex < childNodes.length) { + while (referenceIndex >= 0) { + Node reference = childNodes[referenceIndex]; + if (reference.isRendererAttached) { + afterRenderObject = reference.renderer; + break; + } else { + referenceIndex--; + } + } } + + // Renderer of referenceNode may not moved to a difference place compared to its original place + // in the dom tree due to position absolute/fixed. + // Use the renderPositionPlaceholder to get the same place as dom tree in this case. + if (afterRenderObject is RenderBoxModel) { + RenderBox? renderPositionPlaceholder = afterRenderObject.renderPositionPlaceholder; + if (renderPositionPlaceholder != null) { + afterRenderObject = renderPositionPlaceholder; + } + } + child.attachTo(this, after: afterRenderObject); } } @@ -715,68 +843,32 @@ class Element extends Node return super.replaceChild(newNode, oldNode); } - // The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, - // called the containing block of the element. - // Definition of "containing block": https://www.w3.org/TR/CSS21/visudet.html#containing-block-details - void _addToContainingBlock({RenderBox? after}) { - assert(parentNode != null); + RenderBox? getContainingBlockRenderBox() { + RenderBox? containingBlockRenderBox; CSSPositionType positionType = renderStyle.position; - RenderBoxModel _renderBoxModel = renderBoxModel!; - // HTML element's parentNode is viewportBox. - RenderBox parentRenderBox = parentNode!.renderer!; - // The containing block of an element is defined as follows: - if (positionType == CSSPositionType.relative || positionType == CSSPositionType.static || positionType == CSSPositionType.sticky) { - // If the element's position is 'relative' or 'static', - // the containing block is formed by the content edge of the nearest block container ancestor box. - _attachRenderBoxModel(parentRenderBox, _renderBoxModel, after: after); - - if (positionType == CSSPositionType.sticky) { - // Placeholder of sticky renderBox need to inherit offset from original renderBox, - // so it needs to layout before original renderBox. - _addPositionPlaceholder(parentRenderBox, _renderBoxModel, after: after); - } - } else { - RenderLayoutBox? containingBlockRenderBox; - if (positionType == CSSPositionType.absolute) { + switch(positionType) { + case CSSPositionType.relative: + case CSSPositionType.static: + case CSSPositionType.sticky: + containingBlockRenderBox = parentNode!.renderer; + break; + case CSSPositionType.absolute: // If the element has 'position: absolute', the containing block is established by the nearest ancestor with // a 'position' of 'absolute', 'relative' or 'fixed', in the following way: // 1. In the case that the ancestor is an inline element, the containing block is the bounding box around // the padding boxes of the first and the last inline boxes generated for that element. // In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined. // 2. Otherwise, the containing block is formed by the padding edge of the ancestor. - containingBlockRenderBox = _findContainingBlock(this, elementManager.viewportElement)?._renderLayoutBox; - } else if (positionType == CSSPositionType.fixed) { + containingBlockRenderBox = _findContainingBlock(this, ownerDocument.documentElement!)?._renderLayoutBox; + break; + case CSSPositionType.fixed: // If the element has 'position: fixed', the containing block is established by the viewport // in the case of continuous media or the page area in the case of paged media. - containingBlockRenderBox = elementManager.viewportElement._renderLayoutBox; - } - - if (containingBlockRenderBox == null) return; - - // If container block is same as origin parent, the placeholder must after the origin renderBox - // because placeholder depends the constraints in layout stage. - if (containingBlockRenderBox == parentRenderBox) { - after = _renderBoxModel; - } - - // Set custom positioned parentData. - RenderLayoutParentData parentData = RenderLayoutParentData(); - _renderBoxModel.parentData = CSSPositionedLayout.getPositionParentData(_renderBoxModel, parentData); - // Add child to containing block parent. - _attachRenderBoxModel(containingBlockRenderBox, _renderBoxModel, isLast: true); - // Add position holder to origin position parent. - _addPositionPlaceholder(parentRenderBox, _renderBoxModel, after: after); + containingBlockRenderBox = ownerDocument.documentElement!._renderLayoutBox; + break; } - } - - void _addPositionPlaceholder(RenderBox parentRenderBox, RenderBoxModel renderBoxModel, {RenderBox? after}) { - // Position holder size will be updated on layout. - RenderPositionPlaceholder renderPositionPlaceholder = RenderPositionPlaceholder(preferredSize: Size.zero); - renderBoxModel.renderPositionPlaceholder = renderPositionPlaceholder; - renderPositionPlaceholder.positioned = renderBoxModel; - - _attachRenderBoxModel(parentRenderBox, renderPositionPlaceholder, after: after); + return containingBlockRenderBox; } // FIXME: only compatible with kraken plugins @@ -799,32 +891,34 @@ class Element extends Node // Update renderBoxModel. _updateRenderBoxModel(); // Attach renderBoxModel to parent if change from `display: none` to other values. - if (renderBoxModel!.parent == null) { - _addToContainingBlock(after: previousSibling?.renderer); + if (!isRendererAttached && parentElement != null && parentElement!.isRendererAttached) { + // If element attach WidgetElement, render obeject should be attach to render tree when mount. + if (parentNode is! WidgetElement) { + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? preSibling = previousSibling?.renderer; + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + _renderBoxModel.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: preSibling); + } ensureChildAttached(); } } - void _attachRenderBoxModel(RenderBox parentRenderBox, RenderBox renderBox, {RenderObject? after, bool isLast = false}) { - if (isLast) { - assert(after == null); + void setRenderStyleProperty(String name, dynamic value) { + // Memorize the variable value to renderStyle object. + if (CSSVariable.isVariable(name)) { + renderStyle.setCSSVariable(name, value.toString()); + return; } - if (parentRenderBox is RenderObjectWithChildMixin) { // RenderViewportBox - (parentRenderBox as RenderObjectWithChildMixin).child = renderBox; - } else if (parentRenderBox is ContainerRenderObjectMixin) { // RenderLayoutBox or RenderSliverList - // Should attach to renderScrollingContent if it is scrollable. - if (parentRenderBox is RenderLayoutBox) { - parentRenderBox = parentRenderBox.renderScrollingContent ?? parentRenderBox; - } - if (isLast) { - after = (parentRenderBox as ContainerRenderObjectMixin).lastChild; - } - (parentRenderBox as ContainerRenderObjectMixin).insert(renderBox, after: after); + // Get the computed value of CSS variable. + if (value is CSSVariable) { + value = value.computedValue(name); } - } - void setRenderStyleProperty(String name, dynamic value) { switch (name) { case DISPLAY: renderStyle.display = value; @@ -867,8 +961,9 @@ class Element extends Node renderStyle.contentVisibility = value; break; case POSITION: + CSSPositionType oldPosition = renderStyle.position; renderStyle.position = value; - _updateRenderBoxModelWithPosition(); + _updateRenderBoxModelWithPosition(oldPosition); break; case TOP: renderStyle.top = value; @@ -1127,213 +1222,13 @@ class Element extends Node } } - /// Set internal style value to the element. - dynamic _resolveRenderStyleValue(String property, dynamic present) { - dynamic value; - switch (property) { - case DISPLAY: - value = CSSDisplayMixin.resolveDisplay(present); - break; - case OVERFLOW_X: - case OVERFLOW_Y: - value = CSSOverflowMixin.resolveOverflowType(present); - break; - case POSITION: - value = CSSPositionMixin.resolvePositionType(present); - break; - case Z_INDEX: - value = int.tryParse(present); - break; - case TOP: - case LEFT: - case BOTTOM: - case RIGHT: - case FLEX_BASIS: - case PADDING_TOP: - case PADDING_RIGHT: - case PADDING_BOTTOM: - case PADDING_LEFT: - case WIDTH: - case MIN_WIDTH: - case MAX_WIDTH: - case HEIGHT: - case MIN_HEIGHT: - case MAX_HEIGHT: - case MARGIN_LEFT: - case MARGIN_TOP: - case MARGIN_RIGHT: - case MARGIN_BOTTOM: - case FONT_SIZE: - value = CSSLength.resolveLength(present, renderStyle, property); - break; - case FLEX_DIRECTION: - value = CSSFlexboxMixin.resolveFlexDirection(present); - break; - case FLEX_WRAP: - value = CSSFlexboxMixin.resolveFlexWrap(present); - break; - case ALIGN_CONTENT: - value = CSSFlexboxMixin.resolveAlignContent(present); - break; - case ALIGN_ITEMS: - value = CSSFlexboxMixin.resolveAlignItems(present); - break; - case JUSTIFY_CONTENT: - value = CSSFlexboxMixin.resolveJustifyContent(present); - break; - case ALIGN_SELF: - value = CSSFlexboxMixin.resolveAlignSelf(present); - break; - case FLEX_GROW: - value = CSSFlexboxMixin.resolveFlexGrow(present); - break; - case FLEX_SHRINK: - value = CSSFlexboxMixin.resolveFlexShrink(present); - break; - case SLIVER_DIRECTION: - value = CSSSliverMixin.resolveAxis(present); - break; - case TEXT_ALIGN: - value = CSSTextMixin.resolveTextAlign(present); - break; - case BACKGROUND_ATTACHMENT: - value = CSSBackground.resolveBackgroundAttachment(present); - break; - case BACKGROUND_IMAGE: - value = CSSBackground.resolveBackgroundImage(present, renderStyle, property, elementManager.controller); - break; - case BACKGROUND_REPEAT: - value = CSSBackground.resolveBackgroundRepeat(present); - break; - case BACKGROUND_POSITION_X: - value = CSSPosition.resolveBackgroundPosition(present, renderStyle, property, true); - break; - case BACKGROUND_POSITION_Y: - value = CSSPosition.resolveBackgroundPosition(present, renderStyle, property, false); - break; - case BACKGROUND_SIZE: - value = CSSBackground.resolveBackgroundSize(present, renderStyle, property); - break; - case BACKGROUND_CLIP: - value = CSSBackground.resolveBackgroundClip(present); - break; - case BACKGROUND_ORIGIN: - value = CSSBackground.resolveBackgroundOrigin(present); - break; - case BORDER_LEFT_WIDTH: - case BORDER_TOP_WIDTH: - case BORDER_RIGHT_WIDTH: - case BORDER_BOTTOM_WIDTH: - value = CSSBorderSide.resolveBorderWidth(present, renderStyle, property); - break; - case BORDER_LEFT_STYLE: - case BORDER_TOP_STYLE: - case BORDER_RIGHT_STYLE: - case BORDER_BOTTOM_STYLE: - value = CSSBorderSide.resolveBorderStyle(present); - break; - case COLOR: - case BACKGROUND_COLOR: - case TEXT_DECORATION_COLOR: - case BORDER_LEFT_COLOR: - case BORDER_TOP_COLOR: - case BORDER_RIGHT_COLOR: - case BORDER_BOTTOM_COLOR: - value = CSSColor.resolveColor(present, renderStyle, property); - break; - case BOX_SHADOW: - value = CSSBoxShadow.parseBoxShadow(present, renderStyle, property); - break; - case BORDER_TOP_LEFT_RADIUS: - case BORDER_TOP_RIGHT_RADIUS: - case BORDER_BOTTOM_LEFT_RADIUS: - case BORDER_BOTTOM_RIGHT_RADIUS: - value = CSSBorderRadius.parseBorderRadius(present, renderStyle, property); - break; - case OPACITY: - value = CSSOpacityMixin.resolveOpacity(present); - break; - case VISIBILITY: - value = CSSVisibilityMixin.resolveVisibility(present); - break; - case CONTENT_VISIBILITY: - value = CSSContentVisibilityMixin.resolveContentVisibility(present); - break; - case TRANSFORM: - value = CSSTransformMixin.resolveTransform(present); - break; - case FILTER: - value = CSSFunction.parseFunction(present); - break; - case TRANSFORM_ORIGIN: - value = CSSOrigin.parseOrigin(present, renderStyle, property); - break; - case OBJECT_FIT: - value = CSSObjectFitMixin.resolveBoxFit(present); - break; - case OBJECT_POSITION: - value = CSSObjectPositionMixin.resolveObjectPosition(present); - break; - case TEXT_DECORATION_LINE: - value = CSSText.resolveTextDecorationLine(present); - break; - case TEXT_DECORATION_STYLE: - value = CSSText.resolveTextDecorationStyle(present); - break; - case FONT_WEIGHT: - value = CSSText.resolveFontWeight(present); - break; - case FONT_STYLE: - value = CSSText.resolveFontStyle(present); - break; - case FONT_FAMILY: - value = CSSText.resolveFontFamilyFallback(present); - break; - case LINE_HEIGHT: - value = CSSText.resolveLineHeight(present, renderStyle, property); - break; - case LETTER_SPACING: - value = CSSText.resolveSpacing(present, renderStyle, property); - break; - case WORD_SPACING: - value = CSSText.resolveSpacing(present, renderStyle, property); - break; - case TEXT_SHADOW: - value = CSSText.resolveTextShadow(present, renderStyle, property); - break; - case WHITE_SPACE: - value = CSSText.resolveWhiteSpace(present); - break; - case TEXT_OVERFLOW: - // Overflow will affect text-overflow ellipsis taking effect - value = CSSText.resolveTextOverflow(present); - break; - case LINE_CLAMP: - value = CSSText.parseLineClamp(present); - break; - case VERTICAL_ALIGN: - value = CSSInlineMixin.resolveVerticalAlign(present); - break; - // Transition - case TRANSITION_DELAY: - case TRANSITION_DURATION: - case TRANSITION_TIMING_FUNCTION: - case TRANSITION_PROPERTY: - value = CSSStyleProperty.getMultipleValues(present); - break; - } - - return value; - } - void setRenderStyle(String property, String present) { - dynamic value = present.isEmpty ? null : _resolveRenderStyleValue(property, present); + dynamic value = present.isEmpty ? null : renderStyle.resolveValue(property, present); setRenderStyleProperty(property, value); } void _updateColorRelativePropertyWithColor(Element element) { - RenderStyle renderStyle = element.renderStyle; - renderStyle.updateColorRelativeProperty(); + element.renderStyle.updateColorRelativeProperty(); if (element.children.isNotEmpty) { element.children.forEach((Element child) { if (!child.renderStyle.hasColor) { @@ -1354,8 +1249,7 @@ class Element extends Node } void _updateChildrenFontRelativeLength(Element element) { - RenderStyle renderStyle = element.renderStyle; - renderStyle.updateFontRelativeLength(); + element.renderStyle.updateFontRelativeLength(); if (element.children.isNotEmpty) { element.children.forEach((Element child) { if (!child.renderStyle.hasFontSize) { @@ -1366,8 +1260,7 @@ class Element extends Node } void _updateChildrenRootFontRelativeLength(Element element) { - RenderStyle renderStyle = element.renderStyle; - renderStyle.updateRootFontRelativeLength(); + element.renderStyle.updateRootFontRelativeLength(); if (element.children.isNotEmpty) { element.children.forEach((Element child) { _updateChildrenRootFontRelativeLength(child); @@ -1396,7 +1289,7 @@ class Element extends Node if (classList.isNotEmpty) { const String classSelectorPrefix = '.'; for (String className in classList) { - for (CSSStyleSheet sheet in elementManager.styleSheets) { + for (CSSStyleSheet sheet in ownerDocument.styleSheets) { List rules = sheet.cssRules; for (int i = 0; i < rules.length; i++) { CSSRule rule = rules[i]; @@ -1421,7 +1314,7 @@ class Element extends Node } // Set inline style property. - void setInlineStyle(String property, dynamic value) { + void setInlineStyle(String property, String value) { // Current only for mark property is setting by inline style. inlineStyle[property] = value; style.setProperty(property, value, true); @@ -1507,6 +1400,9 @@ class Element extends Node style.removeProperty(property, true); } + // The Element.getBoundingClientRect() method returns a DOMRect object providing information + // about the size of an element and its position relative to the viewport. + // https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect BoundingClientRect get boundingClientRect { BoundingClientRect boundingClientRect = BoundingClientRect(0, 0, 0, 0, 0, 0, 0, 0); if (isRendererAttached) { @@ -1518,7 +1414,7 @@ class Element extends Node } if (sizedBox.hasSize) { - Offset offset = getOffset(sizedBox); + Offset offset = _getOffset(sizedBox, ancestor: ownerDocument.documentElement); Size size = sizedBox.size; boundingClientRect = BoundingClientRect( offset.dx, @@ -1535,36 +1431,67 @@ class Element extends Node return boundingClientRect; } - double getOffsetX() { + // The HTMLElement.offsetLeft read-only property returns the number of pixels that the upper left corner + // of the current element is offset to the left within the HTMLElement.offsetParent node. + // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft + double get offsetLeft { double offset = 0; RenderBoxModel selfRenderBoxModel = renderBoxModel!; if (selfRenderBoxModel.attached) { - Offset relative = getOffset(selfRenderBoxModel); + Offset relative = _getOffset(selfRenderBoxModel, ancestor: offsetParent); offset += relative.dx; } return offset; } - double getOffsetY() { + // The HTMLElement.offsetTop read-only property returns the distance of the outer border + // of the current element relative to the inner border of the top of the offsetParent node. + // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop + double get offsetTop { double offset = 0; RenderBoxModel selfRenderBoxModel = renderBoxModel!; if (selfRenderBoxModel.attached) { - Offset relative = getOffset(selfRenderBoxModel); + Offset relative = _getOffset(selfRenderBoxModel, ancestor: offsetParent); offset += relative.dy; } return offset; } - Offset getOffset(RenderBox renderBox) { + // The HTMLElement.offsetParent read-only property returns a reference to the element + // which is the closest (nearest in the containment hierarchy) positioned ancestor element. + // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent + Element? get offsetParent { + // Returns null in the following cases. + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent + if (renderStyle.display == CSSDisplay.none + || renderStyle.position == CSSPositionType.fixed + || this is BodyElement + || this == ownerDocument.documentElement) { + return null; + } + + Element? parent = parentElement; + + while (parent != null) { + bool isNonStatic = parent.renderStyle.position != CSSPositionType.static; + if (parent is BodyElement || isNonStatic) { + break; + } + parent = parent.parentElement; + } + return parent; + } + + // Get the offset of current element relative to specified ancestor element. + Offset _getOffset(RenderBox renderBox, { Element? ancestor }) { // Need to flush layout to get correct size. - elementManager - .getRootRenderBox() - .owner! - .flushLayout(); + ownerDocument.documentElement!.renderBoxModel!.owner!.flushLayout(); - Element? element = _findContainingBlock(this, elementManager.viewportElement); - element ??= elementManager.viewportElement; - return renderBox.localToGlobal(Offset.zero, ancestor: element.renderBoxModel); + // Returns (0, 0) when ancestor is null. + if (ancestor == null) { + return Offset.zero; + } + return renderBox.localToGlobal(Offset.zero, ancestor: ancestor.renderBoxModel); } void _ensureEventResponderBound() { @@ -1618,14 +1545,12 @@ class Element extends Node SchedulerBinding.instance!.addPostFrameCallback((_) async { Uint8List captured; - RenderBoxModel? renderObject = targetId == HTML_ID - ? elementManager.viewportElement.renderBoxModel - : renderBoxModel; - if (renderObject!.hasSize && renderObject.size.isEmpty) { + RenderBoxModel? _renderBoxModel = renderBoxModel; + if (_renderBoxModel!.hasSize && _renderBoxModel.size.isEmpty) { // Return a blob with zero length. captured = Uint8List(0); } else { - Image image = await renderObject.toImage(pixelRatio: devicePixelRatio!); + Image image = await _renderBoxModel.toImage(pixelRatio: devicePixelRatio!); ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); captured = byteData!.buffer.asUint8List(); } @@ -1654,7 +1579,7 @@ class Element extends Node // Create a new RenderLayoutBox for the scrolling content. RenderLayoutBox createScrollingContentLayout() { // FIXME: Create an empty renderStyle for do not share renderStyle with element. - RenderStyle scrollingContentRenderStyle = RenderStyle(target: this); + CSSRenderStyle scrollingContentRenderStyle = CSSRenderStyle(target: this); // Scrolling content layout need to be share the same display with its outer layout box. scrollingContentRenderStyle.display = renderStyle.display; RenderLayoutBox scrollingContentLayoutBox = _createRenderLayout( @@ -1692,37 +1617,7 @@ bool _hasIntersectionObserverEvent(Map eventHandlers) { eventHandlers.containsKey('intersectionchange'); } -void _detachRenderBoxModel(RenderBox renderBox) { - if (renderBox.parent == null) return; - - // Remove reference from parent - RenderObject? parentRenderObject = renderBox.parent as RenderObject; - if (parentRenderObject is RenderObjectWithChildMixin) { - parentRenderObject.child = null; // Case for single child, eg. RenderViewportBox - } else if (parentRenderObject is ContainerRenderObjectMixin) { - parentRenderObject.remove(renderBox); // Case for multi children, eg. RenderLayoutBox or RenderSliverList - } -} - -void _removeRenderBoxModel(RenderBoxModel renderBox) { - _detachRenderBoxModel(renderBox); - - // Remove scrolling content layout box of overflow element. - if (renderBox is RenderLayoutBox && renderBox.renderScrollingContent != null) { - renderBox.remove(renderBox.renderScrollingContent!); - } - // Remove placeholder of positioned element. - RenderPositionPlaceholder? renderPositionHolder = renderBox.renderPositionPlaceholder; - if (renderPositionHolder != null) { - RenderLayoutBox? parentLayoutBox = renderPositionHolder.parent as RenderLayoutBox?; - if (parentLayoutBox != null) { - parentLayoutBox.remove(renderPositionHolder); - renderBox.renderPositionPlaceholder = null; - } - } -} - -/// Cache fixed renderObject to root element +// Cache fixed renderObject to root element void _addFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRenderLayoutBox) { rootRenderLayoutBox = rootRenderLayoutBox.renderScrollingContent ?? rootRenderLayoutBox; List fixedChildren = rootRenderLayoutBox.fixedChildren; @@ -1731,7 +1626,7 @@ void _addFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRend } } -/// Remove non fixed renderObject from root element +// Remove non fixed renderObject from root element void _removeFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRenderLayoutBox) { rootRenderLayoutBox = rootRenderLayoutBox.renderScrollingContent ?? rootRenderLayoutBox; List fixedChildren = rootRenderLayoutBox.fixedChildren; diff --git a/kraken/lib/src/dom/event_handler.dart b/kraken/lib/src/dom/element_event.dart similarity index 95% rename from kraken/lib/src/dom/event_handler.dart rename to kraken/lib/src/dom/element_event.dart index 2359bb6413..5d40bff937 100644 --- a/kraken/lib/src/dom/event_handler.dart +++ b/kraken/lib/src/dom/element_event.dart @@ -13,7 +13,7 @@ enum AppearEventType { disappear } -mixin ElementEventMixin on EventTarget { +mixin ElementEventMixin on ElementBase { AppearEventType prevAppearState = AppearEventType.none; void addEventResponder(RenderPointerListenerMixin renderBox) { @@ -45,7 +45,7 @@ mixin ElementEventMixin on EventTarget { } void handleMouseEvent(String eventType, TapUpDetails details) { - RenderBoxModel? root = elementManager.viewportElement.renderBoxModel; + RenderBoxModel? root = ownerDocument.documentElement!.renderBoxModel; if (root == null) { return; } diff --git a/kraken/lib/src/dom/element_manager.dart b/kraken/lib/src/dom/element_manager.dart deleted file mode 100644 index d01e7b45a4..0000000000 --- a/kraken/lib/src/dom/element_manager.dart +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Copyright (C) 2019-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -import 'dart:async'; -import 'dart:core'; -import 'dart:ffi'; -import 'dart:math' as math; -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver, RouteInformation; -import 'package:kraken/bridge.dart'; -import 'package:kraken/css.dart'; -import 'package:kraken/dom.dart'; -import 'package:kraken/gesture.dart'; -import 'package:kraken/launcher.dart'; -import 'package:kraken/module.dart'; -import 'package:kraken/rendering.dart'; -import 'package:kraken/scheduler.dart'; -import 'package:kraken/src/dom/element_registry.dart' as element_registry; -import 'package:kraken/widget.dart'; - -const String UNKNOWN = 'UNKNOWN'; - -const int HTML_ID = -1; -const int WINDOW_ID = -2; -const int DOCUMENT_ID = -3; - -typedef ElementCreator = Element Function(int targetId, Pointer nativeEventTarget, ElementManager elementManager); - -class ElementManager implements WidgetsBindingObserver, ElementsBindingObserver { - // Call from JS Bridge before JS side eventTarget object been Garbage collected. - static void disposeEventTarget(int contextId, int id) { - KrakenController controller = KrakenController.getControllerOfJSContextId(contextId)!; - EventTarget? eventTarget = controller.view.getEventTargetById(id); - if (eventTarget == null) return; - eventTarget.dispose(); - } - - // Alias defineElement export for kraken plugin - static void defineElement(String type, ElementCreator creator) { - element_registry.defineElement(type, creator); - } - - static Map> htmlNativePtrMap = {}; - static Map> documentNativePtrMap = {}; - static Map> windowNativePtrMap = {}; - - static double FOCUS_VIEWINSET_BOTTOM_OVERALL = 32; - // Single child renderView. - late final RenderViewportBox viewport; - late final Document document; - late final Element viewportElement; - Map _eventTargets = {}; - bool? showPerformanceOverlayOverride; - KrakenController controller; - - double get viewportWidth => viewport.viewportSize.width; - double get viewportHeight => viewport.viewportSize.height; - - final int contextId; - - final List _detachCallbacks = []; - - GestureListener? gestureListener; - - WidgetDelegate? widgetDelegate; - - ElementManager({ - required this.contextId, - required this.viewport, - required this.controller, - this.showPerformanceOverlayOverride = false, - this.gestureListener, - this.widgetDelegate, - }) { - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_PROPERTY_INIT); - PerformanceTiming.instance().mark(PERF_ROOT_ELEMENT_INIT_START); - } - - HTMLElement documentElement = HTMLElement(HTML_ID, htmlNativePtrMap[contextId]!, this); - setEventTarget(documentElement); - - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ROOT_ELEMENT_INIT_END); - } - - _setupObserver(); - - Window window = Window(WINDOW_ID, windowNativePtrMap[contextId]!, this, viewportElement); - setEventTarget(window); - - document = Document(DOCUMENT_ID, documentNativePtrMap[contextId]!, this, documentElement); - setEventTarget(document); - - documentElement.attachTo(document); - - element_registry.defineBuiltInElements(); - - // Listeners need to be registered to window in order to dispatch events on demand. - if (gestureListener != null) { - if (gestureListener!.onTouchStart != null) { - window.addEvent(EVENT_TOUCH_START); - } - - if (gestureListener!.onTouchMove != null) { - window.addEvent(EVENT_TOUCH_MOVE); - } - - if (gestureListener!.onTouchEnd != null) { - window.addEvent(EVENT_TOUCH_END); - } - } - } - - void _setupObserver() { - if (ElementsBinding.instance != null) { - ElementsBinding.instance!.addObserver(this); - } else if (WidgetsBinding.instance != null) { - WidgetsBinding.instance!.addObserver(this); - } - } - - void _teardownObserver() { - if (ElementsBinding.instance != null) { - ElementsBinding.instance!.removeObserver(this); - } else if (WidgetsBinding.instance != null) { - WidgetsBinding.instance!.removeObserver(this); - } - } - - T? getEventTargetByTargetId(int targetId) { - EventTarget? target = _eventTargets[targetId]; - if (target is T) - return target as T; - else - return null; - } - - bool existsTarget(int id) { - return _eventTargets.containsKey(id); - } - - void removeTarget(EventTarget target) { - if (_eventTargets.containsKey(target.targetId)) { - _eventTargets.remove(target.targetId); - } - } - - void setDetachCallback(VoidCallback callback) { - _detachCallbacks.add(callback); - } - - void setEventTarget(EventTarget target) { - _eventTargets[target.targetId] = target; - } - - void clearTargets() { - // Set current eventTargets to a new object, clean old targets by gc. - _eventTargets = {}; - } - - Element createElement( - int id, Pointer nativePtr, String type, Map? props, List? events) { - assert(!existsTarget(id), 'ERROR: Can not create element with same id "$id"'); - - List eventList; - if (events != null) { - eventList = []; - for (var eventName in events) { - if (eventName is String) eventList.add(eventName); - } - } - - Element element = element_registry.createElement(id, nativePtr, type, this); - setEventTarget(element); - return element; - } - - void createTextNode(int id, Pointer nativePtr, String data) { - TextNode textNode = TextNode(id, nativePtr, data, this); - setEventTarget(textNode); - } - - void createDocumentFragment(int id, Pointer nativePtr) { - DocumentFragment documentFragment = DocumentFragment(id, nativePtr, this); - setEventTarget(documentFragment); - } - - void createComment(int id, Pointer nativePtr) { - EventTarget comment = Comment(id, nativePtr, this); - setEventTarget(comment); - } - - void cloneNode(int originalId, int newId) { - EventTarget originalTarget = getEventTargetByTargetId(originalId)!; - EventTarget newTarget = getEventTargetByTargetId(newId)!; - - // Current only element clone will process in dart. - if (originalTarget is Element) { - Element newElement = newTarget as Element; - // Copy inline style. - originalTarget.inlineStyle.forEach((key, value) { - newElement.setInlineStyle(key, value); - }); - // Copy element attributes. - originalTarget.properties.forEach((key, value) { - newElement.setProperty(key, value); - }); - } - } - - void removeNode(int targetId) { - assert(existsTarget(targetId), 'targetId: $targetId'); - - Node target = getEventTargetByTargetId(targetId)!; - - // Should detach renderObject. - target.disposeRenderObject(); - - target.parentNode?.removeChild(target); - - _debugDOMTreeChanged(); - } - - // TODO: https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets - List adoptedStyleSheets = []; - List styleSheets = []; - - void addStyleSheet(CSSStyleSheet sheet) { - styleSheets.add(sheet); - recalculateDocumentStyle(); - } - - void removeStyleSheet(CSSStyleSheet sheet) { - styleSheets.remove(sheet); - recalculateDocumentStyle(); - } - - void recalculateDocumentStyle() { - // Recalculate style for all nodes sync. - document.documentElement.recalculateNestedStyle(); - } - - void setProperty(int targetId, String key, dynamic value) { - assert(existsTarget(targetId), 'targetId: $targetId key: $key value: $value'); - Node target = getEventTargetByTargetId(targetId)!; - - if (target is Element) { - // Only Element has properties. - target.setProperty(key, value); - } else if (target is TextNode && key == 'data' || key == 'nodeValue') { - (target as TextNode).data = value; - } else { - debugPrint('Only element has properties, try setting $key to Node(#$targetId).'); - } - } - - dynamic getProperty(int targetId, String key) { - assert(existsTarget(targetId), 'targetId: $targetId key: $key'); - Node target = getEventTargetByTargetId(targetId)!; - - if (target is Element) { - // Only Element has properties - return target.getProperty(key); - } else if (target is TextNode && key == 'data' || key == 'nodeValue') { - return (target as TextNode).data; - } else { - return null; - } - } - - void removeProperty(int targetId, String key) { - assert(existsTarget(targetId), 'targetId: $targetId key: $key'); - Node target = getEventTargetByTargetId(targetId)!; - - if (target is Element) { - target.removeProperty(key); - } else if (target is TextNode && key == 'data' || key == 'nodeValue') { - (target as TextNode).data = ''; - } else { - debugPrint('Only element has properties, try removing $key from Node(#$targetId).'); - } - } - - void setInlineStyle(int targetId, String key, dynamic value) { - assert(existsTarget(targetId), 'id: $targetId key: $key value: $value'); - Node? target = getEventTargetByTargetId(targetId); - if (target == null) return; - - if (target is Element) { - target.setInlineStyle(key, value); - } else { - debugPrint('Only element has style, try setting style.$key from Node(#$targetId).'); - } - } - - void flushPendingStyleProperties(int targetId) { - if (!existsTarget(targetId)) return; - Node? target = getEventTargetByTargetId(targetId); - if (target == null) return; - - if (target is Element) { - target.style.flushPendingProperties(); - } else { - debugPrint('Only element has style, try flushPendingStyleProperties from Node(#$targetId).'); - } - } - - /// - ///

- /// - /// foo - /// - ///

- /// - void insertAdjacentNode(int targetId, String position, int newTargetId) { - assert(existsTarget(targetId), 'targetId: $targetId position: $position newTargetId: $newTargetId'); - assert(existsTarget(newTargetId), 'newTargetId: $newTargetId position: $position'); - - Node target = getEventTargetByTargetId(targetId)!; - Node newNode = getEventTargetByTargetId(newTargetId)!; - Node? targetParentNode = target.parentNode; - - switch (position) { - case 'beforebegin': - targetParentNode!.insertBefore(newNode, target); - break; - case 'afterbegin': - target.insertBefore(newNode, target.firstChild); - break; - case 'beforeend': - target.appendChild(newNode); - break; - case 'afterend': - if (targetParentNode!.lastChild == target) { - targetParentNode.appendChild(newNode); - } else { - targetParentNode.insertBefore( - newNode, - targetParentNode.childNodes[targetParentNode.childNodes.indexOf(target) + 1], - ); - } - - break; - } - - _debugDOMTreeChanged(); - } - - void addEvent(int targetId, String eventType) { - if (!existsTarget(targetId)) return; - EventTarget target = getEventTargetByTargetId(targetId)!; - - if (target is Element) { - target.addEvent(eventType); - } else if (target is Window) { - target.addEvent(eventType); - } else if (target is Document) { - target.addEvent(eventType); - } - } - - void removeEvent(int targetId, String eventType) { - assert(existsTarget(targetId), 'targetId: $targetId event: $eventType'); - - Element target = getEventTargetByTargetId(targetId)!; - - target.removeEvent(eventType); - } - - RenderBox getRootRenderBox() { - return viewport; - } - - double getRootFontSize() { - RenderBoxModel rootBoxModel = viewportElement.renderBoxModel!; - return rootBoxModel.renderStyle.fontSize.computedValue; - } - - bool showPerformanceOverlay = false; - - RenderBox buildRenderBox({bool showPerformanceOverlay = false}) { - this.showPerformanceOverlay = showPerformanceOverlay; - - RenderBox renderBox = getRootRenderBox(); - - // We need to add PerformanceOverlay of it's needed. - if (showPerformanceOverlayOverride != null) showPerformanceOverlay = showPerformanceOverlayOverride!; - - if (showPerformanceOverlay) { - RenderPerformanceOverlay renderPerformanceOverlay = - RenderPerformanceOverlay(optionsMask: 15, rasterizerThreshold: 0); - RenderConstrainedBox renderConstrainedPerformanceOverlayBox = RenderConstrainedBox( - child: renderPerformanceOverlay, - additionalConstraints: BoxConstraints.tight(Size( - math.min(350.0, window.physicalSize.width), - math.min(150.0, window.physicalSize.height), - )), - ); - RenderFpsOverlay renderFpsOverlayBox = RenderFpsOverlay(); - - renderBox = RenderStack( - children: [ - renderBox, - renderConstrainedPerformanceOverlayBox, - renderFpsOverlayBox, - ], - textDirection: TextDirection.ltr, - ); - } - - return renderBox; - } - - void attach(RenderObject parent, RenderObject? previousSibling, {bool showPerformanceOverlay = false}) { - RenderObject root = buildRenderBox(showPerformanceOverlay: showPerformanceOverlay); - - if (parent is ContainerRenderObjectMixin) { - parent.insert(root, after: previousSibling); - } else if (parent is RenderObjectWithChildMixin) { - parent.child = root; - } - } - - void detach() { - RenderObject? parent = viewport.parent as RenderObject?; - - if (parent == null) return; - - // Detach renderObjects - viewportElement.disposeRenderObject(); - - // run detachCallbacks - for (var callback in _detachCallbacks) { - callback(); - } - _detachCallbacks.clear(); - } - - // Hooks for DevTools. - VoidCallback? debugDOMTreeChanged; - void _debugDOMTreeChanged() { - VoidCallback? f = debugDOMTreeChanged; - if (f != null) { - f(); - } - } - - void dispose() { - _teardownObserver(); - debugDOMTreeChanged = null; - } - - @override - void didChangeAccessibilityFeatures() { } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { } - - @override - void didChangeLocales(List? locale) { } - - WindowPadding _prevViewInsets = window.viewInsets; - - @override - void didChangeMetrics() { - double bottomInset = window.viewInsets.bottom / window.devicePixelRatio; - if (_prevViewInsets.bottom > window.viewInsets.bottom) { - // Hide keyboard - viewport.bottomInset = bottomInset; - } else { - bool shouldScrollByToCenter = false; - InputElement? focusInputElement = InputElement.focusInputElement; - if (focusInputElement != null) { - RenderBox? renderer = focusInputElement.renderer; - if (renderer != null && renderer.hasSize) { - Offset focusOffset = renderer.localToGlobal(Offset.zero); - // FOCUS_VIEWINSET_BOTTOM_OVERALL to meet border case. - if (focusOffset.dy > viewportHeight - bottomInset - FOCUS_VIEWINSET_BOTTOM_OVERALL) { - shouldScrollByToCenter = true; - } - } - } - // Show keyboard - viewport.bottomInset = bottomInset; - if (shouldScrollByToCenter) { - SchedulerBinding.instance!.addPostFrameCallback((_) { - viewportElement.scrollBy(dy: bottomInset); - }); - } - } - _prevViewInsets = window.viewInsets; - } - - @override - void didChangePlatformBrightness() { } - - @override - void didChangeTextScaleFactor() { } - - @override - void didHaveMemoryPressure() { } - - @override - Future didPopRoute() async { - return false; - } - - @override - Future didPushRoute(String route) async { - return false; - } - - @override - Future didPushRouteInformation(RouteInformation routeInformation) async { - return false; - } -} diff --git a/kraken/lib/src/dom/element_registry.dart b/kraken/lib/src/dom/element_registry.dart index fe25f33273..a98d5125df 100644 --- a/kraken/lib/src/dom/element_registry.dart +++ b/kraken/lib/src/dom/element_registry.dart @@ -2,12 +2,10 @@ * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; +typedef ElementCreator = Element Function(EventTargetContext? context); + final Map _elementRegistry = {}; void defineElement(String name, ElementCreator creator) { @@ -17,14 +15,14 @@ void defineElement(String name, ElementCreator creator) { _elementRegistry[name] = creator; } -Element createElement(int id, Pointer nativePtr, String name, ElementManager elementManager) { +Element createElement(String name, EventTargetContext? context){ ElementCreator? creator = _elementRegistry[name]; if (creator == null) { print('ERROR: unexpected element type "$name"'); - return Element(id, nativePtr, elementManager); + return Element(context); } - Element element = creator(id, nativePtr, elementManager); + Element element = creator(context); // Assign tagName, used by inspector. element.tagName = name; return element; @@ -35,77 +33,78 @@ void defineBuiltInElements() { if (_isDefined) return; _isDefined = true; // Inline text - defineElement(BR, (id, nativePtr, elementManager) => BRElement(id, nativePtr, elementManager)); - defineElement(B, (id, nativePtr, elementManager) => BringElement(id, nativePtr, elementManager)); - defineElement(ABBR, (id, nativePtr, elementManager) => AbbreviationElement(id, nativePtr, elementManager)); - defineElement(EM, (id, nativePtr, elementManager) => EmphasisElement(id, nativePtr, elementManager)); - defineElement(CITE, (id, nativePtr, elementManager) => CitationElement(id, nativePtr, elementManager)); - defineElement(I, (id, nativePtr, elementManager) => IdiomaticElement(id, nativePtr, elementManager)); - defineElement(CODE, (id, nativePtr, elementManager) => CodeElement(id, nativePtr, elementManager)); - defineElement(SAMP, (id, nativePtr, elementManager) => SampleElement(id, nativePtr, elementManager)); - defineElement(STRONG, (id, nativePtr, elementManager) => StrongElement(id, nativePtr, elementManager)); - defineElement(SMALL, (id, nativePtr, elementManager) => SmallElement(id, nativePtr, elementManager)); - defineElement(S, (id, nativePtr, elementManager) => StrikethroughElement(id, nativePtr, elementManager)); - defineElement(U, (id, nativePtr, elementManager) => UnarticulatedElement(id, nativePtr, elementManager)); - defineElement(VAR, (id, nativePtr, elementManager) => VariableElement(id, nativePtr, elementManager)); - defineElement(TIME, (id, nativePtr, elementManager) => TimeElement(id, nativePtr, elementManager)); - defineElement(DATA, (id, nativePtr, elementManager) => DataElement(id, nativePtr, elementManager)); - defineElement(MARK, (id, nativePtr, elementManager) => MarkElement(id, nativePtr, elementManager)); - defineElement(Q, (id, nativePtr, elementManager) => QuoteElement(id, nativePtr, elementManager)); - defineElement(KBD, (id, nativePtr, elementManager) => KeyboardElement(id, nativePtr, elementManager)); - defineElement(DFN, (id, nativePtr, elementManager) => DefinitionElement(id, nativePtr, elementManager)); - defineElement(SPAN, (id, nativePtr, elementManager) => SpanElement(id, nativePtr, elementManager)); - defineElement(ANCHOR, (id, nativePtr, elementManager) => AnchorElement(id, nativePtr, elementManager)); + defineElement(BR, (context) => BRElement(context)); + defineElement(B, (context) => BringElement(context)); + defineElement(ABBR, (context) => AbbreviationElement(context)); + defineElement(EM, (context) => EmphasisElement(context)); + defineElement(CITE, (context) => CitationElement(context)); + defineElement(I, (context) => IdiomaticElement(context)); + defineElement(CODE, (context) => CodeElement(context)); + defineElement(SAMP, (context) => SampleElement(context)); + defineElement(STRONG, (context) => StrongElement(context)); + defineElement(SMALL, (context) => SmallElement(context)); + defineElement(S, (context) => StrikethroughElement(context)); + defineElement(U, (context) => UnarticulatedElement(context)); + defineElement(VAR, (context) => VariableElement(context)); + defineElement(TIME, (context) => TimeElement(context)); + defineElement(DATA, (context) => DataElement(context)); + defineElement(MARK, (context) => MarkElement(context)); + defineElement(Q, (context) => QuoteElement(context)); + defineElement(KBD, (context) => KeyboardElement(context)); + defineElement(DFN, (context) => DefinitionElement(context)); + defineElement(SPAN, (context) => SpanElement(context)); + defineElement(ANCHOR, (context) => AnchorElement(context)); // Content - defineElement(PRE, (id, nativePtr, elementManager) => PreElement(id, nativePtr, elementManager)); - defineElement(PARAGRAPH, (id, nativePtr, elementManager) => ParagraphElement(id, nativePtr, elementManager)); - defineElement(DIV, (id, nativePtr, elementManager) => DivElement(id, nativePtr, elementManager)); - defineElement(UL, (id, nativePtr, elementManager) => UListElement(id, nativePtr, elementManager)); - defineElement(OL, (id, nativePtr, elementManager) => OListElement(id, nativePtr, elementManager)); - defineElement(LI, (id, nativePtr, elementManager) => LIElement(id, nativePtr, elementManager)); - defineElement(DL, (id, nativePtr, elementManager) => DListElement(id, nativePtr, elementManager)); - defineElement(DT, (id, nativePtr, elementManager) => DTElement(id, nativePtr, elementManager)); - defineElement(DD, (id, nativePtr, elementManager) => DDElement(id, nativePtr, elementManager)); - defineElement(FIGURE, (id, nativePtr, elementManager) => FigureElement(id, nativePtr, elementManager)); - defineElement(FIGCAPTION, (id, nativePtr, elementManager) => FigureCaptionElement(id, nativePtr, elementManager)); - defineElement(BLOCKQUOTE, (id, nativePtr, elementManager) => BlockQuotationElement(id, nativePtr, elementManager)); - defineElement(TEMPLATE, (id, nativePtr, elementManager) => TemplateElement(id, nativePtr, elementManager)); + defineElement(PRE, (context) => PreElement(context)); + defineElement(PARAGRAPH, (context) => ParagraphElement(context)); + defineElement(DIV, (context) => DivElement(context)); + defineElement(UL, (context) => UListElement(context)); + defineElement(OL, (context) => OListElement(context)); + defineElement(LI, (context) => LIElement(context)); + defineElement(DL, (context) => DListElement(context)); + defineElement(DT, (context) => DTElement(context)); + defineElement(DD, (context) => DDElement(context)); + defineElement(FIGURE, (context) => FigureElement(context)); + defineElement(FIGCAPTION, (context) => FigureCaptionElement(context)); + defineElement(BLOCKQUOTE, (context) => BlockQuotationElement(context)); + defineElement(TEMPLATE, (context) => TemplateElement(context)); // Sections - defineElement(ADDRESS, (id, nativePtr, elementManager) => AddressElement(id, nativePtr, elementManager)); - defineElement(ARTICLE, (id, nativePtr, elementManager) => ArticleElement(id, nativePtr, elementManager)); - defineElement(ASIDE, (id, nativePtr, elementManager) => AsideElement(id, nativePtr, elementManager)); - defineElement(FOOTER, (id, nativePtr, elementManager) => FooterElement(id, nativePtr, elementManager)); - defineElement(HEADER, (id, nativePtr, elementManager) => HeaderElement(id, nativePtr, elementManager)); - defineElement(MAIN, (id, nativePtr, elementManager) => MainElement(id, nativePtr, elementManager)); - defineElement(NAV, (id, nativePtr, elementManager) => NavElement(id, nativePtr, elementManager)); - defineElement(SECTION, (id, nativePtr, elementManager) => SectionElement(id, nativePtr, elementManager)); + defineElement(ADDRESS, (context) => AddressElement(context)); + defineElement(ARTICLE, (context) => ArticleElement(context)); + defineElement(ASIDE, (context) => AsideElement(context)); + defineElement(FOOTER, (context) => FooterElement(context)); + defineElement(HEADER, (context) => HeaderElement(context)); + defineElement(MAIN, (context) => MainElement(context)); + defineElement(NAV, (context) => NavElement(context)); + defineElement(SECTION, (context) => SectionElement(context)); // Headings - defineElement(H1, (id, nativePtr, elementManager) => H1Element(id, nativePtr, elementManager)); - defineElement(H2, (id, nativePtr, elementManager) => H2Element(id, nativePtr, elementManager)); - defineElement(H3, (id, nativePtr, elementManager) => H3Element(id, nativePtr, elementManager)); - defineElement(H4, (id, nativePtr, elementManager) => H4Element(id, nativePtr, elementManager)); - defineElement(H5, (id, nativePtr, elementManager) => H5Element(id, nativePtr, elementManager)); - defineElement(H6, (id, nativePtr, elementManager) => H6Element(id, nativePtr, elementManager)); + defineElement(H1, (context) => H1Element(context)); + defineElement(H2, (context) => H2Element(context)); + defineElement(H3, (context) => H3Element(context)); + defineElement(H4, (context) => H4Element(context)); + defineElement(H5, (context) => H5Element(context)); + defineElement(H6, (context) => H6Element(context)); // Forms - defineElement(LABEL, (id, nativePtr, elementManager) => LabelElement(id, nativePtr, elementManager)); - defineElement(BUTTON, (id, nativePtr, elementManager) => ButtonElement(id, nativePtr, elementManager)); - defineElement(INPUT, (id, nativePtr, elementManager) => InputElement(id, nativePtr, elementManager)); + defineElement(LABEL, (context) => LabelElement(context)); + defineElement(BUTTON, (context) => ButtonElement(context)); + defineElement(INPUT, (context) => InputElement(context)); // Edits - defineElement(DEL, (id, nativePtr, elementManager) => DelElement(id, nativePtr, elementManager)); - defineElement(INS, (id, nativePtr, elementManager) => InsElement(id, nativePtr, elementManager)); + defineElement(DEL, (context) => DelElement(context)); + defineElement(INS, (context) => InsElement(context)); // Head - defineElement(HEAD, (id, nativePtr, elementManager) => HeadElement(id, nativePtr, elementManager)); - defineElement(TITLE, (id, nativePtr, elementManager) => TitleElement(id, nativePtr, elementManager)); - defineElement(META, (id, nativePtr, elementManager) => MetaElement(id, nativePtr, elementManager)); - defineElement(LINK, (id, nativePtr, elementManager) => LinkElement(id, nativePtr, elementManager)); - defineElement(STYLE, (id, nativePtr, elementManager) => StyleElement(id, nativePtr, elementManager)); - defineElement(NOSCRIPT, (id, nativePtr, elementManager) => NoScriptElement(id, nativePtr, elementManager)); - defineElement(SCRIPT, (id, nativePtr, elementManager) => ScriptElement(id, nativePtr, elementManager)); + defineElement(HEAD, (context) => HeadElement(context)); + defineElement(TITLE, (context) => TitleElement(context)); + defineElement(META, (context) => MetaElement(context)); + defineElement(LINK, (context) => LinkElement(context)); + defineElement(STYLE, (context) => StyleElement(context)); + defineElement(NOSCRIPT, (context) => NoScriptElement(context)); + defineElement(SCRIPT, (context) => ScriptElement(context)); // Object - defineElement(OBJECT, (id, nativePtr, elementManager) => ObjectElement(id, nativePtr, elementManager)); - defineElement(PARAM, (id, nativePtr, elementManager) => ParamElement(id, nativePtr, elementManager)); + defineElement(OBJECT, (context) => ObjectElement(context)); + defineElement(PARAM, (context) => ParamElement(context)); // Others - defineElement(BODY, (id, nativePtr, elementManager) => BodyElement(id, nativePtr, elementManager)); - defineElement(IMAGE, (id, nativePtr, elementManager) => ImageElement(id, nativePtr, elementManager)); - defineElement(CANVAS, (id, nativePtr, elementManager) => CanvasElement(id, nativePtr, elementManager)); + defineElement(HTML, (context) => HTMLElement(context)); + defineElement(BODY, (context) => BodyElement(context)); + defineElement(IMAGE, (context) => ImageElement(context)); + defineElement(CANVAS, (context) => CanvasElement(context)); } diff --git a/kraken/lib/src/dom/element_native_methods.dart b/kraken/lib/src/dom/element_view.dart similarity index 97% rename from kraken/lib/src/dom/element_native_methods.dart rename to kraken/lib/src/dom/element_view.dart index 77fab31677..519e5665a9 100644 --- a/kraken/lib/src/dom/element_native_methods.dart +++ b/kraken/lib/src/dom/element_view.dart @@ -27,7 +27,7 @@ enum ViewModuleProperty { scrollWidth } -mixin ElementNativeMethods on Node { +mixin ElementViewMixin on ElementBase { @override dynamic handleJSCall(String method, List argv) { Element element = (this as Element); @@ -69,9 +69,9 @@ mixin ElementNativeMethods on Node { ViewModuleProperty kind = ViewModuleProperty.values[property]; switch(kind) { case ViewModuleProperty.offsetTop: - return element.getOffsetY(); + return element.offsetTop; case ViewModuleProperty.offsetLeft: - return element.getOffsetX(); + return element.offsetLeft; case ViewModuleProperty.offsetWidth: return elementRenderBoxModel.hasSize ? elementRenderBoxModel.size.width : 0; case ViewModuleProperty.offsetHeight: diff --git a/kraken/lib/src/dom/elements/a.dart b/kraken/lib/src/dom/elements/a.dart index bd80e73195..70fbd826b4 100644 --- a/kraken/lib/src/dom/elements/a.dart +++ b/kraken/lib/src/dom/elements/a.dart @@ -2,11 +2,7 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - import 'package:flutter/gestures.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/kraken.dart'; import 'package:kraken/module.dart'; @@ -26,8 +22,8 @@ class AnchorElement extends Element { String? _target; - AnchorElement(int targetId, Pointer nativeEventTargetPtr, ElementManager elementManager) - : super(targetId, nativeEventTargetPtr, elementManager) { + AnchorElement(EventTargetContext? context) + : super(context) { addEvent(EVENT_CLICK); } @@ -106,10 +102,10 @@ class AnchorElement extends Element { String? href = _href; if (href.isNotEmpty) { - String baseUrl = elementManager.controller.href; + String baseUrl = ownerDocument.controller.href; Uri baseUri = Uri.parse(baseUrl); - Uri resolvedUri = elementManager.controller.uriParser!.resolve(baseUri, Uri.parse(href)); - elementManager.controller.view.handleNavigationAction( + Uri resolvedUri = ownerDocument.controller.uriParser!.resolve(baseUri, Uri.parse(href)); + ownerDocument.controller.view.handleNavigationAction( baseUrl, resolvedUri.toString(), _getNavigationType(resolvedUri.scheme)); } } diff --git a/kraken/lib/src/dom/elements/body.dart b/kraken/lib/src/dom/elements/body.dart index 7c75c40afe..88ed7cb356 100644 --- a/kraken/lib/src/dom/elements/body.dart +++ b/kraken/lib/src/dom/elements/body.dart @@ -2,10 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -16,20 +12,22 @@ const Map _defaultStyle = { }; class BodyElement extends Element { - BodyElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super( targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); - - @override - void willAttachRenderer() { - super.willAttachRenderer(); - RenderStyle renderStyle = renderBoxModel!.renderStyle; - renderStyle.width = CSSLengthValue(elementManager.viewportWidth, CSSLengthType.PX); - } + BodyElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); @override void addEvent(String eventType) { // Scroll event not working on body. if (eventType == EVENT_SCROLL) return; - super.addEvent(eventType); + + // Event of Body should set to documentElement. + // The Render Object of element which set position may be at the same level as the Render Object of body, + // resulting in the failure to get events handlers. + ownerDocument.documentElement?.addEvent(eventType); + } + + @override + void removeEvent(String eventType) { + ownerDocument.documentElement?.removeEvent(eventType); } } diff --git a/kraken/lib/src/dom/elements/canvas/canvas.dart b/kraken/lib/src/dom/elements/canvas/canvas.dart index b162d5c61a..71a6f6dbb0 100644 --- a/kraken/lib/src/dom/elements/canvas/canvas.dart +++ b/kraken/lib/src/dom/elements/canvas/canvas.dart @@ -47,16 +47,14 @@ class CanvasElement extends Element { static Pointer _getContext( Pointer nativeCanvasElement, Pointer contextId) { - CanvasElement canvasElement = EventTarget.getEventTargetOfNativePtr(nativeCanvasElement) as CanvasElement; + CanvasElement canvasElement = EventTarget.getEventTargetByPointer(nativeCanvasElement) as CanvasElement; canvasElement.getContext(nativeStringToString(contextId)); return canvasElement.painter.context!.nativeCanvasRenderingContext2D; } - CanvasElement(int targetId, Pointer nativeEventTarget, ElementManager elementManager) + CanvasElement(EventTargetContext? context) : super( - targetId, - nativeEventTarget, - elementManager, + context, isIntrinsicBox: true, isDefaultRepaintBoundary: true, defaultStyle: _defaultStyle, diff --git a/kraken/lib/src/dom/elements/canvas/canvas_context_2d.dart b/kraken/lib/src/dom/elements/canvas/canvas_context_2d.dart index e44078dba1..8c7fa519e4 100644 --- a/kraken/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/kraken/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -181,7 +181,7 @@ class CanvasRenderingContext2D { closePath(); break; case 'drawImage': - ImageElement imageElement = EventTarget.getEventTargetOfNativePtr(argv[0]) as ImageElement; + ImageElement imageElement = EventTarget.getEventTargetByPointer(argv[0]) as ImageElement; num sx = 0.0, sy = 0.0, sWidth = 0.0, sHeight = 0.0, dx = 0.0, dy = 0.0, dWidth = 0.0, dHeight = 0.0; if (argv.length == 3) { diff --git a/kraken/lib/src/dom/elements/edits.dart b/kraken/lib/src/dom/elements/edits.dart index c71a983184..3c8690f094 100644 --- a/kraken/lib/src/dom/elements/edits.dart +++ b/kraken/lib/src/dom/elements/edits.dart @@ -2,10 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -22,11 +18,11 @@ const Map _delDefaultStyle = { }; class DelElement extends Element { - DelElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _delDefaultStyle); + DelElement(EventTargetContext? context) + : super(context, defaultStyle: _delDefaultStyle); } class InsElement extends Element { - InsElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _insDefaultStyle); + InsElement(EventTargetContext? context) + : super(context, defaultStyle: _insDefaultStyle); } diff --git a/kraken/lib/src/dom/elements/forms.dart b/kraken/lib/src/dom/elements/forms.dart index 2f0346fd48..a3d6bbac97 100644 --- a/kraken/lib/src/dom/elements/forms.dart +++ b/kraken/lib/src/dom/elements/forms.dart @@ -2,10 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -17,11 +13,11 @@ const Map _defaultStyle = { }; class LabelElement extends Element { - LabelElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager); + LabelElement(EventTargetContext? context) + : super(context); } class ButtonElement extends Element { - ButtonElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + ButtonElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } diff --git a/kraken/lib/src/dom/elements/grouping_content.dart b/kraken/lib/src/dom/elements/grouping_content.dart index 13d297f9b9..32d2daba3a 100644 --- a/kraken/lib/src/dom/elements/grouping_content.dart +++ b/kraken/lib/src/dom/elements/grouping_content.dart @@ -2,9 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -30,6 +27,8 @@ const Map _defaultStyle = { const Map _preDefaultStyle = { DISPLAY: BLOCK, WHITE_SPACE: 'pre', + MARGIN_TOP: '1em', + MARGIN_BOTTOM: '1em', }; const Map _bDefaultStyle = { @@ -60,68 +59,68 @@ const Map _lDefaultStyle = { class DivElement extends Element { - DivElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + DivElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class FigureElement extends Element { - FigureElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _bDefaultStyle); + FigureElement(EventTargetContext? context) + : super(context, defaultStyle: _bDefaultStyle); } class FigureCaptionElement extends Element { - FigureCaptionElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + FigureCaptionElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class BlockQuotationElement extends Element { - BlockQuotationElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _bDefaultStyle); + BlockQuotationElement(EventTargetContext? context) + : super(context, defaultStyle: _bDefaultStyle); } // https://html.spec.whatwg.org/multipage/grouping-content.html#htmlparagraphelement class ParagraphElement extends Element { static Map defaultStyle = _pDefaultStyle; - ParagraphElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: defaultStyle); + ParagraphElement(EventTargetContext? context) + : super(context, defaultStyle: defaultStyle); } // https://html.spec.whatwg.org/multipage/grouping-content.html#htmlulistelement class UListElement extends Element { - UListElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _lDefaultStyle); + UListElement(EventTargetContext? context) + : super(context, defaultStyle: _lDefaultStyle); } // https://html.spec.whatwg.org/multipage/grouping-content.html#htmlolistelement class OListElement extends Element { - OListElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _lDefaultStyle); + OListElement(EventTargetContext? context) + : super(context, defaultStyle: _lDefaultStyle); } class LIElement extends Element { - LIElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + LIElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } // https://html.spec.whatwg.org/multipage/grouping-content.html#htmlpreelement class PreElement extends Element { - PreElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _preDefaultStyle); + PreElement(EventTargetContext? context) + : super(context, defaultStyle: _preDefaultStyle); } // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd class DDElement extends Element { - DDElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _ddDefaultStyle); + DDElement(EventTargetContext? context) + : super(context, defaultStyle: _ddDefaultStyle); } class DTElement extends Element { - DTElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + DTElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } // https://html.spec.whatwg.org/multipage/grouping-content.html#htmldlistelement class DListElement extends Element { - DListElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _pDefaultStyle); + DListElement(EventTargetContext? context) + : super(context, defaultStyle: _pDefaultStyle); } diff --git a/kraken/lib/src/dom/elements/head.dart b/kraken/lib/src/dom/elements/head.dart index f4cd8a8ece..18b0860587 100644 --- a/kraken/lib/src/dom/elements/head.dart +++ b/kraken/lib/src/dom/elements/head.dart @@ -3,12 +3,10 @@ * Author: Kraken Team. */ -import 'dart:ffi'; - import 'package:flutter/scheduler.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; +import 'package:kraken/kraken.dart'; import 'package:kraken/launcher.dart'; // Children of the element all have display:none @@ -25,39 +23,84 @@ const String NOSCRIPT = 'NOSCRIPT'; const String SCRIPT = 'SCRIPT'; class HeadElement extends Element { - HeadElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + HeadElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } +const String _REL_STYLESHEET = 'stylesheet'; + class LinkElement extends Element { - LinkElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + LinkElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); + String? rel; + + @override + void setProperty(String key, dynamic value) { + super.setProperty(key, value); + if (key == 'href') { + _fetchBundle(value); + } else if (key == 'rel') { + rel = value.toString().toLowerCase().trim(); + } + } + + void _fetchBundle(String url) async { + if (url.isNotEmpty && rel == _REL_STYLESHEET && isConnected) { + try { + KrakenBundle bundle = KrakenBundle.fromUrl(url); + await bundle.resolve(contextId); + await bundle.eval(contextId); + + // Successful load. + SchedulerBinding.instance!.addPostFrameCallback((_) { + dispatchEvent(Event(EVENT_LOAD)); + }); + } catch(e) { + // An error occurred. + SchedulerBinding.instance!.addPostFrameCallback((_) { + dispatchEvent(Event(EVENT_ERROR)); + }); + } + SchedulerBinding.instance!.scheduleFrame(); + } + } + + @override + void connectedCallback() async { + super.connectedCallback(); + String? url = getProperty('href'); + if (url != null) { + _fetchBundle(url); + } + } } class MetaElement extends Element { - MetaElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + MetaElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class TitleElement extends Element { - TitleElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + TitleElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class NoScriptElement extends Element { - NoScriptElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + NoScriptElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } -const String _JAVASCRIPT_MIME = 'text/javascript'; +const String _MIME_TEXT_JAVASCRIPT = 'text/javascript'; +const String _MIME_APPLICATION_JAVASCRIPT = 'application/javascript'; +const String _MIME_X_APPLICATION_JAVASCRIPT = 'application/x-javascript'; const String _JAVASCRIPT_MODULE = 'module'; class ScriptElement extends Element { - ScriptElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle) { + ScriptElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle) { } - String type = _JAVASCRIPT_MIME; + String type = _MIME_TEXT_JAVASCRIPT; @override void setProperty(String key, dynamic value) { @@ -70,11 +113,19 @@ class ScriptElement extends Element { } void _fetchBundle(String src) async { + int? contextId = ownerDocument.contextId; + if (contextId == null) return; // Must - if (src.isNotEmpty && isConnected && (type == _JAVASCRIPT_MIME || type == _JAVASCRIPT_MODULE)) { + if (src.isNotEmpty && isConnected && ( + type == _MIME_TEXT_JAVASCRIPT + || type == _MIME_APPLICATION_JAVASCRIPT + || type == _MIME_X_APPLICATION_JAVASCRIPT + || type == _JAVASCRIPT_MODULE + )) { try { - KrakenBundle bundle = await KrakenBundle.getBundle(src, contextId: elementManager.contextId); - await bundle.eval(elementManager.contextId); + KrakenBundle bundle = KrakenBundle.fromUrl(src); + await bundle.resolve(contextId); + await bundle.eval(contextId); // Successful load. SchedulerBinding.instance!.addPostFrameCallback((_) { dispatchEvent(Event(EVENT_LOAD)); @@ -92,10 +143,12 @@ class ScriptElement extends Element { @override void connectedCallback() async { super.connectedCallback(); + int? contextId = ownerDocument.contextId; + if (contextId == null) return; String? src = getProperty('src'); if (src != null) { _fetchBundle(src); - } else if (type == _JAVASCRIPT_MIME || type == _JAVASCRIPT_MODULE){ + } else if (type == _MIME_TEXT_JAVASCRIPT || type == _JAVASCRIPT_MODULE){ // Eval script context: StringBuffer buffer = StringBuffer(); childNodes.forEach((node) { @@ -105,11 +158,11 @@ class ScriptElement extends Element { }); String script = buffer.toString(); if (script.isNotEmpty) { - int contextId = elementManager.contextId; KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); if (controller != null) { - KrakenBundle bundle = await KrakenBundle.getBundle(controller.href, contentOverride: script, contextId: contextId); - await bundle.eval(elementManager.contextId); + KrakenBundle bundle = KrakenBundle.fromContent(script, url: controller.href); + bundle.resolve(contextId); + await bundle.eval(contextId); } } } @@ -119,8 +172,8 @@ class ScriptElement extends Element { const String _CSS_MIME = 'text/css'; class StyleElement extends Element { - StyleElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + StyleElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); String type = _CSS_MIME; CSSStyleSheet? _styleSheet; @@ -131,6 +184,7 @@ class StyleElement extends Element { type = value.toString().toLowerCase().trim(); } } + @override void connectedCallback() { if (type == _CSS_MIME) { @@ -142,7 +196,7 @@ class StyleElement extends Element { }); String style = buffer.toString(); _styleSheet = CSSStyleSheet(style); - elementManager.addStyleSheet(_styleSheet!); + ownerDocument.addStyleSheet(_styleSheet!); } super.connectedCallback(); } @@ -150,7 +204,7 @@ class StyleElement extends Element { @override void disconnectedCallback() { if (_styleSheet != null) { - elementManager.removeStyleSheet(_styleSheet!); + ownerDocument.removeStyleSheet(_styleSheet!); } super.disconnectedCallback(); } diff --git a/kraken/lib/src/dom/elements/headings.dart b/kraken/lib/src/dom/elements/headings.dart index 641f239cec..6fa96a01c4 100644 --- a/kraken/lib/src/dom/elements/headings.dart +++ b/kraken/lib/src/dom/elements/headings.dart @@ -2,9 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -64,31 +61,31 @@ const Map _h6DefaultStyle = { }; class H1Element extends Element { - H1Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h1DefaultStyle); + H1Element(EventTargetContext? context) + : super(context, defaultStyle: _h1DefaultStyle); } class H2Element extends Element { - H2Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h2DefaultStyle); + H2Element(EventTargetContext? context) + : super(context, defaultStyle: _h2DefaultStyle); } class H3Element extends Element { - H3Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h3DefaultStyle); + H3Element(EventTargetContext? context) + : super(context, defaultStyle: _h3DefaultStyle); } class H4Element extends Element { - H4Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h4DefaultStyle); + H4Element(EventTargetContext? context) + : super(context, defaultStyle: _h4DefaultStyle); } class H5Element extends Element { - H5Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h5DefaultStyle); + H5Element(EventTargetContext? context) + : super(context, defaultStyle: _h5DefaultStyle); } class H6Element extends Element { - H6Element(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _h6DefaultStyle); + H6Element(EventTargetContext? context) + : super(context, defaultStyle: _h6DefaultStyle); } diff --git a/kraken/lib/src/dom/elements/html.dart b/kraken/lib/src/dom/elements/html.dart index a4943ae1d5..e808505b33 100644 --- a/kraken/lib/src/dom/elements/html.dart +++ b/kraken/lib/src/dom/elements/html.dart @@ -2,55 +2,20 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; -import 'package:kraken/kraken.dart'; const String HTML = 'HTML'; const Map _defaultStyle = { DISPLAY: BLOCK, - OVERFLOW: AUTO }; class HTMLElement extends Element { static Map defaultStyle = _defaultStyle; - HTMLElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super( - targetId, - nativePtr, - elementManager, - defaultStyle: defaultStyle - ) { - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ROOT_ELEMENT_PROPERTY_INIT); - } - elementManager.viewportElement = this; - // Must init with viewport width. - renderStyle.width = CSSLengthValue(elementManager.viewportWidth, CSSLengthType.PX); - renderStyle.height = CSSLengthValue(elementManager.viewportHeight, CSSLengthType.PX); - } - - @override - void attachTo(Node parent, {RenderBox? after}) { - super.attachTo(parent); - if (renderBoxModel != null) { - elementManager.viewport.child = renderBoxModel!; - } - } - - @override - void disposeRenderObject() { - super.disposeRenderObject(); - if (renderBoxModel != null) { - elementManager.viewport.child = null; - elementManager.viewport.dropChild(renderBoxModel!); - } + HTMLElement(EventTargetContext? context) + : super(context, defaultStyle: defaultStyle) { + // Since the bubbling process is in bridge, we need to globally hijack click for focus shifting, so you need to listen here. + addEvent(EVENT_CLICK); } @override @@ -59,7 +24,4 @@ class HTMLElement extends Element { if (eventType == EVENT_SCROLL) return; super.addEvent(eventType); } - - @override - String get tagName => HTML; } diff --git a/kraken/lib/src/dom/elements/img.dart b/kraken/lib/src/dom/elements/img.dart index 55b8ba4010..60af1f37d2 100644 --- a/kraken/lib/src/dom/elements/img.dart +++ b/kraken/lib/src/dom/elements/img.dart @@ -2,19 +2,19 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - import 'dart:async'; -import 'dart:ffi'; -import 'dart:ui' as ui show Image; +import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; +import 'package:kraken/painting.dart'; import 'package:kraken/rendering.dart'; const String IMAGE = 'IMG'; +const String NATURAL_WIDTH = 'naturalWidth'; +const String NATURAL_HEIGHT = 'naturalHeight'; // FIXME: should be inline default. const Map _defaultStyle = { @@ -24,39 +24,42 @@ const Map _defaultStyle = { // The HTMLImageElement. class ImageElement extends Element { // The render box to draw image. - RenderImage? _renderImage; - - // The image source url. - String? get _source => getProperty('src'); - - ImageProvider? _imageProvider; - - ImageStream? _imageStream; + KrakenRenderImage? _renderImage; - ImageInfo? _imageInfo; + ImageProvider? _cachedImageProvider; + dynamic _imageProviderKey; - late ImageStreamListener _renderStreamListener; + ImageStream? _cachedImageStream; + ImageInfo? _cachedImageInfo; + Uri? _resolvedUri; + // Width and height set through property. double? _propertyWidth; double? _propertyHeight; - ui.Image? get image => _imageInfo?.image; + // Width and height set through style. + double? _styleWidth; + double? _styleHeight; + + ui.Image? get image => _cachedImageInfo?.image; /// Number of image frame, used to identify multi frame image after loaded. int _frameCount = 0; + bool _isListeningStream = false; bool _isInLazyLoading = false; + // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete-dev + // A boolean value which indicates whether or not the image has completely loaded. + bool complete = false; bool get _shouldLazyLoading => properties['loading'] == 'lazy'; + ImageStreamCompleterHandle? _completerHandle; - ImageElement(int targetId, Pointer nativeEventTarget, ElementManager elementManager) + ImageElement(EventTargetContext? context) : super( - targetId, - nativeEventTarget, - elementManager, + context, isIntrinsicBox: true, defaultStyle: _defaultStyle) { - _renderStreamListener = ImageStreamListener(_renderImageStream, onError: _onImageError); } @override @@ -78,11 +81,18 @@ class ImageElement extends Element { // When detach renderer, all listeners will be cleared. renderBoxModel!.addIntersectionChangeListener(_handleIntersectionChange); } else { - _constructImageChild(); _loadImage(); } } - _resize(); + } + + void _loadImage() { + _constructImage(); + // Try to attach image if image is cached. + _attachImage(); + _resizeImage(); + _resolveImage(_resolvedUri); + _listenToStream(); } @override @@ -90,71 +100,101 @@ class ImageElement extends Element { super.didDetachRenderer(); style.removeStyleChangeListener(_stylePropertyChanged); - _resetImage(); + _stopListeningStream(keepStreamAlive: true); + } - // Unlink image render box, which has been detached. - _renderImage = null; + ImageStreamListener? _imageStreamListener; + ImageStreamListener _getListener() { + _imageStreamListener ??= ImageStreamListener( + _handleImageFrame, + onError: _onImageError + ); + return _imageStreamListener!; } - void _resetImage() { - _imageInfo = null; + void _listenToStream() { + if (_isListeningStream) + return; - // @NOTE: Evict image cache, make multi frame image can replay. - // https://github.com/flutter/flutter/issues/51775 - _imageProvider?.evict(); - _imageProvider = null; + _cachedImageStream?.addListener(_getListener()); + _completerHandle?.dispose(); + _completerHandle = null; - _renderImage?.image = null; + _isListeningStream = true; } @override void dispose() { super.dispose(); - - _imageProvider?.evict(); - _imageProvider = null; - - _imageStream?.removeListener(_renderStreamListener); - _imageStream = null; - - _renderImage = null; + _stopListeningStream(); + _completerHandle?.dispose(); + _replaceImage(info: null); + _cachedImageProvider?.evict(); + _cachedImageProvider = null; + _imageProviderKey = null; } double get width { - if (_renderImage != null && _renderImage!.width != null) { - return _renderImage!.width!; - } + double? width = _styleWidth ?? _propertyWidth; - if (renderBoxModel != null && renderBoxModel!.hasSize) { - return renderBoxModel!.clientWidth; + if (width == null) { + width = naturalWidth; + double? height = _styleHeight ?? _propertyHeight; + + if (height != null && naturalHeight != 0) { + width = height * naturalWidth / naturalHeight; + } } - // Fallback to natural width, if image is not on screen. - return naturalWidth; + return width; } double get height { - if (_renderImage != null && _renderImage!.height != null) { - return _renderImage!.height!; - } + double? height = _styleHeight ?? _propertyHeight; - if (renderBoxModel != null && renderBoxModel!.hasSize) { - return renderBoxModel!.clientHeight; + if (height == null) { + height = naturalHeight; + double? width = _styleWidth ?? _propertyWidth; + + if (width != null && naturalWidth != 0) { + height = width * naturalHeight / naturalWidth; + } } - // Fallback to natural height, if image is not on screen. - return naturalHeight; + return height; } - double get naturalWidth => image?.width.toDouble() ?? 0; - double get naturalHeight => image?.height.toDouble() ?? 0; + // Read the original image width of loaded image. + // The getter must be called after image had loaded, otherwise will return 0.0. + double get naturalWidth { + ImageProvider? imageProvider = _cachedImageProvider; + if (imageProvider is KrakenResizeImage) { + Size? size = KrakenResizeImage.getImageNaturalSize(_imageProviderKey); + if (size != null) { + return size.width; + } + } + return image?.width.toDouble() ?? 0; + } + + // Read the original image height of loaded image. + // The getter must be called after image had loaded, otherwise will return 0.0. + double get naturalHeight { + ImageProvider? imageProvider = _cachedImageProvider; + if (imageProvider is KrakenResizeImage) { + Size? size = KrakenResizeImage.getImageNaturalSize(_imageProviderKey); + if (size != null) { + return size.height; + } + } + return image?.height.toDouble() ?? 0; + } void _handleIntersectionChange(IntersectionObserverEntry entry) { // When appear if (entry.isIntersecting) { // Once appear remove the listener _resetLazyLoading(); - _constructImageChild(); _loadImage(); } } @@ -164,12 +204,9 @@ class ImageElement extends Element { renderBoxModel!.removeIntersectionChangeListener(_handleIntersectionChange); } - void _constructImageChild() { - _renderImage = createRenderImageBox(); - - if (childNodes.isEmpty) { - addChild(_renderImage!); - } + void _constructImage() { + RenderImage image = _renderImage = _createRenderImageBox(); + addChild(image); } void _dispatchLoadEvent() { @@ -190,92 +227,44 @@ class ImageElement extends Element { } } - void _renderImageStream(ImageInfo imageInfo, bool synchronousCall) { - _frameCount++; - _imageInfo = imageInfo; - - // Only trigger load once. - if (!_loaded) { - _loaded = true; - - if (synchronousCall) { - // `synchronousCall` happens when caches image and calling `addListener`. - scheduleMicrotask(_handleEventAfterImageLoaded); - } else { - _handleEventAfterImageLoaded(); - } - } - - if (isRendererAttached) { - // Multi frame image should convert to repaint boundary. - if (_frameCount > 2) { - forceToRepaintBoundary = true; - } - _resize(); - _renderImage?.image = image; - } - } - - // Mark if the same src loaded. - bool _loaded = false; - void _onImageError(Object exception, StackTrace? stackTrace) { dispatchEvent(Event(EVENT_ERROR)); } - void _resize() { - if (!isRendererAttached) { - return; - } + void _resizeImage() { + assert(isRendererAttached); - double? width = renderStyle.width.isAuto ? _propertyWidth : renderStyle.width.computedValue; - double? height = renderStyle.height.isAuto ? _propertyHeight : renderStyle.height.computedValue; - - if (renderStyle.width.isAuto && _propertyWidth != null) { + if (_styleWidth == null && _propertyWidth != null) { // The intrinsic width of the image in pixels. Must be an integer without a unit. renderStyle.width = CSSLengthValue(_propertyWidth, CSSLengthType.PX); } - if (renderStyle.height.isAuto && _propertyHeight != null) { + if (_styleHeight == null && _propertyHeight != null) { // The intrinsic height of the image, in pixels. Must be an integer without a unit. renderStyle.height = CSSLengthValue(_propertyHeight, CSSLengthType.PX); } - if (width == null && height == null) { - width = naturalWidth; - height = naturalHeight; - } else if (width != null && height == null && naturalWidth != 0) { - height = width * naturalHeight / naturalWidth; - } else if (width == null && height != null && naturalHeight != 0) { - width = height * naturalWidth / naturalHeight; - } - - if (height == null || !height.isFinite) { - height = 0.0; - } - - if (width == null || !width.isFinite) { - width = 0.0; - } + renderStyle.intrinsicWidth = naturalWidth; + renderStyle.intrinsicHeight = naturalHeight; + // Try to update image size if image already resolved. + // Set size to RenderImage is needs, to avoid makeNeedsLayout when update image. _renderImage?.width = width; _renderImage?.height = height; - renderBoxModel!.intrinsicWidth = naturalWidth; - renderBoxModel!.intrinsicHeight = naturalHeight; if (naturalWidth == 0.0 || naturalHeight == 0.0) { - renderBoxModel!.intrinsicRatio = null; + renderStyle.intrinsicRatio = null; } else { - renderBoxModel!.intrinsicRatio = naturalHeight / naturalWidth; + renderStyle.intrinsicRatio = naturalHeight / naturalWidth; } } - RenderImage createRenderImageBox() { + KrakenRenderImage _createRenderImageBox() { RenderStyle renderStyle = renderBoxModel!.renderStyle; BoxFit objectFit = renderStyle.objectFit; Alignment objectPosition = renderStyle.objectPosition; - return RenderImage( - image: _imageInfo?.image, + return KrakenRenderImage( + image: _cachedImageInfo?.image, fit: objectFit, alignment: objectPosition, ); @@ -285,51 +274,170 @@ class ImageElement extends Element { void removeProperty(String key) { super.removeProperty(key); if (key == 'src') { - _resetImage(); - _loaded = false; - } else if (key == 'loading' && _isInLazyLoading && _imageProvider == null) { + _stopListeningStream(keepStreamAlive: true); + } else if (key == 'loading' && _isInLazyLoading && _cachedImageProvider == null) { _resetLazyLoading(); + _stopListeningStream(keepStreamAlive: true); } } + /// Stops listening to the image stream, if this state object has attached a + /// listener. + /// + /// If the listener from this state is the last listener on the stream, the + /// stream will be disposed. To keep the stream alive, set `keepStreamAlive` + /// to true, which create [ImageStreamCompleterHandle] to keep the completer + /// alive. + void _stopListeningStream({bool keepStreamAlive = false}) { + if (!_isListeningStream) + return; + + if (keepStreamAlive && _completerHandle == null && _cachedImageStream?.completer != null) { + _completerHandle = _cachedImageStream!.completer!.keepAlive(); + } + + _cachedImageStream?.removeListener(_getListener()); + _isListeningStream = false; + } + + Uri? _resolveSrc() { + String? src = properties['src']; + if (src != null && src.isNotEmpty) { + Uri base = Uri.parse(ownerDocument.controller.href); + return ownerDocument.controller.uriParser!.resolve(base, Uri.parse(src)); + } + return null; + } + + void _updateSourceStream(ImageStream newStream) { + if (_cachedImageStream?.key == newStream.key) return; + + if (_isListeningStream) { + _cachedImageStream?.removeListener(_getListener()); + } + + _frameCount = 0; + _cachedImageStream = newStream; + + if (_isListeningStream) { + _cachedImageStream!.addListener(_getListener()); + } + } + + // Obtain image resource from resolvedUri, and create an ImageStream that loads the image streams. + // If imageElement has propertySize or width,height properties on renderStyle, + // The image will be encoded into a small size for better rasterization performance. + void _resolveImage(Uri? resolvedUri, { bool updateImageProvider = false }) async { + if (resolvedUri == null) return; + + // Try to make sure that this image can be encoded into a smaller size. + int? cachedWidth = width > 0 && width.isFinite ? (width * ui.window.devicePixelRatio).toInt() : null; + int? cachedHeight = height > 0 && height.isFinite ? (height * ui.window.devicePixelRatio).toInt() : null; + + ImageProvider? provider = _cachedImageProvider; + if (updateImageProvider || provider == null) { + // When cachedWidth or cachedHeight is not null, KrakenResizeImage will be returned. + provider = _cachedImageProvider = getImageProvider(resolvedUri, cachedWidth: cachedWidth, cachedHeight: cachedHeight); + } + if (provider == null) return; + + // Cached the key of imageProvider to read naturalSize of the image. + _imageProviderKey = await provider.obtainKey(ImageConfiguration.empty); + final ImageStream newStream = provider.resolve(ImageConfiguration.empty); + _updateSourceStream(newStream); + } + + void _replaceImage({required ImageInfo? info}) { + _cachedImageInfo = info; + } + + // Attach image to renderImage box. + void _attachImage() { + assert(isRendererAttached); + assert(_renderImage != null); + if (_cachedImageInfo == null) return; + _renderImage!.image = image?.clone(); + } + + + // Callback when image are loaded, encoded and available to use. + // This callback may fire multiple times when image have multiple frames (such as an animated GIF). + void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) { + _replaceImage(info: imageInfo); + _frameCount++; + + if (!complete) { + complete = true; + if (synchronousCall) { + // `synchronousCall` happens when caches image and calling `addListener`. + scheduleMicrotask(_handleEventAfterImageLoaded); + } else { + _handleEventAfterImageLoaded(); + } + } + + // Multi frame image should wrap a repaint boundary for better composite performance. + if (_frameCount > 2) { + forceToRepaintBoundary = true; + } + + _attachImage(); + _resizeImage(); + } + + // Prefetches an image into the image cache. When the imageElement is attached to the renderTree, the imageProvider can directly + // obtain the cached imageStream from imageCache instead of obtaining resources from I/O. + void _precacheImage() async { + final ImageConfiguration config = ImageConfiguration.empty; + final Uri? resolvedUri = _resolvedUri = _resolveSrc(); + if (resolvedUri == null) return; + final ImageProvider? provider = _cachedImageProvider = getImageProvider(resolvedUri); + if (provider == null) return; + _imageProviderKey = await provider.obtainKey(ImageConfiguration.empty); + _frameCount = 0; + final ImageStream stream = provider.resolve(config); + ImageStreamListener? listener; + listener = ImageStreamListener( + (ImageInfo? imageInfo, bool sync) { + _replaceImage(info: imageInfo); + _frameCount++; + + if (!complete && !_shouldLazyLoading) { + complete = true; + if (sync) { + // `synchronousCall` happens when caches image and calling `addListener`. + scheduleMicrotask(_handleEventAfterImageLoaded); + } else { + _handleEventAfterImageLoaded(); + } + } + stream.removeListener(listener!); + }, + onError: _onImageError + ); + stream.addListener(listener); + } + @override void setProperty(String key, dynamic value) { bool propertyChanged = properties[key] != value; super.setProperty(key, value); - // Reset frame number to zero when image needs to reload - _frameCount = 0; - if (key == 'src' && propertyChanged && !_shouldLazyLoading) { - // Loads the image immediately. - _loaded = false; - _loadImage(); - } else if (key == 'loading' && _isInLazyLoading) { - // Should reset lazy when value change. + if (key == 'src' && propertyChanged) { + final Uri? resolvedUri = _resolvedUri = _resolveSrc(); + // Update image source if image already attached except image is lazy loading. + if (isRendererAttached && !_isInLazyLoading) { + _resolveImage(resolvedUri, updateImageProvider: true); + } else { + _precacheImage(); + } + } else if (key == 'loading' && propertyChanged && _isInLazyLoading) { _resetLazyLoading(); } else if (key == WIDTH) { _propertyWidth = CSSNumber.parseNumber(value); - _resize(); + _resolveImage(_resolvedUri, updateImageProvider: true); } else if (key == HEIGHT) { _propertyHeight = CSSNumber.parseNumber(value); - _resize(); - } - } - - void _loadImage() { - _resetImage(); - - if (_source != null && _source!.isNotEmpty) { - Uri base = Uri.parse(elementManager.controller.href); - Uri resolvedUri = elementManager.controller.uriParser!.resolve(base, Uri.parse(_source!)); - - ImageProvider? imageProvider = _imageProvider ?? CSSUrl.parseUrl(resolvedUri, - cache: properties['caching'], contextId: elementManager.contextId); - - if (imageProvider != null) { - _imageProvider = imageProvider; - _imageStream = imageProvider - .resolve(ImageConfiguration.empty) - ..addListener(_renderStreamListener); - } + _resolveImage(_resolvedUri, updateImageProvider: true); } } @@ -337,21 +445,33 @@ class ImageElement extends Element { dynamic getProperty(String key) { switch (key) { case WIDTH: - return _renderImage?.width ?? 0; + return width; case HEIGHT: - return _renderImage?.height ?? 0; - case 'naturalWidth': + return height; + case NATURAL_WIDTH: return naturalWidth; - case 'naturalHeight': + case NATURAL_HEIGHT: return naturalHeight; } - return super.getProperty(key); } void _stylePropertyChanged(String property, String? original, String present) { if (property == WIDTH || property == HEIGHT) { - _resize(); + if (property == WIDTH) { + double? resolveStyleWidth = renderStyle.width.value == null && renderStyle.width.isNotAuto + ? null : renderStyle.width.computedValue; + // To avoid resolved auto, which computed value is infinity, we can not calculate + // infinite double as valid number, mark null to let width/height resized by decode + // size. + _styleWidth = resolveStyleWidth == double.infinity ? null : resolveStyleWidth; + } else if (property == HEIGHT) { + double? resolveStyleHeight = renderStyle.height.value == null && renderStyle.height.isNotAuto + ? null : renderStyle.height.computedValue; + _styleHeight = resolveStyleHeight == double.infinity ? null : resolveStyleHeight; + } + // Resize image + _resolveImage(_resolvedUri, updateImageProvider: true); } else if (property == OBJECT_FIT && _renderImage != null) { _renderImage!.fit = renderBoxModel!.renderStyle.objectFit; } else if (property == OBJECT_POSITION && _renderImage != null) { diff --git a/kraken/lib/src/dom/elements/input.dart b/kraken/lib/src/dom/elements/input.dart index 3490aa3503..5d8770ba54 100644 --- a/kraken/lib/src/dom/elements/input.dart +++ b/kraken/lib/src/dom/elements/input.dart @@ -2,9 +2,7 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - import 'dart:async'; -import 'dart:ffi'; import 'dart:math' as math; import 'dart:ui'; @@ -16,7 +14,6 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' show TextSelectionOverlay, TextSelectionControls, ClipboardStatusNotifier; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/gesture.dart'; @@ -161,7 +158,7 @@ class InputElement extends Element implements TextInputClient, TickerProvider { static void setFocus(InputElement inputElement) { if (InputElement.focusInputElement != inputElement) { // Focus kraken widget to get focus from other widgets. - WidgetDelegate? widgetDelegate = inputElement.elementManager.widgetDelegate; + WidgetDelegate? widgetDelegate = inputElement.ownerDocument.widgetDelegate; if (widgetDelegate != null) { widgetDelegate.requestFocus(); } @@ -225,20 +222,19 @@ class InputElement extends Element implements TextInputClient, TickerProvider { // TODO: support ::placeholder pseudo element return _buildTextSpan( text: placeholderText, + // The color of input placeholder. + color: Color.fromARGB(255, 169, 169, 169) ); } TextInputConfiguration? _textInputConfiguration; - InputElement( - int targetId, - Pointer nativeEventTarget, - ElementManager elementManager, { + InputElement(EventTargetContext? context, { this.textAlign = TextAlign.left, this.textDirection = TextDirection.ltr, this.minLines = 1, this.maxLines = 1, - }) : super(targetId, nativeEventTarget, elementManager, defaultStyle: _defaultStyle, isIntrinsicBox: true) { + }) : super(context, defaultStyle: _defaultStyle, isIntrinsicBox: true) { _textSelectionDelegate = EditableTextDelegate(this); scrollOffsetX = _scrollableX.position; } @@ -282,10 +278,12 @@ class InputElement extends Element implements TextInputClient, TickerProvider { return super.getProperty(key); } + @override void focus() { setFocus(this); } + @override void blur() { clearFocus(); } @@ -364,7 +362,7 @@ class InputElement extends Element implements TextInputClient, TickerProvider { void _onStyleChanged(String property, String? original, String present) { if (_renderInputLeaderLayer != null && isRendererAttached) { - RenderStyle renderStyle = renderBoxModel!.renderStyle; + CSSRenderStyle renderStyle = renderBoxModel!.renderStyle; if (property == HEIGHT) { _renderInputLeaderLayer!.markNeedsLayout(); @@ -400,8 +398,8 @@ class InputElement extends Element implements TextInputClient, TickerProvider { } } - TextSpan _buildTextSpan({ String? text }) { - return CSSTextMixin.createTextSpan(text ?? '', renderStyle); + TextSpan _buildTextSpan({ String? text, Color? color }) { + return CSSTextMixin.createTextSpan(text ?? '', renderStyle, color: color); } TextSpan _buildPasswordTextSpan(String text) { @@ -461,7 +459,6 @@ class InputElement extends Element implements TextInputClient, TickerProvider { ) { if (event.type == EVENT_TOUCH_END) { _textSelectionDelegate.hideToolbar(false); - InputElement.setFocus(this); } TouchList touches = (event as TouchEvent).touches; @@ -581,7 +578,7 @@ class InputElement extends Element implements TextInputClient, TickerProvider { text = _buildPasswordTextSpan(text.text!); } - WidgetDelegate? widgetDelegate = elementManager.widgetDelegate; + WidgetDelegate? widgetDelegate = ownerDocument.widgetDelegate; if (widgetDelegate != null) { cursorColor = widgetDelegate.getCursorColor(); selectionColor = widgetDelegate.getSelectionColor(); @@ -810,7 +807,7 @@ class InputElement extends Element implements TextInputClient, TickerProvider { return; } - WidgetDelegate? widgetDelegate = elementManager.widgetDelegate; + WidgetDelegate? widgetDelegate = ownerDocument.widgetDelegate; if (_selectionControls == null) { _selectionOverlay?.hide(); @@ -851,7 +848,7 @@ class InputElement extends Element implements TextInputClient, TickerProvider { _showSelectionHandles = willShowSelectionHandles; } - WidgetDelegate? widgetDelegate = elementManager.widgetDelegate; + WidgetDelegate? widgetDelegate = ownerDocument.widgetDelegate; if (widgetDelegate != null) { TargetPlatform platform = widgetDelegate.getTargetPlatform(); switch (platform) { diff --git a/kraken/lib/src/dom/elements/object.dart b/kraken/lib/src/dom/elements/object.dart index 8c4980653a..0cb26d88bb 100644 --- a/kraken/lib/src/dom/elements/object.dart +++ b/kraken/lib/src/dom/elements/object.dart @@ -2,11 +2,7 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - import 'package:flutter/rendering.dart'; -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -25,8 +21,8 @@ const Map _paramStyle = { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param class ParamElement extends Element { - ParamElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _paramStyle); + ParamElement(EventTargetContext? context) + : super(context, defaultStyle: _paramStyle); } _DefaultObjectElementClient _DefaultObjectElementClientFactory(ObjectElementHost objectElementHost) { @@ -39,11 +35,10 @@ class ObjectElement extends Element implements ObjectElementHost { late ObjectElementClientFactory _objectElementClientFactory; late ObjectElementClient _objectElementClient; - ObjectElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _objectStyle, isIntrinsicBox: true) { + ObjectElement(EventTargetContext? context) + : super(context, defaultStyle: _objectStyle, isIntrinsicBox: true) { initObjectClient(); initElementClient(); - initDetachCallback(elementManager); } void initObjectClient() { @@ -59,10 +54,6 @@ class ObjectElement extends Element implements ObjectElementHost { } } - void initDetachCallback(final ElementManager elementManager) { - elementManager.setDetachCallback(disposeClient); - } - @override void setProperty(String key, value) { super.setProperty(key, value); diff --git a/kraken/lib/src/dom/elements/sections.dart b/kraken/lib/src/dom/elements/sections.dart index f91a8b7ec9..269d79b993 100644 --- a/kraken/lib/src/dom/elements/sections.dart +++ b/kraken/lib/src/dom/elements/sections.dart @@ -2,10 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -30,41 +26,41 @@ const Map _addressDefaultStyle = { }; class AddressElement extends Element { - AddressElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _addressDefaultStyle); + AddressElement(EventTargetContext? context) + : super(context, defaultStyle: _addressDefaultStyle); } class ArticleElement extends Element { - ArticleElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + ArticleElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class AsideElement extends Element { - AsideElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + AsideElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class FooterElement extends Element { - FooterElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + FooterElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class HeaderElement extends Element { - HeaderElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + HeaderElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class MainElement extends Element { - MainElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + MainElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class NavElement extends Element { - NavElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + NavElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class SectionElement extends Element { - SectionElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + SectionElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } diff --git a/kraken/lib/src/dom/elements/semantics_text.dart b/kraken/lib/src/dom/elements/semantics_text.dart index 3fee365748..de1bacfb44 100644 --- a/kraken/lib/src/dom/elements/semantics_text.dart +++ b/kraken/lib/src/dom/elements/semantics_text.dart @@ -2,12 +2,10 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; +import 'package:flutter/rendering.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; +import 'package:kraken/rendering.dart'; // https://developer.mozilla.org/en-US/docs/Web/HTML/Element#inline_text_semantics const String SPAN = 'SPAN'; @@ -31,11 +29,6 @@ const String KBD = 'KBD'; const String DFN = 'DFN'; const String BR = 'BR'; -// HACK: current use block layout make text force line break -const Map _breakDefaultStyle = { - DISPLAY: BLOCK, -}; - const Map _uDefaultStyle = { TEXT_DECORATION: UNDERLINE }; @@ -72,108 +65,119 @@ const Map _defaultStyle = { // https://html.spec.whatwg.org/multipage/text-level-semantics.html#htmlbrelement class BRElement extends Element { - BRElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super( - targetId, nativePtr, elementManager, - defaultStyle: _breakDefaultStyle, - isIntrinsicBox: true, - ); + RenderLineBreak? _renderLineBreak; + + BRElement(EventTargetContext? context) + : super(context, isIntrinsicBox: true); + + @override + RenderBoxModel? get renderBoxModel => _renderLineBreak; + + @override + void setRenderStyle(String property, String present) { + // Noop + } + + @override + RenderBox createRenderer() { + return _renderLineBreak ??= RenderLineBreak(renderStyle); + } } class BringElement extends Element { - BringElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _boldDefaultStyle); + BringElement(EventTargetContext? context) + : super(context, defaultStyle: _boldDefaultStyle); } class AbbreviationElement extends Element { - AbbreviationElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _abbrDefaultStyle); + AbbreviationElement(EventTargetContext? context) + : super(context, defaultStyle: _abbrDefaultStyle); } class EmphasisElement extends Element { - EmphasisElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + EmphasisElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class CitationElement extends Element { - CitationElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + CitationElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class DefinitionElement extends Element { - DefinitionElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + DefinitionElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i class IdiomaticElement extends Element { - IdiomaticElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + IdiomaticElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class CodeElement extends Element { - CodeElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _codeDefaultStyle); + CodeElement(EventTargetContext? context) + : super(context, defaultStyle: _codeDefaultStyle); } class SampleElement extends Element { - SampleElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _codeDefaultStyle); + SampleElement(EventTargetContext? context) + : super(context, defaultStyle: _codeDefaultStyle); } class KeyboardElement extends Element { - KeyboardElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _codeDefaultStyle); + KeyboardElement(EventTargetContext? context) + : super(context, defaultStyle: _codeDefaultStyle); } class SpanElement extends Element { - SpanElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager); + SpanElement(EventTargetContext? context) + : super(context); } class DataElement extends Element { - DataElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager); + DataElement(EventTargetContext? context) + : super(context); } // TODO: enclosed text is a short inline quotation class QuoteElement extends Element { - QuoteElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager); + QuoteElement(EventTargetContext? context) + : super(context); } class StrongElement extends Element { - StrongElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _boldDefaultStyle); + StrongElement(EventTargetContext? context) + : super(context, defaultStyle: _boldDefaultStyle); } class TimeElement extends Element { - TimeElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _boldDefaultStyle); + TimeElement(EventTargetContext? context) + : super(context, defaultStyle: _boldDefaultStyle); } class SmallElement extends Element { - SmallElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _smallDefaultStyle); + SmallElement(EventTargetContext? context) + : super(context, defaultStyle: _smallDefaultStyle); } class StrikethroughElement extends Element { - StrikethroughElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _sDefaultStyle); + StrikethroughElement(EventTargetContext? context) + : super(context, defaultStyle: _sDefaultStyle); } // https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-u-element class UnarticulatedElement extends Element { - UnarticulatedElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _uDefaultStyle); + UnarticulatedElement(EventTargetContext? context) + : super(context, defaultStyle: _uDefaultStyle); } class VariableElement extends Element { - VariableElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + VariableElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } class MarkElement extends Element { - MarkElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super(targetId, nativePtr, elementManager, defaultStyle: _markDefaultStyle); + MarkElement(EventTargetContext? context) + : super(context, defaultStyle: _markDefaultStyle); } diff --git a/kraken/lib/src/dom/elements/template.dart b/kraken/lib/src/dom/elements/template.dart index a166e1e86b..7ef2d3fddf 100644 --- a/kraken/lib/src/dom/elements/template.dart +++ b/kraken/lib/src/dom/elements/template.dart @@ -2,10 +2,6 @@ * Copyright (C) 2021-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - -import 'package:kraken/bridge.dart'; import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; @@ -16,6 +12,6 @@ const Map _defaultStyle = { }; class TemplateElement extends Element { - TemplateElement(int targetId, Pointer nativePtr, ElementManager elementManager) - : super( targetId, nativePtr, elementManager, defaultStyle: _defaultStyle); + TemplateElement(EventTargetContext? context) + : super(context, defaultStyle: _defaultStyle); } diff --git a/kraken/lib/src/dom/event.dart b/kraken/lib/src/dom/event.dart index 322e8bb9c1..e51058d9c4 100644 --- a/kraken/lib/src/dom/event.dart +++ b/kraken/lib/src/dom/event.dart @@ -103,7 +103,7 @@ class Event { cancelable ? 1 : 0, timeStamp, defaultPrevented ? 1 : 0, - _target != null ? _target.nativeEventTargetPtr.address : nullptr.address, + (_target != null && _target.pointer != null) ? _target.pointer!.address : nullptr.address, nullptr.address ]; @@ -526,7 +526,7 @@ class Touch { Pointer toNative() { Pointer nativeTouch = malloc.allocate(sizeOf()); nativeTouch.ref.identifier = identifier; - nativeTouch.ref.target = target.nativeEventTargetPtr; + nativeTouch.ref.target = target.pointer!; nativeTouch.ref.clientX = clientX; nativeTouch.ref.clientY = clientY; nativeTouch.ref.screenX = screenX; diff --git a/kraken/lib/src/dom/event_target.dart b/kraken/lib/src/dom/event_target.dart index a07fc5944f..82637fd8f8 100644 --- a/kraken/lib/src/dom/event_target.dart +++ b/kraken/lib/src/dom/event_target.dart @@ -2,7 +2,6 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - import 'dart:collection'; import 'dart:ffi'; @@ -62,7 +61,7 @@ void _callNativeMethods(Pointer nativeEventTarget, Pointer re toNativeValue(returnedValue, null); } else { - EventTarget eventTarget = EventTarget.getEventTargetOfNativePtr(nativeEventTarget.cast()); + EventTarget eventTarget = EventTarget.getEventTargetByPointer(nativeEventTarget.cast()); try { if (method.startsWith(GetPropertyCallPreFix) && values.isEmpty) { String key = method.substring(GetPropertyCallPreFix.length); @@ -85,32 +84,38 @@ String jsMethodToKey(String method) { Pointer> _nativeCallNativeMethods = Pointer.fromFunction(_callNativeMethods); +class EventTargetContext { + final int contextId; + final Pointer pointer; + const EventTargetContext(this.contextId, this.pointer); +} + abstract class EventTarget { static final SplayTreeMap _nativeMap = SplayTreeMap(); - static EventTarget getEventTargetOfNativePtr(Pointer nativePtr) { - EventTarget? target = _nativeMap[nativePtr.address]; - if (target == null) throw FlutterError('Can not get eventTarget of nativePtr: $nativePtr'); + static EventTarget getEventTargetByPointer(Pointer pointer) { + EventTarget? target = _nativeMap[pointer.address]; + if (target == null) throw FlutterError('Can not get eventTarget by pointer: $pointer'); return target; } - // A unique target identifier. - final int targetId; + // JS side context id. + int? contextId; + // JS side EventTarget object pointer. + Pointer? pointer; bool _disposed = false; bool get disposed => _disposed; - // The Add - final Pointer nativeEventTargetPtr; - - // the self reference the ElementManager - ElementManager elementManager; - @protected Map> eventHandlers = {}; - EventTarget(this.targetId, this.nativeEventTargetPtr, this.elementManager) { - nativeEventTargetPtr.ref.callNativeMethods = _nativeCallNativeMethods; - _nativeMap[nativeEventTargetPtr.address] = this; + EventTarget(EventTargetContext? context) { + if (context != null) { + contextId = context.contextId; + pointer = context.pointer; + pointer!.ref.callNativeMethods = _nativeCallNativeMethods; + _nativeMap[pointer!.address] = this; + } } void addEventListener(String eventType, EventHandler eventHandler) { @@ -129,25 +134,12 @@ abstract class EventTarget { currentHandlers.remove(eventHandler); } + @mustCallSuper void dispatchEvent(Event event) { if (disposed) return; - event.target = this; - - emitUIEvent(elementManager.controller.view.contextId, nativeEventTargetPtr, event); - // Dispatch listener for widget. - if (elementManager.gestureListener != null) { - if (elementManager.gestureListener?.onTouchStart != null && event.type == EVENT_TOUCH_START) { - elementManager.gestureListener?.onTouchStart!(event as TouchEvent); - } - - if (elementManager.gestureListener?.onTouchMove != null && event.type == EVENT_TOUCH_MOVE) { - elementManager.gestureListener?.onTouchMove!(event as TouchEvent); - } - - if (elementManager.gestureListener?.onTouchEnd != null && event.type == EVENT_TOUCH_END) { - elementManager.gestureListener?.onTouchEnd!(event as TouchEvent); - } + if (contextId != null && pointer != null) { + emitUIEvent(contextId!, pointer!, event); } } @@ -156,25 +148,27 @@ abstract class EventTarget { } @mustCallSuper - dynamic handleJSCall(String method, List argv) { - } + dynamic handleJSCall(String method, List argv) {} @mustCallSuper void dispose() { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_DISPOSE_EVENT_TARGET_START, uniqueId: targetId); + PerformanceTiming.instance().mark(PERF_DISPOSE_EVENT_TARGET_START, uniqueId: hashCode); } - elementManager.removeTarget(this); - eventHandlers.clear(); - _nativeMap.remove(nativeEventTargetPtr.address); _disposed = true; - malloc.free(nativeEventTargetPtr); + eventHandlers.clear(); + + if (pointer != null) { + _nativeMap.remove(pointer!.address); + } if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_DISPOSE_EVENT_TARGET_END, uniqueId: targetId); + PerformanceTiming.instance().mark(PERF_DISPOSE_EVENT_TARGET_END, uniqueId: hashCode); } } - // void addEvent(String eventType) {} + void focus() {} + + void blur() {} } diff --git a/kraken/lib/src/dom/node.dart b/kraken/lib/src/dom/node.dart index 3ce4d6703b..d9472c7a9a 100644 --- a/kraken/lib/src/dom/node.dart +++ b/kraken/lib/src/dom/node.dart @@ -2,12 +2,12 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ -import 'dart:ffi'; - +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:kraken/bridge.dart'; +import 'package:kraken/rendering.dart'; import 'package:kraken/dom.dart'; import 'package:meta/meta.dart'; +import 'package:kraken/widget.dart'; enum NodeType { ELEMENT_NODE, @@ -17,22 +17,6 @@ enum NodeType { DOCUMENT_FRAGMENT_NODE, } -class Comment extends Node { - Comment(int targetId, Pointer nativeEventTarget, ElementManager elementManager) - : super(NodeType.COMMENT_NODE, targetId, nativeEventTarget, elementManager); - - @override - String get nodeName => '#comment'; - - @override - RenderBox? get renderer => null; - - // @TODO: Get data from bridge side. - String get data => ''; - - int get length => data.length; -} - /// [RenderObjectNode] provide the renderObject related abstract life cycle for /// [Node] or [Element]s, which wrap [RenderObject]s, which provide the actual /// rendering of the application. @@ -89,12 +73,17 @@ abstract class LifecycleCallbacks { } abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCallbacks { + KrakenElementToFlutterElementAdaptor? flutterElement; + KrakenElementToWidgetAdaptor? flutterWidget; List childNodes = []; /// The Node.parentNode read-only property returns the parent of the specified node in the DOM tree. Node? parentNode; NodeType nodeType; String get nodeName; + // The read-only ownerDocument property of the Node interface returns the top-level document object of the node. + late Document ownerDocument; + /// The Node.parentElement read-only property returns the DOM node's parent Element, /// or null if the node either has no parent, or its parent isn't a DOM Element. Element? get parentElement { @@ -112,8 +101,8 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa return _children; } - Node(this.nodeType, int targetId, Pointer nativeEventTarget, ElementManager elementManager) - : super(targetId, nativeEventTarget, elementManager); + Node(this.nodeType, EventTargetContext? context) + : super(context); // If node is on the tree, the root parent is body. bool get isConnected { @@ -121,8 +110,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa while (parent.parentNode != null) { parent = parent.parentNode!; } - Document document = elementManager.document; - return this == document || parent == document; + return parent == ownerDocument; } Node get firstChild => childNodes.first; @@ -145,6 +133,10 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Is child renderObject attached. bool get isRendererAttached => renderer != null && renderer!.attached; + bool contains(Node child) { + return childNodes.contains(child); + } + /// Attach a renderObject to parent. void attachTo(Element parent, {RenderBox? after}) {} @@ -170,10 +162,22 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa void willAttachRenderer() {} @override - void didAttachRenderer() {} + @mustCallSuper + void didAttachRenderer() { + // The node attach may affect the whitespace of the nextSibling and previousSibling text node so prev and next node require layout. + if (renderer is RenderBoxModel) { + (renderer as RenderBoxModel).markAdjacentRenderParagraphNeedsLayout(); + } + } @override - void willDetachRenderer() {} + @mustCallSuper + void willDetachRenderer() { + // The node detach may affect the whitespace of the nextSibling and previousSibling text node so prev and next node require layout. + if (renderer is RenderBoxModel) { + (renderer as RenderBoxModel).markAdjacentRenderParagraphNeedsLayout(); + } + } @override void didDetachRenderer() {} @@ -191,38 +195,31 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa return child; } - bool contains(Node child) { - return childNodes.contains(child); - } - - Node getRootNode() { - Node root = this; - while (root.parentNode != null) { - root = root.parentNode as Node; - } - return root; - } - @mustCallSuper - Node insertBefore(Node newNode, Node referenceNode) { - newNode._ensureOrphan(); + Node insertBefore(Node child, Node referenceNode) { + child._ensureOrphan(); int referenceIndex = childNodes.indexOf(referenceNode); if (referenceIndex == -1) { - return appendChild(newNode); + return appendChild(child); } else { - newNode.parentNode = this; - childNodes.insert(referenceIndex, newNode); - if (newNode.isConnected) newNode.connectedCallback(); - return newNode; + child.parentNode = this; + childNodes.insert(referenceIndex, child); + if (child.isConnected) { + child.connectedCallback(); + } + return child; } } @mustCallSuper Node removeChild(Node child) { if (childNodes.contains(child)) { + bool isConnected = child.isConnected; childNodes.remove(child); child.parentNode = null; - child.disconnectedCallback(); + if (isConnected) { + child.disconnectedCallback(); + } } return child; } @@ -231,16 +228,17 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa Node? replaceChild(Node newNode, Node oldNode) { Node? replacedNode; if (childNodes.contains(oldNode)) { + newNode._ensureOrphan(); + bool isOldNodeConnected = oldNode.isConnected; int referenceIndex = childNodes.indexOf(oldNode); oldNode.parentNode = null; replacedNode = oldNode; childNodes[referenceIndex] = newNode; - if (newNode.isConnected) { - newNode.disconnectedCallback(); + + if (isOldNodeConnected) { + oldNode.disconnectedCallback(); newNode.connectedCallback(); } - } else { - appendChild(newNode); } return replacedNode; } @@ -269,6 +267,34 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa child.disconnectedCallback(); } } + + @override + void dispatchEvent(Event event) { + if (disposed) return; + super.dispatchEvent(event); + + // Dispatch listener for widget. + var gestureListener = ownerDocument.gestureListener; + if (gestureListener != null) { + Function? onTouchStart = gestureListener.onTouchStart; + if (onTouchStart != null && event.type == EVENT_TOUCH_START) { + onTouchStart(event as TouchEvent); + } + + Function? onTouchMove = gestureListener.onTouchMove; + if (onTouchMove != null && event.type == EVENT_TOUCH_MOVE) { + onTouchMove(event as TouchEvent); + } + + Function? onTouchEnd = gestureListener.onTouchEnd; + if (onTouchEnd != null && event.type == EVENT_TOUCH_END) { + onTouchEnd(event as TouchEvent); + } + } + + // Dispatch listener for document to do someting such as shift the focus. + ownerDocument.controller.dispatchEvent(event); + } } /// https://dom.spec.whatwg.org/#dom-node-nodetype diff --git a/kraken/lib/src/dom/sliver_manager.dart b/kraken/lib/src/dom/sliver_manager.dart index 72ce6f5d79..37fcd80f64 100644 --- a/kraken/lib/src/dom/sliver_manager.dart +++ b/kraken/lib/src/dom/sliver_manager.dart @@ -12,6 +12,10 @@ import 'package:kraken/dom.dart'; /// manage element to implement lifecycles for sliver list, generate /// renderer from existing element tree. class RenderSliverElementChildManager implements RenderSliverBoxChildManager { + // @NOTE: For hummer support, no real function here. + void restorePreparedChild(int index) { } + void stashPreparedChild(int index) { } + final Element _target; late RenderSliverListLayout _sliverListLayout; @@ -49,11 +53,6 @@ class RenderSliverElementChildManager implements RenderSliverBoxChildManager { childNode.willAttachRenderer(); RenderBox? child; - - if (childNode is Element) { - childNode.style.flushPendingProperties(); - } - if (childNode is Node) { child = childNode.renderer; } else { @@ -68,8 +67,11 @@ class RenderSliverElementChildManager implements RenderSliverBoxChildManager { _sliverListLayout.insertSliverChild(child, after: after); } + if (childNode is Element) { + childNode.style.flushPendingProperties(); + } + childNode.didAttachRenderer(); - childNode.ensureChildAttached(); } RenderBox _createEmptyRenderObject() { diff --git a/kraken/lib/src/dom/text_node.dart b/kraken/lib/src/dom/text_node.dart index 7fca4795c7..28b7d0674e 100644 --- a/kraken/lib/src/dom/text_node.dart +++ b/kraken/lib/src/dom/text_node.dart @@ -2,24 +2,18 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; - import 'package:flutter/rendering.dart'; -import 'package:kraken/bridge.dart'; -import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/rendering.dart'; -final RegExp _whiteSpaceReg = RegExp(r'\s+'); const String WHITE_SPACE_CHAR = ' '; const String NEW_LINE_CHAR = '\n'; const String RETURN_CHAR = '\r'; const String TAB_CHAR = '\t'; class TextNode extends Node { - TextNode(int targetId, Pointer nativeEventTarget, this._data, ElementManager elementManager) - : super(NodeType.TEXT_NODE, targetId, nativeEventTarget, elementManager); + TextNode(this._data, EventTargetContext? context) + : super(NodeType.TEXT_NODE, context); // Must be existed after text node is attached, and all text update will after text attached. RenderTextBox? _renderTextBox; @@ -27,47 +21,7 @@ class TextNode extends Node { static const String NORMAL_SPACE = '\u0020'; // The text string. String? _data; - String get data { - String? _d = _data; - - if (_d == null || _d.isEmpty) return ''; - - WhiteSpace whiteSpace = CSSText.resolveWhiteSpace(parentElement!.style[WHITE_SPACE]); - - /// https://drafts.csswg.org/css-text-3/#propdef-white-space - /// The following table summarizes the behavior of the various white-space values: - // - // New lines / Spaces and tabs / Text wrapping / End-of-line spaces - // normal Collapse Collapse Wrap Remove - // nowrap Collapse Collapse No wrap Remove - // pre Preserve Preserve No wrap Preserve - // pre-wrap Preserve Preserve Wrap Hang - // pre-line Preserve Collapse Wrap Remove - // break-spaces Preserve Preserve Wrap Wrap - if (whiteSpace == WhiteSpace.pre || - whiteSpace == WhiteSpace.preLine || - whiteSpace == WhiteSpace.preWrap || - whiteSpace == WhiteSpace.breakSpaces) { - return whiteSpace == WhiteSpace.preLine ? _collapseWhitespace(_d) : _d; - } else { - String collapsedData = _collapseWhitespace(_d); - // TODO: - // Remove the leading space while prev element have space too: - //

foo bar

- // Refs: - // https://github.com/WebKit/WebKit/blob/6a970b217d59f36e64606ed03f5238d572c23c48/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp#L295 - - if (previousSibling == null) { - collapsedData = collapsedData.trimLeft(); - } - - if (nextSibling == null) { - collapsedData = collapsedData.trimRight(); - } - - return collapsedData; - } - } + String get data => (_data == null || _data!.isEmpty) ? '' : _data!; set data(String? newData) { assert(newData != null); @@ -165,8 +119,3 @@ class TextNode extends Node { assert(_renderTextBox == null); } } - -// ' a b c \n' => ' a b c ' -String _collapseWhitespace(String string) { - return string.replaceAll(_whiteSpaceReg, WHITE_SPACE_CHAR); -} diff --git a/kraken/lib/src/dom/window.dart b/kraken/lib/src/dom/window.dart index 04d7d6de00..8a5c184f96 100644 --- a/kraken/lib/src/dom/window.dart +++ b/kraken/lib/src/dom/window.dart @@ -2,11 +2,8 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - -import 'dart:ffi'; import 'dart:ui'; -import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/launcher.dart'; import 'package:kraken/module.dart'; @@ -14,38 +11,38 @@ import 'package:kraken/module.dart'; const String WINDOW = 'WINDOW'; class Window extends EventTarget { - final Element viewportElement; + final Document document; - Window(int targetId, Pointer nativeEventTarget, ElementManager elementManager, this.viewportElement) : super(targetId, nativeEventTarget, elementManager) { + Window(EventTargetContext context, this.document) : super(context) { window.onPlatformBrightnessChanged = () { ColorSchemeChangeEvent event = ColorSchemeChangeEvent((window.platformBrightness == Brightness.light) ? 'light' : 'dart'); dispatchEvent(event); }; } - static void _open(ElementManager elementManager, String url) { - KrakenController rootController = elementManager.controller.view.rootController; - String? sourceUrl = rootController.bundleURL ?? rootController.bundlePath; + void _open(String url) { + KrakenController rootController = document.controller.view.rootController; + String? sourceUrl = rootController.href; - elementManager.controller.view.handleNavigationAction(sourceUrl, url, KrakenNavigationType.navigate); + document.controller.view.handleNavigationAction(sourceUrl, url, KrakenNavigationType.navigate); } double scrollX() { - return viewportElement.scrollLeft; + return document.documentElement!.scrollLeft; } double scrollY() { - return viewportElement.scrollTop; + return document.documentElement!.scrollTop; } void scrollTo(num x, num y) { - viewportElement.flushLayout(); - viewportElement.scrollTo(x: x, y: y, withAnimation: false); + document.documentElement!.flushLayout(); + document.documentElement!.scrollTo(x: x, y: y, withAnimation: false); } void scrollBy(num x, num y) { - viewportElement.flushLayout(); - viewportElement.scrollBy(dx: x, dy: y, withAnimation: false); + document.documentElement!.flushLayout(); + document.documentElement!.scrollBy(dx: x, dy: y, withAnimation: false); } void addEvent(String eventType) { @@ -58,23 +55,18 @@ class Window extends EventTarget { return addEventListener(eventType, dispatchEvent); case EVENT_SCROLL: // Fired at the Document or element when the viewport or element is scrolled, respectively. - return viewportElement.addEventListener(eventType, dispatchEvent); + return document.documentElement!.addEventListener(eventType, dispatchEvent); case EVENT_RESIZE: // TODO: Fired at the Window when the viewport is resized. break; default: // Events listened on the Window need to be proxied to the Document, because there is a RenderView on the Document, which can handle hitTest. // https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/VisualViewport.cpp#L61 - viewportElement.addEvent(eventType); + document.documentElement!.addEvent(eventType); break; } } - @override - void dispose() { - super.dispose(); - } - @override dynamic handleJSCall(String method, List argv) { switch(method) { @@ -88,7 +80,7 @@ class Window extends EventTarget { case 'scrollY': return scrollY(); case 'open': - return _open(elementManager, argv[0]); + return _open(argv[0]); default: super.handleJSCall(method, argv); } diff --git a/kraken/lib/src/foundation/http_client.dart b/kraken/lib/src/foundation/http_client.dart index 8487c9eb08..906e77673d 100644 --- a/kraken/lib/src/foundation/http_client.dart +++ b/kraken/lib/src/foundation/http_client.dart @@ -70,12 +70,12 @@ class ProxyHttpClient implements HttpClient { } @override - set authenticate(Future Function(Uri url, String scheme, String realm)? f) { + set authenticate(f) { _nativeHttpClient.authenticate = f; } @override - set authenticateProxy( Future Function(String host, int port, String scheme, String realm)? f) { + set authenticateProxy(f) { _nativeHttpClient.authenticateProxy = f; } diff --git a/kraken/lib/src/foundation/http_client_request.dart b/kraken/lib/src/foundation/http_client_request.dart index 372037b8a5..a358855c14 100644 --- a/kraken/lib/src/foundation/http_client_request.dart +++ b/kraken/lib/src/foundation/http_client_request.dart @@ -40,7 +40,7 @@ class ProxyHttpClientRequest extends HttpClientRequest { _nativeHttpClient = nativeHttpClient; @override - Encoding get encoding => _backendRequest?.encoding ?? Encoding.getByName('utf-8')!; + Encoding get encoding => _backendRequest?.encoding ?? utf8; @override set encoding(Encoding _encoding) { diff --git a/kraken/lib/src/gesture/gesture_manager.dart b/kraken/lib/src/gesture/gesture_manager.dart index a9e3a44904..46b66bcd72 100644 --- a/kraken/lib/src/gesture/gesture_manager.dart +++ b/kraken/lib/src/gesture/gesture_manager.dart @@ -186,28 +186,30 @@ class GestureManager { } void onDoubleClick() { - if (_target != null && _target!.onDoubleClick != null) { - _target!.onDoubleClick!(Event(EVENT_DOUBLE_CLICK)); + Function? onDoubleClick = _target?.onDoubleClick; + if (onDoubleClick != null) { + onDoubleClick(Event(EVENT_DOUBLE_CLICK)); } } void onTapUp(TapUpDetails details) { - if (_target != null && _target!.onClick != null) { - if (_target!.onClick != null) { - _target!.onClick!(EVENT_CLICK, details); - } + Function? onClick = _target?.onClick; + if (onClick != null) { + onClick(EVENT_CLICK, details); } } void onSwipe(Event event) { - if (_target != null && _target!.onSwipe != null) { - _target!.onSwipe!(event); + Function? onSwipe = _target?.onSwipe; + if (onSwipe != null) { + onSwipe(event); } } void onPanStart(DragStartDetails details) { - if (_target != null && _target!.onPan != null) { - _target!.onPan!( + Function? onPan = _target?.onPan; + if (onPan != null) { + onPan( GestureEvent( EVENT_PAN, GestureEventInit( @@ -221,8 +223,9 @@ class GestureManager { } void onPanUpdate(DragUpdateDetails details) { - if (_target != null && _target!.onPan != null) { - _target!.onPan!( + Function? onPan = _target?.onPan; + if (onPan != null) { + onPan( GestureEvent( EVENT_PAN, GestureEventInit( @@ -236,8 +239,9 @@ class GestureManager { } void onPanEnd(DragEndDetails details) { - if (_target != null && _target!.onPan != null) { - _target!.onPan!( + Function? onPan = _target?.onPan; + if (onPan != null) { + onPan( GestureEvent( EVENT_PAN, GestureEventInit( @@ -251,8 +255,9 @@ class GestureManager { } void onScaleStart(ScaleStartDetails details) { - if (_target != null && _target!.onScale != null) { - _target!.onScale!( + Function? onScale = _target?.onScale; + if (onScale != null) { + onScale( GestureEvent( EVENT_SCALE, GestureEventInit( state: EVENT_STATE_START ) @@ -262,8 +267,9 @@ class GestureManager { } void onScaleUpdate(ScaleUpdateDetails details) { - if (_target != null && _target!.onScale != null) { - _target!.onScale!( + Function? onScale = _target?.onScale; + if (onScale != null) { + onScale( GestureEvent( EVENT_SCALE, GestureEventInit( @@ -277,8 +283,9 @@ class GestureManager { } void onScaleEnd(ScaleEndDetails details) { - if (_target != null && _target!.onScale != null) { - _target!.onScale!( + Function? onScale = _target?.onScale; + if (onScale != null) { + onScale( GestureEvent( EVENT_SCALE, GestureEventInit( state: EVENT_STATE_END ) @@ -288,8 +295,9 @@ class GestureManager { } void onLongPressEnd(LongPressEndDetails details) { - if (_target != null && _target!.onLongPress != null) { - _target!.onLongPress!( + Function? onLongPress = _target?.onLongPress; + if (onLongPress != null) { + onLongPress( GestureEvent( EVENT_LONG_PRESS, GestureEventInit( diff --git a/kraken/lib/src/gesture/monodrag.dart b/kraken/lib/src/gesture/monodrag.dart index 55cf3514f1..b98788a874 100644 --- a/kraken/lib/src/gesture/monodrag.dart +++ b/kraken/lib/src/gesture/monodrag.dart @@ -45,10 +45,10 @@ abstract class CompetitiveDragGestureRecognizer extends OneSequenceGestureRecogn /// {@macro flutter.gestures.gestureRecognizer.kind} CompetitiveDragGestureRecognizer({ Object? debugOwner, - PointerDeviceKind? kind, + Set? supportedDevices, this.dragStartBehavior = DragStartBehavior.start, this.velocityTrackerBuilder = _defaultBuilder, - }) : super(debugOwner: debugOwner, kind: kind); + }) : super(debugOwner: debugOwner, supportedDevices: supportedDevices); static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker.withKind(event.kind); /// Configure the behavior of offsets sent to [onStart]. @@ -479,8 +479,8 @@ class ScrollVerticalDragGestureRecognizer extends CompetitiveDragGestureRecogniz /// {@macro flutter.gestures.gestureRecognizer.kind} ScrollVerticalDragGestureRecognizer({ Object? debugOwner, - PointerDeviceKind? kind, - }) : super(debugOwner: debugOwner, kind: kind); + Set? supportedDevices, + }) : super(debugOwner: debugOwner, supportedDevices: supportedDevices); late IsAcceptedDragCallback isAcceptedDrag; @@ -522,8 +522,8 @@ class ScrollHorizontalDragGestureRecognizer extends CompetitiveDragGestureRecogn /// {@macro flutter.gestures.gestureRecognizer.kind} ScrollHorizontalDragGestureRecognizer({ Object? debugOwner, - PointerDeviceKind? kind, - }) : super(debugOwner: debugOwner, kind: kind); + Set? supportedDevices, + }) : super(debugOwner: debugOwner, supportedDevices: supportedDevices); late IsAcceptedDragCallback isAcceptedDrag; diff --git a/kraken/lib/src/gesture/scroll_position_with_single_context.dart b/kraken/lib/src/gesture/scroll_position_with_single_context.dart index e227853ebc..8ea964b089 100644 --- a/kraken/lib/src/gesture/scroll_position_with_single_context.dart +++ b/kraken/lib/src/gesture/scroll_position_with_single_context.dart @@ -205,7 +205,7 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ScrollDragController? _currentDrag; @override - Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { + ScrollDragController drag(DragStartDetails details, VoidCallback dragCancelCallback) { final ScrollDragController drag = ScrollDragController( delegate: this, details: details, diff --git a/kraken/lib/src/gesture/scrollable.dart b/kraken/lib/src/gesture/scrollable.dart index 6ca5a282de..67a95e15a1 100644 --- a/kraken/lib/src/gesture/scrollable.dart +++ b/kraken/lib/src/gesture/scrollable.dart @@ -7,6 +7,7 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; @@ -56,7 +57,7 @@ class _CustomTicker extends Ticker { class KrakenScrollable with _CustomTickerProviderStateMixin implements ScrollContext { late AxisDirection _axisDirection; - ScrollPosition? position; + ScrollPositionWithSingleContext? position; final ScrollPhysics _physics = ScrollPhysics.createScrollPhysics(); DragStartBehavior dragStartBehavior; ScrollListener? scrollListener; @@ -98,7 +99,7 @@ class KrakenScrollable with _CustomTickerProviderStateMixin implements ScrollCon } else { switch (axis) { case Axis.vertical: - // Vertical trag gesture recongnizer to trigger vertical scroll. + // Vertical drag gesture recognizer to trigger vertical scroll. _gestureRecognizers = { ScrollVerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( () => ScrollVerticalDragGestureRecognizer(), @@ -119,7 +120,7 @@ class KrakenScrollable with _CustomTickerProviderStateMixin implements ScrollCon }; break; case Axis.horizontal: - // Horizontal trag gesture recongnizer to horizontal vertical scroll. + // Horizontal drag gesture recognizer to horizontal vertical scroll. _gestureRecognizers = { ScrollHorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( () => ScrollHorizontalDragGestureRecognizer(), @@ -146,20 +147,25 @@ class KrakenScrollable with _CustomTickerProviderStateMixin implements ScrollCon _syncAll(_gestureRecognizers); } - // Used in the Arena to judge whether the vertical trag gesture can trigger the current container to scroll. - bool _isAcceptedVerticalDrag (AxisDirection direction) { - double? pixels = (_drag as ScrollDragController).pixels; - double? maxScrollExtent = (_drag as ScrollDragController).maxScrollExtent; - double? minScrollExtent = (_drag as ScrollDragController).minScrollExtent; - return !((direction == AxisDirection.down && pixels! <= minScrollExtent!) || direction == AxisDirection.up && pixels! >= maxScrollExtent!); + // Used in the Arena to judge whether the vertical drag gesture can trigger the current container to scroll. + bool _isAcceptedVerticalDrag(AxisDirection direction) { + ScrollDragController drag = _drag!; + double pixels = drag.pixels!; + double maxScrollExtent = drag.maxScrollExtent!; + double minScrollExtent = drag.minScrollExtent!; + + return !((direction == AxisDirection.down && (pixels <= minScrollExtent || nearEqual(pixels, minScrollExtent, Tolerance.defaultTolerance.distance))) + || direction == AxisDirection.up && (pixels >= maxScrollExtent || nearEqual(pixels, maxScrollExtent, Tolerance.defaultTolerance.distance))); } - // Used in the Arena to judge whether the horizontal trag gesture can trigger the current container to scroll. - bool _isAcceptedHorizontalDrag (AxisDirection direction) { - double? pixels = (_drag as ScrollDragController).pixels; - double? maxScrollExtent = (_drag as ScrollDragController).maxScrollExtent; - double? minScrollExtent = (_drag as ScrollDragController).minScrollExtent; - return !((direction == AxisDirection.right && pixels! <= minScrollExtent!) || direction == AxisDirection.left && pixels! >= maxScrollExtent!); + // Used in the Arena to judge whether the horizontal drag gesture can trigger the current container to scroll. + bool _isAcceptedHorizontalDrag(AxisDirection direction) { + ScrollDragController drag = _drag!; + double pixels = drag.pixels!; + double maxScrollExtent = drag.maxScrollExtent!; + double minScrollExtent = drag.minScrollExtent!; + return !((direction == AxisDirection.right && (pixels <= minScrollExtent || nearEqual(pixels, minScrollExtent, Tolerance.defaultTolerance.distance))) + || direction == AxisDirection.left && (pixels >= maxScrollExtent || nearEqual(pixels, maxScrollExtent, Tolerance.defaultTolerance.distance))); } void _syncAll(Map gestures) { @@ -179,8 +185,7 @@ class KrakenScrollable with _CustomTickerProviderStateMixin implements ScrollCon } // TOUCH HANDLERS - - Drag? _drag; + ScrollDragController? _drag; ScrollHoldController? _hold; void _handleDragDown(DragDownDetails details) { @@ -246,6 +251,9 @@ mixin RenderOverflowMixin on RenderBox { scrollablePointerListener = null; _scrollOffsetX = null; _scrollOffsetY = null; + // Dispose clip layer. + _clipRRectLayer.layer = null; + _clipRectLayer.layer = null; } bool _clipX = false; @@ -370,8 +378,8 @@ mixin RenderOverflowMixin on RenderBox { return paintOffset < Offset.zero || !(Offset.zero & size).contains((paintOffset & childSize).bottomRight); } - ClipRRectLayer? _oldClipRRectLayer; - ClipRectLayer? _oldClipRectLayer; + final LayerHandle _clipRRectLayer = LayerHandle(); + final LayerHandle _clipRectLayer = LayerHandle(); void paintOverflow(PaintingContext context, Offset offset, EdgeInsets borderEdge, BoxDecoration? decoration, PaintingContextCallback callback) { if (clipX == false && clipY == false) return callback(context, offset); @@ -401,13 +409,13 @@ mixin RenderOverflowMixin on RenderBox { bottomLeft: radius.bottomLeft, bottomRight: radius.bottomRight ); - _oldClipRRectLayer = context.pushClipRRect(_needsCompositing, offset, clipRect, clipRRect, painter, oldLayer: _oldClipRRectLayer); + _clipRRectLayer.layer = context.pushClipRRect(_needsCompositing, offset, clipRect, clipRRect, painter, oldLayer: _clipRRectLayer.layer); } else { - _oldClipRectLayer = context.pushClipRect(_needsCompositing, offset, clipRect, painter, oldLayer: _oldClipRectLayer); + _clipRectLayer.layer = context.pushClipRect(_needsCompositing, offset, clipRect, painter, oldLayer: _clipRectLayer.layer); } } else { - _oldClipRRectLayer = null; - _oldClipRectLayer = null; + _clipRectLayer.layer = null; + _clipRRectLayer.layer = null; callback(context, offset); } } diff --git a/kraken/lib/src/gesture/swipe.dart b/kraken/lib/src/gesture/swipe.dart index bd1a4d05a1..c7cedbc5e5 100644 --- a/kraken/lib/src/gesture/swipe.dart +++ b/kraken/lib/src/gesture/swipe.dart @@ -39,11 +39,11 @@ class SwipeGestureRecognizer extends OneSequenceGestureRecognizer { /// {@macro flutter.gestures.gestureRecognizer.kind} SwipeGestureRecognizer({ Object? debugOwner, - PointerDeviceKind? kind, + Set? supportedDevices, this.dragStartBehavior = DragStartBehavior.start, this.velocityTrackerBuilder = _defaultBuilder, this.onSwipe, - }) : super(debugOwner: debugOwner, kind: kind); + }) : super(debugOwner: debugOwner, supportedDevices: supportedDevices); static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker.withKind(event.kind); /// Configure the behavior of offsets sent to [onStart]. diff --git a/kraken/lib/src/launcher/bundle.dart b/kraken/lib/src/launcher/bundle.dart index a567d7ddee..b030ce0ecf 100644 --- a/kraken/lib/src/launcher/bundle.dart +++ b/kraken/lib/src/launcher/bundle.dart @@ -14,6 +14,7 @@ import 'package:kraken/bridge.dart'; import 'package:kraken/foundation.dart'; import 'package:kraken/launcher.dart'; import 'package:kraken/module.dart'; +import 'package:kraken/css.dart'; import 'manifest.dart'; @@ -22,6 +23,9 @@ const String BUNDLE_PATH = 'KRAKEN_BUNDLE_PATH'; const String ENABLE_DEBUG = 'KRAKEN_ENABLE_DEBUG'; const String ENABLE_PERFORMANCE_OVERLAY = 'KRAKEN_ENABLE_PERFORMANCE_OVERLAY'; +const String ASSETS_PROROCOL = 'assets://'; +final ContentType css = ContentType('text', 'css', charset: 'utf-8'); + String? getBundleURLFromEnv() { return Platform.environment[BUNDLE_URL]; } @@ -46,17 +50,21 @@ String getAcceptHeader() { } bool isAssetAbsolutePath(String path) { - return path.indexOf('assets/') == 0; + return path.startsWith(ASSETS_PROROCOL); } abstract class KrakenBundle { - KrakenBundle(this.url); + KrakenBundle(this.src); // Unique resource locator. - final Uri url; + final String src; + + // Customize the parsed uri by uriParser. + Uri? uri; + late ByteData rawBundle; // JS Content in UTF-8 bytes. - Uint8List? byteCode; + Uint8List? bytecode; // JS Content is String String? content; // JS line offset, default to 0. @@ -69,60 +77,67 @@ abstract class KrakenBundle { // Bundle contentType. ContentType contentType = ContentType.binary; - Future resolve(); - - static Future getBundle(String path, { String? contentOverride, required int contextId }) async { - KrakenBundle bundle; - - if (kDebugMode) { - print('Kraken getting bundle for contextId: $contextId, path: $path'); + @mustCallSuper + Future resolve(int? contextId) async { + uri = Uri.parse(src); + if (contextId != null) { + KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + if (controller != null && !isAssetAbsolutePath(src)) { + uri = controller.uriParser!.resolve(Uri.parse(controller.href), uri!); + } } - Uri uri = Uri.parse(path); - KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); - if (controller != null && !isAssetAbsolutePath(path)) { - uri = controller.uriParser!.resolve(Uri.parse(controller.href), uri); - } + isResolved = true; + } - if (contentOverride != null && contentOverride.isNotEmpty) { - bundle = RawBundle.fromString(contentOverride, uri); - } else if (uri.isScheme('HTTP') || uri.isScheme('HTTPS')) { - bundle = NetworkBundle(uri, contextId: contextId); + static KrakenBundle fromUrl(String url, { Map? additionalHttpHeaders }) { + if (isAssetAbsolutePath(url)) { + return AssetsBundle(url); } else { - bundle = AssetsBundle(uri); + return NetworkBundle(url, additionalHttpHeaders: additionalHttpHeaders); } + } - await bundle.resolve(); + static KrakenBundle fromContent(String content, { String url = '' }) { + return RawBundle.fromString(content, url); + } - return bundle; + static KrakenBundle fromBytecode(Uint8List bytecode, { String url = '' }) { + return RawBundle.fromBytecode(bytecode, url); } - Future eval(int contextId) async { - if (!isResolved) await resolve(); + + Future eval(int? contextId) async { + if (!isResolved) await resolve(contextId); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_JS_BUNDLE_EVAL_START); } - // For raw javascript code or bytecode from API directly. - if (content != null) { - evaluateScripts(contextId, content!, url.toString(), lineOffset); - } else if (byteCode != null) { - evaluateQuickjsByteCode(contextId, byteCode!); - } - - // For javascript code, HTML or bytecode from networks and hardware disk. - else if (contentType.mimeType == ContentType.html.mimeType || url.toString().contains('.html')) { - String code = _resolveStringFromData(rawBundle); - // parse html. - parseHTML(contextId, code); - } else if (isByteCodeSupported(contentType.mimeType, url.toString())) { - Uint8List buffer = rawBundle.buffer.asUint8List(); - evaluateQuickjsByteCode(contextId, buffer); - } else { - String code = _resolveStringFromData(rawBundle); - // eval JavaScript. - evaluateScripts(contextId, code, url.toString(), lineOffset); + if (contextId != null) { + // For raw javascript code or bytecode from API directly. + if (content != null) { + evaluateScripts(contextId, content!, src, lineOffset); + } else if (bytecode != null) { + evaluateQuickjsByteCode(contextId, bytecode!); + } + + // For javascript code, HTML or bytecode from networks and hardware disk. + else if (contentType.mimeType == ContentType.html.mimeType || src.contains('.html')) { + String code = _resolveStringFromData(rawBundle); + // parse html. + parseHTML(contextId, code); + } else if (contentType.mimeType == css.mimeType || src.contains('.css')) { + KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + controller?.view.document.addStyleSheet(CSSStyleSheet(_resolveStringFromData(rawBundle))); + } else if (isByteCodeSupported(contentType.mimeType, src)) { + Uint8List buffer = rawBundle.buffer.asUint8List(); + evaluateQuickjsByteCode(contextId, buffer); + } else { + String code = _resolveStringFromData(rawBundle); + // eval JavaScript. + evaluateScripts(contextId, code, src, lineOffset); + } } if (kProfileMode) { @@ -132,33 +147,37 @@ abstract class KrakenBundle { } class RawBundle extends KrakenBundle { - RawBundle.fromString(String content, Uri url) + RawBundle.fromString(String content, String url) : super(url) { this.content = content; } - RawBundle.fromByteCode(Uint8List byteCode, Uri url) : super(url) { - this.byteCode = byteCode; + RawBundle.fromBytecode(Uint8List bytecode, String url) + : super(url) { + this.bytecode = bytecode; } @override - Future resolve() async { + Future resolve(int? contextId) async { + super.resolve(contextId); isResolved = true; } } class NetworkBundle extends KrakenBundle { - int contextId; - NetworkBundle(Uri url, { required this.contextId }) + NetworkBundle(String url, { this.additionalHttpHeaders }) : super(url); + Map? additionalHttpHeaders = {}; + @override - Future resolve() async { + Future resolve(int? contextId) async { + super.resolve(contextId); KrakenController controller = KrakenController.getControllerOfJSContextId(contextId)!; Uri baseUrl = Uri.parse(controller.href); - NetworkAssetBundle bundle = NetworkAssetBundle(controller.uriParser!.resolve(baseUrl, url), contextId: contextId); + NetworkAssetBundle bundle = NetworkAssetBundle(controller.uriParser!.resolve(baseUrl, Uri.parse(src)), contextId: contextId, additionalHttpHeaders: additionalHttpHeaders); bundle.httpClient.userAgent = getKrakenInfo().userAgent; - String absoluteURL = url.toString(); + String absoluteURL = src; rawBundle = await bundle.load(absoluteURL); contentType = bundle.contentType; isResolved = true; @@ -177,13 +196,15 @@ String _resolveStringFromData(ByteData data) { class NetworkAssetBundle extends AssetBundle { /// Creates an network asset bundle that resolves asset keys as URLs relative /// to the given base URL. - NetworkAssetBundle(Uri baseUrl, { required this.contextId }) + NetworkAssetBundle(Uri baseUrl, {int? contextId, Map? additionalHttpHeaders }) : _baseUrl = baseUrl, + _additionalHttpHeaders = additionalHttpHeaders, httpClient = HttpClient(); + int? contextId; final Uri _baseUrl; - final int contextId; final HttpClient httpClient; + final Map? _additionalHttpHeaders; ContentType contentType = ContentType.binary; Uri _urlFromKey(String key) => _baseUrl.resolve(key); @@ -192,7 +213,13 @@ class NetworkAssetBundle extends AssetBundle { Future load(String key) async { final HttpClientRequest request = await httpClient.getUrl(_urlFromKey(key)); request.headers.set('Accept', getAcceptHeader()); - KrakenHttpOverrides.setContextHeader(request.headers, contextId); + if (_additionalHttpHeaders != null) { + _additionalHttpHeaders?.forEach(request.headers.set); + } + + if (contextId != null) { + KrakenHttpOverrides.setContextHeader(request.headers, contextId!); + } final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) @@ -223,15 +250,18 @@ class NetworkAssetBundle extends AssetBundle { } class AssetsBundle extends KrakenBundle { - AssetsBundle(Uri url) + AssetsBundle(String url) : super(url); @override - Future resolve() async { + Future resolve(int? contextId) async { + super.resolve(contextId); // JSBundle get default bundle manifest. manifest = AppManifest(); - String localPath = url.toString(); - rawBundle = await rootBundle.load(localPath); - isResolved = true; + if (isAssetAbsolutePath(src)) { + String localPath = src.substring(ASSETS_PROROCOL.length); + rawBundle = await rootBundle.load(localPath); + } + return this; } } diff --git a/kraken/lib/src/launcher/controller.dart b/kraken/lib/src/launcher/controller.dart index 4b13669b09..74dfe16063 100644 --- a/kraken/lib/src/launcher/controller.dart +++ b/kraken/lib/src/launcher/controller.dart @@ -8,9 +8,14 @@ import 'dart:collection'; import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'package:flutter/widgets.dart' show RenderObjectElement; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart' + show RouteInformation, WidgetsBinding, WidgetsBindingObserver; import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/foundation.dart'; @@ -18,13 +23,19 @@ import 'package:kraken/gesture.dart'; import 'package:kraken/module.dart'; import 'package:kraken/rendering.dart'; import 'package:kraken/widget.dart'; +import 'package:kraken/src/dom/element_registry.dart' as element_registry; + import 'bundle.dart'; +const int WINDOW_ID = -1; +const int DOCUMENT_ID = -2; + // Error handler when load bundle failed. typedef LoadHandler = void Function(KrakenController controller); typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); typedef JSErrorHandler = void Function(String message); +typedef PendingCallback = void Function(); typedef TraverseElementCallback = void Function(Element element); @@ -55,7 +66,11 @@ abstract class DevToolsService { } // An kraken View Controller designed for multiple kraken view control. -class KrakenViewController { +class KrakenViewController + implements WidgetsBindingObserver, ElementsBindingObserver { + static Map> documentNativePtrMap = {}; + static Map> windowNativePtrMap = {}; + KrakenController rootController; // The methods of the KrakenNavigateDelegation help you implement custom behaviors that are triggered @@ -90,7 +105,6 @@ class KrakenViewController { this._viewportWidth, this._viewportHeight, { this.background, - this.showPerformanceOverlay, this.enableDebug = false, int? contextId, required this.rootController, @@ -98,16 +112,12 @@ class KrakenViewController { this.gestureListener, this.widgetDelegate, }) { - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_VIEW_CONTROLLER_PROPERTY_INIT); - } - if (enableDebug) { debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; debugPaintSizeEnabled = true; } - if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_VIEW_CONTROLLER_PROPERTY_INIT); PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_START); } @@ -115,104 +125,191 @@ class KrakenViewController { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_END); - } - - if (kProfileMode) { PerformanceTiming.instance().mark(PERF_CREATE_VIEWPORT_START); } viewport = RenderViewportBox( - background: background, - viewportSize: Size(viewportWidth, viewportHeight), - gestureListener: gestureListener, - controller: rootController - ); + background: background, + viewportSize: Size(viewportWidth, viewportHeight), + gestureListener: gestureListener, + controller: rootController); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_CREATE_VIEWPORT_END); PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_START); } - _elementManager = ElementManager( - contextId: _contextId, + _setupObserver(); + + element_registry.defineBuiltInElements(); + + document = Document( + EventTargetContext(_contextId, documentNativePtrMap[_contextId]!), viewport: viewport, - showPerformanceOverlayOverride: showPerformanceOverlay, controller: rootController, gestureListener: gestureListener, widgetDelegate: widgetDelegate, ); + _setEventTarget(DOCUMENT_ID, document); + + window = Window( + EventTargetContext(_contextId, windowNativePtrMap[_contextId]!), + document); + _setEventTarget(WINDOW_ID, window); + + // Listeners need to be registered to window in order to dispatch events on demand. + if (gestureListener != null) { + if (gestureListener!.onTouchStart != null) { + window.addEvent(EVENT_TOUCH_START); + } + + if (gestureListener!.onTouchMove != null) { + window.addEvent(EVENT_TOUCH_MOVE); + } + + if (gestureListener!.onTouchEnd != null) { + window.addEvent(EVENT_TOUCH_END); + } + } if (kProfileMode) { PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); } } - // the manager which controller all renderObjects of Kraken - late ElementManager _elementManager; - ElementManager get elementManager => _elementManager; - - // index value which identify javascript runtime context. + // Index value which identify javascript runtime context. late int _contextId; int get contextId => _contextId; - // should render performanceOverlay layer into the screen for performance profile. - bool? showPerformanceOverlay; - - // print debug message when rendering. + // Enable print debug message when rendering. bool enableDebug; - // Kraken have already disposed + // Kraken have already disposed. bool _disposed = false; bool get disposed => _disposed; late RenderViewportBox viewport; + late Document document; + late Window window; + + void shiftFocus(EventTarget target) { + // TODO: get focus for other element which need to get focus. + InputElement? inputElement = InputElement.focusInputElement; + if (inputElement != null && inputElement != target) { + inputElement.blur(); + } + + target.focus(); + } void evaluateJavaScripts(String code, [String source = 'vm://']) { assert(!_disposed, 'Kraken have already disposed'); evaluateScripts(_contextId, code, source); } - // attach kraken's renderObject to an renderObject. - void attachView(RenderObject parent, [RenderObject? previousSibling]) { - _elementManager.attach(parent, previousSibling, showPerformanceOverlay: showPerformanceOverlay ?? false); + void _setupObserver() { + if (ElementsBinding.instance != null) { + ElementsBinding.instance!.addObserver(this); + } else if (WidgetsBinding.instance != null) { + WidgetsBinding.instance!.addObserver(this); + } } - Window? get window => getEventTargetById(WINDOW_ID) as Window?; + void _teardownObserver() { + if (ElementsBinding.instance != null) { + ElementsBinding.instance!.removeObserver(this); + } else if (WidgetsBinding.instance != null) { + WidgetsBinding.instance!.removeObserver(this); + } + } - Document? get document => getEventTargetById(DOCUMENT_ID) as Document?; + // Attach kraken's renderObject to an renderObject. + void attachTo(RenderObject parent, [RenderObject? previousSibling]) { + if (parent is ContainerRenderObjectMixin) { + parent.insert(document.renderer!, after: previousSibling); + } else if (parent is RenderObjectWithChildMixin) { + parent.child = document.renderer; + } + } - // dispose controller and recycle all resources. + // Dispose controller and recycle all resources. void dispose() { - // break circle reference - (_elementManager.getRootRenderBox() as RenderObjectWithControllerMixin).controller = null; + // FIXME: for break circle reference + viewport.controller = null; - detachView(); + debugDOMTreeChanged = null; - // should clear previous page cached ui commands - clearUICommand(_contextId); + _teardownObserver(); - disposeContext(_contextId); + // Should clear previous page cached ui commands + clearUICommand(_contextId); - // DisposeEventTarget command will created when js context disposed, should flush them all. - flushUICommand(); + disposePage(_contextId); - _elementManager.dispose(); + _clearTargets(); + document.dispose(); + window.dispose(); _disposed = true; } + Map _eventTargets = {}; + + T? getEventTargetById(int targetId) { + return _getEventTargetById(targetId); + } + + int? getTargetIdByEventTarget(EventTarget eventTarget) { + if (_eventTargets.containsValue(eventTarget)) { + for (var entry in _eventTargets.entries) { + if (entry.value == eventTarget) { + return entry.key; + } + } + } + return null; + } + + T? _getEventTargetById(int targetId) { + EventTarget? target = _eventTargets[targetId]; + if (target is T) + return target as T; + else + return null; + } + + bool _existsTarget(int id) { + return _eventTargets.containsKey(id); + } + + void _removeTarget(int targetId) { + if (_eventTargets.containsKey(targetId)) { + _eventTargets.remove(targetId); + } + } + + void _setEventTarget(int targetId, EventTarget target) { + _eventTargets[targetId] = target; + } + + void _clearTargets() { + // Set current eventTargets to a new object, clean old targets by gc. + _eventTargets = {}; + } + // export Uint8List bytes from rendered result. - Future toImage(double devicePixelRatio, [int eventTargetId = HTML_ID]) { + Future toImage(double devicePixelRatio, [int? eventTargetId]) { assert(!_disposed, 'Kraken have already disposed'); Completer completer = Completer(); try { - if (!_elementManager.existsTarget(eventTargetId)) { + if (eventTargetId != null && !_existsTarget(eventTargetId)) { String msg = 'toImage: unknown node id: $eventTargetId'; completer.completeError(Exception(msg)); return completer.future; } - - var node = _elementManager.getEventTargetByTargetId(eventTargetId); + var node = eventTargetId == null + ? document.documentElement + : _getEventTargetById(eventTargetId); if (node is Element) { if (!node.isRendererAttached) { String msg = 'toImage: the element is not attached to document tree.'; @@ -223,7 +320,8 @@ class KrakenViewController { node.toBlob(devicePixelRatio: devicePixelRatio).then((Uint8List bytes) { completer.complete(bytes); }).catchError((e, stack) { - String msg = 'toBlob: failed to export image data from element id: $eventTargetId. error: $e}.\n$stack'; + String msg = + 'toBlob: failed to export image data from element id: $eventTargetId. error: $e}.\n$stack'; completer.completeError(Exception(msg)); }); } else { @@ -236,141 +334,316 @@ class KrakenViewController { return completer.future; } - Element createElement(int id, Pointer nativePtr, String tagName) { + void createElement( + int targetId, Pointer nativePtr, String tagName) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_ELEMENT_START, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_ELEMENT_START, uniqueId: targetId); } - Element result = _elementManager.createElement(id, nativePtr, tagName.toUpperCase(), null, null); + assert(!_existsTarget(targetId), + 'ERROR: Can not create element with same id "$targetId"'); + Element element = document.createElement( + tagName.toUpperCase(), EventTargetContext(_contextId, nativePtr)); + _setEventTarget(targetId, element); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_ELEMENT_END, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_ELEMENT_END, uniqueId: targetId); } - return result; } - void createTextNode(int id, Pointer nativePtr, String data) { + void createTextNode( + int targetId, Pointer nativePtr, String data) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_TEXT_NODE_START, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_TEXT_NODE_START, uniqueId: targetId); } - _elementManager.createTextNode(id, nativePtr, data); + TextNode textNode = document.createTextNode( + data, EventTargetContext(_contextId, nativePtr)); + _setEventTarget(targetId, textNode); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_TEXT_NODE_END, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_TEXT_NODE_END, uniqueId: targetId); } } - void createComment(int id, Pointer nativePtr) { + void createComment(int targetId, Pointer nativePtr) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_COMMENT_START, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_COMMENT_START, uniqueId: targetId); } - _elementManager.createComment(id, nativePtr); + Comment comment = + document.createComment(EventTargetContext(_contextId, nativePtr)); + _setEventTarget(targetId, comment); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_COMMENT_END, uniqueId: id); + PerformanceTiming.instance() + .mark(PERF_CREATE_COMMENT_END, uniqueId: targetId); } } - void addEvent(int targetId, String eventType) { + void createDocumentFragment( + int targetId, Pointer nativePtr) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ADD_EVENT_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_CREATE_DOCUMENT_FRAGMENT_START, uniqueId: targetId); } - _elementManager.addEvent(targetId, eventType); + DocumentFragment fragment = document + .createDocumentFragment(EventTargetContext(_contextId, nativePtr)); + _setEventTarget(targetId, fragment); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ADD_EVENT_END, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_CREATE_DOCUMENT_FRAGMENT_END, uniqueId: targetId); } } - void removeEvent(int targetId, String eventType) { + void addEvent(int targetId, String eventType) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_REMOVE_EVENT_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_ADD_EVENT_START, uniqueId: targetId); + } + if (!_existsTarget(targetId)) return; + EventTarget target = _getEventTargetById(targetId)!; + + if (target is Element) { + target.addEvent(eventType); + } else if (target is Window) { + target.addEvent(eventType); + } else if (target is Document) { + target.addEvent(eventType); } - _elementManager.removeEvent(targetId, eventType); + if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_REMOVE_EVENT_END, uniqueId: targetId); + PerformanceTiming.instance().mark(PERF_ADD_EVENT_END, uniqueId: targetId); } } - void insertAdjacentNode(int targetId, String position, int childId) { + void removeEvent(int targetId, String eventType) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_INSERT_ADJACENT_NODE_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_REMOVE_EVENT_START, uniqueId: targetId); } - _elementManager.insertAdjacentNode(targetId, position, childId); + assert(_existsTarget(targetId), 'targetId: $targetId event: $eventType'); + + Element target = _getEventTargetById(targetId)!; + + target.removeEvent(eventType); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_INSERT_ADJACENT_NODE_END, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_REMOVE_EVENT_END, uniqueId: targetId); + } + } + + void cloneNode(int originalId, int newId) { + EventTarget originalTarget = _getEventTargetById(originalId)!; + EventTarget newTarget = _getEventTargetById(newId)!; + + // Current only element clone will process in dart. + if (originalTarget is Element) { + Element newElement = newTarget as Element; + // Copy inline style. + originalTarget.inlineStyle.forEach((key, value) { + newElement.setInlineStyle(key, value); + }); + // Copy element attributes. + originalTarget.properties.forEach((key, value) { + newElement.setProperty(key, value); + }); } } void removeNode(int targetId) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_REMOVE_NODE_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_REMOVE_NODE_START, uniqueId: targetId); } - _elementManager.removeNode(targetId); + + assert(_existsTarget(targetId), 'targetId: $targetId'); + + Node target = _getEventTargetById(targetId)!; + target.parentNode?.removeChild(target); + + _debugDOMTreeChanged(); + if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_REMOVE_NODE_END, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_REMOVE_NODE_END, uniqueId: targetId); } } - void cloneNode(int oldId, int newId) { - _elementManager.cloneNode(oldId, newId); - } - - void setInlineStyle(int targetId, String key, String value) { + /// + ///

+ /// + /// foo + /// + ///

+ /// + void insertAdjacentNode(int targetId, String position, int newTargetId) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_STYLE_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_INSERT_ADJACENT_NODE_START, uniqueId: targetId); } - _elementManager.setInlineStyle(targetId, key, value); - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_STYLE_END, uniqueId: targetId); + + assert(_existsTarget(targetId), + 'targetId: $targetId position: $position newTargetId: $newTargetId'); + assert(_existsTarget(newTargetId), + 'newTargetId: $newTargetId position: $position'); + + Node target = _getEventTargetById(targetId)!; + Node newNode = _getEventTargetById(newTargetId)!; + Node? targetParentNode = target.parentNode; + + switch (position) { + case 'beforebegin': + targetParentNode!.insertBefore(newNode, target); + break; + case 'afterbegin': + target.insertBefore(newNode, target.firstChild); + break; + case 'beforeend': + target.appendChild(newNode); + break; + case 'afterend': + if (targetParentNode!.lastChild == target) { + targetParentNode.appendChild(newNode); + } else { + targetParentNode.insertBefore( + newNode, + targetParentNode + .childNodes[targetParentNode.childNodes.indexOf(target) + 1], + ); + } + break; } - } - void flushPendingStyleProperties(int targetId) { - _elementManager.flushPendingStyleProperties(targetId); + _debugDOMTreeChanged(); + + if (kProfileMode) { + PerformanceTiming.instance() + .mark(PERF_INSERT_ADJACENT_NODE_END, uniqueId: targetId); + } } - void setProperty(int targetId, String key, String value) { + void setProperty(int targetId, String key, dynamic value) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_PROPERTIES_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_SET_PROPERTIES_START, uniqueId: targetId); + } + + assert( + _existsTarget(targetId), 'targetId: $targetId key: $key value: $value'); + Node target = _getEventTargetById(targetId)!; + + if (target is Element) { + // Only Element has properties. + target.setProperty(key, value); + } else if (target is TextNode && key == 'data' || key == 'nodeValue') { + (target as TextNode).data = value; + } else { + debugPrint( + 'Only element has properties, try setting $key to Node(#$targetId).'); } - _elementManager.setProperty(targetId, key, value); + if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_PROPERTIES_END, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_SET_PROPERTIES_END, uniqueId: targetId); + } + } + + dynamic getProperty(int targetId, String key) { + assert(_existsTarget(targetId), 'targetId: $targetId key: $key'); + Node target = _getEventTargetById(targetId)!; + + if (target is Element) { + // Only Element has properties + return target.getProperty(key); + } else if (target is TextNode && key == 'data' || key == 'nodeValue') { + return (target as TextNode).data; + } else { + return null; } } void removeProperty(int targetId, String key) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_PROPERTIES_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_SET_PROPERTIES_START, uniqueId: targetId); + } + assert(_existsTarget(targetId), 'targetId: $targetId key: $key'); + Node target = _getEventTargetById(targetId)!; + + if (target is Element) { + target.removeProperty(key); + } else if (target is TextNode && key == 'data' || key == 'nodeValue') { + (target as TextNode).data = ''; + } else { + debugPrint( + 'Only element has properties, try removing $key from Node(#$targetId).'); } - _elementManager.removeProperty(targetId, key); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_SET_PROPERTIES_END, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_SET_PROPERTIES_END, uniqueId: targetId); } } - void createDocumentFragment(int targetId, Pointer nativePtr) { + void setInlineStyle(int targetId, String key, String value) { if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_DOCUMENT_FRAGMENT_START, uniqueId: targetId); + PerformanceTiming.instance() + .mark(PERF_SET_STYLE_START, uniqueId: targetId); + } + assert(_existsTarget(targetId), 'id: $targetId key: $key value: $value'); + Node? target = _getEventTargetById(targetId); + if (target == null) return; + + if (target is Element) { + target.setInlineStyle(key, value); + } else { + debugPrint( + 'Only element has style, try setting style.$key from Node(#$targetId).'); } - _elementManager.createDocumentFragment(targetId, nativePtr); if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CREATE_DOCUMENT_FRAGMENT_END, uniqueId: targetId); + PerformanceTiming.instance().mark(PERF_SET_STYLE_END, uniqueId: targetId); + } + } + + void flushPendingStyleProperties(int targetId) { + if (!_existsTarget(targetId)) return; + Node? target = _getEventTargetById(targetId); + if (target == null) return; + + if (target is Element) { + target.style.flushPendingProperties(); + } else { + debugPrint( + 'Only element has style, try flushPendingStyleProperties from Node(#$targetId).'); } } - EventTarget? getEventTargetById(int id) { - return _elementManager.getEventTargetByTargetId(id); + // Hooks for DevTools. + VoidCallback? debugDOMTreeChanged; + void _debugDOMTreeChanged() { + VoidCallback? f = debugDOMTreeChanged; + if (f != null) { + f(); + } } - Future handleNavigationAction(String? sourceUrl, String targetUrl, KrakenNavigationType navigationType) async { - KrakenNavigationAction action = KrakenNavigationAction(sourceUrl, targetUrl, navigationType); + Future handleNavigationAction(String? sourceUrl, String targetUrl, + KrakenNavigationType navigationType) async { + KrakenNavigationAction action = + KrakenNavigationAction(sourceUrl, targetUrl, navigationType); KrakenNavigationDelegate _delegate = navigationDelegate!; try { - KrakenNavigationActionPolicy policy = await _delegate.dispatchDecisionHandler(action); + KrakenNavigationActionPolicy policy = + await _delegate.dispatchDecisionHandler(action); if (policy == KrakenNavigationActionPolicy.cancel) return; switch (action.navigationType) { + case KrakenNavigationType.navigate: + await rootController.reload(url: action.target); + break; case KrakenNavigationType.reload: - await rootController.reloadUrl(action.target); + await rootController.reload(url: action.source!); break; default: // Navigate and other type, do nothing. @@ -384,13 +657,101 @@ class KrakenViewController { } } - // detach renderObject from parent but keep everything in active. - void detachView() { - _elementManager.detach(); + // Call from JS Bridge before JS side eventTarget object been Garbage collected. + void disposeEventTarget(int targetId) { + Node? target = _getEventTargetById(targetId); + if (target == null) return; + + _removeTarget(targetId); + target.dispose(); } RenderObject getRootRenderObject() { - return _elementManager.getRootRenderBox(); + return viewport; + } + + @override + void didChangeAccessibilityFeatures() { + // TODO: implement didChangeAccessibilityFeatures + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // TODO: implement didChangeAppLifecycleState + } + + @override + void didChangeLocales(List? locales) { + // TODO: implement didChangeLocales + } + + ui.WindowPadding _prevViewInsets = ui.window.viewInsets; + static double FOCUS_VIEWINSET_BOTTOM_OVERALL = 32; + + @override + void didChangeMetrics() { + double bottomInset = + ui.window.viewInsets.bottom / ui.window.devicePixelRatio; + if (_prevViewInsets.bottom > ui.window.viewInsets.bottom) { + // Hide keyboard + viewport.bottomInset = bottomInset; + } else { + bool shouldScrollByToCenter = false; + InputElement? focusInputElement = InputElement.focusInputElement; + if (focusInputElement != null) { + RenderBox? renderer = focusInputElement.renderer; + if (renderer != null && renderer.hasSize) { + Offset focusOffset = renderer.localToGlobal(Offset.zero); + // FOCUS_VIEWINSET_BOTTOM_OVERALL to meet border case. + if (focusOffset.dy > + viewportHeight - bottomInset - FOCUS_VIEWINSET_BOTTOM_OVERALL) { + shouldScrollByToCenter = true; + } + } + } + // Show keyboard + viewport.bottomInset = bottomInset; + if (shouldScrollByToCenter) { + SchedulerBinding.instance!.addPostFrameCallback((_) { + window.scrollBy(0, bottomInset); + }); + } + } + _prevViewInsets = ui.window.viewInsets; + } + + @override + void didChangePlatformBrightness() { + // TODO: implement didChangePlatformBrightness + } + + @override + void didChangeTextScaleFactor() { + // TODO: implement didChangeTextScaleFactor + } + + @override + void didHaveMemoryPressure() { + // TODO: implement didHaveMemoryPressure + } + + @override + Future didPopRoute() async { + // TODO: implement didPopRoute + return false; + } + + @override + Future didPushRoute(String route) async { + // TODO: implement didPushRoute + return false; + } + + @override + Future didPushRouteInformation( + RouteInformation routeInformation) async { + // TODO: implement didPushRouteInformation + return false; } } @@ -404,18 +765,21 @@ class KrakenModuleController with TimerMixin, ScheduleFrameMixin { } void dispose() { - clearTimer(); - clearAnimationFrame(); + disposeTimer(); + disposeScheduleFrame(); _moduleManager.dispose(); } } class KrakenController { - static final SplayTreeMap _controllerMap = SplayTreeMap(); + static final SplayTreeMap _controllerMap = + SplayTreeMap(); static final Map _nameIdMap = {}; UriParser? uriParser; + late RenderObjectElement rootFlutterElement; + static KrakenController? getControllerOfJSContextId(int? contextId) { if (!_controllerMap.containsKey(contextId)) { return null; @@ -436,6 +800,8 @@ class KrakenController { WidgetDelegate? widgetDelegate; + KrakenBundle? bundle; + LoadHandler? onLoad; // Error handler when load bundle failed. @@ -471,24 +837,19 @@ class KrakenController { double viewportHeight, { bool showPerformanceOverlay = false, enableDebug = false, - String? bundleURL, - String? bundlePath, - String? bundleContent, Color? background, GestureListener? gestureListener, KrakenNavigationDelegate? navigationDelegate, KrakenMethodChannel? methodChannel, this.widgetDelegate, + this.bundle, this.onLoad, this.onLoadError, this.onJSError, this.httpClientInterceptor, this.devToolsService, - this.uriParser + this.uriParser, }) : _name = name, - _bundleURL = bundleURL, - _bundlePath = bundlePath, - _bundleContent = bundleContent, _gestureListener = gestureListener { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_CONTROLLER_PROPERTY_INIT); @@ -498,14 +859,15 @@ class KrakenController { _methodChannel = methodChannel; KrakenMethodChannel.setJSMethodCallCallback(this); - _view = KrakenViewController(viewportWidth, viewportHeight, - background: background, - showPerformanceOverlay: showPerformanceOverlay, - enableDebug: enableDebug, - rootController: this, - navigationDelegate: navigationDelegate ?? KrakenNavigationDelegate(), - gestureListener: _gestureListener, - widgetDelegate: widgetDelegate, + _view = KrakenViewController( + viewportWidth, + viewportHeight, + background: background, + enableDebug: enableDebug, + rootController: this, + navigationDelegate: navigationDelegate ?? KrakenNavigationDelegate(), + gestureListener: _gestureListener, + widgetDelegate: widgetDelegate, ); if (kProfileMode) { @@ -516,10 +878,17 @@ class KrakenController { _module = KrakenModuleController(this, contextId); + if (bundle != null) { + HistoryModule historyModule = + module.moduleManager.getModule('History')!; + historyModule.bundle = bundle!; + } + assert(!_controllerMap.containsKey(contextId), 'found exist contextId of KrakenController, contextId: $contextId'); _controllerMap[contextId] = this; - assert(!_nameIdMap.containsKey(name), 'found exist name of KrakenController, name: $name'); + assert(!_nameIdMap.containsKey(name), + 'found exist name of KrakenController, name: $name'); if (name != null) { _nameIdMap[name] = contextId; } @@ -533,6 +902,13 @@ class KrakenController { } } + void dispatchEvent(Event event) { + EventTarget? target = event.target; + if (event.type == EVENT_CLICK && target != null) { + _view.shiftFocus(target); + } + } + late KrakenViewController _view; KrakenViewController get view { @@ -545,18 +921,14 @@ class KrakenController { return _module; } - // the bundle manager which used to download javascript source and run. - KrakenBundle? _bundle; - KrakenBundle? get bundle => _bundle; - final Queue previousHistoryStack = Queue(); final Queue nextHistoryStack = Queue(); Uri get referrer { - if (bundleURL != null) { - return Uri.parse(bundleURL!); - } else if (bundlePath != null) { - return Directory(bundlePath!).uri; + if (bundle is NetworkBundle) { + return Uri.parse(href); + } else if (bundle is AssetsBundle) { + return Directory(href).uri; } else { return fallbackBundleUri(_view.contextId); } @@ -573,36 +945,25 @@ class KrakenController { Future unload() async { assert(!_view._disposed, 'Kraken have already disposed'); - RenderObject root = _view.getRootRenderObject(); - RenderObject? parent = root.parent as RenderObject?; - RenderObject? previousSibling; - if (parent is ContainerRenderObjectMixin) { - previousSibling = (root.parentData as ContainerParentDataMixin).previousSibling; - } _module.dispose(); - _view.detachView(); + _view.dispose(); // Should clear previous page cached ui commands clearUICommand(_view.contextId); - // Wait for next microtask to make sure C++ native Elements are GC collected and generate disposeEventTarget command in the command queue. + // Wait for next microtask to make sure C++ native Elements are GC collected. Completer completer = Completer(); Future.microtask(() { - disposeContext(_view.contextId); - - // DisposeEventTarget command will created when js context disposed, should flush them before creating new view. - flushUICommand(); + disposePage(_view.contextId); - allocateNewContext(_view.contextId); + allocateNewPage(_view.contextId); - _view = KrakenViewController(view._elementManager.viewportWidth, view._elementManager.viewportHeight, + _view = KrakenViewController(view.viewportWidth, view.viewportHeight, background: _view.background, - showPerformanceOverlay: _view.showPerformanceOverlay, enableDebug: _view.enableDebug, contextId: _view.contextId, rootController: this, navigationDelegate: _view.navigationDelegate); - _view.attachView(parent!, previousSibling); _module = KrakenModuleController(this, _view.contextId); @@ -613,23 +974,31 @@ class KrakenController { } String get href { - HistoryModule historyModule = module.moduleManager.getModule('History')!; + HistoryModule historyModule = + module.moduleManager.getModule('History')!; return historyModule.href; } set href(String value) { - HistoryModule historyModule = module.moduleManager.getModule('History')!; - historyModule.href = value; + _addHistory(KrakenBundle.fromUrl(value)); + } + + _addHistory(KrakenBundle bundle) { + HistoryModule historyModule = + module.moduleManager.getModule('History')!; + historyModule.bundle = bundle; } // reload current kraken view. - Future reload() async { + Future reload({String? url}) async { + assert(!_view._disposed, 'Kraken have already disposed'); + if (devToolsService != null) { devToolsService!.willReload(); } await unload(); - await loadBundle(bundleURL: href); + await loadBundle(bundle: KrakenBundle.fromUrl(url ?? href)); await evalBundle(); if (devToolsService != null) { @@ -637,10 +1006,33 @@ class KrakenController { } } - Future reloadUrl(String url) async { - assert(!_view._disposed, 'Kraken have already disposed'); - href = url; - await reload(); + bool _paused = false; + bool get paused => _paused; + + final List _pendingCallbacks = []; + + void pushPendingCallbacks(PendingCallback callback) { + _pendingCallbacks.add(callback); + } + + void flushPendingCallbacks() { + for (int i = 0; i < _pendingCallbacks.length; i++) { + _pendingCallbacks[i](); + } + _pendingCallbacks.clear(); + } + + // Pause all timers and callbacks if kraken page are invisible. + void pause() { + _paused = true; + module.pauseInterval(); + } + + // Resume all timers and callbacks if kraken page now visible. + void resume() { + _paused = false; + flushPendingCallbacks(); + module.resumeInterval(); } void dispose() { @@ -655,75 +1047,57 @@ class KrakenController { } } - String? _bundleContent; - - String? get bundleContent => _bundleContent; - set bundleContent(String? value) { - if (value == null) return; - _bundleContent = value; - } - - Uint8List? _bundleByteCode; - Uint8List? get bundleByteCode => _bundleByteCode; - set bundleByteCode(Uint8List? value) { - if (value == null) return; - _bundleByteCode = value; - } - - String? _bundlePath; + @deprecated + String? get bundlePath => href; - String? get bundlePath => _bundlePath; + @deprecated set bundlePath(String? value) { - _bundlePath = value; + if (value == null) return; + // Set bundlePath should set the path to history module. + href = value; } - String? _bundleURL; - - String? get bundleURL => _bundleURL; + @deprecated + String? get bundleURL => href; + @deprecated set bundleURL(String? value) { if (value == null) return; - _bundleURL = value; + // Set bundleURL should set the url to history module. + href = value; } - String get origin => _bundleURL ?? _bundlePath ?? 'vm://' + name!; + String get origin => Uri.parse(href).origin; // preload javascript source and cache it. - Future loadBundle({ - String? bundleContent, - String? bundlePath, - String? bundleURL, - Uint8List? bundleByteCode - }) async { + Future loadBundle({KrakenBundle? bundle}) async { assert(!_view._disposed, 'Kraken have already disposed'); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_JS_BUNDLE_LOAD_START); } - _bundleContent = bundleContent ?? _bundleContent; - _bundlePath = bundlePath ?? _bundlePath; - _bundleURL = bundleURL ?? _bundleURL; - _bundleByteCode = bundleByteCode ?? _bundleByteCode; - - String? url = _bundleURL ?? _bundlePath ?? getBundleURLFromEnv() ?? getBundlePathFromEnv(); - - if (url == null && methodChannel is KrakenNativeChannel) { - url = await (methodChannel as KrakenNativeChannel).getUrl(); + // Load bundle need push curret href to history. + if (bundle != null) { + String? url = bundle.uri.toString().isEmpty + ? (getBundleURLFromEnv() ?? getBundlePathFromEnv()) + : href; + if (url == null && methodChannel is KrakenNativeChannel) { + url = await (methodChannel as KrakenNativeChannel).getUrl(); + } + _addHistory(bundle); + this.bundle = bundle; } - url = url ?? ''; if (onLoadError != null) { try { - _bundle = await KrakenBundle.getBundle(url, contentOverride: _bundleContent, contextId: view.contextId); + await bundle?.resolve(view.contextId); } catch (e, stack) { onLoadError!(FlutterError(e.toString()), stack); } } else { - _bundle = await KrakenBundle.getBundle(url, contentOverride: _bundleContent, contextId: view.contextId); + await bundle?.resolve(view.contextId); } - KrakenController controller = KrakenController.getControllerOfJSContextId(view.contextId)!; - controller.href = url; if (kProfileMode) { PerformanceTiming.instance().mark(PERF_JS_BUNDLE_LOAD_END); @@ -733,21 +1107,19 @@ class KrakenController { // execute preloaded javascript source Future evalBundle() async { assert(!_view._disposed, 'Kraken have already disposed'); - if (_bundle != null) { - await _bundle!.eval(_view.contextId); + if (bundle != null) { + await bundle!.eval(_view.contextId); // trigger DOMContentLoaded event module.requestAnimationFrame((_) { Event event = Event(EVENT_DOM_CONTENT_LOADED); - EventTarget? window = view.getEventTargetById(WINDOW_ID); - if (window != null) { + EventTarget window = view.window; + window.dispatchEvent(event); + // @HACK: window.load should trigger after all image had loaded. + // Someone needs to fix this in the future. + module.requestAnimationFrame((_) { + Event event = Event(EVENT_LOAD); window.dispatchEvent(event); - // @HACK: window.load should trigger after all image had loaded. - // Someone needs to fix this in the future. - module.requestAnimationFrame((_) { - Event event = Event(EVENT_LOAD); - window.dispatchEvent(event); - }); - } + }); }); if (onLoad != null) { @@ -756,7 +1128,6 @@ class KrakenController { onLoad!(this); }); } - } } } diff --git a/kraken/lib/src/launcher/launcher.dart b/kraken/lib/src/launcher/launcher.dart index 425f654644..ec278d21a4 100644 --- a/kraken/lib/src/launcher/launcher.dart +++ b/kraken/lib/src/launcher/launcher.dart @@ -5,7 +5,7 @@ import 'dart:io'; import 'dart:ui'; - +import 'dart:async'; import 'package:flutter/rendering.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/kraken.dart'; @@ -19,9 +19,7 @@ typedef ConnectedCallback = void Function(); const _white = Color(0xFFFFFFFF); void launch({ - String? bundleURL, - String? bundlePath, - String? bundleContent, + KrakenBundle? bundle, bool? debugEnableInspector, Color background = _white, DevToolsService? devToolsService, @@ -42,12 +40,9 @@ void launch({ httpClientInterceptor: httpClientInterceptor ); - controller.view.attachView(RendererBinding.instance!.renderView); + controller.view.attachTo(RendererBinding.instance!.renderView); - await controller.loadBundle( - bundleURL: bundleURL, - bundlePath: bundlePath, - bundleContent: bundleContent); + await controller.loadBundle(bundle: bundle); await controller.evalBundle(); } diff --git a/kraken/lib/src/module/history.dart b/kraken/lib/src/module/history.dart index 9c94b3e34c..70f495de12 100644 --- a/kraken/lib/src/module/history.dart +++ b/kraken/lib/src/module/history.dart @@ -2,18 +2,17 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ - - import 'dart:collection'; import 'dart:convert'; +import 'dart:async'; import 'package:kraken/dom.dart'; import 'package:kraken/kraken.dart'; import 'package:kraken/module.dart'; class HistoryItem { - HistoryItem(this.href, this.state, this.needJump); - final String href; + HistoryItem(this.bundle, this.state, this.needJump); + final KrakenBundle bundle; final dynamic state; final bool needJump; } @@ -29,15 +28,16 @@ class HistoryModule extends BaseModule { String get href { if (_previousStack.isEmpty) return ''; - return _previousStack.first.href; + return _previousStack.first.bundle.src; } - set href(String value) { - HistoryItem history = HistoryItem(value, null, true); + + set bundle(KrakenBundle bundle) { + HistoryItem history = HistoryItem(bundle, null, true); _addItem(history); } void _addItem(HistoryItem historyItem) { - if (_previousStack.isNotEmpty && historyItem.href == _previousStack.first.href) return; + if (_previousStack.isNotEmpty && historyItem.bundle.src == _previousStack.first.bundle.src) return; _previousStack.addFirst(historyItem); @@ -54,7 +54,7 @@ class HistoryModule extends BaseModule { _previousStack.removeFirst(); _nextStack.addFirst(currentItem); - await _goTo(_previousStack.first.href); + await _goTo(_previousStack.first.bundle.src); dynamic state = _previousStack.first.state; _dispatchPopStateEvent(state); @@ -67,7 +67,7 @@ class HistoryModule extends BaseModule { _nextStack.removeFirst(); _previousStack.addFirst(currentItem); - _goTo(currentItem.href); + _goTo(currentItem.bundle.src); _dispatchPopStateEvent(currentItem.state); } } @@ -96,7 +96,7 @@ class HistoryModule extends BaseModule { } } - _goTo(_previousStack.first.href); + _goTo(_previousStack.first.bundle.src); _dispatchPopStateEvent(_previousStack.first.state); } @@ -108,7 +108,7 @@ class HistoryModule extends BaseModule { void _dispatchPopStateEvent(dynamic state) { PopStateEventInit init = PopStateEventInit(state); PopStateEvent popStateEvent = PopStateEvent(init); - moduleManager!.controller.view.window!.dispatchEvent(popStateEvent); + moduleManager!.controller.view.window.dispatchEvent(popStateEvent); } void _pushState(List params) { @@ -119,7 +119,7 @@ class HistoryModule extends BaseModule { if (params[2] != null) { url = params[2]; - String currentUrl = _previousStack.first.href; + String currentUrl = _previousStack.first.bundle.src; Uri currentUri = Uri.parse(currentUrl); Uri uri = Uri.parse(url!); @@ -131,7 +131,8 @@ class HistoryModule extends BaseModule { return; } - HistoryItem history = HistoryItem(uri.toString(), state, false); + KrakenBundle bundle = KrakenBundle.fromUrl(uri.toString()); + HistoryItem history = HistoryItem(bundle, state, false); _addItem(history); } } @@ -144,7 +145,7 @@ class HistoryModule extends BaseModule { if (params[2] != null) { url = params[2]; - String currentUrl = _previousStack.first.href; + String currentUrl = _previousStack.first.bundle.src; Uri currentUri = Uri.parse(currentUrl); Uri uri = Uri.parse(url!); @@ -156,7 +157,8 @@ class HistoryModule extends BaseModule { return; } - HistoryItem history = HistoryItem(uri.toString(), state, false); + KrakenBundle bundle = KrakenBundle.fromUrl(uri.toString()); + HistoryItem history = HistoryItem(bundle, state, false); _previousStack.removeFirst(); _previousStack.addFirst(history); diff --git a/kraken/lib/src/module/method_channel.dart b/kraken/lib/src/module/method_channel.dart index 52c2cd4a91..6d02281d4c 100644 --- a/kraken/lib/src/module/method_channel.dart +++ b/kraken/lib/src/module/method_channel.dart @@ -9,15 +9,7 @@ import 'module_manager.dart'; typedef MethodCallCallback = Future Function(String method, dynamic arguments); const String METHOD_CHANNEL_NOT_INITIALIZED = 'MethodChannel not initialized.'; const String CONTROLLER_NOT_INITIALIZED = 'Kraken controller not initialized.'; - -Future _invokeMethodFromJavaScript(KrakenController? controller, String method, List args) { - if (controller == null || controller.methodChannel == null) { - return Future.error(FlutterError(METHOD_CHANNEL_NOT_INITIALIZED)); - } - return controller.methodChannel!._invokeMethodFromJavaScript(method, args); -} - -const METHOD_CHANNEL_NAME = 'MethodChannel'; +const String METHOD_CHANNEL_NAME = 'MethodChannel'; class MethodChannelModule extends BaseModule { @override @@ -47,12 +39,12 @@ void setJSMethodCallCallback(KrakenController controller) { try { controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); } catch (e, stack) { - print('invoke module event: $e, $stack'); + print('Error invoke module event: $e, $stack'); } }; } -class KrakenMethodChannel { +abstract class KrakenMethodChannel { MethodCallCallback? _onJSMethodCallCallback; set _onJSMethodCall(MethodCallCallback? value) { @@ -60,12 +52,10 @@ class KrakenMethodChannel { _onJSMethodCallCallback = value; } - Future _invokeMethodFromJavaScript(String method, List arguments) async {} + Future invokeMethodFromJavaScript(String method, List arguments); static void setJSMethodCallCallback(KrakenController controller) { - if (controller.methodChannel == null) return; - - controller.methodChannel!._onJSMethodCall = (String method, dynamic arguments) async { + controller.methodChannel?._onJSMethodCall = (String method, dynamic arguments) async { controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); }; } @@ -73,10 +63,12 @@ class KrakenMethodChannel { class KrakenJavaScriptChannel extends KrakenMethodChannel { Future invokeMethod(String method, dynamic arguments) async { - if (_onJSMethodCallCallback == null) { + MethodCallCallback? jsMethodCallCallback = _onJSMethodCallCallback; + if (jsMethodCallCallback != null) { + return jsMethodCallCallback(method, arguments); + } else { return null; } - return _onJSMethodCallCallback!(method, arguments); } MethodCallCallback? _methodCallCallback; @@ -89,9 +81,13 @@ class KrakenJavaScriptChannel extends KrakenMethodChannel { } @override - Future _invokeMethodFromJavaScript(String method, List arguments) { - if (_methodCallCallback == null) return Future.value(null); - return _methodCallCallback!(method, arguments); + Future invokeMethodFromJavaScript(String method, List arguments) { + MethodCallCallback? methodCallCallback = _methodCallCallback; + if (methodCallCallback != null) { + return _methodCallCallback!(method, arguments); + } else { + return Future.value(null); + } } } @@ -116,7 +112,7 @@ class KrakenNativeChannel extends KrakenMethodChannel { }); @override - Future _invokeMethodFromJavaScript(String method, List arguments) async { + Future invokeMethodFromJavaScript(String method, List arguments) async { Map argsWrap = { 'method': method, 'args': arguments, @@ -134,4 +130,20 @@ class KrakenNativeChannel extends KrakenMethodChannel { if (url != null && url.isEmpty) url = null; return url; } + + static Future syncDynamicLibraryPath() async { + String? path = await _nativeChannel.invokeMethod('getDynamicLibraryPath'); + if (path != null) { + KrakenDynamicLibrary.dynamicLibraryPath = path; + } + } +} + +Future _invokeMethodFromJavaScript(KrakenController? controller, String method, List args) { + KrakenMethodChannel? krakenMethodChannel = controller?.methodChannel; + if (krakenMethodChannel != null) { + return krakenMethodChannel.invokeMethodFromJavaScript(method, args); + } else { + return Future.error(FlutterError(METHOD_CHANNEL_NOT_INITIALIZED)); + } } diff --git a/kraken/lib/src/module/navigation.dart b/kraken/lib/src/module/navigation.dart index efe00e4231..7d9f051a85 100644 --- a/kraken/lib/src/module/navigation.dart +++ b/kraken/lib/src/module/navigation.dart @@ -38,14 +38,12 @@ class NavigationModule extends BaseModule { // Navigate kraken page to target Url. Future goTo(String targetUrl) async { - String? sourceUrl = moduleManager!.controller.bundlePath ?? moduleManager!.controller.bundleURL; + String? sourceUrl = moduleManager!.controller.href; Uri targetUri = Uri.parse(targetUrl); - Uri? sourceUri = sourceUrl != null ? Uri.parse(sourceUrl) : null; + Uri sourceUri = Uri.parse(sourceUrl); - if (sourceUri == null || targetUri != sourceUri) { - await moduleManager!.controller.view.handleNavigationAction(sourceUrl, targetUrl, KrakenNavigationType.reload); - } + await moduleManager!.controller.view.handleNavigationAction(sourceUrl, targetUrl, targetUri == sourceUri ? KrakenNavigationType.reload : KrakenNavigationType.navigate); } @override diff --git a/kraken/lib/src/module/performance_timing.dart b/kraken/lib/src/module/performance_timing.dart index 72b294b1fc..5b067595a3 100644 --- a/kraken/lib/src/module/performance_timing.dart +++ b/kraken/lib/src/module/performance_timing.dart @@ -82,6 +82,7 @@ final int PERFORMANCE_NONE_UNIQUE_ID = -1024; class PerformanceTiming { static PerformanceTiming? _instance; static final bool _enabled = profileModeEnabled(); + static bool enabled() => _enabled; static PerformanceTiming instance() { _instance ??= PerformanceTiming(); diff --git a/kraken/lib/src/module/schedule_frame.dart b/kraken/lib/src/module/schedule_frame.dart index b0228fb158..e0ad5eb95d 100644 --- a/kraken/lib/src/module/schedule_frame.dart +++ b/kraken/lib/src/module/schedule_frame.dart @@ -37,7 +37,7 @@ mixin ScheduleFrameMixin { SchedulerBinding.instance!.scheduleFrame(); } - void clearAnimationFrame() { + void disposeScheduleFrame() { _animationFrameCallbackMap.clear(); } } diff --git a/kraken/lib/src/module/timer.dart b/kraken/lib/src/module/timer.dart index f89cf5e8b6..14cf75f395 100644 --- a/kraken/lib/src/module/timer.dart +++ b/kraken/lib/src/module/timer.dart @@ -5,11 +5,62 @@ import 'dart:async'; +/// A [Timer] that can be paused, resumed. +class PausablePeriodicTimer implements Timer { + Timer? _timer; + final void Function(Timer) _callback; + final Duration _duration; + int _tick = 0; + + void _startTimer() { + var boundCallback = _callback; + if (Zone.current != Zone.root) { + boundCallback = Zone.current.bindUnaryCallbackGuarded(_callback); + } + _timer = Zone.current.createPeriodicTimer(_duration, (Timer timer) { + _tick++; + boundCallback(timer); + }); + } + + /// Creates a new timer. + PausablePeriodicTimer(Duration duration, void Function(Timer) callback) + : assert(duration >= Duration.zero), + _duration = duration, + _callback = callback { + _startTimer(); + } + + @override + bool get isActive => _timer != null; + + @override + void cancel() { + _timer?.cancel(); + _timer = null; + } + + /// Resume the timer. + void resume() { + if (isActive) return; + _startTimer(); + } + + /// Pauses an active timer. + void pause() { + _timer?.cancel(); + _timer = null; + } + + @override + int get tick => _tick; +} + mixin TimerMixin { int _timerId = 1; final Map _timerMap = {}; - int setTimeout(int timeout, Function callback) { + int setTimeout(int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); int id = _timerId++; _timerMap[id] = Timer(timeoutDurationMS, () { @@ -27,19 +78,35 @@ mixin TimerMixin { } } - void clearTimer() { - _timerMap.forEach((key, timer) { - timer.cancel(); - }); - _timerMap.clear(); - } - - int setInterval(int timeout, Function callback) { + int setInterval(int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); int id = _timerId++; - _timerMap[id] = Timer.periodic(timeoutDurationMS, (Timer timer) { + _timerMap[id] = PausablePeriodicTimer(timeoutDurationMS, (_) { callback(); }); return id; } + + void pauseInterval() { + _timerMap.forEach((key, timer) { + if (timer is PausablePeriodicTimer) { + timer.pause(); + } + }); + } + + void resumeInterval() { + _timerMap.forEach((key, timer) { + if (timer is PausablePeriodicTimer) { + timer.resume(); + } + }); + } + + void disposeTimer() { + _timerMap.forEach((key, timer) { + timer.cancel(); + }); + _timerMap.clear(); + } } diff --git a/kraken/lib/src/painting/cached_network_image.dart b/kraken/lib/src/painting/cached_network_image.dart index fddaa8e39b..f401229c23 100644 --- a/kraken/lib/src/painting/cached_network_image.dart +++ b/kraken/lib/src/painting/cached_network_image.dart @@ -13,7 +13,30 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:kraken/foundation.dart'; -class CachedNetworkImage extends ImageProvider { +class CachedNetworkImageKey { + CachedNetworkImageKey({ + required this.url, + required this.scale + }); + + final String url; + + final double scale; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) + return false; + return other is CachedNetworkImageKey + && other.url == url + && other.scale == scale; + } + + @override + int get hashCode => hashValues(url, scale); +} + +class CachedNetworkImage extends ImageProvider { const CachedNetworkImage(this.url, {this.scale = 1.0, this.headers, this.contextId}); final String url; @@ -39,14 +62,14 @@ class CachedNetworkImage extends ImageProvider { return client; } - Future loadFile(CachedNetworkImage key, StreamController chunkEvents) async { + Future loadFile(CachedNetworkImageKey key, StreamController chunkEvents) async { HttpCacheController cacheController = HttpCacheController.instance( getOrigin(getReferrer(contextId))); Uri uri = Uri.parse(url); - HttpCacheObject? cacheObject = await cacheController.getCacheObject(uri); Uint8List? bytes; try { + HttpCacheObject? cacheObject = await cacheController.getCacheObject(uri); bytes = await cacheObject.toBinaryContent(); } catch (error, stackTrace) { print('Error while reading cache, $error\n$stackTrace'); @@ -59,7 +82,7 @@ class CachedNetworkImage extends ImageProvider { } Future _loadImage( - CachedNetworkImage key, DecoderCallback decode, StreamController chunkEvents) async { + CachedNetworkImageKey key, DecoderCallback decode, StreamController chunkEvents) async { Uint8List bytes = await loadFile(key, chunkEvents); if (bytes.isNotEmpty) { @@ -68,7 +91,7 @@ class CachedNetworkImage extends ImageProvider { return null; } - Future fetchFile(CachedNetworkImage key, + Future fetchFile(CachedNetworkImageKey key, StreamController chunkEvents, HttpCacheController cacheController) async { try { @@ -107,12 +130,15 @@ class CachedNetworkImage extends ImageProvider { } @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(CachedNetworkImageKey( + url: url, + scale: scale + )); } @override - ImageStreamCompleter load(CachedNetworkImage key, DecoderCallback decode) { + ImageStreamCompleter load(CachedNetworkImageKey key, DecoderCallback decode) { // Ownership of this controller is handed off to [_loadAsync]; it is that // method's responsibility to close the controller's stream when the image // has been loaded or an error is thrown. @@ -125,7 +151,7 @@ class CachedNetworkImage extends ImageProvider { informationCollector: () { return [ DiagnosticsProperty('Image provider', this), - DiagnosticsProperty('Image key', key), + DiagnosticsProperty('Image key', key), ]; }); } diff --git a/kraken/lib/src/painting/image_provider_factory.dart b/kraken/lib/src/painting/image_provider_factory.dart index 10e2c383ea..9755e5d8c6 100644 --- a/kraken/lib/src/painting/image_provider_factory.dart +++ b/kraken/lib/src/painting/image_provider_factory.dart @@ -3,8 +3,8 @@ * Author: Kraken Team. */ - import 'dart:io'; +import 'dart:ui'; import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; @@ -12,11 +12,42 @@ import 'package:flutter/painting.dart'; import 'package:kraken/bridge.dart'; import 'package:kraken/foundation.dart'; import 'package:kraken/painting.dart'; +import 'package:quiver/collection.dart'; /// This class allows user to customize Kraken's image loading. +class ImageProviderParams { + int? cachedWidth; + int? cachedHeight; + + ImageProviderParams({this.cachedWidth, this.cachedHeight}); +} + +class CachedNetworkImageProviderParams extends ImageProviderParams { + int? contextId; + + CachedNetworkImageProviderParams(this.contextId, + {int? cachedWidth, int? cachedHeight}) + : super(cachedWidth: cachedWidth, cachedHeight: cachedHeight); +} + +class FileImageProviderParams extends ImageProviderParams { + File file; + + FileImageProviderParams(this.file, {int? cachedWidth, int? cachedHeight}) + : super(cachedWidth: cachedWidth, cachedHeight: cachedHeight); +} + +class DataUrlImageProviderParams extends ImageProviderParams { + Uint8List bytes; + + DataUrlImageProviderParams(this.bytes, {int? cachedWidth, int? cachedHeight}) + : super(cachedWidth: cachedWidth, cachedHeight: cachedHeight); +} + /// A factory function allow user to build an customized ImageProvider class. -typedef ImageProviderFactory = ImageProvider? Function(Uri uri, [dynamic param]); +typedef ImageProviderFactory = ImageProvider? Function( + Uri uri, ImageProviderParams params); /// defines the types of supported image source. enum ImageType { @@ -75,7 +106,67 @@ ImageProviderFactory _dataUrlProviderFactory = defaultDataUrlProviderFactory; ImageProviderFactory _blobProviderFactory = defaultBlobProviderFactory; ImageProviderFactory _assetsProviderFactory = defaultAssetsProvider; -ImageProviderFactory getImageProviderFactory(ImageType imageType) { +ImageType parseImageUrl(Uri resolvedUri, {cache = 'auto'}) { + if (resolvedUri.isScheme('HTTP') || resolvedUri.isScheme('HTTPS')) { + return (cache == 'store' || cache == 'auto') + ? ImageType.cached + : ImageType.network; + } else if (resolvedUri.isScheme('FILE')) { + return ImageType.file; + } else if (resolvedUri.isScheme('DATA')) { + return ImageType.dataUrl; + } else if (resolvedUri.isScheme('BLOB')) { + return ImageType.blob; + } else { + return ImageType.assets; + } +} + +ImageProvider? getImageProvider(Uri resolvedUri, + {int? contextId, cache = 'auto', int? cachedWidth, int? cachedHeight}) { + ImageType imageType = parseImageUrl(resolvedUri, cache: cache); + ImageProviderFactory factory = _getImageProviderFactory(imageType); + + switch (imageType) { + case ImageType.cached: + return factory( + resolvedUri, + CachedNetworkImageProviderParams(contextId, + cachedWidth: cachedWidth, cachedHeight: cachedHeight)); + case ImageType.network: + return factory( + resolvedUri, + CachedNetworkImageProviderParams(contextId, + cachedWidth: cachedWidth, cachedHeight: cachedHeight)); + case ImageType.file: + File file = File.fromUri(resolvedUri); + return factory( + resolvedUri, + FileImageProviderParams(file, + cachedWidth: cachedWidth, cachedHeight: cachedHeight)); + case ImageType.dataUrl: + // Data URL: https://tools.ietf.org/html/rfc2397 + // dataurl := "data:" [ mediatype ] [ ";base64" ] "," data + UriData data = UriData.fromUri(resolvedUri); + if (data.isBase64) { + return factory( + resolvedUri, + DataUrlImageProviderParams(data.contentAsBytes(), + cachedWidth: cachedWidth, cachedHeight: cachedHeight)); + } + return null; + case ImageType.blob: + // TODO: support blob data type + return null; + case ImageType.assets: + return factory( + resolvedUri, + ImageProviderParams( + cachedWidth: cachedWidth, cachedHeight: cachedHeight)); + } +} + +ImageProviderFactory _getImageProviderFactory(ImageType imageType) { switch (imageType) { case ImageType.cached: return _cachedProviderFactory; @@ -93,7 +184,8 @@ ImageProviderFactory getImageProviderFactory(ImageType imageType) { } } -void setCustomImageProviderFactory(ImageType imageType, ImageProviderFactory customImageProviderFactory) { +void setCustomImageProviderFactory( + ImageType imageType, ImageProviderFactory customImageProviderFactory) { switch (imageType) { case ImageType.cached: _cachedProviderFactory = customImageProviderFactory; @@ -117,55 +209,138 @@ void setCustomImageProviderFactory(ImageType imageType, ImageProviderFactory cus } } -int? _getContextId(param) { - int? contextId; - if (param is List && param.isNotEmpty) { - contextId = param[0]; +class KrakenResizeImage extends ResizeImage { + KrakenResizeImage( + ImageProvider imageProvider, { + int? width, + int? height, + }) : super(imageProvider, width: width, height: height); + + static final LinkedLruHashMap _imageNaturalSize = LinkedLruHashMap(maximumSize: 100); + static Size? getImageNaturalSize(dynamic key) { + return _imageNaturalSize[key]; + } + + static ImageProvider resizeIfNeeded( + int? cacheWidth, int? cacheHeight, ImageProvider provider) { + if (cacheWidth != null || cacheHeight != null) { + return KrakenResizeImage(provider, + width: cacheWidth, height: cacheHeight); + } + return provider; + } + + @override + void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, key, ImageErrorListener handleError) { + // This is an unusual edge case where someone has told us that they found + // the image we want before getting to this method. We should avoid calling + // load again, but still update the image cache with LRU information. + if (stream.completer != null) { + final ImageStreamCompleter? completer = + PaintingBinding.instance!.imageCache!.putIfAbsent( + key, + () => stream.completer!, + onError: handleError, + ); + assert(identical(completer, stream.completer)); + return; + } + final ImageStreamCompleter? completer = + PaintingBinding.instance!.imageCache!.putIfAbsent( + key, + () => load(key, instantiateImageCodec), + onError: handleError, + ); + if (completer != null) { + stream.setCompleter(completer); + } + } + + Future instantiateImageCodec( + Uint8List bytes, { + int? cacheWidth, + int? cacheHeight, + bool allowUpscaling = false, + }) async { + assert(cacheWidth == null || cacheWidth > 0); + assert(cacheHeight == null || cacheHeight > 0); + + final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(bytes); + final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer); + if (!allowUpscaling) { + if (cacheWidth != null && cacheWidth > descriptor.width) { + cacheWidth = descriptor.width; + } + if (cacheHeight != null && cacheHeight > descriptor.height) { + cacheHeight = descriptor.height; + } + } + + // Cache the image's original size for element.naturalWidth and element.naturalHeight API. + dynamic key = await obtainKey(ImageConfiguration.empty); + _imageNaturalSize[key] = Size(descriptor.width.toDouble(), descriptor.height.toDouble()); + + return descriptor.instantiateCodec( + targetWidth: cacheWidth, + targetHeight: cacheHeight, + ); + } + + @override + Future evict({ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty}) async { + Future result = super.evict(cache: cache, configuration: configuration); + // Clear _imageNaturalSize when imageProvider evicted. + final key = await obtainKey(configuration); + _imageNaturalSize.remove(key); + return result; } - return contextId; } /// default ImageProviderFactory implementation of [ImageType.cached] -ImageProvider defaultCachedProviderFactory(Uri uri, [param]) { - int? contextId = _getContextId(param); - return CachedNetworkImage(uri.toString(), contextId: contextId); +ImageProvider defaultCachedProviderFactory( + Uri uri, ImageProviderParams params) { + return KrakenResizeImage.resizeIfNeeded( + params.cachedWidth, + params.cachedHeight, + CachedNetworkImage(uri.toString(), + contextId: (params as CachedNetworkImageProviderParams).contextId)); } /// default ImageProviderFactory implementation of [ImageType.network] -ImageProvider defaultNetworkProviderFactory(Uri uri, [param]) { - int? contextId = _getContextId(param); +ImageProvider defaultNetworkProviderFactory( + Uri uri, ImageProviderParams params) { NetworkImage networkImage = NetworkImage(uri.toString(), headers: { HttpHeaders.userAgentHeader: getKrakenInfo().userAgent, - HttpHeaderContext: contextId.toString(), + HttpHeaderContext: + (params as CachedNetworkImageProviderParams).contextId.toString(), }); - return networkImage; + return KrakenResizeImage.resizeIfNeeded( + params.cachedWidth, params.cachedHeight, networkImage); } /// default ImageProviderFactory implementation of [ImageType.file] -ImageProvider? defaultFileProviderFactory(Uri uri, [param]) { - ImageProvider? _imageProvider; - if (param is File) { - _imageProvider = FileImage(param); - } - return _imageProvider; +ImageProvider? defaultFileProviderFactory(Uri uri, ImageProviderParams params) { + return KrakenResizeImage.resizeIfNeeded(params.cachedWidth, + params.cachedHeight, FileImage((params as FileImageProviderParams).file)); } /// default ImageProviderFactory implementation of [ImageType.dataUrl]. -ImageProvider? defaultDataUrlProviderFactory(Uri uri, [param]) { - ImageProvider? _imageProvider; - if (param is Uint8List) { - _imageProvider = MemoryImage(param); - } - return _imageProvider; +ImageProvider? defaultDataUrlProviderFactory( + Uri uri, ImageProviderParams params) { + return KrakenResizeImage.resizeIfNeeded( + params.cachedWidth, + params.cachedHeight, + MemoryImage((params as DataUrlImageProviderParams).bytes)); } /// default ImageProviderFactory implementation of [ImageType.blob]. -ImageProvider? defaultBlobProviderFactory(Uri uri, [param]) { +ImageProvider? defaultBlobProviderFactory(Uri uri, ImageProviderParams params) { // @TODO: support blob file url return null; } /// default ImageProviderFactory implementation of [ImageType.assets]. -ImageProvider defaultAssetsProvider(Uri uri, [param]) { - return AssetImage(uri.toString()); +ImageProvider defaultAssetsProvider(Uri uri, ImageProviderParams params) { + return KrakenResizeImage.resizeIfNeeded( + params.cachedWidth, params.cachedHeight, AssetImage(uri.toString())); } diff --git a/kraken/lib/src/rendering/box_decoration.dart b/kraken/lib/src/rendering/box_decoration.dart index 5495a43635..ccdc4fc443 100644 --- a/kraken/lib/src/rendering/box_decoration.dart +++ b/kraken/lib/src/rendering/box_decoration.dart @@ -26,12 +26,6 @@ mixin RenderBoxDecorationMixin on RenderBoxModelBase { void disposePainter() { _painter?.dispose(); _painter = null; - // Since we're disposing of our painter, we won't receive change - // notifications. We mark ourselves as needing paint so that we will - // resubscribe to change notifications. If we didn't do this, then, for - // example, animated GIFs would stop animating when a DecoratedBox gets - // moved around the tree due to GlobalKey reparenting. - markNeedsPaint(); } void paintBackground( diff --git a/kraken/lib/src/rendering/box_decoration_painter.dart b/kraken/lib/src/rendering/box_decoration_painter.dart index 9005a7abdc..397627cf38 100644 --- a/kraken/lib/src/rendering/box_decoration_painter.dart +++ b/kraken/lib/src/rendering/box_decoration_painter.dart @@ -25,10 +25,8 @@ class BoxDecorationPainter extends BoxPainter { : super(onChanged); EdgeInsets? padding; - RenderStyle renderStyle; - CSSBoxDecoration get _decoration { - return renderStyle.decoration!; - } + CSSRenderStyle renderStyle; + CSSBoxDecoration get _decoration => renderStyle.decoration!; Paint? _cachedBackgroundPaint; Rect? _rectForCachedBackgroundPaint; @@ -480,7 +478,7 @@ class BoxDecorationImagePainter { this._onChanged ); - final RenderStyle _renderStyle; + final CSSRenderStyle _renderStyle; final DecorationImage _details; CSSBackgroundPosition get _backgroundPositionX { return _renderStyle.backgroundPositionX; diff --git a/kraken/lib/src/rendering/box_model.dart b/kraken/lib/src/rendering/box_model.dart index e14d13957f..2afae1ce8e 100644 --- a/kraken/lib/src/rendering/box_model.dart +++ b/kraken/lib/src/rendering/box_model.dart @@ -155,11 +155,7 @@ class RenderLayoutBox extends RenderBoxModel ContainerBoxParentData>, RenderBoxContainerDefaultsMixin> { - RenderLayoutBox({ - required RenderStyle renderStyle, - }) : super( - renderStyle: renderStyle, - ); + RenderLayoutBox({required CSSRenderStyle renderStyle}) : super(renderStyle: renderStyle); // Host content which can be scrolled. RenderLayoutBox? get renderScrollingContent { @@ -196,16 +192,6 @@ class RenderLayoutBox extends RenderBoxModel _paintingOrder = null; } - @override - void markNeedsLayout() { - super.markNeedsLayout(); - - // FlexItem layout must trigger flex container to layout. - if (parent is RenderFlexLayout) { - markParentNeedsLayout(); - } - } - // Sort children by zIndex, used for paint and hitTest. List? _paintingOrder; List get paintingOrder { @@ -226,6 +212,14 @@ class RenderLayoutBox extends RenderBoxModel List children = getChildren(); if (_childrenNeedsSort) { children.sort((RenderBox left, RenderBox right) { + // @FIXME: Add patch to handle nested fixed element paint priority, need to remove + // this logic after Kraken has implemented stacking context tree. + if (left is RenderBoxModel && left.renderStyle.position == CSSPositionType.fixed && + right is RenderBoxModel && right.renderStyle.position == CSSPositionType.fixed) { + // Child element always paint after parent element in the renderObject tree. + return right.renderStyle.isAncestorOf(left.renderStyle) ? 1 : -1; + } + bool isLeftNeedsStacking = left is RenderBoxModel && left.needsStacking; bool isRightNeedsStacking = right is RenderBoxModel && right.needsStacking; if (!isLeftNeedsStacking && isRightNeedsStacking) { @@ -359,8 +353,6 @@ class RenderLayoutBox extends RenderBoxModel /// Common layout content size (including flow and flexbox layout) calculation logic Size getContentSize({ - double? logicalContentWidth, - double? logicalContentHeight, required double contentWidth, required double contentHeight, }) { @@ -368,8 +360,8 @@ class RenderLayoutBox extends RenderBoxModel double finalContentHeight = contentHeight; // Size which is specified by sizing styles - double? specifiedContentWidth = logicalContentWidth; - double? specifiedContentHeight = logicalContentHeight; + double? specifiedContentWidth = renderStyle.contentBoxLogicalWidth; + double? specifiedContentHeight = renderStyle.contentBoxLogicalHeight; // Flex basis takes priority over main size in flex item. if (parent is RenderFlexLayout) { RenderBoxModel? parentRenderBoxModel = parent as RenderBoxModel?; @@ -559,7 +551,7 @@ class RenderLayoutBox extends RenderBoxModel } mixin RenderBoxModelBase on RenderBox { - late RenderStyle renderStyle; + late CSSRenderStyle renderStyle; Size? boxSize; } @@ -586,7 +578,7 @@ class RenderBoxModel extends RenderBox bool _debugShouldPaintOverlay = false; @override - late RenderStyle renderStyle; + late CSSRenderStyle renderStyle; bool get debugShouldPaintOverlay => _debugShouldPaintOverlay; @@ -607,18 +599,6 @@ class RenderBoxModel extends RenderBox return _contentConstraints; } - /// Used when setting percentage line-height style, it needs to be calculated when node attached - /// where it needs to know the font-size of its own element - bool _shouldLazyCalLineHeight = false; - - bool get shouldLazyCalLineHeight => _shouldLazyCalLineHeight; - - set shouldLazyCalLineHeight(bool value) { - if (_shouldLazyCalLineHeight != value) { - _shouldLazyCalLineHeight = value; - } - } - // When RenderBoxModel is scrolling box, contentConstraints are always equal to BoxConstraints(); bool isScrollingContentBox = false; @@ -733,53 +713,6 @@ class RenderBoxModel extends RenderBox ..parentData = parentData; } - // Boxes which have intrinsic ratio - double? _intrinsicWidth; - - double? get intrinsicWidth { - return _intrinsicWidth; - } - - set intrinsicWidth(double? value) { - if (_intrinsicWidth == value) return; - _intrinsicWidth = value; - _markSelfAndParentNeedsLayout(); - } - - // Boxes which have intrinsic ratio - double? _intrinsicHeight; - - double? get intrinsicHeight { - return _intrinsicHeight; - } - - set intrinsicHeight(double? value) { - if (_intrinsicHeight == value) return; - _intrinsicHeight = value; - _markSelfAndParentNeedsLayout(); - } - - double? _intrinsicRatio; - - double? get intrinsicRatio { - return _intrinsicRatio; - } - - set intrinsicRatio(double? value) { - if (_intrinsicRatio == value) return; - _intrinsicRatio = value; - _markSelfAndParentNeedsLayout(); - } - - // Sizing may affect parent size, mark parent as needsLayout in case - // renderBoxModel has tight constraints which will prevent parent from marking. - void _markSelfAndParentNeedsLayout() { - markNeedsLayout(); - if (parent is RenderBoxModel) { - (parent as RenderBoxModel).markNeedsLayout(); - } - } - /// Whether current box is the root of the document which corresponds to HTML element in dom tree. bool get isDocumentRootBox { // Get the outer box of overflow scroll box @@ -801,8 +734,8 @@ class RenderBoxModel extends RenderBox // child has percentage length and parent's size can not be calculated by style // thus parent needs relayout for its child calculate percentage length. void markParentNeedsRelayout() { - RenderBoxModel? parent = this.parent as RenderBoxModel?; - if (parent != null) { + AbstractNode? parent = this.parent; + if (parent is RenderBoxModel) { parent.needsRelayout = true; } } @@ -853,7 +786,20 @@ class RenderBoxModel extends RenderBox super.layout(newConstraints, parentUsesSize: parentUsesSize); } - /// Calculate renderBoxModel constraints + void markAdjacentRenderParagraphNeedsLayout() { + if (parent != null && parent is RenderFlowLayout && parentData is RenderLayoutParentData) { + if ((parentData as RenderLayoutParentData).nextSibling is RenderTextBox) { + ((parentData as RenderLayoutParentData).nextSibling as RenderTextBox).markRenderParagraphNeedsLayout(); + } + + if ((parentData as RenderLayoutParentData).previousSibling is RenderTextBox) { + ((parentData as RenderLayoutParentData).previousSibling as RenderTextBox).markRenderParagraphNeedsLayout(); + } + } + } + + // Calculate constraints of renderBoxModel on layout stage and + // only needed to be executed once on every layout. BoxConstraints getConstraints() { // Inner scrolling content box of overflow element inherits constraints from parent // but has indefinite max constraints to allow children overflow @@ -881,42 +827,29 @@ class RenderBoxModel extends RenderBox CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay; bool isDisplayInline = effectiveDisplay == CSSDisplay.inline; - EdgeInsets borderEdge = renderStyle.border; - EdgeInsetsGeometry? padding = renderStyle.padding; - - double horizontalBorderLength = borderEdge.horizontal; - double verticalBorderLength = borderEdge.vertical; - double horizontalPaddingLength = padding.horizontal; - double verticalPaddingLength = padding.vertical; - double? minWidth = renderStyle.minWidth.isAuto ? null : renderStyle.minWidth.computedValue; double? maxWidth = renderStyle.maxWidth.isNone ? null : renderStyle.maxWidth.computedValue; double? minHeight = renderStyle.minHeight.isAuto ? null : renderStyle.minHeight.computedValue; double? maxHeight = renderStyle.maxHeight.isNone ? null : renderStyle.maxHeight.computedValue; - // Content size calculated from style - logicalContentWidth = renderStyle.getLogicalContentWidth(); - logicalContentHeight = renderStyle.getLogicalContentHeight(); - - // Box size calculated from style - double? logicalWidth = logicalContentWidth != null - ? logicalContentWidth! + horizontalPaddingLength + horizontalBorderLength - : null; - double? logicalHeight = logicalContentHeight != null - ? logicalContentHeight! + verticalPaddingLength + verticalBorderLength - : null; + // Need to calculated logic content size on every layout. + renderStyle.computeContentBoxLogicalWidth(); + renderStyle.computeContentBoxLogicalHeight(); - // Constraints // Width should be not smaller than border and padding in horizontal direction // when box-sizing is border-box which is only supported. - double minConstraintWidth = renderStyle.effectiveBorderLeftWidth.computedValue + renderStyle.effectiveBorderRightWidth.computedValue + - renderStyle.paddingLeft.computedValue + renderStyle.paddingRight.computedValue; - double maxConstraintWidth = logicalWidth ?? double.infinity; + double minConstraintWidth = renderStyle.effectiveBorderLeftWidth.computedValue + + renderStyle.effectiveBorderRightWidth.computedValue + + renderStyle.paddingLeft.computedValue + + renderStyle.paddingRight.computedValue; + double maxConstraintWidth = renderStyle.borderBoxLogicalWidth ?? double.infinity; // Height should be not smaller than border and padding in vertical direction // when box-sizing is border-box which is only supported. - double minConstraintHeight = renderStyle.effectiveBorderTopWidth.computedValue + renderStyle.effectiveBorderBottomWidth.computedValue + - renderStyle.paddingTop.computedValue + renderStyle.paddingBottom.computedValue; - double maxConstraintHeight = logicalHeight ?? double.infinity; + double minConstraintHeight = renderStyle.effectiveBorderTopWidth.computedValue + + renderStyle.effectiveBorderBottomWidth.computedValue + + renderStyle.paddingTop.computedValue + + renderStyle.paddingBottom.computedValue; + double maxConstraintHeight = renderStyle.borderBoxLogicalHeight ?? double.infinity; if (parent is RenderFlexLayout) { double? flexBasis = renderStyle.flexBasis == CSSLengthValue.auto ? null : renderStyle.flexBasis?.computedValue; @@ -1012,12 +945,6 @@ class RenderBoxModel extends RenderBox Size? _contentSize; Size get contentSize => _contentSize ?? Size.zero; - /// Logical content width calculated from style - double? logicalContentWidth; - - /// Logical content height calculated from style - double? logicalContentHeight; - double get clientWidth { double width = contentSize.width; width += renderStyle.padding.horizontal; @@ -1034,67 +961,22 @@ class RenderBoxModel extends RenderBox // Base layout methods to compute content constraints before content box layout. // Call this method before content box layout. void beforeLayout() { - BoxConstraints boxConstraints = constraints; - // Deflate border constraints. - boxConstraints = renderStyle.deflateBorderConstraints(boxConstraints); - - // Deflate padding constraints. - boxConstraints = renderStyle.deflatePaddingConstraints(boxConstraints); - - logicalContentWidth = renderStyle.getLogicalContentWidth(); - logicalContentHeight = renderStyle.getLogicalContentHeight(); - - if (!isScrollingContentBox && (logicalContentWidth != null || logicalContentHeight != null)) { - double minWidth; - double? maxWidth; - double minHeight; - double? maxHeight; - - if (boxConstraints.hasTightWidth) { - minWidth = maxWidth = boxConstraints.maxWidth; - } else if (logicalContentWidth != null) { - minWidth = 0.0; - maxWidth = logicalContentWidth; - } else { - minWidth = boxConstraints.minWidth; - maxWidth = boxConstraints.maxWidth; - } - - if (boxConstraints.hasTightHeight) { - minHeight = maxHeight = boxConstraints.maxHeight; - } else if (logicalContentHeight != null) { - minHeight = 0.0; - maxHeight = logicalContentHeight; - } else { - minHeight = boxConstraints.minHeight; - maxHeight = boxConstraints.maxHeight; - } - - // max and min size of intrinsc element should respect intrinsc ratio of each other - if (intrinsicRatio != null) { - if (!renderStyle.minWidth.isAuto && renderStyle.minHeight.isAuto) { - minHeight = minWidth * intrinsicRatio!; - } - if (!renderStyle.maxWidth.isNone && renderStyle.maxHeight.isNone) { - maxHeight = maxWidth! * intrinsicRatio!; - } - if (renderStyle.minWidth.isAuto && !renderStyle.minHeight.isAuto) { - minWidth = minHeight / intrinsicRatio!; - } - if (renderStyle.maxWidth.isNone && !renderStyle.maxHeight.isNone) { - maxWidth = maxHeight! / intrinsicRatio!; - } - } - - _contentConstraints = BoxConstraints( - minWidth: minWidth, - maxWidth: maxWidth!, - minHeight: minHeight, - maxHeight: maxHeight! - ); + BoxConstraints contentConstraints; + // @FIXME: Normally constraints is calculated in getConstraints by parent RenderLayoutBox in Kraken, + // except in sliver layout, constraints is calculated by [RenderSliverList] which kraken can not control, + // so it needs to invoke getConstraints here for sliver container's direct child. + if (parent is RenderSliverList) { + contentConstraints = getConstraints(); } else { - _contentConstraints = boxConstraints; + // Constraints is already calculated in parent layout. + contentConstraints = constraints; } + + // Deflate border constraints. + contentConstraints = renderStyle.deflateBorderConstraints(contentConstraints); + // Deflate padding constraints. + contentConstraints = renderStyle.deflatePaddingConstraints(contentConstraints); + _contentConstraints = contentConstraints; } /// Find scroll container @@ -1179,19 +1061,19 @@ class RenderBoxModel extends RenderBox @override void paint(PaintingContext context, Offset offset) { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childPaintDuration = 0; PerformanceTiming.instance().mark(PERF_PAINT_START, uniqueId: hashCode); } if (renderStyle.isVisibilityHidden) { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { PerformanceTiming.instance().mark(PERF_PAINT_END, uniqueId: hashCode); } return; } paintBoxModel(context, offset); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { int amendEndTime = DateTime.now().microsecondsSinceEpoch - childPaintDuration; PerformanceTiming.instance() @@ -1207,10 +1089,13 @@ class RenderBoxModel extends RenderBox )); } + // Reaint native EngineLayer sources with LayerHandle. + final LayerHandle _colorFilterLayer = LayerHandle(); + void paintColorFilter(PaintingContext context, Offset offset, PaintingContextCallback callback) { ColorFilter? colorFilter = renderStyle.colorFilter; if (colorFilter != null) { - context.pushColorFilter(offset, colorFilter, callback); + _colorFilterLayer.layer = context.pushColorFilter(offset, colorFilter, callback, oldLayer: _colorFilterLayer.layer); } else { callback(context, offset); } @@ -1232,13 +1117,13 @@ class RenderBoxModel extends RenderBox } } - ImageFilterLayer? _imageFilterLayer; + final LayerHandle _imageFilterLayer = LayerHandle(); void paintImageFilter(PaintingContext context, Offset offset, PaintingContextCallback callback) { if (renderStyle.imageFilter != null) { - _imageFilterLayer ??= ImageFilterLayer(); - _imageFilterLayer!.imageFilter = renderStyle.imageFilter; - context.pushLayer(_imageFilterLayer!, callback, offset); + _imageFilterLayer.layer ??= ImageFilterLayer(); + _imageFilterLayer.layer!.imageFilter = renderStyle.imageFilter; + context.pushLayer(_imageFilterLayer.layer!, callback, offset); } else { callback(context, offset); } @@ -1324,22 +1209,109 @@ class RenderBoxModel extends RenderBox return null; } - bool _hasLocalBackgroundImage(RenderStyle renderStyle) { + bool _hasLocalBackgroundImage(CSSRenderStyle renderStyle) { return renderStyle.backgroundImage != null && renderStyle.backgroundAttachment == CSSBackgroundAttachmentType.local; } - void _detachAllChildren() { - if (this is RenderObjectWithChildMixin) { - (this as RenderObjectWithChildMixin).child = null; - } else if (this is ContainerRenderObjectMixin) { - (this as ContainerRenderObjectMixin).removeAll(); + // Detach renderBoxModel from its containing block. + // Need to remove position placeholder besides removing itself. + void detachFromContainingBlock() { + detachRenderBox(this); + + // Remove placeholder of positioned element. + _detachPositionPlaceholder(this); + } + + // The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, + // called the containing block of the element. + // Definition of "containing block": https://www.w3.org/TR/CSS21/visudet.html#containing-block-details + void attachToContainingBlock( + RenderBox? containingBlockRenderBox, + { RenderBox? parent, RenderBox? after } + ) { + if (parent == null || containingBlockRenderBox == null) return; + + RenderBoxModel renderBoxModel = this; + CSSPositionType positionType = renderBoxModel.renderStyle.position; + // The containing block of an element is defined as follows: + if (positionType == CSSPositionType.relative + || positionType == CSSPositionType.static + || positionType == CSSPositionType.sticky + ) { + // If the element's position is 'relative' or 'static', + // the containing block is formed by the content edge of the nearest block container ancestor box. + attachRenderBox(containingBlockRenderBox, renderBoxModel, after: after); + + if (positionType == CSSPositionType.sticky) { + // Placeholder of sticky renderBox need to inherit offset from original renderBox, + // so it needs to layout before original renderBox. + _attachPositionPlaceholder(containingBlockRenderBox, renderBoxModel, after: after); + } + } else { + // Set custom positioned parentData. + RenderLayoutParentData parentData = RenderLayoutParentData(); + renderBoxModel.parentData = CSSPositionedLayout.getPositionParentData(renderBoxModel, parentData); + // Add child to containing block parent. + attachRenderBox(containingBlockRenderBox, renderBoxModel, isLast: true); + + // If container block is same as origin parent, the placeholder must after the origin renderBox + // because placeholder depends the constraints in layout stage. + RenderBox? previousSibling = containingBlockRenderBox == parent ? + renderBoxModel : after; + + // Add position holder to origin position parent. + _attachPositionPlaceholder(parent, renderBoxModel, after: previousSibling); } } + // Find previous sibling renderObject of renderBoxModel, used for inserting to containing block. + // If renderBoxModel is positioned, find the original place (position placeholder) to insert to + // when its position changes to relative/static/sticky. + RenderBox? getPreviousSibling() { + RenderBoxModel renderBoxModel = this; + RenderBox? previousSibling; + RenderPositionPlaceholder? renderPositionPlaceholder = renderBoxModel.renderPositionPlaceholder; + // It needs to find the previous sibling of the previous sibling if the placeholder of + // positioned element exists and follows renderObject at the same time, eg. + //
+ if (renderPositionPlaceholder != null) { + previousSibling = (renderPositionPlaceholder.parentData as ContainerParentDataMixin).previousSibling; + // The placeholder's previousSibling maybe the origin renderBox. + if (previousSibling == renderBoxModel) { + previousSibling = (renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + } + } else { + previousSibling = (renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + } + return previousSibling; + } + + // Attach placeholder of renderBoxModel from tree. + void _attachPositionPlaceholder(RenderBox parentRenderBox, RenderBoxModel renderBoxModel, {RenderBox? after}) { + // Position holder size will be updated on layout. + RenderPositionPlaceholder renderPositionPlaceholder = RenderPositionPlaceholder(preferredSize: Size.zero); + renderBoxModel.renderPositionPlaceholder = renderPositionPlaceholder; + renderPositionPlaceholder.positioned = renderBoxModel; + + attachRenderBox(parentRenderBox, renderPositionPlaceholder, after: after); + } + + // Detach placeholder of renderBoxModel from tree. + void _detachPositionPlaceholder(RenderBoxModel renderBoxModel) { + RenderPositionPlaceholder? renderPositionHolder = renderBoxModel.renderPositionPlaceholder; + if (renderPositionHolder != null) { + detachRenderBox(renderPositionHolder); + renderBoxModel.renderPositionPlaceholder = null; + } + } + + /// Called when its corresponding element disposed + @override @mustCallSuper void dispose() { + super.dispose(); // Clear renderObjects in list when disposed to avoid memory leak if (fixedChildren.isNotEmpty) { fixedChildren.clear(); @@ -1348,13 +1320,17 @@ class RenderBoxModel extends RenderBox // Dispose scroll behavior disposeScrollable(); + // Clear all paint layers + _colorFilterLayer.layer = null; + _imageFilterLayer.layer = null; + disposeTransformLayer(); + disposeOpacityLayer(); + disposeIntersectionObserverLayer(); + // Dispose box decoration painter. disposePainter(); // Evict render decoration image cache. renderStyle.decoration?.image?.image.evict(); - - // Remove reference from childs - _detachAllChildren(); } Offset getTotalScrollOffset() { @@ -1476,6 +1452,7 @@ class RenderBoxModel extends RenderBox @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('creatorElement', renderStyle.target)); properties.add(DiagnosticsProperty('contentSize', _contentSize)); properties.add(DiagnosticsProperty( 'contentConstraints', _contentConstraints, @@ -1490,12 +1467,12 @@ class RenderBoxModel extends RenderBox if (renderPositionPlaceholder != null) properties.add( DiagnosticsProperty('renderPositionHolder', renderPositionPlaceholder)); - if (intrinsicWidth != null) - properties.add(DiagnosticsProperty('intrinsicWidth', intrinsicWidth)); - if (intrinsicHeight != null) - properties.add(DiagnosticsProperty('intrinsicHeight', intrinsicHeight)); - if (intrinsicRatio != null) - properties.add(DiagnosticsProperty('intrinsicRatio', intrinsicRatio)); + if (renderStyle.intrinsicWidth != null) + properties.add(DiagnosticsProperty('intrinsicWidth', renderStyle.intrinsicWidth)); + if (renderStyle.intrinsicHeight != null) + properties.add(DiagnosticsProperty('intrinsicHeight', renderStyle.intrinsicHeight)); + if (renderStyle.intrinsicRatio != null) + properties.add(DiagnosticsProperty('intrinsicRatio', renderStyle.intrinsicRatio)); debugBoxDecorationProperties(properties); debugVisibilityProperties(properties); @@ -1503,4 +1480,40 @@ class RenderBoxModel extends RenderBox debugTransformProperties(properties); debugOpacityProperties(properties); } + + // Attach renderBox from tree. + static void attachRenderBox( + RenderObject parentRenderObject, + RenderBox renderBox, + {RenderObject? after, bool isLast = false} + ) { + if (isLast) { + assert(after == null); + } + if (parentRenderObject is RenderObjectWithChildMixin) { // RenderViewportBox + parentRenderObject.child = renderBox; + } else if (parentRenderObject is ContainerRenderObjectMixin) { // RenderLayoutBox or RenderSliverList + // Should attach to renderScrollingContent if it is scrollable. + if (parentRenderObject is RenderLayoutBox) { + parentRenderObject = parentRenderObject.renderScrollingContent ?? parentRenderObject; + } + if (isLast) { + after = parentRenderObject.lastChild; + } + parentRenderObject.insert(renderBox, after: after); + } + } + + // Detach renderBox from tree. + static void detachRenderBox(RenderObject renderBox) { + if (renderBox.parent == null) return; + + // Remove reference from parent. + RenderObject? parentRenderObject = renderBox.parent as RenderObject; + if (parentRenderObject is RenderObjectWithChildMixin) { + parentRenderObject.child = null; // Case for single child, eg. RenderViewportBox. + } else if (parentRenderObject is ContainerRenderObjectMixin) { + parentRenderObject.remove(renderBox); // Case for multi children, eg. RenderLayoutBox or RenderSliverList. + } + } } diff --git a/kraken/lib/src/rendering/flex.dart b/kraken/lib/src/rendering/flex.dart index 2b9b52721c..d6ac7a98cc 100644 --- a/kraken/lib/src/rendering/flex.dart +++ b/kraken/lib/src/rendering/flex.dart @@ -11,8 +11,8 @@ import 'package:kraken/rendering.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/css.dart'; -/// Infos of each run (flex line) in flex layout -/// https://www.w3.org/TR/css-flexbox-1/#flex-lines +// Position and size info of each run (flex line) in flex layout. +// https://www.w3.org/TR/css-flexbox-1/#flex-lines class _RunMetrics { _RunMetrics( this.mainAxisExtent, @@ -26,13 +26,13 @@ class _RunMetrics { _totalFlexShrink = totalFlexShrink, _remainingFreeSpace = remainingFreeSpace; - // Main size extent of the run + // Main size extent of the run. final double mainAxisExtent; - // Cross size extent of the run + // Cross size extent of the run. final double crossAxisExtent; - // Total flex grow factor in the run + // Total flex grow factor in the run. double get totalFlexGrow => _totalFlexGrow; double _totalFlexGrow; @@ -42,7 +42,7 @@ class _RunMetrics { } } - // Total flex shrink factor in the run + // Total flex shrink factor in the run. double get totalFlexShrink => _totalFlexShrink; double _totalFlexShrink; @@ -52,13 +52,13 @@ class _RunMetrics { } } - // Max extent above each flex items in the run + // Max extent above each flex items in the run. final double baselineExtent; - // All the children RenderBox of layout in the run + // All the children RenderBox of layout in the run. final Map runChildren; - // Remaining free space in the run + // Remaining free space in the run. double get remainingFreeSpace => _remainingFreeSpace; double _remainingFreeSpace = 0; @@ -69,7 +69,7 @@ class _RunMetrics { } } -/// Infos about Flex item in the run +// Infos about flex item in the run. class _RunChild { _RunChild( RenderBox child, @@ -81,7 +81,7 @@ class _RunChild { _adjustedMainSize = adjustedMainSize, _frozen = frozen; - /// Render object of flex item + // Render object of flex item. RenderBox get child => _child; RenderBox _child; @@ -91,7 +91,7 @@ class _RunChild { } } - /// Original main size on first layout + // Original main size on first layout. double get originalMainSize => _originalMainSize; double _originalMainSize; @@ -101,7 +101,7 @@ class _RunChild { } } - /// Adjusted main size after flexible length resolve algorithm + // Adjusted main size after flexible length resolve algorithm. double get adjustedMainSize => _adjustedMainSize; double _adjustedMainSize; @@ -111,7 +111,7 @@ class _RunChild { } } - /// Whether flex item should be frozen in flexible length resolve algorithm + // Whether flex item should be frozen in flexible length resolve algorithm. bool get frozen => _frozen; bool _frozen = false; @@ -122,65 +122,53 @@ class _RunChild { } } -bool? _startIsTopLeft(FlexDirection direction) { - switch (direction) { - case FlexDirection.column: - case FlexDirection.row: - return true; - case FlexDirection.rowReverse: - case FlexDirection.columnReverse: - return false; - } - -} - /// ## Layout algorithm /// -/// _This section describes how the framework causes [RenderFlexLayout] to position +/// _This section describes how the framework causes [RenderFlowLayout] to position /// its children._ /// -/// Layout for a [RenderFlexLayout] proceeds in 5 steps: +/// Layout for a [RenderFlowLayout] proceeds in 7 steps: /// -/// 1. Layout placeholder child of positioned element(absolute/fixed) in new layer -/// 2. Layout no positioned children with no constraints, compare children width with flex container main axis extent -/// to caculate total flex lines -/// 3. Caculate horizontal constraints of each child according to availabe horizontal space in each flex line -/// and flex-grow and flex-shrink properties -/// 4. Caculate vertical constraints of each child accordint to availabe vertical space in flex container vertial -/// and align-content properties and set -/// 5. Layout children again with above cacluated constraints -/// 6. Caculate flex line leading space and between space and position children in each flex line +/// 1. Layout positioned (eg. absolute/fixed) child first cause the size of position placeholder renderObject which is +/// layouted later depends on the size of its original RenderBoxModel. +/// 2. Layout flex items (not including position child and its position placeholder renderObject) +/// with no constraints and compute information of flex lines. +/// 3. Relayout children if flex factor styles (eg. flex-grow/flex-shrink) or cross axis stretch style (eg. align-items) exist. +/// 4. Set flex container depends on children size and container size styles. +/// 5. Set children offset based on flex container size and flex alignment styles (eg. justify-content). +/// 6. Layout and set offset of all the positioned placeholder renderObjects based on flex container size and +/// flex alignment styles cause positioned placeholder renderObject layout in a separated layer which is different +/// from flow layout algorithm. +/// 7. Set positioned child offset based on flex container size and its offset styles (eg. top/right/bottom/left). /// class RenderFlexLayout extends RenderLayoutBox { - /// Creates a flex render object. - /// - /// By default, the flex layout is horizontal and children are aligned to the - /// start of the main axis and the center of the cross axis. RenderFlexLayout({ List? children, - required RenderStyle renderStyle, - }) : super( - renderStyle: renderStyle, - ) { + required CSSRenderStyle renderStyle, + }) : super(renderStyle: renderStyle) { addAll(children); } - // Set during layout if overflow occurred on the main axis. - double? _overflow; + // Flex line boxes of flex layout. + // https://www.w3.org/TR/css-flexbox-1/#flex-lines + List<_RunMetrics> _flexLineBoxMetrics = <_RunMetrics>[]; - // Check whether any meaningful overflow is present. Values below an epsilon - // are treated as not overflowing. - bool get _hasOverflow => _overflow != null && _overflow! > precisionErrorTolerance; + // Cache the intrinsic size of children before flex-grow/flex-shrink + // to avoid relayout when style of flex items changes. + final Map _childrenIntrinsicMainSizes = {}; - /// Flex line boxs of flex layout - List<_RunMetrics> flexLineBoxMetrics = <_RunMetrics>[]; + // Cache original constraints of children on the first layout. + final Map _childrenOldConstraints = {}; - /// Cache the intrinsic size of children before flex-grow/flex-shrink - /// to avoid relayout when style of flex items changes - Map childrenIntrinsicMainSizes = {}; + @override + void dispose() { + super.dispose(); - /// Cache original constraints of children on the first layout - Map childrenOldConstraints = {}; + // Do not forget to clear reference variables, or it will cause memory leaks! + _flexLineBoxMetrics.clear(); + _childrenIntrinsicMainSizes.clear(); + _childrenOldConstraints.clear(); + } @override void setupParentData(RenderBox child) { @@ -197,105 +185,7 @@ class RenderFlexLayout extends RenderLayoutBox { return CSSFlex.isHorizontalFlexDirection(renderStyle.flexDirection); } - @override - void dispose() { - super.dispose(); - - flexLineBoxMetrics.clear(); - childrenIntrinsicMainSizes.clear(); - childrenOldConstraints.clear(); - } - - double _getIntrinsicSize({ - FlexDirection? sizingDirection, - double? - extent, // the extent in the direction that isn't the sizing direction - double Function(RenderBox child, double? extent)? - childSize, // a method to find the size in the sizing direction - }) { - if (renderStyle.flexDirection == sizingDirection) { - // INTRINSIC MAIN SIZE - // Intrinsic main size is the smallest size the flex container can take - // while maintaining the min/max-content contributions of its flex items. - double totalFlexGrow = 0.0; - double inflexibleSpace = 0.0; - double maxFlexFractionSoFar = 0.0; - RenderBox? child = firstChild; - while (child != null) { - final double flex = _getFlexGrow(child); - totalFlexGrow += flex; - if (flex > 0) { - final double flexFraction = - childSize!(child, extent) / _getFlexGrow(child); - maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction); - } else { - inflexibleSpace += childSize!(child, extent); - } - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - child = childParentData.nextSibling; - } - return maxFlexFractionSoFar * totalFlexGrow + inflexibleSpace; - } else { - // INTRINSIC CROSS SIZE - // Intrinsic cross size is the max of the intrinsic cross sizes of the - // children, after the flexible children are fit into the available space, - // with the children sized using their max intrinsic dimensions. - - // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction. - final double? availableMainSpace = extent; - double totalFlexGrow = 0; - double inflexibleSpace = 0.0; - double maxCrossSize = 0.0; - RenderBox? child = firstChild; - while (child != null) { - final double flex = _getFlexGrow(child); - totalFlexGrow += flex; - double? mainSize; - late double crossSize; - if (flex == 0) { - switch (renderStyle.flexDirection) { - case FlexDirection.rowReverse: - case FlexDirection.row: - mainSize = child.getMaxIntrinsicWidth(double.infinity); - crossSize = childSize!(child, mainSize); - break; - case FlexDirection.column: - case FlexDirection.columnReverse: - mainSize = child.getMaxIntrinsicHeight(double.infinity); - crossSize = childSize!(child, mainSize); - break; - } - inflexibleSpace += mainSize; - maxCrossSize = math.max(maxCrossSize, crossSize); - } - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - child = childParentData.nextSibling; - } - - // Determine the spacePerFlex by allocating the remaining available space. - // When you're overconstrained spacePerFlex can be negative. - final double spacePerFlex = math.max( - 0.0, (availableMainSpace! - inflexibleSpace) / totalFlexGrow); - - // Size remaining (flexible) items, find the maximum cross size. - child = firstChild; - while (child != null) { - final double flex = _getFlexGrow(child); - if (flex > 0) - maxCrossSize = - math.max(maxCrossSize, childSize!(child, spacePerFlex * flex)); - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - child = childParentData.nextSibling; - } - - return maxCrossSize; - } - } - - /// Get start/end padding in the main axis according to flex direction + // Get start/end padding in the main axis according to flex direction. double flowAwareMainAxisPadding({bool isEnd = false}) { if (_isHorizontalFlexDirection) { return isEnd ? renderStyle.paddingRight.computedValue : renderStyle.paddingLeft.computedValue; @@ -304,7 +194,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get start/end padding in the cross axis according to flex direction + // Get start/end padding in the cross axis according to flex direction. double flowAwareCrossAxisPadding({bool isEnd = false}) { if (_isHorizontalFlexDirection) { return isEnd ? renderStyle.paddingBottom.computedValue : renderStyle.paddingTop.computedValue; @@ -313,7 +203,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get start/end border in the main axis according to flex direction + // Get start/end border in the main axis according to flex direction. double flowAwareMainAxisBorder({bool isEnd = false}) { if (_isHorizontalFlexDirection) { return isEnd ? renderStyle.effectiveBorderRightWidth.computedValue : renderStyle.effectiveBorderLeftWidth.computedValue; @@ -322,7 +212,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get start/end border in the cross axis according to flex direction + // Get start/end border in the cross axis according to flex direction. double flowAwareCrossAxisBorder({bool isEnd = false}) { if (_isHorizontalFlexDirection) { return isEnd ? renderStyle.effectiveBorderBottomWidth.computedValue : renderStyle.effectiveBorderTopWidth.computedValue; @@ -331,7 +221,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get start/end margin of child in the main axis according to flex direction + // Get start/end margin of child in the main axis according to flex direction. double? flowAwareChildMainAxisMargin(RenderBox child, {bool isEnd = false}) { RenderBoxModel? childRenderBoxModel; if (child is RenderBoxModel) { @@ -354,7 +244,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get start/end margin of child in the cross axis according to flex direction + // Get start/end margin of child in the cross axis according to flex direction. double? flowAwareChildCrossAxisMargin(RenderBox child, {bool isEnd = false}) { RenderBoxModel? childRenderBoxModel; if (child is RenderBoxModel) { @@ -377,48 +267,8 @@ class RenderFlexLayout extends RenderLayoutBox { } } - @override - double computeMinIntrinsicWidth(double height) { - return _getIntrinsicSize( - sizingDirection: FlexDirection.row, - extent: height, - childSize: (RenderBox child, double? extent) => - child.getMinIntrinsicWidth(extent!), - ); - } - - @override - double computeMaxIntrinsicWidth(double height) { - return _getIntrinsicSize( - sizingDirection: FlexDirection.row, - extent: height, - childSize: (RenderBox child, double? extent) => - child.getMaxIntrinsicWidth(extent!), - ); - } - - @override - double computeMinIntrinsicHeight(double width) { - return _getIntrinsicSize( - sizingDirection: FlexDirection.column, - extent: width, - childSize: (RenderBox child, double? extent) => - child.getMinIntrinsicHeight(extent!), - ); - } - - @override - double computeMaxIntrinsicHeight(double width) { - return _getIntrinsicSize( - sizingDirection: FlexDirection.column, - extent: width, - childSize: (RenderBox child, double? extent) => - child.getMaxIntrinsicHeight(extent!), - ); - } - double _getFlexGrow(RenderBox child) { - // Flex shrink has no effect on placeholder of positioned element + // Flex shrink has no effect on placeholder of positioned element. if (child is RenderPositionPlaceholder) { return 0; } @@ -426,7 +276,7 @@ class RenderFlexLayout extends RenderLayoutBox { } double _getFlexShrink(RenderBox child) { - // Flex shrink has no effect on placeholder of positioned element + // Flex shrink has no effect on placeholder of positioned element. if (child is RenderPositionPlaceholder) { return 0; } @@ -441,7 +291,7 @@ class RenderFlexLayout extends RenderLayoutBox { } AlignSelf _getAlignSelf(RenderBox child) { - // Flex shrink has no effect on placeholder of positioned element + // Flex shrink has no effect on placeholder of positioned element. if (child is RenderPositionPlaceholder) { return AlignSelf.auto; } @@ -463,15 +313,15 @@ class RenderFlexLayout extends RenderLayoutBox { return maxMainSize ?? double.infinity; } - /// Calculate automatic minimum size of flex item - /// Refer to https://www.w3.org/TR/css-flexbox-1/#min-size-auto for detail rules + // Calculate automatic minimum size of flex item. + // Refer to https://www.w3.org/TR/css-flexbox-1/#min-size-auto for detail rules double? _getMinMainAxisSize(RenderBoxModel child) { double? minMainSize; double? contentSize = 0; - // Min width of flex item if min-width is not specified use auto min width instead + // Min width of flex item if min-width is not specified use auto min width instead. double? minWidth = 0; - // Min height of flex item if min-height is not specified use auto min height instead + // Min height of flex item if min-height is not specified use auto min height instead. double? minHeight = 0; RenderStyle? childRenderStyle = child.renderStyle; @@ -493,24 +343,25 @@ class RenderFlexLayout extends RenderLayoutBox { : minHeight; if (child is RenderIntrinsic && - child.intrinsicRatio != null && + childRenderStyle.intrinsicRatio != null && _isHorizontalFlexDirection && childRenderStyle.width.isAuto) { double transferredSize = childRenderStyle.height.isNotAuto - ? childRenderStyle.height.computedValue * child.intrinsicRatio! - : child.intrinsicWidth!; + ? childRenderStyle.height.computedValue * childRenderStyle.intrinsicRatio! + : childRenderStyle.intrinsicWidth!; minMainSize = math.min(contentSize, transferredSize); } else if (child is RenderIntrinsic && - child.intrinsicRatio != null && + childRenderStyle.intrinsicRatio != null && !_isHorizontalFlexDirection && childRenderStyle.height.isAuto) { double transferredSize = childRenderStyle.width.isNotAuto - ? childRenderStyle.width.computedValue / child.intrinsicRatio! - : child.intrinsicHeight!; + ? childRenderStyle.width.computedValue / childRenderStyle.intrinsicRatio! + : childRenderStyle.intrinsicHeight!; minMainSize = math.min(contentSize, transferredSize); } else if (child is RenderBoxModel) { - double? specifiedMainSize = - _isHorizontalFlexDirection ? child.logicalContentWidth : child.logicalContentHeight; + double? specifiedMainSize = _isHorizontalFlexDirection + ? child.renderStyle.contentBoxLogicalWidth + : child.renderStyle.contentBoxLogicalHeight; minMainSize = specifiedMainSize != null ? math.min(contentSize, specifiedMainSize) : contentSize; @@ -562,7 +413,7 @@ class RenderFlexLayout extends RenderLayoutBox { childRenderBoxModel = child; } else if (child is RenderPositionPlaceholder) { // Position placeholder of flex item need to layout as its original renderBox - // so it needs to add margin to its extent + // so it needs to add margin to its extent. childRenderBoxModel = child.positioned; } @@ -602,7 +453,7 @@ class RenderFlexLayout extends RenderLayoutBox { childRenderBoxModel = child; } else if (child is RenderPositionPlaceholder) { // Position placeholder of flex item need to layout as its original renderBox - // so it needs to add margin to its extent + // so it needs to add margin to its extent. childRenderBoxModel = child.positioned; } @@ -635,7 +486,7 @@ class RenderFlexLayout extends RenderLayoutBox { @override void performLayout() { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutDuration = 0; PerformanceTiming.instance() .mark(PERF_FLEX_LAYOUT_START, uniqueId: hashCode); @@ -648,7 +499,7 @@ class RenderFlexLayout extends RenderLayoutBox { needsRelayout = false; } - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime flexLayoutEndTime = DateTime.now(); int amendEndTime = flexLayoutEndTime.microsecondsSinceEpoch - childLayoutDuration; @@ -660,54 +511,73 @@ class RenderFlexLayout extends RenderLayoutBox { void _doPerformLayout() { beforeLayout(); + List _positionedChildren = []; + List _positionPlaceholderChildren = []; + List _flexItemChildren = []; + List _stickyChildren = []; + + // Prepare children of different type for layout. RenderBox? child = firstChild; - // Layout positioned element while (child != null) { final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; - // Layout placeholder of positioned element(absolute/fixed) in new layer if (child is RenderBoxModel && childParentData.isPositioned) { - CSSPositionedLayout.layoutPositionedChild(this, child); - } else if (child is RenderPositionPlaceholder && isPlaceholderPositioned(child)) { - _layoutChildren(child); + _positionedChildren.add(child); + } else if (child is RenderPositionPlaceholder && _isPlaceholderPositioned(child)) { + _positionPlaceholderChildren.add(child); + } else { + _flexItemChildren.add(child); + if (child is RenderBoxModel && CSSPositionedLayout.isSticky(child)) { + _stickyChildren.add(child); + } } - child = childParentData.nextSibling; } - // Layout non positioned element and its placeholder - _layoutChildren(null); + // Need to layout out of flow positioned element before normal flow element + // cause the size of RenderPositionPlaceholder in flex layout needs to use + // the size of its original RenderBoxModel. + for (RenderBoxModel child in _positionedChildren) { + CSSPositionedLayout.layoutPositionedChild(this, child); + } - // Reset offset of positioned and sticky element - child = firstChild; - while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; + // Layout non positioned element (include element in normal flow and + // placeholder of positioned element). + _layoutFlexItems(_flexItemChildren); - if (child is RenderBoxModel && childParentData.isPositioned) { - CSSPositionedLayout.applyPositionedChildOffset(this, child); - - extendMaxScrollableSize(child); - ensureBoxSizeLargerThanScrollableSize(); - } else if (child is RenderBoxModel && - CSSPositionedLayout.isSticky(child)) { - RenderBoxModel scrollContainer = child.findScrollContainer()!; - // Sticky offset depends on the layout of scroll container, delay the calculation of - // sticky offset to the layout stage of scroll container if its not layouted yet - // due to the layout order of Flutter renderObject tree is from down to up. - if (scrollContainer.hasSize) { - CSSPositionedLayout.applyStickyChildOffset(scrollContainer, child); - } + // Every placeholder of positioned element should be layouted in a separated layer in flex layout + // which is different from the placeholder in flow layout which layout in the same flow as + // other elements in normal flow. + for (RenderPositionPlaceholder child in _positionPlaceholderChildren) { + _layoutPositionPlaceholder(child); + } + + // Set offset of positioned element after flex box size is set. + for (RenderBoxModel child in _positionedChildren) { + CSSPositionedLayout.applyPositionedChildOffset(this, child); + // Position of positioned element affect the scroll size of container. + extendMaxScrollableSize(child); + ensureBoxSizeLargerThanScrollableSize(); + } + + // Set offset of sticky element on each layout. + for (RenderBoxModel child in _stickyChildren) { + RenderBoxModel scrollContainer = child.findScrollContainer()!; + // Sticky offset depends on the layout of scroll container, delay the calculation of + // sticky offset to the layout stage of scroll container if its not layouted yet + // due to the layout order of Flutter renderObject tree is from down to up. + if (scrollContainer.hasSize) { + CSSPositionedLayout.applyStickyChildOffset(scrollContainer, child); } - child = childParentData.nextSibling; } bool isScrollContainer = - (renderStyle.effectiveOverflowX != CSSOverflowType.visible || - renderStyle.effectiveOverflowY != CSSOverflowType.visible); + renderStyle.effectiveOverflowX != CSSOverflowType.visible + || renderStyle.effectiveOverflowY != CSSOverflowType.visible; + if (isScrollContainer) { - // Find all the sticky children when scroll container is layouted + // Find all the sticky children when scroll container is layouted. stickyChildren = findStickyChildren(); - // Calculate the offset of its sticky children + // Calculate the offset of its sticky children. for (RenderBoxModel stickyChild in stickyChildren) { CSSPositionedLayout.applyStickyChildOffset(this, stickyChild); } @@ -716,163 +586,57 @@ class RenderFlexLayout extends RenderLayoutBox { didLayout(); } - - bool isPlaceholderPositioned(RenderObject child) { - if (child is RenderPositionPlaceholder) { - RenderBoxModel realDisplayedBox = child.positioned!; - RenderLayoutParentData parentData = - realDisplayedBox.parentData as RenderLayoutParentData; - if (parentData.isPositioned) { - return true; - } - } - return false; - } - - /// There are 4 stages when layouting children - /// 1. Layout children in flow order to calculate flex lines according to its constaints and flex-wrap property - /// 2. Relayout children according to flex-grow and flex-shrink factor - /// 3. Set flex container size according to children size - /// 4. Align children according to justify-content, align-items and align-self properties - void _layoutChildren(RenderPositionPlaceholder? placeholderChild) { - /// If no child exists, stop layout. - if (childCount == 0) { - Size layoutContentSize = getContentSize( - logicalContentWidth: logicalContentWidth, - logicalContentHeight: logicalContentHeight, - contentWidth: 0, - contentHeight: 0, - ); - setMaxScrollableSize(layoutContentSize); - size = getBoxSize(layoutContentSize); + // There are 4 steps for layout flex items. + // 1. Layout children to generate flex line boxes metrics. + // 2. Relayout children according to flex factor properties and alignment properties in cross axis. + // 3. Set flex container size according to children size and its own size styles. + // 4. Align children according to alignment properties. + void _layoutFlexItems(List children) { + // If no child exists, stop layout. + if (children.isEmpty) { + _setContainerSizeWithNoChild(); return; } - assert(contentConstraints != null); - - // Metrics of each flex line - List<_RunMetrics> runMetrics = <_RunMetrics>[]; - // Flex container size in main and cross direction - Map containerSizeMap = { - 'main': 0.0, - 'cross': 0.0, - }; - - if (placeholderChild == null) { - flexLineBoxMetrics = runMetrics; - } - /// Stage 1: Layout children in flow order to calculate flex lines - _layoutByFlexLine( - runMetrics, - placeholderChild, - containerSizeMap, - ); + // Layout children to compute metrics of flex lines. + List<_RunMetrics> _runMetrics = _computeRunMetrics(children); - /// If no non positioned child exists, stop layout - if (runMetrics.isEmpty) { - Size contentSize = Size( - logicalContentWidth ?? 0, - logicalContentHeight ?? 0, - ); - setMaxScrollableSize(contentSize); - size = getBoxSize(contentSize); - return; - } + // Compute spacing before and between each flex line. + Map _runSpacingMap = _computeRunSpacing(_runMetrics); - double containerCrossAxisExtent = 0.0; + // Adjust children size based on flex properties which may affect children size. + _adjustChildrenSize(_runMetrics, _runSpacingMap); - if (!_isHorizontalFlexDirection) { - containerCrossAxisExtent = logicalContentWidth ?? 0; - } else { - containerCrossAxisExtent = logicalContentHeight ?? 0; - } + // Set flex container size. + _setContainerSize(_runMetrics); - /// Calculate leading and between space between flex lines - final double crossAxisFreeSpace = containerCrossAxisExtent - containerSizeMap['cross']!; - final int runCount = runMetrics.length; - double runLeadingSpace = 0.0; - double runBetweenSpace = 0.0; + // Set children offset based on flex alignment properties. + _setChildrenOffset(_runMetrics, _runSpacingMap); + } - /// Align-content only works in when flex-wrap is no nowrap - if (renderStyle.flexWrap == FlexWrap.wrap || - renderStyle.flexWrap == FlexWrap.wrapReverse) { - switch (renderStyle.alignContent) { - case AlignContent.flexStart: - case AlignContent.start: - break; - case AlignContent.flexEnd: - case AlignContent.end: - runLeadingSpace = crossAxisFreeSpace; - break; - case AlignContent.center: - runLeadingSpace = crossAxisFreeSpace / 2.0; - break; - case AlignContent.spaceBetween: - if (crossAxisFreeSpace < 0) { - runBetweenSpace = 0; - } else { - runBetweenSpace = - runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0; - } - break; - case AlignContent.spaceAround: - if (crossAxisFreeSpace < 0) { - runLeadingSpace = crossAxisFreeSpace / 2.0; - runBetweenSpace = 0; - } else { - runBetweenSpace = crossAxisFreeSpace / runCount; - runLeadingSpace = runBetweenSpace / 2.0; - } - break; - case AlignContent.spaceEvenly: - if (crossAxisFreeSpace < 0) { - runLeadingSpace = crossAxisFreeSpace / 2.0; - runBetweenSpace = 0; - } else { - runBetweenSpace = crossAxisFreeSpace / (runCount + 1); - runLeadingSpace = runBetweenSpace; - } - break; - case AlignContent.stretch: - runBetweenSpace = crossAxisFreeSpace / runCount; - if (runBetweenSpace < 0) { - runBetweenSpace = 0; - } - break; - } - } + // Layout position placeholder. + void _layoutPositionPlaceholder(RenderPositionPlaceholder child) { + List _positionPlaceholderChildren = [child]; - /// Stage 2: Layout flex item second time based on flex factor and actual size - _relayoutByFlexFactor( - runMetrics, - runBetweenSpace, - placeholderChild, - containerSizeMap, - ); + // Layout children to compute metrics of flex lines. + List<_RunMetrics> _runMetrics = _computeRunMetrics(_positionPlaceholderChildren); - /// Stage 3: Set flex container size according to children size - _setContainerSize( - runMetrics, - containerSizeMap, - ); + // Compute spacing before and between each flex line. + Map _runSpacingMap = _computeRunSpacing(_runMetrics); - /// Stage 4: Set children offset based on flex alignment properties - _alignChildren( - runMetrics, - runBetweenSpace, - runLeadingSpace, - placeholderChild, - ); + // Set children offset based on flex alignment properties. + _setChildrenOffset(_runMetrics, _runSpacingMap); } - /// 1. Layout children in flow order to calculate flex lines according to its constaints and flex-wrap property - void _layoutByFlexLine( - List<_RunMetrics> runMetrics, - RenderPositionPlaceholder? placeholderChild, - Map containerSizeMap, + // Layout children in normal flow order to calculate metrics of flex lines according to its constraints + // and flex-wrap property. + List<_RunMetrics> _computeRunMetrics( + List children, ) { - double mainAxisExtent = 0.0; - double crossAxisExtent = 0.0; + List<_RunMetrics> _runMetrics = <_RunMetrics>[]; + + if (children.isEmpty) return _runMetrics; + double runMainAxisExtent = 0.0; double runCrossAxisExtent = 0.0; @@ -895,44 +659,30 @@ class RenderFlexLayout extends RenderLayoutBox { flexLineLimit = containerBox!.contentConstraints!.maxHeight; } - RenderBox? child = placeholderChild ?? firstChild; - - // Infos about each flex item in each flex line + // Info about each flex item in each flex line Map runChildren = {}; - while (child != null) { - final RenderLayoutParentData? childParentData = - child.parentData as RenderLayoutParentData?; - // Exclude positioned placeholder renderObject when layout non placeholder object - // and positioned renderObject - if (placeholderChild == null && - (isPlaceholderPositioned(child) || childParentData!.isPositioned)) { - child = childParentData!.nextSibling; - continue; - } - + for (RenderBox child in children) { + final RenderLayoutParentData? childParentData = child.parentData as RenderLayoutParentData?; BoxConstraints childConstraints; - - int? childNodeId; - if (child is RenderTextBox) { - childNodeId = child.hashCode; - } else if (child is RenderBoxModel) { - childNodeId = child.hashCode; - } - - if (child is RenderPositionPlaceholder && isPlaceholderPositioned(child)) { - RenderBoxModel realDisplayedBox = child.positioned!; - // Flutter only allow access size of direct children, so cannot use realDisplayedBox.size - Size realDisplayedBoxSize = - realDisplayedBox.getBoxSize(realDisplayedBox.contentSize); - double realDisplayedBoxWidth = realDisplayedBoxSize.width; - double realDisplayedBoxHeight = realDisplayedBoxSize.height; - childConstraints = BoxConstraints( - minWidth: realDisplayedBoxWidth, - maxWidth: realDisplayedBoxWidth, - minHeight: realDisplayedBoxHeight, - maxHeight: realDisplayedBoxHeight, - ); + int childNodeId = child.hashCode; + + if (_isPlaceholderPositioned(child)) { + RenderBoxModel positionedBox = (child as RenderPositionPlaceholder).positioned!; + if (positionedBox.hasSize) { + // Flutter only allow access size of direct children, so cannot use realDisplayedBox.size + Size realDisplayedBoxSize = positionedBox.getBoxSize(positionedBox.contentSize); + double realDisplayedBoxWidth = realDisplayedBoxSize.width; + double realDisplayedBoxHeight = realDisplayedBoxSize.height; + childConstraints = BoxConstraints( + minWidth: realDisplayedBoxWidth, + maxWidth: realDisplayedBoxWidth, + minHeight: realDisplayedBoxHeight, + maxHeight: realDisplayedBoxHeight, + ); + } else { + childConstraints = BoxConstraints(); + } } else if (child is RenderBoxModel) { childConstraints = child.getConstraints(); } else if (child is RenderTextBox) { @@ -941,73 +691,72 @@ class RenderFlexLayout extends RenderLayoutBox { childConstraints = BoxConstraints(); } - // Whether child need to layout + // Whether child need to layout. bool isChildNeedsLayout = true; if (child.hasSize && - !needsRelayout && - (childConstraints == childrenOldConstraints[child.hashCode]) && - ((child is RenderBoxModel && !child.needsLayout) || - (child is RenderTextBox && !child.needsLayout))) { + !needsRelayout && + (childConstraints == _childrenOldConstraints[child.hashCode]) && + ((child is RenderBoxModel && !child.needsLayout) || + (child is RenderTextBox && !child.needsLayout))) { isChildNeedsLayout = false; } if (isChildNeedsLayout) { late DateTime childLayoutStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutStart = DateTime.now(); } - childrenOldConstraints[child.hashCode] = childConstraints; + _childrenOldConstraints[child.hashCode] = childConstraints; // Inflate constraints of percentage renderBoxModel to force it layout after percentage resolved // cause Flutter will skip child layout if its constraints not changed between two layouts. if (child is RenderBoxModel && needsRelayout) { childConstraints = BoxConstraints( minWidth: childConstraints.maxWidth != double.infinity - ? childConstraints.maxWidth - : 0, + ? childConstraints.maxWidth + : 0, maxWidth: double.infinity, minHeight: childConstraints.maxHeight != double.infinity - ? childConstraints.maxHeight - : 0, + ? childConstraints.maxHeight + : 0, maxHeight: double.infinity, ); } child.layout(childConstraints, parentUsesSize: true); - if (kProfileMode) { + + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childLayoutEnd = DateTime.now(); childLayoutDuration += (childLayoutEnd.microsecondsSinceEpoch - - childLayoutStart.microsecondsSinceEpoch); + childLayoutStart.microsecondsSinceEpoch); } Size? childSize = _getChildSize(child); - childrenIntrinsicMainSizes[child.hashCode] = - _isHorizontalFlexDirection - ? childSize!.width - : childSize!.height; + _childrenIntrinsicMainSizes[child.hashCode] = + _isHorizontalFlexDirection + ? childSize!.width + : childSize!.height; } Size? childSize = _getChildSize(child, shouldUseIntrinsicMainSize: true); double childMainAxisExtent = - _getMainAxisExtent(child, shouldUseIntrinsicMainSize: true); + _getMainAxisExtent(child, shouldUseIntrinsicMainSize: true); double childCrossAxisExtent = _getCrossAxisExtent(child); bool isExceedFlexLineLimit = - runMainAxisExtent + childMainAxisExtent > flexLineLimit; + runMainAxisExtent + childMainAxisExtent > flexLineLimit; // calculate flex line if ((renderStyle.flexWrap == FlexWrap.wrap || - renderStyle.flexWrap == FlexWrap.wrapReverse) && - runChildren.isNotEmpty && - isExceedFlexLineLimit) { - mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent); - crossAxisExtent += runCrossAxisExtent; - - runMetrics.add(_RunMetrics( - runMainAxisExtent, - runCrossAxisExtent, - totalFlexGrow, - totalFlexShrink, - maxSizeAboveBaseline, - runChildren, - 0)); + renderStyle.flexWrap == FlexWrap.wrapReverse) && + runChildren.isNotEmpty && + isExceedFlexLineLimit) { + + _runMetrics.add(_RunMetrics( + runMainAxisExtent, + runCrossAxisExtent, + totalFlexGrow, + totalFlexShrink, + maxSizeAboveBaseline, + runChildren, + 0)); runChildren = {}; runMainAxisExtent = 0.0; runCrossAxisExtent = 0.0; @@ -1020,18 +769,18 @@ class RenderFlexLayout extends RenderLayoutBox { runMainAxisExtent += childMainAxisExtent; runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent); - /// Calculate baseline extent of layout box + // Calculate baseline extent of layout box. AlignSelf alignSelf = _getAlignSelf(child); - // Vertical align is only valid for inline box - // Baseline alignment in column direction behave the same as flex-start + // Vertical align is only valid for inline box. + // Baseline alignment in column direction behave the same as flex-start. if (_isHorizontalFlexDirection && (alignSelf == AlignSelf.baseline || - renderStyle.alignItems == AlignItems.baseline)) { + renderStyle.alignItems == AlignItems.baseline)) { // Distance from top to baseline of child double childAscent = _getChildAscent(child); double? lineHeight = _getLineHeight(child); - // Leading space between content box and virtual box of child + // Leading space between content box and virtual box of child. double childLeading = 0; if (lineHeight != null) { childLeading = lineHeight - childSize!.height; @@ -1049,10 +798,10 @@ class RenderFlexLayout extends RenderLayoutBox { ); maxSizeBelowBaseline = math.max( childMarginTop + - childMarginBottom + - childSize!.height - - childAscent + - childLeading / 2, + childMarginBottom + + childSize!.height - + childAscent + + childLeading / 2, maxSizeBelowBaseline, ); runCrossAxisExtent = maxSizeAboveBaseline + maxSizeBelowBaseline; @@ -1067,7 +816,7 @@ class RenderFlexLayout extends RenderLayoutBox { false, ); - childParentData!.runIndex = runMetrics.length; + childParentData!.runIndex = _runMetrics.length; assert(child.parentData == childParentData); @@ -1079,28 +828,128 @@ class RenderFlexLayout extends RenderLayoutBox { if (flexShrink > 0) { totalFlexShrink += flexShrink; } - // Only layout placeholder renderObject child - child = placeholderChild == null ? childParentData.nextSibling : null; } if (runChildren.isNotEmpty) { - mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent); - crossAxisExtent += runCrossAxisExtent; - runMetrics.add(_RunMetrics( - runMainAxisExtent, - runCrossAxisExtent, - totalFlexGrow, - totalFlexShrink, - maxSizeAboveBaseline, - runChildren, - 0)); + _runMetrics.add(_RunMetrics( + runMainAxisExtent, + runCrossAxisExtent, + totalFlexGrow, + totalFlexShrink, + maxSizeAboveBaseline, + runChildren, + 0)); + } + + _flexLineBoxMetrics = _runMetrics; + + return _runMetrics; + } + + // Compute the leading and between spacing of each flex line. + Map _computeRunSpacing( + List<_RunMetrics> _runMetrics, + ) { + double? contentBoxLogicalWidth = renderStyle.contentBoxLogicalWidth; + double? contentBoxLogicalHeight = renderStyle.contentBoxLogicalHeight; + double containerCrossAxisExtent = 0.0; + + if (!_isHorizontalFlexDirection) { + containerCrossAxisExtent = contentBoxLogicalWidth ?? 0; + } else { + containerCrossAxisExtent = contentBoxLogicalHeight ?? 0; + } + + double runCrossSize = _getRunsCrossSize(_runMetrics); + + // Calculate leading and between space between flex lines. + final double crossAxisFreeSpace = containerCrossAxisExtent - runCrossSize; + final int runCount = _runMetrics.length; + double runLeadingSpace = 0.0; + double runBetweenSpace = 0.0; + + // Align-content only works in when flex-wrap is no nowrap. + if (renderStyle.flexWrap == FlexWrap.wrap || + renderStyle.flexWrap == FlexWrap.wrapReverse) { + switch (renderStyle.alignContent) { + case AlignContent.flexStart: + case AlignContent.start: + break; + case AlignContent.flexEnd: + case AlignContent.end: + runLeadingSpace = crossAxisFreeSpace; + break; + case AlignContent.center: + runLeadingSpace = crossAxisFreeSpace / 2.0; + break; + case AlignContent.spaceBetween: + if (crossAxisFreeSpace < 0) { + runBetweenSpace = 0; + } else { + runBetweenSpace = + runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0; + } + break; + case AlignContent.spaceAround: + if (crossAxisFreeSpace < 0) { + runLeadingSpace = crossAxisFreeSpace / 2.0; + runBetweenSpace = 0; + } else { + runBetweenSpace = crossAxisFreeSpace / runCount; + runLeadingSpace = runBetweenSpace / 2.0; + } + break; + case AlignContent.spaceEvenly: + if (crossAxisFreeSpace < 0) { + runLeadingSpace = crossAxisFreeSpace / 2.0; + runBetweenSpace = 0; + } else { + runBetweenSpace = crossAxisFreeSpace / (runCount + 1); + runLeadingSpace = runBetweenSpace; + } + break; + case AlignContent.stretch: + runBetweenSpace = crossAxisFreeSpace / runCount; + if (runBetweenSpace < 0) { + runBetweenSpace = 0; + } + break; + } + } + Map _runSpacingMap = { + 'leading': runLeadingSpace, + 'between': runBetweenSpace + }; + return _runSpacingMap; + } - containerSizeMap['cross'] = crossAxisExtent; + // Find the size in the cross axis of flex lines. + // @TODO: add cache to avoid recalculate in one layout stage. + double _getRunsCrossSize( + List<_RunMetrics> _runMetrics, + ) { + double crossSize = 0; + for (_RunMetrics run in _runMetrics) { + crossSize += run.crossAxisExtent; } + return crossSize; } - /// Resolve flex item length if flex-grow or flex-shrink exists - /// https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths + // Find the max size in the main axis of flex lines. + // @TODO: add cache to avoid recalculate in one layout stage. + double _getRunsMaxMainSize( + List<_RunMetrics> _runMetrics, + ) { + // Find the max size of flex lines. + _RunMetrics maxMainSizeMetrics = + _runMetrics.reduce((_RunMetrics curr, _RunMetrics next) { + return curr.mainAxisExtent > next.mainAxisExtent ? curr : next; + }); + return maxMainSizeMetrics.mainAxisExtent; + } + + // Resolve flex item length if flex-grow or flex-shrink exists. + // https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths bool _resolveFlexibleLengths( _RunMetrics runMetric, double initialFreeSpace, @@ -1113,8 +962,8 @@ class RenderFlexLayout extends RenderLayoutBox { double sumFlexFactors = isFlexGrow ? totalFlexGrow : totalFlexShrink; - /// If the sum of the unfrozen flex items’ flex factors is less than one, - /// multiply the initial free space by this sum as remaining free space + // If the sum of the unfrozen flex items’ flex factors is less than one, + // multiply the initial free space by this sum as remaining free space. if (sumFlexFactors > 0 && sumFlexFactors < 1) { double remainingFreeSpace = initialFreeSpace; double fractional = initialFreeSpace * sumFlexFactors; @@ -1128,18 +977,13 @@ class RenderFlexLayout extends RenderLayoutBox { List<_RunChild> maxViolations = []; double totalViolation = 0; - /// Loop flex item to find min/max violations + // Loop flex item to find min/max violations. runChildren.forEach((int? index, _RunChild runChild) { if (runChild.frozen) { return; } RenderBox child = runChild.child; - int? childNodeId; - if (child is RenderTextBox) { - childNodeId = child.hashCode; - } else if (child is RenderBoxModel) { - childNodeId = child.hashCode; - } + int childNodeId = child.hashCode; _RunChild? current = runChildren[childNodeId]; @@ -1149,10 +993,10 @@ class RenderFlexLayout extends RenderLayoutBox { double computedSize = originalMainSize; - /// Computed size by flex factor + // Computed size by flex factor. double adjustedSize = originalMainSize; - /// Adjusted size after min and max size clamp + // Adjusted size after min and max size clamp. double flexGrow = _getFlexGrow(child); double flexShrink = _getFlexShrink(child); @@ -1164,8 +1008,8 @@ class RenderFlexLayout extends RenderLayoutBox { final double flexGrow = _getFlexGrow(child); computedSize = originalMainSize + spacePerFlex * flexGrow; } else if (isFlexShrink && flexShrink > 0) { - /// If child's mainAxis have clips, it will create a new format context in it's children's. - /// so we do't need to care about child's size. + // If child's mainAxis have clips, it will create a new format context in it's children's. + // so we do't need to care about child's size. if (child is RenderBoxModel && _isChildMainAxisClip(child)) { computedSize = originalMainSize + remainingFreeSpace > 0 ? originalMainSize + remainingFreeSpace : 0; @@ -1178,7 +1022,7 @@ class RenderFlexLayout extends RenderLayoutBox { adjustedSize = computedSize; - /// Find all the violations by comparing min and max size of flex items + // Find all the violations by comparing min and max size of flex items. if (child is RenderBoxModel && !_isChildMainAxisClip(child)) { double minMainAxisSize = _getMinMainAxisSize(child)!; double maxMainAxisSize = _getMaxMainAxisSize(child); @@ -1191,7 +1035,7 @@ class RenderFlexLayout extends RenderLayoutBox { double violation = adjustedSize - computedSize; - /// Collect all the flex items with violations + // Collect all the flex items with violations. if (violation > 0) { minViolations.add(runChild); } else if (violation < 0) { @@ -1201,9 +1045,9 @@ class RenderFlexLayout extends RenderLayoutBox { totalViolation += violation; }); - /// Freeze over-flexed items + // Freeze over-flexed items. if (totalViolation == 0) { - /// If total violation is zero, freeze all the flex items and exit loop + // If total violation is zero, freeze all the flex items and exit loop. runChildren.forEach((int? index, _RunChild runChild) { runChild.frozen = true; }); @@ -1211,7 +1055,7 @@ class RenderFlexLayout extends RenderLayoutBox { List<_RunChild> violations = totalViolation < 0 ? maxViolations : minViolations; - /// Find all the violations, set main size and freeze all the flex items + // Find all the violations, set main size and freeze all the flex items. for (int i = 0; i < violations.length; i++) { _RunChild runChild = violations[i]; runChild.frozen = true; @@ -1222,11 +1066,11 @@ class RenderFlexLayout extends RenderLayoutBox { double flexGrow = _getFlexGrow(child); double flexShrink = _getFlexShrink(child); - /// If total violation is positive, freeze all the items with min violations + // If total violation is positive, freeze all the items with min violations. if (flexGrow > 0) { runMetric.totalFlexGrow -= flexGrow; - /// If total violation is negative, freeze all the items with max violations + // If total violation is negative, freeze all the items with max violations. } else if (flexShrink > 0) { runMetric.totalFlexShrink -= flexShrink; } @@ -1236,28 +1080,31 @@ class RenderFlexLayout extends RenderLayoutBox { return totalViolation != 0; } - /// Stage 2: Set size of flex item based on flex factors and min and max constraints and relayout - /// https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths - void _relayoutByFlexFactor( - List<_RunMetrics> runMetrics, - double runBetweenSpace, - RenderPositionPlaceholder? placeholderChild, - Map containerSizeMap, + // Adjust children size (not include position placeholder) based on + // flex factors (flex-grow/flex-shrink) and alignment in cross axis (align-items). + // https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths + void _adjustChildrenSize( + List<_RunMetrics> _runMetrics, + Map _runSpacingMap, ) { - RenderBox? child = placeholderChild ?? firstChild; + if (_runMetrics.isEmpty) return; + + double runBetweenSpace = _runSpacingMap['between']!; + double? contentBoxLogicalWidth = renderStyle.contentBoxLogicalWidth; + double? contentBoxLogicalHeight = renderStyle.contentBoxLogicalHeight; - // Container's width specified by style or inherited from parent + // Container's width specified by style or inherited from parent. double? containerWidth = 0; - if (logicalContentWidth != null) { - containerWidth = logicalContentWidth; + if (contentBoxLogicalWidth != null) { + containerWidth = contentBoxLogicalWidth; } else if (contentConstraints!.hasTightWidth) { containerWidth = contentConstraints!.maxWidth; } - // Container's height specified by style or inherited from parent + // Container's height specified by style or inherited from parent. double? containerHeight = 0; - if (logicalContentHeight != null) { - containerHeight = logicalContentHeight; + if (contentBoxLogicalHeight != null) { + containerHeight = contentBoxLogicalHeight; } else if (contentConstraints!.hasTightHeight) { containerHeight = contentConstraints!.maxHeight; } @@ -1266,23 +1113,12 @@ class RenderFlexLayout extends RenderLayoutBox { final BoxSizeType mainSizeType = maxMainSize == 0.0 ? BoxSizeType.automatic : BoxSizeType.specified; - // Find max size of flex lines - _RunMetrics maxMainSizeMetrics = - runMetrics.reduce((_RunMetrics curr, _RunMetrics next) { - return curr.mainAxisExtent > next.mainAxisExtent ? curr : next; - }); - // Actual main axis size of flex items - double maxAllocatedMainSize = maxMainSizeMetrics.mainAxisExtent; - // Main axis size of flex container - containerSizeMap['main'] = mainSizeType != BoxSizeType.automatic - ? maxMainSize - : maxAllocatedMainSize; - - for (int i = 0; i < runMetrics.length; ++i) { - final _RunMetrics metrics = runMetrics[i]; + for (int i = 0; i < _runMetrics.length; ++i) { + final _RunMetrics metrics = _runMetrics[i]; final double totalFlexGrow = metrics.totalFlexGrow; final double totalFlexShrink = metrics.totalFlexShrink; final Map runChildren = metrics.runChildren; + final List<_RunChild> runChildrenList = runChildren.values.toList(); double totalSpace = 0; // Flex factor calculation depends on flex-basis if exists. @@ -1309,50 +1145,32 @@ class RenderFlexLayout extends RenderLayoutBox { // Flexbox with no size on main axis should adapt the main axis size with children. double initialFreeSpace = mainSizeType != BoxSizeType.automatic ? - maxMainSize! - totalSpace : 0; + maxMainSize - totalSpace : 0; bool isFlexGrow = initialFreeSpace > 0 && totalFlexGrow > 0; bool isFlexShrink = initialFreeSpace < 0 && totalFlexShrink > 0; if (isFlexGrow || isFlexShrink) { - /// remainingFreeSpace starts out at the same value as initialFreeSpace - /// but as we place and lay out flex items we subtract from it. + // remainingFreeSpace starts out at the same value as initialFreeSpace + // but as we place and lay out flex items we subtract from it. metrics.remainingFreeSpace = initialFreeSpace; - /// Loop flex items to resolve flexible length of flex items with flex factor + // Loop flex items to resolve flexible length of flex items with flex factor. while (_resolveFlexibleLengths(metrics, initialFreeSpace)) {} } - while (child != null) { - final RenderLayoutParentData? childParentData = - child.parentData as RenderLayoutParentData?; - - AlignSelf alignSelf = _getAlignSelf(child); - - // If size exists in align-items direction, stretch not works - bool isStretchSelfValid = false; - if (child is RenderBoxModel) { - isStretchSelfValid = _isHorizontalFlexDirection - ? child.renderStyle.height.isAuto - : child.renderStyle.width.isAuto; - } - - // Whether child should be stretched - bool isStretchSelf = placeholderChild == null && - isStretchSelfValid && - (alignSelf != AlignSelf.auto ? alignSelf == AlignSelf.stretch - : renderStyle.effectiveAlignItems == AlignItems.stretch); + for (_RunChild runChild in runChildrenList) { + RenderBox child = runChild.child; - // Whether child is positioned placeholder or positioned renderObject - bool isChildPositioned = placeholderChild == null && - (isPlaceholderPositioned(child) || childParentData!.isPositioned); - // Whether child cross size should be changed based on cross axis alignment change + // Whether child needs to be stretched in the cross axis. + bool isStretchSelf = needToStretchChildCrossSize(child); + // Whether child cross size should be changed based on cross axis alignment change. bool isCrossSizeChanged = false; if (child is RenderBoxModel && child.hasSize) { Size? childSize = _getChildSize(child); - double? childContentWidth = child.logicalContentWidth; - double? childContentHeight = child.logicalContentHeight; + double? childContentWidth = child.renderStyle.contentBoxLogicalWidth; + double? childContentHeight = child.renderStyle.contentBoxLogicalHeight; double paddingLeft = child.renderStyle.paddingLeft.computedValue; double paddingRight = child.renderStyle.paddingRight.computedValue; double paddingTop = child.renderStyle.paddingTop.computedValue; @@ -1363,67 +1181,57 @@ class RenderFlexLayout extends RenderLayoutBox { double borderBottom = child.renderStyle.effectiveBorderBottomWidth.computedValue; double? childLogicalWidth = childContentWidth != null - ? childContentWidth + - borderLeft + - borderRight + - paddingLeft + - paddingRight - : null; + ? childContentWidth + + borderLeft + + borderRight + + paddingLeft + + paddingRight + : null; double? childLogicalHeight = childContentHeight != null - ? childContentHeight + - borderTop + - borderBottom + - paddingTop + - paddingBottom - : null; - - // Cross size calculated from style which not including padding and border + ? childContentHeight + + borderTop + + borderBottom + + paddingTop + + paddingBottom + : null; + + // Cross size calculated from style which not including padding and border. double? childCrossLogicalSize = - _isHorizontalFlexDirection - ? childLogicalHeight - : childLogicalWidth; - // Cross size from first layout + _isHorizontalFlexDirection + ? childLogicalHeight + : childLogicalWidth; + // Cross size from first layout. double childCrossSize = - _isHorizontalFlexDirection - ? childSize!.height - : childSize!.width; + _isHorizontalFlexDirection + ? childSize!.height + : childSize!.width; isCrossSizeChanged = childCrossSize != childCrossLogicalSize; } - // Don't need to relayout child in following cases - // 1. child is placeholder when in layout non placeholder stage - // 2. child is positioned renderObject, it needs to layout in its special stage - // 3. child's size don't need to recompute if no flex-grow、flex-shrink or cross size not changed - if (isChildPositioned || - (!isFlexGrow && !isFlexShrink && !isCrossSizeChanged)) { - child = childParentData!.nextSibling; + // Child's size don't need to recompute if no flex-grow、flex-shrink or cross size not changed. + if (!isFlexGrow && !isFlexShrink && !isCrossSizeChanged) { continue; } - if (childParentData!.runIndex != i) break; - - // Skip scrolling content box + // Skip scrolling content box. if (child is RenderBoxModel && child.isScrollingContentBox) { - child = childParentData.nextSibling; continue; } double flexGrow = _getFlexGrow(child); double flexShrink = _getFlexShrink(child); - // Whether child need to layout + // Whether child need to layout. bool isChildNeedsLayout = (isFlexGrow && flexGrow > 0) || - (isFlexShrink && flexShrink > 0) || - isStretchSelf; + (isFlexShrink && flexShrink > 0) || + isStretchSelf; if (!isChildNeedsLayout) { - child = childParentData.nextSibling; continue; } - late DateTime childLayoutStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutStart = DateTime.now(); } @@ -1437,25 +1245,17 @@ class RenderFlexLayout extends RenderLayoutBox { ); child.layout(childConstraints, parentUsesSize: true); - // @FIXME: need to update runMetrics cause child relayout may affect container size - - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childLayoutEnd = DateTime.now(); childLayoutDuration += (childLayoutEnd.microsecondsSinceEpoch - - childLayoutStart.microsecondsSinceEpoch); + childLayoutStart.microsecondsSinceEpoch); } - - containerSizeMap['cross'] = - math.max(containerSizeMap['cross']!, _getCrossAxisExtent(child)); - - // Only layout placeholder renderObject child - child = childParentData.nextSibling; } } } - /// Get constraints of flex items which needs to change size due to - /// flex-grow/flex-shrink or align-items stretch + // Get constraints of flex items which needs to change size due to + // flex-grow/flex-shrink or align-items stretch. BoxConstraints getChildConstraints( RenderBox child, _RunMetrics metrics, @@ -1484,7 +1284,7 @@ class RenderFlexLayout extends RenderLayoutBox { minConstraintHeight = maxConstraintHeight = mainSize; } } - // Change cross axis constraints + // Change cross axis constraints. if (isStretchSelf) { bool isFlexWrap = renderStyle.flexWrap == FlexWrap.wrap || renderStyle.flexWrap == FlexWrap.wrapReverse; @@ -1495,7 +1295,7 @@ class RenderFlexLayout extends RenderLayoutBox { bool hasMaxConstraints = constraints.maxHeight != double.infinity; // Margin auto alignment takes priority over align-items stretch, - // it will not stretch child in vertical direction + // it will not stretch child in vertical direction. if (marginTop.isAuto || marginBottom.isAuto) { minConstraintHeight = maxConstraintHeight = childSize!.height; } else { @@ -1505,7 +1305,7 @@ class RenderFlexLayout extends RenderLayoutBox { double marginVertical = marginTop.computedValue + marginBottom.computedValue; double childCrossSize = flexLineHeight - marginVertical; double stretchedHeight; - // Flex line height should not exceed container's cross size if specified when flex-wrap is nowrap + // Flex line height should not exceed container's cross size if specified when flex-wrap is nowrap. if (!isFlexWrap && hasMaxConstraints) { double verticalBorderLength = renderStyle.border.vertical; double verticalPaddingLength = renderStyle.padding.vertical; @@ -1524,26 +1324,26 @@ class RenderFlexLayout extends RenderLayoutBox { if (child is RenderIntrinsic && child.renderStyle.width.isAuto && child.renderStyle.minWidth.isAuto && - child.intrinsicRatio != null + child.renderStyle.intrinsicRatio != null ) { - minConstraintWidth = maxConstraintWidth = minConstraintHeight / child.intrinsicRatio!; + minConstraintWidth = maxConstraintWidth = minConstraintHeight / child.renderStyle.intrinsicRatio!; } } else { CSSLengthValue marginLeft = childRenderStyle.marginLeft; CSSLengthValue marginRight = childRenderStyle.marginRight; bool hasMaxConstraints = constraints.maxHeight != double.infinity; // Margin auto alignment takes priority over align-items stretch, - // it will not stretch child in horizontal direction + // it will not stretch child in horizontal direction. if (marginLeft.isAuto || marginRight.isAuto) { minConstraintWidth = maxConstraintWidth = childSize!.width; } else { double flexLineHeight = _getFlexLineHeight(runCrossAxisExtent, runBetweenSpace); - // Should subtract margin when layout child + // Should subtract margin when layout child. double marginHorizontal = marginLeft.computedValue + marginRight.computedValue; double childCrossSize = flexLineHeight - marginHorizontal; double stretchedWidth; - // Flex line height should not exceed container's cross size if specified when flex-wrap is nowrap + // Flex line height should not exceed container's cross size if specified when flex-wrap is nowrap. if (!isFlexWrap && hasMaxConstraints) { double horizontalBorderLength = renderStyle.border.horizontal; double horizontalPaddingLength = renderStyle.padding.horizontal; @@ -1562,9 +1362,9 @@ class RenderFlexLayout extends RenderLayoutBox { if (child is RenderIntrinsic && child.renderStyle.height.isAuto && child.renderStyle.minHeight.isAuto && - child.intrinsicRatio != null + child.renderStyle.intrinsicRatio != null ) { - minConstraintHeight = maxConstraintHeight = minConstraintWidth * child.intrinsicRatio!; + minConstraintHeight = maxConstraintHeight = minConstraintWidth * child.renderStyle.intrinsicRatio!; } } } @@ -1580,51 +1380,57 @@ class RenderFlexLayout extends RenderLayoutBox { return childConstraints; } - /// Stage 3: Set flex container size according to children size + // Set flex container size according to children size. void _setContainerSize( - List<_RunMetrics> runMetrics, - Map containerSizeMap, + List<_RunMetrics> _runMetrics, ) { - // Find max size of flex lines - _RunMetrics maxMainSizeMetrics = - runMetrics.reduce((_RunMetrics curr, _RunMetrics next) { - return curr.mainAxisExtent > next.mainAxisExtent ? curr : next; - }); - // Actual main axis size of flex items - double maxAllocatedMainSize = maxMainSizeMetrics.mainAxisExtent; + if (_runMetrics.isEmpty) { + _setContainerSizeWithNoChild(); + return; + } - /// Stage 3: Set flex container size - double? contentWidth = - _isHorizontalFlexDirection - ? maxAllocatedMainSize - : containerSizeMap['cross']; - double? contentHeight = - _isHorizontalFlexDirection - ? containerSizeMap['cross'] - : maxAllocatedMainSize; + double runMaxMainSize = _getRunsMaxMainSize(_runMetrics); + double runCrossSize = _getRunsCrossSize(_runMetrics); + + double contentWidth = _isHorizontalFlexDirection + ? runMaxMainSize + : runCrossSize; + double contentHeight = _isHorizontalFlexDirection + ? runCrossSize + : runMaxMainSize; + + // Set flex container size. Size layoutContentSize = getContentSize( - logicalContentWidth: logicalContentWidth, - logicalContentHeight: logicalContentHeight, - contentWidth: contentWidth!, - contentHeight: contentHeight!, + contentWidth: contentWidth, + contentHeight: contentHeight, ); size = getBoxSize(layoutContentSize); - _setMaxScrollableSizeForFlex(runMetrics); + _setMaxScrollableSizeForFlex(_runMetrics); - /// Set auto value of min-width and min-height based on size of flex items + // Set auto value of min-width and min-height based on size of flex items. if (_isHorizontalFlexDirection) { - autoMinWidth = _getMainAxisAutoSize(runMetrics); - autoMinHeight = _getCrossAxisAutoSize(runMetrics); + autoMinWidth = _getMainAxisAutoSize(_runMetrics); + autoMinHeight = _getCrossAxisAutoSize(_runMetrics); } else { - autoMinHeight = _getMainAxisAutoSize(runMetrics); - autoMinWidth = _getCrossAxisAutoSize(runMetrics); + autoMinHeight = _getMainAxisAutoSize(_runMetrics); + autoMinWidth = _getCrossAxisAutoSize(_runMetrics); } } - /// Record the main size of all lines - void _recordRunsMainSize(_RunMetrics runMetrics, List runMainSize) { - Map runChildren = runMetrics.runChildren; + // Set size when layout has no child. + void _setContainerSizeWithNoChild() { + Size layoutContentSize = getContentSize( + contentWidth: 0, + contentHeight: 0, + ); + setMaxScrollableSize(layoutContentSize); + size = getBoxSize(layoutContentSize); + } + + // Record the main size of all lines. + void _recordRunsMainSize(_RunMetrics _runMetrics, List runMainSize) { + Map runChildren = _runMetrics.runChildren; double runMainExtent = 0; void iterateRunChildren(int? hashCode, _RunChild runChild) { RenderBox child = runChild.child; @@ -1652,19 +1458,19 @@ class RenderFlexLayout extends RenderLayoutBox { runMainSize.add(runMainExtent); } - /// Get auto min size in the main axis which equals the main axis size of its contents - /// https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size + // Get auto min size in the main axis which equals the main axis size of its contents. + // https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size double _getMainAxisAutoSize( - List<_RunMetrics> runMetrics, + List<_RunMetrics> _runMetrics, ) { double autoMinSize = 0; - // Main size of each run + // Main size of each run. List runMainSize = []; - // Calculate the max main size of all runs - for (_RunMetrics runMetrics in runMetrics) { - _recordRunsMainSize(runMetrics, runMainSize); + // Calculate the max main size of all runs. + for (_RunMetrics _runMetrics in _runMetrics) { + _recordRunsMainSize(_runMetrics, runMainSize); } autoMinSize = runMainSize.reduce((double curr, double next) { @@ -1673,9 +1479,9 @@ class RenderFlexLayout extends RenderLayoutBox { return autoMinSize; } - /// Record the cross size of all lines - void _recordRunsCrossSize(_RunMetrics runMetrics, List runCrossSize) { - Map runChildren = runMetrics.runChildren; + // Record the cross size of all lines. + void _recordRunsCrossSize(_RunMetrics _runMetrics, List runCrossSize) { + Map runChildren = _runMetrics.runChildren; double runCrossExtent = 0; List runChildrenCrossSize = []; void iterateRunChildren(int? hashCode, _RunChild runChild) { @@ -1698,19 +1504,19 @@ class RenderFlexLayout extends RenderLayoutBox { runCrossSize.add(runCrossExtent); } - /// Get auto min size in the cross axis which equals the cross axis size of its contents - /// https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size + // Get auto min size in the cross axis which equals the cross axis size of its contents. + // https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size double _getCrossAxisAutoSize( - List<_RunMetrics> runMetrics, + List<_RunMetrics> _runMetrics, ) { double autoMinSize = 0; - // Cross size of each run + // Cross size of each run. List runCrossSize = []; - // Calculate the max cross size of all runs - for (_RunMetrics runMetrics in runMetrics) { - _recordRunsCrossSize(runMetrics, runCrossSize); + // Calculate the max cross size of all runs. + for (_RunMetrics _runMetrics in _runMetrics) { + _recordRunsCrossSize(_runMetrics, runCrossSize); } // Get the sum of lines @@ -1721,28 +1527,28 @@ class RenderFlexLayout extends RenderLayoutBox { return autoMinSize; } - /// Set the size of scrollable overflow area for flex layout - /// https://drafts.csswg.org/css-overflow-3/#scrollable - void _setMaxScrollableSizeForFlex(List<_RunMetrics> runMetrics) { - // Scrollable main size collection of each line + // Set the size of scrollable overflow area for flex layout. + // https://drafts.csswg.org/css-overflow-3/#scrollable + void _setMaxScrollableSizeForFlex(List<_RunMetrics> _runMetrics) { + // Scrollable main size collection of each line. List scrollableMainSizeOfLines = []; - // Scrollable cross size collection of each line + // Scrollable cross size collection of each line. List scrollableCrossSizeOfLines = []; - // Total cross size of previous lines + // Total cross size of previous lines. double preLinesCrossSize = 0; - for (_RunMetrics runMetric in runMetrics) { + for (_RunMetrics runMetric in _runMetrics) { Map runChildren = runMetric.runChildren; List runChildrenList = []; - // Scrollable main size collection of each child in the line + // Scrollable main size collection of each child in the line. List scrollableMainSizeOfChildren = []; - // Scrollable cross size collection of each child in the line + // Scrollable cross size collection of each child in the line. List scrollableCrossSizeOfChildren = []; void iterateRunChildren(int? hashCode, _RunChild runChild) { RenderBox child = runChild.child; - // Total main size of previous siblings + // Total main size of previous siblings. double preSiblingsMainSize = 0; for (RenderBox sibling in runChildrenList) { preSiblingsMainSize += @@ -1756,7 +1562,7 @@ class RenderFlexLayout extends RenderLayoutBox { RenderStyle childRenderStyle = child.renderStyle; CSSOverflowType overflowX = childRenderStyle.effectiveOverflowX; CSSOverflowType overflowY = childRenderStyle.effectiveOverflowY; - // Only non scroll container need to use scrollable size, otherwise use its own size + // Only non scroll container need to use scrollable size, otherwise use its own size. if (overflowX == CSSOverflowType.visible && overflowY == CSSOverflowType.visible) { childScrollableSize = child.scrollableSize; @@ -1777,13 +1583,13 @@ class RenderFlexLayout extends RenderLayoutBox { runChildren.forEach(iterateRunChildren); - // Max scrollable main size of all the children in the line + // Max scrollable main size of all the children in the line. double maxScrollableMainSizeOfLine = scrollableMainSizeOfChildren.reduce((double curr, double next) { return curr > next ? curr : next; }); - // Max scrollable cross size of all the children in the line + // Max scrollable cross size of all the children in the line. double maxScrollableCrossSizeOfLine = preLinesCrossSize + scrollableCrossSizeOfChildren.reduce((double curr, double next) { return curr > next ? curr : next; @@ -1794,7 +1600,7 @@ class RenderFlexLayout extends RenderLayoutBox { preLinesCrossSize += runMetric.crossAxisExtent; } - // Max scrollable main size of all lines + // Max scrollable main size of all lines. double maxScrollableMainSizeOfLines = scrollableMainSizeOfLines.reduce((double curr, double next) { return curr > next ? curr : next; @@ -1806,7 +1612,7 @@ class RenderFlexLayout extends RenderLayoutBox { renderStyle.effectiveOverflowX != CSSOverflowType.visible || renderStyle.effectiveOverflowY != CSSOverflowType.visible; - // No need to add padding for scrolling content box + // No need to add padding for scrolling content box. double maxScrollableMainSizeOfChildren = isScrollContainer ? maxScrollableMainSizeOfLines : (_isHorizontalFlexDirection @@ -1814,13 +1620,13 @@ class RenderFlexLayout extends RenderLayoutBox { : container.renderStyle.paddingTop.computedValue) + maxScrollableMainSizeOfLines; - // Max scrollable cross size of all lines + // Max scrollable cross size of all lines. double maxScrollableCrossSizeOfLines = scrollableCrossSizeOfLines.reduce((double curr, double next) { return curr > next ? curr : next; }); - // No need to add padding for scrolling content box + // No need to add padding for scrolling content box. double maxScrollableCrossSizeOfChildren = isScrollContainer ? maxScrollableCrossSizeOfLines : (_isHorizontalFlexDirection @@ -1846,19 +1652,22 @@ class RenderFlexLayout extends RenderLayoutBox { : Size(maxScrollableCrossSize, maxScrollableMainSize); } - /// Get flex line height according to flex-wrap style - double _getFlexLineHeight(double runCrossAxisExtent, double runBetweenSpace, - {bool beforeSetSize = true}) { - // Flex line of align-content stretch should includes between space + // Get flex line height according to flex-wrap style. + double _getFlexLineHeight( + double runCrossAxisExtent, + double runBetweenSpace, + { bool beforeSetSize = true } + ) { + // Flex line of align-content stretch should includes between space. bool isMultiLineStretch = (renderStyle.flexWrap == FlexWrap.wrap || renderStyle.flexWrap == FlexWrap.wrapReverse) && renderStyle.alignContent == AlignContent.stretch; - // The height of flex line in single line equals to flex container's cross size + // The height of flex line in single line equals to flex container's cross size. bool isSingleLine = (renderStyle.flexWrap != FlexWrap.wrap && renderStyle.flexWrap != FlexWrap.wrapReverse); if (isSingleLine) { - // Use content size if container size is not set yet + // Use content size if container size is not set yet. return beforeSetSize ? runCrossAxisExtent : _getContentCrossSize(); } else if (isMultiLineStretch) { return runCrossAxisExtent + runBetweenSpace; @@ -1867,15 +1676,16 @@ class RenderFlexLayout extends RenderLayoutBox { } } - // Set flex item offset based on flex alignment properties - void _alignChildren( - List<_RunMetrics> runMetrics, - double runBetweenSpace, - double runLeadingSpace, - RenderPositionPlaceholder? placeholderChild, + // Set children offset based on alignment properties. + void _setChildrenOffset( + List<_RunMetrics> _runMetrics, + Map _runSpacingMap, ) { - RenderBox? child = placeholderChild ?? firstChild; - // Cross axis offset of each flex line + if (_runMetrics.isEmpty) return; + + double runLeadingSpace = _runSpacingMap['leading']!; + double runBetweenSpace = _runSpacingMap['between']!; + // Cross axis offset of each flex line. double crossAxisOffset = runLeadingSpace; double mainAxisContentSize; double crossAxisContentSize; @@ -1888,23 +1698,23 @@ class RenderFlexLayout extends RenderLayoutBox { crossAxisContentSize = contentSize.width; } - /// Set offset of children - for (int i = 0; i < runMetrics.length; ++i) { - final _RunMetrics metrics = runMetrics[i]; + // Set offset of children in each flex line. + for (int i = 0; i < _runMetrics.length; ++i) { + final _RunMetrics metrics = _runMetrics[i]; final double runMainAxisExtent = metrics.mainAxisExtent; final double runCrossAxisExtent = metrics.crossAxisExtent; final double runBaselineExtent = metrics.baselineExtent; final double totalFlexGrow = metrics.totalFlexGrow; final double totalFlexShrink = metrics.totalFlexShrink; final Map runChildren = metrics.runChildren; + final List<_RunChild> runChildrenList = runChildren.values.toList(); final double mainContentSizeDelta = mainAxisContentSize - runMainAxisExtent; bool isFlexGrow = mainContentSizeDelta > 0 && totalFlexGrow > 0; bool isFlexShrink = mainContentSizeDelta < 0 && totalFlexShrink > 0; - _overflow = math.max(0.0, -mainContentSizeDelta); - // If flex grow or flex shrink exists, remaining space should be zero + // If flex grow or flex shrink exists, remaining space should be zero. final double remainingSpace = (isFlexGrow || isFlexShrink) ? 0 : mainContentSizeDelta; late double leadingSpace; @@ -1967,19 +1777,14 @@ class RenderFlexLayout extends RenderLayoutBox { default: } - // Calculate margin auto children in the main axis + // Calculate margin auto children in the main axis. double mainAxisMarginAutoChildrenCount = 0; - RenderBox? runChild = firstChild; - - while (runChild != null) { - final RenderLayoutParentData childParentData = - runChild.parentData as RenderLayoutParentData; - if (childParentData.runIndex != i) break; - if (isChildMainAxisMarginAutoExist(runChild)) { + for (_RunChild runChild in runChildrenList) { + RenderBox child = runChild.child; + if (isChildMainAxisMarginAutoExist(child)) { mainAxisMarginAutoChildrenCount++; } - runChild = childParentData.nextSibling; } // Justify-content has no effect if auto margin of child exists in the main axis. @@ -1994,7 +1799,7 @@ class RenderFlexLayout extends RenderLayoutBox { double mainAxisStartBorder = flowAwareMainAxisBorder(); double crossAxisStartBorder = flowAwareCrossAxisBorder(); - // Main axis position of child while layout + // Main axis position of child on layout. double childMainPosition = flipMainAxis ? mainAxisStartPadding + mainAxisStartBorder + @@ -2002,21 +1807,10 @@ class RenderFlexLayout extends RenderLayoutBox { leadingSpace : leadingSpace + mainAxisStartPadding + mainAxisStartBorder; - while (child != null) { - final RenderLayoutParentData? childParentData = - child.parentData as RenderLayoutParentData?; - // Exclude positioned placeholder renderObject when layout non placeholder object - // and positioned renderObject - if (placeholderChild == null && - (isPlaceholderPositioned(child) || childParentData!.isPositioned)) { - child = childParentData!.nextSibling; - continue; - } - - if (childParentData!.runIndex != i) break; - + for (_RunChild runChild in runChildrenList) { + RenderBox child = runChild.child; double childMainAxisMargin = flowAwareChildMainAxisMargin(child)!; - // Add start margin of main axis when setting offset + // Add start margin of main axis when setting offset. childMainPosition += childMainAxisMargin; double? childCrossPosition; AlignSelf alignSelf = _getAlignSelf(child); @@ -2054,7 +1848,7 @@ class RenderFlexLayout extends RenderLayoutBox { alignment = 'center'; break; case AlignItems.baseline: - // FIXME: baseline alignment in wrap-reverse flexWrap may display different from browser in some case + // FIXME: baseline alignment in wrap-reverse flexWrap may display different from browser in some case if (_isHorizontalFlexDirection) { alignment = 'baseline'; } else if (renderStyle.flexWrap == FlexWrap.wrapReverse) { @@ -2081,7 +1875,7 @@ class RenderFlexLayout extends RenderLayoutBox { // Calculate margin auto length according to CSS spec rules // https://www.w3.org/TR/css-flexbox-1/#auto-margins // margin auto takes up available space in the remaining space - // between flex items and flex container + // between flex items and flex container. if (child is RenderBoxModel) { RenderStyle childRenderStyle = child.renderStyle; CSSLengthValue marginLeft = childRenderStyle.marginLeft; @@ -2091,10 +1885,10 @@ class RenderFlexLayout extends RenderLayoutBox { double horizontalRemainingSpace; double verticalRemainingSpace; - // Margin auto does not work with negative remaining space + // Margin auto does not work with negative remaining space. double mainAxisRemainingSpace = math.max(0, remainingSpace); double crossAxisRemainingSpace = - math.max(0, crossAxisContentSize - _getCrossAxisExtent(child)); + math.max(0, crossAxisContentSize - _getCrossAxisExtent(child)); if (_isHorizontalFlexDirection) { horizontalRemainingSpace = mainAxisRemainingSpace; @@ -2102,12 +1896,12 @@ class RenderFlexLayout extends RenderLayoutBox { if (totalFlexGrow == 0 && marginLeft.isAuto) { if (marginRight.isAuto) { childMainPosition += - (horizontalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; + (horizontalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; betweenSpace = - (horizontalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; + (horizontalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; } else { childMainPosition += - horizontalRemainingSpace / mainAxisMarginAutoChildrenCount; + horizontalRemainingSpace / mainAxisMarginAutoChildrenCount; } } @@ -2124,12 +1918,12 @@ class RenderFlexLayout extends RenderLayoutBox { if (totalFlexGrow == 0 && marginTop.isAuto) { if (marginBottom.isAuto) { childMainPosition += - (verticalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; + (verticalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; betweenSpace = - (verticalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; + (verticalRemainingSpace / mainAxisMarginAutoChildrenCount) / 2; } else { childMainPosition += - verticalRemainingSpace / mainAxisMarginAutoChildrenCount; + verticalRemainingSpace / mainAxisMarginAutoChildrenCount; } } @@ -2148,33 +1942,53 @@ class RenderFlexLayout extends RenderLayoutBox { double crossOffset; if (renderStyle.flexWrap == FlexWrap.wrapReverse) { crossOffset = childCrossPosition! + - (crossAxisContentSize - - crossAxisOffset - - runCrossAxisExtent - - runBetweenSpace); + (crossAxisContentSize - + crossAxisOffset - + runCrossAxisExtent - + runBetweenSpace); } else { crossOffset = childCrossPosition! + crossAxisOffset; } Offset relativeOffset = _getOffset(childMainPosition, crossOffset); - // Apply position relative offset change + // Apply position relative offset change. CSSPositionedLayout.applyRelativeOffset(relativeOffset, child); - // Need to substract start margin of main axis when calculating next child's start position + // Need to subtract start margin of main axis when calculating next child's start position. if (flipMainAxis) { childMainPosition -= betweenSpace + childMainAxisMargin; } else { childMainPosition += - _getMainAxisExtent(child) - childMainAxisMargin + betweenSpace; + _getMainAxisExtent(child) - childMainAxisMargin + betweenSpace; } - // Only layout placeholder renderObject child - child = placeholderChild == null ? childParentData.nextSibling : null; } crossAxisOffset += runCrossAxisExtent + runBetweenSpace; } } + // Whether need to stretch child in the cross axis according to alignment property and child cross length. + bool needToStretchChildCrossSize(RenderBox child) { + // Position placeholder and BR element has size of zero, so they can not be stretched. + if (child is RenderPositionPlaceholder || child is RenderLineBreak) return false; + + AlignSelf alignSelf = _getAlignSelf(child); + bool isChildAlignmentStretch = alignSelf != AlignSelf.auto + ? alignSelf == AlignSelf.stretch + : renderStyle.effectiveAlignItems == AlignItems.stretch; + + if (!isChildAlignmentStretch) return false; + + // If child length is auto in cross axis, stretch does not work. + if (child is RenderBoxModel) { + bool isLengthAuto = _isHorizontalFlexDirection + ? child.renderStyle.height.isAuto + : child.renderStyle.width.isAuto; + return isLengthAuto; + } + return false; + } + // Whether margin auto of child is set in the main axis. bool isChildMainAxisMarginAutoExist(RenderBox child) { if (child is RenderBoxModel) { @@ -2220,7 +2034,7 @@ class RenderFlexLayout extends RenderLayoutBox { double crossAxisStartPadding, double crossAxisStartBorder, ) { - // Leading between height of line box's content area and line height of line box + // Leading between height of line box's content area and line height of line box. double lineBoxLeading = 0; double? lineBoxHeight = _getLineHeight(this); if (lineBoxHeight != null) { @@ -2246,7 +2060,7 @@ class RenderFlexLayout extends RenderLayoutBox { case 'start': return crossStartAddedOffset; case 'end': - // Length returned by _getCrossAxisExtent includes margin, so end alignment should add start margin + // Length returned by _getCrossAxisExtent includes margin, so end alignment should add start margin. return crossAxisStartPadding + crossAxisStartBorder + flexLineHeight - @@ -2256,7 +2070,7 @@ class RenderFlexLayout extends RenderLayoutBox { return childCrossPosition = crossStartAddedOffset + (flexLineHeight - _getCrossAxisExtent(child)) / 2.0; case 'baseline': - // Distance from top to baseline of child + // Distance from top to baseline of child. double childAscent = _getChildAscent(child); return crossStartAddedOffset + lineBoxLeading / 2 + @@ -2266,7 +2080,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Compute distance to baseline of flex layout + // Compute distance to baseline of flex layout. @override double? computeDistanceToBaseline() { double lineDistance = 0; @@ -2277,7 +2091,7 @@ class RenderFlexLayout extends RenderLayoutBox { bool isDisplayInline = effectiveDisplay != CSSDisplay.block && effectiveDisplay != CSSDisplay.flex; // Use margin bottom as baseline if layout has no children - if (flexLineBoxMetrics.isEmpty) { + if (_flexLineBoxMetrics.isEmpty) { if (isDisplayInline) { // Flex item baseline does not includes margin-bottom lineDistance = isParentFlowLayout @@ -2289,8 +2103,8 @@ class RenderFlexLayout extends RenderLayoutBox { } } - // Always use the baseline of the first child as the baseline in flex layout - _RunMetrics firstLineMetrics = flexLineBoxMetrics[0]; + // Always use the baseline of the first child as the baseline in flex layout. + _RunMetrics firstLineMetrics = _flexLineBoxMetrics[0]; List<_RunChild> firstRunChildren = firstLineMetrics.runChildren.values.toList(); _RunChild firstRunChild = firstRunChildren[0]; @@ -2307,8 +2121,8 @@ class RenderFlexLayout extends RenderLayoutBox { childBaseLineDistance = child.computeDistanceToFirstLineBaseline(); } - // Baseline of relative positioned element equals its originial position - // so it needs to substract its vertical offset + // Baseline of relative positioned element equals its original position + // so it needs to subtract its vertical offset. Offset? relativeOffset; double childOffsetY = childParentData.offset.dy - childMarginTop; if (child is RenderBoxModel) { @@ -2319,13 +2133,13 @@ class RenderFlexLayout extends RenderLayoutBox { childOffsetY -= relativeOffset.dy; } - // It needs to subtract margin-top cause offset already includes margin-top + // It needs to subtract margin-top cause offset already includes margin-top. lineDistance = (childBaseLineDistance ?? 0) + childOffsetY; lineDistance += marginTop; return lineDistance; } - /// Get child size through boxSize to avoid flutter error when parentUsesSize is set to false + // Get child size through boxSize to avoid flutter error when parentUsesSize is set to false. Size? _getChildSize(RenderBox? child, {bool shouldUseIntrinsicMainSize = false}) { Size? childSize; @@ -2335,10 +2149,14 @@ class RenderFlexLayout extends RenderLayoutBox { childSize = child.boxSize; } else if (child is RenderTextBox) { childSize = child.boxSize; + } else if (child != null && child.hasSize) { + // child is WidgetElement. + childSize = child.size; } + if (shouldUseIntrinsicMainSize) { double? childIntrinsicMainSize = - childrenIntrinsicMainSizes[child.hashCode]; + _childrenIntrinsicMainSizes[child.hashCode]; if (_isHorizontalFlexDirection) { childSize = Size(childIntrinsicMainSize!, childSize!.height); } else { @@ -2348,9 +2166,9 @@ class RenderFlexLayout extends RenderLayoutBox { return childSize; } - // Get distance from top to baseline of child incluing margin + // Get distance from top to baseline of child including margin. double _getChildAscent(RenderBox child) { - // Distance from top to baseline of child + // Distance from top to baseline of child. double? childAscent = child.getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); double? childMarginTop = 0; @@ -2365,7 +2183,7 @@ class RenderFlexLayout extends RenderLayoutBox { double baseline = parent is RenderFlowLayout ? childMarginTop + childSize!.height + childMarginBottom : childMarginTop + childSize!.height; - // When baseline of children not found, use boundary of margin bottom as baseline + // When baseline of children not found, use boundary of margin bottom as baseline. double extentAboveBaseline = childAscent ?? baseline; return extentAboveBaseline; @@ -2379,7 +2197,7 @@ class RenderFlexLayout extends RenderLayoutBox { } } - /// Get cross size of content size + // Get cross size of content size. double _getContentCrossSize() { if (_isHorizontalFlexDirection) { return contentSize.height; @@ -2413,16 +2231,16 @@ class RenderFlexLayout extends RenderLayoutBox { void performPaint(PaintingContext context, Offset offset) { for (int i = 0; i < paintingOrder.length; i++) { RenderObject child = paintingOrder[i]; - // Don't paint placeholder of positioned element + // Don't paint placeholder of positioned element. if (child is! RenderPositionPlaceholder) { late DateTime childPaintStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childPaintStart = DateTime.now(); } final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; context.paintChild(child, childParentData.offset + offset); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childPaintEnd = DateTime.now(); childPaintDuration += (childPaintEnd.microsecondsSinceEpoch - childPaintStart.microsecondsSinceEpoch); @@ -2431,17 +2249,6 @@ class RenderFlexLayout extends RenderLayoutBox { } } - @override - Rect? describeApproximatePaintClip(RenderObject child) => - _hasOverflow ? Offset.zero & size : null; - - @override - String toStringShort() { - String header = super.toStringShort(); - if (_overflow is double && _hasOverflow) header += ' OVERFLOWING'; - return header; - } - @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -2454,13 +2261,35 @@ class RenderFlexLayout extends RenderLayoutBox { properties .add(DiagnosticsProperty('flexWrap', renderStyle.flexWrap)); } + + static bool _isPlaceholderPositioned(RenderObject child) { + if (child is RenderPositionPlaceholder) { + RenderBoxModel realDisplayedBox = child.positioned!; + RenderLayoutParentData parentData = realDisplayedBox.parentData as RenderLayoutParentData; + if (parentData.isPositioned) { + return true; + } + } + return false; + } + + static bool? _startIsTopLeft(FlexDirection direction) { + switch (direction) { + case FlexDirection.column: + case FlexDirection.row: + return true; + case FlexDirection.rowReverse: + case FlexDirection.columnReverse: + return false; + } + } } // Render flex layout with self repaint boundary. class RenderRepaintBoundaryFlexLayout extends RenderFlexLayout { RenderRepaintBoundaryFlexLayout({ List? children, - required RenderStyle renderStyle, + required CSSRenderStyle renderStyle, }) : super( children: children, renderStyle: renderStyle, diff --git a/kraken/lib/src/rendering/flow.dart b/kraken/lib/src/rendering/flow.dart index d4c86eb323..c5e2e7aaf8 100644 --- a/kraken/lib/src/rendering/flow.dart +++ b/kraken/lib/src/rendering/flow.dart @@ -10,8 +10,8 @@ import 'package:kraken/css.dart'; import 'package:kraken/module.dart'; import 'package:kraken/rendering.dart'; -/// Infos of each run (line box) in flow layout -/// https://www.w3.org/TR/css-inline-3/#line-boxes +// Position and size of each run (line box) in flow layout. +// https://www.w3.org/TR/css-inline-3/#line-boxes class _RunMetrics { _RunMetrics( this.mainAxisExtent, @@ -20,166 +20,51 @@ class _RunMetrics { this.runChildren, ); - // Main size extent of the run + // Main size extent of the run. final double mainAxisExtent; - // Cross size extent of the run + // Cross size extent of the run. final double crossAxisExtent; - // Max extent above each flex items in the run + // Max extent above each flex items in the run. final double baselineExtent; - // All the children RenderBox of layout in the run + // All the children RenderBox of layout in the run. final Map runChildren; } -/// Impl flow layout algorithm. +/// ## Layout algorithm +/// +/// _This section describes how the framework causes [RenderFlowLayout] to position +/// its children._ +/// +/// Layout for a [RenderFlowLayout] proceeds in 5 steps: +/// +/// 1. Layout positioned (eg. absolute/fixed) child first cause the size of position placeholder renderObject which is +/// layouted later depends on the size of its original RenderBoxModel. +/// 2. Layout children (not including positioned child) with no constraints and compute information of line boxes. +/// 3. Set container size depends on children size and container size styles (eg. width/height). +/// 4. Set children offset based on flow container size and flow alignment styles (eg. text-align). +/// 5. Set positioned children offset based on flow container size and its offset styles (eg. top/right/bottom/right). +/// class RenderFlowLayout extends RenderLayoutBox { RenderFlowLayout({ List? children, - required RenderStyle renderStyle, - }) : super( - renderStyle: renderStyle, - ) { + required CSSRenderStyle renderStyle, + }) : super(renderStyle: renderStyle) { addAll(children); } - /// The direction to use as the main axis. - /// - /// For example, if [direction] is [Axis.horizontal], the default, the - /// children are placed adjacent to one another in a horizontal run until the - /// available horizontal space is consumed, at which point a subsequent - /// children are placed in a new run vertically adjacent to the previous run. - Axis get direction => _direction; - Axis _direction = Axis.horizontal; - - set direction(Axis value) { - if (_direction == value) return; - _direction = value; - markNeedsLayout(); - } - - /// How the runs themselves should be placed in the cross axis. - /// - /// For example, if [runAlignment] is [MainAxisAlignment.center], the runs are - /// grouped together in the center of the overall [RenderWrap] in the cross - /// axis. - /// - /// Defaults to [MainAxisAlignment.start]. - /// - MainAxisAlignment get runAlignment => _runAlignment; - MainAxisAlignment _runAlignment = MainAxisAlignment.start; - - set runAlignment(MainAxisAlignment value) { - if (_runAlignment == value) return; - _runAlignment = value; - markNeedsLayout(); - } - - /// If there is additional free space in the overall [RenderWrap] (e.g., - /// The distance by which the child's top edge is inset from the top of the stack. - double? top; - - /// The distance by which the child's right edge is inset from the right of the stack. - double? right; - - /// The distance by which the child's bottom edge is inset from the bottom of the stack. - double? bottom; - - /// The distance by which the child's left edge is inset from the left of the stack. - double? left; - - /// How the children within a run should be aligned relative to each other in - /// the cross axis. - /// - /// For example, if this is set to [CrossAxisAlignment.end], and the - /// [direction] is [Axis.horizontal], then the children within each - /// run will have their bottom edges aligned to the bottom edge of the run. - /// - /// Defaults to [CrossAxisAlignment.end]. - /// - CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment; - CrossAxisAlignment _crossAxisAlignment = CrossAxisAlignment.end; - - set crossAxisAlignment(CrossAxisAlignment value) { - if (_crossAxisAlignment == value) return; - _crossAxisAlignment = value; - markNeedsLayout(); - } - - /// Determines the order to lay children out horizontally and how to interpret - /// `start` and `end` in the horizontal direction. - /// - /// If the [direction] is [Axis.horizontal], this controls the order in which - /// children are positioned (left-to-right or right-to-left), and the meaning - /// of the textAlign style's [TextAlign.start] and - /// [TextAlign.end] values. - /// - /// If the [direction] is [Axis.horizontal], and either the - /// textAlign style is either [TextAlign.start] or [TextAlign.end], or - /// there's more than one child, then the [textDirection] must not be null. - /// - /// If the [direction] is [Axis.vertical], this controls the order in - /// which runs are positioned, the meaning of the [runAlignment] property's - /// [TextAlign.start] and [TextAlign.end] values, as well as the - /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and - /// [CrossAxisAlignment.end] values. - /// - /// If the [direction] is [Axis.vertical], and either the - /// [runAlignment] is either [MainAxisAlignment.start] or [MainAxisAlignment.end], the - /// [crossAxisAlignment] is either [CrossAxisAlignment.start] or - /// [CrossAxisAlignment.end], or there's more than one child, then the - /// [textDirection] must not be null. - TextDirection get textDirection => _textDirection; - TextDirection _textDirection = TextDirection.ltr; - - set textDirection(TextDirection value) { - if (_textDirection != value) { - _textDirection = value; - markNeedsLayout(); - } - } - - /// Determines the order to lay children out vertically and how to interpret - /// `start` and `end` in the vertical direction. - /// - /// If the [direction] is [Axis.vertical], this controls which order children - /// are painted in (down or up), the meaning of the textAlign style's - /// [TextAlign.start] and [TextAlign.end] values. - /// - /// If the [direction] is [Axis.vertical], and either the textAlign - /// is either [TextAlign.start] or [TextAlign.end], or there's - /// more than one child, then the [verticalDirection] must not be null. - /// - /// If the [direction] is [Axis.horizontal], this controls the order in which - /// runs are positioned, the meaning of the [runAlignment] property's - /// [MainAxisAlignment.start] and [MainAxisAlignment.end] values, as well as the - /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and - /// [CrossAxisAlignment.end] values. - /// - /// If the [direction] is [Axis.horizontal], and either the - /// [runAlignment] is either [MainAxisAlignment.start] or [MainAxisAlignment.end], the - /// [crossAxisAlignment] is either [CrossAxisAlignment.start] or - /// [CrossAxisAlignment.end], or there's more than one child, then the - /// [verticalDirection] must not be null. - VerticalDirection get verticalDirection => _verticalDirection; - VerticalDirection _verticalDirection = VerticalDirection.down; - - set verticalDirection(VerticalDirection value) { - if (_verticalDirection != value) { - _verticalDirection = value; - markNeedsLayout(); - } - } - - /// Line boxes of flow layout - List<_RunMetrics> lineBoxMetrics = <_RunMetrics>[]; + // Line boxes of flow layout. + // https://www.w3.org/TR/css-inline-3/#line-boxes + List<_RunMetrics> _lineBoxMetrics = <_RunMetrics>[]; @override void dispose() { super.dispose(); - lineBoxMetrics.clear(); + // Do not forget to clear reference variables, or it will cause memory leaks! + _lineBoxMetrics.clear(); } @override @@ -193,189 +78,43 @@ class RenderFlowLayout extends RenderLayoutBox { } } - double _computeIntrinsicHeightForWidth(double width) { - assert(direction == Axis.horizontal); - double height = 0.0; - double runWidth = 0.0; - double runHeight = 0.0; - int childCount = 0; - RenderBox? child = firstChild; - while (child != null) { - final double childWidth = child.getMaxIntrinsicWidth(double.infinity); - final double childHeight = child.getMaxIntrinsicHeight(childWidth); - if (runWidth + childWidth > width) { - height += runHeight; - runWidth = 0.0; - runHeight = 0.0; - childCount = 0; - } - runWidth += childWidth; - runHeight = math.max(runHeight, childHeight); - childCount += 1; - child = childAfter(child); - } - if (childCount > 0) height += runHeight; - return height; - } - - double _computeIntrinsicWidthForHeight(double height) { - assert(direction == Axis.vertical); - double width = 0.0; - double runHeight = 0.0; - double runWidth = 0.0; - int childCount = 0; - RenderBox? child = firstChild; - while (child != null) { - final double childHeight = child.getMaxIntrinsicHeight(double.infinity); - final double childWidth = child.getMaxIntrinsicWidth(childHeight); - if (runHeight + childHeight > height) { - width += runWidth; - runHeight = 0.0; - runWidth = 0.0; - childCount = 0; - } - runHeight += childHeight; - runWidth = math.max(runWidth, childWidth); - childCount += 1; - child = childAfter(child); - } - if (childCount > 0) width += runWidth; - return width; - } - - @override - double computeMinIntrinsicWidth(double height) { - switch (direction) { - case Axis.horizontal: - double width = 0.0; - RenderBox? child = firstChild; - while (child != null) { - width = math.max(width, child.getMinIntrinsicWidth(double.infinity)); - child = childAfter(child); - } - return width; - case Axis.vertical: - return _computeIntrinsicWidthForHeight(height); - } - } - - @override - double computeMaxIntrinsicWidth(double height) { - switch (direction) { - case Axis.horizontal: - double width = 0.0; - RenderBox? child = firstChild; - while (child != null) { - width += child.getMaxIntrinsicWidth(double.infinity); - child = childAfter(child); - } - return width; - case Axis.vertical: - return _computeIntrinsicWidthForHeight(height); - } - } - - @override - double computeMinIntrinsicHeight(double width) { - switch (direction) { - case Axis.horizontal: - return _computeIntrinsicHeightForWidth(width); - case Axis.vertical: - double height = 0.0; - RenderBox? child = firstChild; - while (child != null) { - height = - math.max(height, child.getMinIntrinsicHeight(double.infinity)); - child = childAfter(child); - } - return height; - } - } - - /// Get current offset. - Offset get offset => (parentData as BoxParentData).offset; - - @override - double computeMaxIntrinsicHeight(double width) { - switch (direction) { - case Axis.horizontal: - return _computeIntrinsicHeightForWidth(width); - case Axis.vertical: - double height = 0.0; - RenderBox? child = firstChild; - while (child != null) { - height += child.getMaxIntrinsicHeight(double.infinity); - child = childAfter(child); - } - return height; - } - } - double _getMainAxisExtent(RenderBox child) { double marginHorizontal = 0; - double marginVertical = 0; if (child is RenderBoxModel) { marginHorizontal = child.renderStyle.marginLeft.computedValue + child.renderStyle.marginRight.computedValue; - marginVertical = _getChildMarginTop(child) + _getChildMarginBottom(child); } Size childSize = _getChildSize(child) ?? Size.zero; - switch (direction) { - case Axis.horizontal: - return childSize.width + marginHorizontal; - case Axis.vertical: - return childSize.height + marginVertical; - } + + return childSize.width + marginHorizontal; } double _getCrossAxisExtent(RenderBox child) { bool isLineHeightValid = _isLineHeightValid(child); double? lineHeight = isLineHeightValid ? _getLineHeight(child) : 0; double marginVertical = 0; - double marginHorizontal = 0; if (child is RenderBoxModel) { - marginHorizontal = child.renderStyle.marginLeft.computedValue + - child.renderStyle.marginRight.computedValue; marginVertical = _getChildMarginTop(child) + _getChildMarginBottom(child); } Size childSize = _getChildSize(child) ?? Size.zero; - switch (direction) { - case Axis.horizontal: - return lineHeight != null - ? math.max(lineHeight, childSize.height) + marginVertical - : childSize.height + marginVertical; - case Axis.vertical: - return childSize.width + marginHorizontal; - } + + return lineHeight != null + ? math.max(lineHeight, childSize.height) + marginVertical + : childSize.height + marginVertical; } Offset _getOffset(double mainAxisOffset, double crossAxisOffset) { - switch (direction) { - case Axis.horizontal: - return Offset(mainAxisOffset, crossAxisOffset); - case Axis.vertical: - return Offset(crossAxisOffset, mainAxisOffset); - } + return Offset(mainAxisOffset, crossAxisOffset); } - double _getChildCrossAxisOffset(bool flipCrossAxis, double runCrossAxisExtent, - double childCrossAxisExtent) { - final double freeSpace = runCrossAxisExtent - childCrossAxisExtent; - switch (crossAxisAlignment) { - case CrossAxisAlignment.start: - return flipCrossAxis ? freeSpace : 0.0; - case CrossAxisAlignment.end: - return flipCrossAxis ? 0.0 : freeSpace; - case CrossAxisAlignment.center: - return freeSpace / 2.0; - case CrossAxisAlignment.baseline: - return 0.0; - case CrossAxisAlignment.stretch: - return 0.0; - } + double _getChildCrossAxisOffset( + double runCrossAxisExtent, + double childCrossAxisExtent + ) { + return runCrossAxisExtent - childCrossAxisExtent; } double? _getLineHeight(RenderBox child) { @@ -396,7 +135,7 @@ class RenderFlowLayout extends RenderLayoutBox { @override void performLayout() { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutDuration = 0; PerformanceTiming.instance() .mark(PERF_FLOW_LAYOUT_START, uniqueId: hashCode); @@ -409,7 +148,7 @@ class RenderFlowLayout extends RenderLayoutBox { needsRelayout = false; } - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime flowLayoutEndTime = DateTime.now(); int amendEndTime = flowLayoutEndTime.microsecondsSinceEpoch - childLayoutDuration; @@ -421,53 +160,63 @@ class RenderFlowLayout extends RenderLayoutBox { void _doPerformLayout() { beforeLayout(); - RenderBox? child = firstChild; + List _positionedChildren = []; + List _nonPositionedChildren = []; + List _stickyChildren = []; - // Layout positioned element + // Prepare children of different type for layout. + RenderBox? child = firstChild; while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - if (childParentData.isPositioned) { - CSSPositionedLayout.layoutPositionedChild( - this, child as RenderBoxModel); + final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; + if (child is RenderBoxModel && childParentData.isPositioned) { + _positionedChildren.add(child); + } else { + _nonPositionedChildren.add(child); + if (child is RenderBoxModel && CSSPositionedLayout.isSticky(child)) { + _stickyChildren.add(child); + } } child = childParentData.nextSibling; } - // Layout non positioned element - _layoutChildren(); + // Need to layout out of flow positioned element before normal flow element + // cause the size of RenderPositionPlaceholder in flex layout needs to use + // the size of its original RenderBoxModel. + for (RenderBoxModel child in _positionedChildren) { + CSSPositionedLayout.layoutPositionedChild(this, child); + } - // Set offset of positioned and sticky element - child = firstChild; - while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; + // Layout non positioned element (include element in flow and + // placeholder of positioned element). + _layoutChildren(_nonPositionedChildren); - if (child is RenderBoxModel && childParentData.isPositioned) { - CSSPositionedLayout.applyPositionedChildOffset(this, child); - - extendMaxScrollableSize(child); - ensureBoxSizeLargerThanScrollableSize(); - } else if (child is RenderBoxModel && - CSSPositionedLayout.isSticky(child)) { - RenderBoxModel scrollContainer = child.findScrollContainer()!; - // Sticky offset depends on the layout of scroll container, delay the calculation of - // sticky offset to the layout stage of scroll container if its not layouted yet - // due to the layout order of Flutter renderObject tree is from down to up. - if (scrollContainer.hasSize) { - CSSPositionedLayout.applyStickyChildOffset(scrollContainer, child); - } + // Set offset of positioned element after flex box size is set. + for (RenderBoxModel child in _positionedChildren) { + CSSPositionedLayout.applyPositionedChildOffset(this, child); + // Position of positioned element affect the scroll size of container. + extendMaxScrollableSize(child); + ensureBoxSizeLargerThanScrollableSize(); + } + + // Set offset of sticky element on each layout. + for (RenderBoxModel child in _stickyChildren) { + RenderBoxModel scrollContainer = child.findScrollContainer()!; + // Sticky offset depends on the layout of scroll container, delay the calculation of + // sticky offset to the layout stage of scroll container if its not layouted yet + // due to the layout order of Flutter renderObject tree is from down to up. + if (scrollContainer.hasSize) { + CSSPositionedLayout.applyStickyChildOffset(scrollContainer, child); } - child = childParentData.nextSibling; } bool isScrollContainer = - (renderStyle.effectiveOverflowX != CSSOverflowType.visible || - renderStyle.effectiveOverflowY != CSSOverflowType.visible); + renderStyle.effectiveOverflowX != CSSOverflowType.visible + || renderStyle.effectiveOverflowY != CSSOverflowType.visible; + if (isScrollContainer) { - // Find all the sticky children when scroll container is layouted + // Find all the sticky children when scroll container is layouted. stickyChildren = findStickyChildren(); - // Calculate the offset of its sticky children + // Calculate the offset of its sticky children. for (RenderBoxModel stickyChild in stickyChildren) { CSSPositionedLayout.applyStickyChildOffset(this, stickyChild); } @@ -476,44 +225,36 @@ class RenderFlowLayout extends RenderLayoutBox { didLayout(); } - void _layoutChildren() { - RenderBox? child = firstChild; + // There are 3 steps for layout children. + // 1. Layout children to generate line boxes metrics. + // 2. Set flex container size according to children size and its own size styles. + // 3. Align children according to alignment properties. + void _layoutChildren(List children) { // If no child exists, stop layout. - if (childCount == 0) { - Size layoutContentSize = getContentSize( - logicalContentWidth: logicalContentWidth, - logicalContentHeight: logicalContentHeight, - contentWidth: 0, - contentHeight: 0, - ); - setMaxScrollableSize(layoutContentSize); - size = getBoxSize(layoutContentSize); + if (children.isEmpty) { + _setContainerSizeWithNoChild(); return; } - double mainAxisLimit = 0.0; - bool flipMainAxis = false; - bool flipCrossAxis = false; - - // Use scrolling container to calculate flex line limit for scrolling content box - RenderBoxModel? containerBox = - isScrollingContentBox ? parent as RenderBoxModel? : this; - switch (direction) { - case Axis.horizontal: - mainAxisLimit = renderStyle.contentMaxConstraintsWidth; - if (textDirection == TextDirection.rtl) flipMainAxis = true; - if (verticalDirection == VerticalDirection.up) flipCrossAxis = true; - break; - case Axis.vertical: - mainAxisLimit = containerBox!.contentConstraints!.maxHeight; - if (verticalDirection == VerticalDirection.up) flipMainAxis = true; - if (textDirection == TextDirection.rtl) flipCrossAxis = true; - break; - } - List<_RunMetrics> runMetrics = <_RunMetrics>[]; - double mainAxisExtent = 0.0; - double crossAxisExtent = 0.0; + // Layout children to compute metrics of lines. + List<_RunMetrics> _runMetrics = _computeRunMetrics(children); + + // Set container size. + _setContainerSize(_runMetrics); + + // Set children offset based on alignment properties. + _setChildrenOffset(_runMetrics); + } + + // Layout children in normal flow order to calculate metrics of lines according to its constraints + // and alignment properties. + List<_RunMetrics> _computeRunMetrics( + List children, + ) { + List<_RunMetrics> _runMetrics = <_RunMetrics>[]; + double mainAxisLimit = renderStyle.contentMaxConstraintsWidth; + double runMainAxisExtent = 0.0; double runCrossAxisExtent = 0.0; RenderBox? preChild; @@ -521,49 +262,39 @@ class RenderFlowLayout extends RenderLayoutBox { double maxSizeBelowBaseline = 0; Map runChildren = {}; - lineBoxMetrics = runMetrics; - WhiteSpace? whiteSpace = renderStyle.whiteSpace; - while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - - if (childParentData.isPositioned) { - child = childParentData.nextSibling; - continue; - } - - int? childNodeId; - if (child is RenderTextBox) { - childNodeId = child.hashCode; - } else if (child is RenderBoxModel) { - childNodeId = child.hashCode; - } + for (RenderBox child in children) { + final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; + int childNodeId = child.hashCode; BoxConstraints childConstraints; if (child is RenderBoxModel) { childConstraints = child.getConstraints(); } else if (child is RenderTextBox) { childConstraints = child.getConstraints(); - } else { + } else if (child is RenderPositionPlaceholder) { childConstraints = BoxConstraints(); + } else { + // RenderObject of custom element need to inherit constraints from its parents + // which adhere to flutter's rule. + childConstraints = constraints; } - // Whether child need to layout + // Whether child need to layout. bool isChildNeedsLayout = true; if (child.hasSize && - !needsRelayout && - (childConstraints == child.constraints) && - ((child is RenderBoxModel && !child.needsLayout) || - (child is RenderTextBox && !child.needsLayout))) { + !needsRelayout && + (childConstraints == child.constraints) && + ((child is RenderBoxModel && !child.needsLayout) || + (child is RenderTextBox && !child.needsLayout))) { isChildNeedsLayout = false; } if (isChildNeedsLayout) { late DateTime childLayoutStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutStart = DateTime.now(); } @@ -572,21 +303,21 @@ class RenderFlowLayout extends RenderLayoutBox { if (child is RenderBoxModel && needsRelayout) { childConstraints = BoxConstraints( minWidth: childConstraints.maxWidth != double.infinity - ? childConstraints.maxWidth - : 0, + ? childConstraints.maxWidth + : 0, maxWidth: double.infinity, minHeight: childConstraints.maxHeight != double.infinity - ? childConstraints.maxHeight - : 0, + ? childConstraints.maxHeight + : 0, maxHeight: double.infinity, ); } child.layout(childConstraints, parentUsesSize: true); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childLayoutEnd = DateTime.now(); childLayoutDuration += (childLayoutEnd.microsecondsSinceEpoch - - childLayoutStart.microsecondsSinceEpoch); + childLayoutStart.microsecondsSinceEpoch); } } @@ -598,28 +329,25 @@ class RenderFlowLayout extends RenderLayoutBox { RenderBoxModel? childRenderBoxModel = positionHolder.positioned; if (childRenderBoxModel != null) { RenderLayoutParentData childParentData = - childRenderBoxModel.parentData as RenderLayoutParentData; + childRenderBoxModel.parentData as RenderLayoutParentData; if (childParentData.isPositioned) { childMainAxisExtent = childCrossAxisExtent = 0; } } } - - // white-space property not only specifies whether and how white space is collapsed - // but only specifies whether lines may wrap at unforced soft wrap opportunities - // https://www.w3.org/TR/css-text-3/#line-breaking - bool isChildBlockLevel = _isChildBlockLevel(child); - bool isPreChildBlockLevel = _isChildBlockLevel(preChild); - bool isLineLengthExceedContainer = whiteSpace != WhiteSpace.nowrap && - (runMainAxisExtent + childMainAxisExtent > mainAxisLimit); - if (runChildren.isNotEmpty && - (isChildBlockLevel || - isPreChildBlockLevel || - isLineLengthExceedContainer)) { - mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent); - crossAxisExtent += runCrossAxisExtent; - runMetrics.add(_RunMetrics( + // Current is block. + (_isChildBlockLevel(child) || + // Previous is block. + _isChildBlockLevel(preChild) || + // Line length is exceed container. + // The white-space property not only specifies whether and how white space is collapsed + // but only specifies whether lines may wrap at unforced soft wrap opportunities + // https://www.w3.org/TR/css-text-3/#line-breaking + (whiteSpace != WhiteSpace.nowrap && (runMainAxisExtent + childMainAxisExtent > mainAxisLimit)) || + // Previous is linebreak. + preChild is RenderLineBreak)) { + _runMetrics.add(_RunMetrics( runMainAxisExtent, runCrossAxisExtent, maxSizeAboveBaseline, @@ -633,13 +361,16 @@ class RenderFlowLayout extends RenderLayoutBox { } runMainAxisExtent += childMainAxisExtent; - /// Calculate baseline extent of layout box - RenderStyle childRenderStyle = _getChildRenderStyle(child)!; - VerticalAlign verticalAlign = childRenderStyle.verticalAlign; + // Calculate baseline extent of layout box. + RenderStyle? childRenderStyle = _getChildRenderStyle(child); + VerticalAlign verticalAlign = VerticalAlign.baseline; + if (childRenderStyle != null) { + verticalAlign = childRenderStyle.verticalAlign; + } bool isLineHeightValid = _isLineHeightValid(child); - // Vertical align is only valid for inline box + // Vertical align is only valid for inline box. if (verticalAlign == VerticalAlign.baseline && isLineHeightValid) { double childMarginTop = 0; double childMarginBottom = 0; @@ -650,21 +381,21 @@ class RenderFlowLayout extends RenderLayoutBox { Size childSize = _getChildSize(child)!; double? lineHeight = _getLineHeight(child); - // Leading space between content box and virtual box of child + // Leading space between content box and virtual box of child. double childLeading = 0; if (child is! RenderTextBox && lineHeight != null) { childLeading = lineHeight - childSize.height; } - // When baseline of children not found, use boundary of margin bottom as baseline + // When baseline of children not found, use boundary of margin bottom as baseline. double childAscent = _getChildAscent(child); double extentAboveBaseline = childAscent + childLeading / 2; double extentBelowBaseline = childMarginTop + - childSize.height + - childMarginBottom - - childAscent + - childLeading / 2; + childSize.height + + childMarginBottom - + childAscent + + childLeading / 2; maxSizeAboveBaseline = math.max( extentAboveBaseline, @@ -675,21 +406,18 @@ class RenderFlowLayout extends RenderLayoutBox { extentBelowBaseline, maxSizeBelowBaseline, ); - runCrossAxisExtent = maxSizeAboveBaseline + maxSizeBelowBaseline; + runCrossAxisExtent = math.max(runCrossAxisExtent, maxSizeAboveBaseline + maxSizeBelowBaseline); } else { runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent); } runChildren[childNodeId] = child; - childParentData.runIndex = runMetrics.length; + childParentData.runIndex = _runMetrics.length; preChild = child; - child = childParentData.nextSibling; } if (runChildren.isNotEmpty) { - mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent); - crossAxisExtent += runCrossAxisExtent; - runMetrics.add(_RunMetrics( + _runMetrics.add(_RunMetrics( runMainAxisExtent, runCrossAxisExtent, maxSizeAboveBaseline, @@ -697,78 +425,91 @@ class RenderFlowLayout extends RenderLayoutBox { )); } - final int runCount = runMetrics.length; + _lineBoxMetrics = _runMetrics; - Size layoutContentSize = getContentSize( - logicalContentWidth: logicalContentWidth, - logicalContentHeight: logicalContentHeight, - contentWidth: mainAxisExtent, - contentHeight: crossAxisExtent, - ); + return _runMetrics; + } - // Main and cross content size of flow layout - double mainAxisContentSize = 0.0; - double crossAxisContentSize = 0.0; - - switch (direction) { - case Axis.horizontal: - size = getBoxSize(layoutContentSize); - mainAxisContentSize = contentSize.width; - crossAxisContentSize = contentSize.height; - break; - case Axis.vertical: - Size logicalSize = Size(crossAxisExtent, mainAxisExtent); - size = getBoxSize(logicalSize); - - mainAxisContentSize = contentSize.height; - crossAxisContentSize = contentSize.width; - break; + // Find the size in the cross axis of lines. + // @TODO: add cache to avoid recalculate in one layout stage. + double _getRunsCrossSize( + List<_RunMetrics> _runMetrics, + ) { + double crossSize = 0; + for (_RunMetrics run in _runMetrics) { + crossSize += run.crossAxisExtent; } + return crossSize; + } + + // Find the max size in the main axis of lines. + // @TODO: add cache to avoid recalculate in one layout stage. + double _getRunsMaxMainSize( + List<_RunMetrics> _runMetrics, + ) { + // Find the max size of lines. + _RunMetrics maxMainSizeMetrics = + _runMetrics.reduce((_RunMetrics curr, _RunMetrics next) { + return curr.mainAxisExtent > next.mainAxisExtent ? curr : next; + }); + return maxMainSizeMetrics.mainAxisExtent; + } - _setMaxScrollableSizeForFlow(runMetrics); - - autoMinWidth = _getMainAxisAutoSize(runMetrics); - autoMinHeight = _getCrossAxisAutoSize(runMetrics); - - final double crossAxisFreeSpace = - math.max(0.0, crossAxisContentSize - crossAxisExtent); - double runLeadingSpace = 0.0; - double runBetweenSpace = 0.0; - switch (runAlignment) { - case MainAxisAlignment.start: - break; - case MainAxisAlignment.end: - runLeadingSpace = crossAxisFreeSpace; - break; - case MainAxisAlignment.center: - runLeadingSpace = crossAxisFreeSpace / 2.0; - break; - case MainAxisAlignment.spaceBetween: - runBetweenSpace = - runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0; - break; - case MainAxisAlignment.spaceAround: - runBetweenSpace = crossAxisFreeSpace / runCount; - runLeadingSpace = runBetweenSpace / 2.0; - break; - case MainAxisAlignment.spaceEvenly: - runBetweenSpace = crossAxisFreeSpace / (runCount + 1); - runLeadingSpace = runBetweenSpace; - break; + // Set flex container size according to children size. + void _setContainerSize( + List<_RunMetrics> _runMetrics, + ) { + if (_runMetrics.isEmpty) { + _setContainerSizeWithNoChild(); + return; } - double crossAxisOffset = flipCrossAxis - ? crossAxisContentSize - runLeadingSpace - : runLeadingSpace; - child = firstChild; + double runMaxMainSize = _getRunsMaxMainSize(_runMetrics); + double runCrossSize = _getRunsCrossSize(_runMetrics); + + Size layoutContentSize = getContentSize( + contentWidth: runMaxMainSize, + contentHeight: runCrossSize, + ); + + size = getBoxSize(layoutContentSize); + + _setMaxScrollableSizeForFlow(_runMetrics); + + autoMinWidth = _getMainAxisAutoSize(_runMetrics); + autoMinHeight = _getCrossAxisAutoSize(_runMetrics); + } + + // Set size when layout has no child. + void _setContainerSizeWithNoChild() { + Size layoutContentSize = getContentSize( + contentWidth: 0, + contentHeight: 0, + ); + setMaxScrollableSize(layoutContentSize); + size = getBoxSize(layoutContentSize); + } + + // Set children offset based on alignment properties. + void _setChildrenOffset( + List<_RunMetrics> _runMetrics, + ) { + if (_runMetrics.isEmpty) return; + + double runLeadingSpace = 0; + double runBetweenSpace = 0; + // Cross axis offset of each flex line. + double crossAxisOffset = runLeadingSpace; + double mainAxisContentSize = contentSize.width; - /// Set offset of children - for (int i = 0; i < runCount; ++i) { - final _RunMetrics metrics = runMetrics[i]; + // Set offset of children in each line. + for (int i = 0; i < _runMetrics.length; ++i) { + final _RunMetrics metrics = _runMetrics[i]; final double runMainAxisExtent = metrics.mainAxisExtent; final double runCrossAxisExtent = metrics.crossAxisExtent; final double runBaselineExtent = metrics.baselineExtent; final Map runChildren = metrics.runChildren; + final List runChildrenList = runChildren.values.toList(); final int runChildrenCount = metrics.runChildren.length; final double mainAxisFreeSpace = math.max(0.0, mainAxisContentSize - runMainAxisExtent); @@ -787,7 +528,7 @@ class RenderFlowLayout extends RenderLayoutBox { runContainInlineChild = childEffectiveDisplay != CSSDisplay.block && childEffectiveDisplay != CSSDisplay.flex; } - // Text-align only works on inline level children + // Text-align only works on inline level children. if (runContainInlineChild) { switch (renderStyle.textAlign) { case TextAlign.left: @@ -808,21 +549,9 @@ class RenderFlowLayout extends RenderLayoutBox { } } - double childMainPosition = flipMainAxis - ? mainAxisContentSize - childLeadingSpace - : childLeadingSpace; - - if (flipCrossAxis) crossAxisOffset -= runCrossAxisExtent; + double childMainPosition = childLeadingSpace; - while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; - - if (childParentData.isPositioned) { - child = childParentData.nextSibling; - continue; - } - if (childParentData.runIndex != i) break; + for (RenderBox child in runChildrenList) { final double childMainAxisExtent = _getMainAxisExtent(child); final double childCrossAxisExtent = _getCrossAxisExtent(child); @@ -830,18 +559,18 @@ class RenderFlowLayout extends RenderLayoutBox { // https://www.w3.org/TR/CSS21/visudet.html#blockwidth // margin-left and margin-right auto takes up available space // between element and its containing block on block-level element - // which is not positioned and computed to 0px in other cases + // which is not positioned and computed to 0px in other cases. if (child is RenderBoxModel) { RenderStyle childRenderStyle = child.renderStyle; CSSDisplay? childEffectiveDisplay = - childRenderStyle.effectiveDisplay; + childRenderStyle.effectiveDisplay; CSSLengthValue marginLeft = childRenderStyle.marginLeft; CSSLengthValue marginRight = childRenderStyle.marginRight; // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + // 'border-right-width' + 'margin-right' = width of containing block if (childEffectiveDisplay == CSSDisplay.block || - childEffectiveDisplay == CSSDisplay.flex) { + childEffectiveDisplay == CSSDisplay.flex) { if (marginLeft.isAuto) { double remainingSpace = mainAxisContentSize - childMainAxisExtent; if (marginRight.isAuto) { @@ -856,31 +585,29 @@ class RenderFlowLayout extends RenderLayoutBox { // Always align to the top of run when positioning positioned element placeholder // @HACK(kraken): Judge positioned holder to impl top align. final double childCrossAxisOffset = isPositionPlaceholder(child) - ? 0 - : _getChildCrossAxisOffset( - flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent); - if (flipMainAxis) childMainPosition -= childMainAxisExtent; + ? 0 + : _getChildCrossAxisOffset(runCrossAxisExtent, childCrossAxisExtent); Size? childSize = _getChildSize(child); - // Line height of child + // Line height of child. double? childLineHeight = _getLineHeight(child); - // Leading space between content box and virtual box of child + // Leading space between content box and virtual box of child. double childLeading = 0; if (childLineHeight != null) { childLeading = childLineHeight - childSize!.height; } - // Child line extent caculated according to vertical align + // Child line extent calculated according to vertical align. double childLineExtent = childCrossAxisOffset; bool isLineHeightValid = _isLineHeightValid(child); if (isLineHeightValid) { - // Distance from top to baseline of child + // Distance from top to baseline of child. double childAscent = _getChildAscent(child); RenderStyle childRenderStyle = _getChildRenderStyle(child)!; VerticalAlign verticalAlign = childRenderStyle.verticalAlign; - // Leading between height of line box's content area and line height of line box + // Leading between height of line box's content area and line height of line box. double lineBoxLeading = 0; double? lineBoxHeight = _getLineHeight(this); if (child is! RenderTextBox && lineBoxHeight != null) { @@ -890,21 +617,21 @@ class RenderFlowLayout extends RenderLayoutBox { switch (verticalAlign) { case VerticalAlign.baseline: childLineExtent = - lineBoxLeading / 2 + (runBaselineExtent - childAscent); + lineBoxLeading / 2 + (runBaselineExtent - childAscent); break; case VerticalAlign.top: childLineExtent = childLeading / 2; break; case VerticalAlign.bottom: childLineExtent = - (lineBoxHeight ?? runCrossAxisExtent) - - childSize!.height - - childLeading / 2; + (lineBoxHeight ?? runCrossAxisExtent) - + childSize!.height - + childLeading / 2; break; - // @TODO Vertical align middle needs to caculate the baseline of the parent box plus half the x-height of the parent from W3C spec, - // currently flutter lack the api to caculate x-height of glyph - // case VerticalAlign.middle: - // break; + // @TODO: Vertical align middle needs to caculate the baseline of the parent box plus + // half the x-height of the parent from W3C spec currently flutter lack the api to calculate x-height of glyph. + // case VerticalAlign.middle: + // break; } } @@ -925,34 +652,26 @@ class RenderFlowLayout extends RenderLayoutBox { // No need to add padding and border for scrolling content box. Offset relativeOffset = _getOffset( - childMainPosition + - renderStyle.paddingLeft.computedValue + - renderStyle.effectiveBorderLeftWidth.computedValue + - childMarginLeft, - crossAxisOffset + - childLineExtent + - renderStyle.paddingTop.computedValue + - renderStyle.effectiveBorderTopWidth.computedValue + - childMarginTop); + childMainPosition + + renderStyle.paddingLeft.computedValue + + renderStyle.effectiveBorderLeftWidth.computedValue + + childMarginLeft, + crossAxisOffset + + childLineExtent + + renderStyle.paddingTop.computedValue + + renderStyle.effectiveBorderTopWidth.computedValue + + childMarginTop); // Apply position relative offset change. CSSPositionedLayout.applyRelativeOffset(relativeOffset, child); - if (flipMainAxis) - childMainPosition -= childBetweenSpace; - else - childMainPosition += childMainAxisExtent + childBetweenSpace; - - child = childParentData.nextSibling; + childMainPosition += childMainAxisExtent + childBetweenSpace; } - if (flipCrossAxis) - crossAxisOffset -= runBetweenSpace; - else - crossAxisOffset += runCrossAxisExtent + runBetweenSpace; + crossAxisOffset += runCrossAxisExtent + runBetweenSpace; } } - /// Compute distance to baseline of flow layout + // Compute distance to baseline of flow layout. @override double? computeDistanceToBaseline() { double? lineDistance; @@ -966,10 +685,10 @@ class RenderFlowLayout extends RenderLayoutBox { effectiveDisplay == CSSDisplay.inlineBlock || effectiveDisplay == CSSDisplay.inlineFlex; - // Use margin bottom as baseline if layout has no children - if (lineBoxMetrics.isEmpty) { + // Use margin bottom as baseline if layout has no children. + if (_lineBoxMetrics.isEmpty) { if (isDisplayInline) { - // Flex item baseline does not includes margin-bottom + // Flex item baseline does not includes margin-bottom. lineDistance = isParentFlowLayout ? marginTop + boxSize!.height + marginBottom : marginTop + boxSize!.height; @@ -980,12 +699,12 @@ class RenderFlowLayout extends RenderLayoutBox { } // Use baseline of last line in flow layout and layout is inline-level - // otherwise use baseline of first line + // otherwise use baseline of first line. bool isLastLineBaseline = isParentFlowLayout && isDisplayInline; _RunMetrics lineMetrics = isLastLineBaseline - ? lineBoxMetrics[lineBoxMetrics.length - 1] - : lineBoxMetrics[0]; - // Use the max baseline of the children as the baseline in flow layout + ? _lineBoxMetrics[_lineBoxMetrics.length - 1] + : _lineBoxMetrics[0]; + // Use the max baseline of the children as the baseline in flow layout. lineMetrics.runChildren.forEach((int? hashCode, RenderBox child) { double? childMarginTop = child is RenderBoxModel ? _getChildMarginTop(child) : 0; @@ -995,14 +714,14 @@ class RenderFlowLayout extends RenderLayoutBox { if (child is RenderBoxModel) { childBaseLineDistance = child.computeDistanceToBaseline(); } else if (child is RenderTextBox) { - // Text baseline not depends on its own parent but its grand parents + // Text baseline not depends on its own parent but its grand parents. childBaseLineDistance = isLastLineBaseline ? child.computeDistanceToLastLineBaseline() : child.computeDistanceToFirstLineBaseline(); } if (childBaseLineDistance != null && childParentData != null) { // Baseline of relative positioned element equals its original position - // so it needs to subtract its vertical offset + // so it needs to subtract its vertical offset. Offset? relativeOffset; double childOffsetY = childParentData.offset.dy - childMarginTop; if (child is RenderBoxModel) { @@ -1012,7 +731,7 @@ class RenderFlowLayout extends RenderLayoutBox { if (relativeOffset != null) { childOffsetY -= relativeOffset.dy; } - // It needs to subtract margin-top cause offset already includes margin-top + // It needs to subtract margin-top cause offset already includes margin-top. childBaseLineDistance += childOffsetY; if (lineDistance != null) lineDistance = math.max(lineDistance!, childBaseLineDistance); @@ -1021,14 +740,14 @@ class RenderFlowLayout extends RenderLayoutBox { } }); - // If no inline child found, use margin-bottom as baseline + // If no inline child found, use margin-bottom as baseline. if (isDisplayInline && lineDistance != null) { lineDistance = lineDistance! + marginTop; } return lineDistance; } - /// Record the main size of all lines + // Record the main size of all lines. void _recordRunsMainSize(_RunMetrics runMetrics, List runMainSize) { Map runChildren = runMetrics.runChildren; double runMainExtent = 0; @@ -1050,17 +769,17 @@ class RenderFlowLayout extends RenderLayoutBox { runMainSize.add(runMainExtent); } - /// Get auto min size in the main axis which equals the main axis size of its contents - /// https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size + // Get auto min size in the main axis which equals the main axis size of its contents. + // https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size double _getMainAxisAutoSize( List<_RunMetrics> runMetrics, ) { double autoMinSize = 0; - // Main size of each run + // Main size of each run. List runMainSize = []; - // Calculate the max main size of all runs + // Calculate the max main size of all runs. for (_RunMetrics runMetrics in runMetrics) { _recordRunsMainSize(runMetrics, runMainSize); } @@ -1074,7 +793,7 @@ class RenderFlowLayout extends RenderLayoutBox { return autoMinSize; } - /// Record the cross size of all lines + // Record the cross size of all lines. void _recordRunsCrossSize(_RunMetrics runMetrics, List runCrossSize) { Map runChildren = runMetrics.runChildren; double runCrossExtent = 0; @@ -1095,21 +814,21 @@ class RenderFlowLayout extends RenderLayoutBox { runCrossSize.add(runCrossExtent); } - /// Get auto min size in the cross axis which equals the cross axis size of its contents - /// https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size + // Get auto min size in the cross axis which equals the cross axis size of its contents. + // https://www.w3.org/TR/css-sizing-3/#automatic-minimum-size double _getCrossAxisAutoSize( List<_RunMetrics> runMetrics, ) { double autoMinSize = 0; - // Cross size of each run + // Cross size of each run. List runCrossSize = []; - // Calculate the max cross size of all runs + // Calculate the max cross size of all runs. for (_RunMetrics runMetrics in runMetrics) { _recordRunsCrossSize(runMetrics, runCrossSize); } - // Get the sum of lines + // Get the sum of lines. for (double crossSize in runCrossSize) { autoMinSize += crossSize; } @@ -1117,27 +836,27 @@ class RenderFlowLayout extends RenderLayoutBox { return autoMinSize; } - /// Set the size of scrollable overflow area for flow layout - /// https://drafts.csswg.org/css-overflow-3/#scrollable + // Set the size of scrollable overflow area for flow layout. + // https://drafts.csswg.org/css-overflow-3/#scrollable void _setMaxScrollableSizeForFlow(List<_RunMetrics> runMetrics) { - // Scrollable main size collection of each line + // Scrollable main size collection of each line. List scrollableMainSizeOfLines = []; - // Scrollable cross size collection of each line + // Scrollable cross size collection of each line. List scrollableCrossSizeOfLines = []; - // Total cross size of previous lines + // Total cross size of previous lines. double preLinesCrossSize = 0; for (_RunMetrics runMetric in runMetrics) { Map runChildren = runMetric.runChildren; List runChildrenList = []; - // Scrollable main size collection of each child in the line + // Scrollable main size collection of each child in the line. List scrollableMainSizeOfChildren = []; - // Scrollable cross size collection of each child in the line + // Scrollable cross size collection of each child in the line. List scrollableCrossSizeOfChildren = []; void iterateRunChildren(int? hashCode, RenderBox child) { - // Total main size of previous siblings + // Total main size of previous siblings. double preSiblingsMainSize = 0; for (RenderBox sibling in runChildrenList) { preSiblingsMainSize += sibling.size.width; @@ -1150,7 +869,7 @@ class RenderFlowLayout extends RenderLayoutBox { RenderStyle childRenderStyle = child.renderStyle; CSSOverflowType overflowX = childRenderStyle.effectiveOverflowX; CSSOverflowType overflowY = childRenderStyle.effectiveOverflowY; - // Only non scroll container need to use scrollable size, otherwise use its own size + // Only non scroll container need to use scrollable size, otherwise use its own size. if (overflowX == CSSOverflowType.visible && overflowY == CSSOverflowType.visible) { childScrollableSize = child.scrollableSize; @@ -1168,13 +887,13 @@ class RenderFlowLayout extends RenderLayoutBox { runChildren.forEach(iterateRunChildren); - // Max scrollable main size of all the children in the line + // Max scrollable main size of all the children in the line. double maxScrollableMainSizeOfLine = scrollableMainSizeOfChildren.reduce((double curr, double next) { return curr > next ? curr : next; }); - // Max scrollable cross size of all the children in the line + // Max scrollable cross size of all the children in the line. double maxScrollableCrossSizeOfLine = preLinesCrossSize + scrollableCrossSizeOfChildren.reduce((double curr, double next) { return curr > next ? curr : next; @@ -1185,7 +904,7 @@ class RenderFlowLayout extends RenderLayoutBox { preLinesCrossSize += runMetric.crossAxisExtent; } - // Max scrollable main size of all lines + // Max scrollable main size of all lines. double maxScrollableMainSizeOfLines = scrollableMainSizeOfLines.reduce((double curr, double next) { return curr > next ? curr : next; @@ -1197,18 +916,18 @@ class RenderFlowLayout extends RenderLayoutBox { renderStyle.effectiveOverflowX != CSSOverflowType.visible || renderStyle.effectiveOverflowY != CSSOverflowType.visible; - // No need to add padding for scrolling content box + // No need to add padding for scrolling content box. double maxScrollableMainSizeOfChildren = isScrollContainer ? maxScrollableMainSizeOfLines : container.renderStyle.paddingLeft.computedValue + maxScrollableMainSizeOfLines; - // Max scrollable cross size of all lines + // Max scrollable cross size of all lines. double maxScrollableCrossSizeOfLines = scrollableCrossSizeOfLines.reduce((double curr, double next) { return curr > next ? curr : next; }); - // No need to add padding for scrolling content box + // No need to add padding for scrolling content box. double maxScrollableCrossSizeOfChildren = isScrollContainer ? maxScrollableCrossSizeOfLines : container.renderStyle.paddingTop.computedValue + maxScrollableCrossSizeOfLines; @@ -1227,9 +946,9 @@ class RenderFlowLayout extends RenderLayoutBox { scrollableSize = Size(maxScrollableMainSize, maxScrollableCrossSize); } - // Get distance from top to baseline of child including margin + // Get distance from top to baseline of child including margin. double _getChildAscent(RenderBox child) { - // Distance from top to baseline of child + // Distance from top to baseline of child. double? childAscent = child.getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); double? childMarginTop = 0; @@ -1244,13 +963,13 @@ class RenderFlowLayout extends RenderLayoutBox { double baseline = parent is RenderFlowLayout ? childMarginTop + childSize!.height + childMarginBottom : childMarginTop + childSize!.height; - // When baseline of children not found, use boundary of margin bottom as baseline + // When baseline of children not found, use boundary of margin bottom as baseline. double extentAboveBaseline = childAscent ?? baseline; return extentAboveBaseline; } - /// Get child size through boxSize to avoid flutter error when parentUsesSize is set to false + // Get child size through boxSize to avoid flutter error when parentUsesSize is set to false. Size? _getChildSize(RenderBox child) { if (child is RenderBoxModel) { return child.boxSize; @@ -1258,6 +977,9 @@ class RenderFlowLayout extends RenderLayoutBox { return child.boxSize; } else if (child is RenderTextBox) { return child.boxSize; + } else if (child.hasSize) { + // child is WidgetElement. + return child.size; } return null; } @@ -1272,7 +994,6 @@ class RenderFlowLayout extends RenderLayoutBox { childDisplay == CSSDisplay.inlineFlex; } return false; - } RenderStyle? _getChildRenderStyle(RenderBox child) { @@ -1288,8 +1009,8 @@ class RenderFlowLayout extends RenderLayoutBox { } bool _isChildBlockLevel(RenderBox? child) { - if (child != null && child is! RenderTextBox) { - RenderStyle? childRenderStyle = _getChildRenderStyle(child); + if (child is RenderBoxModel || child is RenderPositionPlaceholder) { + RenderStyle? childRenderStyle = _getChildRenderStyle(child!); if (childRenderStyle != null) { CSSDisplay? childDisplay = childRenderStyle.display; return childDisplay == CSSDisplay.block || @@ -1299,7 +1020,7 @@ class RenderFlowLayout extends RenderLayoutBox { return false; } - /// Get the collapsed margin top with the margin-bottom of its previous sibling + // Get the collapsed margin top with the margin-bottom of its previous sibling. double _getCollapsedMarginTopWithPreSibling(RenderBoxModel renderBoxModel, RenderObject preSibling, double marginTop) { if (preSibling is RenderBoxModel && (preSibling.renderStyle.effectiveDisplay == CSSDisplay.block || @@ -1313,7 +1034,7 @@ class RenderFlowLayout extends RenderLayoutBox { return marginTop; } - /// Get the collapsed margin top with parent if it is the first child of its parent + // Get the collapsed margin top with parent if it is the first child of its parent. double _getCollapsedMarginTopWithParent(RenderBoxModel renderBoxModel, double marginTop) { RenderLayoutBox parent = renderBoxModel.parent as RenderLayoutBox; // Use parent renderStyle if renderBoxModel is scrollingContentBox cause its style is not @@ -1338,7 +1059,7 @@ class RenderFlowLayout extends RenderLayoutBox { return marginTop; } - /// Get the collapsed margin top with its nested first child + // Get the collapsed margin top with its nested first child. double _getCollapsedMarginTopWithNestedFirstChild(RenderBoxModel renderBoxModel) { // Use parent renderStyle if renderBoxModel is scrollingContentBox cause its style is not // the same with its parent. @@ -1380,8 +1101,8 @@ class RenderFlowLayout extends RenderLayoutBox { return marginTop; } - /// Get the collapsed margin top of child due to the margin collapse rule. - /// https://www.w3.org/TR/CSS2/box.html#collapsing-margins + // Get the collapsed margin top of child due to the margin collapse rule. + // https://www.w3.org/TR/CSS2/box.html#collapsing-margins double _getChildMarginTop(RenderBoxModel child) { CSSDisplay? childEffectiveDisplay = child.renderStyle.effectiveDisplay; // Margin is invalid for inline element. @@ -1434,7 +1155,7 @@ class RenderFlowLayout extends RenderLayoutBox { return marginTop; } - /// Get the collapsed margin bottom with parent if it is the last child of its parent + // Get the collapsed margin bottom with parent if it is the last child of its parent. double _getCollapsedMarginBottomWithParent(RenderBoxModel renderBoxModel, double marginBottom) { RenderLayoutBox parent = renderBoxModel.parent as RenderLayoutBox; // Use parent renderStyle if renderBoxModel is scrollingContentBox cause its style is not @@ -1459,7 +1180,7 @@ class RenderFlowLayout extends RenderLayoutBox { return marginBottom; } - /// Get the collapsed margin bottom with its nested last child + // Get the collapsed margin bottom with its nested last child. double _getCollapsedMarginBottomWithNestedLastChild(RenderBoxModel renderBoxModel) { // Use parent renderStyle if renderBoxModel is scrollingContentBox cause its style is not // the same with its parent. @@ -1502,8 +1223,8 @@ class RenderFlowLayout extends RenderLayoutBox { return marginBottom; } - /// Get the collapsed margin bottom of child due to the margin collapse rule. - /// https://www.w3.org/TR/CSS2/box.html#collapsing-margins + // Get the collapsed margin bottom of child due to the margin collapse rule. + // https://www.w3.org/TR/CSS2/box.html#collapsing-margins double _getChildMarginBottom(RenderBoxModel child) { CSSDisplay? childEffectiveDisplay = child.renderStyle.effectiveDisplay; // Margin is invalid for inline element. @@ -1564,13 +1285,13 @@ class RenderFlowLayout extends RenderLayoutBox { RenderObject child = paintingOrder[i]; if (child is! RenderPositionPlaceholder) { late DateTime childPaintStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childPaintStart = DateTime.now(); } final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; context.paintChild(child, childParentData.offset + offset); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childPaintEnd = DateTime.now(); childPaintDuration += (childPaintEnd.microsecondsSinceEpoch - childPaintStart.microsecondsSinceEpoch); @@ -1589,7 +1310,7 @@ class RenderFlowLayout extends RenderLayoutBox { class RenderRepaintBoundaryFlowLayout extends RenderFlowLayout { RenderRepaintBoundaryFlowLayout({ List? children, - required RenderStyle renderStyle, + required CSSRenderStyle renderStyle, }) : super( children: children, renderStyle: renderStyle, diff --git a/kraken/lib/src/rendering/image.dart b/kraken/lib/src/rendering/image.dart new file mode 100644 index 0000000000..2fca93edc3 --- /dev/null +++ b/kraken/lib/src/rendering/image.dart @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'dart:ui' as ui show Image; +import 'package:flutter/rendering.dart'; + +class KrakenRenderImage extends RenderImage { + KrakenRenderImage({ + ui.Image? image, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + }) : super( + image: image, + fit: fit, + alignment: alignment, + ); + + @override + void performLayout() { + Size trySize = constraints.biggest; + size = trySize.isInfinite ? constraints.smallest : trySize; + } +} + + diff --git a/kraken/lib/src/rendering/intersection_observer.dart b/kraken/lib/src/rendering/intersection_observer.dart index a363d5b365..4044af8b00 100644 --- a/kraken/lib/src/rendering/intersection_observer.dart +++ b/kraken/lib/src/rendering/intersection_observer.dart @@ -25,11 +25,17 @@ typedef IntersectionChangeCallback = void Function( mixin RenderIntersectionObserverMixin on RenderBox { IntersectionChangeCallback? _onIntersectionChange; - IntersectionObserverLayer? intersectionObserverLayer; + // IntersectionObserverLayer? intersectionObserverLayer; + + final LayerHandle _intersectionObserverLayer = LayerHandle(); /// A list of event handlers List? _listeners; + void disposeIntersectionObserverLayer() { + _intersectionObserverLayer.layer = null; + } + void addIntersectionChangeListener(IntersectionChangeCallback callback) { // Init things if (_listeners == null) { @@ -78,18 +84,18 @@ mixin RenderIntersectionObserverMixin on RenderBox { return; } - if (intersectionObserverLayer == null) { - intersectionObserverLayer = IntersectionObserverLayer( + if (_intersectionObserverLayer.layer == null) { + _intersectionObserverLayer.layer = IntersectionObserverLayer( elementSize: size, paintOffset: offset, onIntersectionChange: _onIntersectionChange! ); } else { - intersectionObserverLayer!.elementSize = semanticBounds.size; - intersectionObserverLayer!.paintOffset = offset; + _intersectionObserverLayer.layer!.elementSize = semanticBounds.size; + _intersectionObserverLayer.layer!.paintOffset = offset; } - context.pushLayer(intersectionObserverLayer!, callback, offset); + context.pushLayer(_intersectionObserverLayer.layer!, callback, offset); } } @@ -171,7 +177,7 @@ class IntersectionObserverLayer extends ContainerLayer { Matrix4? cachedTransform = _layerTransformCache[child.hashCode]; // Get the transform of parent layer to root layer directly if exists. if (cachedTransform != null) { - transform = cachedTransform; + transform = cachedTransform.clone(); } else { (parent as ContainerLayer).applyTransform(child, transform); // Cache the transform of parent layer to root layer. diff --git a/kraken/lib/src/rendering/intrinsic.dart b/kraken/lib/src/rendering/intrinsic.dart index 55296f16f6..dce6794a5b 100644 --- a/kraken/lib/src/rendering/intrinsic.dart +++ b/kraken/lib/src/rendering/intrinsic.dart @@ -12,11 +12,7 @@ import 'package:kraken/rendering.dart'; class RenderIntrinsic extends RenderBoxModel with RenderObjectWithChildMixin, RenderProxyBoxMixin { - RenderIntrinsic( - RenderStyle renderStyle, - ) : super( - renderStyle: renderStyle, - ); + RenderIntrinsic(CSSRenderStyle renderStyle) : super(renderStyle: renderStyle); @override BoxSizeType get widthSizeType { @@ -30,12 +26,18 @@ class RenderIntrinsic extends RenderBoxModel return heightDefined ? BoxSizeType.specified : BoxSizeType.intrinsic; } - // Set clipX and clipY to true for background cannot overflow beyond the boundary of replaced element + // The content of replaced elements is always trimmed to the content edge curve. + // https://www.w3.org/TR/css-backgrounds-3/#corner-clipping @override - bool get clipX => true; + bool get clipX { + // Only clip when content of replaced element is loaded. + return renderStyle.borderRadius != null && renderStyle.intrinsicRatio != null; + } @override - bool get clipY => true; + bool get clipY { + return renderStyle.borderRadius != null && renderStyle.intrinsicRatio != null; + } @override void setupParentData(RenderBox child) { @@ -52,7 +54,7 @@ class RenderIntrinsic extends RenderBoxModel @override void performLayout() { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutDuration = 0; PerformanceTiming.instance() .mark(PERF_INTRINSIC_LAYOUT_START, uniqueId: hashCode); @@ -60,76 +62,24 @@ class RenderIntrinsic extends RenderBoxModel beforeLayout(); - double? width = renderStyle.width.isAuto ? null : renderStyle.width.computedValue; - double? height = renderStyle.height.isAuto ? null : renderStyle.height.computedValue; - double? minWidth = renderStyle.minWidth.isAuto ? null : renderStyle.minWidth.computedValue; - double? maxWidth = renderStyle.maxWidth.isNone ? null : renderStyle.maxWidth.computedValue; - double? minHeight = renderStyle.minHeight.isAuto ? null : renderStyle.minHeight.computedValue; - double? maxHeight = renderStyle.maxHeight.isNone ? null : renderStyle.maxHeight.computedValue; - if (child != null) { late DateTime childLayoutStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutStart = DateTime.now(); } child!.layout(contentConstraints!, parentUsesSize: true); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childLayoutEnd = DateTime.now(); childLayoutDuration += (childLayoutEnd.microsecondsSinceEpoch) - childLayoutStart.microsecondsSinceEpoch; } - setMaxScrollableSize(child!.size); - - CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay; - bool isInlineLevel = effectiveDisplay == CSSDisplay.inlineBlock || - effectiveDisplay == CSSDisplay.inlineFlex; - - double constraintWidth = child!.size.width; - double constraintHeight = child!.size.height; - - // Constrain to min-width or max-width if width not exists - if (isInlineLevel && maxWidth != null && width == null) { - constraintWidth = - constraintWidth > maxWidth ? maxWidth : constraintWidth; - - // max-height should respect intrinsic ratio with max-width - if (intrinsicRatio != null && maxHeight == null) { - constraintHeight = constraintWidth * intrinsicRatio!; - } - } else if (isInlineLevel && minWidth != null && width == null) { - constraintWidth = - constraintWidth < minWidth ? minWidth : constraintWidth; - - // max-height should respect intrinsic ratio with max-width - if (intrinsicRatio != null && minHeight == null) { - constraintHeight = constraintWidth * intrinsicRatio!; - } - } - - // Constrain to min-height or max-height if width not exists - if (isInlineLevel && maxHeight != null && height == null) { - constraintHeight = - constraintHeight > maxHeight ? maxHeight : constraintHeight; - - // max-width should respect intrinsic ratio with max-height - if (intrinsicRatio != null && maxWidth == null) { - constraintWidth = constraintHeight / intrinsicRatio!; - } - } else if (isInlineLevel && minHeight != null && height == null) { - constraintHeight = - constraintHeight < minHeight ? minHeight : constraintHeight; - - // max-width should respect intrinsic ratio with max-height - if (intrinsicRatio != null && minWidth == null) { - constraintWidth = constraintHeight / intrinsicRatio!; - } - } + Size childSize = child!.size; - Size contentSize = Size(constraintWidth, constraintHeight); - size = getBoxSize(contentSize); + setMaxScrollableSize(childSize); + size = getBoxSize(childSize); autoMinWidth = size.width; autoMinHeight = size.height; @@ -139,7 +89,7 @@ class RenderIntrinsic extends RenderBoxModel performResize(); } - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { PerformanceTiming.instance() .mark(PERF_INTRINSIC_LAYOUT_END, uniqueId: hashCode); } @@ -192,11 +142,11 @@ class RenderIntrinsic extends RenderBoxModel if (child != null) { late DateTime childPaintStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childPaintStart = DateTime.now(); } context.paintChild(child!, offset); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childPaintEnd = DateTime.now(); childPaintDuration += (childPaintEnd.microsecondsSinceEpoch - childPaintStart.microsecondsSinceEpoch); @@ -222,11 +172,7 @@ class RenderIntrinsic extends RenderBoxModel } class RenderRepaintBoundaryIntrinsic extends RenderIntrinsic { - RenderRepaintBoundaryIntrinsic( - RenderStyle renderStyle, - ) : super( - renderStyle, - ); + RenderRepaintBoundaryIntrinsic(CSSRenderStyle renderStyle) : super(renderStyle); @override bool get isRepaintBoundary => true; diff --git a/kraken/lib/src/rendering/line_break.dart b/kraken/lib/src/rendering/line_break.dart new file mode 100644 index 0000000000..eac17ca7f7 --- /dev/null +++ b/kraken/lib/src/rendering/line_break.dart @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:flutter/rendering.dart'; +import 'package:kraken/css.dart'; +import 'package:kraken/rendering.dart'; + +class RenderLineBreak extends RenderIntrinsic { + RenderLineBreak( + CSSRenderStyle renderStyle, + ) : super( + renderStyle, + ); + + TextPainter get textPainter { + double fontSize = renderStyle.fontSize.computedValue; + + TextStyle textStyle = TextStyle( + fontFamilyFallback: renderStyle.fontFamily, + fontSize: fontSize, + textBaseline: CSSText.getTextBaseLine(), + package: CSSText.getFontPackage(), + locale: CSSText.getLocale(), + ); + TextPainter painter = TextPainter( + text: TextSpan( + text: ' ', + style: textStyle, + ), + textDirection: TextDirection.ltr + ); + painter.layout(); + return painter; + } + + // Height of BR element is only determined by its parents line-height. + // @TODO add cache to avoid create TextPainter to measure size on every layout. + double get lineHeight { + CSSLengthValue lineHeight = renderStyle.parent!.lineHeight; + if (lineHeight.type != CSSLengthType.NORMAL) { + return lineHeight.computedValue; + } else { + return textPainter.size.height; + } + } + + @override + void performLayout() { + size = Size(0, constraints.maxHeight); + } + + @override + BoxConstraints getConstraints() { + + // BR element is a special element in HTML which accepts no style, + // it dimension is only affected by the line-height of its parent. + // https://www.w3.org/TR/CSS1/#br-elements + double height = lineHeight; + BoxConstraints constraints = BoxConstraints( + minWidth: 0, + maxWidth: 0, + minHeight: height, + maxHeight: height, + ); + return constraints; + } + + @override + double computeDistanceToBaseline() { + return textPainter.computeDistanceToActualBaseline(CSSText.getTextBaseLine()); + } +} diff --git a/kraken/lib/src/rendering/opacity.dart b/kraken/lib/src/rendering/opacity.dart index 6d3eeaccd9..de1c090c9e 100644 --- a/kraken/lib/src/rendering/opacity.dart +++ b/kraken/lib/src/rendering/opacity.dart @@ -12,19 +12,24 @@ mixin RenderOpacityMixin on RenderBox { int alpha = ui.Color.getAlphaFromOpacity(1.0); - OpacityLayer? _opacityLayer; + + final LayerHandle _opacityLayer = LayerHandle(); + + void disposeOpacityLayer() { + _opacityLayer.layer = null; + } void paintOpacity(PaintingContext context, Offset offset, PaintingContextCallback callback) { if (alpha == 255) { - _opacityLayer = null; + _opacityLayer.layer = null; // No need to keep the layer. We'll create a new one if necessary. callback(context, offset); return; } - _opacityLayer = - context.pushOpacity(offset, alpha, callback, oldLayer: _opacityLayer); + _opacityLayer.layer = + context.pushOpacity(offset, alpha, callback, oldLayer: _opacityLayer.layer); } void debugOpacityProperties(DiagnosticPropertiesBuilder properties) { diff --git a/kraken/lib/src/rendering/paragraph.dart b/kraken/lib/src/rendering/paragraph.dart index c58f4e6c9d..168dd7bdb8 100644 --- a/kraken/lib/src/rendering/paragraph.dart +++ b/kraken/lib/src/rendering/paragraph.dart @@ -3,15 +3,12 @@ * Author: Kraken Team. */ -import 'dart:collection'; -import 'dart:math' as math; import 'dart:ui' as ui show LineMetrics, Gradient, Shader, TextBox, - PlaceholderAlignment, TextHeightBehavior; import 'package:flutter/foundation.dart'; @@ -68,7 +65,6 @@ class KrakenRenderParagraph extends RenderBox textWidthBasis: textWidthBasis, textHeightBehavior: textHeightBehavior) { addAll(children); - _extractPlaceholderSpans(text); } @override @@ -108,24 +104,10 @@ class KrakenRenderParagraph extends RenderBox case RenderComparison.layout: _textPainter.text = value; _overflowShader = null; - _extractPlaceholderSpans(value); break; } } - late List _placeholderSpans; - - void _extractPlaceholderSpans(InlineSpan span) { - _placeholderSpans = []; - span.visitChildren((InlineSpan span) { - if (span is PlaceholderSpan) { - final PlaceholderSpan placeholderSpan = span; - _placeholderSpans.add(placeholderSpan); - } - return true; - }); - } - /// How the text should be aligned horizontally. TextAlign get textAlign => _textPainter.textAlign; @@ -255,46 +237,6 @@ class KrakenRenderParagraph extends RenderBox _overflowShader = null; markNeedsLayout(); } - - @override - double computeMinIntrinsicWidth(double height) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenWidthWithMinIntrinsics(height); - _layoutText(); // layout with infinite width. - return _textPainter.minIntrinsicWidth; - } - - @override - double computeMaxIntrinsicWidth(double height) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenWidthWithMaxIntrinsics(height); - _layoutText(); // layout with infinite width. - return _textPainter.maxIntrinsicWidth; - } - - double _computeIntrinsicHeight(double width) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenHeightWithMinIntrinsics(width); - _layoutText(minWidth: width, maxWidth: width); - return _textPainter.height; - } - - @override - double computeMinIntrinsicHeight(double width) { - return _computeIntrinsicHeight(width); - } - - @override - double computeMaxIntrinsicHeight(double width) { - return _computeIntrinsicHeight(width); - } - /// Compute distance to baseline of first text line double computeDistanceToFirstLineBaseline() { double firstLineOffset = _lineOffset[0]; @@ -313,94 +255,6 @@ class KrakenRenderParagraph extends RenderBox return text.text == '' ? 0.0 : (lastLineOffset + lastLineMetrics.ascent); } - // Intrinsics cannot be calculated without a full layout for - // alignments that require the baseline (baseline, aboveBaseline, - // belowBaseline). - bool _canComputeIntrinsics() { - for (final PlaceholderSpan span in _placeholderSpans) { - switch (span.alignment) { - case ui.PlaceholderAlignment.baseline: - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - { - assert( - RenderObject.debugCheckingIntrinsics, - 'Intrinsics are not available for PlaceholderAlignment.baseline, ' - 'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline,'); - return false; - } - case ui.PlaceholderAlignment.top: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.bottom: - { - continue; - } - } - } - return true; - } - - void _computeChildrenWidthWithMaxIntrinsics(double height) { - RenderBox? child = firstChild; - final List placeholderDimensions = - List.filled(childCount, null); - int childIndex = 0; - while (child != null) { - // Height and baseline is irrelevant as all text will be laid - // out in a single line. - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(child.getMaxIntrinsicWidth(height), height), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions( - placeholderDimensions as List?); - } - - void _computeChildrenWidthWithMinIntrinsics(double height) { - RenderBox? child = firstChild; - final List placeholderDimensions = - List.filled(childCount, null); - int childIndex = 0; - while (child != null) { - final double intrinsicWidth = child.getMinIntrinsicWidth(height); - final double intrinsicHeight = - child.getMinIntrinsicHeight(intrinsicWidth); - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(intrinsicWidth, intrinsicHeight), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions( - placeholderDimensions as List?); - } - - void _computeChildrenHeightWithMinIntrinsics(double width) { - RenderBox? child = firstChild; - final List placeholderDimensions = - List.filled(childCount, null); - int childIndex = 0; - while (child != null) { - final double intrinsicHeight = child.getMinIntrinsicHeight(width); - final double intrinsicWidth = child.getMinIntrinsicWidth(intrinsicHeight); - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(intrinsicWidth, intrinsicHeight), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions( - placeholderDimensions as List?); - } - @override bool hitTestSelf(Offset position) => true; @@ -482,84 +336,11 @@ class KrakenRenderParagraph extends RenderBox _textPainter.markNeedsLayout(); } - // Placeholder dimensions representing the sizes of child inline widgets. - // - // These need to be cached because the text painter's placeholder dimensions - // will be overwritten during intrinsic width/height calculations and must be - // restored to the original values before final layout and painting. - List? _placeholderDimensions; - void _layoutTextWithConstraints(BoxConstraints constraints) { - _textPainter.setPlaceholderDimensions( - _placeholderDimensions as List?); _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); } - // Layout the child inline widgets. We then pass the dimensions of the - // children to _textPainter so that appropriate placeholders can be inserted - // into the LibTxt layout. This does not do anything if no inline widgets were - // specified. - void _layoutChildren(BoxConstraints constraints) { - if (childCount == 0) { - return; - } - RenderBox? child = firstChild; - _placeholderDimensions = - List.filled(childCount, null); - int childIndex = 0; - while (child != null) { - // Only constrain the width to the maximum width of the paragraph. - // Leave height unconstrained, which will overflow if expanded past. - child.layout( - BoxConstraints( - maxWidth: constraints.maxWidth, - ), - parentUsesSize: true, - ); - double? baselineOffset; - switch (_placeholderSpans[childIndex].alignment) { - case ui.PlaceholderAlignment.baseline: - { - baselineOffset = child - .getDistanceToBaseline(_placeholderSpans[childIndex].baseline!); - break; - } - default: - { - baselineOffset = null; - break; - } - } - _placeholderDimensions![childIndex] = PlaceholderDimensions( - size: child.size, - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - baselineOffset: baselineOffset, - ); - child = childAfter(child); - childIndex += 1; - } - } - - // Iterate through the laid-out children and set the parentData offsets based - // off of the placeholders inserted for each child. - void _setParentData() { - RenderBox? child = firstChild; - int childIndex = 0; - while (child != null && - childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData as TextParentData; - textParentData.offset = Offset( - _textPainter.inlinePlaceholderBoxes![childIndex].left, - _textPainter.inlinePlaceholderBoxes![childIndex].top, - ); - textParentData.scale = _textPainter.inlinePlaceholderScales![childIndex]; - child = childAfter(child); - childIndex += 1; - } - } - - /// Get text of each line in the paragraph + // Get text of each line in the paragraph. List _getLineTexts(TextPainter textPainter, TextSpan textSpan) { TextSelection selection = TextSelection(baseOffset: 0, extentOffset: textSpan.text!.length); @@ -596,11 +377,9 @@ class KrakenRenderParagraph extends RenderBox return lineTexts; } - // Create and layout line text painters according to text lines in the paragraph - void _layoutMultiLineTextWithConstraints(BoxConstraints constraints) { - // Get text of each line - List lineTexts = - _getLineTexts(_textPainter, _textPainter.text as TextSpan); + // Compute line metrics and line offset according to line-height spec. + // https://www.w3.org/TR/css-inline-3/#inline-height + void _computeLineMetrics() { _lineMetrics = _textPainter.computeLineMetrics(); // Leading of each line List _lineLeading = []; @@ -610,18 +389,43 @@ class KrakenRenderParagraph extends RenderBox ui.LineMetrics lineMetric = _lineMetrics[i]; // Do not add line height in the case of textOverflow ellipsis // cause height of line metric equals to 0. - double leading = lineHeight != null && lineMetric.height != 0 ? - lineHeight! - lineMetric.height : 0; + double leading = lineHeight != null && lineMetric.height != 0 + ? lineHeight! - lineMetric.height + : 0; _lineLeading.add(leading); // Offset of previous line double preLineBottom = i > 0 - ? _lineOffset[i - 1] + - _lineMetrics[i - 1].height + - _lineLeading[i - 1] / 2 - : 0; + ? _lineOffset[i - 1] + _lineMetrics[i - 1].height + _lineLeading[i - 1] / 2 + : 0; double offset = preLineBottom + leading / 2; _lineOffset.add(offset); } + } + + // Compute paragraph height according to line metrics. + double _getParagraphHeight() { + double paragraphHeight = 0; + // Height of paragraph + for (int i = 0; i < _lineMetrics.length; i++) { + ui.LineMetrics lineMetric = _lineMetrics[i]; + // Do not add line height in the case of textOverflow ellipsis + // cause height of line metric equals to 0. + double height = lineHeight != null && lineMetric.height != 0 ? + lineHeight! : lineMetric.height; + paragraphHeight += height; + } + + return paragraphHeight; + } + + // Create and layout text painter of each line in the paragraph for later use + // in the paint stage to adjust the vertical space between text painters according to + // W3C line-height spec. + void _relayoutMultiLineText() { + final BoxConstraints constraints = this.constraints; + // Get text of each line + List lineTexts = _getLineTexts(_textPainter, _textPainter.text as TextSpan); + _lineTextPainters = []; // Create text painter of each line and layout for (int i = 0; i < lineTexts.length; i++) { @@ -654,22 +458,15 @@ class KrakenRenderParagraph extends RenderBox @override void performLayout() { final BoxConstraints constraints = this.constraints; - _layoutChildren(constraints); _layoutTextWithConstraints(constraints); - _setParentData(); - _layoutMultiLineTextWithConstraints(constraints); - - double paragraphHeight = 0; - if (text.text != '') { - // Height of paragraph - for (int i = 0; i < _lineMetrics.length; i++) { - ui.LineMetrics lineMetric = _lineMetrics[i]; - // Do not add line height in the case of textOverflow ellipsis - // cause height of line metric equals to 0. - double height = lineHeight != null && lineMetric.height != 0 ? - lineHeight! : lineMetric.height; - paragraphHeight += height; - } + _computeLineMetrics(); + + // @FIXME: Layout twice will hurt performance, ideally this logic should be done + // in flutter text engine. + // Layout each line of the paragraph indivisually to + // place each line according to W3C line-height rule. + if (lineHeight != null) { + _relayoutMultiLineText(); } // We grab _textPainter.size and _textPainter.didExceedMaxLines here because @@ -680,7 +477,9 @@ class KrakenRenderParagraph extends RenderBox final Size textSize = _textPainter.size; final bool textDidExceedMaxLines = _textPainter.didExceedMaxLines; - Size paragraphSize = Size(_textPainter.size.width, paragraphHeight); + double paragraphHeight = _getParagraphHeight(); + Size paragraphSize = Size(textSize.width, paragraphHeight); + size = constraints.constrain(paragraphSize); final bool didOverflowHeight = @@ -747,13 +546,6 @@ class KrakenRenderParagraph extends RenderBox @override void paint(PaintingContext context, Offset offset) { - // Paint line painters - for (int i = 0; i < _lineTextPainters.length; i++) { - TextPainter _lineTextPainter = _lineTextPainters[i]; - Offset lineOffset = Offset(offset.dx, offset.dy + _lineOffset[i]); - _lineTextPainter.paint(context.canvas, lineOffset); - } - assert(() { if (debugRepaintTextRainbowEnabled) { final Paint paint = Paint()..color = debugCurrentRepaintColor.toColor(); @@ -774,31 +566,17 @@ class KrakenRenderParagraph extends RenderBox context.canvas.clipRect(bounds); } - RenderBox? child = firstChild; - int childIndex = 0; - // childIndex might be out of index of placeholder boxes. This can happen - // if engine truncates children due to ellipsis. Sadly, we would not know - // it until we finish layout, and RenderObject is in immutable state at - // this point. - while (child != null && - childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData as TextParentData; - - final double scale = textParentData.scale!; - context.pushTransform( - needsCompositing, - offset + textParentData.offset, - Matrix4.diagonal3Values(scale, scale, scale), - (PaintingContext context, Offset offset) { - context.paintChild( - child!, - offset, - ); - }, - ); - child = childAfter(child); - childIndex += 1; + if (lineHeight != null) { + // Adjust text paint offset of each line according to line-height. + for (int i = 0; i < _lineTextPainters.length; i++) { + TextPainter _lineTextPainter = _lineTextPainters[i]; + Offset lineOffset = Offset(offset.dx, offset.dy + _lineOffset[i]); + _lineTextPainter.paint(context.canvas, lineOffset); + } + } else { + _textPainter.paint(context.canvas, offset); } + if (_needsClipping) { if (_overflowShader != null) { context.canvas.translate(offset.dx, offset.dy); @@ -875,40 +653,6 @@ class KrakenRenderParagraph extends RenderBox /// [assembleSemanticsNode] and [_combineSemanticsInfo]. List? _semanticsInfo; - /// Combines _semanticsInfo entries where permissible, determined by - /// [InlineSpanSemanticsInformation.requiresOwnNode]. - List _combineSemanticsInfo() { - assert(_semanticsInfo != null); - final List combined = - []; - String workingText = ''; - String? workingLabel; - for (final InlineSpanSemanticsInformation info in _semanticsInfo!) { - if (info.requiresOwnNode) { - combined.add(InlineSpanSemanticsInformation( - workingText, - semanticsLabel: workingLabel ?? workingText, - )); - workingText = ''; - workingLabel = null; - combined.add(info); - } else { - workingText += info.text; - workingLabel ??= ''; - if (info.semanticsLabel != null) { - workingLabel += info.semanticsLabel!; - } else { - workingLabel += info.text; - } - } - } - combined.add(InlineSpanSemanticsInformation( - workingText, - semanticsLabel: workingLabel, - )); - return combined; - } - @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); @@ -928,105 +672,6 @@ class KrakenRenderParagraph extends RenderBox } } - // Caches [SemanticsNode]s created during [assembleSemanticsNode] so they - // can be re-used when [assembleSemanticsNode] is called again. This ensures - // stable ids for the [SemanticsNode]s of [TextSpan]s across - // [assembleSemanticsNode] invocations. - Queue? _cachedChildNodes; - - @override - void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, - Iterable children) { - assert(_semanticsInfo != null && _semanticsInfo!.isNotEmpty); - final List newChildren = []; - TextDirection currentDirection = textDirection; - Rect currentRect; - double ordinal = 0.0; - int start = 0; - int placeholderIndex = 0; - RenderBox? child = firstChild; - final Queue newChildCache = Queue(); - for (final InlineSpanSemanticsInformation info in _combineSemanticsInfo()) { - final TextDirection initialDirection = currentDirection; - final TextSelection selection = TextSelection( - baseOffset: start, - extentOffset: start + info.text.length, - ); - final List rects = getBoxesForSelection(selection); - if (rects.isEmpty) { - continue; - } - Rect rect = rects.first.toRect(); - currentDirection = rects.first.direction; - for (final ui.TextBox textBox in rects.skip(1)) { - rect = rect.expandToInclude(textBox.toRect()); - currentDirection = textBox.direction; - } - // Any of the text boxes may have had infinite dimensions. - // We shouldn't pass infinite dimensions up to the bridges. - rect = Rect.fromLTWH( - math.max(0.0, rect.left), - math.max(0.0, rect.top), - math.min(rect.width, constraints.maxWidth), - math.min(rect.height, constraints.maxHeight), - ); - // round the current rectangle to make this API testable and add some - // padding so that the accessibility rects do not overlap with the text. - currentRect = Rect.fromLTRB( - rect.left.floorToDouble() - 4.0, - rect.top.floorToDouble() - 4.0, - rect.right.ceilToDouble() + 4.0, - rect.bottom.ceilToDouble() + 4.0, - ); - - if (info.isPlaceholder) { - final SemanticsNode childNode = children.elementAt(placeholderIndex++); - final TextParentData parentData = child!.parentData as TextParentData; - childNode.rect = Rect.fromLTWH( - childNode.rect.left, - childNode.rect.top, - childNode.rect.width * parentData.scale!, - childNode.rect.height * parentData.scale!, - ); - newChildren.add(childNode); - child = childAfter(child); - } else { - final SemanticsConfiguration configuration = SemanticsConfiguration() - ..sortKey = OrdinalSortKey(ordinal++) - ..textDirection = initialDirection - ..label = info.semanticsLabel ?? info.text; - final GestureRecognizer? recognizer = info.recognizer; - if (recognizer != null) { - if (recognizer is TapGestureRecognizer) { - configuration.onTap = recognizer.onTap; - configuration.isLink = true; - } else if (recognizer is LongPressGestureRecognizer) { - configuration.onLongPress = recognizer.onLongPress; - } else { - assert(false); - } - } - final SemanticsNode newChild = (_cachedChildNodes?.isNotEmpty == true) - ? _cachedChildNodes!.removeFirst() - : SemanticsNode(); - newChild - ..updateWith(config: configuration) - ..rect = currentRect; - newChildCache.addLast(newChild); - newChildren.add(newChild); - } - start += info.text.length; - } - _cachedChildNodes = newChildCache; - node.updateWith(config: config, childrenInInversePaintOrder: newChildren); - } - - @override - void clearSemantics() { - super.clearSemantics(); - _cachedChildNodes = null; - } - @override List debugDescribeChildren() { return [ diff --git a/kraken/lib/src/rendering/sliver_list.dart b/kraken/lib/src/rendering/sliver_list.dart index a5d0119392..032391c8db 100644 --- a/kraken/lib/src/rendering/sliver_list.dart +++ b/kraken/lib/src/rendering/sliver_list.dart @@ -21,7 +21,7 @@ class RenderSliverListLayout extends RenderLayoutBox { late RenderViewport _renderViewport; // The sliver list render object reference. - late RenderSliverList _renderSliverList; + RenderSliverList? _renderSliverList; // The scrollable context to handle gestures. late KrakenScrollable scrollable; @@ -36,13 +36,12 @@ class RenderSliverListLayout extends RenderLayoutBox { final RenderSliverBoxChildManager _renderSliverBoxChildManager; RenderSliverListLayout({ - required RenderStyle renderStyle, + required CSSRenderStyle renderStyle, required RenderSliverElementChildManager manager, ScrollListener? onScroll, }) : _renderSliverBoxChildManager = manager, _scrollListener = onScroll, super(renderStyle: renderStyle) { - scrollablePointerListener = _scrollablePointerListener; scrollable = KrakenScrollable(axisDirection: getAxisDirection(axis)); axis = renderStyle.sliverDirection; @@ -57,17 +56,21 @@ class RenderSliverListLayout extends RenderLayoutBox { break; } - _renderSliverList = _buildRenderSliverList(); + RenderSliverList renderSliverList = _renderSliverList = _buildRenderSliverList(); _renderViewport = RenderViewport( offset: scrollable.position!, axisDirection: scrollable.axisDirection, crossAxisDirection: getCrossAxisDirection(axis), - children: [_renderSliverList], + children: [renderSliverList], ); manager.setupSliverListLayout(this); super.insert(_renderViewport); } + // Override the scrollable pointer listener. + @override + void Function(PointerEvent event) get scrollablePointerListener => _scrollablePointerListener; + @override ScrollListener? get scrollListener => _scrollListener; @@ -87,7 +90,7 @@ class RenderSliverListLayout extends RenderLayoutBox { // Insert render box child as sliver child. void insertSliverChild(RenderBox child, { RenderBox? after }) { setupParentData(child); - _renderSliverList.insert(child, after: after); + _renderSliverList?.insert(child, after: after); } @override @@ -95,20 +98,19 @@ class RenderSliverListLayout extends RenderLayoutBox { if (child == _renderViewport) { super.remove(child); } else if (child.parent == _renderSliverList) { - _renderSliverList.remove(child); + _renderSliverList?.remove(child); } } @override void removeAll() { - _renderSliverList.removeAll(); + _renderSliverList?.removeAll(); } @override void move(RenderBox child, {RenderBox? after}) { if (child.parent == _renderSliverList) { - remove(child); - insertSliverChild(child, after: after); + _renderSliverList?.move(child, after: after); } } @@ -132,6 +134,13 @@ class RenderSliverListLayout extends RenderLayoutBox { return _renderSliverList = RenderSliverList(childManager: _renderSliverBoxChildManager); } + // Trigger sliver list to rebuild children. + @override + void markNeedsLayout() { + super.markNeedsLayout(); + _renderSliverList?.markNeedsLayout(); + } + /// Child count should rely on element's childNodes, the real /// child renderObject count is not exactly. @override @@ -141,7 +150,7 @@ class RenderSliverListLayout extends RenderLayoutBox { @override void performLayout() { - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutDuration = 0; PerformanceTiming.instance() .mark(PERF_SILVER_LAYOUT_START, uniqueId: hashCode); @@ -175,13 +184,13 @@ class RenderSliverListLayout extends RenderLayoutBox { } late DateTime childLayoutStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childLayoutStart = DateTime.now(); } child.layout(childConstraints, parentUsesSize: true); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childLayoutEnd = DateTime.now(); childLayoutDuration += (childLayoutEnd.microsecondsSinceEpoch - childLayoutStart.microsecondsSinceEpoch); @@ -191,7 +200,7 @@ class RenderSliverListLayout extends RenderLayoutBox { didLayout(); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { PerformanceTiming.instance().mark(PERF_SILVER_LAYOUT_END, uniqueId: hashCode, startTime: @@ -208,11 +217,11 @@ class RenderSliverListLayout extends RenderLayoutBox { if (firstChild != null) { late DateTime childPaintStart; - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { childPaintStart = DateTime.now(); } context.paintChild(firstChild!, offset); - if (kProfileMode) { + if (kProfileMode && PerformanceTiming.enabled()) { DateTime childPaintEnd = DateTime.now(); childPaintDuration += (childPaintEnd.microsecondsSinceEpoch - childPaintStart.microsecondsSinceEpoch); @@ -232,7 +241,7 @@ class RenderSliverListLayout extends RenderLayoutBox { // Ignore detached render object. if (!child.attached) continue; - final ContainerBoxParentData childParentData = child.parentData as ContainerBoxParentData; + final ContainerBoxParentData childParentData = child.parentData as ContainerBoxParentData; final bool isHit = result.addWithPaintOffset( offset: childParentData.offset + currentOffset, position: position, diff --git a/kraken/lib/src/rendering/text.dart b/kraken/lib/src/rendering/text.dart index 73c1223342..e105e85425 100644 --- a/kraken/lib/src/rendering/text.dart +++ b/kraken/lib/src/rendering/text.dart @@ -8,26 +8,96 @@ import 'package:kraken/css.dart'; import 'package:kraken/dom.dart'; import 'package:kraken/rendering.dart'; +final RegExp _whiteSpaceReg = RegExp(r'\s+'); + class TextParentData extends ContainerBoxParentData {} enum WhiteSpace { normal, nowrap, pre, preWrap, preLine, breakSpaces } class RenderTextBox extends RenderBox with RenderObjectWithChildMixin { - RenderTextBox( - this.data, { + RenderTextBox(data, { required this.renderStyle, - }) { - TextSpan text = CSSTextMixin.createTextSpan(data, renderStyle); + }) : _data = data { + TextSpan text = CSSTextMixin.createTextSpan(_data, renderStyle); _renderParagraph = child = KrakenRenderParagraph( text, textDirection: TextDirection.ltr, ); } - late String data; + String _data; + + set data(String value) { + _data = value; + } + + String get data => _data; + + bool isEndWithSpace(String str) { + return str.endsWith(WHITE_SPACE_CHAR) || str.endsWith(NEW_LINE_CHAR) || str.endsWith(RETURN_CHAR) || str.endsWith(TAB_CHAR); + } + + String get _trimmedData { + if (parentData is RenderLayoutParentData) { + /// https://drafts.csswg.org/css-text-3/#propdef-white-space + /// The following table summarizes the behavior of the various white-space values: + // + // New lines / Spaces and tabs / Text wrapping / End-of-line spaces + // normal Collapse Collapse Wrap Remove + // nowrap Collapse Collapse No wrap Remove + // pre Preserve Preserve No wrap Preserve + // pre-wrap Preserve Preserve Wrap Hang + // pre-line Preserve Collapse Wrap Remove + // break-spaces Preserve Preserve Wrap Wrap + CSSRenderStyle parentRenderStyle = (parent as RenderLayoutBox).renderStyle; + WhiteSpace whiteSpace = parentRenderStyle.whiteSpace; + if (whiteSpace == WhiteSpace.pre || + whiteSpace == WhiteSpace.preLine || + whiteSpace == WhiteSpace.preWrap || + whiteSpace == WhiteSpace.breakSpaces) { + return whiteSpace == WhiteSpace.preLine ? _collapseWhitespace(_data) : _data; + } else { + String collapsedData = _collapseWhitespace(_data); + // TODO: + // Remove the leading space while prev element have space too: + //

foo bar

+ // Refs: + // https://github.com/WebKit/WebKit/blob/6a970b217d59f36e64606ed03f5238d572c23c48/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp#L295 + RenderObject? previousSibling = (parentData as RenderLayoutParentData).previousSibling; + + if (previousSibling == null) { + collapsedData = collapsedData.trimLeft(); + } else if (previousSibling is RenderBoxModel &&(previousSibling.renderStyle.display == CSSDisplay.block || previousSibling.renderStyle.display == CSSDisplay.flex)) { + // If previousSibling is block,should trimLeft slef. + CSSDisplay? display = previousSibling.renderStyle.display; + if (display == CSSDisplay.block || display == CSSDisplay.sliver || display == CSSDisplay.flex) { + collapsedData = collapsedData.trimLeft(); + } + } else if (previousSibling is RenderTextBox && isEndWithSpace(previousSibling.data)) { + collapsedData = collapsedData.trimLeft(); + } + + RenderObject? nextSibling = (parentData as RenderLayoutParentData).nextSibling; + if (nextSibling == null) { + collapsedData = collapsedData.trimRight(); + } else if (nextSibling is RenderBoxModel && (nextSibling.renderStyle.display == CSSDisplay.block || nextSibling.renderStyle.display == CSSDisplay.flex)) { + // If nextSibling is block,should trimRight slef. + CSSDisplay? display = nextSibling.renderStyle.display; + if (display == CSSDisplay.block || display == CSSDisplay.sliver || display == CSSDisplay.flex) { + collapsedData = collapsedData.trimRight(); + } + } + + return collapsedData; + } + } + + return _data; + } + late KrakenRenderParagraph _renderParagraph; - RenderStyle renderStyle; + CSSRenderStyle renderStyle; BoxSizeType? widthSizeType; BoxSizeType? heightSizeType; @@ -52,27 +122,38 @@ class RenderTextBox extends RenderBox super.size = value; } - WhiteSpace? get whiteSpace { - return renderStyle.whiteSpace; + @override + void setupParentData(RenderBox child) { + if (child.parentData is! TextParentData) { + child.parentData = TextParentData(); + } } - TextOverflow get overflow { - // Set line-clamp to number makes text-overflow ellipsis which takes priority over text-overflow - if (renderStyle.lineClamp != null && renderStyle.lineClamp! > 0) { - return TextOverflow.ellipsis; - } else if (renderStyle.effectiveOverflowX != CSSOverflowType.hidden || renderStyle.whiteSpace != WhiteSpace.nowrap) { - // To make text overflow its container you have to set overflowX hidden and white-space: nowrap. - return TextOverflow.visible; - } else { - return renderStyle.textOverflow; + int? get _maxLines { + int? lineClamp = renderStyle.lineClamp; + // Forcing a break after a set number of lines. + // https://drafts.csswg.org/css-overflow-3/#max-lines + if (lineClamp != null) { + return lineClamp; + } + // Force display single line when white-space is nowrap. + if (renderStyle.whiteSpace == WhiteSpace.nowrap) { + return 1; } + return null; } - @override - void setupParentData(RenderBox child) { - if (child.parentData is! TextParentData) { - child.parentData = TextParentData(); + double? get _lineHeight { + if (renderStyle.lineHeight.type != CSSLengthType.NORMAL) { + return renderStyle.lineHeight.computedValue; } + return null; + } + + TextSpan get _textSpan { + String clippedText = _getClippedText(_trimmedData); + // FIXME(yuanyan): do not create text span every time. + return CSSTextMixin.createTextSpan(clippedText, renderStyle); } // Mirror debugNeedsLayout flag in Flutter to use in layout performance optimization @@ -84,16 +165,21 @@ class RenderTextBox extends RenderBox needsLayout = true; } + void markRenderParagraphNeedsLayout() { + _renderParagraph.markNeedsLayout(); + } + // @HACK: sync _needsLayout flag in Flutter to do performance opt. void syncNeedsLayoutFlag() { needsLayout = true; } BoxConstraints getConstraints() { - if (whiteSpace == WhiteSpace.nowrap && - overflow != TextOverflow.ellipsis) { + if (renderStyle.whiteSpace == WhiteSpace.nowrap && + renderStyle.effectiveTextOverflow != TextOverflow.ellipsis) { return BoxConstraints(); } + double maxConstraintWidth = double.infinity; if (parent is RenderBoxModel) { RenderBoxModel parentRenderBoxModel = parent as RenderBoxModel; @@ -102,9 +188,9 @@ class RenderTextBox extends RenderBox if (parentRenderBoxModel.isScrollingContentBox) { maxConstraintWidth = parentConstraints.minWidth; } else if (parentConstraints.maxWidth == double.infinity) { - final RenderLayoutParentData parentParentData = parentRenderBoxModel.parentData as RenderLayoutParentData; + final ParentData? parentParentData = parentRenderBoxModel.parentData; // Width of positioned element does not constrained by parent. - if (parentParentData.isPositioned) { + if (parentParentData is RenderLayoutParentData && parentParentData.isPositioned) { maxConstraintWidth = double.infinity; } else { maxConstraintWidth = parentRenderBoxModel.renderStyle.contentMaxConstraintsWidth; @@ -136,26 +222,100 @@ class RenderTextBox extends RenderBox maxHeight: double.infinity); } + // Empty string is the minimum size character, use it as the base size + // for calculating the maximum characters to display in its container. + Size get minCharSize { + TextStyle textStyle = TextStyle( + fontFamilyFallback: renderStyle.fontFamily, + fontSize: renderStyle.fontSize.computedValue, + textBaseline: CSSText.getTextBaseLine(), + package: CSSText.getFontPackage(), + locale: CSSText.getLocale(), + ); + TextPainter painter = TextPainter( + text: TextSpan( + text: ' ', + style: textStyle, + ), + textDirection: TextDirection.ltr + ); + painter.layout(); + return painter.size; + } + + // Avoid to render the whole text when text overflows its parent and text is not + // displayed fully and parent is not scrollable to improve text layout performance. + String _getClippedText(String data) { + // Only clip text in container which meets CSS box model spec. + if (parent is! RenderBoxModel) { + return data; + } + + String clippedText = data; + RenderBoxModel parentRenderBoxModel = parent as RenderBoxModel; + BoxConstraints? parentContentConstraints = parentRenderBoxModel.contentConstraints; + // Text only need to render in parent container's content area when + // white-space is nowrap and overflow is hidden/clip. + CSSOverflowType effectiveOverflowX = renderStyle.effectiveOverflowX; + + if (parentContentConstraints != null + && (effectiveOverflowX == CSSOverflowType.hidden + || effectiveOverflowX == CSSOverflowType.clip) + ) { + // Max character to display in one line. + int? maxCharsOfLine; + // Max lines in parent. + int? maxLines; + + if (parentContentConstraints.maxWidth.isFinite) { + maxCharsOfLine = (parentContentConstraints.maxWidth / minCharSize.width).ceil(); + } + if (parentContentConstraints.maxHeight.isFinite) { + maxLines = (parentContentConstraints.maxHeight / (_lineHeight ?? minCharSize.height)).ceil(); + } + + if (renderStyle.whiteSpace == WhiteSpace.nowrap) { + if (maxCharsOfLine != null) { + int maxChars = maxCharsOfLine; + if (data.length > maxChars) { + clippedText = data.substring(0, maxChars); + } + } + } else { + if (maxCharsOfLine != null && maxLines != null) { + int maxChars = maxCharsOfLine * maxLines; + if (data.length > maxChars) { + clippedText = data.substring(0, maxChars); + } + } + } + } + return clippedText; + } + + // ' a b c \n' => ' a b c ' + static String _collapseWhitespace(String string) { + return string.replaceAll(_whiteSpaceReg, WHITE_SPACE_CHAR); + } + @override void performLayout() { - if (child != null) { - // FIXME(yuanyan): do not create text span every time. - _renderParagraph.text = CSSTextMixin.createTextSpan(data, renderStyle); - _renderParagraph.overflow = overflow; - // Forcing a break after a set number of lines - // https://drafts.csswg.org/css-overflow-3/#max-lines - _renderParagraph.maxLines = renderStyle.lineClamp; - _renderParagraph.textAlign = renderStyle.textAlign; - if (renderStyle.lineHeight.type != CSSLengthType.NORMAL) { - _renderParagraph.lineHeight = renderStyle.lineHeight.computedValue; - } + KrakenRenderParagraph? paragraph = child as KrakenRenderParagraph?; + if (paragraph != null) { + paragraph.overflow = renderStyle.effectiveTextOverflow; + paragraph.textAlign = renderStyle.textAlign; + paragraph.text = _textSpan; + paragraph.maxLines = _maxLines; + paragraph.lineHeight = _lineHeight; + paragraph.layout(constraints, parentUsesSize: true); - child!.layout(constraints, parentUsesSize: true); - size = child!.size; + size = paragraph.size; // @FIXME: Minimum size of text equals to single word in browser // which cannot be calculated in Flutter currently. - autoMinWidth = size.width; + // Set minimum width to 0 to allow flex item containing text to shrink into + // flex container which is similar to the effect of word-break: break-all in the browser. + autoMinWidth = 0; autoMinHeight = size.height; } else { performResize(); diff --git a/kraken/lib/src/rendering/transform.dart b/kraken/lib/src/rendering/transform.dart index 10ac4e3c53..c92f8fdb9d 100644 --- a/kraken/lib/src/rendering/transform.dart +++ b/kraken/lib/src/rendering/transform.dart @@ -32,7 +32,11 @@ mixin RenderTransformMixin on RenderBoxModelBase { return result; } - TransformLayer? _transformLayer; + final LayerHandle _transformLayer = LayerHandle(); + + void disposeTransformLayer() { + _transformLayer.layer = null; + } void paintTransform(PaintingContext context, Offset offset, PaintingContextCallback callback) { @@ -40,16 +44,16 @@ mixin RenderTransformMixin on RenderBoxModelBase { final Matrix4 transform = getEffectiveTransform(); final Offset? childOffset = MatrixUtils.getAsTranslation(transform); if (childOffset == null) { - _transformLayer = context.pushTransform( + _transformLayer.layer = context.pushTransform( needsCompositing, offset, transform, callback, - oldLayer: _transformLayer, + oldLayer: _transformLayer.layer, ); } else { callback(context, offset + childOffset); - _transformLayer = null; + _transformLayer.layer = null; } } else { callback(context, offset); diff --git a/kraken/lib/src/widget/element_to_widget_adaptor.dart b/kraken/lib/src/widget/element_to_widget_adaptor.dart new file mode 100644 index 0000000000..0674f4c9d3 --- /dev/null +++ b/kraken/lib/src/widget/element_to_widget_adaptor.dart @@ -0,0 +1,49 @@ +import 'package:flutter/widgets.dart'; +import 'package:kraken/dom.dart' as dom; + +class KrakenElementToWidgetAdaptor extends RenderObjectWidget { + final dom.Node _krakenNode; + + KrakenElementToWidgetAdaptor(this._krakenNode, { Key? key }): super(key: key) { + _krakenNode.flutterWidget = this; + } + + @override + RenderObjectElement createElement() { + _krakenNode.flutterElement = KrakenElementToFlutterElementAdaptor(this); + return _krakenNode.flutterElement as RenderObjectElement; + } + + @override + RenderObject createRenderObject(BuildContext context) { + return _krakenNode.renderer!; + } +} + +class KrakenElementToFlutterElementAdaptor extends RenderObjectElement { + KrakenElementToFlutterElementAdaptor(RenderObjectWidget widget) : super(widget); + + @override + KrakenElementToWidgetAdaptor get widget => super.widget as KrakenElementToWidgetAdaptor; + + @override + void mount(Element? parent, Object? newSlot) { + widget._krakenNode.createRenderer(); + super.mount(parent, newSlot); + + widget._krakenNode.ensureChildAttached(); + + if (widget._krakenNode is dom.Element) { + (widget._krakenNode as dom.Element).style.flushPendingProperties(); + } + } + + @override + void unmount() { + super.unmount(); + (widget._krakenNode as dom.Element).disposeRenderObject(); + } + + @override + void insertRenderObjectChild(RenderObject child, Object? slot) {} +} diff --git a/kraken/lib/src/widget/kraken.dart b/kraken/lib/src/widget/kraken.dart new file mode 100644 index 0000000000..609077f621 --- /dev/null +++ b/kraken/lib/src/widget/kraken.dart @@ -0,0 +1,965 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +import 'dart:io'; +import 'dart:ui'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:kraken/kraken.dart'; +import 'package:kraken/rendering.dart'; +import 'package:kraken/dom.dart' as dom; +import 'package:kraken/module.dart'; +import 'package:kraken/gesture.dart'; +import 'package:kraken/css.dart'; +import 'package:kraken/src/dom/element_registry.dart'; + +/// Get context of current widget. +typedef GetContext = BuildContext Function(); +/// Request focus of current widget. +typedef RequestFocus = void Function(); +/// Get the target platform. +typedef GetTargetPlatform = TargetPlatform Function(); +/// Get the cursor color according to the widget theme and platform theme. +typedef GetCursorColor = Color Function(); +/// Get the selection color according to the widget theme and platform theme. +typedef GetSelectionColor = Color Function(); +/// Get the cursor radius according to the target platform. +typedef GetCursorRadius = Radius Function(); +/// Get the text selection controls according to the target platform. +typedef GetTextSelectionControls = TextSelectionControls Function(); +typedef OnControllerCreated = void Function(KrakenController controller); + +/// Delegate methods of widget +class WidgetDelegate { + GetContext getContext; + RequestFocus requestFocus; + GetTargetPlatform getTargetPlatform; + GetCursorColor getCursorColor; + GetSelectionColor getSelectionColor; + GetCursorRadius getCursorRadius; + GetTextSelectionControls getTextSelectionControls; + + WidgetDelegate( + this.getContext, + this.requestFocus, + this.getTargetPlatform, + this.getCursorColor, + this.getSelectionColor, + this.getCursorRadius, + this.getTextSelectionControls, + ); +} + +class Kraken extends StatefulWidget { + // The background color for viewport, default to transparent. + final Color? background; + + // the width of krakenWidget + final double? viewportWidth; + + // the height of krakenWidget + final double? viewportHeight; + + // The initial bundle to load. + final KrakenBundle? bundle; + + // The animationController of Flutter Route object. + // Pass this object to KrakenWidget to make sure Kraken execute JavaScripts scripts after route transition animation completed. + final AnimationController? animationController; + + // The methods of the KrakenNavigateDelegation help you implement custom behaviors that are triggered + // during a kraken view's process of loading, and completing a navigation request. + final KrakenNavigationDelegate? navigationDelegate; + + // A method channel for receiving messaged from JavaScript code and sending message to JavaScript. + final KrakenMethodChannel? javaScriptChannel; + + // Register the RouteObserver to observer page navigation. + // This is useful if you wants to pause kraken timers and callbacks when kraken widget are hidden by page route. + // https://api.flutter.dev/flutter/widgets/RouteObserver-class.html + final RouteObserver>? routeObserver; + + // Trigger when kraken controller once created. + final OnControllerCreated? onControllerCreated; + + final LoadErrorHandler? onLoadError; + + final LoadHandler? onLoad; + + final JSErrorHandler ?onJSError; + + // Open a service to support Chrome DevTools for debugging. + // https://github.com/openkraken/devtools + final DevToolsService? devToolsService; + + final GestureListener? gestureListener; + + final HttpClientInterceptor? httpClientInterceptor; + + final UriParser? uriParser; + + KrakenController? get controller { + return KrakenController.getControllerOfName(shortHash(this)); + } + + // Set kraken http cache mode. + static void setHttpCacheMode(HttpCacheMode mode) { + HttpCacheController.mode = mode; + if (kDebugMode) { + print('Kraken http cache mode set to $mode.'); + } + } + + static bool _isValidCustomElementName(localName) { + return RegExp(r'^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$').hasMatch(localName); + } + + static void defineCustomElement(String tagName, ElementCreator creator) { + if (!_isValidCustomElementName(tagName)) { + throw ArgumentError('The element name "$tagName" is not valid.'); + } + defineElement(tagName.toUpperCase(), creator); + } + + loadBundle(KrakenBundle bundle) async { + await controller!.unload(); + await controller!.loadBundle( + bundle: bundle + ); + _evalBundle(controller!, animationController); + } + + @deprecated + loadContent(String bundleContent) async { + await controller!.unload(); + await controller!.loadBundle( + bundle: KrakenBundle.fromContent(bundleContent) + ); + _evalBundle(controller!, animationController); + } + + @deprecated + loadByteCode(Uint8List bundleByteCode) async { + await controller!.unload(); + await controller!.loadBundle( + bundle: KrakenBundle.fromBytecode(bundleByteCode) + ); + _evalBundle(controller!, animationController); + } + + @deprecated + loadURL(String bundleURL, { String? bundleContent, Uint8List? bundleByteCode }) async { + await controller!.unload(); + + KrakenBundle bundle; + if (bundleByteCode != null) { + bundle = KrakenBundle.fromBytecode(bundleByteCode, url: bundleURL); + } else if (bundleContent != null) { + bundle = KrakenBundle.fromContent(bundleContent, url: bundleURL); + } else { + bundle = KrakenBundle.fromUrl(bundleURL); + } + + await controller!.loadBundle( + bundle: bundle + ); + _evalBundle(controller!, animationController); + } + + @deprecated + loadPath(String bundlePath, { String? bundleContent, Uint8List? bundleByteCode }) async { + await controller!.unload(); + + KrakenBundle bundle; + if (bundleByteCode != null) { + bundle = KrakenBundle.fromBytecode(bundleByteCode, url: bundlePath); + } else if (bundleContent != null) { + bundle = KrakenBundle.fromContent(bundleContent, url: bundlePath); + } else { + bundle = KrakenBundle.fromUrl(bundlePath); + } + + await controller!.loadBundle( + bundle: bundle + ); + _evalBundle(controller!, animationController); + } + + reload() async { + await controller!.reload(); + } + + Kraken({ + Key? key, + this.viewportWidth, + this.viewportHeight, + this.bundle, + this.onControllerCreated, + this.onLoad, + this.navigationDelegate, + this.javaScriptChannel, + this.background, + this.gestureListener, + this.devToolsService, + // Kraken's http client interceptor. + this.httpClientInterceptor, + this.uriParser, + this.routeObserver, + // Kraken's viewportWidth options only works fine when viewportWidth is equal to window.physicalSize.width / window.devicePixelRatio. + // Maybe got unexpected error when change to other values, use this at your own risk! + // We will fixed this on next version released. (v0.6.0) + // Disable viewportWidth check and no assertion error report. + bool disableViewportWidthAssertion = false, + // Kraken's viewportHeight options only works fine when viewportHeight is equal to window.physicalSize.height / window.devicePixelRatio. + // Maybe got unexpected error when change to other values, use this at your own risk! + // We will fixed this on next version release. (v0.6.0) + // Disable viewportHeight check and no assertion error report. + bool disableViewportHeightAssertion = false, + // Callback functions when loading Javascript scripts failed. + this.onLoadError, + this.animationController, + this.onJSError + }) : super(key: key); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('viewportWidth', viewportWidth)); + properties.add(DiagnosticsProperty('viewportHeight', viewportHeight)); + } + + @override + _KrakenState createState() => _KrakenState(); + +} +class _KrakenState extends State with RouteAware { + Map>? _actionMap; + + final FocusNode _focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + _actionMap = >{ + // Action of focus. + NextFocusIntent: CallbackAction(onInvoke: _handleNextFocus), + PreviousFocusIntent: CallbackAction(onInvoke: _handlePreviousFocus), + + // Action of mouse move hotkeys. + MoveSelectionRightByLineTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightByLineText), + MoveSelectionLeftByLineTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftByLineText), + MoveSelectionRightByWordTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightByWordText), + MoveSelectionLeftByWordTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftByWordText), + MoveSelectionUpTextIntent: CallbackAction(onInvoke: _handleMoveSelectionUpText), + MoveSelectionDownTextIntent: CallbackAction(onInvoke: _handleMoveSelectionDownText), + MoveSelectionLeftTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftText), + MoveSelectionRightTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightText), + MoveSelectionToStartTextIntent: CallbackAction(onInvoke: _handleMoveSelectionToStartText), + MoveSelectionToEndTextIntent: CallbackAction(onInvoke: _handleMoveSelectionToEndText), + + // Action of selection hotkeys. + ExtendSelectionLeftTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftText), + ExtendSelectionRightTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightText), + ExtendSelectionUpTextIntent: CallbackAction(onInvoke: _handleExtendSelectionUpText), + ExtendSelectionDownTextIntent: CallbackAction(onInvoke: _handleExtendSelectionDownText), + ExpandSelectionToEndTextIntent: CallbackAction(onInvoke: _handleExtendSelectionToEndText), + ExpandSelectionToStartTextIntent: CallbackAction(onInvoke: _handleExtendSelectionToStartText), + ExpandSelectionLeftByLineTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftByLineText), + ExpandSelectionRightByLineTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightByLineText), + ExtendSelectionLeftByWordTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftByWordText), + ExtendSelectionRightByWordTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightByWordText), + }; + } + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: FocusableActionDetector( + actions: _actionMap, + focusNode: _focusNode, + onFocusChange: _handleFocusChange, + child: _KrakenRenderObjectWidget( + context.widget as Kraken, + widgetDelegate, + ) + ) + ); + } + + WidgetDelegate get widgetDelegate { + return WidgetDelegate( + _getContext, + _requestFocus, + _getTargetPlatform, + _getCursorColor, + _getSelectionColor, + _getCursorRadius, + _getTextSelectionControls, + ); + } + + // Get context of current widget. + BuildContext _getContext() { + return context; + } + + // Request focus of current widget. + void _requestFocus() { + _focusNode.requestFocus(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (widget.routeObserver != null) { + widget.routeObserver!.subscribe(this, ModalRoute.of(context)!); + } + } + + // Resume call timer and callbacks when kraken widget change to visible. + @override + void didPopNext() { + assert(widget.controller != null); + widget.controller!.resume(); + } + + // Pause all timer and callbacks when kraken widget has been invisible. + @override + void didPushNext() { + assert(widget.controller != null); + widget.controller!.pause(); + } + + @override + void dispose() { + if (widget.routeObserver != null) { + widget.routeObserver!.unsubscribe(this); + } + super.dispose(); + } + + + // Get the target platform. + TargetPlatform _getTargetPlatform() { + final ThemeData theme = Theme.of(context); + return theme.platform; + } + + // Get the cursor color according to the widget theme and platform theme. + Color _getCursorColor() { + Color cursorColor = CSSColor.initial; + TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context); + ThemeData theme = Theme.of(context); + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + cursorColor = selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; + break; + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + cursorColor = selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; + break; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + cursorColor = selectionTheme.cursorColor ?? theme.colorScheme.primary; + break; + + case TargetPlatform.linux: + case TargetPlatform.windows: + cursorColor = selectionTheme.cursorColor ?? theme.colorScheme.primary; + break; + } + + return cursorColor; + } + + // Get the selection color according to the widget theme and platform theme. + Color _getSelectionColor() { + Color selectionColor = CSSColor.initial.withOpacity(0.4); + TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context); + ThemeData theme = Theme.of(context); + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); + break; + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); + break; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); + break; + + case TargetPlatform.linux: + case TargetPlatform.windows: + selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); + break; + } + + return selectionColor; + } + + // Get the cursor radius according to the target platform. + Radius _getCursorRadius() { + Radius cursorRadius = const Radius.circular(2.0); + TargetPlatform platform = _getTargetPlatform(); + + switch (platform) { + case TargetPlatform.iOS: + cursorRadius = const Radius.circular(2.0); + break; + + case TargetPlatform.macOS: + cursorRadius = const Radius.circular(2.0); + break; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + break; + } + + return cursorRadius; + } + + // Get the text selection controls according to the target platform. + TextSelectionControls _getTextSelectionControls() { + TextSelectionControls _selectionControls; + TargetPlatform platform = _getTargetPlatform(); + + switch (platform) { + case TargetPlatform.iOS: + _selectionControls = cupertinoTextSelectionControls; + break; + + case TargetPlatform.macOS: + _selectionControls = cupertinoDesktopTextSelectionControls; + break; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + _selectionControls = materialTextSelectionControls; + break; + + case TargetPlatform.linux: + case TargetPlatform.windows: + _selectionControls = desktopTextSelectionControls; + break; + } + + return _selectionControls; + } + + // Handle focus change of focusNode. + void _handleFocusChange(bool focused) { + dom.Element rootElement = _findRootElement(); + List focusableElements = _findFocusableElements(rootElement); + if (focusableElements.isNotEmpty) { + dom.Element? focusedElement = _findFocusedElement(focusableElements); + // Currently only input element is focusable. + if (focused) { + if (dom.InputElement.focusInputElement == null) { + (focusableElements[0] as dom.InputElement).focus(); + } + } else { + if (focusedElement != null) { + (focusedElement as dom.InputElement).blur(); + } + } + } + } + + // Handle focus action usually by pressing the [Tab] hotkey. + void _handleNextFocus(NextFocusIntent intent) { + dom.Element rootElement = _findRootElement(); + List focusableElements = _findFocusableElements(rootElement); + if (focusableElements.isNotEmpty) { + dom.Element? focusedElement = _findFocusedElement(focusableElements); + // None focusable element is focused, focus the first focusable element. + if (focusedElement == null) { + _focusNode.requestFocus(); + (focusableElements[0] as dom.InputElement).focus(); + + // Some focusable element is focused, focus the next element, if it is the last focusable element, + // then focus the next widget. + } else { + int idx = focusableElements.indexOf(focusedElement); + if (idx == focusableElements.length - 1) { + _focusNode.nextFocus(); + (focusableElements[focusableElements.length - 1] as dom.InputElement).blur(); + } else { + _focusNode.requestFocus(); + (focusableElements[idx] as dom.InputElement).blur(); + (focusableElements[idx + 1] as dom.InputElement).focus(); + } + } + + // None focusable element exists, focus the next widget. + } else { + _focusNode.nextFocus(); + } + } + + // Handle focus action usually by pressing the [Shift]+[Tab] hotkey in the reverse direction. + void _handlePreviousFocus(PreviousFocusIntent intent) { + dom.Element rootElement = _findRootElement(); + List focusableElements = _findFocusableElements(rootElement); + if (focusableElements.isNotEmpty) { + dom.Element? focusedElement = _findFocusedElement(focusableElements); + // None editable is focused, focus the last editable. + if (focusedElement == null) { + _focusNode.requestFocus(); + (focusableElements[focusableElements.length - 1] as dom.InputElement).focus(); + + // Some editable is focused, focus the previous editable, if it is the first editable, + // then focus the previous widget. + } else { + int idx = focusableElements.indexOf(focusedElement); + if (idx == 0) { + _focusNode.previousFocus(); + (focusableElements[0] as dom.InputElement).blur(); + } else { + _focusNode.requestFocus(); + (focusableElements[idx] as dom.InputElement).blur(); + (focusableElements[idx - 1] as dom.InputElement).focus(); + } + } + // None editable exists, focus the previous widget. + } else { + _focusNode.previousFocus(); + } + } + + void _handleMoveSelectionRightByLineText(MoveSelectionRightByLineTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionRightByLine(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionLeftByLineText(MoveSelectionLeftByLineTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionLeftByLine(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionRightByWordText(MoveSelectionRightByWordTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionRightByWord(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionLeftByWordText(MoveSelectionLeftByWordTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionLeftByWord(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionUpText(MoveSelectionUpTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionUp(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionDownText(MoveSelectionDownTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionDown(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionLeftText(MoveSelectionLeftTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionLeft(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionRightText(MoveSelectionRightTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionRight(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionToEndText(MoveSelectionToEndTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionToEnd(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleMoveSelectionToStartText(MoveSelectionToStartTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.moveSelectionToStart(SelectionChangedCause.keyboard); + // Make caret visible while moving cursor. + focusedElement.scrollToCaret(); + } + } + } + + void _handleExtendSelectionLeftText(ExtendSelectionLeftTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionLeft(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionRightText(ExtendSelectionRightTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionRight(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionUpText(ExtendSelectionUpTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionUp(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionDownText(ExtendSelectionDownTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionDown(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionToEndText(ExpandSelectionToEndTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.expandSelectionToEnd(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionToStartText(ExpandSelectionToStartTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.expandSelectionToStart(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionLeftByLineText(ExpandSelectionLeftByLineTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.expandSelectionLeftByLine(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionRightByLineText(ExpandSelectionRightByLineTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.expandSelectionRightByLine(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionLeftByWordText(ExtendSelectionLeftByWordTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionLeftByWord(SelectionChangedCause.keyboard); + } + } + } + + void _handleExtendSelectionRightByWordText(ExtendSelectionRightByWordTextIntent intent) { + dom.Element? focusedElement = _findFocusedElement(); + if (focusedElement != null) { + RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; + if (focusedRenderEditable != null) { + focusedRenderEditable.extendSelectionRightByWord(SelectionChangedCause.keyboard); + } + } + } + + // Find RenderViewportBox in the renderObject tree. + RenderViewportBox? _findRenderViewportBox(RenderObject parent) { + RenderViewportBox? result; + parent.visitChildren((RenderObject child) { + if (child is RenderViewportBox) { + result = child; + } else { + result = _findRenderViewportBox(child); + } + }); + return result; + } + + // Find root element of dom tree. + dom.Element _findRootElement() { + RenderObject? _rootRenderObject = context.findRenderObject(); + RenderViewportBox? renderViewportBox = _findRenderViewportBox(_rootRenderObject!); + KrakenController controller = (renderViewportBox as RenderObjectWithControllerMixin).controller!; + dom.Element documentElement = controller.view.document.documentElement!; + return documentElement; + } + + // Find all the focusable elements in the element tree. + List _findFocusableElements(dom.Element element) { + List result = []; + traverseElement(element, (dom.Element child) { + // Currently only input element is focusable. + if (child is dom.InputElement) { + result.add(child); + } + }); + return result; + } + + // Find the focused element in the element tree. + dom.Element? _findFocusedElement([List? focusableElements]) { + dom.Element? result; + if (focusableElements == null) { + dom.Element rootElement = _findRootElement(); + focusableElements = _findFocusableElements(rootElement); + } + + if (focusableElements.isNotEmpty) { + // Currently only input element is focusable. + for (dom.Element inputElement in focusableElements) { + RenderEditable? renderEditable = (inputElement as dom.InputElement).renderEditable; + if (renderEditable != null && renderEditable.hasFocus) { + result = inputElement; + break; + } + } + } + return result; + } +} + +class _KrakenRenderObjectWidget extends SingleChildRenderObjectWidget { + // Creates a widget that visually hides its child. + const _KrakenRenderObjectWidget( + Kraken widget, + WidgetDelegate widgetDelegate, + {Key? key} + ) : _krakenWidget = widget, + _widgetDelegate = widgetDelegate, + super(key: key); + + final Kraken _krakenWidget; + final WidgetDelegate _widgetDelegate; + + @override + RenderObject createRenderObject(BuildContext context) { + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_START); + } + + double viewportWidth = _krakenWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; + double viewportHeight = _krakenWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; + + if (viewportWidth == 0.0 && viewportHeight == 0.0) { + throw FlutterError('''Can't get viewportSize from window. Please set viewportWidth and viewportHeight manually. +This situation often happened when you trying creating kraken when FlutterView not initialized.'''); + } + + KrakenController controller = KrakenController( + shortHash(_krakenWidget.hashCode), + viewportWidth, + viewportHeight, + background: _krakenWidget.background, + showPerformanceOverlay: Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, + bundle: _krakenWidget.bundle, + onLoad: _krakenWidget.onLoad, + onLoadError: _krakenWidget.onLoadError, + onJSError: _krakenWidget.onJSError, + methodChannel: _krakenWidget.javaScriptChannel, + gestureListener: _krakenWidget.gestureListener, + navigationDelegate: _krakenWidget.navigationDelegate, + devToolsService: _krakenWidget.devToolsService, + httpClientInterceptor: _krakenWidget.httpClientInterceptor, + widgetDelegate: _widgetDelegate, + uriParser: _krakenWidget.uriParser + ); + + OnControllerCreated? onControllerCreated = _krakenWidget.onControllerCreated; + if (onControllerCreated != null) { + onControllerCreated(controller); + } + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_END); + } + + return controller.view.getRootRenderObject(); + } + + @override + void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { + super.updateRenderObject(context, renderObject); + KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; + controller.name = shortHash(_krakenWidget.hashCode); + + bool viewportWidthHasChanged = controller.view.viewportWidth != _krakenWidget.viewportWidth; + bool viewportHeightHasChanged = controller.view.viewportHeight != _krakenWidget.viewportHeight; + + double viewportWidth = _krakenWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; + double viewportHeight = _krakenWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; + + if (controller.view.document.documentElement == null) return; + + if (viewportWidthHasChanged) { + controller.view.viewportWidth = viewportWidth; + controller.view.document.documentElement!.renderStyle.width = CSSLengthValue(viewportWidth, CSSLengthType.PX); + } + + if (viewportHeightHasChanged) { + controller.view.viewportHeight = viewportHeight; + controller.view.document.documentElement!.renderStyle.height = CSSLengthValue(viewportHeight, CSSLengthType.PX); + } + } + + @override + void didUnmountRenderObject(covariant RenderObject renderObject) { + KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; + controller.dispose(); + } + + @override + _KrakenRenderObjectElement createElement() { + return _KrakenRenderObjectElement(this); + } +} + +class _KrakenRenderObjectElement extends SingleChildRenderObjectElement { + _KrakenRenderObjectElement(_KrakenRenderObjectWidget widget) : super(widget); + + @override + void mount(Element? parent, Object? newSlot) async { + super.mount(parent, newSlot); + + KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; + + // We should make sure every flutter elements created under kraken can be walk up to the root. + // So we bind _KrakenRenderObjectElement into KrakenController, and widgetElements created by controller can follow this to the root. + controller.rootFlutterElement = this; + + if (controller.bundle == null || (controller.bundle?.content == null && controller.bundle?.bytecode == null && controller.bundle?.src == null)) { + return; + } + + await controller.loadBundle(); + + _evalBundle(controller, widget._krakenWidget.animationController); + } + + // RenderObjects created by kraken are manager by kraken itself. There are no needs to operate renderObjects on _KrakenRenderObjectElement. + @override + void insertRenderObjectChild(RenderObject child, Object? slot) {} + @override + void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {} + @override + void removeRenderObjectChild(RenderObject child, Object? slot) {} + + @override + _KrakenRenderObjectWidget get widget => super.widget as _KrakenRenderObjectWidget; +} + +void _evalBundle(KrakenController controller, AnimationController? animationController) async { + // Execute JavaScript scripts will block the Flutter UI Threads. + // Listen for animationController listener to make sure to execute Javascript after route transition had completed. + if (animationController != null) { + animationController.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + controller.evalBundle(); + } + }); + } else { + await controller.evalBundle(); + } +} diff --git a/kraken/lib/src/widget/widget_to_element_adaptor.dart b/kraken/lib/src/widget/widget_to_element_adaptor.dart new file mode 100644 index 0000000000..a1fd608c01 --- /dev/null +++ b/kraken/lib/src/widget/widget_to_element_adaptor.dart @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:kraken/kraken.dart'; +import 'package:kraken/dom.dart' as dom; + +import 'element_to_widget_adaptor.dart'; + +class KrakenRenderObjectToWidgetAdapter extends RenderObjectWidget { + /// Creates a bridge from a [RenderObject] to an [Element] tree. + /// + /// Used by [WidgetsBinding] to attach the root widget to the [RenderView]. + KrakenRenderObjectToWidgetAdapter({ + this.child, + required this.container, + this.debugShortDescription, + }) : super(key: GlobalObjectKey(container)); + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget? child; + + /// The [RenderObject] that is the parent of the [Element] created by this widget. + final ContainerRenderObjectMixin> container; + + /// A short description of this widget used by debugging aids. + final String? debugShortDescription; + + @override + KrakenRenderObjectToWidgetElement createElement() => KrakenRenderObjectToWidgetElement(this); + + @override + ContainerRenderObjectMixin> createRenderObject(BuildContext context) => container; + + @override + void updateRenderObject(BuildContext context, RenderObject renderObject) { } + + /// Inflate this widget and actually set the resulting [RenderObject] as the + /// child of [container]. + KrakenRenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, RenderObjectElement parentElement, bool needBuild) { + Element? element; + + owner.lockState(() { + element = createElement(); + assert(element != null); + }); + + // If renderview is building,skip the buildScope phase. + if (!needBuild) { + if (element != null) { + element?.mount(parentElement, null); + } + } else { + owner.buildScope(element!, () { + if (element != null) { + element?.mount(parentElement, null); + } + }); + } + + return element! as KrakenRenderObjectToWidgetElement; + } + + @override + String toStringShort() => debugShortDescription ?? super.toStringShort(); +} + +class KrakenRenderObjectToWidgetElement extends RenderObjectElement { + /// Creates an element that is hosted by a [RenderObject]. + /// + /// The [RenderObject] created by this element is not automatically set as a + /// child of the hosting [RenderObject]. To actually attach this element to + /// the render tree, call [RenderObjectToWidgetAdapter.attachToRenderTree]. + KrakenRenderObjectToWidgetElement(KrakenRenderObjectToWidgetAdapter widget) : super(widget); + + @override + KrakenRenderObjectToWidgetAdapter get widget => super.widget as KrakenRenderObjectToWidgetAdapter; + + Element? _child; + + static const Object _rootChildSlot = Object(); + + @override + void visitChildren(ElementVisitor visitor) { + if (_child != null) + visitor(_child!); + } + + @override + void forgetChild(Element child) { + assert(child == _child); + _child = null; + super.forgetChild(child); + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); + _rebuild(); + assert(_child != null); + } + + @override + void update(RenderObjectToWidgetAdapter newWidget) { + super.update(newWidget); + assert(widget == newWidget); + _rebuild(); + } + + // When we are assigned a new widget, we store it here + // until we are ready to update to it. + Widget? _newWidget; + + @override + void performRebuild() { + if (_newWidget != null) { + // _newWidget can be null if, for instance, we were rebuilt + // due to a reassemble. + final Widget newWidget = _newWidget!; + _newWidget = null; + update(newWidget as RenderObjectToWidgetAdapter); + } + super.performRebuild(); + assert(_newWidget == null); + } + + void _rebuild() { + try { + _child = updateChild(_child, widget.child, _rootChildSlot); + } catch (exception, stack) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets library', + context: ErrorDescription('attaching to the render tree'), + ); + FlutterError.reportError(details); + final Widget error = ErrorWidget.builder(details); + _child = updateChild(null, error, _rootChildSlot); + } + } + + @override + ContainerRenderObjectMixin> get renderObject => super.renderObject as ContainerRenderObjectMixin>; + + @override + void insertRenderObjectChild(RenderObject child, Object? slot) { + assert(renderObject.debugValidateChild(child)); + renderObject.add(child as RenderBox); + } + + @override + void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { + assert(false); + } + + @override + void removeRenderObjectChild(RenderObject child, Object? slot) { + renderObject.remove(child as RenderBox); + } +} + +abstract class WidgetElement extends dom.Element { + late Widget _widget; + _KrakenAdapterWidgetState? _state; + + WidgetElement(dom.EventTargetContext? context, { + Map? defaultStyle, + bool isIntrinsicBox = false, + // WidgetElement Adds repaintBoundary by default to prevent the internal paint process from affecting the outside. + // If a lot of WidgetElement is used in a scene, you need to modify the default repaintBoundary according to the scene analysis. + // Otherwise it will cause performance problems by creating most layers. + bool isDefaultRepaintBoundary = true, + }) : super( + context, + defaultStyle: defaultStyle, + isIntrinsicBox: isIntrinsicBox, + isDefaultRepaintBoundary: isDefaultRepaintBoundary, + ) { + WidgetsFlutterBinding.ensureInitialized(); + _state = _KrakenAdapterWidgetState(this, properties, childNodes); + _widget = _KrakenAdapterWidget(_state!); + } + + Widget build(BuildContext context, Map properties, List children); + + @override + void didDetachRenderer() { + super.didDetachRenderer(); + } + + @override + void didAttachRenderer() { + super.didAttachRenderer(); + + _attachWidget(_widget); + } + + @override + void removeProperty(String key) { + super.removeProperty(key); + if (_state != null) { + _state!.onAttributeChanged(properties); + } + } + + @override + void setProperty(String key, dynamic value) { + super.setProperty(key, value); + if (_state != null) { + _state!.onAttributeChanged(properties); + } + } + + @override + dom.Node appendChild(dom.Node child) { + super.appendChild(child); + + if (_state != null) { + _state!.onChildrenChanged(childNodes); + } + + return child; + } + + @override + dom.Node removeChild(dom.Node child) { + super.removeChild(child); + + if (_state != null) { + _state!.onChildrenChanged(children); + } + + return child; + } + + RenderObjectElement? renderObjectElement; + + void _attachWidget(Widget widget) { + RenderObjectElement rootFlutterElement = ownerDocument.controller.rootFlutterElement; + + KrakenRenderObjectToWidgetAdapter adaptor = KrakenRenderObjectToWidgetAdapter( + child: widget, + container: renderBoxModel as ContainerRenderObjectMixin> + ); + + Element? parentFlutterElement; + if (parentNode is WidgetElement) { + parentFlutterElement = (parentNode as WidgetElement).renderObjectElement; + } else { + parentFlutterElement = (parentNode as dom.Element).flutterElement; + } + + renderObjectElement = adaptor.attachToRenderTree(rootFlutterElement.owner!, (parentFlutterElement ?? rootFlutterElement) as RenderObjectElement, parentFlutterElement == null); + } +} + +class _KrakenAdapterWidget extends StatefulWidget { + final _KrakenAdapterWidgetState _state; + + _KrakenAdapterWidget(this._state); + + @override + State createState() { + return _state; + } +} + + +class _KrakenAdapterWidgetState extends State<_KrakenAdapterWidget> { + Map _properties; + final WidgetElement _element; + late List _childNodes; + + _KrakenAdapterWidgetState(this._element, this._properties, this._childNodes) { + } + + void onAttributeChanged(Map properties) { + if (mounted) { + setState(() { + _properties = properties; + }); + } else { + _properties = properties; + } + } + + List convertNodeListToWidgetList(List childNodes) { + List children = List.generate(childNodes.length, (index) { + if (childNodes[index] is WidgetElement) { + _KrakenAdapterWidgetState state = (childNodes[index] as WidgetElement)._state!; + return state.build(context); + } else { + return childNodes[index].flutterWidget ?? KrakenElementToWidgetAdaptor(childNodes[index], key: Key(index.toString())); + } + }); + + return children; + } + + void onChildrenChanged(List childNodes) { + if (mounted) { + setState(() { + _childNodes = childNodes; + }); + } else { + _childNodes = childNodes; + } + } + + @override + Widget build(BuildContext context) { + return _element.build(context, _properties, convertNodeListToWidgetList(_childNodes)); + } +} diff --git a/kraken/lib/widget.dart b/kraken/lib/widget.dart index ff55f60dea..5a0d077f0b 100644 --- a/kraken/lib/widget.dart +++ b/kraken/lib/widget.dart @@ -2,997 +2,7 @@ * Copyright (C) 2019-present Alibaba Inc. All rights reserved. * Author: Kraken Team. */ -import 'dart:io'; -import 'dart:ui'; -import 'dart:ffi'; -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:kraken/kraken.dart'; -import 'package:kraken/rendering.dart'; -import 'package:kraken/dom.dart' as dom; -import 'package:kraken/module.dart'; -import 'package:kraken/gesture.dart'; -import 'package:kraken/css.dart'; -import 'package:kraken/src/dom/element_registry.dart'; -import 'package:kraken/src/dom/element_manager.dart'; -import 'package:kraken/bridge.dart'; - -/// Get context of current widget. -typedef GetContext = BuildContext Function(); -/// Request focus of current widget. -typedef RequestFocus = void Function(); -/// Get the target platform. -typedef GetTargetPlatform = TargetPlatform Function(); -/// Get the cursor color according to the widget theme and platform theme. -typedef GetCursorColor = Color Function(); -/// Get the selection color according to the widget theme and platform theme. -typedef GetSelectionColor = Color Function(); -/// Get the cursor radius according to the target platform. -typedef GetCursorRadius = Radius Function(); -/// Get the text selection controls according to the target platform. -typedef GetTextSelectionControls = TextSelectionControls Function(); - -/// Delegate methods of widget -class WidgetDelegate { - GetContext getContext; - RequestFocus requestFocus; - GetTargetPlatform getTargetPlatform; - GetCursorColor getCursorColor; - GetSelectionColor getSelectionColor; - GetCursorRadius getCursorRadius; - GetTextSelectionControls getTextSelectionControls; - - WidgetDelegate( - this.getContext, - this.requestFocus, - this.getTargetPlatform, - this.getCursorColor, - this.getSelectionColor, - this.getCursorRadius, - this.getTextSelectionControls, - ); -} - -const Map _defaultStyle = { - DISPLAY: INLINE_BLOCK, -}; - -abstract class WidgetElement extends dom.Element { - late Element _renderViewElement; - late BuildOwner _buildOwner; - late Widget _widget; - _KrakenAdapterWidgetPropertiesState? _propertiesState; - WidgetElement(int targetId, Pointer nativeEventTarget, dom.ElementManager elementManager) - : super( - targetId, - nativeEventTarget, - elementManager, - isIntrinsicBox: true, - defaultStyle: _defaultStyle - ); - - Widget build(BuildContext context, Map properties); - - @override - void didAttachRenderer() { - super.didAttachRenderer(); - - WidgetsFlutterBinding.ensureInitialized(); - - _propertiesState = _KrakenAdapterWidgetPropertiesState(this, properties); - _widget = _KrakenAdapterWidget(_propertiesState!); - _attachWidget(_widget); - } - - @override - void removeProperty(String key) { - super.removeProperty(key); - if (_propertiesState != null) { - _propertiesState!.onAttributeChanged(properties); - } - } - - @override - void setProperty(String key, dynamic value) { - super.setProperty(key, value); - if (_propertiesState != null) { - _propertiesState!.onAttributeChanged(properties); - } - } - - void _handleBuildScheduled() { - // Register drawFrame callback same with [WidgetsBinding.drawFrame] - SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) { - _buildOwner.buildScope(_renderViewElement); - // ignore: invalid_use_of_protected_member - RendererBinding.instance!.drawFrame(); - _buildOwner.finalizeTree(); - }); - SchedulerBinding.instance!.ensureVisualUpdate(); - } - - void _attachWidget(Widget widget) { - // A new buildOwner difference with flutter's buildOwner - _buildOwner = BuildOwner(focusManager: WidgetsBinding.instance!.buildOwner!.focusManager); - _buildOwner.onBuildScheduled = _handleBuildScheduled; - _renderViewElement = RenderObjectToWidgetAdapter( - child: widget, - container: renderBoxModel as RenderObjectWithChildMixin, - ).attachToRenderTree(_buildOwner); - } -} - -class _KrakenAdapterWidget extends StatefulWidget { - final _KrakenAdapterWidgetPropertiesState _state; - _KrakenAdapterWidget(this._state); - @override - State createState() { - return _state; - } -} - -class _KrakenAdapterWidgetPropertiesState extends State<_KrakenAdapterWidget> { - Map _properties; - final WidgetElement _element; - _KrakenAdapterWidgetPropertiesState(this._element, this._properties); - - void onAttributeChanged(Map properties) { - setState(() { - _properties = properties; - }); - } - - @override - Widget build(BuildContext context) { - return _element.build(context, _properties); - } -} - -class Kraken extends StatefulWidget { - // The background color for viewport, default to transparent. - final Color? background; - - // the width of krakenWidget - final double? viewportWidth; - - // the height of krakenWidget - final double? viewportHeight; - - // The initial URL to load. - final String? bundleURL; - - // The initial assets path to load. - final String? bundlePath; - - // The initial raw javascript content to load. - final String? bundleContent; - - // The initial raw bytecode to load. - final Uint8List? bundleByteCode; - - // The animationController of Flutter Route object. - // Pass this object to KrakenWidget to make sure Kraken execute JavaScripts scripts after route transition animation completed. - final AnimationController? animationController; - - // The methods of the KrakenNavigateDelegation help you implement custom behaviors that are triggered - // during a kraken view's process of loading, and completing a navigation request. - final KrakenNavigationDelegate? navigationDelegate; - - // A method channel for receiving messaged from JavaScript code and sending message to JavaScript. - final KrakenMethodChannel? javaScriptChannel; - - final LoadErrorHandler? onLoadError; - - final LoadHandler? onLoad; - - final JSErrorHandler ?onJSError; - - // Open a service to support Chrome DevTools for debugging. - // https://github.com/openkraken/devtools - final DevToolsService? devToolsService; - - final GestureListener? gestureListener; - - final HttpClientInterceptor? httpClientInterceptor; - - final UriParser? uriParser; - - KrakenController? get controller { - return KrakenController.getControllerOfName(shortHash(this)); - } - - // Set kraken http cache mode. - static void setHttpCacheMode(HttpCacheMode mode) { - HttpCacheController.mode = mode; - if (kDebugMode) { - print('Kraken http cache mode set to $mode.'); - } - } - - static bool _isValidCustomElementName(localName) { - return RegExp(r'^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$').hasMatch(localName); - } - - static void defineCustomElement(String tagName, ElementCreator creator) { - if (!_isValidCustomElementName(tagName)) { - throw ArgumentError('The element name "$tagName" is not valid.'); - } - defineElement(tagName.toUpperCase(), creator); - } - - loadContent(String bundleContent) async { - await controller!.unload(); - await controller!.loadBundle( - bundleContent: bundleContent - ); - _evalBundle(controller!, animationController); - } - - loadByteCode(Uint8List bytecode) async { - await controller!.unload(); - await controller!.loadBundle( - bundleByteCode: bytecode - ); - _evalBundle(controller!, animationController); - } - - loadURL(String bundleURL) async { - await controller!.unload(); - await controller!.loadBundle( - bundleURL: bundleURL - ); - _evalBundle(controller!, animationController); - } - - loadPath(String bundlePath) async { - await controller!.unload(); - await controller!.loadBundle( - bundlePath: bundlePath - ); - _evalBundle(controller!, animationController); - } - - reload() async { - await controller!.reload(); - } - - Kraken({ - Key? key, - this.viewportWidth, - this.viewportHeight, - this.bundleURL, - this.bundlePath, - this.bundleContent, - this.bundleByteCode, - this.onLoad, - this.navigationDelegate, - this.javaScriptChannel, - this.background, - this.gestureListener, - this.devToolsService, - // Kraken's http client interceptor. - this.httpClientInterceptor, - this.uriParser, - // Kraken's viewportWidth options only works fine when viewportWidth is equal to window.physicalSize.width / window.devicePixelRatio. - // Maybe got unexpected error when change to other values, use this at your own risk! - // We will fixed this on next version released. (v0.6.0) - // Disable viewportWidth check and no assertion error report. - bool disableViewportWidthAssertion = false, - // Kraken's viewportHeight options only works fine when viewportHeight is equal to window.physicalSize.height / window.devicePixelRatio. - // Maybe got unexpected error when change to other values, use this at your own risk! - // We will fixed this on next version release. (v0.6.0) - // Disable viewportHeight check and no assertion error report. - bool disableViewportHeightAssertion = false, - // Callback functions when loading Javascript scripts failed. - this.onLoadError, - this.animationController, - this.onJSError - }) : super(key: key); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('viewportWidth', viewportWidth)); - properties.add(DiagnosticsProperty('viewportHeight', viewportHeight)); - } - - @override - _KrakenState createState() => _KrakenState(); - -} -class _KrakenState extends State { - Map>? _actionMap; - - final FocusNode _focusNode = FocusNode(); - - @override - void initState() { - super.initState(); - _actionMap = >{ - // Action of focus. - NextFocusIntent: CallbackAction(onInvoke: _handleNextFocus), - PreviousFocusIntent: CallbackAction(onInvoke: _handlePreviousFocus), - - // Action of mouse move hotkeys. - MoveSelectionRightByLineTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightByLineText), - MoveSelectionLeftByLineTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftByLineText), - MoveSelectionRightByWordTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightByWordText), - MoveSelectionLeftByWordTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftByWordText), - MoveSelectionUpTextIntent: CallbackAction(onInvoke: _handleMoveSelectionUpText), - MoveSelectionDownTextIntent: CallbackAction(onInvoke: _handleMoveSelectionDownText), - MoveSelectionLeftTextIntent: CallbackAction(onInvoke: _handleMoveSelectionLeftText), - MoveSelectionRightTextIntent: CallbackAction(onInvoke: _handleMoveSelectionRightText), - MoveSelectionToStartTextIntent: CallbackAction(onInvoke: _handleMoveSelectionToStartText), - MoveSelectionToEndTextIntent: CallbackAction(onInvoke: _handleMoveSelectionToEndText), - - // Action of selection hotkeys. - ExtendSelectionLeftTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftText), - ExtendSelectionRightTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightText), - ExtendSelectionUpTextIntent: CallbackAction(onInvoke: _handleExtendSelectionUpText), - ExtendSelectionDownTextIntent: CallbackAction(onInvoke: _handleExtendSelectionDownText), - ExpandSelectionToEndTextIntent: CallbackAction(onInvoke: _handleExtendSelectionToEndText), - ExpandSelectionToStartTextIntent: CallbackAction(onInvoke: _handleExtendSelectionToStartText), - ExpandSelectionLeftByLineTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftByLineText), - ExpandSelectionRightByLineTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightByLineText), - ExtendSelectionLeftByWordTextIntent: CallbackAction(onInvoke: _handleExtendSelectionLeftByWordText), - ExtendSelectionRightByWordTextIntent: CallbackAction(onInvoke: _handleExtendSelectionRightByWordText), - }; - } - - @override - Widget build(BuildContext context) { - return RepaintBoundary( - child: FocusableActionDetector( - actions: _actionMap, - focusNode: _focusNode, - onFocusChange: _handleFocusChange, - child: _KrakenRenderObjectWidget( - context.widget as Kraken, - widgetDelegate, - ) - ) - ); - } - - WidgetDelegate get widgetDelegate { - return WidgetDelegate( - _getContext, - _requestFocus, - _getTargetPlatform, - _getCursorColor, - _getSelectionColor, - _getCursorRadius, - _getTextSelectionControls, - ); - } - - // Get context of current widget. - BuildContext _getContext() { - return context; - } - - // Request focus of current widget. - void _requestFocus() { - _focusNode.requestFocus(); - } - - // Get the target platform. - TargetPlatform _getTargetPlatform() { - final ThemeData theme = Theme.of(context); - return theme.platform; - } - - // Get the cursor color according to the widget theme and platform theme. - Color _getCursorColor() { - Color cursorColor = CSSColor.initial; - TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context); - ThemeData theme = Theme.of(context); - - switch (theme.platform) { - case TargetPlatform.iOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - cursorColor = selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; - break; - - case TargetPlatform.macOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - cursorColor = selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; - break; - - case TargetPlatform.android: - case TargetPlatform.fuchsia: - cursorColor = selectionTheme.cursorColor ?? theme.colorScheme.primary; - break; - - case TargetPlatform.linux: - case TargetPlatform.windows: - cursorColor = selectionTheme.cursorColor ?? theme.colorScheme.primary; - break; - } - - return cursorColor; - } - - // Get the selection color according to the widget theme and platform theme. - Color _getSelectionColor() { - Color selectionColor = CSSColor.initial.withOpacity(0.4); - TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context); - ThemeData theme = Theme.of(context); - - switch (theme.platform) { - case TargetPlatform.iOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); - break; - - case TargetPlatform.macOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); - break; - - case TargetPlatform.android: - case TargetPlatform.fuchsia: - selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); - break; - - case TargetPlatform.linux: - case TargetPlatform.windows: - selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); - break; - } - - return selectionColor; - } - - // Get the cursor radius according to the target platform. - Radius _getCursorRadius() { - Radius cursorRadius = const Radius.circular(2.0); - TargetPlatform platform = _getTargetPlatform(); - - switch (platform) { - case TargetPlatform.iOS: - cursorRadius = const Radius.circular(2.0); - break; - - case TargetPlatform.macOS: - cursorRadius = const Radius.circular(2.0); - break; - - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - break; - } - - return cursorRadius; - } - - // Get the text selection controls according to the target platform. - TextSelectionControls _getTextSelectionControls() { - TextSelectionControls _selectionControls; - TargetPlatform platform = _getTargetPlatform(); - - switch (platform) { - case TargetPlatform.iOS: - _selectionControls = cupertinoTextSelectionControls; - break; - - case TargetPlatform.macOS: - _selectionControls = cupertinoDesktopTextSelectionControls; - break; - - case TargetPlatform.android: - case TargetPlatform.fuchsia: - _selectionControls = materialTextSelectionControls; - break; - - case TargetPlatform.linux: - case TargetPlatform.windows: - _selectionControls = desktopTextSelectionControls; - break; - } - - return _selectionControls; - } - - // Handle focus change of focusNode. - void _handleFocusChange(bool focused) { - dom.Element rootElement = _findRootElement(); - List focusableElements = _findFocusableElements(rootElement); - if (focusableElements.isNotEmpty) { - dom.Element? focusedElement = _findFocusedElement(focusableElements); - // Currently only input element is focusable. - if (focused) { - if (dom.InputElement.focusInputElement == null) { - (focusableElements[0] as dom.InputElement).focus(); - } - } else { - if (focusedElement != null) { - (focusedElement as dom.InputElement).blur(); - } - } - } - } - - // Handle focus action usually by pressing the [Tab] hotkey. - void _handleNextFocus(NextFocusIntent intent) { - dom.Element rootElement = _findRootElement(); - List focusableElements = _findFocusableElements(rootElement); - if (focusableElements.isNotEmpty) { - dom.Element? focusedElement = _findFocusedElement(focusableElements); - // None focusable element is focused, focus the first focusable element. - if (focusedElement == null) { - _focusNode.requestFocus(); - (focusableElements[0] as dom.InputElement).focus(); - - // Some focusable element is focused, focus the next element, if it is the last focusable element, - // then focus the next widget. - } else { - int idx = focusableElements.indexOf(focusedElement); - if (idx == focusableElements.length - 1) { - _focusNode.nextFocus(); - (focusableElements[focusableElements.length - 1] as dom.InputElement).blur(); - } else { - _focusNode.requestFocus(); - (focusableElements[idx] as dom.InputElement).blur(); - (focusableElements[idx + 1] as dom.InputElement).focus(); - } - } - - // None focusable element exists, focus the next widget. - } else { - _focusNode.nextFocus(); - } - } - - // Handle focus action usually by pressing the [Shift]+[Tab] hotkey in the reverse direction. - void _handlePreviousFocus(PreviousFocusIntent intent) { - dom.Element rootElement = _findRootElement(); - List focusableElements = _findFocusableElements(rootElement); - if (focusableElements.isNotEmpty) { - dom.Element? focusedElement = _findFocusedElement(focusableElements); - // None editable is focused, focus the last editable. - if (focusedElement == null) { - _focusNode.requestFocus(); - (focusableElements[focusableElements.length - 1] as dom.InputElement).focus(); - - // Some editable is focused, focus the previous editable, if it is the first editable, - // then focus the previous widget. - } else { - int idx = focusableElements.indexOf(focusedElement); - if (idx == 0) { - _focusNode.previousFocus(); - (focusableElements[0] as dom.InputElement).blur(); - } else { - _focusNode.requestFocus(); - (focusableElements[idx] as dom.InputElement).blur(); - (focusableElements[idx - 1] as dom.InputElement).focus(); - } - } - // None editable exists, focus the previous widget. - } else { - _focusNode.previousFocus(); - } - } - - void _handleMoveSelectionRightByLineText(MoveSelectionRightByLineTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionRightByLine(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionLeftByLineText(MoveSelectionLeftByLineTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionLeftByLine(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionRightByWordText(MoveSelectionRightByWordTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionRightByWord(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionLeftByWordText(MoveSelectionLeftByWordTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionLeftByWord(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionUpText(MoveSelectionUpTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionUp(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionDownText(MoveSelectionDownTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionDown(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionLeftText(MoveSelectionLeftTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionLeft(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionRightText(MoveSelectionRightTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionRight(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionToEndText(MoveSelectionToEndTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionToEnd(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleMoveSelectionToStartText(MoveSelectionToStartTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.moveSelectionToStart(SelectionChangedCause.keyboard); - // Make caret visible while moving cursor. - focusedElement.scrollToCaret(); - } - } - } - - void _handleExtendSelectionLeftText(ExtendSelectionLeftTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionLeft(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionRightText(ExtendSelectionRightTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionRight(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionUpText(ExtendSelectionUpTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionUp(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionDownText(ExtendSelectionDownTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionDown(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionToEndText(ExpandSelectionToEndTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.expandSelectionToEnd(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionToStartText(ExpandSelectionToStartTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.expandSelectionToStart(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionLeftByLineText(ExpandSelectionLeftByLineTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.expandSelectionLeftByLine(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionRightByLineText(ExpandSelectionRightByLineTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.expandSelectionRightByLine(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionLeftByWordText(ExtendSelectionLeftByWordTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionLeftByWord(SelectionChangedCause.keyboard); - } - } - } - - void _handleExtendSelectionRightByWordText(ExtendSelectionRightByWordTextIntent intent) { - dom.Element? focusedElement = _findFocusedElement(); - if (focusedElement != null) { - RenderEditable? focusedRenderEditable = (focusedElement as dom.InputElement).renderEditable; - if (focusedRenderEditable != null) { - focusedRenderEditable.extendSelectionRightByWord(SelectionChangedCause.keyboard); - } - } - } - - // Find RenderViewportBox in the renderObject tree. - RenderViewportBox? _findRenderViewportBox(RenderObject parent) { - RenderViewportBox? result; - parent.visitChildren((RenderObject child) { - if (child is RenderViewportBox) { - result = child; - } else { - result = _findRenderViewportBox(child); - } - }); - return result; - } - - // Find root element of dom tree. - dom.Element _findRootElement() { - RenderObject? _rootRenderObject = context.findRenderObject(); - RenderViewportBox? renderViewportBox = _findRenderViewportBox(_rootRenderObject!); - KrakenController controller = (renderViewportBox as RenderObjectWithControllerMixin).controller!; - dom.Element documentElement = controller.view.document!.documentElement; - return documentElement; - } - - // Find all the focusable elements in the element tree. - List _findFocusableElements(dom.Element element) { - List result = []; - traverseElement(element, (dom.Element child) { - // Currently only input element is focusable. - if (child is dom.InputElement) { - result.add(child); - } - }); - return result; - } - - // Find the focused element in the element tree. - dom.Element? _findFocusedElement([List? focusableElements]) { - dom.Element? result; - if (focusableElements == null) { - dom.Element rootElement = _findRootElement(); - focusableElements = _findFocusableElements(rootElement); - } - - if (focusableElements.isNotEmpty) { - // Currently only input element is focusable. - for (dom.Element inputElement in focusableElements) { - RenderEditable? renderEditable = (inputElement as dom.InputElement).renderEditable; - if (renderEditable != null && renderEditable.hasFocus) { - result = inputElement; - break; - } - } - } - return result; - } -} - -class _KrakenRenderObjectWidget extends SingleChildRenderObjectWidget { - /// Creates a widget that visually hides its child. - const _KrakenRenderObjectWidget( - Kraken widget, - WidgetDelegate widgetDelegate, - {Key? key} - ) : _krakenWidget = widget, - _widgetDelegate = widgetDelegate, - super(key: key); - - final Kraken _krakenWidget; - final WidgetDelegate _widgetDelegate; - - @override - RenderObject createRenderObject(BuildContext context) { - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_START); - } - - double viewportWidth = _krakenWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; - double viewportHeight = _krakenWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; - - if (viewportWidth == 0.0 && viewportHeight == 0.0) { - throw FlutterError('''Can't get viewportSize from window. Please set viewportWidth and viewportHeight manually. -This situation often happened when you trying creating kraken when FlutterView not initialized.'''); - } - - KrakenController controller = KrakenController( - shortHash(_krakenWidget.hashCode), - viewportWidth, - viewportHeight, - background: _krakenWidget.background, - showPerformanceOverlay: Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, - bundleContent: _krakenWidget.bundleContent, - bundleURL: _krakenWidget.bundleURL, - bundlePath: _krakenWidget.bundlePath, - onLoad: _krakenWidget.onLoad, - onLoadError: _krakenWidget.onLoadError, - onJSError: _krakenWidget.onJSError, - methodChannel: _krakenWidget.javaScriptChannel, - gestureListener: _krakenWidget.gestureListener, - navigationDelegate: _krakenWidget.navigationDelegate, - devToolsService: _krakenWidget.devToolsService, - httpClientInterceptor: _krakenWidget.httpClientInterceptor, - widgetDelegate: _widgetDelegate, - uriParser: _krakenWidget.uriParser - ); - - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_END); - } - - // FIXME: reset href when dart hot reload that href is prev href - controller.href = ''; - - return controller.view.getRootRenderObject(); - } - - @override - void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { - super.updateRenderObject(context, renderObject); - KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; - controller.name = shortHash(_krakenWidget.hashCode); - - bool viewportWidthHasChanged = controller.view.viewportWidth != _krakenWidget.viewportWidth; - bool viewportHeightHasChanged = controller.view.viewportHeight != _krakenWidget.viewportHeight; - - double viewportWidth = _krakenWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; - double viewportHeight = _krakenWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; - - if (viewportWidthHasChanged) { - controller.view.viewportWidth = viewportWidth; - controller.view.document!.documentElement.renderStyle.width = CSSLengthValue(viewportWidth, CSSLengthType.PX); - } - - if (viewportHeightHasChanged) { - controller.view.viewportHeight = viewportHeight; - controller.view.document!.documentElement.renderStyle.height = CSSLengthValue(viewportHeight, CSSLengthType.PX); - } - - if (viewportWidthHasChanged || viewportHeightHasChanged) { - traverseElement(controller.view.document!.documentElement, (element) { - if (element.isRendererAttached) { - element.style.flushPendingProperties(); - element.renderBoxModel?.markNeedsLayout(); - } - }); - } - } - - @override - void didUnmountRenderObject(covariant RenderObject renderObject) { - KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; - controller.dispose(); - } - - @override - _KrakenRenderObjectElement createElement() { - return _KrakenRenderObjectElement(this); - } -} - -class _KrakenRenderObjectElement extends SingleChildRenderObjectElement { - _KrakenRenderObjectElement(_KrakenRenderObjectWidget widget) : super(widget); - - @override - void mount(Element? parent, Object? newSlot) async { - super.mount(parent, newSlot); - - KrakenController controller = (renderObject as RenderObjectWithControllerMixin).controller!; - - if (controller.bundleContent == null && controller.bundlePath == null && controller.bundleURL == null) { - return; - } - - await controller.loadBundle(); - - _evalBundle(controller, widget._krakenWidget.animationController); - } - - @override - _KrakenRenderObjectWidget get widget => super.widget as _KrakenRenderObjectWidget; -} - -void _evalBundle(KrakenController controller, AnimationController? animationController) async { - // Execute JavaScript scripts will block the Flutter UI Threads. - // Listen for animationController listener to make sure to execute Javascript after route transition had completed. - if (animationController != null) { - animationController.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - controller.evalBundle(); - } - }); - } else { - await controller.evalBundle(); - } -} +export 'src/widget/kraken.dart'; +export 'src/widget/widget_to_element_adaptor.dart'; +export 'src/widget/element_to_widget_adaptor.dart'; diff --git a/kraken/macos/kraken.podspec b/kraken/macos/kraken.podspec index 5535876532..1d838610a7 100644 --- a/kraken/macos/kraken.podspec +++ b/kraken/macos/kraken.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'kraken' - s.version = '0.6.3' + s.version = '0.10.0' s.summary = 'A high-performance, web standards-compliant rendering engine.' s.description = <<-DESC A high-performance, web standards-compliant rendering engine. diff --git a/kraken/macos/prepare.sh b/kraken/macos/prepare.sh index 07f80c6135..b00f0e37c1 100755 --- a/kraken/macos/prepare.sh +++ b/kraken/macos/prepare.sh @@ -4,14 +4,14 @@ read_version() { export VERSION=${VERSION_STR:1:$END_POS} } +ROOT=$(pwd) + if [ -L "libkraken.dylib" ]; then - ROOT=$(pwd) rm libkraken.dylib ln -s $ROOT/../../bridge/build/macos/lib/x86_64/libkraken.dylib fi if [ -L "libquickjs.dylib" ]; then - ROOT=$(pwd) rm libquickjs.dylib ln -s $ROOT/../../bridge/build/macos/lib/x86_64/libquickjs.dylib fi diff --git a/kraken/pubspec.yaml b/kraken/pubspec.yaml index bbc670c03b..9ae43b1cb5 100644 --- a/kraken/pubspec.yaml +++ b/kraken/pubspec.yaml @@ -1,11 +1,11 @@ name: kraken description: A high-performance, web standards-compliant rendering engine. -version: 0.9.1-rc +version: 0.10.0-rc.3 homepage: https://openkraken.com environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.2.0" + sdk: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" dependencies: flutter: diff --git a/kraken/test/kraken_test.dart b/kraken/test/kraken_test.dart index 4ccc39cf67..2ded1325c3 100644 --- a/kraken/test/kraken_test.dart +++ b/kraken/test/kraken_test.dart @@ -16,6 +16,7 @@ import 'src/module/fetch.dart' as fetch; import 'src/css/style_rule_parser.dart' as style_rule_parser; import 'src/css/style_sheet_parser.dart' as style_sheet_parser; +import 'src/css/values.dart' as css_values; import 'src/gesture/scroll_physics.dart' as scroll_physics; @@ -23,7 +24,7 @@ import 'src/gesture/scroll_physics.dart' as scroll_physics; // Setup all common logic. void main() { // Setup environment. - WidgetsFlutterBinding.ensureInitialized(); + TestWidgetsFlutterBinding.ensureInitialized(); // Start local HTTP server. LocalHttpServer.basePath = 'test/fixtures'; @@ -54,6 +55,7 @@ void main() { group('css', () { style_rule_parser.main(); style_sheet_parser.main(); + css_values.main(); }); group('gesture', () { diff --git a/kraken/test/src/css/values.dart b/kraken/test/src/css/values.dart new file mode 100644 index 0000000000..7c3718d7b7 --- /dev/null +++ b/kraken/test/src/css/values.dart @@ -0,0 +1,54 @@ +import 'package:kraken/css.dart'; +import 'package:test/test.dart'; + +void main() { + group('CSSValues', () { + + group('CSSFunction', () { + var cases = [ + 'var(--x)', + 'url(https://some.com/path)', + 'conic-gradient(from 45deg, blue, red)', + 'device-cmyk(0 81% 81% 30% / .5, rgb(178 34 34))', + 'calc(var(--widthA) / 2)', + 'url(https://some.com/path), url(https://some.com/path2)' + '''conic-gradient( + hsl(360, 100%, 50%), + hsl(315, 100%, 50%), + hsl(270, 100%, 50%), + hsl(225, 100%, 50%), + hsl(180, 100%, 50%), + hsl(135, 100%, 50%), + hsl(90, 100%, 50%), + hsl(45, 100%, 50%), + hsl(0, 100%, 50%) + )''', + ]; + cases.forEach((String input) { + test('simple case #${cases.indexOf(input)}', () { + expect(CSSFunction.isFunction(input), true); + }); + }); + + test('specified function name #0', () { + var input = 'var(--x)'; + expect(CSSFunction.isFunction(input, functionName: 'var'), true); + }); + + test('specified function name #1', () { + var input = 'var(--x)'; + expect(CSSFunction.isFunction(input, functionName: 'var1'), false); + }); + + test('specified function name #2', () { + var input = 'var(--x)'; + expect(CSSFunction.isFunction(input, functionName: 'url'), false); + }); + + test('specified function name #3', () { + var input = 'conic-gradient(--x)'; + expect(CSSFunction.isFunction(input, functionName: 'conic-gradient'), true); + }); + }); + }); +} diff --git a/kraken/test/src/foundation/http_cache.dart b/kraken/test/src/foundation/http_cache.dart index f66859d6c5..808caad50c 100644 --- a/kraken/test/src/foundation/http_cache.dart +++ b/kraken/test/src/foundation/http_cache.dart @@ -10,6 +10,7 @@ import '../../local_http_server.dart'; void main() { var server = LocalHttpServer.getInstance(); int contextId = 1; + HttpOverrides.global = null; setupHttpOverrides(null, contextId: contextId); HttpClient httpClient = HttpClient(); diff --git a/package.json b/package.json index 99481d0da2..2ded3ecea0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "build_bridge:all:profile": "ENABLE_PROFILE=true npm run build:bridge:all:release", "build:android:sdk": "KRAKEN_BUILD=Release node scripts/build_android_sdk", "build:ios:sdk": "KRAKEN_BUILD=Release node scripts/build_ios_sdk", - "pretest": "npm install && npm run lint && ENABLE_ASAN=true node scripts/build_darwin_dylib", + "pretest": "npm install && ENABLE_ASAN=true node scripts/build_darwin_dylib", + "posttest": "npm run lint", "benchmark": "npm install && ENABLE_PROFILE=true KRAKEN_BUILD=Release node scripts/run_benchmark.js", "start": "cd kraken/example && flutter run", "test": "node scripts/run_test.js", diff --git a/performance_tests/lib/main.dart b/performance_tests/lib/main.dart index abd14bcb09..69df899065 100644 --- a/performance_tests/lib/main.dart +++ b/performance_tests/lib/main.dart @@ -62,7 +62,7 @@ class _MyHomePageState extends State { controller: textEditingController, onSubmitted: (value) { textEditingController.text = value; - _kraken?.loadURL(value); + _kraken?.loadBundle(KrakenBundle.fromUrl(value)); }, decoration: InputDecoration( hintText: 'Enter a app url', @@ -90,7 +90,7 @@ class _MyHomePageState extends State { child: _kraken = Kraken( viewportWidth: viewportSize.width - queryData.padding.horizontal, viewportHeight: viewportSize.height - appBar.preferredSize.height - queryData.padding.vertical, - bundleURL: 'https://kraken.oss-cn-hangzhou.aliyuncs.com/data/cvd3r6f068.js', + bundle: KrakenBundle.fromUrl('https://kraken.oss-cn-hangzhou.aliyuncs.com/data/cvd3r6f068.js'), onLoad: (KrakenController controller) { Timer(Duration(seconds: 4), () { exit(0); diff --git a/performance_tests/pubspec.lock b/performance_tests/pubspec.lock deleted file mode 100644 index 400224e07f..0000000000 --- a/performance_tests/pubspec.lock +++ /dev/null @@ -1,327 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.6.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - connectivity: - dependency: transitive - description: - name: connectivity - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.6" - connectivity_for_web: - dependency: transitive - description: - name: connectivity_for_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - connectivity_macos: - dependency: transitive - description: - name: connectivity_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - connectivity_platform_interface: - dependency: transitive - description: - name: connectivity_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - device_info: - dependency: transitive - description: - name: device_info - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - device_info_platform_interface: - dependency: transitive - description: - name: device_info_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - ffi: - dependency: transitive - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - kraken: - dependency: "direct main" - description: - path: "../kraken" - relative: true - source: path - version: "0.9.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.1" - shared_preferences: - dependency: transitive - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - win32: - dependency: transitive - description: - name: win32 - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.5" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" -sdks: - dart: ">=2.13.0 <3.0.0" - flutter: ">=2.2.0" diff --git a/plugins/devtools/.gitignore b/plugins/devtools/.gitignore new file mode 100644 index 0000000000..bdd36f01e9 --- /dev/null +++ b/plugins/devtools/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +.dart_tool/ + +pubspec.lock +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub/ + +build/ +.idea/ +*.dSYM/ diff --git a/plugins/devtools/.metadata b/plugins/devtools/.metadata new file mode 100644 index 0000000000..2b779e526d --- /dev/null +++ b/plugins/devtools/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 + channel: unknown + +project_type: plugin diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md new file mode 100644 index 0000000000..96615e979e --- /dev/null +++ b/plugins/devtools/CHANGELOG.md @@ -0,0 +1,35 @@ +## 0.3.1 + +* chore: disable kraken_devtools for armv7 android. + +## 0.3.0 + +* chore: modify toDisplayPortValue params according to kraken 0.8.1 change. + +## 0.2.2 + +* chore: get targetId from elementDelegate of renderBoxModel due to targetId removal in renderBoxModel. + +## 0.2.1 + +* add protection when platform not supported + +## 0.2.0 + +* upgrade to kraken 0.8.0 + +## 0.1.2+1 + +* fix: flutter: NoSuchMethodError: The method 'send' was called on null https://github.com/openkraken/kraken/issues/237 + +## 0.1.2 + +* feat: support android platform. + +## 0.1.1 + +* fix: fix crash when `location.reload()` called. + +## 0.1.0 + +* first edition of kraken_devtools. diff --git a/plugins/devtools/LICENSE b/plugins/devtools/LICENSE new file mode 100644 index 0000000000..291cc7dd73 --- /dev/null +++ b/plugins/devtools/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 OpenKraken + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md new file mode 100644 index 0000000000..6348614255 --- /dev/null +++ b/plugins/devtools/README.md @@ -0,0 +1,43 @@ +# Kraken Devtools [![pub package](https://img.shields.io/pub/v/kraken_devtools.svg)](https://pub.dev/packages/kraken_devtools) + +A kraken plugin which starting a service to provide Chrome DevTools support. + +## Install + +First, add `kraken_devtools` as a dependency in your pubspec.yaml file. + +```dart +import 'package:kraken_devtools/kraken_devtools.dart'; + +Kraken kraken = Kraken( + // ... + devToolsService: ChromeDevToolsService(), +); +``` + +## How to use + +When kraken app started, there will be logs printed in terminal like below. +``` +flutter: Kraken DevTool listening at ws://127.0.0.1:9222 +flutter: Open Chrome/Edge and paste following url to your navigator: +flutter: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9222 +``` + +Open Chrome/Edge and paste url started with 'devtools://' to your navigator. + +## Features + +**DOM inspector** + +![image](https://user-images.githubusercontent.com/4409743/116355211-1dfbca00-a82c-11eb-8904-5839c14f5393.png) + +**Console Panel** + +![image](https://user-images.githubusercontent.com/4409743/116355389-5dc2b180-a82c-11eb-98e5-4bd9e7456904.png) + +**JavaScript Debugger** + +> Not supported in quickjs engine now. + +![image](https://user-images.githubusercontent.com/4409743/116355613-aaa68800-a82c-11eb-93f4-b2fcbcbbd0ba.png) diff --git a/plugins/devtools/bridge/.clang-format b/plugins/devtools/bridge/.clang-format new file mode 100644 index 0000000000..576c53d42e --- /dev/null +++ b/plugins/devtools/bridge/.clang-format @@ -0,0 +1,8 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +ColumnLimit: 120 +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortFunctionsOnASingleLine: Empty +ContinuationIndentWidth: 2 +ConstructorInitializerIndentWidth: 2 \ No newline at end of file diff --git a/plugins/devtools/bridge/.editorconfig b/plugins/devtools/bridge/.editorconfig new file mode 100644 index 0000000000..3da9b1a557 --- /dev/null +++ b/plugins/devtools/bridge/.editorconfig @@ -0,0 +1,14 @@ +# Apply the following rules to these file types +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.{cpp,cc,c,h,hpp}] +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +max_line_length = 120 diff --git a/plugins/devtools/bridge/.gitignore b/plugins/devtools/bridge/.gitignore new file mode 100644 index 0000000000..9e44832be3 --- /dev/null +++ b/plugins/devtools/bridge/.gitignore @@ -0,0 +1,3 @@ +.kraken-test-tools +cmake-build-* +kraken diff --git a/plugins/devtools/bridge/CMakeLists.txt b/plugins/devtools/bridge/CMakeLists.txt new file mode 100644 index 0000000000..ccbe3561d1 --- /dev/null +++ b/plugins/devtools/bridge/CMakeLists.txt @@ -0,0 +1,147 @@ +cmake_minimum_required(VERSION 3.10.0) +project(kraken_devtools) + +find_package(kraken) + +add_library(kraken_devtools SHARED + kraken_devtools.cc + kraken_devtools.h + dart_methods.cc + dart_methods.h + inspector/frontdoor.cc + inspector/frontdoor.h + inspector/inspector_session.cc + inspector/inspector_session.h + inspector/protocol_handler.h + inspector/rpc_session.cc + inspector/rpc_session.h + inspector/impl/jsc_console_client_impl.cc + inspector/impl/jsc_console_client_impl.h + inspector/impl/jsc_debugger_agent_impl.cc + inspector/impl/jsc_debugger_agent_impl.h + inspector/impl/jsc_debugger_impl.cc + inspector/impl/jsc_debugger_impl.h + inspector/impl/jsc_heap_profiler_agent_impl.cc + inspector/impl/jsc_heap_profiler_agent_impl.h + inspector/impl/jsc_log_agent_impl.cc + inspector/impl/jsc_log_agent_impl.h + inspector/impl/jsc_page_agent_impl.cc + inspector/impl/jsc_page_agent_impl.h + inspector/impl/jsc_runtime_agent_impl.cc + inspector/impl/jsc_runtime_agent_impl.h + inspector/protocol/break_location.cc + inspector/protocol/break_location.h + inspector/protocol/breakpoint_resolved_notification.cc + inspector/protocol/breakpoint_resolved_notification.h + inspector/protocol/call_argument.cc + inspector/protocol/call_argument.h + inspector/protocol/call_frame.cc + inspector/protocol/call_frame.h + inspector/protocol/debug_dispatcher_impl.cc + inspector/protocol/debug_dispatcher_impl.h + inspector/protocol/debugger_backend.h + inspector/protocol/debugger_dispatcher_contract.cc + inspector/protocol/debugger_dispatcher_contract.h + inspector/protocol/debugger_frontend.cc + inspector/protocol/debugger_frontend.h + inspector/protocol/dispatch_response.cc + inspector/protocol/dispatch_response.h + inspector/protocol/dispatcher_base.cc + inspector/protocol/dispatcher_base.h + inspector/protocol/domain.h + inspector/protocol/entry_added_notification.cc + inspector/protocol/entry_added_notification.h + inspector/protocol/entry_preview.cc + inspector/protocol/entry_preview.h + inspector/protocol/error_support.cc + inspector/protocol/error_support.h + inspector/protocol/exception_details.cc + inspector/protocol/exception_details.h + inspector/protocol/execution_context_created_notification.cc + inspector/protocol/execution_context_created_notification.h + inspector/protocol/execution_context_description.cc + inspector/protocol/execution_context_description.h + inspector/protocol/frontend_channel.h + inspector/protocol/heap_profiler_backend.h + inspector/protocol/heap_profiler_dispatcher_contract.cc + inspector/protocol/heap_profiler_dispatcher_contract.h + inspector/protocol/heap_profiler_dispatcher_impl.cc + inspector/protocol/heap_profiler_dispatcher_impl.h + inspector/protocol/internal_property_descriptor.cc + inspector/protocol/internal_property_descriptor.h + inspector/protocol/location.cc + inspector/protocol/location.h + inspector/protocol/log_backend.h + inspector/protocol/log_dispatcher_contract.cc + inspector/protocol/log_dispatcher_contract.h + inspector/protocol/log_dispatcher_impl.cc + inspector/protocol/log_dispatcher_impl.h + inspector/protocol/log_entry.cc + inspector/protocol/log_entry.h + inspector/protocol/log_frontend.cc + inspector/protocol/log_frontend.h + inspector/protocol/maybe.h + inspector/protocol/object_preview.cc + inspector/protocol/object_preview.h + inspector/protocol/page_backend.h + inspector/protocol/page_dispatcher_contract.cc + inspector/protocol/page_dispatcher_contract.h + inspector/protocol/page_dispatcher_impl.cc + inspector/protocol/page_dispatcher_impl.h + inspector/protocol/paused_notification.cc + inspector/protocol/paused_notification.h + inspector/protocol/private_property_descriptor.cc + inspector/protocol/private_property_descriptor.h + inspector/protocol/property_descriptor.cc + inspector/protocol/property_descriptor.h + inspector/protocol/property_preview.cc + inspector/protocol/property_preview.h + inspector/protocol/remote_object.cc + inspector/protocol/remote_object.h + inspector/protocol/runtime_backend.h + inspector/protocol/runtime_dispatcher_contract.cc + inspector/protocol/runtime_dispatcher_contract.h + inspector/protocol/runtime_dispatcher_impl.cc + inspector/protocol/runtime_dispatcher_impl.h + inspector/protocol/runtime_frontend.cc + inspector/protocol/runtime_frontend.h + inspector/protocol/scope.cc + inspector/protocol/scope.h + inspector/protocol/script_failed_to_parse_notification.cc + inspector/protocol/script_failed_to_parse_notification.h + inspector/protocol/script_parsed_notification.cc + inspector/protocol/script_parsed_notification.h + inspector/protocol/script_position.cc + inspector/protocol/script_position.h + inspector/protocol/search_match.cc + inspector/protocol/search_match.h + inspector/protocol/stacktrace.cc + inspector/protocol/stacktrace.h + inspector/protocol/stacktrace_id.cc + inspector/protocol/stacktrace_id.h + inspector/protocol/uber_dispatcher.cc + inspector/protocol/uber_dispatcher.h + inspector/service/rpc/object_serializer.h + inspector/service/rpc/protocol.h +) + +if ($ENV{KRAKEN_JS_ENGINE} MATCHES "jsc") + set_target_properties(kraken_devtools PROPERTIES OUTPUT_NAME kraken_devtools_jsc) +endif() + +list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}) +list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/rapidjson-1.1.0/include) +list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/kraken/include) +target_link_libraries(kraken_devtools ${BRIDGE_LINK_LIBS} kraken) +target_include_directories(kraken_devtools PUBLIC ${BRIDGE_INCLUDE}) +target_compile_definitions(kraken_devtools PUBLIC RAPIDJSON_HAS_STDSTRING=1) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set_target_properties(kraken_devtools PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../macos" + ) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Android") + set_target_properties(kraken_devtools PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../android/jniLibs/${ANDROID_ABI}/" + ) +endif () diff --git a/plugins/devtools/bridge/dart_methods.cc b/plugins/devtools/bridge/dart_methods.cc new file mode 100644 index 0000000000..fc4ca9be9d --- /dev/null +++ b/plugins/devtools/bridge/dart_methods.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dart_methods.h" +#include "kraken_bridge.h" +#include + +namespace kraken { + +std::shared_ptr uiMethodPointer = std::make_shared(); + +std::shared_ptr getUIDartMethod() { + return uiMethodPointer; +} + +void registerUIDartMethods(uint64_t *methodBytes, int32_t length) { + size_t i = 0; + uiMethodPointer->postTaskToInspectorThread = reinterpret_cast(methodBytes[i++]); + assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); +} + +std::shared_ptr inspectorMethodPointer = std::make_shared(); +std::shared_ptr getInspectorDartMethod() { + assert_m(std::this_thread::get_id() != getUIThreadId(), "inspector dart methods should be called on the inspector thread."); + return inspectorMethodPointer; +} +void registerInspectorDartMethods(uint64_t *methodBytes, int32_t length) { + size_t i = 0; + inspectorMethodPointer->inspectorMessage = reinterpret_cast(methodBytes[i++]); + inspectorMethodPointer->registerInspectorMessageCallback = reinterpret_cast(methodBytes[i++]); + inspectorMethodPointer->postTaskToUiThread = reinterpret_cast(methodBytes[i++]); +} + +} // namespace kraken diff --git a/plugins/devtools/bridge/dart_methods.h b/plugins/devtools/bridge/dart_methods.h new file mode 100644 index 0000000000..0c8873015f --- /dev/null +++ b/plugins/devtools/bridge/dart_methods.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEVTOOLS_DART_METHODS_H_ +#define KRAKEN_DEVTOOLS_DART_METHODS_H_ + +#include "kraken_bridge.h" + +#include +#include + +struct NativeString; +struct Screen; + +typedef void (*InspectorMessage)(int32_t contextId, const char *message); +typedef void (*InspectorMessageCallback)(void *rpcSession, const char *message); +typedef void (*RegisterInspectorMessageCallback)(int32_t contextId, void *rpcSession, + InspectorMessageCallback inspectorMessageCallback); +typedef void (*PostTaskToInspectorThread)(int32_t contextId, void *context, void (*)(void *)); +typedef void (*PostTaskToUIThread)(int32_t contextId, void *context, void (*)(void *)); + +namespace kraken { + +struct UIDartMethodPointer { + UIDartMethodPointer() = default; + PostTaskToInspectorThread postTaskToInspectorThread{nullptr}; +}; +std::shared_ptr getUIDartMethod(); +void registerUIDartMethods(uint64_t *methodBytes, int32_t length); + +struct InspectorDartMethodPointer { + InspectorMessage inspectorMessage{nullptr}; + RegisterInspectorMessageCallback registerInspectorMessageCallback{nullptr}; + PostTaskToUIThread postTaskToUiThread{nullptr}; +}; +std::shared_ptr getInspectorDartMethod(); +void registerInspectorDartMethods(uint64_t *methodBytes, int32_t length); + +#ifdef IS_TEST +KRAKEN_EXPORT +void registerTestEnvDartMethods(uint64_t *methodBytes, int32_t length); +#endif + + +} // namespace kraken + +#endif diff --git a/plugins/devtools/bridge/inspector/frontdoor.cc b/plugins/devtools/bridge/inspector/frontdoor.cc new file mode 100644 index 0000000000..003ba610fc --- /dev/null +++ b/plugins/devtools/bridge/inspector/frontdoor.cc @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/frontdoor.h" + +namespace kraken::debugger { + +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/frontdoor.h b/plugins/devtools/bridge/inspector/frontdoor.h new file mode 100644 index 0000000000..2caad606d8 --- /dev/null +++ b/plugins/devtools/bridge/inspector/frontdoor.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_FRONTDOOR_H +#define KRAKEN_DEBUGGER_FRONTDOOR_H + +#include "inspector/inspector_session.h" +#include "inspector/protocol_handler.h" +#include "inspector/rpc_session.h" +#include +#include +#include + +namespace kraken::debugger { +class FrontDoor final { +public: + ~FrontDoor() = default; + FrontDoor(int32_t contextId, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, const std::shared_ptr handler) { + m_rpc_session = std::make_shared(contextId, ctx, globalObject, handler); + } + + static void handleConsoleMessage(void* ctx, const std::string &message, int logLevel) { + JSObjectRef globalObjectRef = JSContextGetGlobalObject(reinterpret_cast(ctx)); + auto client = JSObjectGetPrivate(globalObjectRef); + if (client && client != ((void *)0x1)) { + auto client_impl = reinterpret_cast(client); + client_impl->sendMessageToConsole(static_cast(logLevel), message); + } + } + +private: + std::shared_ptr m_rpc_session; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_FRONTDOOR_H diff --git a/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.cc new file mode 100644 index 0000000000..5b587d7873 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.cc @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include +#include +#include +#include +#include +#include "jsc_console_client_impl.h" +#include "inspector/impl/jsc_log_agent_impl.h" +#include "inspector/protocol/log_entry.h" +#include + +namespace kraken::debugger { + +JSCConsoleClientImpl::JSCConsoleClientImpl(kraken::debugger::JSCLogAgentImpl *logAgent) : m_consoleAgent(logAgent) {} + +void JSCConsoleClientImpl::sendMessageToConsole(MessageLevel level, const std::string &message) { + std::string v8_level = LogEntry::LevelEnum::Verbose; + if (level == MessageLevel::Log) { + v8_level = LogEntry::LevelEnum::Verbose; + } else if (level == MessageLevel::Info) { + v8_level = LogEntry::LevelEnum::Info; + } else if (level == MessageLevel::Warning) { + v8_level = LogEntry::LevelEnum::Warning; + } else if (level == MessageLevel::Debug) { + v8_level = LogEntry::LevelEnum::Info; + } else if (level == MessageLevel::Error) { + v8_level = LogEntry::LevelEnum::Error; + } + + std::string v8_source = LogEntry::SourceEnum::Javascript; + + auto now = std::chrono::high_resolution_clock::now(); + auto logEntry = LogEntry::create() + .setLevel(v8_level) + .setTimestamp(std::chrono::duration_cast(now.time_since_epoch()).count()) + .setSource(v8_source) + .setText(message) + .build(); + m_consoleAgent->addMessageToConsole(std::move(logEntry)); +} + +void JSCConsoleClientImpl::messageWithTypeAndLevel(MessageType type, MessageLevel level, JSC::ExecState *exec, + Ref &&arguments) { + // WTF::String message; + // arguments->getFirstArgumentAsString(message); + + WTF::StringBuilder builder; + for (size_t i = 0; i < exec->argumentCount(); i++) { + auto &&string = exec->argument(i).getString(exec); + builder.appendCharacters(string.characters8(), string.length()); + builder.append(" "); + } + + std::string v8_level = LogEntry::LevelEnum::Verbose; + if (level == MessageLevel::Log) { + v8_level = LogEntry::LevelEnum::Verbose; + } else if (level == MessageLevel::Info) { + v8_level = LogEntry::LevelEnum::Info; + } else if (level == MessageLevel::Warning) { + v8_level = LogEntry::LevelEnum::Warning; + } else if (level == MessageLevel::Debug) { + v8_level = LogEntry::LevelEnum::Info; + } else if (level == MessageLevel::Error) { + v8_level = LogEntry::LevelEnum::Error; + } + + std::string v8_source = LogEntry::SourceEnum::Javascript; + + auto now = std::chrono::high_resolution_clock::now(); + auto logEntry = LogEntry::create() + .setLevel(v8_level) + .setTimestamp(std::chrono::duration_cast(now.time_since_epoch()).count()) + .setSource(v8_source) + .setText(builder.toString().utf8().data()) + .build(); + + m_consoleAgent->addMessageToConsole(std::move(logEntry)); +} + +void JSCConsoleClientImpl::count(JSC::ExecState *, const String& label) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.count")); +} + +void JSCConsoleClientImpl::profile(JSC::ExecState *, const String &title) { + auto now = std::chrono::high_resolution_clock::now(); + auto logEntry = LogEntry::create() + .setLevel(LogEntry::LevelEnum::Error) + .setTimestamp(std::chrono::duration_cast(now.time_since_epoch()).count()) + .setSource(LogEntry::SourceEnum::Javascript) + .setText(title.utf8().data()) + .build(); + m_consoleAgent->addMessageToConsole(std::move(logEntry)); +} + +void JSCConsoleClientImpl::profileEnd(JSC::ExecState *, const String &title) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.profileEnd")); +} + +void JSCConsoleClientImpl::takeHeapSnapshot(JSC::ExecState *, const String &title) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.takeHeapSnapshot")); +} + +void JSCConsoleClientImpl::time(JSC::ExecState *, const String &title) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.time")); +} + +void JSCConsoleClientImpl::timeEnd(JSC::ExecState *, const String &title) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.timeEnd")); +} + +void JSCConsoleClientImpl::timeStamp(JSC::ExecState *, Ref &&) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.timeStamp")); +} + +void JSCConsoleClientImpl::record(JSC::ExecState *, Ref &&) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.record")); +} + +void JSCConsoleClientImpl::recordEnd(JSC::ExecState*, Ref&&) { + warnUnimplemented(ASCIILiteral::fromLiteralUnsafe("console.recordEnd")); +}; + +void JSCConsoleClientImpl::warnUnimplemented(const String &method) { + String message = method + " is currently ignored in JavaScript context inspection."; + auto now = std::chrono::high_resolution_clock::now(); + auto logEntry = LogEntry::create() + .setLevel(LogEntry::LevelEnum::Warning) + .setTimestamp(std::chrono::duration_cast(now.time_since_epoch()).count()) + .setSource(LogEntry::SourceEnum::Javascript) + .setText(message.utf8().data()) + .build(); + + m_consoleAgent->addMessageToConsole(std::move(logEntry)); +} + +void JSCConsoleClientImpl::countReset(JSC::ExecState *, const String &label) {} +void JSCConsoleClientImpl::timeLog(JSC::ExecState *, const String &label, Ref &&) {} +void JSCConsoleClientImpl::screenshot(JSC::ExecState *, Ref &&) {} + +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.h new file mode 100644 index 0000000000..a87d830845 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_console_client_impl.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_CONSOLE_CLIENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_CONSOLE_CLIENT_IMPL_H + +#include +#include +#include +#include +#include + +namespace kraken::debugger { +class JSCLogAgentImpl; +class JSCConsoleClientImpl : public JSC::ConsoleClient { + WTF_MAKE_FAST_ALLOCATED; + +public: + explicit JSCConsoleClientImpl(JSCLogAgentImpl *); + virtual ~JSCConsoleClientImpl() {} + + void sendMessageToConsole(MessageLevel, const std::string &message); + +protected: + void messageWithTypeAndLevel(MessageType, MessageLevel, JSC::ExecState *, + Ref &&) override; + void count(JSC::ExecState *, const String& label) override; + void profile(JSC::ExecState *, const String &title) override; + void profileEnd(JSC::ExecState *, const String &title) override; + void takeHeapSnapshot(JSC::ExecState *, const String &title) override; + void time(JSC::ExecState *, const String &title) override; + void timeEnd(JSC::ExecState *, const String &title) override; + void timeStamp(JSC::ExecState *, Ref &&) override; + void record(JSC::ExecState*, Ref&&) override; + void recordEnd(JSC::ExecState*, Ref&&) override; + void countReset(JSC::ExecState*, const String& label) override; + void timeLog(JSC::ExecState*, const String& label, Ref&&) override; + void screenshot(JSC::ExecState*, Ref&&) override; + +private: + void warnUnimplemented(const String &method); + + JSCLogAgentImpl *m_consoleAgent; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_JSC_CONSOLE_CLIENT_IMPL_H diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.cc new file mode 100644 index 0000000000..3df69e6425 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.cc @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/impl/jsc_debugger_agent_impl.h" +#include "inspector/inspector_session.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/maybe.h" + +#include + +namespace kraken::debugger { +const char *JSCDebuggerAgentImpl::backtraceObjectGroup = "backtrace"; + +// Objects created and retained by evaluating breakpoint actions are put into object groups +// according to the breakpoint action identifier assigned by the frontend. A breakpoint may +// have several object groups, and objects from several backend breakpoint action instances may +// create objects in the same group. +static String objectGroupForBreakpointAction(const Inspector::ScriptBreakpointAction &action) { + static NeverDestroyed objectGroup(ASCIILiteral::fromLiteralUnsafe("breakpoint-action-")); + return makeString(objectGroup.get(), String::number(action.identifier)); +} + +JSCDebuggerAgentImpl::JSCDebuggerAgentImpl(debugger::InspectorSession *session, debugger::AgentContext &context) + : m_session(session), + m_frontend(context.channel), m_debugger(context.debugger), + m_injectedScriptManager(context.injectedScriptManager), m_continueToLocationBreakpointID(JSC::noBreakpointID) +{ + clearBreakDetails(); +} + +JSCDebuggerAgentImpl::~JSCDebuggerAgentImpl() {} + +//////////////////////Inspector::ScriptDebugListener/////////////////////////////// + +static bool isWebKitInjectedScript(const String &sourceURL) { + return sourceURL.startsWith("__InjectedScript_") && sourceURL.endsWith(".js"); +} + +static bool matches(const String &url, const String &pattern, bool isRegex) { + if (isRegex) { + JSC::Yarr::RegularExpression regex(pattern, JSC::Yarr::TextCaseSensitive); + return regex.match(url) != -1; + } + return url == pattern; +} + +static Ref buildObjectForBreakpointCookie(const String &url, int lineNumber, + int columnNumber, const String &condition, + RefPtr &actions, bool isRegex, + bool autoContinue, unsigned ignoreCount) { + Ref breakpointObject = JSON::Object::create(); + breakpointObject->setString("url"_s, url); + breakpointObject->setInteger("lineNumber"_s, lineNumber); + breakpointObject->setInteger("columnNumber"_s, columnNumber); + breakpointObject->setString("condition"_s, condition); + breakpointObject->setBoolean("isRegex"_s, isRegex); + breakpointObject->setBoolean("autoContinue"_s, autoContinue); + breakpointObject->setInteger("ignoreCount"_s, ignoreCount); + + if (actions) breakpointObject->setArray("actions"_s, actions); + + return breakpointObject; +} + +void JSCDebuggerAgentImpl::didParseSource(JSC::SourceID sourceID, + const Inspector::ScriptDebugListener::Script &script) { + String scriptIDStr = String::number(sourceID); + bool hasSourceURL = !script.sourceURL.isEmpty(); + String sourceURL = script.sourceURL; + String sourceMappingURL = sourceMapURLForScript(script); + + const bool isModule = script.sourceProvider->sourceType() == JSC::SourceProviderSourceType::Module; + const bool *isContentScript = script.isContentScript ? &script.isContentScript : nullptr; + String *sourceURLParam = hasSourceURL ? &sourceURL : nullptr; + String *sourceMapURLParam = sourceMappingURL.isEmpty() ? nullptr : &sourceMappingURL; + + int executionContextId = 1; + + m_frontend.scriptParsed(scriptIDStr.utf8().data(), script.url.utf8().data(), script.startLine, script.startColumn, + script.endLine, script.endColumn, executionContextId, + script.sourceProvider ? std::to_string(script.sourceProvider->hash()) : "unknown", + kraken::debugger::Maybe() /*executionContextAuxData*/, + kraken::debugger::Maybe() /*isLiveEdit*/, + sourceMapURLParam == nullptr + ? kraken::debugger::Maybe() + : kraken::debugger::Maybe((*sourceMapURLParam).utf8().data()) /*sourceMapURL*/, + kraken::debugger::Maybe(hasSourceURL), kraken::debugger::Maybe(isModule), + kraken::debugger::Maybe(script.source.length()) /*length*/, + kraken::debugger::Maybe() /*stackTrace*/ + ); + + m_scripts.set(sourceID, script); + + if (hasSourceURL && isWebKitInjectedScript(sourceURL)) m_debugger->addToBlacklist(sourceID); + + String scriptURLForBreakpoints = hasSourceURL ? script.sourceURL : script.url; + if (scriptURLForBreakpoints.isEmpty()) return; + + for (auto &entry : m_javaScriptBreakpoints) { + RefPtr breakpointObject = entry.value; + + bool isRegex; + String url; + breakpointObject->getBoolean(ASCIILiteral::fromLiteralUnsafe("isRegex"), isRegex); + breakpointObject->getString(ASCIILiteral::fromLiteralUnsafe("url"), url); + if (!matches(scriptURLForBreakpoints, url, isRegex)) continue; + + Inspector::ScriptBreakpoint scriptBreakpoint; + breakpointObject->getInteger(ASCIILiteral::fromLiteralUnsafe("lineNumber"), scriptBreakpoint.lineNumber); + breakpointObject->getInteger(ASCIILiteral::fromLiteralUnsafe("columnNumber"), scriptBreakpoint.columnNumber); + breakpointObject->getString(ASCIILiteral::fromLiteralUnsafe("condition"), scriptBreakpoint.condition); + breakpointObject->getBoolean(ASCIILiteral::fromLiteralUnsafe("autoContinue"), scriptBreakpoint.autoContinue); + breakpointObject->getInteger(ASCIILiteral::fromLiteralUnsafe("ignoreCount"), scriptBreakpoint.ignoreCount); + Inspector::ErrorString errorString; + RefPtr actions; + breakpointObject->getArray(ASCIILiteral::fromLiteralUnsafe("actions"), actions); + if (!breakpointActionsFromProtocol(errorString, actions, &scriptBreakpoint.actions)) { + ASSERT_NOT_REACHED(); + continue; + } + + JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, + scriptBreakpoint.condition, scriptBreakpoint.autoContinue, scriptBreakpoint.ignoreCount); + resolveBreakpoint(script, breakpoint); + if (!breakpoint.resolved) continue; + + bool existing; + setBreakpoint(breakpoint, existing); + if (existing) continue; + + String breakpointIdentifier = entry.key; + didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); + + auto location = Location::create() + .setScriptId(String::number(breakpoint.sourceID).utf8().data()) + .setLineNumber(breakpoint.line) + .setColumnNumber(breakpoint.column) + .build(); + + m_frontend.breakpointResolved(breakpointIdentifier.utf8().data(), std::move(location)); + } +} + +void JSCDebuggerAgentImpl::failedToParseSource(const WTF::String &url, const WTF::String &data, int firstLine, + int errorLine, const WTF::String &errorMessage) { + KRAKEN_LOG(ERROR) << "failed to parse source." << errorMessage.utf8().data(); + m_frontend.scriptFailedToParse(url.utf8().data() /*scriptId*/, url.utf8().data() /*url*/, firstLine /*startLine*/, + 0 /*startColumn*/, errorLine /*endLine*/, 0 /*endColumn*/, 1 /*executionContextId*/, + "unknown" /*hash*/ + ); +} + +bool JSCDebuggerAgentImpl::convertCallFrames( + const std::string &in_callframes_str, + std::unique_ptr>> *out_callframes) { + + rapidjson::Document call_frames_obj; + call_frames_obj.Parse(in_callframes_str.c_str()); + if (call_frames_obj.HasParseError() || !call_frames_obj.IsArray()) { + KRAKEN_LOG(ERROR) << "callframes parsed error..."; + return false; + } + auto array = call_frames_obj.GetArray(); + convertCallFrames(&array, call_frames_obj.GetAllocator(), out_callframes); + + return true; +} + +bool JSCDebuggerAgentImpl::convertCallFrames( + rapidjson::Value::Array *in_callframes, rapidjson::Document::AllocatorType &in_allocator, + std::unique_ptr>> *out_callframes) { + *out_callframes = std::make_unique>>(); + for (auto &callframe : *in_callframes) { + if (callframe.IsObject()) { + if (callframe.HasMember("location") && callframe.HasMember("functionName")) { + if (callframe["functionName"].GetStringLength() == 0) continue; + WTF::String scriptId = callframe["location"]["scriptId"].GetString(); + auto iterator = m_scripts.find(scriptId.toIntPtr()); + if (iterator != m_scripts.end()) { + auto script = iterator->value; + WTF::String url = !script.sourceURL.isEmpty() ? script.sourceURL : script.url; + callframe.AddMember("url", std::string(url.utf8().data()), in_allocator); + } + } else { + callframe.AddMember("url", "(gart)", in_allocator); + } + + if (callframe.HasMember("scopeChain") && callframe["scopeChain"].IsArray()) { + for (auto &scope : callframe["scopeChain"].GetArray()) { + // location -> startLocation + auto loc_iterator = scope.FindMember("location"); + if (loc_iterator != scope.MemberEnd()) { + loc_iterator->name.SetString("startLocation", in_allocator); + } + + // nestedLexical -> local + // globalLexicalEnvironment -> block + auto type_iterator = scope.FindMember("type"); + if (type_iterator != scope.MemberEnd()) { + const char *type = type_iterator->value.GetString(); + if (std::strcmp(type, "nestedLexical") == 0) { + type_iterator->value.SetString("local", in_allocator); + } else if (std::strcmp(type, "globalLexicalEnvironment") == 0) { + type_iterator->value.SetString("block", in_allocator); + } + } + } + } + + ErrorSupport err; + auto item = debugger::CallFrame::fromValue(&callframe, &err); + if (err.hasErrors()) { + KRAKEN_LOG(ERROR) << "callframe transformed error," << err.errors(); + continue; + } + (*out_callframes)->push_back(std::move(item)); + } else { + return false; + } + } + return true; +} + +bool JSCDebuggerAgentImpl::convertStackTrace(const std::string &in_stackTrace_str, + std::unique_ptr *out_trace) { + + rapidjson::Document stackTrace_obj; + stackTrace_obj.Parse(in_stackTrace_str.c_str()); + if (stackTrace_obj.HasParseError() || !stackTrace_obj.IsObject()) { + KRAKEN_LOG(ERROR) << "stackTrace parsed error..."; + return false; + } + + if (stackTrace_obj.HasMember("callFrames")) { + rapidjson::Value::Array call_frames = stackTrace_obj["callFrames"].GetArray(); + std::unique_ptr>> callFrames; + convertCallFrames(&call_frames, stackTrace_obj.GetAllocator(), &callFrames); + + *out_trace = debugger::StackTrace::create().setCallFrames(std::move(callFrames)).build(); + } else { + return false; + } + + return true; +} + +void JSCDebuggerAgentImpl::didPause(JSC::ExecState &scriptState, JSC::JSValue callFrames, + JSC::JSValue exceptionOrCaughtValue) { + m_pausedScriptState = &scriptState; + m_currentCallStack = {scriptState.vm(), callFrames}; + + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(&scriptState); + + // If a high level pause pause reason is not already set, try to infer a reason from the debugger. + JSC::BreakpointID debuggerBreakpointId = JSC::noBreakpointID; + if (m_breakReason == Inspector::DebuggerFrontendDispatcher::Reason::Other) { + switch (m_debugger->reasonForPause()) { + case JSC::Debugger::PausedForBreakpoint: { + debuggerBreakpointId = m_debugger->pausingBreakpointID(); + if (debuggerBreakpointId != m_continueToLocationBreakpointID) { + m_breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Breakpoint; + m_breakAuxData = buildBreakpointPauseReason(debuggerBreakpointId); + } + break; + } + case JSC::Debugger::PausedForDebuggerStatement: + m_breakReason = Inspector::DebuggerFrontendDispatcher::Reason::DebuggerStatement; + m_breakAuxData = nullptr; + break; + case JSC::Debugger::PausedForException: + m_breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Exception; + m_breakAuxData = buildExceptionPauseReason(exceptionOrCaughtValue, injectedScript); + break; + case JSC::Debugger::PausedAtStatement: + case JSC::Debugger::PausedAtExpression: + case JSC::Debugger::PausedBeforeReturn: + case JSC::Debugger::PausedAtEndOfProgram: + // Pause was just stepping. Nothing to report. + break; + case JSC::Debugger::NotPaused: + ASSERT_NOT_REACHED(); + break; + } + } + + // Set $exception to the exception or caught value. + if (exceptionOrCaughtValue && !injectedScript.hasNoValue()) { + injectedScript.setExceptionValue(exceptionOrCaughtValue); + m_hasExceptionValue = true; + } + + m_conditionToDispatchResumed = ShouldDispatchResumed::No; + m_enablePauseWhenIdle = false; + + RefPtr asyncStackTrace; + if (m_currentAsyncCallIdentifier) { + auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifier.value()); + if (it != m_pendingAsyncCalls.end()) asyncStackTrace = it->value->buildInspectorObject(); + } + + std::string reason = Inspector::Protocol::InspectorHelpers::getEnumConstantValue(m_breakReason).utf8().data(); + + auto _callFrames = currentCallFrames(injectedScript); + std::string callframes_str = _callFrames->toJSONString().utf8().data(); // jsc call frames + std::unique_ptr>> callFrames_v8; + if (!convertCallFrames(callframes_str, &callFrames_v8)) { + return; + } + + std::unique_ptr data_v8 = nullptr; + if (m_breakAuxData) { + std::string aux_str = m_breakAuxData->toJSONString().utf8().data(); + + rapidjson::Document aux_obj; + aux_obj.Parse(aux_str.c_str()); + if (aux_obj.HasParseError() || !aux_obj.IsObject()) { + KRAKEN_LOG(ERROR) << "aux data parsed error..."; + return; + } + data_v8 = std::make_unique(rapidjson::kObjectType); + data_v8->CopyFrom(aux_obj, m_doc.GetAllocator()); + } + + std::unique_ptr> hitBreakpoints_v8 = nullptr; + if (debuggerBreakpointId != JSC::noBreakpointID) { + hitBreakpoints_v8 = std::make_unique>(); + hitBreakpoints_v8->push_back(WTF::String::number(debuggerBreakpointId).utf8().data()); + } + + std::unique_ptr asyncStackTrace_v8 = nullptr; + if (asyncStackTrace) { + convertStackTrace(asyncStackTrace->toJSONString().utf8().data(), &asyncStackTrace_v8); + } + + std::unique_ptr asyncStackTraceId_v8 = nullptr; // TODO + std::unique_ptr asyncCallStackTraceId_v8 = nullptr; // TODO + + m_frontend.paused(std::move(callFrames_v8), reason, std::move(data_v8), std::move(hitBreakpoints_v8), + std::move(asyncStackTrace_v8), std::move(asyncStackTraceId_v8), + std::move(asyncCallStackTraceId_v8)); + + m_javaScriptPauseScheduled = false; + + if (m_continueToLocationBreakpointID != JSC::noBreakpointID) { + m_debugger->removeBreakpoint(m_continueToLocationBreakpointID); + m_continueToLocationBreakpointID = JSC::noBreakpointID; + } + + RefPtr stopwatch = m_injectedScriptManager->inspectorEnvironment().executionStopwatch(); + if (stopwatch && stopwatch->isActive()) { + stopwatch->stop(); + m_didPauseStopwatch = true; + } +} + +void JSCDebuggerAgentImpl::didContinue() { + if (m_didPauseStopwatch) { + m_didPauseStopwatch = false; + m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->start(); + } + + m_pausedScriptState = nullptr; + m_currentCallStack = {}; + m_injectedScriptManager->releaseObjectGroup(JSCDebuggerAgentImpl::backtraceObjectGroup); + clearBreakDetails(); + clearExceptionValue(); + + if (m_conditionToDispatchResumed == ShouldDispatchResumed::WhenContinued) { + m_frontend.resumed(); + } +} + +void JSCDebuggerAgentImpl::breakpointActionLog(JSC::ExecState &state, const WTF::String &message) { + KRAKEN_LOG(VERBOSE) << "breakpointActionLog: " << message.utf8().data(); +} + +void JSCDebuggerAgentImpl::breakpointActionSound(int breakpointActionIdentifier) { +} + +void JSCDebuggerAgentImpl::breakpointActionProbe(JSC::ExecState &scriptState, + const Inspector::ScriptBreakpointAction &action, unsigned batchId, + unsigned sampleId, JSC::JSValue sample) { + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(&scriptState); + auto payload = injectedScript.wrapObject(sample, objectGroupForBreakpointAction(action), true); + auto result = Inspector::Protocol::Debugger::ProbeSample::create() + .setProbeId(action.identifier) + .setBatchId(batchId) + .setSampleId(sampleId) + .setTimestamp(m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->elapsedTime().milliseconds()) + .setPayload(WTFMove(payload)) + .release(); +} + +/////////////////////Backend Interface//////////////////////////////// + +static bool parseLocation(Inspector::ErrorString &errorString, std::unique_ptr in_location, + JSC::SourceID &sourceID, unsigned &lineNumber, unsigned &columnNumber) { + if (in_location == nullptr) { + errorString = ASCIILiteral::fromLiteralUnsafe("location not exists."); + return false; + } + String scriptIDStr = in_location->getScriptId().c_str(); + lineNumber = static_cast(in_location->getLineNumber()); + sourceID = scriptIDStr.toIntPtr(); + columnNumber = 0; + if (in_location->hasColumnNumber()) { + columnNumber = static_cast(in_location->getColumnNumber(0)); + } + return true; +} + +DispatchResponse JSCDebuggerAgentImpl::continueToLocation(std::unique_ptr in_location, + Maybe in_targetCallFrames) { + Inspector::ErrorString errorString; + if (!assertPaused(errorString)) return DispatchResponse::Error(errorString.utf8().data()); + + if (m_continueToLocationBreakpointID != JSC::noBreakpointID) { + m_debugger->removeBreakpoint(m_continueToLocationBreakpointID); + m_continueToLocationBreakpointID = JSC::noBreakpointID; + } + + JSC::SourceID sourceID; + unsigned lineNumber; + unsigned columnNumber; + + if (!parseLocation(errorString, std::move(in_location), sourceID, lineNumber, columnNumber)) + return DispatchResponse::Error(errorString.utf8().data()); + + auto scriptIterator = m_scripts.find(sourceID); + if (scriptIterator == m_scripts.end()) { + m_debugger->continueProgram(); + m_frontend.resumed(); + errorString = ASCIILiteral::fromLiteralUnsafe("No script for id: ") + String::number(sourceID); + return DispatchResponse::Error(errorString.utf8().data()); + } + + String condition; + bool autoContinue = false; + unsigned ignoreCount = 0; + JSC::Breakpoint breakpoint(sourceID, lineNumber, columnNumber, condition, autoContinue, ignoreCount); + Script &script = scriptIterator->value; + resolveBreakpoint(script, breakpoint); + if (!breakpoint.resolved) { + m_debugger->continueProgram(); + m_frontend.resumed(); + errorString = ASCIILiteral::fromLiteralUnsafe("Could not resolve breakpoint"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + bool existing; + setBreakpoint(breakpoint, existing); + if (existing) { + // There is an existing breakpoint at this location. Instead of + // acting like a series of steps, just resume and we will either + // hit this new breakpoint or not. + m_debugger->continueProgram(); + m_frontend.resumed(); + return DispatchResponse::OK(); + } + + m_continueToLocationBreakpointID = breakpoint.id; + + // Treat this as a series of steps until reaching the new breakpoint. + // So don't issue a resumed event unless we exit the VM without pausing. + willStepAndMayBecomeIdle(); + m_debugger->continueProgram(); + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::disable() { + disable(false); + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::enable(Maybe in_maxScriptsCacheSize, std::string *out_debuggerId) { + enable(); + + *out_debuggerId = "(KRAKEN_debugger_id_" + std::to_string(m_debugger_id++) + ")"; + + return DispatchResponse::OK(); +} + +bool JSCDebuggerAgentImpl::convertRemoteObject(const std::string &in_result, std::unique_ptr *out_result, + Inspector::ErrorString &error) { + rapidjson::Document in_result_doc; + in_result_doc.Parse(in_result.c_str()); + if (in_result_doc.HasParseError() || !in_result_doc.IsObject()) { + KRAKEN_LOG(ERROR) << "remoteObject parsed error..."; + return false; + } + auto copy = rapidjson::Value(in_result_doc, m_doc.GetAllocator()); + ErrorSupport errorSupport; + *out_result = debugger::RemoteObject::fromValue(©, &errorSupport); + if (errorSupport.hasErrors()) { + error = errorSupport.errors().c_str(); + return false; + } + return true; +} + +DispatchResponse JSCDebuggerAgentImpl::evaluateOnCallFrame( + const std::string &in_callFrameId, const std::string &in_expression, Maybe in_objectGroup, + Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_throwOnSideEffect /*不支持*/, Maybe in_timeout /*不支持*/, + std::unique_ptr *out_result, Maybe *out_exceptionDetails) { + + Inspector::ErrorString errorString; + if (!m_currentCallStack) { + errorString = ASCIILiteral::fromLiteralUnsafe("Not paused"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(in_callFrameId.c_str()); + if (injectedScript.hasNoValue()) { + errorString = ASCIILiteral::fromLiteralUnsafe("Could not find InjectedScript for callFrameId"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + JSC::Debugger::PauseOnExceptionsState previousPauseOnExceptionsState = m_debugger->pauseOnExceptionsState(); + if (in_silent.fromMaybe(false)) { + if (previousPauseOnExceptionsState != JSC::Debugger::DontPauseOnExceptions) + m_debugger->setPauseOnExceptionsState(JSC::Debugger::DontPauseOnExceptions); + } + + bool saveResult = false; // TODO V8不支持 + WTF::Optional wasThrown; // TODO V8不支持 + WTF::Optional savedResultIndex; // TODO V8不支持 + + RefPtr result; + injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack.get(), in_callFrameId.c_str(), in_expression.c_str(), + in_objectGroup.fromMaybe("").c_str(), in_includeCommandLineAPI.fromMaybe(false), + in_returnByValue.fromMaybe(false), in_generatePreview.fromMaybe(false), saveResult, + result, wasThrown, savedResultIndex); + std::string raw_result = result->toJSONString().utf8().data(); + if (!convertRemoteObject(raw_result, out_result, errorString)) { + return DispatchResponse::Error(errorString.utf8().data()); + } + + if (in_silent.fromMaybe(false)) { + if (m_debugger->pauseOnExceptionsState() != previousPauseOnExceptionsState) + m_debugger->setPauseOnExceptionsState(previousPauseOnExceptionsState); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::getPossibleBreakpoints( + std::unique_ptr in_start, Maybe in_end, Maybe in_restrictToFunction, + std::unique_ptr>> *out_locations) { + + std::string scriptId = in_start->getScriptId(); + if (in_start->getLineNumber() < 0 || in_start->getColumnNumber(0) < 0) { + return DispatchResponse::Error("start.lineNumber and start.columnNumber should be >= 0"); + } + + if (in_end.isJust()) { + if (in_end.fromJust()->getScriptId() != scriptId) + return DispatchResponse::Error("Locations should contain the same scriptId"); + int line = in_end.fromJust()->getLineNumber(); + int column = in_end.fromJust()->getColumnNumber(0); + if (line < 0 || column < 0) return DispatchResponse::Error("end.lineNumber and end.columnNumber should be >= 0"); + } + + JSC::SourceID sourceID = static_cast(std::stoi(scriptId)); + + auto it = m_scripts.find(sourceID); + if (it == m_scripts.end()) { + return DispatchResponse::Error("No script for id: " + scriptId); + } + + *out_locations = std::make_unique>>(); + + for (auto &entry : m_javaScriptBreakpoints) { + RefPtr breakpointObject = entry.value; + + Inspector::ScriptBreakpoint scriptBreakpoint; + breakpointObject->getInteger(ASCIILiteral::fromLiteralUnsafe("lineNumber"), scriptBreakpoint.lineNumber); + + breakpointObject->getInteger(ASCIILiteral::fromLiteralUnsafe("columnNumber"), scriptBreakpoint.columnNumber); + + if (scriptBreakpoint.lineNumber < in_start->getLineNumber()) { + continue; + } + if (in_start->hasColumnNumber() && scriptBreakpoint.columnNumber < in_start->getColumnNumber(0)) { + continue; + } + + if (in_end.isJust()) { + if (scriptBreakpoint.lineNumber > in_end.fromJust()->getLineNumber()) { + continue; + } + if (in_end.fromJust()->hasColumnNumber() && + scriptBreakpoint.columnNumber > in_end.fromJust()->getColumnNumber(0)) { + continue; + } + } + + auto location = BreakLocation::create() + .setScriptId(scriptId) + .setLineNumber(scriptBreakpoint.lineNumber) + .setColumnNumber(scriptBreakpoint.columnNumber) + .build(); + + (*out_locations)->push_back(std::move(location)); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::getScriptSource(const std::string &in_scriptId, std::string *out_scriptSource) { + JSC::SourceID sourceID = static_cast(std::stoi(in_scriptId)); + ScriptsMap::iterator it = m_scripts.find(sourceID); + if (it != m_scripts.end()) { + *out_scriptSource = it->value.source.utf8().data(); + } else { + return DispatchResponse::Error("No script for id: " + in_scriptId); + } + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::getStackTrace(std::unique_ptr in_stackTraceId, + std::unique_ptr *out_stackTrace) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::pause() { + schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason::PauseOnNextStatement, nullptr); + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::pauseOnAsyncCall(std::unique_ptr in_parentStackTraceId) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::removeBreakpoint(const std::string &in_breakpointId) { + WTF::String breakpointIdentifier = in_breakpointId.c_str(); + m_javaScriptBreakpoints.remove(breakpointIdentifier); + + for (JSC::BreakpointID breakpointID : m_breakpointIdentifierToDebugServerBreakpointIDs.take(breakpointIdentifier)) { + m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.remove(breakpointID); + + const Inspector::BreakpointActions &breakpointActions = m_debugger->getActionsForBreakpoint(breakpointID); + for (auto &action : breakpointActions) + m_injectedScriptManager->releaseObjectGroup(objectGroupForBreakpointAction(action)); + + JSC::JSLockHolder locker(m_debugger->vm()); + m_debugger->removeBreakpointActions(breakpointID); + m_debugger->removeBreakpoint(breakpointID); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::restartFrame(const std::string &in_callFrameId, + std::unique_ptr> *out_callFrames, + Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::resume() { + Inspector::ErrorString errorString; + if (!m_pausedScriptState && !m_javaScriptPauseScheduled) { + errorString = ASCIILiteral::fromLiteralUnsafe("Was not paused or waiting to pause"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + cancelPauseOnNextStatement(); + m_debugger->continueProgram(); + m_conditionToDispatchResumed = ShouldDispatchResumed::WhenContinued; + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::searchInContent(const std::string &in_scriptId, const std::string &in_query, + Maybe in_caseSensitive, Maybe in_isRegex, + std::unique_ptr> *out_result) { + Inspector::ErrorString error; + JSC::SourceID sourceID = WTF::String(in_scriptId.c_str()).toIntPtr(); + auto it = m_scripts.find(sourceID); + if (it == m_scripts.end()) { + error = ASCIILiteral::fromLiteralUnsafe("No script for id: ") + WTF::String(in_scriptId.c_str()); + return DispatchResponse::Error(error.utf8().data()); + } + + auto result = Inspector::ContentSearchUtilities::searchInTextByLines( + it->value.source, in_query.c_str(), in_caseSensitive.fromMaybe(false), in_isRegex.fromMaybe(false)); + + const char *result_str = result->toJSONString().utf8().data(); + + // TODO 转换 + *out_result = std::make_unique>(); + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setAsyncCallStackDepth(int in_maxDepth) { + if (m_asyncStackTraceDepth == in_maxDepth) return DispatchResponse::OK(); + + if (in_maxDepth < 0) { + return DispatchResponse::Error("depth must be a positive number."); + } + m_asyncStackTraceDepth = in_maxDepth; + if (!m_asyncStackTraceDepth) { + clearAsyncStackTraceData(); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setBlackboxPatterns(std::unique_ptr> in_patterns) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse +JSCDebuggerAgentImpl::setBlackboxedRanges(const std::string &in_scriptId, + std::unique_ptr>> in_positions) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::setBreakpoint(std::unique_ptr in_location, + Maybe in_condition, std::string *out_breakpointId, + std::unique_ptr *out_actualLocation) { + + Inspector::ErrorString errorString; + JSC::SourceID sourceID; + unsigned lineNumber; + unsigned columnNumber; + if (!parseLocation(errorString, std::move(in_location), sourceID, lineNumber, columnNumber)) + return DispatchResponse::Error(errorString.utf8().data()); + + String condition = in_condition.fromMaybe("").c_str(); + bool autoContinue = false; // TODO 不支持 + unsigned ignoreCount = 0; // TODO 不支持 + RefPtr actions; // TODO 不支持 + + Inspector::BreakpointActions breakpointActions; + if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions)) + return DispatchResponse::Error(errorString.utf8().data()); + + auto scriptIterator = m_scripts.find(sourceID); + if (scriptIterator == m_scripts.end()) { + errorString = ASCIILiteral::fromLiteralUnsafe("No script for id: ") + String::number(sourceID); + return DispatchResponse::Error(errorString.utf8().data()); + } + + Script &script = scriptIterator->value; + JSC::Breakpoint breakpoint(sourceID, lineNumber, columnNumber, condition, autoContinue, ignoreCount); + resolveBreakpoint(script, breakpoint); + if (!breakpoint.resolved) { + errorString = ASCIILiteral::fromLiteralUnsafe("Could not resolve breakpoint"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + bool existing; + setBreakpoint(breakpoint, existing); + if (existing) { + errorString = ASCIILiteral::fromLiteralUnsafe("Breakpoint at specified location already exists"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + String breakpointIdentifier = + String::number(sourceID) + ':' + String::number(breakpoint.line) + ':' + String::number(breakpoint.column); + Inspector::ScriptBreakpoint scriptBreakpoint(breakpoint.line, breakpoint.column, condition, breakpointActions, + autoContinue, ignoreCount); + didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); + + *out_actualLocation = Location::create() + .setScriptId(String::number(breakpoint.sourceID).utf8().data()) + .setLineNumber(breakpoint.line) + .setColumnNumber(breakpoint.column) + .build(); + + *out_breakpointId = breakpointIdentifier.utf8().data(); + return DispatchResponse::OK(); +} + +DispatchResponse +JSCDebuggerAgentImpl::setBreakpointByUrl(int in_lineNumber, Maybe in_url, Maybe in_urlRegex, + Maybe in_scriptHash, Maybe in_columnNumber, + Maybe in_condition, std::string *out_breakpointId, + std::unique_ptr>> *out_locations) { + *out_locations = std::make_unique>>(); + + if (!in_url.isJust() && !in_urlRegex.isJust()) { + return DispatchResponse::Error("Either url or urlRegex must be specified."); + } + + String url = in_url.isJust() ? in_url.fromJust().c_str() : in_urlRegex.fromJust().c_str(); + int columnNumber = in_columnNumber.isJust() ? in_columnNumber.fromJust() : 0; + bool isRegex = in_urlRegex.isJust(); + + String breakpointIdentifier = + (isRegex ? "/" + url + "/" : url) + ':' + String::number(in_lineNumber) + ':' + String::number(columnNumber); + if (m_javaScriptBreakpoints.contains(breakpointIdentifier)) { + return DispatchResponse::Error("Breakpoint at specified location already exists."); + } + + String condition = emptyString(); + if (in_condition.isJust()) { + condition = in_condition.fromJust().c_str(); + } + bool autoContinue = false; + unsigned ignoreCount = 0; + + // type: Different kinds of breakpoint actions. ["log", "evaluate", "sound", "probe"] + // data: Data associated with this breakpoint type + // id: A frontend-assigned identifier for this breakpoint action. + RefPtr actions = JSON::Array::create(); + auto inspectorObj = JSON::Object::create(); + inspectorObj->setString("type", "evaluate"); + actions->pushObject(std::move(inspectorObj)); + + Inspector::BreakpointActions breakpointActions; + Inspector::ErrorString errorString; + if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions)) { + return DispatchResponse::Error(errorString.utf8().data()); + } + + m_javaScriptBreakpoints.set(breakpointIdentifier, + buildObjectForBreakpointCookie(url, in_lineNumber, columnNumber, condition, actions, + isRegex, autoContinue, ignoreCount)); + + for (auto &entry : m_scripts) { + Script &script = entry.value; + String scriptURLForBreakpoints = !script.sourceURL.isEmpty() ? script.sourceURL : script.url; + if (!matches(scriptURLForBreakpoints, url, isRegex)) continue; + + JSC::SourceID sourceID = entry.key; + JSC::Breakpoint breakpoint(sourceID, in_lineNumber, columnNumber, condition, autoContinue, ignoreCount); + resolveBreakpoint(script, breakpoint); + if (!breakpoint.resolved) continue; + + bool existing; + setBreakpoint(breakpoint, existing); + if (existing) continue; + + Inspector::ScriptBreakpoint scriptBreakpoint(breakpoint.line, breakpoint.column, condition, breakpointActions, + autoContinue, ignoreCount); + didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); + + (*out_locations) + ->push_back(Location::create() + .setScriptId(String::number(breakpoint.sourceID).utf8().data()) + .setLineNumber(breakpoint.line) + .setColumnNumber(breakpoint.column) + .build()); + } + + *out_breakpointId = breakpointIdentifier.utf8().data(); + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setBreakpointOnFunctionCall(const std::string &in_objectId, + Maybe in_condition, + std::string *out_breakpointId) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::setBreakpointsActive(bool in_active) { + if (in_active) { + m_debugger->activateBreakpoints(); + } else { + m_debugger->deactivateBreakpoints(); + } + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setPauseOnExceptions(const std::string &in_state) { + JSC::Debugger::PauseOnExceptionsState pauseState; + if (in_state == "none") + pauseState = JSC::Debugger::DontPauseOnExceptions; + else if (in_state == "all") + pauseState = JSC::Debugger::PauseOnAllExceptions; + else if (in_state == "uncaught") + pauseState = JSC::Debugger::PauseOnUncaughtExceptions; + else { + return DispatchResponse::Error("Unknown pause on exceptions mode: " + in_state); + } + + m_debugger->setPauseOnExceptionsState(pauseState); + if (m_debugger->pauseOnExceptionsState() != pauseState) { + return DispatchResponse::Error("Internal error. Could not change pause on exceptions state"); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setReturnValue(std::unique_ptr in_newValue) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::setScriptSource( + const std::string &in_scriptId, const std::string &in_scriptSource, Maybe in_dryRun, + Maybe> *out_callFrames, Maybe *out_stackChanged, Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId, Maybe *out_exceptionDetails) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::setSkipAllPauses(bool in_skip) { + if (in_skip) { + setBreakpointsActive(false); + setPauseOnExceptions("none"); + } else { // not skip + setBreakpointsActive(true); + setPauseOnExceptions("all"); + } + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::setVariableValue(int in_scopeNumber, const std::string &in_variableName, + std::unique_ptr in_newValue, + const std::string &in_callFrameId) { + return DispatchResponse::Error("not implement"); +} + +DispatchResponse JSCDebuggerAgentImpl::stepInto(Maybe in_breakOnAsyncCall) { + Inspector::ErrorString errorString; + if (!assertPaused(errorString)) return DispatchResponse::Error(errorString.utf8().data()); + + willStepAndMayBecomeIdle(); + m_debugger->stepIntoStatement(); + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::stepOut() { + Inspector::ErrorString errorString; + if (!assertPaused(errorString)) return DispatchResponse::Error(errorString.utf8().data()); + + willStepAndMayBecomeIdle(); + m_debugger->stepOutOfFunction(); + return DispatchResponse::OK(); +} + +DispatchResponse JSCDebuggerAgentImpl::stepOver() { + Inspector::ErrorString errorString; + if (!assertPaused(errorString)) return DispatchResponse::Error(errorString.utf8().data()); + ; + + willStepAndMayBecomeIdle(); + m_debugger->stepOverStatement(); + return DispatchResponse::OK(); +} + +///////////////////// Own Public//////////////////////////////// + +bool JSCDebuggerAgentImpl::isPaused() const { + return m_debugger->isPaused(); +} + +bool JSCDebuggerAgentImpl::breakpointsActive() const { + return m_debugger->breakpointsActive(); +} + +void JSCDebuggerAgentImpl::setSuppressAllPauses(bool suppress) { + m_debugger->setSuppressAllPauses(suppress); +} + +static RefPtr buildAssertPauseReason(const String &message) { + auto reason = Inspector::Protocol::Debugger::AssertPauseReason::create().release(); + if (!message.isNull()) reason->setMessage(message); + return reason->openAccessors(); +} + +void JSCDebuggerAgentImpl::handleConsoleAssert(const String &message) { + if (!m_debugger->breakpointsActive()) return; + + if (m_pauseOnAssertionFailures) + breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::Assert, buildAssertPauseReason(message)); +} + +void JSCDebuggerAgentImpl::didScheduleAsyncCall(JSC::ExecState *exec, int asyncCallType, int callbackIdentifier, + bool singleShot) { + if (!m_asyncStackTraceDepth) return; + + if (!m_debugger->breakpointsActive()) return; + + Ref callStack = Inspector::createScriptCallStack(exec, m_asyncStackTraceDepth); + ASSERT(callStack->size()); + if (!callStack->size()) return; + + RefPtr parentStackTrace; + if (m_currentAsyncCallIdentifier) { + auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifier.value()); + ASSERT(it != m_pendingAsyncCalls.end()); + parentStackTrace = it->value; + } + + auto identifier = std::make_pair(asyncCallType, callbackIdentifier); + auto asyncStackTrace = Inspector::AsyncStackTrace::create(WTFMove(callStack), singleShot, WTFMove(parentStackTrace)); + + m_pendingAsyncCalls.set(identifier, WTFMove(asyncStackTrace)); +} + +void JSCDebuggerAgentImpl::didCancelAsyncCall(int asyncCallType, int callbackIdentifier) { + if (!m_asyncStackTraceDepth) return; + + auto identifier = std::make_pair(asyncCallType, callbackIdentifier); + auto it = m_pendingAsyncCalls.find(identifier); + if (it == m_pendingAsyncCalls.end()) return; + + auto &asyncStackTrace = it->value; + asyncStackTrace->didCancelAsyncCall(); + + if (m_currentAsyncCallIdentifier && m_currentAsyncCallIdentifier.value() == identifier) return; + + m_pendingAsyncCalls.remove(identifier); +} + +void JSCDebuggerAgentImpl::willDispatchAsyncCall(int asyncCallType, int callbackIdentifier) { + if (!m_asyncStackTraceDepth) return; + + if (m_currentAsyncCallIdentifier) return; + + // A call can be scheduled before the Inspector is opened, or while async stack + // traces are disabled. If no call data exists, do nothing. + auto identifier = std::make_pair(asyncCallType, callbackIdentifier); + auto it = m_pendingAsyncCalls.find(identifier); + if (it == m_pendingAsyncCalls.end()) return; + + auto &asyncStackTrace = it->value; + asyncStackTrace->willDispatchAsyncCall(m_asyncStackTraceDepth); + + m_currentAsyncCallIdentifier = identifier; +} + +void JSCDebuggerAgentImpl::didDispatchAsyncCall() { + if (!m_asyncStackTraceDepth) return; + + if (!m_currentAsyncCallIdentifier) return; + + auto identifier = m_currentAsyncCallIdentifier.value(); + auto it = m_pendingAsyncCalls.find(identifier); + ASSERT(it != m_pendingAsyncCalls.end()); + + auto &asyncStackTrace = it->value; + asyncStackTrace->didDispatchAsyncCall(); + + m_currentAsyncCallIdentifier = WTF::nullopt; + + if (!asyncStackTrace->isPending()) m_pendingAsyncCalls.remove(identifier); +} + +void JSCDebuggerAgentImpl::schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason breakReason, + RefPtr &&data) { + if (m_javaScriptPauseScheduled) return; + + m_javaScriptPauseScheduled = true; + + m_breakReason = breakReason; + m_breakAuxData = WTFMove(data); + + JSC::JSLockHolder locker(m_debugger->vm()); + m_debugger->setPauseOnNextStatement(true); +} + +void JSCDebuggerAgentImpl::cancelPauseOnNextStatement() { + if (!m_javaScriptPauseScheduled) return; + + m_javaScriptPauseScheduled = false; + + clearBreakDetails(); + m_debugger->setPauseOnNextStatement(false); + m_enablePauseWhenIdle = false; +} + +void JSCDebuggerAgentImpl::breakProgram(Inspector::DebuggerFrontendDispatcher::Reason breakReason, + RefPtr &&data) { + m_breakReason = breakReason; + m_breakAuxData = WTFMove(data); + m_debugger->breakProgram(); +} + +static RefPtr buildCSPViolationPauseReason(const String &directiveText) { + auto reason = Inspector::Protocol::Debugger::CSPViolationPauseReason::create().setDirective(directiveText).release(); + return reason->openAccessors(); +} + +void JSCDebuggerAgentImpl::scriptExecutionBlockedByCSP(const String &directiveText) { + if (m_debugger->pauseOnExceptionsState() != JSC::Debugger::DontPauseOnExceptions) + breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::CSPViolation, + buildCSPViolationPauseReason(directiveText)); +} + +///////////////////// Own Protected//////////////////////////////// + +String JSCDebuggerAgentImpl::sourceMapURLForScript(const Inspector::ScriptDebugListener::Script &script) { + return script.sourceMappingURL; +} + +void JSCDebuggerAgentImpl::enable() { + if (m_enabled) return; + + m_debugger->addListener(this); + + if (m_listener) m_listener->debuggerWasEnabled(); + // active breakpoints by default + setBreakpointsActive(true); + m_enabled = true; +} + +void JSCDebuggerAgentImpl::disable(bool isBeingDestroyed) { + if (!m_enabled) return; + + m_debugger->removeListener(this, isBeingDestroyed); + clearInspectorBreakpointState(); + + if (!isBeingDestroyed) m_debugger->deactivateBreakpoints(); + + ASSERT(m_javaScriptBreakpoints.isEmpty()); + + if (m_listener) m_listener->debuggerWasDisabled(); + + clearAsyncStackTraceData(); + + m_pauseOnAssertionFailures = false; + + m_enabled = false; +} + +void JSCDebuggerAgentImpl::didClearGlobalObject() { + // Clear breakpoints from the debugger, but keep the inspector's model of which + // pages have what breakpoints, as the mapping is only sent to DebuggerAgent once. + clearDebuggerBreakpointState(); + + clearAsyncStackTraceData(); + + //m_frontendDispatcher->globalObjectCleared(); +} + +///////////////////// Own Private//////////////////////////////// + +Ref> +JSCDebuggerAgentImpl::currentCallFrames(const Inspector::InjectedScript &injectedScript) { + if (injectedScript.hasNoValue()) + return JSON::ArrayOf::create(); + + return injectedScript.wrapCallFrames(m_currentCallStack.get()); +} + +void JSCDebuggerAgentImpl::resolveBreakpoint(const Inspector::ScriptDebugListener::Script &script, + JSC::Breakpoint &breakpoint) { + if (breakpoint.line < static_cast(script.startLine) || + static_cast(script.endLine) < breakpoint.line) + return; + + m_debugger->resolveBreakpoint(breakpoint, script.sourceProvider.get()); +} + +void JSCDebuggerAgentImpl::setBreakpoint(JSC::Breakpoint &breakpoint, bool &existing) { + JSC::JSLockHolder locker(m_debugger->vm()); + m_debugger->setBreakpoint(breakpoint, existing); +} + +void JSCDebuggerAgentImpl::didSetBreakpoint(const JSC::Breakpoint &breakpoint, const String &breakpointIdentifier, + const Inspector::ScriptBreakpoint &scriptBreakpoint) { + JSC::BreakpointID id = breakpoint.id; + m_debugger->setBreakpointActions(id, scriptBreakpoint); + + auto debugServerBreakpointIDsIterator = m_breakpointIdentifierToDebugServerBreakpointIDs.find(breakpointIdentifier); + if (debugServerBreakpointIDsIterator == m_breakpointIdentifierToDebugServerBreakpointIDs.end()) + debugServerBreakpointIDsIterator = + m_breakpointIdentifierToDebugServerBreakpointIDs.set(breakpointIdentifier, Vector()).iterator; + debugServerBreakpointIDsIterator->value.append(id); + + m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.set(id, breakpointIdentifier); +} + +bool JSCDebuggerAgentImpl::assertPaused(Inspector::ErrorString &errorString) { + if (!m_pausedScriptState) { + errorString = ASCIILiteral::fromLiteralUnsafe("Can only perform operation while paused."); + return false; + } + + return true; +} + +void JSCDebuggerAgentImpl::clearDebuggerBreakpointState() { + { + JSC::JSLockHolder holder(m_debugger->vm()); + m_debugger->clearBreakpointActions(); + m_debugger->clearBreakpoints(); + m_debugger->clearBlacklist(); + } + + m_pausedScriptState = nullptr; + m_currentCallStack = {}; + m_scripts.clear(); + m_breakpointIdentifierToDebugServerBreakpointIDs.clear(); + m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.clear(); + m_continueToLocationBreakpointID = JSC::noBreakpointID; + clearBreakDetails(); + m_javaScriptPauseScheduled = false; + m_hasExceptionValue = false; + + if (isPaused()) { + m_debugger->continueProgram(); + m_frontend.resumed(); + } +} + +void JSCDebuggerAgentImpl::clearInspectorBreakpointState() { + Inspector::ErrorString dummyError; + Vector breakpointIdentifiers; + for (auto key : m_breakpointIdentifierToDebugServerBreakpointIDs.keys()) { + breakpointIdentifiers.append(key); + } + for (const String &identifier : breakpointIdentifiers) { + removeBreakpoint(identifier.utf8().data()); + } + + m_javaScriptBreakpoints.clear(); + + clearDebuggerBreakpointState(); +} + +void JSCDebuggerAgentImpl::clearBreakDetails() { + m_breakReason = Inspector::DebuggerFrontendDispatcher::Reason::Other; + m_breakAuxData = nullptr; +} + +void JSCDebuggerAgentImpl::clearExceptionValue() { + if (m_hasExceptionValue) { + m_injectedScriptManager->clearExceptionValue(); + m_hasExceptionValue = false; + } +} + +void JSCDebuggerAgentImpl::clearAsyncStackTraceData() { + m_pendingAsyncCalls.clear(); + m_currentAsyncCallIdentifier = WTF::nullopt; +} + +void JSCDebuggerAgentImpl::registerIdleHandler() { + if (!m_registeredIdleCallback) { + m_registeredIdleCallback = true; + JSC::VM &vm = m_debugger->vm(); + vm.whenIdle([this]() { didBecomeIdle(); }); + } +} + +void JSCDebuggerAgentImpl::willStepAndMayBecomeIdle() { + // When stepping the backend must eventually trigger a "paused" or "resumed" event. + // If the step causes us to exit the VM, then we should issue "resumed". + m_conditionToDispatchResumed = ShouldDispatchResumed::WhenIdle; + + registerIdleHandler(); +} + +void JSCDebuggerAgentImpl::didBecomeIdle() { + m_registeredIdleCallback = false; + + if (m_conditionToDispatchResumed == ShouldDispatchResumed::WhenIdle) { + cancelPauseOnNextStatement(); + m_debugger->continueProgram(); + m_frontend.resumed(); + } + + m_conditionToDispatchResumed = ShouldDispatchResumed::No; + + if (m_enablePauseWhenIdle) { + pause(); + } +} + +RefPtr JSCDebuggerAgentImpl::buildBreakpointPauseReason(JSC::BreakpointID debuggerBreakpointIdentifier) { + + // ASSERT(debuggerBreakpointIdentifier != JSC::noBreakpointID); + auto it = m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.find(debuggerBreakpointIdentifier); + if (it == m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.end()) return nullptr; + + auto reason = Inspector::Protocol::Debugger::BreakpointPauseReason::create().setBreakpointId(it->value).release(); + return reason->openAccessors(); +} + +RefPtr JSCDebuggerAgentImpl::buildExceptionPauseReason(JSC::JSValue exception, + const Inspector::InjectedScript &injectedScript) { + + // ASSERT(exception); + if (!exception) return nullptr; + + // ASSERT(!injectedScript.hasNoValue()); + if (injectedScript.hasNoValue()) return nullptr; + + return injectedScript.wrapObject(exception, JSCDebuggerAgentImpl::backtraceObjectGroup)->openAccessors(); +} + +static bool breakpointActionTypeForString(const String &typeString, Inspector::ScriptBreakpointActionType *output) { + if (typeString == Inspector::Protocol::InspectorHelpers::getEnumConstantValue( + Inspector::Protocol::Debugger::BreakpointAction::Type::Log)) { + *output = Inspector::ScriptBreakpointActionTypeLog; + return true; + } + if (typeString == Inspector::Protocol::InspectorHelpers::getEnumConstantValue( + Inspector::Protocol::Debugger::BreakpointAction::Type::Evaluate)) { + *output = Inspector::ScriptBreakpointActionTypeEvaluate; + return true; + } + if (typeString == Inspector::Protocol::InspectorHelpers::getEnumConstantValue( + Inspector::Protocol::Debugger::BreakpointAction::Type::Sound)) { + *output = Inspector::ScriptBreakpointActionTypeSound; + return true; + } + if (typeString == Inspector::Protocol::InspectorHelpers::getEnumConstantValue( + Inspector::Protocol::Debugger::BreakpointAction::Type::Probe)) { + *output = Inspector::ScriptBreakpointActionTypeProbe; + return true; + } + + return false; +} + +bool JSCDebuggerAgentImpl::breakpointActionsFromProtocol(Inspector::ErrorString &errorString, + RefPtr &actions, + Inspector::BreakpointActions *result) { + if (!actions) return true; + + unsigned actionsLength = actions->length(); + if (!actionsLength) return true; + + result->reserveCapacity(actionsLength); + for (unsigned i = 0; i < actionsLength; ++i) { + RefPtr value = actions->get(i); + RefPtr object; + if (!value->asObject(object)) { + errorString = ASCIILiteral::fromLiteralUnsafe("BreakpointAction of incorrect type, expected object"); + return false; + } + + String typeString; + if (!object->getString(ASCIILiteral::fromLiteralUnsafe("type"), typeString)) { + errorString = ASCIILiteral::fromLiteralUnsafe("BreakpointAction had type missing"); + return false; + } + + Inspector::ScriptBreakpointActionType type; + if (!breakpointActionTypeForString(typeString, &type)) { + errorString = ASCIILiteral::fromLiteralUnsafe("BreakpointAction had unknown type"); + return false; + } + + // Specifying an identifier is optional. They are used to correlate probe samples + // in the frontend across multiple backend probe actions and segregate object groups. + int identifier = 0; + object->getInteger(ASCIILiteral::fromLiteralUnsafe("id"), identifier); + + String data; + object->getString(ASCIILiteral::fromLiteralUnsafe("data"), data); + + result->append(Inspector::ScriptBreakpointAction(type, identifier, data)); + } + + return true; +} + +void JSCDebuggerAgentImpl::willRunMicrotask() { +} +void JSCDebuggerAgentImpl::didRunMicrotask() { +} + +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.h new file mode 100644 index 0000000000..a8742f3d0a --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_agent_impl.h @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_DEBUGGER_AGENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_DEBUGGER_AGENT_IMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inspector/impl/jsc_debugger_impl.h" +#include "inspector/protocol/debugger_backend.h" +#include "inspector/protocol/debugger_frontend.h" +#include "kraken_foundation.h" +#include + +#include +#include +#include + +namespace kraken::debugger { + +class InspectorSession; +class AgentContext; + +class JSCDebuggerAgentImpl : public DebuggerBackend, public Inspector::ScriptDebugListener { + WTF_MAKE_FAST_ALLOCATED; + +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCDebuggerAgentImpl); + +public: + JSCDebuggerAgentImpl(InspectorSession *session, debugger::AgentContext &context); + ~JSCDebuggerAgentImpl() override; + + /* Inspector::ScriptDebugListener */ + + void didParseSource(JSC::SourceID, const Inspector::ScriptDebugListener::Script &) override; + + void failedToParseSource(const WTF::String &url, const WTF::String &data, int firstLine, int errorLine, + const WTF::String &errorMessage) override; + + void didPause(JSC::ExecState &, JSC::JSValue callFrames, JSC::JSValue exception) override; + + void didContinue() override; + + void breakpointActionLog(JSC::ExecState &, const WTF::String &) override; + + void breakpointActionSound(int breakpointActionIdentifier) override; + + void breakpointActionProbe(JSC::ExecState &, const Inspector::ScriptBreakpointAction &, unsigned batchId, + unsigned sampleId, JSC::JSValue result) override; + + /*Backend Interface*/ + + DispatchResponse continueToLocation(std::unique_ptr in_location, + Maybe in_targetCallFrames) override; + DispatchResponse disable() override; + DispatchResponse enable(Maybe in_maxScriptsCacheSize, std::string *out_debuggerId) override; + DispatchResponse evaluateOnCallFrame(const std::string &in_callFrameId, const std::string &in_expression, + Maybe in_objectGroup, Maybe in_includeCommandLineAPI, + Maybe in_silent, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_throwOnSideEffect, + Maybe in_timeout, std::unique_ptr *out_result, + Maybe *out_exceptionDetails) override; + DispatchResponse + getPossibleBreakpoints(std::unique_ptr in_start, Maybe in_end, Maybe in_restrictToFunction, + std::unique_ptr>> *out_locations) override; + DispatchResponse getScriptSource(const std::string &in_scriptId, std::string *out_scriptSource) override; + DispatchResponse getStackTrace(std::unique_ptr in_stackTraceId, + std::unique_ptr *out_stackTrace) override; + + DispatchResponse pause() override; + DispatchResponse pauseOnAsyncCall(std::unique_ptr in_parentStackTraceId) override; + DispatchResponse removeBreakpoint(const std::string &in_breakpointId) override; + DispatchResponse restartFrame(const std::string &in_callFrameId, + std::unique_ptr> *out_callFrames, + Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId) override; + + DispatchResponse resume() override; + DispatchResponse searchInContent(const std::string &in_scriptId, const std::string &in_query, + Maybe in_caseSensitive, Maybe in_isRegex, + std::unique_ptr> *out_result) override; + + DispatchResponse setAsyncCallStackDepth(int in_maxDepth) override; + DispatchResponse setBlackboxPatterns(std::unique_ptr> in_patterns) override; + DispatchResponse + setBlackboxedRanges(const std::string &in_scriptId, + std::unique_ptr>> in_positions) override; + DispatchResponse setBreakpoint(std::unique_ptr in_location, Maybe in_condition, + std::string *out_breakpointId, std::unique_ptr *out_actualLocation) override; + + DispatchResponse setBreakpointByUrl(int in_lineNumber, Maybe in_url, Maybe in_urlRegex, + Maybe in_scriptHash, Maybe in_columnNumber, + Maybe in_condition, std::string *out_breakpointId, + std::unique_ptr>> *out_locations) override; + DispatchResponse setBreakpointOnFunctionCall(const std::string &in_objectId, Maybe in_condition, + std::string *out_breakpointId) override; + + DispatchResponse setBreakpointsActive(bool in_active) override; + DispatchResponse setPauseOnExceptions(const std::string &in_state) override; + DispatchResponse setReturnValue(std::unique_ptr in_newValue) override; + DispatchResponse setScriptSource(const std::string &in_scriptId, const std::string &in_scriptSource, + Maybe in_dryRun, Maybe> *out_callFrames, + Maybe *out_stackChanged, Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId, + Maybe *out_exceptionDetails) override; + DispatchResponse setSkipAllPauses(bool in_skip) override; + DispatchResponse setVariableValue(int in_scopeNumber, const std::string &in_variableName, + std::unique_ptr in_newValue, + const std::string &in_callFrameId) override; + DispatchResponse stepInto(Maybe in_breakOnAsyncCall) override; + DispatchResponse stepOut() override; + DispatchResponse stepOver() override; + + void willRunMicrotask() override; + void didRunMicrotask() override; + + /*own*/ + + static const char *backtraceObjectGroup; + + bool isPaused() const; + + bool breakpointsActive() const; + + void setSuppressAllPauses(bool); + + void handleConsoleAssert(const String &message); + + void didScheduleAsyncCall(JSC::ExecState *, int asyncCallType, int callbackIdentifier, bool singleShot); + void didCancelAsyncCall(int asyncCallType, int callbackIdentifier); + void willDispatchAsyncCall(int asyncCallType, int callbackIdentifier); + void didDispatchAsyncCall(); + + void schedulePauseOnNextStatement(Inspector::DebuggerFrontendDispatcher::Reason breakReason, + RefPtr&& data); + void cancelPauseOnNextStatement(); + bool pauseOnNextStatementEnabled() const { + return m_javaScriptPauseScheduled; + } + + void breakProgram(Inspector::DebuggerFrontendDispatcher::Reason breakReason, + RefPtr &&data); + void scriptExecutionBlockedByCSP(const String &directiveText); + + class Listener { + public: + virtual ~Listener() {} + virtual void debuggerWasEnabled() = 0; + virtual void debuggerWasDisabled() = 0; + }; + void setListener(Listener *listener) { + m_listener = listener; + } + + virtual void enable(); + virtual void disable(bool skipRecompile); + +protected: + virtual String sourceMapURLForScript(const Script &); + + void didClearGlobalObject(); + +private: + bool convertCallFrames(const std::string &in_callframes_str, + std::unique_ptr>> *out_callframes); + + bool convertCallFrames(rapidjson::Value::Array *in_array, rapidjson::Document::AllocatorType &in_allocator, + std::unique_ptr>> *out_callframes); + + bool convertStackTrace(const std::string &in_stackTrace_str, std::unique_ptr *out_trace); + + bool convertRemoteObject(const std::string &in_result, std::unique_ptr *out_result, + Inspector::ErrorString &error); + + Ref> currentCallFrames(const Inspector::InjectedScript &); + + void resolveBreakpoint(const Script &, JSC::Breakpoint &); + void setBreakpoint(JSC::Breakpoint &, bool &existing); + void didSetBreakpoint(const JSC::Breakpoint &, const String &, const Inspector::ScriptBreakpoint &); + + bool assertPaused(Inspector::ErrorString &); + void clearDebuggerBreakpointState(); + void clearInspectorBreakpointState(); + void clearBreakDetails(); + void clearExceptionValue(); + void clearAsyncStackTraceData(); + + enum class ShouldDispatchResumed { No, WhenIdle, WhenContinued }; + void registerIdleHandler(); + void willStepAndMayBecomeIdle(); + void didBecomeIdle(); + + RefPtr buildBreakpointPauseReason(JSC::BreakpointID); + RefPtr buildExceptionPauseReason(JSC::JSValue exception, + const Inspector::InjectedScript &); + + bool breakpointActionsFromProtocol(Inspector::ErrorString &, RefPtr &actions, + Inspector::BreakpointActions *result); + + typedef std::pair AsyncCallIdentifier; + + typedef HashMap ScriptsMap; + typedef HashMap> BreakpointIdentifierToDebugServerBreakpointIDsMap; + typedef HashMap> BreakpointIdentifierToBreakpointMap; + typedef HashMap DebugServerBreakpointIDToBreakpointIdentifier; + + Inspector::InjectedScriptManager *m_injectedScriptManager; + Listener *m_listener{nullptr}; + JSC::ExecState *m_pausedScriptState{nullptr}; + JSC::Strong m_currentCallStack; + ScriptsMap m_scripts; + BreakpointIdentifierToDebugServerBreakpointIDsMap m_breakpointIdentifierToDebugServerBreakpointIDs; + BreakpointIdentifierToBreakpointMap m_javaScriptBreakpoints; + DebugServerBreakpointIDToBreakpointIdentifier m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier; + JSC::BreakpointID m_continueToLocationBreakpointID; + Inspector::DebuggerFrontendDispatcher::Reason m_breakReason; + RefPtr m_breakAuxData; + ShouldDispatchResumed m_conditionToDispatchResumed{ShouldDispatchResumed::No}; + bool m_enablePauseWhenIdle{false}; + HashMap> m_pendingAsyncCalls; + WTF::Optional m_currentAsyncCallIdentifier{WTF::nullopt}; + bool m_enabled{false}; + bool m_javaScriptPauseScheduled{false}; + bool m_hasExceptionValue{false}; + bool m_didPauseStopwatch{false}; + bool m_pauseOnAssertionFailures{false}; + bool m_registeredIdleCallback{false}; + int m_asyncStackTraceDepth{0}; + +private: + InspectorSession *m_session; + DebuggerFrontend m_frontend; + debugger::JSCDebuggerImpl *m_debugger; + + rapidjson::Document m_doc; + int m_debugger_id = {0}; +}; +} // namespace kraken + +#endif diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc new file mode 100644 index 0000000000..74cd91f433 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "jsc_debugger_impl.h" +#include +#include +#include "kraken_bridge.h" + +namespace kraken::debugger { +using namespace JSC; +JSCDebuggerImpl::JSCDebuggerImpl(int32_t contextId, JSGlobalObject *globalObject) + : Inspector::ScriptDebugServer(globalObject->globalExec()->vm()), m_globalObject(globalObject), m_contextId(contextId) {} + +void JSCDebuggerImpl::recompileAllJSFunctions() { + JSC::JSLockHolder holder(vm()); + JSC::Debugger::recompileAllJSFunctions(); +} + +void JSCDebuggerImpl::attachDebugger() { + attach(m_globalObject); +} + +void JSCDebuggerImpl::detachDebugger(bool isBeingDestroyed) { + detach(m_globalObject, + isBeingDestroyed ? Debugger::GlobalObjectIsDestructing : Debugger::TerminatingDebuggingSession); + if (!isBeingDestroyed) recompileAllJSFunctions(); +} + +void JSCDebuggerImpl::runEventLoopWhilePaused() { + // Drop all locks so another thread can work in the VM while we are nested. + JSC::JSLock::DropAllLocks dropAllLocks(m_globalObject->globalExec()->vm()); + + Inspector::EventLoop loop; + while (!m_doneProcessingDebuggerEvents && !loop.ended()) { + loop.cycle(); + flushUITask(m_contextId); + } +} + +void JSCDebuggerImpl::reportException(JSC::ExecState *exec, JSC::Exception *exception) const { + if (m_globalObject && m_globalObject->consoleClient()) { + JSC::VM &vm = m_globalObject->vm(); + if (isTerminatedExecutionException(vm, exception)) return; + + auto scope = DECLARE_CATCH_SCOPE(vm); + JSC::ErrorHandlingScope errorScope(vm); + + Ref callStack = Inspector::createScriptCallStackFromException( + exec, exception, Inspector::ScriptCallStack::maxCallStackSizeToCapture); + + String errorMessage = exception->value().getString(exec); + scope.clearException(); + + m_globalObject->consoleClient()->profile(nullptr, errorMessage); + } +} +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h new file mode 100644 index 0000000000..3485fb379b --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_ANDROID_PLAYGROUND_JSC_DEBUGGER_IMPL_H +#define KRAKEN_ANDROID_PLAYGROUND_JSC_DEBUGGER_IMPL_H + +#include "kraken_foundation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kraken::debugger { +class JSCDebuggerImpl : public Inspector::ScriptDebugServer { +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCDebuggerImpl); + +public: + explicit JSCDebuggerImpl(int32_t contextId, JSC::JSGlobalObject *); + virtual ~JSCDebuggerImpl() {} + + JSC::JSGlobalObject *globalObject() const { + return m_globalObject; + } + +private: + void attachDebugger() override; + void detachDebugger(bool isBeingDestroyed) override; + + void recompileAllJSFunctions() override; + + void didPause(JSC::JSGlobalObject *) override { + } + + void didContinue(JSC::JSGlobalObject *) override { + } + + void runEventLoopWhilePaused() override; + bool isContentScript(JSC::ExecState *) const override { + return false; + } + + void reportException(JSC::ExecState *exec, JSC::Exception *exception) const override; + + JSC::JSGlobalObject *m_globalObject; + int32_t m_contextId; +}; +} // namespace kraken + +#endif // KRAKEN_ANDROID_PLAYGROUND_JSC_DEBUGGER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.cc new file mode 100644 index 0000000000..2562cfde80 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "jsc_heap_profiler_agent_impl.h" +#include "inspector/impl/jsc_log_agent_impl.h" + +namespace kraken::debugger { +JSCHeapProfilerAgentImpl::JSCHeapProfilerAgentImpl(kraken::debugger::InspectorSession *session, + kraken::debugger::AgentContext &context) + : m_session(session), m_environment(context.environment) {} + +DispatchResponse JSCHeapProfilerAgentImpl::collectGarbage() { + auto &vm = m_environment->vm(); + JSC::JSLockHolder lock(vm); + JSC::sanitizeStackForVM(vm); + vm.heap.collectSync(); + return DispatchResponse::OK(); +} + +DispatchResponse JSCHeapProfilerAgentImpl::enable() { + if (m_enabled) return DispatchResponse::OK(); + m_enabled = true; + m_environment->vm().heap.addObserver(this); + return DispatchResponse::OK(); +} + +DispatchResponse JSCHeapProfilerAgentImpl::disable() { + if (!m_enabled) return DispatchResponse::OK(); + m_enabled = false; + m_environment->vm().heap.removeObserver(this); + // TODO clearHeapSnapshots + return DispatchResponse::OK(); +} + +// HeapObserver +void JSCHeapProfilerAgentImpl::willGarbageCollect() { + if (!m_enabled) return; + m_gcStartTime = m_environment->executionStopwatch()->elapsedTime().milliseconds(); +} + +void JSCHeapProfilerAgentImpl::didGarbageCollect(JSC::CollectionScope) { + if (m_gcStartTime == -1) return; + + double endTime = m_environment->executionStopwatch()->elapsedTime().milliseconds(); + + WTF::StringBuilder builder; + builder.append("last gc elapsed "); + auto &&string = WTF::String::number(endTime - m_gcStartTime); + builder.append(string.characters8(), string.length()); + builder.append("ms"); + auto now = std::chrono::high_resolution_clock::now(); + m_session->logAgent()->addMessageToConsole( + LogEntry::create() + .setLevel(LogEntry::LevelEnum::Verbose) + .setTimestamp(std::chrono::duration_cast(now.time_since_epoch()).count()) + .setSource(LogEntry::SourceEnum::Javascript) + .setText(builder.toString().utf8().data()) + .build()); + m_gcStartTime = -1; +} + +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.h new file mode 100644 index 0000000000..61e84dc6bc --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_heap_profiler_agent_impl.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_HEAP_PROFILER_AGENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_HEAP_PROFILER_AGENT_IMPL_H + +#include "inspector/inspector_session.h" +#include "inspector/protocol/heap_profiler_backend.h" + +#include + +namespace kraken::debugger { +class JSCHeapProfilerAgentImpl : public HeapProfilerBackend, public JSC::HeapObserver { +public: + JSCHeapProfilerAgentImpl(InspectorSession *session, debugger::AgentContext &context); + + DispatchResponse collectGarbage() override; + DispatchResponse disable() override; + DispatchResponse enable() override; + + void willGarbageCollect() override; + void didGarbageCollect(JSC::CollectionScope) override; + +private: + InspectorSession *m_session; + bool m_enabled{false}; + double m_gcStartTime{-1}; + Inspector::InspectorEnvironment *m_environment; +}; +} // namespace kraken + +#endif diff --git a/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.cc new file mode 100644 index 0000000000..9439138621 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "jsc_log_agent_impl.h" +#include "inspector/inspector_session.h" + +namespace kraken::debugger { + +JSCLogAgentImpl::JSCLogAgentImpl(kraken::debugger::InspectorSession *session, + kraken::debugger::AgentContext &context) + : m_session(session), m_frontend(context.channel) {} + +JSCLogAgentImpl::~JSCLogAgentImpl() {} + +DispatchResponse JSCLogAgentImpl::enable() { + m_enabled = true; + return DispatchResponse::OK(); +} + +DispatchResponse JSCLogAgentImpl::disable() { + m_enabled = false; + return DispatchResponse::OK(); +} + +void JSCLogAgentImpl::addMessageToConsole(std::unique_ptr entry) { + m_frontend.entryAdded(std::move(entry)); +} + +DispatchResponse JSCLogAgentImpl::clear() { + return DispatchResponse::OK(); +} + +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.h new file mode 100644 index 0000000000..6b52e80049 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_log_agent_impl.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_LOG_AGENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_LOG_AGENT_IMPL_H + +#include "inspector/protocol/log_backend.h" +#include "inspector/protocol/log_frontend.h" + +namespace kraken::debugger { +class InspectorSession; +class AgentContext; + +class JSCLogAgentImpl : public LogBackend { +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCLogAgentImpl); + +public: + JSCLogAgentImpl(InspectorSession *session, debugger::AgentContext &context); + ~JSCLogAgentImpl() override; + + /***************** LogBackend *********************/ + DispatchResponse disable() override; + DispatchResponse enable() override; + DispatchResponse clear() override; + void addMessageToConsole(std::unique_ptr entry) override; + +private: + bool m_enabled{false}; + +private: + InspectorSession *m_session; + LogFrontend m_frontend; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_JSC_LOG_AGENT_IMPL_H diff --git a/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.cc new file mode 100644 index 0000000000..af54df9561 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "jsc_page_agent_impl.h" +#include "inspector/inspector_session.h" + +namespace kraken::debugger { + +JSCPageAgentImpl::JSCPageAgentImpl(kraken::debugger::InspectorSession *session, + kraken::debugger::AgentContext &context) + : m_session(session) {} + +JSCPageAgentImpl::~JSCPageAgentImpl() {} + +DispatchResponse JSCPageAgentImpl::enable() { + m_enabled = true; + return DispatchResponse::OK(); +} + +DispatchResponse JSCPageAgentImpl::disable() { + m_enabled = false; + return DispatchResponse::OK(); +} + +DispatchResponse JSCPageAgentImpl::reload(kraken::debugger::Maybe in_ignoreCache, + kraken::debugger::Maybe in_scriptToEvaluateOnLoad) { + if (m_session && m_session->protocolHandler()) { + m_session->protocolHandler()->handlePageReload(); + return DispatchResponse::OK(); + } else { + return DispatchResponse::Error("session destroyed or protocol handler destroyed"); + } +} +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.h new file mode 100644 index 0000000000..0df17dc7ee --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_page_agent_impl.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_PAGE_AGENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_PAGE_AGENT_IMPL_H + +#include "inspector/protocol/page_backend.h" +#include "kraken_foundation.h" + +namespace kraken::debugger { + +class InspectorSession; +class AgentContext; + +class JSCPageAgentImpl : public PageBackend { +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCPageAgentImpl); + +public: + JSCPageAgentImpl(InspectorSession *session, debugger::AgentContext &context); + ~JSCPageAgentImpl() override; + + /***************** PageBackend *********************/ + DispatchResponse disable() override; + DispatchResponse enable() override; + + DispatchResponse reload(Maybe in_ignoreCache, Maybe in_scriptToEvaluateOnLoad) override; + +private: + bool m_enabled{false}; + +private: + InspectorSession *m_session; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_JSC_PAGE_AGENT_IMPL_H diff --git a/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.cc new file mode 100644 index 0000000000..ab2627f142 --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.cc @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "jsc_runtime_agent_impl.h" +#include "inspector/inspector_session.h" +#include + +namespace kraken { +namespace debugger { + +JSCRuntimeAgentImpl::JSCRuntimeAgentImpl(kraken::debugger::InspectorSession *session, + kraken::debugger::AgentContext &context) + : m_session(session), m_frontend(context.channel), m_debugger(context.debugger), + m_injectedScriptManager(context.injectedScriptManager) {} + +static Inspector::ScriptDebugServer::PauseOnExceptionsState +setPauseOnExceptionsState(Inspector::ScriptDebugServer &scriptDebugServer, + Inspector::ScriptDebugServer::PauseOnExceptionsState newState) { + Inspector::ScriptDebugServer::PauseOnExceptionsState presentState = scriptDebugServer.pauseOnExceptionsState(); + if (presentState != newState) scriptDebugServer.setPauseOnExceptionsState(newState); + return presentState; +} + +/***************** RuntimeBackend *********************/ +void JSCRuntimeAgentImpl::awaitPromise(const std::string &in_promiseObjectId, Maybe in_returnByValue, + Maybe in_generatePreview, std::unique_ptr callback) { + // TODO +} + +bool JSCRuntimeAgentImpl::convertRemoteObject(const std::string &in_result, std::unique_ptr *out_result, + Inspector::ErrorString &error) { + rapidjson::Document in_result_doc; + in_result_doc.Parse(in_result.c_str()); + if (in_result_doc.HasParseError() || !in_result_doc.IsObject()) { + KRAKEN_LOG(ERROR) << "remoteObject parsed error..."; + return false; + } + auto copy = rapidjson::Value(in_result_doc, m_doc.GetAllocator()); + ErrorSupport errorSupport; + *out_result = debugger::RemoteObject::fromValue(©, &errorSupport); + if (errorSupport.hasErrors()) { + error = errorSupport.errors().c_str(); + return false; + } + return true; +} + +void JSCRuntimeAgentImpl::callFunctionOn(const std::string &in_functionDeclaration, Maybe in_objectId, + Maybe>> in_arguments, + Maybe in_silent, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_userGesture, + Maybe in_awaitPromise, Maybe in_executionContextId, + Maybe in_objectGroup, + std::unique_ptr callback) { + if (!in_objectId.isJust()) { + callback->sendFailure(DispatchResponse::Error("params invalid. objectId not specified")); + return; + } + + Inspector::InjectedScript injectedScript = + m_injectedScriptManager->injectedScriptForObjectId(in_objectId.fromJust().c_str()); + if (injectedScript.hasNoValue()) { + callback->sendFailure(DispatchResponse::Error("Could not find InjectedScript for objectId")); + return; + } + + std::string arguments; + if (in_arguments.isJust()) { + rapidjson::Document doc; + doc.SetArray(); + for (auto &&arg : *in_arguments.fromJust()) { + doc.PushBack(arg->toValue(doc.GetAllocator()), doc.GetAllocator()); + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + arguments = buffer.GetString(); + } + RefPtr out_result; + WTF::String errorString; + WTF::Optional wasThrown; + injectedScript.callFunctionOn(errorString, in_objectId.fromJust().c_str(), in_functionDeclaration.c_str(), + arguments.c_str(), in_returnByValue.fromMaybe(false), + in_generatePreview.fromMaybe(false), out_result, wasThrown); + + if (!errorString.isEmpty() || !out_result) { + callback->sendFailure(DispatchResponse::Error(errorString.utf8().data())); + return; + } + + std::unique_ptr result; + convertRemoteObject(out_result->toJSONString().utf8().data(), &result, errorString); + if (!errorString.isEmpty() || !result) { + callback->sendFailure(DispatchResponse::Error(errorString.utf8().data())); + return; + } + + callback->sendSuccess(std::move(result), {}); +} + +DispatchResponse JSCRuntimeAgentImpl::compileScript(const std::string &in_expression, const std::string &in_sourceURL, + bool in_persistScript, Maybe in_executionContextId, + Maybe *out_scriptId, + Maybe *out_exceptionDetails) { + int executionContextId = in_executionContextId.fromMaybe(0); + WTF::String errorString; + Inspector::InjectedScript injectedScript = injectedScriptForEval(errorString, &executionContextId); + if (!errorString.isEmpty()) { + return DispatchResponse::Error(errorString.utf8().data()); + } + if (injectedScript.hasNoValue()) { + return DispatchResponse::Error("injected script not found"); + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCRuntimeAgentImpl::disable() { + m_enabled = false; + m_frontend.executionContextDestroyed(0); + return DispatchResponse::OK(); +} + +DispatchResponse JSCRuntimeAgentImpl::discardConsoleEntries() { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::enable() { + m_enabled = true; + m_frontend.executionContextCreated( + ExecutionContextDescription::create().setId(m_session->rpcSession()->sessionId()).setName("default").setOrigin("default").setAuxData(nullptr).build()); + return DispatchResponse::OK(); +} + +Inspector::InjectedScript JSCRuntimeAgentImpl::injectedScriptForEval(WTF::String &errorString, + const int *executionContextId) { + JSC::ExecState *scriptState = m_debugger->globalObject()->globalExec(); + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(scriptState); + if (injectedScript.hasNoValue()) + errorString = ASCIILiteral::fromLiteralUnsafe("Internal error: main world execution context not found."); + return injectedScript; +} + +void JSCRuntimeAgentImpl::evaluate(const std::string &in_expression, Maybe in_objectGroup, + Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_contextId, + Maybe in_returnByValue, Maybe in_generatePreview, + Maybe in_userGesture, Maybe in_awaitPromise, + Maybe in_throwOnSideEffect, Maybe in_timeout, + std::unique_ptr callback) { + int executionContextId = in_contextId.fromMaybe(0); + WTF::String errorString; + Inspector::InjectedScript injectedScript = injectedScriptForEval(errorString, &executionContextId); + if (!errorString.isEmpty()) { + callback->sendFailure(DispatchResponse::Error(errorString.utf8().data())); + return; + } + if (injectedScript.hasNoValue()) { + callback->sendFailure(DispatchResponse::Error("injected script not found")); + return; + } + + WTF::Optional wasThrown; + WTF::Optional savedResultIndex; + RefPtr result; + injectedScript.evaluate(errorString, in_expression.c_str(), in_objectGroup.fromMaybe("").c_str(), + in_includeCommandLineAPI.fromMaybe(false), in_returnByValue.fromMaybe(false), + in_generatePreview.fromMaybe(false), + false, // saveResult + result, wasThrown, savedResultIndex); + + if (result) { + std::unique_ptr resultV8; + convertRemoteObject(result->toJSONString().utf8().data(), &resultV8, errorString); + if (resultV8) { + callback->sendSuccess(std::move(resultV8), {}); + } else { + callback->sendFailure(DispatchResponse::Error(errorString.utf8().data())); + } + } else { + callback->sendFailure(DispatchResponse::Error("Runtime.evaluate internal error")); + } +} + +DispatchResponse JSCRuntimeAgentImpl::getIsolateId(std::string *out_id) { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::getHeapUsage(double *out_usedSize, double *out_totalSize) { + if (!m_debugger || !m_debugger->globalObject()) { + return DispatchResponse::Error("internal error"); + } + auto &vm = m_debugger->vm(); + JSC::JSLockHolder holder(vm); + auto &heap = vm.heap; + *out_usedSize = heap.size(); + *out_totalSize = heap.capacity(); + + // *out_usedSize = 0; + // *out_totalSize = 0; + return DispatchResponse::OK(); +} + +static WTF::String generatePreview(const rapidjson::Value &value) { + WTF::StringBuilder builder; + if (value.HasMember("value")) { + if (value["value"].IsString()) { + builder.append("\""); + builder.append(value["value"].GetString()); + builder.append("\""); + } else if (value["value"].IsBool()) { + builder.append(value["value"].GetBool() ? "true" : "false"); + } else if (value["value"].IsDouble()) { + builder.append(value["value"].GetDouble()); + } else if (value["value"].IsInt()) { + builder.append(value["value"].GetInt()); + } else { + builder.append("\"unknown\""); + } + + } else if (value.HasMember("description") && value["description"].IsString()) { + builder.append("\""); + builder.append(value["description"].GetString()); + builder.append("\""); + } else if (value.HasMember("className") && value["className"].IsString()) { + builder.append("\""); + builder.append(value["className"].GetString()); + builder.append("\""); + } else if (value.HasMember("type") && value["type"].IsString()) { + builder.append("\""); + builder.append(value["type"].GetString()); + builder.append("\""); + } else { + builder.append("\""); + builder.append("unknown"); + builder.append("\""); + } + return builder.toString(); +} + +DispatchResponse JSCRuntimeAgentImpl::getProperties( + const std::string &in_objectId, Maybe in_ownProperties, Maybe in_accessorPropertiesOnly, + Maybe in_generatePreview, std::unique_ptr>> *out_result, + Maybe>> *out_internalProperties, + Maybe>> *out_privateProperties, + Maybe *out_exceptionDetails) { + WTF::String objectId = in_objectId.c_str(); + + Inspector::ErrorString errorString; + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId); + if (injectedScript.hasNoValue()) { + errorString = ASCIILiteral::fromLiteralUnsafe("Could not find InjectedScript for objectId"); + return DispatchResponse::Error(errorString.utf8().data()); + } + + // resolve objectId + rapidjson::Document d; + d.Parse(in_objectId.c_str()); + if (d.HasMember("type") && d["type"].IsString() && strcmp(d["type"].GetString(), "collection") == 0) { + // map or set + RefPtr> collection_entries; + injectedScript.getCollectionEntries(errorString, objectId, WTF::String(), 0, 0, collection_entries); + if (!collection_entries) { + return DispatchResponse::Error("target object is not a collection"); + } + rapidjson::Document collectionJson; + collectionJson.Parse(collection_entries->toJSONString().utf8().data()); + if (!collectionJson.IsArray()) { + return DispatchResponse::Error("target object is not a collection"); + } + *out_result = std::make_unique>>(); + int index = 0; + for (auto &entry : collectionJson.GetArray()) { + // 不支持嵌套 + WTF::StringBuilder desc; + if (entry.HasMember("key") && entry.HasMember("value")) { + desc.append("{"); + desc.append(generatePreview(entry["key"]).characters8()); + desc.append(" => "); + desc.append(generatePreview(entry["value"]).characters8()); + desc.append("}"); + } else if (entry.HasMember("value")) { + desc.append(generatePreview(entry["value"]).characters8()); + } + + auto remoteObj = RemoteObject::create() + .setType("Object") + .setSubtype("Object") + .setObjectId("") + .setValue(nullptr) + .setUnserializableValue("") + .setDescription(desc.toString().utf8().data()) + // .setPreview(std::move(preview)) + .build(); + + auto descriptor = PropertyDescriptor::create() + .setEnumerable(true) + .setConfigurable(true) + .setName(std::to_string(index++)) + .setValue(std::move(remoteObj)) + .build(); + + (*out_result)->push_back(std::move(descriptor)); + } + + return DispatchResponse::OK(); + } + + Inspector::ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = + setPauseOnExceptionsState(*m_debugger, Inspector::ScriptDebugServer::DontPauseOnExceptions); + + RefPtr> result; + RefPtr> internalProperties; + + injectedScript.getProperties(errorString, objectId, in_ownProperties.fromMaybe(false), + in_generatePreview.fromMaybe(false), result); + + injectedScript.getInternalProperties(errorString, objectId, in_generatePreview.fromMaybe(false), internalProperties); + + // TODO: support set / map ... +// RefPtr> collection_entries; +// injectedScript.getCollectionEntries(errorString, objectId, WTF::String(), 0, 0, collection_entries); +// +// if (collection_entries && result) { +// auto descriptor = Inspector::Protocol::Runtime::PropertyDescriptor::create() +// .setName("[[Entries]]") +// .setEnumerable(true) +// .setConfigurable(false) +// .release(); +// auto remoteObj = Inspector::Protocol::Runtime::RemoteObject::create() +// .setType(Inspector::Protocol::Runtime::RemoteObject::Type::Object) +// .release(); +// WTF::StringBuilder builder; +// builder.append("Array("); +// builder.append(WTF::String::number(collection_entries->length())); +// builder.append(")"); +// remoteObj->setDescription(builder.toString()); +// +// d.AddMember("type", "collection", d.GetAllocator()); +// rapidjson::StringBuffer buffer; +// rapidjson::Writer writer(buffer); +// d.Accept(writer); +// remoteObj->setObjectId(buffer.GetString()); +// descriptor->setValue(std::move(remoteObj)); +// result->addItem(std::move(descriptor)); +// } + + setPauseOnExceptionsState(*m_debugger, previousPauseOnExceptionsState); + + // transfer jsc -> v8 + + if (result == nullptr) { + return DispatchResponse::Error("result not found"); + } + + *out_result = std::make_unique>>(); + + std::string resultStr = result->toJSONString().utf8().data(); + + rapidjson::Document result_obj; + result_obj.Parse(resultStr.c_str()); + if (result_obj.HasParseError() || !result_obj.IsArray()) { + KRAKEN_LOG(ERROR) << "[Runtime.getProperties] resultObj parsed error..."; + KRAKEN_LOG(ERROR) << result_obj.HasParseError() << "|" << result_obj.IsArray() << "|" << resultStr.c_str(); + return DispatchResponse::Error("resultObj parsed json error"); + } + + for (auto &propItem : result_obj.GetArray()) { + if (propItem.IsObject()) { + ErrorSupport err; + auto prop = PropertyDescriptor::fromValue(&propItem, &err); + if (err.hasErrors()) { + KRAKEN_LOG(ERROR) << "[Runtime.getProperties] PropertyDescriptor transformed error," << err.errors(); + continue; + } + (*out_result)->push_back(std::move(prop)); + } + } + + if (internalProperties != nullptr) { + *out_internalProperties = std::make_unique>>(); + std::string internalPropStr = internalProperties->toJSONString().utf8().data(); + + rapidjson::Document internal_prop_obj; + internal_prop_obj.Parse(internalPropStr.c_str()); + if (internal_prop_obj.HasParseError() || !internal_prop_obj.IsArray()) { + KRAKEN_LOG(ERROR) << "[Runtime.getProperties] internal_prop_obj parsed error..."; + return DispatchResponse::Error("resultObj parsed json error"); + } + + for (auto &internalPropItem : internal_prop_obj.GetArray()) { + if (internalPropItem.IsObject()) { + ErrorSupport err; + auto internalProp = InternalPropertyDescriptor::fromValue(&internalPropItem, &err); + if (err.hasErrors()) { + KRAKEN_LOG(ERROR) << "[Runtime.getProperties] PropertyDescriptor transformed error," << err.errors(); + continue; + } + (*out_internalProperties).fromJust()->push_back(std::move(internalProp)); + } + } + } + + return DispatchResponse::OK(); +} + +DispatchResponse JSCRuntimeAgentImpl::globalLexicalScopeNames(Maybe in_executionContextId, + std::unique_ptr> *out_names) { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::queryObjects(const std::string &in_prototypeObjectId, + Maybe in_objectGroup, + std::unique_ptr *out_objects) { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::releaseObject(const std::string &in_objectId) { + WTF::String objectId = in_objectId.c_str(); + Inspector::InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(objectId); + if (!injectedScript.hasNoValue()) injectedScript.releaseObject(objectId); + return DispatchResponse::OK(); +} + +DispatchResponse JSCRuntimeAgentImpl::releaseObjectGroup(const std::string &in_objectGroup) { + m_injectedScriptManager->releaseObjectGroup(in_objectGroup.c_str()); + return DispatchResponse::OK(); +} + +DispatchResponse JSCRuntimeAgentImpl::runIfWaitingForDebugger() { + return DispatchResponse::OK(); +} + +void JSCRuntimeAgentImpl::runScript(const std::string &in_scriptId, Maybe in_executionContextId, + Maybe in_objectGroup, Maybe in_silent, + Maybe in_includeCommandLineAPI, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_awaitPromise, + std::unique_ptr callback) { + // TODO +} + +DispatchResponse JSCRuntimeAgentImpl::setCustomObjectFormatterEnabled(bool in_enabled) { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::setMaxCallStackSizeToCapture(int in_size) { + return DispatchResponse::Error("not implement yet"); +} + +void JSCRuntimeAgentImpl::terminateExecution(std::unique_ptr callback) { + // TODO +} + +DispatchResponse JSCRuntimeAgentImpl::addBinding(const std::string &in_name, Maybe in_executionContextId) { + return DispatchResponse::Error("not implement yet"); +} + +DispatchResponse JSCRuntimeAgentImpl::removeBinding(const std::string &in_name) { + return DispatchResponse::Error("not implement yet"); +} + +JSCRuntimeAgentImpl::~JSCRuntimeAgentImpl() { + m_frontend.executionContextsCleared( + ExecutionContextDescription::create().setId(m_session->rpcSession()->sessionId()).setName("default").setOrigin("default").setAuxData(nullptr).build()); +} + +/// Own + +void JSCRuntimeAgentImpl::setTypeProfilerEnabledState(bool isTypeProfilingEnabled) { + if (m_isTypeProfilingEnabled == isTypeProfilingEnabled) { + return; + } + m_isTypeProfilingEnabled = isTypeProfilingEnabled; + + JSC::VM &vm = m_debugger->vm(); + vm.whenIdle([&vm, isTypeProfilingEnabled]() { + bool shouldRecompileFromTypeProfiler = + (isTypeProfilingEnabled ? vm.enableTypeProfiler() : vm.disableTypeProfiler()); + if (shouldRecompileFromTypeProfiler) vm.deleteAllCode(JSC::PreventCollectionAndDeleteAllCode); + }); +} + +void JSCRuntimeAgentImpl::setControlFlowProfilerEnabledState(bool isControlFlowProfilingEnabled) { + if (m_isControlFlowProfilingEnabled == isControlFlowProfilingEnabled) { + return; + } + m_isControlFlowProfilingEnabled = isControlFlowProfilingEnabled; + JSC::VM &vm = m_debugger->vm(); + vm.whenIdle([&vm, isControlFlowProfilingEnabled]() { + bool shouldRecompileFromControlFlowProfiler = + (isControlFlowProfilingEnabled ? vm.enableControlFlowProfiler() : vm.disableControlFlowProfiler()); + + if (shouldRecompileFromControlFlowProfiler) vm.deleteAllCode(JSC::PreventCollectionAndDeleteAllCode); + }); +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.h new file mode 100644 index 0000000000..07952ac9eb --- /dev/null +++ b/plugins/devtools/bridge/inspector/impl/jsc_runtime_agent_impl.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_JSC_RUNTIME_AGENT_IMPL_H +#define KRAKEN_DEBUGGER_JSC_RUNTIME_AGENT_IMPL_H + +#include "inspector/impl/jsc_debugger_impl.h" +#include "inspector/protocol/runtime_backend.h" +#include "inspector/protocol/runtime_frontend.h" +#include "kraken_foundation.h" +#include +#include +#include +#include + +namespace kraken::debugger { +class InspectorSession; +class AgentContext; + +class JSCRuntimeAgentImpl : public RuntimeBackend { +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCRuntimeAgentImpl); + +public: + JSCRuntimeAgentImpl(InspectorSession *session, debugger::AgentContext &context); + ~JSCRuntimeAgentImpl() override; + + /***************** RuntimeBackend *********************/ + void awaitPromise(const std::string &in_promiseObjectId, Maybe in_returnByValue, Maybe in_generatePreview, + std::unique_ptr callback) override; + + void callFunctionOn(const std::string &in_functionDeclaration, Maybe in_objectId, + Maybe>> in_arguments, Maybe in_silent, + Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, + Maybe in_awaitPromise, Maybe in_executionContextId, Maybe in_objectGroup, + std::unique_ptr callback) override; + + DispatchResponse compileScript(const std::string &in_expression, const std::string &in_sourceURL, + bool in_persistScript, Maybe in_executionContextId, + Maybe *out_scriptId, + Maybe *out_exceptionDetails) override; + + DispatchResponse disable() override; + + DispatchResponse discardConsoleEntries() override; + + DispatchResponse enable() override; + + void evaluate(const std::string &in_expression, Maybe in_objectGroup, + Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_contextId, + Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, + Maybe in_awaitPromise, Maybe in_throwOnSideEffect, Maybe in_timeout, + std::unique_ptr callback) override; + + DispatchResponse getIsolateId(std::string *out_id) override; + + DispatchResponse getHeapUsage(double *out_usedSize, double *out_totalSize) override; + + DispatchResponse + getProperties(const std::string &in_objectId, Maybe in_ownProperties, Maybe in_accessorPropertiesOnly, + Maybe in_generatePreview, + std::unique_ptr>> *out_result, + Maybe>> *out_internalProperties, + Maybe>> *out_privateProperties, + Maybe *out_exceptionDetails) override; + + DispatchResponse globalLexicalScopeNames(Maybe in_executionContextId, + std::unique_ptr> *out_names) override; + + DispatchResponse queryObjects(const std::string &in_prototypeObjectId, Maybe in_objectGroup, + std::unique_ptr *out_objects) override; + + DispatchResponse releaseObject(const std::string &in_objectId) override; + + DispatchResponse releaseObjectGroup(const std::string &in_objectGroup) override; + + DispatchResponse runIfWaitingForDebugger() override; + void runScript(const std::string &in_scriptId, Maybe in_executionContextId, Maybe in_objectGroup, + Maybe in_silent, Maybe in_includeCommandLineAPI, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_awaitPromise, + std::unique_ptr callback) override; + + DispatchResponse setCustomObjectFormatterEnabled(bool in_enabled) override; + + DispatchResponse setMaxCallStackSizeToCapture(int in_size) override; + + void terminateExecution(std::unique_ptr callback) override; + + DispatchResponse addBinding(const std::string &in_name, Maybe in_executionContextId) override; + + DispatchResponse removeBinding(const std::string &in_name) override; + +protected: + virtual Inspector::InjectedScript injectedScriptForEval(WTF::String &, const int *executionContextId); + +private: + bool convertRemoteObject(const std::string &in_result, std::unique_ptr *out_result, + Inspector::ErrorString &error); + + void setTypeProfilerEnabledState(bool); + void setControlFlowProfilerEnabledState(bool); + + Inspector::InjectedScriptManager *m_injectedScriptManager; + bool m_enabled{false}; + bool m_isTypeProfilingEnabled{false}; + bool m_isControlFlowProfilingEnabled{false}; + +private: + InspectorSession *m_session; + RuntimeFrontend m_frontend; + debugger::JSCDebuggerImpl *m_debugger; + rapidjson::Document m_doc; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_JSC_RUNTIME_AGENT_IMPL_H diff --git a/plugins/devtools/bridge/inspector/inspector_session.cc b/plugins/devtools/bridge/inspector/inspector_session.cc new file mode 100644 index 0000000000..9dcaa32803 --- /dev/null +++ b/plugins/devtools/bridge/inspector/inspector_session.cc @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/inspector_session.h" +#include "inspector/impl/jsc_console_client_impl.h" +#include "inspector/impl/jsc_debugger_agent_impl.h" +#include "inspector/impl/jsc_heap_profiler_agent_impl.h" +#include "inspector/impl/jsc_log_agent_impl.h" +#include "inspector/impl/jsc_page_agent_impl.h" +#include "inspector/impl/jsc_runtime_agent_impl.h" +#include "inspector/protocol/debugger_dispatcher_contract.h" +#include "inspector/protocol/heap_profiler_dispatcher_contract.h" +#include "inspector/protocol/log_dispatcher_contract.h" +#include "inspector/protocol/page_dispatcher_contract.h" +#include "inspector/protocol/runtime_dispatcher_contract.h" + +#include + +#include +#include + +namespace kraken::debugger { + +InspectorSession::InspectorSession(RPCSession *rpcSession, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, + std::shared_ptr handler) + : m_rpcSession(rpcSession), m_dispatcher(this), m_protocol_handler(handler), + m_executionStopwatch(Stopwatch::create()) { + m_executionStopwatch->start(); + m_debugger = std::make_unique(rpcSession->sessionId(), globalObject); + m_injectedScriptManager = + std::make_unique(*this, Inspector::InjectedScriptHost::create()); + AgentContext context = {this->m_debugger.get(), this, this->m_injectedScriptManager.get(), this}; + + m_debugger_agent.reset(new JSCDebuggerAgentImpl(this, context)); + DebuggerDispatcherContract::wire(&m_dispatcher, m_debugger_agent.get()); + + m_runtime_agent.reset(new JSCRuntimeAgentImpl(this, context)); + RuntimeDispatcherContract::wire(&m_dispatcher, m_runtime_agent.get()); + + m_page_agent.reset(new JSCPageAgentImpl(this, context)); + PageDispatcherContract::wire(&m_dispatcher, m_page_agent.get()); + + m_log_agent.reset(new JSCLogAgentImpl(this, context)); + LogDispatcherContract::wire(&m_dispatcher, m_log_agent.get()); + + m_console_client = new JSCConsoleClientImpl(m_log_agent.get()); + globalObject->setConsoleClient(m_console_client); // bind console client + + JSObjectRef globalObjectRef = JSContextGetGlobalObject(ctx); + JSObjectSetPrivate(globalObjectRef, m_console_client); + + m_heap_profiler_agent.reset(new JSCHeapProfilerAgentImpl(this, context)); + HeapProfilerDispatcherContract::wire(&m_dispatcher, m_heap_profiler_agent.get()); +} + +InspectorSession::~InspectorSession() { +} + +void InspectorSession::onSessionClosed(int, const std::string &) { + if (m_debugger->globalObject()) { + m_debugger->globalObject()->setConsoleClient(nullptr); + } + + m_debugger_agent->disable(true); + m_runtime_agent->disable(); + + m_injectedScriptManager->disconnect(); +} + +void InspectorSession::sendProtocolResponse(uint64_t callId, debugger::Response message) { + if (m_rpcSession && callId == message.id) { + m_rpcSession->sendResponse(std::move(message)); + } +} + +void InspectorSession::sendProtocolNotification(debugger::Event message) { + if (m_rpcSession) { + m_rpcSession->sendEvent(std::move(message)); + } +} + +void InspectorSession::sendProtocolError(debugger::Error message) { + if (m_rpcSession) { + m_rpcSession->sendError(std::move(message)); + } +} + +void InspectorSession::fallThrough(uint64_t callId, const std::string &method, JSONObject message) { + KRAKEN_LOG(ERROR) << "[fallThrough] can not handle request: " << callId << "," << method; +} + +void InspectorSession::dispatchProtocolMessage(Request message) { + JSC::JSGlobalObject *globalObject = m_debugger->globalObject(); + JSC::VM &vm = globalObject->globalExec()->vm(); + JSC::JSLockHolder locker(vm); + m_dispatcher.dispatch(message.id, message.method, std::move(message.params)); +} + +//////////////////////Inspector::InspectorEnvironment/////////////////////////////// + +bool InspectorSession::developerExtrasEnabled() const { + return true; +} + +bool InspectorSession::canAccessInspectedScriptState(JSC::ExecState *) const { + return true; +} + +Inspector::InspectorFunctionCallHandler InspectorSession::functionCallHandler() const { + return JSC::call; +} + +Inspector::InspectorEvaluateHandler InspectorSession::evaluateHandler() const { + return JSC::evaluate; +} + +void InspectorSession::frontendInitialized() {} + +Ref InspectorSession::executionStopwatch() { + return m_executionStopwatch.copyRef(); +} + +Inspector::ScriptDebugServer &InspectorSession::scriptDebugServer() { + return *this->m_debugger; +} + +JSC::VM &InspectorSession::vm() { + return this->m_debugger->vm(); +} + +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/inspector_session.h b/plugins/devtools/bridge/inspector/inspector_session.h new file mode 100644 index 0000000000..c2fb1cc0bf --- /dev/null +++ b/plugins/devtools/bridge/inspector/inspector_session.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_INSPECTOR_SESSION_IMPL_H +#define KRAKEN_DEBUGGER_INSPECTOR_SESSION_IMPL_H + +#include "inspector/rpc_session.h" +#include "inspector/impl/jsc_console_client_impl.h" +#include "inspector/impl/jsc_debugger_impl.h" +#include "inspector/impl/jsc_debugger_agent_impl.h" +#include "inspector/impl/jsc_heap_profiler_agent_impl.h" +#include "inspector/impl/jsc_runtime_agent_impl.h" +#include "inspector/impl/jsc_log_agent_impl.h" +#include "inspector/impl/jsc_page_agent_impl.h" +#include "inspector/impl/jsc_runtime_agent_impl.h" +#include "inspector/protocol/frontend_channel.h" +#include "inspector/protocol/uber_dispatcher.h" +#include "inspector/protocol_handler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kraken::debugger { +//class JSCPageAgentImpl; +//class JSCLogAgentImpl; +//class JSCConsoleClientImpl; +class JSCHeapProfilerAgentImpl; +class RPCSession; + +struct AgentContext { + debugger::JSCDebuggerImpl *debugger; + Inspector::InspectorEnvironment *environment; + Inspector::InjectedScriptManager *injectedScriptManager; + FrontendChannel *channel; +}; + +class InspectorSession : public FrontendChannel, public Inspector::InspectorEnvironment { +public: + InspectorSession() = delete; + explicit InspectorSession(RPCSession *rpcSession, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, + std::shared_ptr handler); + + ~InspectorSession(); + + void onSessionClosed(int, const std::string &); + + JSCDebuggerAgentImpl *debuggerAgent() { + return m_debugger_agent.get(); + } + + ProtocolHandler *protocolHandler() { + return m_protocol_handler.get(); + } + + JSCLogAgentImpl *logAgent() { + return m_log_agent.get(); + } + + bool isDebuggerPaused() { + return m_debugger->isPaused(); + } + + RPCSession *rpcSession() { return m_rpcSession; } + + /***** FrontendChannel *******/ + void sendProtocolResponse(uint64_t callId, Response message) override; + + void sendProtocolNotification(Event message) override; + + void sendProtocolError(Error message) override; + + void fallThrough(uint64_t callId, const std::string &method, JSONObject message) override; + + void dispatchProtocolMessage(Request message); + + /***** InspectorEnvironment *******/ + bool developerExtrasEnabled() const override; + bool canAccessInspectedScriptState(JSC::ExecState *) const override; + Inspector::InspectorFunctionCallHandler functionCallHandler() const override; + Inspector::InspectorEvaluateHandler evaluateHandler() const override; + void frontendInitialized() override; + Ref executionStopwatch() override; + Inspector::ScriptDebugServer &scriptDebugServer() override; + JSC::VM &vm() override; + +private: + RPCSession *m_rpcSession; + UberDispatcher m_dispatcher; + std::unique_ptr m_debugger; + std::unique_ptr m_debugger_agent; + std::unique_ptr m_runtime_agent; + std::unique_ptr m_page_agent; + std::unique_ptr m_log_agent; + JSCConsoleClientImpl *m_console_client{nullptr}; + std::unique_ptr m_heap_profiler_agent; + + std::shared_ptr m_protocol_handler; + std::unique_ptr m_injectedScriptManager; + + Ref m_executionStopwatch; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_INSPECTOR_SESSION_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/break_location.cc b/plugins/devtools/bridge/inspector/protocol/break_location.cc new file mode 100644 index 0000000000..fdee179895 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/break_location.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "break_location.h" + +namespace kraken { +namespace debugger { +std::unique_ptr BreakLocation::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new BreakLocation()); + errors->push(); + + if (value->HasMember("scriptId") && (*value)["scriptId"].IsString()) { + result->m_scriptId = (*value)["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + if (value->HasMember("lineNumber") && (*value)["lineNumber"].IsInt()) { + result->m_lineNumber = (*value)["lineNumber"].GetInt(); + } else { + errors->setName("lineNumber"); + errors->addError("lineNumber not found"); + } + + if (value->HasMember("columnNumber")) { + errors->setName("columnNumber"); + if ((*value)["columnNumber"].IsInt()) { + result->m_columnNumber = (*value)["columnNumber"].GetInt(); + } else { + errors->addError("columnNumber should be int"); + } + } + + if (value->HasMember("type")) { + errors->setName("type"); + if ((*value)["type"].IsString()) { + result->m_type = (*value)["type"].GetString(); + } else { + errors->addError("type should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value BreakLocation::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + result.AddMember("scriptId", m_scriptId, allocator); + result.AddMember("lineNumber", m_lineNumber, allocator); + if (m_columnNumber.isJust()) result.AddMember("columnNumber", m_columnNumber.fromJust(), allocator); + if (m_type.isJust()) result.AddMember("type", m_type.fromJust(), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/break_location.h b/plugins/devtools/bridge/inspector/protocol/break_location.h new file mode 100644 index 0000000000..3b382a7d57 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/break_location.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_BREAK_LOCATION_H +#define KRAKEN_DEBUGGER_BREAK_LOCATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include "rapidjson/document.h" +#include +#include +#include + +namespace kraken::debugger { +class BreakLocation { + KRAKEN_DISALLOW_COPY(BreakLocation); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~BreakLocation() {} + + std::string getScriptId() { + return m_scriptId; + } + + void setScriptId(const std::string &value) { + m_scriptId = value; + } + + int getLineNumber() { + return m_lineNumber; + } + + void setLineNumber(int value) { + m_lineNumber = value; + } + + bool hasColumnNumber() { + return m_columnNumber.isJust(); + } + + int getColumnNumber(int defaultValue) { + return m_columnNumber.isJust() ? m_columnNumber.fromJust() : defaultValue; + } + + void setColumnNumber(int value) { + m_columnNumber = value; + } + + struct TypeEnum { + static const char *DebuggerStatement; + static const char *Call; + static const char *Return; + }; // TypeEnum + + bool hasType() { + return m_type.isJust(); + } + + std::string getType(const std::string &defaultValue) { + return m_type.isJust() ? m_type.fromJust() : defaultValue; + } + + void setType(const std::string &value) { + m_type = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class BreakLocationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + LineNumberSet = 1 << 2, + AllFieldsSet = (ScriptIdSet | LineNumberSet | 0) + }; + + BreakLocationBuilder &setScriptId(const std::string &value) { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + BreakLocationBuilder &setLineNumber(int value) { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + BreakLocationBuilder &setColumnNumber(int value) { + m_result->setColumnNumber(value); + return *this; + } + + BreakLocationBuilder &setType(const std::string &value) { + m_result->setType(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class BreakLocation; + + BreakLocationBuilder() : m_result(new BreakLocation()) {} + + template BreakLocationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static BreakLocationBuilder<0> create() { + return BreakLocationBuilder<0>(); + } + +private: + BreakLocation() { + m_lineNumber = 0; + } + + std::string m_scriptId; + int m_lineNumber; + Maybe m_columnNumber; + Maybe m_type; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_BREAK_LOCATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.cc b/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.cc new file mode 100644 index 0000000000..1541d071fb --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.cc @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "breakpoint_resolved_notification.h" + +namespace kraken::debugger { +std::unique_ptr +BreakpointResolvedNotification::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new BreakpointResolvedNotification()); + errors->push(); + + if (value->HasMember("breakpointId") && (*value)["breakpointId"].IsString()) { + result->m_breakpointId = (*value)["breakpointId"].GetString(); + } else { + errors->setName("breakpointId"); + errors->addError("breakpointId not found"); + } + + if (value->HasMember("location") && (*value)["location"].IsObject()) { + rapidjson::Value _location = rapidjson::Value((*value)["location"].GetObject()); + result->m_location = Location::fromValue(&_location, errors); + } else { + errors->setName("location"); + errors->addError("location not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value BreakpointResolvedNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("breakpointId", m_breakpointId, allocator); + result.AddMember("location", m_location->toValue(allocator), allocator); + return result; +} +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.h b/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.h new file mode 100644 index 0000000000..4a4418cc4f --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/breakpoint_resolved_notification.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_RESOLVED_NOTIFICATION_H +#define KRAKEN_DEBUGGER_RESOLVED_NOTIFICATION_H + +#include "kraken_foundation.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/location.h" +#include +#include +#include + +namespace kraken::debugger { + +class BreakpointResolvedNotification { + KRAKEN_DISALLOW_COPY(BreakpointResolvedNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~BreakpointResolvedNotification() {} + + std::string getBreakpointId() { + return m_breakpointId; + } + + void setBreakpointId(const std::string &value) { + m_breakpointId = value; + } + + Location *getLocation() { + return m_location.get(); + } + + void setLocation(std::unique_ptr value) { + m_location = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class BreakpointResolvedNotificationBuilder { + public: + enum { + NoFieldsSet = 0, + BreakpointIdSet = 1 << 1, + LocationSet = 1 << 2, + AllFieldsSet = (BreakpointIdSet | LocationSet | 0) + }; + + BreakpointResolvedNotificationBuilder &setBreakpointId(const std::string &value) { + static_assert(!(STATE & BreakpointIdSet), "property breakpointId should not be set yet"); + m_result->setBreakpointId(value); + return castState(); + } + + BreakpointResolvedNotificationBuilder &setLocation(std::unique_ptr value) { + static_assert(!(STATE & LocationSet), "property location should not be set yet"); + m_result->setLocation(std::move(value)); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class BreakpointResolvedNotification; + + BreakpointResolvedNotificationBuilder() : m_result(new BreakpointResolvedNotification()) {} + + template BreakpointResolvedNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static BreakpointResolvedNotificationBuilder<0> create() { + return BreakpointResolvedNotificationBuilder<0>(); + } + +private: + BreakpointResolvedNotification() {} + + std::string m_breakpointId; + std::unique_ptr m_location; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_RESOLVED_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/call_argument.cc b/plugins/devtools/bridge/inspector/protocol/call_argument.cc new file mode 100644 index 0000000000..345ef7d64e --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/call_argument.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "call_argument.h" + +namespace kraken { +namespace debugger { +std::unique_ptr CallArgument::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new CallArgument()); + errors->push(); + + if (value->HasMember("value")) { + result->m_value = std::make_unique((*value)["value"], result->m_holder.GetAllocator()); + } + + if (value->HasMember("unserializableValue")) { + errors->setName("unserializableValue"); + if ((*value)["unserializableValue"].IsString()) { + result->m_unserializableValue = (*value)["unserializableValue"].GetString(); + } else { + errors->addError("unserializableValue should be string"); + } + } + + if (value->HasMember("objectId")) { + errors->setName("objectId"); + if ((*value)["objectId"].IsString()) { + result->m_objectId = (*value)["objectId"].GetString(); + } else { + errors->addError("objectId should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value CallArgument::toValue(rapidjson::Document::AllocatorType &allocator) const { + + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + if (m_value.isJust()) result.AddMember("value", *m_value.fromJust(), allocator); + if (m_unserializableValue.isJust()) + result.AddMember("unserializableValue", m_unserializableValue.fromJust(), allocator); + if (m_objectId.isJust()) result.AddMember("objectId", m_objectId.fromJust(), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/call_argument.h b/plugins/devtools/bridge/inspector/protocol/call_argument.h new file mode 100644 index 0000000000..87aeb67db0 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/call_argument.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_CALL_ARGUMENT_H +#define KRAKEN_DEBUGGER_CALL_ARGUMENT_H + +#include "kraken_foundation.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/stacktrace.h" +#include +#include +#include +#include + +namespace kraken::debugger { + +class CallArgument { + KRAKEN_DISALLOW_COPY(CallArgument); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~CallArgument() {} + + bool hasValue() { + return m_value.isJust(); + } + + rapidjson::Value *getValue(rapidjson::Value *defaultValue) { + return m_value.isJust() ? m_value.fromJust() : defaultValue; + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + bool hasUnserializableValue() { + return m_unserializableValue.isJust(); + } + + std::string getUnserializableValue(const std::string &defaultValue) { + return m_unserializableValue.isJust() ? m_unserializableValue.fromJust() : defaultValue; + } + + void setUnserializableValue(const std::string &value) { + m_unserializableValue = value; + } + + bool hasObjectId() { + return m_objectId.isJust(); + } + + std::string getObjectId(const std::string &defaultValue) { + return m_objectId.isJust() ? m_objectId.fromJust() : defaultValue; + } + + void setObjectId(const std::string &value) { + m_objectId = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class CallArgumentBuilder { + public: + enum { NoFieldsSet = 0, AllFieldsSet = (0) }; + + CallArgumentBuilder &setValue(std::unique_ptr value) { + m_result->setValue(std::move(value)); + return *this; + } + + CallArgumentBuilder &setUnserializableValue(const std::string &value) { + m_result->setUnserializableValue(value); + return *this; + } + + CallArgumentBuilder &setObjectId(const std::string &value) { + m_result->setObjectId(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CallArgument; + + CallArgumentBuilder() : m_result(new CallArgument()) {} + + template CallArgumentBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static CallArgumentBuilder<0> create() { + return CallArgumentBuilder<0>(); + } + +private: + CallArgument() {} + + Maybe m_value; + Maybe m_unserializableValue; + Maybe m_objectId; + rapidjson::Document m_holder; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_CALL_ARGUMENT_H diff --git a/plugins/devtools/bridge/inspector/protocol/call_frame.cc b/plugins/devtools/bridge/inspector/protocol/call_frame.cc new file mode 100644 index 0000000000..f83028c219 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/call_frame.cc @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "call_frame.h" + +namespace kraken { +namespace debugger { +std::unique_ptr CallFrame::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new CallFrame()); + errors->push(); + + if (value->HasMember("callFrameId") && (*value)["callFrameId"].IsString()) { + result->m_callFrameId = (*value)["callFrameId"].GetString(); + } else { + errors->setName("callFrameId"); + errors->addError("callFrameId not found"); + } + + if (value->HasMember("functionName") && (*value)["functionName"].IsString()) { + result->m_functionName = (*value)["functionName"].GetString(); + } else { + errors->setName("functionName"); + errors->addError("functionName not found"); + } + + if (value->HasMember("functionLocation") && (*value)["functionLocation"].IsObject()) { + errors->setName("functionLocation"); + rapidjson::Value fl = rapidjson::Value((*value)["functionLocation"].GetObject()); + result->m_functionLocation = Location::fromValue(&fl, errors); + } + + if (value->HasMember("location") && (*value)["location"].IsObject()) { + rapidjson::Value lo = rapidjson::Value((*value)["location"].GetObject()); + result->m_location = Location::fromValue(&lo, errors); + } else { + errors->setName("location"); + errors->addError("location not found"); + } + + if (value->HasMember("url") && (*value)["url"].IsString()) { + result->m_url = (*value)["url"].GetString(); + } else { + errors->setName("url"); + errors->addError("url not found"); + } + + if (value->HasMember("scopeChain") && (*value)["scopeChain"].IsArray()) { + auto arr = std::make_unique>>(); + for (auto &v : (*value)["scopeChain"].GetArray()) { + if (v.IsObject()) { + rapidjson::Value _scope = rapidjson::Value(v.GetObject()); + arr->push_back(Scope::fromValue(&_scope, errors)); + } + } + result->m_scopeChain = std::move(arr); + } else { + errors->setName("scopeChain"); + errors->addError("scopeChain not found"); + } + + if (value->HasMember("this") && (*value)["this"].IsObject()) { + rapidjson::Value _this = rapidjson::Value((*value)["this"].GetObject()); + result->m_this = RemoteObject::fromValue(&_this, errors); + } else { + errors->setName("this"); + errors->addError("this not found"); + } + + if (value->HasMember("returnValue")) { + errors->setName("returnValue"); + if ((*value)["returnValue"].IsObject()) { + rapidjson::Value _retV = rapidjson::Value((*value)["returnValue"].GetObject()); + result->m_returnValue = RemoteObject::fromValue(&_retV, errors); + } else { + errors->addError("returnValue should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value CallFrame::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + result.AddMember("callFrameId", m_callFrameId, allocator); + result.AddMember("functionName", m_functionName, allocator); + + if (m_functionLocation.isJust()) { + result.AddMember("functionLocation", m_functionLocation.fromJust()->toValue(allocator), allocator); + } + result.AddMember("location", m_location->toValue(allocator), allocator); + result.AddMember("url", m_url, allocator); + + rapidjson::Value arr = rapidjson::Value(rapidjson::kArrayType); + arr.SetArray(); + + for (const auto &val : *m_scopeChain.get()) { + arr.PushBack(val->toValue(allocator), allocator); + } + result.AddMember("scopeChain", arr, allocator); + result.AddMember("this", m_this->toValue(allocator), allocator); + + if (m_returnValue.isJust()) { + result.AddMember("returnValue", m_returnValue.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/call_frame.h b/plugins/devtools/bridge/inspector/protocol/call_frame.h new file mode 100644 index 0000000000..b34482b50e --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/call_frame.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_CALL_FRAME_H +#define KRAKEN_DEBUGGER_CALL_FRAME_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/remote_object.h" +#include "inspector/protocol/scope.h" +#include "kraken_foundation.h" +#include +#include +#include + +namespace kraken { +namespace debugger { +class CallFrame { + KRAKEN_DISALLOW_COPY(CallFrame); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~CallFrame() {} + + std::string getCallFrameId() { + return m_callFrameId; + } + + void setCallFrameId(const std::string &value) { + m_callFrameId = value; + } + + std::string getFunctionName() { + return m_functionName; + } + + void setFunctionName(const std::string &value) { + m_functionName = value; + } + + bool hasFunctionLocation() { + return m_functionLocation.isJust(); + } + + Location *getFunctionLocation(Location *defaultValue) { + return m_functionLocation.isJust() ? m_functionLocation.fromJust() : defaultValue; + } + + void setFunctionLocation(std::unique_ptr value) { + m_functionLocation = std::move(value); + } + + Location *getLocation() { + return m_location.get(); + } + + void setLocation(std::unique_ptr value) { + m_location = std::move(value); + } + + std::string getUrl() { + return m_url; + } + + void setUrl(const std::string &value) { + m_url = value; + } + + std::vector> *getScopeChain() { + return m_scopeChain.get(); + } + + void setScopeChain(std::unique_ptr>> value) { + m_scopeChain = std::move(value); + } + + RemoteObject *getThis() { + return m_this.get(); + } + + void setThis(std::unique_ptr value) { + m_this = std::move(value); + } + + bool hasReturnValue() { + return m_returnValue.isJust(); + } + + RemoteObject *getReturnValue(RemoteObject *defaultValue) { + return m_returnValue.isJust() ? m_returnValue.fromJust() : defaultValue; + } + + void setReturnValue(std::unique_ptr value) { + m_returnValue = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class CallFrameBuilder { + public: + enum { + NoFieldsSet = 0, + CallFrameIdSet = 1 << 1, + FunctionNameSet = 1 << 2, + LocationSet = 1 << 3, + UrlSet = 1 << 4, + ScopeChainSet = 1 << 5, + ThisSet = 1 << 6, + AllFieldsSet = (CallFrameIdSet | FunctionNameSet | LocationSet | UrlSet | ScopeChainSet | ThisSet | 0) + }; + + CallFrameBuilder &setCallFrameId(const std::string &value) { + static_assert(!(STATE & CallFrameIdSet), "property callFrameId should not be set yet"); + m_result->setCallFrameId(value); + return castState(); + } + + CallFrameBuilder &setFunctionName(const std::string &value) { + static_assert(!(STATE & FunctionNameSet), "property functionName should not be set yet"); + m_result->setFunctionName(value); + return castState(); + } + + CallFrameBuilder &setFunctionLocation(std::unique_ptr value) { + m_result->setFunctionLocation(std::move(value)); + return *this; + } + + CallFrameBuilder &setLocation(std::unique_ptr value) { + static_assert(!(STATE & LocationSet), "property location should not be set yet"); + m_result->setLocation(std::move(value)); + return castState(); + } + + CallFrameBuilder &setUrl(const std::string &value) { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + CallFrameBuilder &setScopeChain(std::unique_ptr>> value) { + static_assert(!(STATE & ScopeChainSet), "property scopeChain should not be set yet"); + m_result->setScopeChain(std::move(value)); + return castState(); + } + + CallFrameBuilder &setThis(std::unique_ptr value) { + static_assert(!(STATE & ThisSet), "property this should not be set yet"); + m_result->setThis(std::move(value)); + return castState(); + } + + CallFrameBuilder &setReturnValue(std::unique_ptr value) { + m_result->setReturnValue(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CallFrame; + + CallFrameBuilder() : m_result(new CallFrame()) {} + + template CallFrameBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static CallFrameBuilder<0> create() { + return CallFrameBuilder<0>(); + } + +private: + CallFrame() {} + + std::string m_callFrameId; + std::string m_functionName; + Maybe m_functionLocation; + std::unique_ptr m_location; + std::string m_url; + std::unique_ptr>> m_scopeChain; + std::unique_ptr m_this; + Maybe m_returnValue; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_CALL_FRAME_H diff --git a/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.cc b/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.cc new file mode 100644 index 0000000000..472e3db6a9 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.cc @@ -0,0 +1,1319 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/break_location.h" +#include "inspector/protocol/debug_dispatcher_impl.h" +#include "inspector/protocol/exception_details.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/remote_object.h" +#include "inspector/protocol/search_match.h" +#include "inspector/protocol/stacktrace.h" +#include "inspector/protocol/stacktrace_id.h" + +// #include "foundation/make_copyable.h" + +namespace kraken { +namespace debugger { + +bool DebugDispatcherImpl::canDispatch(const std::string &method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} + +void DebugDispatcherImpl::dispatch(uint64_t callId, const std::string &method, debugger::JSONObject message) { + std::unordered_map::iterator it = m_dispatchMap.find(method); + if (it == m_dispatchMap.end()) { + return; + } + ErrorSupport errors; + (it->second)(callId, method, std::move(message), &errors); +} + +/** + * + * Continues execution until specific location is reached. + * + * @params location: Location to continue to. + * @params targetCallFrames + * + * */ +void DebugDispatcherImpl::continueToLocation(uint64_t callId, const std::string &method, + debugger::JSONObject message, debugger::ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + errors->setName("location"); + + std::unique_ptr in_location = nullptr; + if (message.HasMember("location") && message["location"].IsObject()) { + rapidjson::Value _loc = message["location"].GetObject(); + in_location = Location::fromValue(&_loc, errors); + } else { + errors->addError("location not found"); + } + + Maybe in_targetCallFrames; + if (message.HasMember("targetCallFrames")) { + errors->setName("targetCallFrames"); + if (message["targetCallFrames"].IsString()) { + in_targetCallFrames = message["targetCallFrames"].GetString(); + } else { + errors->addError("targetCallFrames should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->continueToLocation(std::move(in_location), std::move(in_targetCallFrames)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +/** + * + * Disables debugger for given page. + * + * */ +void DebugDispatcherImpl::disable(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +/* + * + * Enables debugger for the given page. Clients should not assume that the debugging has been + * enabled until the result for this command is received. + * + * + * @params + * - maxScriptsCacheSize: The maximum size in bytes of collected scripts (not referenced by other heap objects) + * the debugger can hold. Puts no limit if parameter is omitted + * + * **/ +void DebugDispatcherImpl::enable(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + double maxScriptsCacheSize = -1; + if (message.HasMember("maxScriptsCacheSize")) { + maxScriptsCacheSize = message["maxScriptsCacheSize"].GetDouble(); + } + + std::string out_debuggerId; + auto weak = this->weakPtr(); + + DispatchResponse response = m_backend->enable(maxScriptsCacheSize, &out_debuggerId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + debugger::JSONObject result; + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + debugger::JSONObject debugId; + debugId.SetString(out_debuggerId.c_str(), m_json_doc.GetAllocator()); + result.AddMember("debuggerId", debugId, m_json_doc.GetAllocator()); + } + if (weak->get()) { + weak->get()->sendResponse(callId, response, std::move(result)); + } +} + +/** + * Evaluates expression on a given call frame + * + * @param callFrameId: Call frame identifier to evaluate on. + * @param expression: Expression to evaluate. + * @param objectGroup: String object group name to put result into (allows rapid releasing resulting object handles + * using `releaseObjectGroup`). + * @param includeCommandLineAPI: Specifies whether command line API should be available to the evaluated expression, + * defaults to false. + * @param silent: In silent mode exceptions thrown during evaluation are not reported and do not pause execution. + * Overrides `setPauseOnException` state. + * @param returnByValue: Whether the result is expected to be a JSON object that should be sent by value. + * @param generatePreview: Whether preview should be generated for the result. + * @param throwOnSideEffect: Whether to throw an exception if side effect cannot be ruled out during evaluation. + * @param timeout: Terminate execution after timing out (number of milliseconds). + * + * @return + * - result: Object wrapper for the evaluation result. + * - exceptionDetails: Exception details. + * + * */ +void DebugDispatcherImpl::evaluateOnCallFrame(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_callFrameId; + if (message.HasMember("callFrameId") && message["callFrameId"].IsString()) { + in_callFrameId = message["callFrameId"].GetString(); + } else { + errors->setName("callFrameId"); + errors->addError("callFrameId not found"); + } + + std::string in_expression; + if (message.HasMember("expression") && message["expression"].IsString()) { + in_expression = message["expression"].GetString(); + } else { + errors->setName("expression"); + errors->addError("expression not found"); + } + + Maybe in_objectGroup; + if (message.HasMember("objectGroup")) { + if (message["objectGroup"].IsString()) { + in_objectGroup = message["objectGroup"].GetString(); + } else { + errors->setName("objectGroup"); + errors->addError("objectGroup should be string"); + } + } + + Maybe in_includeCommandLineAPI; + if (message.HasMember("includeCommandLineAPI")) { + if (message["includeCommandLineAPI"].IsBool()) { + in_includeCommandLineAPI = message["includeCommandLineAPI"].GetBool(); + } else { + errors->setName("includeCommandLineAPI"); + errors->addError("includeCommandLineAPI should be bool"); + } + } + + Maybe in_silent; + if (message.HasMember("silent")) { + if (message["silent"].IsBool()) { + in_silent = message["silent"].GetBool(); + } else { + errors->setName("silent"); + errors->addError("silent should be bool"); + } + } + + Maybe in_returnByValue; + if (message.HasMember("returnByValue")) { + if (message["returnByValue"].IsBool()) { + in_returnByValue = message["returnByValue"].GetBool(); + } else { + errors->setName("returnByValue"); + errors->addError("returnByValue should be bool"); + } + } + + Maybe in_generatePreview; + if (message.HasMember("generatePreview")) { + if (message["generatePreview"].IsBool()) { + in_generatePreview = message["generatePreview"].GetBool(); + } else { + errors->setName("generatePreview"); + errors->addError("generatePreview should be bool"); + } + } + + Maybe in_throwOnSideEffect; + if (message.HasMember("throwOnSideEffect")) { + if (message["throwOnSideEffect"].IsBool()) { + in_throwOnSideEffect = message["throwOnSideEffect"].GetBool(); + } else { + errors->setName("throwOnSideEffect"); + errors->addError("throwOnSideEffect should be bool"); + } + } + + Maybe in_timeout; + if (message.HasMember("timeout")) { + if (message["timeout"].IsDouble()) { + in_timeout = message["timeout"].GetDouble(); + } else { + errors->setName("timeout"); + errors->addError("timeout should be double"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::unique_ptr out_result; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->evaluateOnCallFrame( + in_callFrameId, in_expression, std::move(in_objectGroup), std::move(in_includeCommandLineAPI), std::move(in_silent), + std::move(in_returnByValue), std::move(in_generatePreview), std::move(in_throwOnSideEffect), std::move(in_timeout), + &out_result, &out_exceptionDetails); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("result", out_result->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + if (out_exceptionDetails.isJust()) + result.AddMember("exceptionDetails", out_exceptionDetails.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * + * Returns possible locations for breakpoint. scriptId in start and end range locations should be the same. + * + * @param start: Start of range to search possible breakpoint locations in. + * @param end: End of range to search possible breakpoint locations in (excluding). + * When not specified, end of scripts is used as end of range. + * + * @param restrictToFunction: Only consider locations which are in the same (non-nested) function as start. + * + * @return locations: List of the possible breakpoint locations. + * + * */ +void DebugDispatcherImpl::getPossibleBreakpoints(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::unique_ptr in_start = nullptr; + if (message.HasMember("start") && message["start"].IsObject()) { + rapidjson::Value _start = message["start"].GetObject(); + in_start = Location::fromValue(&_start, errors); + } else { + errors->setName("start"); + errors->addError("start not found"); + } + + Maybe in_end; + if (message.HasMember("end")) { + errors->setName("end"); + if (message["end"].IsObject()) { + rapidjson::Value _end = message["end"].GetObject(); + in_end = Location::fromValue(&_end, errors); + } else { + errors->addError("end should be object"); + } + } + + Maybe in_restrictToFunction; + if (message.HasMember("restrictToFunction")) { + errors->setName("restrictToFunction"); + if (message["restrictToFunction"].IsBool()) { + in_restrictToFunction = message["restrictToFunction"].GetBool(); + } else { + errors->addError("restrictToFunction should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::unique_ptr>> out_locations; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getPossibleBreakpoints(std::move(in_start), std::move(in_end), + std::move(in_restrictToFunction), &out_locations); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + rapidjson::Value location_array = rapidjson::Value(rapidjson::kArrayType); + if (out_locations != nullptr) { + for (auto &val : *out_locations) { + location_array.PushBack(val->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("locations", location_array, m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * + * Returns source for the script with given id. + * + * @params scriptId: Id of the script to get source for. + * @return scriptSource: Script source. + * + * */ +void DebugDispatcherImpl::getScriptSource(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_scriptId; + if (message.HasMember("scriptId") && message["scriptId"].IsString()) { + in_scriptId = message["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::string out_scriptSource; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getScriptSource(in_scriptId, &out_scriptSource); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + rapidjson::Value _source(out_scriptSource.c_str(), out_scriptSource.length(), m_json_doc.GetAllocator()); + result.AddMember("scriptSource", _source, m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * + * Returns stack trace with given `stackTraceId`. [experimental] + * + * @params stackTraceId + * @return stackTrace + * + * */ +void DebugDispatcherImpl::getStackTrace(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + errors->setName("stackTraceId"); + std::unique_ptr in_stackTraceId = nullptr; + if (message.HasMember("stackTraceId") && message["stackTraceId"].IsObject()) { + rapidjson::Value _stack_trace_id = message["stackTraceId"].GetObject(); + in_stackTraceId = StackTraceId::fromValue(&_stack_trace_id, errors); + } else { + errors->setName("stackTraceId"); + errors->addError("stackTraceId not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::unique_ptr out_stackTrace; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getStackTrace(std::move(in_stackTraceId), &out_stackTrace); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("stackTrace", out_stackTrace->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * + * Stops on the next JavaScript statement. + * + * */ +void DebugDispatcherImpl::pause(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->pause(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::pauseOnAsyncCall(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::unique_ptr in_parentStackTraceId = nullptr; + if (message.HasMember("parentStackTraceId") && message["parentStackTraceId"].IsObject()) { + rapidjson::Value _parentStackTraceId = message["parentStackTraceId"].GetObject(); + in_parentStackTraceId = StackTraceId::fromValue(&_parentStackTraceId, errors); + } else { + errors->setName("parentStackTraceId"); + errors->addError("parentStackTraceId not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->pauseOnAsyncCall(std::move(in_parentStackTraceId)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +/** + * + * Removes JavaScript breakpoint. + * + * @param breakpointId + * */ +void DebugDispatcherImpl::removeBreakpoint(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_breakpointId = ""; + if (message.HasMember("breakpointId") && message["breakpointId"].IsString()) { + in_breakpointId = message["breakpointId"].GetString(); + } else { + errors->setName("breakpointId"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->removeBreakpoint(in_breakpointId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +/** + * Restarts particular call frame from the beginning. + * + * @param callFrameId Call frame identifier to evaluate on. + * + * @return + * - callFrames: New stack trace. + * - asyncStackTrace: Async stack trace, if any. + * - asyncStackTraceId: Async stack trace, if any. + * */ +void DebugDispatcherImpl::restartFrame(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_callFrameId; + if (message.HasMember("callFrameId") && message["callFrameId"].IsString()) { + in_callFrameId = message["callFrameId"].GetString(); + } else { + errors->setName("callFrameId"); + errors->addError("callFrameId not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + // Declare output parameters. + std::unique_ptr> out_callFrames; + Maybe out_asyncStackTrace; + Maybe out_asyncStackTraceId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->restartFrame(in_callFrameId, &out_callFrames, &out_asyncStackTrace, &out_asyncStackTraceId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + if (out_callFrames != nullptr) { + rapidjson::Value _callFrames_arr(rapidjson::kArrayType); + _callFrames_arr.SetArray(); + for (const auto &frame : *out_callFrames) { + _callFrames_arr.PushBack(frame.toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("callFrames", _callFrames_arr, m_json_doc.GetAllocator()); + } + if (out_asyncStackTrace.isJust()) { + result.AddMember("asyncStackTrace", out_asyncStackTrace.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + if (out_asyncStackTraceId.isJust()) { + result.AddMember("asyncStackTraceId", out_asyncStackTraceId.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void DebugDispatcherImpl::resume(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->resume(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +/** + * + * Searches for given string in script content. + * + * @param scriptId: Id of the script to search in. + * @param query: String to search for. + * @param caseSensitive: If true, search is case sensitive. + * @param isRegex: If true, treats string parameter as regex. + * + * @return + * - result: List of search matches. + * + * */ +void DebugDispatcherImpl::searchInContent(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_scriptId; + if (message.HasMember("scriptId") && message["scriptId"].IsString()) { + in_scriptId = message["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + std::string in_query; + if (message.HasMember("query") && message["query"].IsString()) { + in_query = message["query"].GetString(); + } else { + errors->setName("query"); + errors->addError("query not found"); + } + + Maybe in_caseSensitive; + if (message.HasMember("caseSensitive")) { + errors->setName("caseSensitive"); + if (message["caseSensitive"].IsBool()) { + in_caseSensitive = message["caseSensitive"].GetBool(); + } else { + errors->addError("caseSensitive should be bool"); + } + } + + Maybe in_isRegex; + if (message.HasMember("isRegex")) { + errors->setName("isRegex"); + if (message["isRegex"].IsBool()) { + in_isRegex = message["isRegex"].GetBool(); + } else { + errors->addError("isRegex should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::unique_ptr> out_result; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->searchInContent(in_scriptId, in_query, std::move(in_caseSensitive), std::move(in_isRegex), &out_result); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess && out_result != nullptr) { + rapidjson::Value _arr(rapidjson::kArrayType); + _arr.SetArray(); + for (const auto &match : *out_result) { + _arr.PushBack(match.toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("result", _arr, m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void DebugDispatcherImpl::setAsyncCallStackDepth(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + int in_maxDepth = 0; + if (message.HasMember("maxDepth") && message["maxDepth"].IsInt()) { + in_maxDepth = message["maxDepth"].GetInt(); + } else { + errors->setName("maxDepth"); + errors->addError("maxDepth not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setAsyncCallStackDepth(in_maxDepth); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setBlackboxPatterns(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::unique_ptr> in_patterns = std::make_unique>(); + if (message.HasMember("patterns") && message["patterns"].IsArray()) { + auto _patterns = message["patterns"].GetArray(); + for (auto &v : _patterns) { + if (v.IsString()) { + in_patterns->push_back(v.GetString()); + } + } + } else { + errors->setName("patterns"); + errors->addError("patterns not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBlackboxPatterns(std::move(in_patterns)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setBlackboxedRanges(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_scriptId; + if (message.HasMember("scriptId") && message["scriptId"].IsString()) { + in_scriptId = message["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + auto in_positions = std::make_unique>>(); + if (message.HasMember("positions") && message["positions"].IsArray()) { + auto _positions = message["positions"].GetArray(); + for (auto &pos : _positions) { + if (pos.IsObject()) { + in_positions->push_back(ScriptPosition::fromValue(&pos, errors)); + } + } + } else { + errors->setName("positions"); + errors->addError("positions not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBlackboxedRanges(in_scriptId, std::move(in_positions)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setBreakpoint(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::unique_ptr in_location = nullptr; + if (message.HasMember("location") && message["location"].IsObject()) { + rapidjson::Value _location = message["location"].GetObject(); + in_location = Location::fromValue(&_location, errors); + } else { + errors->setName("location"); + errors->addError("location not found"); + } + + Maybe in_condition; + if (message.HasMember("condition")) { + if (message["condition"].IsString()) { + in_condition = message["condition"].GetString(); + } else { + errors->setName("condition"); + errors->addError("condition should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::string out_breakpointId; + std::unique_ptr out_actualLocation; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->setBreakpoint(std::move(in_location), std::move(in_condition), &out_breakpointId, &out_actualLocation); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("breakpointId", out_breakpointId, m_json_doc.GetAllocator()); + result.AddMember("actualLocation", out_actualLocation.get()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * Sets JavaScript breakpoint at given location specified either by URL or URL regex. + * Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in + * `locations` property. Further matching script parsing will result in subsequent `breakpointResolved` events issued. + * This logical breakpoint will survive page reloads. + * + * + * @param lineNumber: Line number to set breakpoint at. + * @param url: URL of the resources to set breakpoint on. + * @param urlRegex: Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or `urlRegex` must + * be specified. + * @param scriptHash: Script hash of the resources to set breakpoint on. + * @param columnNumber: Offset in the line to set breakpoint at. + * @param condition: Expression to use as a breakpoint condition. When specified, debugger will only stop on the + * breakpoint if this expression evaluates to true. + * + * */ +void DebugDispatcherImpl::setBreakpointByUrl(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + int in_lineNumber = 0; + if (message.HasMember("lineNumber") && message["lineNumber"].IsInt()) { + in_lineNumber = message["lineNumber"].GetInt(); + } else { + errors->setName("lineNumber"); + errors->addError("lineNumber not found"); + } + + Maybe in_url; + if (message.HasMember("url")) { + errors->setName("url"); + if (message["url"].IsString()) { + in_url = message["url"].GetString(); + } else { + errors->addError("url should be string"); + } + } + + Maybe in_urlRegex; + if (message.HasMember("urlRegex")) { + errors->setName("urlRegex"); + if (message["urlRegex"].IsString()) { + in_urlRegex = message["urlRegex"].GetString(); + } else { + errors->addError("urlRegex should be string"); + } + } + + Maybe in_scriptHash; + if (message.HasMember("scriptHash")) { + errors->setName("scriptHash"); + if (message["scriptHash"].IsString()) { + in_scriptHash = message["scriptHash"].GetString(); + } else { + errors->addError("scriptHash should be string"); + } + } + + Maybe in_columnNumber; + if (message.HasMember("columnNumber")) { + errors->setName("columnNumber"); + if (message["columnNumber"].IsInt()) { + in_columnNumber = message["columnNumber"].GetInt(); + } else { + errors->addError("columnNumber should be string"); + } + } + + Maybe in_condition; + if (message.HasMember("condition")) { + errors->setName("condition"); + if (message["condition"].IsString()) { + in_condition = message["condition"].GetString(); + } else { + errors->addError("condition should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + // Declare output parameters. + std::string out_breakpointId; + std::unique_ptr>> out_locations; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpointByUrl(in_lineNumber, std::move(in_url), std::move(in_urlRegex), + std::move(in_scriptHash), std::move(in_columnNumber), + std::move(in_condition), &out_breakpointId, &out_locations); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("breakpointId", out_breakpointId, m_json_doc.GetAllocator()); + + if (out_locations != nullptr) { + rapidjson::Value _location_array(rapidjson::kArrayType); + _location_array.SetArray(); + for (const auto &loc : *out_locations) { + _location_array.PushBack(loc->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("locations", _location_array, m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void DebugDispatcherImpl::setBreakpointOnFunctionCall(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::string in_objectId = ""; + if (message.HasMember("objectId") && message["objectId"].IsString()) { + in_objectId = message["objectId"].GetString(); + } else { + errors->setName("objectId"); + errors->addError("objectId not found"); + } + + Maybe in_condition; + if (message.HasMember("condition")) { + errors->setName("condition"); + if (message["condition"].IsString()) { + in_condition = message["condition"].GetString(); + } else { + errors->addError("condition should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + // Declare output parameters. + std::string out_breakpointId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->setBreakpointOnFunctionCall(in_objectId, std::move(in_condition), &out_breakpointId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("breakpointId", out_breakpointId, m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void DebugDispatcherImpl::setBreakpointsActive(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + bool in_active = false; + if (message.HasMember("active") && message["active"].IsBool()) { + in_active = message["active"].GetBool(); + } else { + errors->setName("active"); + errors->addError("active not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpointsActive(in_active); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setPauseOnExceptions(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::string in_state; + if (message.HasMember("state") && message["state"].IsString()) { + in_state = message["state"].GetString(); + } else { + errors->setName("state"); + errors->addError("state not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setPauseOnExceptions(in_state); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setReturnValue(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::unique_ptr in_newValue; + if (message.HasMember("newValue") && message["newValue"].IsObject()) { + rapidjson::Value _call_argument = message["newValue"].GetObject(); + in_newValue = CallArgument::fromValue(&_call_argument, errors); + } else { + errors->setName("newValue"); + errors->addError("newValue not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setReturnValue(std::move(in_newValue)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setScriptSource(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_scriptId; + if (message.HasMember("scriptId") && message["scriptId"].IsString()) { + in_scriptId = message["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + std::string in_scriptSource; + if (message.HasMember("scriptSource") && message["scriptSource"].IsString()) { + in_scriptSource = message["scriptSource"].GetString(); + } else { + errors->setName("scriptSource"); + errors->addError("scriptSource not found"); + } + + Maybe in_dryRun; + if (message.HasMember("dryRun")) { + errors->setName("dryRun"); + if (message["dryRun"].IsBool()) { + in_dryRun = message["dryRun"].GetBool(); + } else { + errors->addError("dryRun should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + // Declare output parameters. + Maybe> out_callFrames; + Maybe out_stackChanged; + Maybe out_asyncStackTrace; + Maybe out_asyncStackTraceId; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->setScriptSource(in_scriptId, in_scriptSource, std::move(in_dryRun), &out_callFrames, &out_stackChanged, + &out_asyncStackTrace, &out_asyncStackTraceId, &out_exceptionDetails); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + if (response.status() == DispatchResponse::kSuccess) { + if (out_callFrames.isJust()) { + rapidjson::Value _arr(rapidjson::kArrayType); + _arr.SetArray(); + + for (const auto &frame : *(out_callFrames.fromJust())) { + _arr.PushBack(frame.toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("callFrames", _arr, m_json_doc.GetAllocator()); + } + if (out_stackChanged.isJust()) { + result.AddMember("stackChanged", out_stackChanged.fromJust(), m_json_doc.GetAllocator()); + } + if (out_asyncStackTrace.isJust()) { + result.AddMember("asyncStackTrace", out_asyncStackTrace.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + if (out_asyncStackTraceId.isJust()) { + result.AddMember("asyncStackTraceId", out_asyncStackTraceId.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + if (out_exceptionDetails.isJust()) { + result.AddMember("exceptionDetails", out_exceptionDetails.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void DebugDispatcherImpl::setSkipAllPauses(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + bool in_skip = false; + if (message.HasMember("skip") && message["skip"].IsBool()) { + in_skip = message["skip"].GetBool(); + } else { + errors->setName("skip"); + errors->addError("skip not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setSkipAllPauses(in_skip); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::setVariableValue(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + int in_scopeNumber = 0; + if (message.HasMember("scopeNumber") && message["scopeNumber"].IsInt()) { + in_scopeNumber = message["scopeNumber"].GetInt(); + } else { + errors->setName("scopeNumber"); + errors->addError("scopeNumber not found"); + } + + std::string in_variableName; + if (message.HasMember("variableName") && message["variableName"].IsString()) { + in_variableName = message["variableName"].GetString(); + } else { + errors->setName("variableName"); + errors->addError("variableName not found"); + } + + errors->setName("newValue"); + std::unique_ptr in_newValue; + if (message.HasMember("newValue") && message["newValue"].IsObject()) { + rapidjson::Value _newValue = message["newValue"].GetObject(); + in_newValue = CallArgument::fromValue(&_newValue, errors); + } else { + errors->setName("newValue"); + errors->addError("newValue not found"); + } + + std::string in_callFrameId; + if (message.HasMember("callFrameId") && message["callFrameId"].IsString()) { + in_callFrameId = message["callFrameId"].GetString(); + } else { + errors->setName("callFrameId"); + errors->addError("callFrameId not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->setVariableValue(in_scopeNumber, in_variableName, std::move(in_newValue), in_callFrameId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::stepInto(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + Maybe in_breakOnAsyncCall; + if (message.HasMember("breakOnAsyncCall")) { + if (message["breakOnAsyncCall"].IsBool()) { + in_breakOnAsyncCall = message["breakOnAsyncCall"].GetBool(); + } else { + errors->setName("breakOnAsyncCall"); + errors->addError("breakOnAsyncCall should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepInto(std::move(in_breakOnAsyncCall)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::stepOut(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepOut(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void DebugDispatcherImpl::stepOver(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepOver(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.h b/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.h new file mode 100644 index 0000000000..4d47c84745 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debug_dispatcher_impl.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DEBUG_DISPATCHER_IMPL_H +#define KRAKEN_DEBUGGER_DEBUG_DISPATCHER_IMPL_H + +#include "inspector/protocol/debugger_backend.h" +#include "inspector/protocol/dispatcher_base.h" + +#include +#include + +namespace kraken::debugger { + +class DebugDispatcherImpl : public DispatcherBase { + +public: + DebugDispatcherImpl(FrontendChannel *frontendChannel, DebuggerBackend *backend) + : DispatcherBase(frontendChannel), m_backend(backend) { + m_dispatchMap["Debugger.continueToLocation"] = + std::bind(&DebugDispatcherImpl::continueToLocation, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.disable"] = std::bind(&DebugDispatcherImpl::disable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.enable"] = std::bind(&DebugDispatcherImpl::enable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.evaluateOnCallFrame"] = + std::bind(&DebugDispatcherImpl::evaluateOnCallFrame, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.getPossibleBreakpoints"] = + std::bind(&DebugDispatcherImpl::getPossibleBreakpoints, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.getScriptSource"] = + std::bind(&DebugDispatcherImpl::getScriptSource, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.getStackTrace"] = + std::bind(&DebugDispatcherImpl::getStackTrace, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.pause"] = std::bind(&DebugDispatcherImpl::pause, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.pauseOnAsyncCall"] = + std::bind(&DebugDispatcherImpl::pauseOnAsyncCall, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.removeBreakpoint"] = + std::bind(&DebugDispatcherImpl::removeBreakpoint, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.restartFrame"] = + std::bind(&DebugDispatcherImpl::restartFrame, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.resume"] = std::bind(&DebugDispatcherImpl::resume, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.searchInContent"] = + std::bind(&DebugDispatcherImpl::searchInContent, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setAsyncCallStackDepth"] = + std::bind(&DebugDispatcherImpl::setAsyncCallStackDepth, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBlackboxPatterns"] = + std::bind(&DebugDispatcherImpl::setBlackboxPatterns, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBlackboxedRanges"] = + std::bind(&DebugDispatcherImpl::setBlackboxedRanges, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBreakpoint"] = + std::bind(&DebugDispatcherImpl::setBreakpoint, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBreakpointByUrl"] = + std::bind(&DebugDispatcherImpl::setBreakpointByUrl, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBreakpointOnFunctionCall"] = + std::bind(&DebugDispatcherImpl::setBreakpointOnFunctionCall, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setBreakpointsActive"] = + std::bind(&DebugDispatcherImpl::setBreakpointsActive, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setPauseOnExceptions"] = + std::bind(&DebugDispatcherImpl::setPauseOnExceptions, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setReturnValue"] = + std::bind(&DebugDispatcherImpl::setReturnValue, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setScriptSource"] = + std::bind(&DebugDispatcherImpl::setScriptSource, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setSkipAllPauses"] = + std::bind(&DebugDispatcherImpl::setSkipAllPauses, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.setVariableValue"] = + std::bind(&DebugDispatcherImpl::setVariableValue, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.stepInto"] = std::bind(&DebugDispatcherImpl::stepInto, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.stepOut"] = std::bind(&DebugDispatcherImpl::stepOut, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Debugger.stepOver"] = std::bind(&DebugDispatcherImpl::stepOver, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + } + ~DebugDispatcherImpl() override {} + + bool canDispatch(const std::string &method) override; + void dispatch(uint64_t callId, const std::string &method, JSONObject message) override; + std::unordered_map &redirects() { + return m_redirects; + } + +protected: + DebuggerBackend *m_backend; /*debugger实现*/ + std::unordered_map m_redirects; + + rapidjson::Document m_json_doc; + using CallHandler = std::function; + using DispatchMap = std::unordered_map; + DispatchMap m_dispatchMap; + + /*debugger commands (28)*/ + + void continueToLocation(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void disable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void enable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void evaluateOnCallFrame(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getPossibleBreakpoints(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getScriptSource(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getStackTrace(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void pause(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void pauseOnAsyncCall(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void removeBreakpoint(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void restartFrame(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void resume(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void searchInContent(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setAsyncCallStackDepth(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setBlackboxPatterns(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setBlackboxedRanges(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setBreakpoint(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setBreakpointByUrl(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setBreakpointOnFunctionCall(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *); + void setBreakpointsActive(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setPauseOnExceptions(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setReturnValue(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setScriptSource(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setSkipAllPauses(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setVariableValue(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void stepInto(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void stepOut(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void stepOver(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DEBUG_DISPATCHER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/debugger_backend.h b/plugins/devtools/bridge/inspector/protocol/debugger_backend.h new file mode 100644 index 0000000000..181224c6c7 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debugger_backend.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DEBUGGERBACKEND_H +#define KRAKEN_DEBUGGER_DEBUGGERBACKEND_H + +#include +#include +#include + +#include "inspector/protocol/break_location.h" +#include "inspector/protocol/call_argument.h" +#include "inspector/protocol/call_frame.h" +#include "inspector/protocol/dispatch_response.h" +#include "inspector/protocol/exception_details.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/remote_object.h" +#include "inspector/protocol/script_position.h" +#include "inspector/protocol/search_match.h" +#include "inspector/protocol/stacktrace.h" +#include "inspector/protocol/stacktrace_id.h" + +namespace JSC { +class VM; +} + +namespace kraken { +namespace debugger { + +class DebuggerBackend { +public: + virtual ~DebuggerBackend() {} + + virtual DispatchResponse continueToLocation(std::unique_ptr in_location, + Maybe in_targetCallFrames) = 0; + + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable(Maybe in_maxScriptsCacheSize, std::string *out_debuggerId) = 0; + virtual DispatchResponse evaluateOnCallFrame(const std::string &in_callFrameId, const std::string &in_expression, + Maybe in_objectGroup, Maybe in_includeCommandLineAPI, + Maybe in_silent, Maybe in_returnByValue, + Maybe in_generatePreview, Maybe in_throwOnSideEffect, + Maybe in_timeout, std::unique_ptr *out_result, + Maybe *out_exceptionDetails) = 0; + + virtual DispatchResponse + getPossibleBreakpoints(std::unique_ptr in_start, Maybe in_end, Maybe in_restrictToFunction, + std::unique_ptr>> *out_locations) = 0; + + virtual DispatchResponse getScriptSource(const std::string &in_scriptId, std::string *out_scriptSource) = 0; + virtual DispatchResponse getStackTrace(std::unique_ptr in_stackTraceId, + std::unique_ptr *out_stackTrace) = 0; + virtual DispatchResponse pause() = 0; + virtual DispatchResponse pauseOnAsyncCall(std::unique_ptr in_parentStackTraceId) = 0; + virtual DispatchResponse removeBreakpoint(const std::string &in_breakpointId) = 0; + virtual DispatchResponse restartFrame(const std::string &in_callFrameId, + std::unique_ptr> *out_callFrames, + Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId) = 0; + virtual DispatchResponse resume() = 0; + virtual DispatchResponse searchInContent(const std::string &in_scriptId, const std::string &in_query, + Maybe in_caseSensitive, Maybe in_isRegex, + std::unique_ptr> *out_result) = 0; + virtual DispatchResponse setAsyncCallStackDepth(int in_maxDepth) = 0; + virtual DispatchResponse setBlackboxPatterns(std::unique_ptr> in_patterns) = 0; + virtual DispatchResponse + setBlackboxedRanges(const std::string &in_scriptId, + std::unique_ptr>> in_positions) = 0; + virtual DispatchResponse setBreakpoint(std::unique_ptr in_location, Maybe in_condition, + std::string *out_breakpointId, + std::unique_ptr *out_actualLocation) = 0; + virtual DispatchResponse + setBreakpointByUrl(int in_lineNumber, Maybe in_url, Maybe in_urlRegex, + Maybe in_scriptHash, Maybe in_columnNumber, Maybe in_condition, + std::string *out_breakpointId, + std::unique_ptr>> *out_locations) = 0; + virtual DispatchResponse setBreakpointOnFunctionCall(const std::string &in_objectId, Maybe in_condition, + std::string *out_breakpointId) = 0; + virtual DispatchResponse setBreakpointsActive(bool in_active) = 0; + virtual DispatchResponse setPauseOnExceptions(const std::string &in_state) = 0; + virtual DispatchResponse setReturnValue(std::unique_ptr in_newValue) = 0; + virtual DispatchResponse setScriptSource(const std::string &in_scriptId, const std::string &in_scriptSource, + Maybe in_dryRun, Maybe> *out_callFrames, + Maybe *out_stackChanged, Maybe *out_asyncStackTrace, + Maybe *out_asyncStackTraceId, + Maybe *out_exceptionDetails) = 0; + virtual DispatchResponse setSkipAllPauses(bool in_skip) = 0; + virtual DispatchResponse setVariableValue(int in_scopeNumber, const std::string &in_variableName, + std::unique_ptr in_newValue, + const std::string &in_callFrameId) = 0; + virtual DispatchResponse stepInto(Maybe in_breakOnAsyncCall) = 0; + virtual DispatchResponse stepOut() = 0; + virtual DispatchResponse stepOver() = 0; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DEBUGGERBACKEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.cc b/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.cc new file mode 100644 index 0000000000..746833aa78 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/debugger_dispatcher_contract.h" +#include "inspector/protocol/debug_dispatcher_impl.h" + +namespace kraken { +namespace debugger { +void DebuggerDispatcherContract::wire(debugger::UberDispatcher *uber, debugger::DebuggerBackend *backend) { + std::unique_ptr dispatcher(new DebugDispatcherImpl(uber->channel(), backend)); + uber->setupRedirects(dispatcher->redirects()); + uber->registerBackend("Debugger", std::move(dispatcher)); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.h b/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.h new file mode 100644 index 0000000000..4493e350d4 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debugger_dispatcher_contract.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DEBUG_DISPATCHER_CONTRACT_H +#define KRAKEN_DEBUGGER_DEBUG_DISPATCHER_CONTRACT_H + +#include "inspector/protocol/debugger_backend.h" +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken { +namespace debugger { +class DebuggerDispatcherContract { +public: + static void wire(UberDispatcher *, DebuggerBackend *); + +private: + DebuggerDispatcherContract() {} +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DEBUG_DISPATCHER_CONTRACT_H diff --git a/plugins/devtools/bridge/inspector/protocol/debugger_frontend.cc b/plugins/devtools/bridge/inspector/protocol/debugger_frontend.cc new file mode 100644 index 0000000000..a6b9dff48c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debugger_frontend.cc @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/debugger_frontend.h" +#include "inspector/protocol/breakpoint_resolved_notification.h" +#include "inspector/protocol/paused_notification.h" +#include "inspector/protocol/script_failed_to_parse_notification.h" +#include "inspector/protocol/script_parsed_notification.h" + +namespace kraken { +namespace debugger { +void DebuggerFrontend::breakpointResolved(const std::string &breakpointId, + std::unique_ptr location) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = + BreakpointResolvedNotification::create().setBreakpointId(breakpointId).setLocation(std::move(location)).build(); + + rapidjson::Document doc; + Event event = {"Debugger.breakpointResolved", messageData->toValue(doc.GetAllocator())}; + m_frontendChannel->sendProtocolNotification(std::move(event)); +} + +void DebuggerFrontend::paused(std::unique_ptr>> callFrames, + const std::string &reason, kraken::debugger::Maybe data, + kraken::debugger::Maybe> hitBreakpoints, + kraken::debugger::Maybe asyncStackTrace, + kraken::debugger::Maybe asyncStackTraceId, + kraken::debugger::Maybe asyncCallStackTraceId) { + + if (!m_frontendChannel) return; + std::unique_ptr messageData = + PausedNotification::create().setCallFrames(std::move(callFrames)).setReason(reason).build(); + if (data.isJust()) messageData->setData(std::move(data).takeJust()); + if (hitBreakpoints.isJust()) messageData->setHitBreakpoints(std::move(hitBreakpoints).takeJust()); + if (asyncStackTrace.isJust()) messageData->setAsyncStackTrace(std::move(asyncStackTrace).takeJust()); + if (asyncStackTraceId.isJust()) messageData->setAsyncStackTraceId(std::move(asyncStackTraceId).takeJust()); + if (asyncCallStackTraceId.isJust()) + messageData->setAsyncCallStackTraceId(std::move(asyncCallStackTraceId).takeJust()); + + rapidjson::Document doc; + Event event = {"Debugger.paused", messageData->toValue(doc.GetAllocator())}; + m_frontendChannel->sendProtocolNotification(std::move(event)); +} + +void DebuggerFrontend::resumed() { + if (!m_frontendChannel) return; + Event event = {"Debugger.resumed", rapidjson::Value(rapidjson::kObjectType)}; + m_frontendChannel->sendProtocolNotification(std::move(event)); +} + +void DebuggerFrontend::scriptFailedToParse(const std::string &scriptId, const std::string &url, int startLine, + int startColumn, int endLine, int endColumn, int executionContextId, + const std::string &hash, + kraken::debugger::Maybe executionContextAuxData, + kraken::debugger::Maybe sourceMapURL, + kraken::debugger::Maybe hasSourceURL, + kraken::debugger::Maybe isModule, kraken::debugger::Maybe length, + kraken::debugger::Maybe stackTrace) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = ScriptFailedToParseNotification::create() + .setScriptId(scriptId) + .setUrl(url) + .setStartLine(startLine) + .setStartColumn(startColumn) + .setEndLine(endLine) + .setEndColumn(endColumn) + .setExecutionContextId(executionContextId) + .setHash(hash) + .build(); + if (executionContextAuxData.isJust()) + messageData->setExecutionContextAuxData(std::move(executionContextAuxData).takeJust()); + if (sourceMapURL.isJust()) messageData->setSourceMapURL(std::move(sourceMapURL).takeJust()); + if (hasSourceURL.isJust()) messageData->setHasSourceURL(std::move(hasSourceURL).takeJust()); + if (isModule.isJust()) messageData->setIsModule(std::move(isModule).takeJust()); + if (length.isJust()) messageData->setLength(std::move(length).takeJust()); + if (stackTrace.isJust()) messageData->setStackTrace(std::move(stackTrace).takeJust()); + + rapidjson::Document doc; + Event event = {"Debugger.scriptFailedToParse", messageData->toValue(doc.GetAllocator())}; + + m_frontendChannel->sendProtocolNotification(std::move(event)); +} + +void DebuggerFrontend::scriptParsed(const std::string &scriptId, const std::string &url, int startLine, int startColumn, + int endLine, int endColumn, int executionContextId, const std::string &hash, + kraken::debugger::Maybe executionContextAuxData, + kraken::debugger::Maybe isLiveEdit, + kraken::debugger::Maybe sourceMapURL, + kraken::debugger::Maybe hasSourceURL, kraken::debugger::Maybe isModule, + kraken::debugger::Maybe length, + kraken::debugger::Maybe stackTrace) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = ScriptParsedNotification::create() + .setScriptId(scriptId) + .setUrl(url) + .setStartLine(startLine) + .setStartColumn(startColumn) + .setEndLine(endLine) + .setEndColumn(endColumn) + .setExecutionContextId(executionContextId) + .setHash(hash) + .build(); + if (executionContextAuxData.isJust()) + messageData->setExecutionContextAuxData(std::move(executionContextAuxData).takeJust()); + if (isLiveEdit.isJust()) messageData->setIsLiveEdit(std::move(isLiveEdit).takeJust()); + if (sourceMapURL.isJust()) messageData->setSourceMapURL(std::move(sourceMapURL).takeJust()); + if (hasSourceURL.isJust()) messageData->setHasSourceURL(std::move(hasSourceURL).takeJust()); + if (isModule.isJust()) messageData->setIsModule(std::move(isModule).takeJust()); + if (length.isJust()) messageData->setLength(std::move(length).takeJust()); + if (stackTrace.isJust()) messageData->setStackTrace(std::move(stackTrace).takeJust()); + + rapidjson::Document doc; + Event event = {"Debugger.scriptParsed", messageData->toValue(doc.GetAllocator())}; + + m_frontendChannel->sendProtocolNotification(std::move(event)); +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/debugger_frontend.h b/plugins/devtools/bridge/inspector/protocol/debugger_frontend.h new file mode 100644 index 0000000000..c95ccd9099 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/debugger_frontend.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DEBUGGERFRONTEND_H +#define KRAKEN_DEBUGGER_DEBUGGERFRONTEND_H + +#include "inspector/protocol/call_frame.h" +#include "inspector/protocol/frontend_channel.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/stacktrace.h" +#include "inspector/protocol/stacktrace_id.h" +#include +#include + +namespace kraken { +namespace debugger { + +// +// json-rpc events +// JSDebugger ---------------------------> chrome devTool +// +// +class DebuggerFrontend { +public: + explicit DebuggerFrontend(FrontendChannel *frontendChannel) : m_frontendChannel(frontendChannel) {} + void breakpointResolved(const std::string &breakpointId, std::unique_ptr location); + + void paused(std::unique_ptr>> callFrames, const std::string &reason, + Maybe data = Maybe(), + Maybe> hitBreakpoints = Maybe>(), + Maybe asyncStackTrace = Maybe(), + Maybe asyncStackTraceId = Maybe(), + Maybe asyncCallStackTraceId = Maybe()); + + void resumed(); + + void scriptFailedToParse(const std::string &scriptId, const std::string &url, int startLine, int startColumn, + int endLine, int endColumn, int executionContextId, const std::string &hash, + Maybe executionContextAuxData = Maybe(), + Maybe sourceMapURL = Maybe(), + Maybe hasSourceURL = Maybe(), Maybe isModule = Maybe(), + Maybe length = Maybe(), Maybe stackTrace = Maybe()); + + void scriptParsed(const std::string &scriptId, const std::string &url, int startLine, int startColumn, int endLine, + int endColumn, int executionContextId, const std::string &hash, + Maybe executionContextAuxData = Maybe(), + Maybe isLiveEdit = Maybe(), Maybe sourceMapURL = Maybe(), + Maybe hasSourceURL = Maybe(), Maybe isModule = Maybe(), + Maybe length = Maybe(), Maybe stackTrace = Maybe()); + +private: + FrontendChannel *m_frontendChannel; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DEBUGGERFRONTEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/dispatch_response.cc b/plugins/devtools/bridge/inspector/protocol/dispatch_response.cc new file mode 100644 index 0000000000..5e391c079d --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/dispatch_response.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/dispatch_response.h" + +namespace kraken { +namespace debugger { + +DispatchResponse DispatchResponse::OK() { + DispatchResponse result; + result.m_status = kSuccess; + result.m_errorCode = kParseError; + return result; +} + +DispatchResponse DispatchResponse::Error(const std::string &error) { + DispatchResponse result; + result.m_status = kError; + result.m_errorCode = kServerError; + result.m_errorMessage = error; + return result; +} + +DispatchResponse DispatchResponse::InternalError() { + DispatchResponse result; + result.m_status = kError; + result.m_errorCode = kInternalError; + result.m_errorMessage = "Internal error"; + return result; +} + +DispatchResponse DispatchResponse::InvalidParams(const std::string &error) { + DispatchResponse result; + result.m_status = kError; + result.m_errorCode = kInvalidParams; + result.m_errorMessage = error; + return result; +} + +DispatchResponse DispatchResponse::FallThrough() { + DispatchResponse result; + result.m_status = kFallThrough; + result.m_errorCode = kParseError; + return result; +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/dispatch_response.h b/plugins/devtools/bridge/inspector/protocol/dispatch_response.h new file mode 100644 index 0000000000..05401ab975 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/dispatch_response.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DISPATCH_RESPONSE_H +#define KRAKEN_DEBUGGER_DISPATCH_RESPONSE_H + +#include "inspector/service/rpc/protocol.h" +#include + +namespace kraken { +namespace debugger { + +class DispatchResponse { +public: + enum Status { + kSuccess = 0, + kError = 1, + kFallThrough = 2, + }; + + Status status() const { + return m_status; + } + const std::string &errorMessage() const { + return m_errorMessage; + } + ErrorCode errorCode() const { + return m_errorCode; + } + bool isSuccess() const { + return m_status == kSuccess; + } + + static DispatchResponse OK(); + static DispatchResponse Error(const std::string &); + static DispatchResponse InternalError(); + static DispatchResponse InvalidParams(const std::string &); + static DispatchResponse FallThrough(); + +private: + Status m_status; + std::string m_errorMessage; + ErrorCode m_errorCode; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DISPATCH_RESPONSE_H diff --git a/plugins/devtools/bridge/inspector/protocol/dispatcher_base.cc b/plugins/devtools/bridge/inspector/protocol/dispatcher_base.cc new file mode 100644 index 0000000000..056d4b15e3 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/dispatcher_base.cc @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/dispatcher_base.h" + +namespace kraken { +namespace debugger { + +const char DispatcherBase::kInvalidParamsString[] = "Invalid parameters"; + +DispatcherBase::Callback::Callback(std::unique_ptr backendImpl, + uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message) + : m_backendImpl(std::move(backendImpl)), m_callId(callId), m_method(method), m_message(std::move(message)) {} + +DispatcherBase::Callback::~Callback() = default; + +void DispatcherBase::Callback::dispose() { + m_backendImpl = nullptr; +} + +void DispatcherBase::Callback::sendIfActive(kraken::debugger::JSONObject message, + const kraken::debugger::DispatchResponse &response) { + if (!m_backendImpl || !m_backendImpl->get()) return; + m_backendImpl->get()->sendResponse(m_callId, response, std::move(message)); + m_backendImpl = nullptr; +} + +void DispatcherBase::Callback::fallThroughIfActive() { + if (!m_backendImpl || !m_backendImpl->get()) return; + m_backendImpl->get()->channel()->fallThrough(m_callId, m_method, std::move(m_message)); + m_backendImpl = nullptr; +} + +/*****************************DispatcherBase::WeakPtr***************************/ + +DispatcherBase::WeakPtr::WeakPtr(debugger::DispatcherBase *dispatcher) : m_dispatcher(dispatcher) {} + +DispatcherBase::WeakPtr::~WeakPtr() { + if (m_dispatcher) { + m_dispatcher->m_weakPtrs.erase(this); + } +} + +/**********************************DispatcherBase********************************/ +DispatcherBase::DispatcherBase(debugger::FrontendChannel *frontendChannel) : m_frontendChannel(frontendChannel) {} + +DispatcherBase::~DispatcherBase() { + clearFrontend(); +} + +void DispatcherBase::sendResponse(uint64_t callId, const debugger::DispatchResponse &response) { + sendResponse(callId, response, JSONObject(rapidjson::kObjectType)); +} + +void DispatcherBase::sendResponse(uint64_t callId, const debugger::DispatchResponse &response, + debugger::JSONObject result) { + if (!m_frontendChannel) { + KRAKEN_LOG(ERROR) << "FrontendChannel invalid..."; + return; + } + if (response.status() == DispatchResponse::kError) { + reportProtocolError(callId, response.errorCode(), response.errorMessage(), nullptr); + return; + } + m_frontendChannel->sendProtocolResponse( + callId, {callId, std::move(result), JSONObject(rapidjson::kObjectType), false}); +} + +void DispatcherBase::reportProtocolError(uint64_t callId, debugger::ErrorCode code, + const std::string &errorMessage, debugger::ErrorSupport *errors) { + Internal::reportProtocolErrorTo(m_frontendChannel, callId, code, errorMessage, errors); +} + +void DispatcherBase::clearFrontend() { + m_frontendChannel = nullptr; + for (const auto &weak : m_weakPtrs) { + weak->dispose(); + } + m_weakPtrs.clear(); +} + +std::unique_ptr DispatcherBase::weakPtr() { + auto weak = std::make_unique(this); + m_weakPtrs.insert(weak.get()); + return weak; +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/dispatcher_base.h b/plugins/devtools/bridge/inspector/protocol/dispatcher_base.h new file mode 100644 index 0000000000..a996587c64 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/dispatcher_base.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DISPATCHER_BASE_H +#define KRAKEN_DEBUGGER_DISPATCHER_BASE_H + +#include "inspector/protocol/dispatch_response.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/frontend_channel.h" +#include "inspector/service/rpc/protocol.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { + +namespace Internal { + +/** + * + * jsonRPC error response + * + * { + * id:100, + * error:{ + * code:10001, + * message:'', + * data:'' + * } + * } + * + * */ +static void reportProtocolErrorTo(FrontendChannel *frontendChannel, uint64_t callId, ErrorCode code, + const std::string &errorMessage, ErrorSupport *errors) { + if (!frontendChannel) { + return; + } + + Response response; + response.id = callId; + response.result = JSONObject(rapidjson::kObjectType); + response.hasError = true; + + rapidjson::Document d; + JSONObject error; + error.SetObject(); + error.AddMember("code", code, d.GetAllocator()); + + JSONObject msg; + msg.SetString(errorMessage.c_str(), errorMessage.length(), d.GetAllocator()); + + error.AddMember("message", msg, d.GetAllocator()); + if (errors) { + JSONObject err; + err.SetString(errors->errors().c_str(), errors->errors().length(), d.GetAllocator()); + error.AddMember("data", err, d.GetAllocator()); + } + response.error = std::move(error); + frontendChannel->sendProtocolResponse(callId, std::move(response)); +} + +/** + * jsonRPC error notification + * A Notification is a Request object without an "id" member. + * + * { + * error:{ + * code:'', + * message: '' + * } + * } + * + * */ +static void reportProtocolErrorTo(FrontendChannel *frontendChannel, ErrorCode code, + const std::string &errorMessage) { + if (!frontendChannel) { + return; + } + Error error; + + error.code = code; + error.message = errorMessage; + error.data = JSONObject(rapidjson::kObjectType); + frontendChannel->sendProtocolError(std::move(error)); +} +} // namespace Internal + +class DispatcherBase { +private: + KRAKEN_DISALLOW_COPY(DispatcherBase); + +public: + static const char kInvalidParamsString[]; + + /*RAII*/ + class WeakPtr { + public: + explicit WeakPtr(DispatcherBase *); + ~WeakPtr(); + DispatcherBase *get() { + return m_dispatcher; + } + void dispose() { + m_dispatcher = nullptr; + } + + private: + DispatcherBase *m_dispatcher; + }; + + class Callback { + public: + Callback(std::unique_ptr backendImpl, uint64_t callId, const std::string &method, + JSONObject message); + virtual ~Callback(); + void dispose(); + + protected: + void sendIfActive(JSONObject message, const DispatchResponse &response); + void fallThroughIfActive(); + + private: + std::unique_ptr m_backendImpl; + uint64_t m_callId; + std::string m_method; + + JSONObject m_message; + }; + + explicit DispatcherBase(debugger::FrontendChannel *); + virtual ~DispatcherBase(); + + virtual bool canDispatch(const std::string &method) = 0; + virtual void dispatch(uint64_t callId, const std::string &method, JSONObject message) = 0; + FrontendChannel *channel() { + return m_frontendChannel; + } + + void sendResponse(uint64_t callId, const DispatchResponse &, JSONObject result); + void sendResponse(uint64_t callId, const DispatchResponse &); + + void reportProtocolError(uint64_t callId, ErrorCode, const std::string &errorMessage, ErrorSupport *errors); + void clearFrontend(); + + std::unique_ptr weakPtr(); + +private: + FrontendChannel *m_frontendChannel; + std::unordered_set m_weakPtrs; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DISPATCHER_BASE_H diff --git a/plugins/devtools/bridge/inspector/protocol/domain.h b/plugins/devtools/bridge/inspector/protocol/domain.h new file mode 100644 index 0000000000..eb0de8e28f --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/domain.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DOMAIN_H +#define KRAKEN_DEBUGGER_DOMAIN_H + +#include + +namespace kraken { +namespace debugger { + +class Domain { + KRAKEN_DISALLOW_COPY(Domain); + +public: + ~Domain() {} + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + std::string getVersion() { + return m_version; + } + + void setVersion(const std::string &value) { + m_version = value; + } + + template class DomainBuilder { + public: + enum { NoFieldsSet = 0, NameSet = 1 << 1, VersionSet = 1 << 2, AllFieldsSet = (NameSet | VersionSet | 0) }; + + DomainBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + DomainBuilder &setVersion(const std::string &value) { + static_assert(!(STATE & VersionSet), "property version should not be set yet"); + m_result->setVersion(value); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Domain; + + DomainBuilder() : m_result(new Domain()) {} + + template DomainBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static DomainBuilder<0> create() { + return DomainBuilder<0>(); + } + +private: + Domain() {} + + std::string m_name; + std::string m_version; +}; + +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DOMAIN_H diff --git a/plugins/devtools/bridge/inspector/protocol/entry_added_notification.cc b/plugins/devtools/bridge/inspector/protocol/entry_added_notification.cc new file mode 100644 index 0000000000..a5a9d881d5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/entry_added_notification.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "entry_added_notification.h" + +namespace kraken { +namespace debugger { +std::unique_ptr EntryAddedNotification::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new EntryAddedNotification()); + errors->push(); + + if (value->HasMember("entry") && (*value)["entry"].IsObject()) { + rapidjson::Value _entry = (*value)["entry"].GetObject(); + result->m_entry = LogEntry::fromValue(&_entry, errors); + } else { + errors->setName("entry"); + errors->addError("entry not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value EntryAddedNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("entry", m_entry.get()->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/entry_added_notification.h b/plugins/devtools/bridge/inspector/protocol/entry_added_notification.h new file mode 100644 index 0000000000..757937e3d5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/entry_added_notification.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_ENTRY_ADDED_NOTIFICATION_H +#define KRAKEN_DEBUGGER_ENTRY_ADDED_NOTIFICATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/log_entry.h" + +namespace kraken { +namespace debugger { +class EntryAddedNotification { + KRAKEN_DISALLOW_COPY(EntryAddedNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~EntryAddedNotification() {} + + LogEntry *getEntry() { + return m_entry.get(); + } + + void setEntry(std::unique_ptr value) { + m_entry = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class EntryAddedNotificationBuilder { + public: + enum { NoFieldsSet = 0, EntrySet = 1 << 1, AllFieldsSet = (EntrySet | 0) }; + + EntryAddedNotificationBuilder &setEntry(std::unique_ptr value) { + static_assert(!(STATE & EntrySet), "property entry should not be set yet"); + m_result->setEntry(std::move(value)); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class EntryAddedNotification; + + EntryAddedNotificationBuilder() : m_result(new EntryAddedNotification()) {} + + template EntryAddedNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static EntryAddedNotificationBuilder<0> create() { + return EntryAddedNotificationBuilder<0>(); + } + +private: + EntryAddedNotification() {} + + std::unique_ptr m_entry; +}; + +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_ENTRY_ADDED_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/entry_preview.cc b/plugins/devtools/bridge/inspector/protocol/entry_preview.cc new file mode 100644 index 0000000000..15d069f57d --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/entry_preview.cc @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "entry_preview.h" +#include "inspector/protocol/object_preview.h" +#include "remote_object.h" + +namespace kraken { +namespace debugger { +std::unique_ptr EntryPreview::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new EntryPreview()); + errors->push(); + + if (value->HasMember("key")) { + errors->setName("key"); + if ((*value)["key"].IsObject()) { + rapidjson::Value _key = (*value)["key"].GetObject(); + result->m_key = ObjectPreview::fromValue(&_key, errors); + } else { + errors->addError("key should be object"); + } + } + + if (value->HasMember("value") && (*value)["value"].IsObject()) { + rapidjson::Value _value = (*value)["value"].GetObject(); + result->m_value = ObjectPreview::fromValue(&_value, errors); + } else { + errors->setName("value"); + errors->addError("value not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value EntryPreview::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + if (m_key.isJust()) result.AddMember("key", m_key.fromJust()->toValue(allocator), allocator); + result.AddMember("value", m_value.get()->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/entry_preview.h b/plugins/devtools/bridge/inspector/protocol/entry_preview.h new file mode 100644 index 0000000000..fff905a84a --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/entry_preview.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_ENTRY_PREVIEW_H +#define KRAKEN_DEBUGGER_ENTRY_PREVIEW_H + +#include +#include + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include + +namespace kraken { +namespace debugger { +class ObjectPreview; +class EntryPreview { + KRAKEN_DISALLOW_COPY(EntryPreview); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~EntryPreview() {} + + bool hasKey() { + return m_key.isJust(); + } + + ObjectPreview *getKey(ObjectPreview *defaultValue) { + return m_key.isJust() ? m_key.fromJust() : defaultValue; + } + + void setKey(std::unique_ptr value) { + m_key = std::move(value); + } + + ObjectPreview *getValue() { + return m_value.get(); + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class EntryPreviewBuilder { + public: + enum { NoFieldsSet = 0, ValueSet = 1 << 1, AllFieldsSet = (ValueSet | 0) }; + + EntryPreviewBuilder &setKey(std::unique_ptr value) { + m_result->setKey(std::move(value)); + return *this; + } + + EntryPreviewBuilder &setValue(std::unique_ptr value) { + static_assert(!(STATE & ValueSet), "property value should not be set yet"); + m_result->setValue(std::move(value)); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class EntryPreview; + + EntryPreviewBuilder() : m_result(new EntryPreview()) {} + + template EntryPreviewBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static EntryPreviewBuilder<0> create() { + return EntryPreviewBuilder<0>(); + } + +private: + EntryPreview() {} + + Maybe m_key; + std::unique_ptr m_value; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_ENTRY_PREVIEW_H diff --git a/plugins/devtools/bridge/inspector/protocol/error_support.cc b/plugins/devtools/bridge/inspector/protocol/error_support.cc new file mode 100644 index 0000000000..99680ec107 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/error_support.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "error_support.h" +#include + +namespace kraken::debugger { +ErrorSupport::ErrorSupport() {} +ErrorSupport::~ErrorSupport() {} + +void ErrorSupport::setName(const char *name) { + setName(std::string(name)); +} + +void ErrorSupport::setName(const std::string &name) { + if (m_path.size() > 0) { + m_path[m_path.size() - 1] = name; + } +} + +void ErrorSupport::push() { + m_path.push_back(std::string()); +} + +void ErrorSupport::addError(const char *error) { + addError(std::string(error)); +} + +void ErrorSupport::addError(const std::string &error) { + std::stringstream builder; + for (size_t i = 0; i < m_path.size(); ++i) { + if (i) { + builder << '.'; + } + builder << m_path[i]; + } + builder << ": "; + builder << error; + m_errors.push_back(builder.str()); + builder.str(""); +} + +std::string ErrorSupport::errors() { + std::stringstream builder; + for (size_t i = 0; i < m_errors.size(); ++i) { + if (i) { + builder << "; "; + } + builder << m_errors[i]; + } + std::string result = builder.str(); + builder.str(""); + return result; +} + +bool ErrorSupport::hasErrors() { + return m_errors.size() != 0; +} + +void ErrorSupport::pop() { + m_path.pop_back(); +} +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/error_support.h b/plugins/devtools/bridge/inspector/protocol/error_support.h new file mode 100644 index 0000000000..86a6689471 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/error_support.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_ERROR_SUPPORT_H +#define KRAKEN_DEBUGGER_ERROR_SUPPORT_H + +#include +#include + +namespace kraken::debugger { +class ErrorSupport { +public: + ErrorSupport(); + ~ErrorSupport(); + + void push(); + void setName(const char *); + void setName(const std::string &); + void pop(); + void addError(const char *); + void addError(const std::string &); + bool hasErrors(); + std::string errors(); + +private: + std::vector m_path; + std::vector m_errors; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_ERROR_SUPPORT_H diff --git a/plugins/devtools/bridge/inspector/protocol/exception_details.cc b/plugins/devtools/bridge/inspector/protocol/exception_details.cc new file mode 100644 index 0000000000..2d272e7e80 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/exception_details.cc @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "exception_details.h" + +namespace kraken { +namespace debugger { +std::unique_ptr ExceptionDetails::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ExceptionDetails()); + errors->push(); + + if (value->HasMember("exceptionId") && (*value)["exceptionId"].IsInt()) { + result->m_exceptionId = (*value)["exceptionId"].GetInt(); + } else { + errors->setName("exceptionId"); + errors->addError("exceptionId not found"); + } + + if (value->HasMember("text") && (*value)["text"].IsString()) { + result->m_text = (*value)["text"].GetString(); + } else { + errors->setName("text"); + errors->addError("text not found"); + } + + if (value->HasMember("lineNumber") && (*value)["lineNumber"].IsInt()) { + result->m_lineNumber = (*value)["lineNumber"].GetInt(); + } else { + errors->setName("lineNumber"); + errors->addError("lineNumber not found"); + } + + if (value->HasMember("columnNumber") && (*value)["columnNumber"].IsInt()) { + result->m_columnNumber = (*value)["columnNumber"].GetInt(); + } else { + errors->setName("columnNumber"); + errors->addError("columnNumber not found"); + } + + if (value->HasMember("scriptId")) { + errors->setName("scriptId"); + if ((*value)["scriptId"].IsString()) { + result->m_scriptId = (*value)["scriptId"].GetString(); + } else { + errors->addError("scriptId should be string"); + } + } + + if (value->HasMember("url")) { + errors->setName("url"); + if ((*value)["url"].IsString()) { + result->m_url = (*value)["url"].GetString(); + } else { + errors->addError("url should be string"); + } + } + + if (value->HasMember("stackTrace")) { + errors->setName("stackTrace"); + if ((*value)["stackTrace"].IsObject()) { + rapidjson::Value _stack_trace = (*value)["stackTrace"].GetObject(); + result->m_stackTrace = StackTrace::fromValue(&_stack_trace, errors); + } else { + errors->addError("stackTrace should be object"); + } + } + + if (value->HasMember("exception")) { + errors->setName("exception"); + if ((*value)["exception"].IsObject()) { + rapidjson::Value _exception = (*value)["exception"].GetObject(); + result->m_exception = RemoteObject::fromValue(&_exception, errors); + } else { + errors->addError("exception should be object"); + } + } + + if (value->HasMember("executionContextId")) { + errors->setName("executionContextId"); + if ((*value)["executionContextId"].IsInt()) { + result->m_executionContextId = (*value)["executionContextId"].GetInt(); + } else { + errors->addError("executionContextId should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ExceptionDetails::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("exceptionId", m_exceptionId, allocator); + result.AddMember("text", m_text, allocator); + result.AddMember("lineNumber", m_lineNumber, allocator); + result.AddMember("columnNumber", m_columnNumber, allocator); + if (m_scriptId.isJust()) result.AddMember("scriptId", m_scriptId.fromJust(), allocator); + if (m_url.isJust()) result.AddMember("url", m_url.fromJust(), allocator); + if (m_stackTrace.isJust()) result.AddMember("stackTrace", m_stackTrace.fromJust()->toValue(allocator), allocator); + if (m_exception.isJust()) result.AddMember("exception", m_exception.fromJust()->toValue(allocator), allocator); + if (m_executionContextId.isJust()) result.AddMember("executionContextId", m_executionContextId.fromJust(), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/exception_details.h b/plugins/devtools/bridge/inspector/protocol/exception_details.h new file mode 100644 index 0000000000..6264b51351 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/exception_details.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_EXCEPTION_DETAILS_H +#define KRAKEN_DEBUGGER_EXCEPTION_DETAILS_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/remote_object.h" +#include "inspector/protocol/stacktrace.h" +#include +#include +#include + +namespace kraken::debugger { + +class ExceptionDetails { + KRAKEN_DISALLOW_COPY(ExceptionDetails); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ExceptionDetails() {} + + int getExceptionId() { + return m_exceptionId; + } + + void setExceptionId(int value) { + m_exceptionId = value; + } + + std::string getText() { + return m_text; + } + + void setText(const std::string &value) { + m_text = value; + } + + int getLineNumber() { + return m_lineNumber; + } + + void setLineNumber(int value) { + m_lineNumber = value; + } + + int getColumnNumber() { + return m_columnNumber; + } + + void setColumnNumber(int value) { + m_columnNumber = value; + } + + bool hasScriptId() { + return m_scriptId.isJust(); + } + + std::string getScriptId(const std::string &defaultValue) { + return m_scriptId.isJust() ? m_scriptId.fromJust() : defaultValue; + } + + void setScriptId(const std::string &value) { + m_scriptId = value; + } + + bool hasUrl() { + return m_url.isJust(); + } + + std::string getUrl(const std::string &defaultValue) { + return m_url.isJust() ? m_url.fromJust() : defaultValue; + } + + void setUrl(const std::string &value) { + m_url = value; + } + + bool hasStackTrace() { + return m_stackTrace.isJust(); + } + + StackTrace *getStackTrace(StackTrace *defaultValue) { + return m_stackTrace.isJust() ? m_stackTrace.fromJust() : defaultValue; + } + + void setStackTrace(std::unique_ptr value) { + m_stackTrace = std::move(value); + } + + bool hasException() { + return m_exception.isJust(); + } + + RemoteObject *getException(RemoteObject *defaultValue) { + return m_exception.isJust() ? m_exception.fromJust() : defaultValue; + } + + void setException(std::unique_ptr value) { + m_exception = std::move(value); + } + + bool hasExecutionContextId() { + return m_executionContextId.isJust(); + } + + int getExecutionContextId(int defaultValue) { + return m_executionContextId.isJust() ? m_executionContextId.fromJust() : defaultValue; + } + + void setExecutionContextId(int value) { + m_executionContextId = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ExceptionDetailsBuilder { + public: + enum { + NoFieldsSet = 0, + ExceptionIdSet = 1 << 1, + TextSet = 1 << 2, + LineNumberSet = 1 << 3, + ColumnNumberSet = 1 << 4, + AllFieldsSet = (ExceptionIdSet | TextSet | LineNumberSet | ColumnNumberSet | 0) + }; + + ExceptionDetailsBuilder &setExceptionId(int value) { + static_assert(!(STATE & ExceptionIdSet), "property exceptionId should not be set yet"); + m_result->setExceptionId(value); + return castState(); + } + + ExceptionDetailsBuilder &setText(const std::string &value) { + static_assert(!(STATE & TextSet), "property text should not be set yet"); + m_result->setText(value); + return castState(); + } + + ExceptionDetailsBuilder &setLineNumber(int value) { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + ExceptionDetailsBuilder &setColumnNumber(int value) { + static_assert(!(STATE & ColumnNumberSet), "property columnNumber should not be set yet"); + m_result->setColumnNumber(value); + return castState(); + } + + ExceptionDetailsBuilder &setScriptId(const std::string &value) { + m_result->setScriptId(value); + return *this; + } + + ExceptionDetailsBuilder &setUrl(const std::string &value) { + m_result->setUrl(value); + return *this; + } + + ExceptionDetailsBuilder &setStackTrace(std::unique_ptr value) { + m_result->setStackTrace(std::move(value)); + return *this; + } + + ExceptionDetailsBuilder &setException(std::unique_ptr value) { + m_result->setException(std::move(value)); + return *this; + } + + ExceptionDetailsBuilder &setExecutionContextId(int value) { + m_result->setExecutionContextId(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ExceptionDetails; + + ExceptionDetailsBuilder() : m_result(new ExceptionDetails()) {} + + template ExceptionDetailsBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ExceptionDetailsBuilder<0> create() { + return ExceptionDetailsBuilder<0>(); + } + +private: + ExceptionDetails() { + m_exceptionId = 0; + m_lineNumber = 0; + m_columnNumber = 0; + } + + int m_exceptionId; + std::string m_text; + int m_lineNumber; + int m_columnNumber; + Maybe m_scriptId; + Maybe m_url; + Maybe m_stackTrace; + Maybe m_exception; + Maybe m_executionContextId; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_EXCEPTION_DETAILS_H diff --git a/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.cc b/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.cc new file mode 100644 index 0000000000..78681c75de --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "execution_context_created_notification.h" + +namespace kraken { +namespace debugger { +std::unique_ptr +ExecutionContextCreatedNotification::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ExecutionContextCreatedNotification()); + errors->push(); + + if (value->HasMember("context") && (*value)["context"].IsObject()) { + rapidjson::Value val = (*value)["context"].GetObject(); + result->m_context = ExecutionContextDescription::fromValue(&val, errors); + } else { + errors->setName("context"); + errors->addError("context not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ExecutionContextCreatedNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("context", m_context->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.h b/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.h new file mode 100644 index 0000000000..e3b547c586 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/execution_context_created_notification.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_EXECUTION_CONTEXT_CREATED_NOTIFICATION_H +#define KRAKEN_DEBUGGER_EXECUTION_CONTEXT_CREATED_NOTIFICATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/execution_context_description.h" +#include "inspector/protocol/maybe.h" +#include +#include + +namespace kraken { +namespace debugger { +class ExecutionContextCreatedNotification { + KRAKEN_DISALLOW_COPY(ExecutionContextCreatedNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ExecutionContextCreatedNotification() {} + + ExecutionContextDescription *getContext() { + return m_context.get(); + } + + void setContext(std::unique_ptr value) { + m_context = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ExecutionContextCreatedNotificationBuilder { + public: + enum { NoFieldsSet = 0, ContextSet = 1 << 1, AllFieldsSet = (ContextSet | 0) }; + + ExecutionContextCreatedNotificationBuilder & + setContext(std::unique_ptr value) { + static_assert(!(STATE & ContextSet), "property context should not be set yet"); + m_result->setContext(std::move(value)); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ExecutionContextCreatedNotification; + + ExecutionContextCreatedNotificationBuilder() : m_result(new ExecutionContextCreatedNotification()) {} + + template ExecutionContextCreatedNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ExecutionContextCreatedNotificationBuilder<0> create() { + return ExecutionContextCreatedNotificationBuilder<0>(); + } + +private: + ExecutionContextCreatedNotification() {} + + std::unique_ptr m_context; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_EXECUTION_CONTEXT_CREATED_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/execution_context_description.cc b/plugins/devtools/bridge/inspector/protocol/execution_context_description.cc new file mode 100644 index 0000000000..2e8cacb41c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/execution_context_description.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/execution_context_description.h" + +namespace kraken::debugger { + +std::unique_ptr +ExecutionContextDescription::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ExecutionContextDescription()); + errors->push(); + + if (value->HasMember("id") && (*value)["id"].IsInt()) { + result->m_id = (*value)["id"].GetInt(); + } else { + errors->setName("id"); + errors->addError("id not found"); + } + + if (value->HasMember("origin") && (*value)["origin"].IsString()) { + result->m_origin = (*value)["origin"].GetString(); + } else { + errors->setName("origin"); + errors->addError("origin not found"); + } + + if (value->HasMember("name") && (*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->setName("name"); + errors->addError("name not found"); + } + + if (value->HasMember("auxData")) { + result->m_auxData = std::make_unique((*value)["auxData"], result->m_doc.GetAllocator()); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ExecutionContextDescription::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("id", m_id, allocator); + result.AddMember("origin", m_origin, allocator); + result.AddMember("name", m_name, allocator); + if (m_auxData.isJust()) result.AddMember("auxData", *m_auxData.fromJust(), allocator); + return result; +} +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/protocol/execution_context_description.h b/plugins/devtools/bridge/inspector/protocol/execution_context_description.h new file mode 100644 index 0000000000..8fa555bf3e --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/execution_context_description.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_EXECUTION_CONTEXT_DESCRIPTION_H +#define KRAKEN_DEBUGGER_EXECUTION_CONTEXT_DESCRIPTION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include +#include +#include +#include + +namespace kraken::debugger { +class ExecutionContextDescription { + KRAKEN_DISALLOW_COPY(ExecutionContextDescription); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ExecutionContextDescription() {} + + int getId() { + return m_id; + } + + void setId(int value) { + m_id = value; + } + + std::string getOrigin() { + return m_origin; + } + + void setOrigin(const std::string &value) { + m_origin = value; + } + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + bool hasAuxData() { + return m_auxData.isJust(); + } + + rapidjson::Value *getAuxData(rapidjson::Value *defaultValue) { + return m_auxData.isJust() ? m_auxData.fromJust() : defaultValue; + } + + void setAuxData(std::unique_ptr value) { + m_auxData = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ExecutionContextDescriptionBuilder { + public: + enum { + NoFieldsSet = 0, + IdSet = 1 << 1, + OriginSet = 1 << 2, + NameSet = 1 << 3, + AllFieldsSet = (IdSet | OriginSet | NameSet | 0) + }; + + ExecutionContextDescriptionBuilder &setId(int value) { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + ExecutionContextDescriptionBuilder &setOrigin(const std::string &value) { + static_assert(!(STATE & OriginSet), "property origin should not be set yet"); + m_result->setOrigin(value); + return castState(); + } + + ExecutionContextDescriptionBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + ExecutionContextDescriptionBuilder &setAuxData(std::unique_ptr value) { + m_result->setAuxData(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ExecutionContextDescription; + + ExecutionContextDescriptionBuilder() : m_result(new ExecutionContextDescription()) {} + + template ExecutionContextDescriptionBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ExecutionContextDescriptionBuilder<0> create() { + return ExecutionContextDescriptionBuilder<0>(); + } + +private: + ExecutionContextDescription() { + m_id = 0; + } + + int m_id; + std::string m_origin; + std::string m_name; + Maybe m_auxData; + rapidjson::Document m_doc; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_EXECUTION_CONTEXT_DESCRIPTION_H diff --git a/plugins/devtools/bridge/inspector/protocol/frontend_channel.h b/plugins/devtools/bridge/inspector/protocol/frontend_channel.h new file mode 100644 index 0000000000..99dc349173 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/frontend_channel.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_FRONTEND_CHANNEL_H +#define KRAKEN_DEBUGGER_FRONTEND_CHANNEL_H + +#include "inspector/service/rpc/protocol.h" + +namespace kraken::debugger { +class FrontendChannel { +public: + virtual ~FrontendChannel() {} + + // response + virtual void sendProtocolResponse(uint64_t callId, Response message) = 0; + + // event + virtual void sendProtocolNotification(Event message) = 0; + + // error + virtual void sendProtocolError(Error message) = 0; + + // There's no other layer to handle the command. + virtual void fallThrough(uint64_t callId, const std::string &method, JSONObject message) = 0; + + // virtual void flushProtocolNotifications() = 0; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_FRONTEND_CHANNEL_H diff --git a/plugins/devtools/bridge/inspector/protocol/heap_profiler_backend.h b/plugins/devtools/bridge/inspector/protocol/heap_profiler_backend.h new file mode 100644 index 0000000000..fb5faa9058 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/heap_profiler_backend.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_HEAP_PROFILER_BACKEND_H +#define KRAKEN_DEBUGGER_HEAP_PROFILER_BACKEND_H + +#include "inspector/protocol/dispatch_response.h" + +namespace kraken { +namespace debugger { +class HeapProfilerBackend { +public: + virtual ~HeapProfilerBackend() {} + + // virtual DispatchResponse addInspectedHeapObject(const std::string& in_heapObjectId) = 0; + virtual DispatchResponse collectGarbage() = 0; + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable() = 0; + // virtual DispatchResponse getHeapObjectId(const std::string& in_objectId, std::string* + // out_heapSnapshotObjectId) = 0; virtual DispatchResponse getObjectByHeapObjectId(const std::string& + // in_objectId, Maybe in_objectGroup, std::unique_ptr* + // out_result) = 0; virtual DispatchResponse + // getSamplingProfile(std::unique_ptr* out_profile) = 0; + // virtual DispatchResponse startSampling(Maybe in_samplingInterval) = 0; + // virtual DispatchResponse startTrackingHeapObjects(Maybe in_trackAllocations) = 0; + // virtual DispatchResponse stopSampling(std::unique_ptr* + // out_profile) = 0; virtual DispatchResponse stopTrackingHeapObjects(Maybe in_reportProgress) = 0; + // virtual DispatchResponse takeHeapSnapshot(Maybe in_reportProgress) = 0; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_HEAP_PROFILER_BACKEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.cc b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.cc new file mode 100644 index 0000000000..829541f249 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "heap_profiler_dispatcher_contract.h" +#include "inspector/protocol/heap_profiler_dispatcher_impl.h" + +namespace kraken { +namespace debugger { +void HeapProfilerDispatcherContract::wire(kraken::debugger::UberDispatcher *uber, + kraken::debugger::HeapProfilerBackend *backend) { + std::unique_ptr dispatcher(new HeapProfilerDispatcherImpl(uber->channel(), backend)); + uber->setupRedirects(dispatcher->redirects()); + uber->registerBackend("HeapProfiler", std::move(dispatcher)); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.h b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.h new file mode 100644 index 0000000000..7d77d0a9a7 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_contract.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_CONTRACT_H +#define KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_CONTRACT_H + +#include "inspector/protocol/heap_profiler_backend.h" +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken { +namespace debugger { +class HeapProfilerDispatcherContract { + +public: + static void wire(UberDispatcher *, HeapProfilerBackend *); + +private: + HeapProfilerDispatcherContract() {} +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_CONTRACT_H diff --git a/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.cc b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.cc new file mode 100644 index 0000000000..e42a173948 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.cc @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "heap_profiler_dispatcher_impl.h" + +namespace kraken { +namespace debugger { + +bool HeapProfilerDispatcherImpl::canDispatch(const std::string &method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} + +void HeapProfilerDispatcherImpl::dispatch(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message) { + std::unordered_map::iterator it = m_dispatchMap.find(method); + if (it == m_dispatchMap.end()) { + return; + } + ErrorSupport errors; + (it->second)(callId, method, std::move(message), &errors); +} + +void HeapProfilerDispatcherImpl::enable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void HeapProfilerDispatcherImpl::disable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void HeapProfilerDispatcherImpl::collectGarbage(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->collectGarbage(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void HeapProfilerDispatcherImpl::addInspectedHeapObject(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::getHeapObjectId(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::getObjectByHeapObjectId(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::getSamplingProfile(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::startSampling(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::startTrackingHeapObjects(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::stopSampling(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::stopTrackingHeapObjects(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +void HeapProfilerDispatcherImpl::takeHeapSnapshot(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) {} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.h b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.h new file mode 100644 index 0000000000..aed259185e --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/heap_profiler_dispatcher_impl.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_IMPL_H +#define KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_IMPL_H + +#include "inspector/protocol/dispatcher_base.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/heap_profiler_backend.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { +class HeapProfilerDispatcherImpl : public DispatcherBase { +public: + HeapProfilerDispatcherImpl(FrontendChannel *frontendChannel, HeapProfilerBackend *backend) + : DispatcherBase(frontendChannel), m_backend(backend) { + + m_dispatchMap["HeapProfiler.addInspectedHeapObject"] = + std::bind(&HeapProfilerDispatcherImpl::addInspectedHeapObject, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.collectGarbage"] = + std::bind(&HeapProfilerDispatcherImpl::collectGarbage, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.disable"] = + std::bind(&HeapProfilerDispatcherImpl::disable, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.enable"] = + std::bind(&HeapProfilerDispatcherImpl::enable, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.getHeapObjectId"] = + std::bind(&HeapProfilerDispatcherImpl::getHeapObjectId, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.getObjectByHeapObjectId"] = + std::bind(&HeapProfilerDispatcherImpl::getObjectByHeapObjectId, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.getSamplingProfile"] = + std::bind(&HeapProfilerDispatcherImpl::getSamplingProfile, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.startSampling"] = + std::bind(&HeapProfilerDispatcherImpl::startSampling, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.startTrackingHeapObjects"] = + std::bind(&HeapProfilerDispatcherImpl::startTrackingHeapObjects, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.stopSampling"] = + std::bind(&HeapProfilerDispatcherImpl::stopSampling, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.stopTrackingHeapObjects"] = + std::bind(&HeapProfilerDispatcherImpl::stopTrackingHeapObjects, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["HeapProfiler.takeHeapSnapshot"] = + std::bind(&HeapProfilerDispatcherImpl::takeHeapSnapshot, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + } + + ~HeapProfilerDispatcherImpl() override {} + + bool canDispatch(const std::string &method) override; + void dispatch(uint64_t callId, const std::string &method, JSONObject message) override; + std::unordered_map &redirects() { + return m_redirects; + } + +protected: + using CallHandler = std::function; + using DispatchMap = std::unordered_map; + + DispatchMap m_dispatchMap; + std::unordered_map m_redirects; + + void addInspectedHeapObject(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void collectGarbage(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void disable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void enable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getHeapObjectId(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getObjectByHeapObjectId(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getSamplingProfile(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void startSampling(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void startTrackingHeapObjects(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *); + void stopSampling(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void stopTrackingHeapObjects(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void takeHeapSnapshot(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + + HeapProfilerBackend *m_backend; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_HEAP_PROFILER_DISPATCHER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.cc b/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.cc new file mode 100644 index 0000000000..f41b65385b --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "internal_property_descriptor.h" + +namespace kraken { +namespace debugger { +std::unique_ptr +InternalPropertyDescriptor::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new InternalPropertyDescriptor()); + errors->push(); + + if (value->HasMember("name") && (*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->setName("name"); + errors->addError("name not found"); + } + + if (value->HasMember("value")) { + errors->setName("value"); + if ((*value)["value"].IsObject()) { + rapidjson::Value _value = (*value)["value"].GetObject(); + result->m_value = RemoteObject::fromValue(&_value, errors); + } else { + errors->addError("value should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value InternalPropertyDescriptor::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + + result.AddMember("name", m_name, allocator); + if (m_value.isJust()) result.AddMember("value", m_value.fromJust()->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.h b/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.h new file mode 100644 index 0000000000..b091994f17 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/internal_property_descriptor.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_INTERNAL_PROPERTY_DESCRIPTOR_H +#define KRAKEN_DEBUGGER_INTERNAL_PROPERTY_DESCRIPTOR_H + +#include "inspector/protocol/remote_object.h" +#include + +namespace kraken { +namespace debugger { +class InternalPropertyDescriptor { + KRAKEN_DISALLOW_COPY(InternalPropertyDescriptor); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~InternalPropertyDescriptor() {} + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + bool hasValue() { + return m_value.isJust(); + } + + RemoteObject *getValue(RemoteObject *defaultValue) { + return m_value.isJust() ? m_value.fromJust() : defaultValue; + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class InternalPropertyDescriptorBuilder { + public: + enum { NoFieldsSet = 0, NameSet = 1 << 1, AllFieldsSet = (NameSet | 0) }; + + InternalPropertyDescriptorBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + InternalPropertyDescriptorBuilder &setValue(std::unique_ptr value) { + m_result->setValue(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class InternalPropertyDescriptor; + + InternalPropertyDescriptorBuilder() : m_result(new InternalPropertyDescriptor()) {} + + template InternalPropertyDescriptorBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static InternalPropertyDescriptorBuilder<0> create() { + return InternalPropertyDescriptorBuilder<0>(); + } + +private: + InternalPropertyDescriptor() {} + + std::string m_name; + Maybe m_value; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_INTERNAL_PROPERTY_DESCRIPTOR_H diff --git a/plugins/devtools/bridge/inspector/protocol/location.cc b/plugins/devtools/bridge/inspector/protocol/location.cc new file mode 100644 index 0000000000..90fb4981d2 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/location.cc @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "location.h" + +namespace kraken::debugger { +std::unique_ptr Location::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new Location()); + errors->push(); + errors->setName("scriptId"); + if (value->HasMember("scriptId") && (*value)["scriptId"].IsString()) { + result->m_scriptId = (*value)["scriptId"].GetString(); + } else { + errors->addError("scriptId not found"); + } + errors->setName("lineNumber"); + if (value->HasMember("lineNumber") && (*value)["lineNumber"].IsInt()) { + result->m_lineNumber = (*value)["lineNumber"].GetInt(); + } else { + errors->addError("lineNumber not found"); + } + + if (value->HasMember("columnNumber")) { + errors->setName("columnNumber"); + if ((*value)["columnNumber"].IsInt()) { + result->m_columnNumber = (*value)["columnNumber"].GetInt(); + } else { + errors->addError("columnNumber should be int"); + } + } + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value Location::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value value = rapidjson::Value(rapidjson::kObjectType); + value.SetObject(); + value.AddMember("scriptId", m_scriptId, allocator); + value.AddMember("lineNumber", m_lineNumber, allocator); + if (m_columnNumber.isJust()) { + value.AddMember("columnNumber", m_columnNumber.fromJust(), allocator); + } + return value; +} +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/location.h b/plugins/devtools/bridge/inspector/protocol/location.h new file mode 100644 index 0000000000..f43cef15c7 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/location.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOCATION_H +#define KRAKEN_DEBUGGER_LOCATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include +#include +#include +#include + +namespace kraken::debugger { + +class Location { + KRAKEN_DISALLOW_COPY(Location); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~Location() {} + + std::string getScriptId() { + return m_scriptId; + } + + void setScriptId(const std::string &value) { + m_scriptId = value; + } + + int getLineNumber() { + return m_lineNumber; + } + + void setLineNumber(int value) { + m_lineNumber = value; + } + + bool hasColumnNumber() { + return m_columnNumber.isJust(); + } + + int getColumnNumber(int defaultValue) { + return m_columnNumber.isJust() ? m_columnNumber.fromJust() : defaultValue; + } + + void setColumnNumber(int value) { + m_columnNumber = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + // String serializeToJSON() override { return toValue()->serializeToJSON(); } + // std::vector serializeToBinary() override { return toValue()->serializeToBinary(); } + // String toJSON() const { return toValue()->toJSONString(); } + // std::unique_ptr clone() const; + + template class LocationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + LineNumberSet = 1 << 2, + AllFieldsSet = (ScriptIdSet | LineNumberSet | 0) + }; + + LocationBuilder &setScriptId(const std::string &value) { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + LocationBuilder &setLineNumber(int value) { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + LocationBuilder &setColumnNumber(int value) { + m_result->setColumnNumber(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Location; + + LocationBuilder() : m_result(new Location()) {} + + template LocationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static LocationBuilder<0> create() { + return LocationBuilder<0>(); + } + +private: + Location() { + m_lineNumber = 0; + } + + std::string m_scriptId; + int m_lineNumber; + Maybe m_columnNumber; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_LOCATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/log_backend.h b/plugins/devtools/bridge/inspector/protocol/log_backend.h new file mode 100644 index 0000000000..cf36692d07 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_backend.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOG_BACKEND_H +#define KRAKEN_DEBUGGER_LOG_BACKEND_H + +#include "inspector/protocol/dispatch_response.h" +#include "inspector/protocol/log_entry.h" + +namespace kraken { +namespace debugger { +class LogBackend { +public: + virtual ~LogBackend() {} + + virtual DispatchResponse clear() = 0; + + virtual DispatchResponse disable() = 0; + + virtual DispatchResponse enable() = 0; + + virtual void addMessageToConsole(std::unique_ptr entry) = 0; + // virtual DispatchResponse + // startViolationsReport(std::unique_ptr> in_config) = + // 0; virtual DispatchResponse stopViolationsReport() = 0; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_LOG_BACKEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.cc b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.cc new file mode 100644 index 0000000000..dd4ad7dc2c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "log_dispatcher_contract.h" +#include "inspector/protocol/log_dispatcher_impl.h" +namespace kraken { +namespace debugger { +void LogDispatcherContract::wire(kraken::debugger::UberDispatcher *uber, kraken::debugger::LogBackend *backend) { + std::unique_ptr dispatcher(new LogDispatcherImpl(uber->channel(), backend)); + uber->setupRedirects(dispatcher->redirects()); + uber->registerBackend("Log", std::move(dispatcher)); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.h b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.h new file mode 100644 index 0000000000..bef22e66c0 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_contract.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOG_DISPATCHER_CONTRACT_H +#define KRAKEN_DEBUGGER_LOG_DISPATCHER_CONTRACT_H + +#include "inspector/protocol/log_backend.h" +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken { +namespace debugger { +class LogDispatcherContract { +public: + static void wire(UberDispatcher *, LogBackend *); + +private: + LogDispatcherContract() {} +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_LOG_DISPATCHER_CONTRACT_H diff --git a/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.cc b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.cc new file mode 100644 index 0000000000..1e15f06d4c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "log_dispatcher_impl.h" + +namespace kraken { +namespace debugger { + +bool LogDispatcherImpl::canDispatch(const std::string &method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} + +void LogDispatcherImpl::dispatch(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message) { + std::unordered_map::iterator it = m_dispatchMap.find(method); + if (it == m_dispatchMap.end()) { + return; + } + ErrorSupport errors; + (it->second)(callId, method, std::move(message), &errors); +} + +///////// + +void LogDispatcherImpl::enable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void LogDispatcherImpl::disable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void LogDispatcherImpl::clear(uint64_t callId, const std::string &method, kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->clear(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void LogDispatcherImpl::startViolationsReport(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + // TODO +} + +void LogDispatcherImpl::stopViolationsReport(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, + kraken::debugger::ErrorSupport *) { + // TODO +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.h b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.h new file mode 100644 index 0000000000..c6c25f2eed --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_dispatcher_impl.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOG_DISPATCHER_IMPL_H +#define KRAKEN_DEBUGGER_LOG_DISPATCHER_IMPL_H + +#include "inspector/protocol/dispatcher_base.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/log_backend.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { +class LogDispatcherImpl : public DispatcherBase { +public: + LogDispatcherImpl(FrontendChannel *frontendChannel, LogBackend *backend) + : DispatcherBase(frontendChannel), m_backend(backend) { + m_dispatchMap["Log.clear"] = std::bind(&LogDispatcherImpl::clear, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Log.disable"] = std::bind(&LogDispatcherImpl::disable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Log.enable"] = std::bind(&LogDispatcherImpl::enable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + // m_dispatchMap["Log.startViolationsReport"] = + // std::bind(&LogDispatcherImpl::startViolationsReport,this, std::placeholders::_1, + // std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + // m_dispatchMap["Log.stopViolationsReport"] = + // std::bind( &LogDispatcherImpl::stopViolationsReport,this, std::placeholders::_1, + // std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + } + ~LogDispatcherImpl() override {} + bool canDispatch(const std::string &method) override; + void dispatch(uint64_t callId, const std::string &method, JSONObject message) override; + std::unordered_map &redirects() { + return m_redirects; + } + +protected: + using CallHandler = std::function; + using DispatchMap = std::unordered_map; + + DispatchMap m_dispatchMap; + std::unordered_map m_redirects; + + void clear(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void disable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void enable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void startViolationsReport(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void stopViolationsReport(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + + LogBackend *m_backend; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_LOG_DISPATCHER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/log_entry.cc b/plugins/devtools/bridge/inspector/protocol/log_entry.cc new file mode 100644 index 0000000000..e2220065ca --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_entry.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "log_entry.h" + +namespace kraken::debugger { +const char *LogEntry::SourceEnum::Xml = "xml"; +const char *LogEntry::SourceEnum::Javascript = "javascript"; +const char *LogEntry::SourceEnum::Network = "network"; +const char *LogEntry::SourceEnum::Storage = "storage"; +const char *LogEntry::SourceEnum::Appcache = "appcache"; +const char *LogEntry::SourceEnum::Rendering = "rendering"; +const char *LogEntry::SourceEnum::Security = "security"; +const char *LogEntry::SourceEnum::Deprecation = "deprecation"; +const char *LogEntry::SourceEnum::Worker = "worker"; +const char *LogEntry::SourceEnum::Violation = "violation"; +const char *LogEntry::SourceEnum::Intervention = "intervention"; +const char *LogEntry::SourceEnum::Recommendation = "recommendation"; +const char *LogEntry::SourceEnum::Other = "other"; + +const char *LogEntry::LevelEnum::Verbose = "verbose"; +const char *LogEntry::LevelEnum::Info = "info"; +const char *LogEntry::LevelEnum::Warning = "warning"; +const char *LogEntry::LevelEnum::Error = "error"; + +std::unique_ptr LogEntry::fromValue(rapidjson::Value *value, ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new LogEntry()); + errors->push(); + + if (value->HasMember("source") && (*value)["source"].IsString()) { + result->m_source = (*value)["source"].GetString(); + } else { + errors->setName("source"); + errors->addError("source not found"); + } + + if (value->HasMember("level") && (*value)["level"].IsString()) { + result->m_level = (*value)["level"].GetString(); + } else { + errors->setName("level"); + errors->addError("level not found"); + } + + if (value->HasMember("text") && (*value)["text"].IsString()) { + result->m_text = (*value)["text"].GetString(); + } else { + errors->setName("text"); + errors->addError("text not found"); + } + + if (value->HasMember("timestamp") && (*value)["timestamp"].IsDouble()) { + result->m_timestamp = (*value)["timestamp"].GetDouble(); + } else { + errors->setName("timestamp"); + errors->addError("timestamp not found"); + } + + if (value->HasMember("url")) { + errors->setName("url"); + if ((*value)["url"].IsString()) { + result->m_url = (*value)["url"].GetString(); + } else { + errors->addError("url should be string"); + } + } + + if (value->HasMember("lineNumber")) { + errors->setName("lineNumber"); + if ((*value)["lineNumber"].IsInt()) { + result->m_lineNumber = (*value)["lineNumber"].GetInt(); + } else { + errors->addError("lineNumber should be int"); + } + } + + if (value->HasMember("stackTrace")) { + errors->setName("stackTrace"); + if ((*value)["stackTrace"].IsObject()) { + rapidjson::Value _stack_trace = (*value)["stackTrace"].GetObject(); + result->m_stackTrace = StackTrace::fromValue(&_stack_trace, errors); + } else { + errors->addError("stackTrace should be object"); + } + } + + if (value->HasMember("networkRequestId")) { + errors->setName("networkRequestId"); + if ((*value)["networkRequestId"].IsString()) { + result->m_networkRequestId = (*value)["networkRequestId"].GetString(); + } else { + errors->addError("networkRequestId should be string"); + } + } + + if (value->HasMember("workerId")) { + errors->setName("workerId"); + if ((*value)["workerId"].IsString()) { + result->m_workerId = (*value)["workerId"].GetString(); + } else { + errors->addError("workerId should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value LogEntry::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + + result.AddMember("source", m_source, allocator); + result.AddMember("level", m_level, allocator); + result.AddMember("text", m_text, allocator); + result.AddMember("timestamp", m_timestamp, allocator); + if (m_url.isJust()) result.AddMember("url", m_url.fromJust(), allocator); + if (m_lineNumber.isJust()) result.AddMember("lineNumber", m_lineNumber.fromJust(), allocator); + if (m_stackTrace.isJust()) result.AddMember("stackTrace", m_stackTrace.fromJust()->toValue(allocator), allocator); + if (m_networkRequestId.isJust()) result.AddMember("networkRequestId", m_networkRequestId.fromJust(), allocator); + if (m_workerId.isJust()) result.AddMember("workerId", m_workerId.fromJust(), allocator); + return result; +} +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/protocol/log_entry.h b/plugins/devtools/bridge/inspector/protocol/log_entry.h new file mode 100644 index 0000000000..c713b2d3ab --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_entry.h @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOG_ENTRY_H +#define KRAKEN_DEBUGGER_LOG_ENTRY_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/stacktrace.h" +#include "kraken_foundation.h" + +#include + +namespace kraken::debugger { +class LogEntry { + KRAKEN_DISALLOW_COPY(LogEntry); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~LogEntry() {} + + struct SourceEnum { + static const char *Xml; + static const char *Javascript; + static const char *Network; + static const char *Storage; + static const char *Appcache; + static const char *Rendering; + static const char *Security; + static const char *Deprecation; + static const char *Worker; + static const char *Violation; + static const char *Intervention; + static const char *Recommendation; + static const char *Other; + }; // SourceEnum + + std::string getSource() { + return m_source; + } + + void setSource(const std::string &value) { + m_source = value; + } + + struct LevelEnum { + static const char *Verbose; + static const char *Info; + static const char *Warning; + static const char *Error; + }; // LevelEnum + + std::string getLevel() { + return m_level; + } + + void setLevel(const std::string &value) { + m_level = value; + } + + std::string getText() { + return m_text; + } + + void setText(const std::string &value) { + m_text = value; + } + + double getTimestamp() { + return m_timestamp; + } + + void setTimestamp(double value) { + m_timestamp = value; + } + + bool hasUrl() { + return m_url.isJust(); + } + + std::string getUrl(const std::string &defaultValue) { + return m_url.isJust() ? m_url.fromJust() : defaultValue; + } + + void setUrl(const std::string &value) { + m_url = value; + } + + bool hasLineNumber() { + return m_lineNumber.isJust(); + } + + int getLineNumber(int defaultValue) { + return m_lineNumber.isJust() ? m_lineNumber.fromJust() : defaultValue; + } + + void setLineNumber(int value) { + m_lineNumber = value; + } + + bool hasStackTrace() { + return m_stackTrace.isJust(); + } + + StackTrace *getStackTrace(StackTrace *defaultValue) { + return m_stackTrace.isJust() ? m_stackTrace.fromJust() : defaultValue; + } + + void setStackTrace(std::unique_ptr value) { + m_stackTrace = std::move(value); + } + + bool hasNetworkRequestId() { + return m_networkRequestId.isJust(); + } + + std::string getNetworkRequestId(const std::string &defaultValue) { + return m_networkRequestId.isJust() ? m_networkRequestId.fromJust() : defaultValue; + } + + void setNetworkRequestId(const std::string &value) { + m_networkRequestId = value; + } + + bool hasWorkerId() { + return m_workerId.isJust(); + } + + std::string getWorkerId(const std::string &defaultValue) { + return m_workerId.isJust() ? m_workerId.fromJust() : defaultValue; + } + + void setWorkerId(const std::string &value) { + m_workerId = value; + } + + // bool hasArgs() { return m_args.isJust(); } + // + // std::vector * + // getArgs(std::vector *defaultValue) { + // return m_args.isJust() ? m_args.fromJust() : defaultValue; + // } + // + // void setArgs(std::unique_ptr> value) { + // m_args = std::move(value); + // } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class LogEntryBuilder { + public: + enum { + NoFieldsSet = 0, + SourceSet = 1 << 1, + LevelSet = 1 << 2, + TextSet = 1 << 3, + TimestampSet = 1 << 4, + AllFieldsSet = (SourceSet | LevelSet | TextSet | TimestampSet | 0) + }; + + LogEntryBuilder &setSource(const std::string &value) { + static_assert(!(STATE & SourceSet), "property source should not be set yet"); + m_result->setSource(value); + return castState(); + } + + LogEntryBuilder &setLevel(const std::string &value) { + static_assert(!(STATE & LevelSet), "property level should not be set yet"); + m_result->setLevel(value); + return castState(); + } + + LogEntryBuilder &setText(const std::string &value) { + static_assert(!(STATE & TextSet), "property text should not be set yet"); + m_result->setText(value); + return castState(); + } + + LogEntryBuilder &setTimestamp(double value) { + static_assert(!(STATE & TimestampSet), "property timestamp should not be set yet"); + m_result->setTimestamp(value); + return castState(); + } + + LogEntryBuilder &setUrl(const std::string &value) { + m_result->setUrl(value); + return *this; + } + + LogEntryBuilder &setLineNumber(int value) { + m_result->setLineNumber(value); + return *this; + } + + LogEntryBuilder &setStackTrace(std::unique_ptr value) { + m_result->setStackTrace(std::move(value)); + return *this; + } + + LogEntryBuilder &setNetworkRequestId(const std::string &value) { + m_result->setNetworkRequestId(value); + return *this; + } + + LogEntryBuilder &setWorkerId(const std::string &value) { + m_result->setWorkerId(value); + return *this; + } + + // LogEntryBuilder & + // setArgs(std::unique_ptr> value) { + // m_result->setArgs(std::move(value)); + // return *this; + // } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class LogEntry; + + LogEntryBuilder() : m_result(new LogEntry()) {} + + template LogEntryBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static LogEntryBuilder<0> create() { + return LogEntryBuilder<0>(); + } + +private: + LogEntry() { + m_timestamp = 0; + } + + std::string m_source; + std::string m_level; + std::string m_text; + double m_timestamp; + Maybe m_url; + Maybe m_lineNumber; + Maybe m_stackTrace; + Maybe m_networkRequestId; + Maybe m_workerId; + // Maybe> m_args; +}; +} // namespace kraken::debugger + +#endif // KRAKEN_DEBUGGER_LOG_ENTRY_H diff --git a/plugins/devtools/bridge/inspector/protocol/log_frontend.cc b/plugins/devtools/bridge/inspector/protocol/log_frontend.cc new file mode 100644 index 0000000000..f46f4e6e89 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_frontend.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "log_frontend.h" +#include "inspector/protocol/entry_added_notification.h" + +namespace kraken { +namespace debugger { + +void LogFrontend::entryAdded(std::unique_ptr entry) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = + EntryAddedNotification::create().setEntry(std::move(entry)).build(); + rapidjson::Document doc; + m_frontendChannel->sendProtocolNotification({"Log.entryAdded", messageData->toValue(doc.GetAllocator())}); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/log_frontend.h b/plugins/devtools/bridge/inspector/protocol/log_frontend.h new file mode 100644 index 0000000000..413969d139 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/log_frontend.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_LOG_FRONTEND_H +#define KRAKEN_DEBUGGER_LOG_FRONTEND_H + +#include "inspector/protocol/frontend_channel.h" +#include "inspector/protocol/log_entry.h" +#include + +namespace kraken { +namespace debugger { +class LogFrontend { +public: + explicit LogFrontend(FrontendChannel *frontendChannel) : m_frontendChannel(frontendChannel) {} + + void entryAdded(std::unique_ptr entry); + +private: + FrontendChannel *m_frontendChannel; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_LOG_FRONTEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/maybe.h b/plugins/devtools/bridge/inspector/protocol/maybe.h new file mode 100644 index 0000000000..2243a12a05 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/maybe.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_MAYBE_H +#define KRAKEN_DEBUGGER_MAYBE_H + +#define IP_NOEXCEPT noexcept + +#include +#include + +namespace kraken { +namespace debugger { +template class Maybe { +public: + Maybe() : m_value() {} + Maybe(std::unique_ptr value) : m_value(std::move(value)) {} + Maybe(Maybe &&other) IP_NOEXCEPT : m_value(std::move(other.m_value)) {} + + void operator=(std::unique_ptr value) { + m_value = std::move(value); + } + T *fromJust() const { + return m_value.get(); + } + T *fromMaybe(T *defaultValue) const { + return m_value ? m_value.get() : defaultValue; + } + bool isJust() const { + return !!m_value; + } + std::unique_ptr takeJust() { + return std::move(m_value); + } + +private: + std::unique_ptr m_value; +}; + +template class MaybeBase { +public: + MaybeBase() : m_isJust(false) {} + MaybeBase(T value) : m_isJust(true), m_value(value) {} + MaybeBase(MaybeBase &&other) IP_NOEXCEPT : m_isJust(other.m_isJust), m_value(std::move(other.m_value)) {} + void operator=(T value) { + m_value = value; + m_isJust = true; + } + T fromJust() const { + return m_value; + } + T fromMaybe(const T &defaultValue) const { + return m_isJust ? m_value : defaultValue; + } + bool isJust() const { + return m_isJust; + } + T takeJust() { + return m_value; + } + +protected: + bool m_isJust; + T m_value; +}; + +template <> class Maybe : public MaybeBase { +public: + Maybe() { + m_value = false; + } + Maybe(bool value) : MaybeBase(value) {} + Maybe(Maybe &&other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; + +template <> class Maybe : public MaybeBase { +public: + Maybe() { + m_value = 0; + } + Maybe(int value) : MaybeBase(value) {} + Maybe(Maybe &&other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; + +template <> class Maybe : public MaybeBase { +public: + Maybe() { + m_value = 0; + } + Maybe(double value) : MaybeBase(value) {} + Maybe(Maybe &&other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; + +template <> class Maybe : public MaybeBase { +public: + Maybe() {} + Maybe(const std::string &value) : MaybeBase(value) {} + Maybe(Maybe &&other) IP_NOEXCEPT : MaybeBase(std::move(other)) {} + using MaybeBase::operator=; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_MAYBE_H diff --git a/plugins/devtools/bridge/inspector/protocol/object_preview.cc b/plugins/devtools/bridge/inspector/protocol/object_preview.cc new file mode 100644 index 0000000000..07d397be92 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/object_preview.cc @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "object_preview.h" + +namespace kraken { +namespace debugger { + +const char *ObjectPreview::TypeEnum::Object = "object"; +const char *ObjectPreview::TypeEnum::Function = "function"; +const char *ObjectPreview::TypeEnum::Undefined = "undefined"; +const char *ObjectPreview::TypeEnum::String = "string"; +const char *ObjectPreview::TypeEnum::Number = "number"; +const char *ObjectPreview::TypeEnum::Boolean = "boolean"; +const char *ObjectPreview::TypeEnum::Symbol = "symbol"; +const char *ObjectPreview::TypeEnum::Bigint = "bigint"; + +const char *ObjectPreview::SubtypeEnum::Array = "array"; +const char *ObjectPreview::SubtypeEnum::Null = "null"; +const char *ObjectPreview::SubtypeEnum::Node = "node"; +const char *ObjectPreview::SubtypeEnum::Regexp = "regexp"; +const char *ObjectPreview::SubtypeEnum::Date = "date"; +const char *ObjectPreview::SubtypeEnum::Map = "map"; +const char *ObjectPreview::SubtypeEnum::Set = "set"; +const char *ObjectPreview::SubtypeEnum::Weakmap = "weakmap"; +const char *ObjectPreview::SubtypeEnum::Weakset = "weakset"; +const char *ObjectPreview::SubtypeEnum::Iterator = "iterator"; +const char *ObjectPreview::SubtypeEnum::Generator = "generator"; +const char *ObjectPreview::SubtypeEnum::Error = "error"; + +std::unique_ptr ObjectPreview::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ObjectPreview()); + errors->push(); + + if (value->HasMember("type") && (*value)["type"].IsString()) { + result->m_type = (*value)["type"].GetString(); + } else { + errors->setName("type"); + errors->addError("type not found"); + } + + if (value->HasMember("subtype")) { + errors->setName("subtype"); + if ((*value)["subtype"].IsString()) { + result->m_subtype = (*value)["subtype"].GetString(); + } else { + errors->addError("subtype not found"); + } + } + + if (value->HasMember("description")) { + errors->setName("description"); + if ((*value)["description"].IsString()) { + result->m_description = (*value)["description"].GetString(); + } else { + errors->addError("description not found"); + } + } + + if (value->HasMember("overflow")) { + errors->setName("overflow"); + if ((*value)["overflow"].IsBool()) { + result->m_overflow = (*value)["overflow"].GetBool(); + } else { + errors->addError("overflow should be bool"); + } + } + + if (value->HasMember("properties") && (*value)["properties"].IsArray()) { + auto prop_preview_arr = std::make_unique>>(); + for (auto &v : (*value)["properties"].GetArray()) { + if (v.IsObject()) { + rapidjson::Value _property_preview = rapidjson::Value(v.GetObject()); + prop_preview_arr->push_back(PropertyPreview::fromValue(&_property_preview, errors)); + } + } + result->m_properties = std::move(prop_preview_arr); + } + + if (value->HasMember("entries")) { + errors->setName("entries"); + if ((*value)["entries"].IsArray()) { + auto entry_preview_arr = std::make_unique>>(); + for (auto &v : (*value)["entries"].GetArray()) { + if (v.IsObject()) { + rapidjson::Value _entry_preview = rapidjson::Value(v.GetObject()); + entry_preview_arr->push_back(EntryPreview::fromValue(&_entry_preview, errors)); + } + } + result->m_entries = std::move(entry_preview_arr); + } else { + errors->addError("entries not found"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ObjectPreview::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + + result.AddMember("type", m_type, allocator); + if (m_subtype.isJust()) result.AddMember("subtype", m_subtype.fromJust(), allocator); + if (m_description.isJust()) result.AddMember("description", m_description.fromJust(), allocator); + result.AddMember("overflow", m_overflow, allocator); + + rapidjson::Value _properties = rapidjson::Value(rapidjson::kArrayType); + _properties.SetArray(); + + if (m_properties) { + for (const auto &val : *m_properties.get()) { + _properties.PushBack(val->toValue(allocator), allocator); + } + result.AddMember("properties", _properties, allocator); + } + + if (m_entries.isJust()) { + rapidjson::Value _entries = rapidjson::Value(rapidjson::kArrayType); + _entries.SetArray(); + + for (const auto &val : *m_entries.fromJust()) { + _entries.PushBack(val->toValue(allocator), allocator); + } + result.AddMember("entries", _entries, allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/object_preview.h b/plugins/devtools/bridge/inspector/protocol/object_preview.h new file mode 100644 index 0000000000..b37467433c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/object_preview.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_OBJECT_PREVIEW_H +#define KRAKEN_DEBUGGER_OBJECT_PREVIEW_H + +#include +#include + +#include "inspector/protocol/entry_preview.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/property_preview.h" +#include "kraken_foundation.h" +#include + +namespace kraken { +namespace debugger { + +class ObjectPreview { + KRAKEN_DISALLOW_COPY(ObjectPreview); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ObjectPreview() {} + + struct TypeEnum { + static const char *Object; + static const char *Function; + static const char *Undefined; + static const char *String; + static const char *Number; + static const char *Boolean; + static const char *Symbol; + static const char *Bigint; + }; // TypeEnum + + std::string getType() { + return m_type; + } + + void setType(const std::string &value) { + m_type = value; + } + + struct SubtypeEnum { + static const char *Array; + static const char *Null; + static const char *Node; + static const char *Regexp; + static const char *Date; + static const char *Map; + static const char *Set; + static const char *Weakmap; + static const char *Weakset; + static const char *Iterator; + static const char *Generator; + static const char *Error; + }; // SubtypeEnum + + bool hasSubtype() { + return m_subtype.isJust(); + } + + std::string getSubtype(const std::string &defaultValue) { + return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; + } + + void setSubtype(const std::string &value) { + m_subtype = value; + } + + bool hasDescription() { + return m_description.isJust(); + } + + std::string getDescription(const std::string &defaultValue) { + return m_description.isJust() ? m_description.fromJust() : defaultValue; + } + + void setDescription(const std::string &value) { + m_description = value; + } + + bool getOverflow() { + return m_overflow; + } + + void setOverflow(bool value) { + m_overflow = value; + } + + std::vector> *getProperties() { + return m_properties.get(); + } + + void setProperties(std::unique_ptr>> value) { + m_properties = std::move(value); + } + + bool hasEntries() { + return m_entries.isJust(); + } + + std::vector> *getEntries(std::vector> *defaultValue) { + return m_entries.isJust() ? m_entries.fromJust() : defaultValue; + } + + void setEntries(std::unique_ptr>> value) { + m_entries = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ObjectPreviewBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + OverflowSet = 1 << 2, + PropertiesSet = 1 << 3, + AllFieldsSet = (TypeSet | OverflowSet | PropertiesSet | 0) + }; + + ObjectPreviewBuilder &setType(const std::string &value) { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + ObjectPreviewBuilder &setSubtype(const std::string &value) { + m_result->setSubtype(value); + return *this; + } + + ObjectPreviewBuilder &setDescription(const std::string &value) { + m_result->setDescription(value); + return *this; + } + + ObjectPreviewBuilder &setOverflow(bool value) { + static_assert(!(STATE & OverflowSet), "property overflow should not be set yet"); + m_result->setOverflow(value); + return castState(); + } + + ObjectPreviewBuilder & + setProperties(std::unique_ptr>> value) { + static_assert(!(STATE & PropertiesSet), "property properties should not be set yet"); + m_result->setProperties(std::move(value)); + return castState(); + } + + ObjectPreviewBuilder &setEntries(std::unique_ptr>> value) { + m_result->setEntries(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ObjectPreview; + + ObjectPreviewBuilder() : m_result(new ObjectPreview()) {} + + template ObjectPreviewBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ObjectPreviewBuilder<0> create() { + return ObjectPreviewBuilder<0>(); + } + +private: + ObjectPreview() { + m_overflow = false; + } + + std::string m_type; + Maybe m_subtype; + Maybe m_description; + bool m_overflow; + std::unique_ptr>> m_properties; + Maybe>> m_entries; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_OBJECT_PREVIEW_H diff --git a/plugins/devtools/bridge/inspector/protocol/page_backend.h b/plugins/devtools/bridge/inspector/protocol/page_backend.h new file mode 100644 index 0000000000..3ea3441265 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/page_backend.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PAGE_BACKEND_H +#define KRAKEN_DEBUGGER_PAGE_BACKEND_H + +#include "inspector/protocol/dispatch_response.h" +#include "inspector/protocol/maybe.h" +#include + +namespace kraken { +namespace debugger { +class PageBackend { +public: + virtual ~PageBackend() {} + + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable() = 0; + + virtual DispatchResponse reload(Maybe in_ignoreCache, Maybe in_scriptToEvaluateOnLoad) = 0; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PAGE_BACKEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.cc b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.cc new file mode 100644 index 0000000000..b41b2d3813 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "page_dispatcher_contract.h" +#include "inspector/protocol/page_dispatcher_impl.h" + +namespace kraken { +namespace debugger { +void PageDispatcherContract::wire(kraken::debugger::UberDispatcher *uber, kraken::debugger::PageBackend *backend) { + std::unique_ptr dispatcher(new PageDispatcherImpl(uber->channel(), backend)); + uber->setupRedirects(dispatcher->redirects()); + uber->registerBackend("Page", std::move(dispatcher)); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.h b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.h new file mode 100644 index 0000000000..e383721f0c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_contract.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PAGE_DISPATCHER_CONTRACT_H +#define KRAKEN_DEBUGGER_PAGE_DISPATCHER_CONTRACT_H + +#include "inspector/protocol/page_backend.h" +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken { +namespace debugger { +class PageDispatcherContract { +public: + static void wire(UberDispatcher *, PageBackend *); + +private: + PageDispatcherContract() {} +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PAGE_DISPATCHER_CONTRACT_H diff --git a/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.cc b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.cc new file mode 100644 index 0000000000..f0346bfb18 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.cc @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "page_dispatcher_impl.h" + +namespace kraken { +namespace debugger { +bool PageDispatcherImpl::canDispatch(const std::string &method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} + +void PageDispatcherImpl::dispatch(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message) { + std::unordered_map::iterator it = m_dispatchMap.find(method); + if (it == m_dispatchMap.end()) { + return; + } + ErrorSupport errors; + (it->second)(callId, method, std::move(message), &errors); +} + +//-------------------------------------- + +void PageDispatcherImpl::enable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void PageDispatcherImpl::disable(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, kraken::debugger::ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void PageDispatcherImpl::reload(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, kraken::debugger::ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + Maybe in_ignoreCache; + if (message.HasMember("ignoreCache")) { + errors->setName("ignoreCache"); + if (message["ignoreCache"].IsBool()) { + in_ignoreCache = message["ignoreCache"].GetBool(); + } else { + errors->addError("ignoreCache should be bool"); + } + } + + Maybe in_scriptToEvaluateOnLoad; + if (message.HasMember("scriptToEvaluateOnLoad")) { + errors->setName("scriptToEvaluateOnLoad"); + if (message["scriptToEvaluateOnLoad"].IsString()) { + in_scriptToEvaluateOnLoad = message["scriptToEvaluateOnLoad"].GetString(); + } else { + errors->addError("scriptToEvaluateOnLoad should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->reload(std::move(in_ignoreCache), std::move(in_scriptToEvaluateOnLoad)); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.h b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.h new file mode 100644 index 0000000000..e5dd7cb1f3 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/page_dispatcher_impl.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_DISPATCHER_IMPL_H +#define KRAKEN_DEBUGGER_DISPATCHER_IMPL_H + +#include "inspector/protocol/dispatcher_base.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/page_backend.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { +class PageDispatcherImpl : public DispatcherBase { +public: + PageDispatcherImpl(FrontendChannel *frontendChannel, PageBackend *backend) + : DispatcherBase(frontendChannel), m_backend(backend) { + m_dispatchMap["Page.disable"] = std::bind(&PageDispatcherImpl::disable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Page.enable"] = std::bind(&PageDispatcherImpl::enable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Page.reload"] = std::bind(&PageDispatcherImpl::reload, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + } + ~PageDispatcherImpl() override {} + bool canDispatch(const std::string &method) override; + void dispatch(uint64_t callId, const std::string &method, JSONObject message) override; + std::unordered_map &redirects() { + return m_redirects; + } + +protected: + using CallHandler = std::function; + using DispatchMap = std::unordered_map; + + DispatchMap m_dispatchMap; + std::unordered_map m_redirects; + PageBackend *m_backend; + + void disable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void enable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void reload(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_DISPATCHER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/paused_notification.cc b/plugins/devtools/bridge/inspector/protocol/paused_notification.cc new file mode 100644 index 0000000000..1ddbd69004 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/paused_notification.cc @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "paused_notification.h" + +namespace kraken { +namespace debugger { + +const char *PausedNotification::ReasonEnum::XHR = "XHR"; +const char *PausedNotification::ReasonEnum::DOM = "DOM"; +const char *PausedNotification::ReasonEnum::EventListener = "EventListener"; +const char *PausedNotification::ReasonEnum::Exception = "exception"; +const char *PausedNotification::ReasonEnum::Assert = "assert"; +const char *PausedNotification::ReasonEnum::DebugCommand = "debugCommand"; +const char *PausedNotification::ReasonEnum::PromiseRejection = "promiseRejection"; +const char *PausedNotification::ReasonEnum::OOM = "OOM"; +const char *PausedNotification::ReasonEnum::Other = "other"; +const char *PausedNotification::ReasonEnum::Ambiguous = "ambiguous"; + +std::unique_ptr PausedNotification::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new PausedNotification()); + errors->push(); + + if (value->HasMember("callFrames") && (*value)["callFrames"].IsArray()) { + auto arr = std::make_unique>>(); + for (auto &v : (*value)["callFrames"].GetArray()) { + if (v.IsObject()) { + rapidjson::Value _scope = rapidjson::Value(v.GetObject()); + arr->push_back(CallFrame::fromValue(&_scope, errors)); + } + } + result->m_callFrames = std::move(arr); + } else { + errors->setName("callFrames"); + errors->addError("callFrames not found"); + } + + if (value->HasMember("reason") && (*value)["reason"].IsString()) { + result->m_reason = (*value)["reason"].GetString(); + } else { + errors->setName("reason"); + errors->addError("reason not found"); + } + + if (value->HasMember("data")) { + result->m_data = std::make_unique((*value)["data"], result->m_holder.GetAllocator()); + } + + if (value->HasMember("hitBreakpoints")) { + errors->setName("hitBreakpoints"); + if ((*value)["hitBreakpoints"].IsArray()) { + auto arr = std::make_unique>(); + for (auto &v : (*value)["hitBreakpoints"].GetArray()) { + if (v.IsString()) { + arr->push_back(v.GetString()); + } + } + result->m_hitBreakpoints = std::move(arr); + } else { + errors->addError("hitBreakpoints should be object"); + } + } + + if (value->HasMember("asyncStackTrace")) { + errors->setName("asyncStackTrace"); + if ((*value)["asyncStackTrace"].IsObject()) { + rapidjson::Value _async_stack_trace = rapidjson::Value((*value)["asyncStackTrace"].GetObject()); + result->m_asyncStackTrace = StackTrace::fromValue(&_async_stack_trace, errors); + } else { + errors->addError("asyncStackTrace should be object"); + } + } + + if (value->HasMember("asyncStackTraceId")) { + errors->setName("asyncStackTraceId"); + if ((*value)["asyncStackTraceId"].IsObject()) { + rapidjson::Value _async_stack_trace_id = rapidjson::Value((*value)["asyncStackTraceId"].GetObject()); + result->m_asyncStackTraceId = StackTraceId::fromValue(&_async_stack_trace_id, errors); + } else { + errors->addError("asyncStackTraceId should be object"); + } + } + + if (value->HasMember("asyncCallStackTraceId")) { + errors->setName("asyncCallStackTraceId"); + if ((*value)["asyncCallStackTraceId"].IsObject()) { + rapidjson::Value _asyncCallStackTraceId = rapidjson::Value((*value)["asyncCallStackTraceId"].GetObject()); + result->m_asyncCallStackTraceId = StackTraceId::fromValue(&_asyncCallStackTraceId, errors); + } else { + errors->addError("asyncCallStackTraceId should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value PausedNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + rapidjson::Value arr = rapidjson::Value(rapidjson::kArrayType); + arr.SetArray(); + + for (const auto &val : *m_callFrames.get()) { + arr.PushBack(val->toValue(allocator), allocator); + } + result.AddMember("callFrames", arr, allocator); + + result.AddMember("reason", m_reason, allocator); + if (m_data.isJust()) { + result.AddMember("data", *m_data.fromJust(), allocator); + } + if (m_hitBreakpoints.isJust()) { + rapidjson::Value hit_points = rapidjson::Value(rapidjson::kArrayType); + hit_points.SetArray(); + for (const auto &val : *m_hitBreakpoints.fromJust()) { + hit_points.PushBack(rapidjson::Value().SetString(val.c_str(), allocator), allocator); + } + result.AddMember("hitBreakpoints", hit_points, allocator); + } + if (m_asyncStackTrace.isJust()) { + result.AddMember("asyncStackTrace", m_asyncStackTrace.fromJust()->toValue(allocator), allocator); + } + if (m_asyncStackTraceId.isJust()) { + result.AddMember("asyncStackTraceId", m_asyncStackTraceId.fromJust()->toValue(allocator), allocator); + } + if (m_asyncCallStackTraceId.isJust()) { + result.AddMember("asyncCallStackTraceId", m_asyncCallStackTraceId.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/paused_notification.h b/plugins/devtools/bridge/inspector/protocol/paused_notification.h new file mode 100644 index 0000000000..8af4f836db --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/paused_notification.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PAUSED_NOTIFICATION_H +#define KRAKEN_DEBUGGER_PAUSED_NOTIFICATION_H + +#include "inspector/protocol/call_frame.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/stacktrace.h" +#include "inspector/protocol/stacktrace_id.h" +#include +#include +#include + +namespace kraken { +namespace debugger { +class PausedNotification { + KRAKEN_DISALLOW_COPY(PausedNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~PausedNotification() {} + + std::vector> *getCallFrames() { + return m_callFrames.get(); + } + void setCallFrames(std::unique_ptr>> value) { + m_callFrames = std::move(value); + } + + struct ReasonEnum { + static const char *XHR; + static const char *DOM; + static const char *EventListener; + static const char *Exception; + static const char *Assert; + static const char *DebugCommand; + static const char *PromiseRejection; + static const char *OOM; + static const char *Other; + static const char *Ambiguous; + }; // ReasonEnum + + std::string getReason() { + return m_reason; + } + void setReason(const std::string &value) { + m_reason = value; + } + + bool hasData() { + return m_data.isJust(); + } + rapidjson::Value *getData(rapidjson::Value *defaultValue) { + return m_data.isJust() ? m_data.fromJust() : defaultValue; + } + void setData(std::unique_ptr value) { + m_data = std::move(value); + } + + bool hasHitBreakpoints() { + return m_hitBreakpoints.isJust(); + } + std::vector *getHitBreakpoints(std::vector *defaultValue) { + return m_hitBreakpoints.isJust() ? m_hitBreakpoints.fromJust() : defaultValue; + } + void setHitBreakpoints(std::unique_ptr> value) { + m_hitBreakpoints = std::move(value); + } + + bool hasAsyncStackTrace() { + return m_asyncStackTrace.isJust(); + } + StackTrace *getAsyncStackTrace(StackTrace *defaultValue) { + return m_asyncStackTrace.isJust() ? m_asyncStackTrace.fromJust() : defaultValue; + } + void setAsyncStackTrace(std::unique_ptr value) { + m_asyncStackTrace = std::move(value); + } + + bool hasAsyncStackTraceId() { + return m_asyncStackTraceId.isJust(); + } + StackTraceId *getAsyncStackTraceId(StackTraceId *defaultValue) { + return m_asyncStackTraceId.isJust() ? m_asyncStackTraceId.fromJust() : defaultValue; + } + void setAsyncStackTraceId(std::unique_ptr value) { + m_asyncStackTraceId = std::move(value); + } + + bool hasAsyncCallStackTraceId() { + return m_asyncCallStackTraceId.isJust(); + } + StackTraceId *getAsyncCallStackTraceId(StackTraceId *defaultValue) { + return m_asyncCallStackTraceId.isJust() ? m_asyncCallStackTraceId.fromJust() : defaultValue; + } + void setAsyncCallStackTraceId(std::unique_ptr value) { + m_asyncCallStackTraceId = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class PausedNotificationBuilder { + public: + enum { + NoFieldsSet = 0, + CallFramesSet = 1 << 1, + ReasonSet = 1 << 2, + AllFieldsSet = (CallFramesSet | ReasonSet | 0) + }; + + PausedNotificationBuilder & + setCallFrames(std::unique_ptr>> value) { + static_assert(!(STATE & CallFramesSet), "property callFrames should not be set yet"); + m_result->setCallFrames(std::move(value)); + return castState(); + } + + PausedNotificationBuilder &setReason(const std::string &value) { + static_assert(!(STATE & ReasonSet), "property reason should not be set yet"); + m_result->setReason(value); + return castState(); + } + + PausedNotificationBuilder &setData(std::unique_ptr value) { + m_result->setData(std::move(value)); + return *this; + } + + PausedNotificationBuilder &setHitBreakpoints(std::unique_ptr> value) { + m_result->setHitBreakpoints(std::move(value)); + return *this; + } + + PausedNotificationBuilder &setAsyncStackTrace(std::unique_ptr value) { + m_result->setAsyncStackTrace(std::move(value)); + return *this; + } + + PausedNotificationBuilder &setAsyncStackTraceId(std::unique_ptr value) { + m_result->setAsyncStackTraceId(std::move(value)); + return *this; + } + + PausedNotificationBuilder &setAsyncCallStackTraceId(std::unique_ptr value) { + m_result->setAsyncCallStackTraceId(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PausedNotification; + PausedNotificationBuilder() : m_result(new PausedNotification()) {} + + template PausedNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static PausedNotificationBuilder<0> create() { + return PausedNotificationBuilder<0>(); + } + +private: + PausedNotification() {} + + std::unique_ptr>> m_callFrames; + std::string m_reason; + Maybe m_data; + Maybe> m_hitBreakpoints; + Maybe m_asyncStackTrace; + Maybe m_asyncStackTraceId; + Maybe m_asyncCallStackTraceId; + rapidjson::Document m_holder; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PAUSED_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.cc b/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.cc new file mode 100644 index 0000000000..3c513635f5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.cc @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "private_property_descriptor.h" + +namespace kraken { +namespace debugger { +std::unique_ptr +PrivatePropertyDescriptor::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new PrivatePropertyDescriptor()); + errors->push(); + + if (value->HasMember("name") && (*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->setName("name"); + errors->addError("name not found"); + } + + if (value->HasMember("value") && (*value)["value"].IsObject()) { + rapidjson::Value _value = (*value)["value"].GetObject(); + result->m_value = RemoteObject::fromValue(&_value, errors); + } else { + errors->setName("value"); + errors->addError("value not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value PrivatePropertyDescriptor::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("name", m_name, allocator); + result.AddMember("value", m_value->toValue(allocator), allocator); + return result; +} +} // namespace debugger +}; // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.h b/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.h new file mode 100644 index 0000000000..7ca9e00d31 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/private_property_descriptor.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PRIVATE_PROPERTY_DESCRIPTOR_H +#define KRAKEN_DEBUGGER_PRIVATE_PROPERTY_DESCRIPTOR_H + +#include "inspector/protocol/remote_object.h" +#include "kraken_foundation.h" +#include + +namespace kraken { +namespace debugger { +class PrivatePropertyDescriptor { + KRAKEN_DISALLOW_COPY(PrivatePropertyDescriptor); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~PrivatePropertyDescriptor() {} + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + RemoteObject *getValue() { + return m_value.get(); + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class PrivatePropertyDescriptorBuilder { + public: + enum { NoFieldsSet = 0, NameSet = 1 << 1, ValueSet = 1 << 2, AllFieldsSet = (NameSet | ValueSet | 0) }; + + PrivatePropertyDescriptorBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PrivatePropertyDescriptorBuilder &setValue(std::unique_ptr value) { + static_assert(!(STATE & ValueSet), "property value should not be set yet"); + m_result->setValue(std::move(value)); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PrivatePropertyDescriptor; + + PrivatePropertyDescriptorBuilder() : m_result(new PrivatePropertyDescriptor()) {} + + template PrivatePropertyDescriptorBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static PrivatePropertyDescriptorBuilder<0> create() { + return PrivatePropertyDescriptorBuilder<0>(); + } + +private: + PrivatePropertyDescriptor() {} + + std::string m_name; + std::unique_ptr m_value; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PRIVATE_PROPERTY_DESCRIPTOR_H diff --git a/plugins/devtools/bridge/inspector/protocol/property_descriptor.cc b/plugins/devtools/bridge/inspector/protocol/property_descriptor.cc new file mode 100644 index 0000000000..87db9403b5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/property_descriptor.cc @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "property_descriptor.h" + +namespace kraken { +namespace debugger { +std::unique_ptr PropertyDescriptor::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new PropertyDescriptor()); + errors->push(); + + if (value->HasMember("name") && (*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->setName("name"); + errors->addError("name not found"); + } + + if (value->HasMember("value")) { + errors->setName("value"); + if ((*value)["value"].IsObject()) { + rapidjson::Value _value = (*value)["value"].GetObject(); + result->m_value = RemoteObject::fromValue(&_value, errors); + } else { + errors->addError("value should be object"); + } + } + + if (value->HasMember("writable")) { + errors->setName("writable"); + if ((*value)["writable"].IsBool()) { + result->m_writable = (*value)["writable"].GetBool(); + } else { + errors->addError("writable should be bool"); + } + } + + if (value->HasMember("get")) { + errors->setName("get"); + if ((*value)["get"].IsObject()) { + rapidjson::Value _get = (*value)["get"].GetObject(); + result->m_get = RemoteObject::fromValue(&_get, errors); + } else { + errors->addError("get should be object"); + } + } + + if (value->HasMember("set")) { + errors->setName("set"); + if ((*value)["set"].IsObject()) { + rapidjson::Value _set = (*value)["set"].GetObject(); + result->m_set = RemoteObject::fromValue(&_set, errors); + } else { + errors->addError("set should be object"); + } + } + + if (value->HasMember("configurable") && (*value)["configurable"].IsBool()) { + result->m_configurable = (*value)["configurable"].GetBool(); + } else { + errors->setName("configurable"); + errors->addError("configurable not found"); + } + + if (value->HasMember("enumerable") && (*value)["enumerable"].IsBool()) { + result->m_enumerable = (*value)["enumerable"].GetBool(); + } else { + errors->setName("enumerable"); + errors->addError("enumerable not found"); + } + + if (value->HasMember("wasThrown")) { + errors->setName("wasThrown"); + if ((*value)["wasThrown"].IsBool()) { + result->m_wasThrown = (*value)["wasThrown"].GetBool(); + } else { + errors->addError("wasThrown should be bool"); + } + } + + if (value->HasMember("isOwn")) { + errors->setName("isOwn"); + if ((*value)["isOwn"].IsBool()) { + result->m_isOwn = (*value)["isOwn"].GetBool(); + } else { + errors->addError("isOwn should be bool"); + } + } + + if (value->HasMember("symbol")) { + errors->setName("symbol"); + if ((*value)["symbol"].IsObject()) { + rapidjson::Value _symbol = (*value)["symbol"].GetObject(); + result->m_symbol = RemoteObject::fromValue(&_symbol, errors); + } else { + errors->addError("symbol should be object"); + } + } + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value PropertyDescriptor::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("name", m_name, allocator); + if (m_value.isJust()) result.AddMember("value", m_value.fromJust()->toValue(allocator), allocator); + if (m_writable.isJust()) result.AddMember("writable", m_writable.fromJust(), allocator); + if (m_get.isJust()) result.AddMember("get", m_get.fromJust()->toValue(allocator), allocator); + if (m_set.isJust()) result.AddMember("set", m_set.fromJust()->toValue(allocator), allocator); + result.AddMember("configurable", m_configurable, allocator); + result.AddMember("enumerable", m_enumerable, allocator); + if (m_wasThrown.isJust()) result.AddMember("wasThrown", m_wasThrown.fromJust(), allocator); + if (m_isOwn.isJust()) result.AddMember("isOwn", m_isOwn.fromJust(), allocator); + if (m_symbol.isJust()) result.AddMember("symbol", m_symbol.fromJust()->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/property_descriptor.h b/plugins/devtools/bridge/inspector/protocol/property_descriptor.h new file mode 100644 index 0000000000..459538109c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/property_descriptor.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PROPERTY_DESCRIPTOR_H +#define KRAKEN_DEBUGGER_PROPERTY_DESCRIPTOR_H + +#include "inspector/protocol/remote_object.h" +#include "kraken_foundation.h" +#include +#include + +namespace kraken { +namespace debugger { +class PropertyDescriptor { + KRAKEN_DISALLOW_COPY(PropertyDescriptor); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~PropertyDescriptor() {} + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + bool hasValue() { + return m_value.isJust(); + } + + RemoteObject *getValue(RemoteObject *defaultValue) { + return m_value.isJust() ? m_value.fromJust() : defaultValue; + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + bool hasWritable() { + return m_writable.isJust(); + } + + bool getWritable(bool defaultValue) { + return m_writable.isJust() ? m_writable.fromJust() : defaultValue; + } + + void setWritable(bool value) { + m_writable = value; + } + + bool hasGet() { + return m_get.isJust(); + } + + RemoteObject *getGet(RemoteObject *defaultValue) { + return m_get.isJust() ? m_get.fromJust() : defaultValue; + } + + void setGet(std::unique_ptr value) { + m_get = std::move(value); + } + + bool hasSet() { + return m_set.isJust(); + } + + RemoteObject *getSet(RemoteObject *defaultValue) { + return m_set.isJust() ? m_set.fromJust() : defaultValue; + } + + void setSet(std::unique_ptr value) { + m_set = std::move(value); + } + + bool getConfigurable() { + return m_configurable; + } + + void setConfigurable(bool value) { + m_configurable = value; + } + + bool getEnumerable() { + return m_enumerable; + } + + void setEnumerable(bool value) { + m_enumerable = value; + } + + bool hasWasThrown() { + return m_wasThrown.isJust(); + } + + bool getWasThrown(bool defaultValue) { + return m_wasThrown.isJust() ? m_wasThrown.fromJust() : defaultValue; + } + + void setWasThrown(bool value) { + m_wasThrown = value; + } + + bool hasIsOwn() { + return m_isOwn.isJust(); + } + + bool getIsOwn(bool defaultValue) { + return m_isOwn.isJust() ? m_isOwn.fromJust() : defaultValue; + } + + void setIsOwn(bool value) { + m_isOwn = value; + } + + bool hasSymbol() { + return m_symbol.isJust(); + } + + RemoteObject *getSymbol(RemoteObject *defaultValue) { + return m_symbol.isJust() ? m_symbol.fromJust() : defaultValue; + } + + void setSymbol(std::unique_ptr value) { + m_symbol = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class PropertyDescriptorBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + ConfigurableSet = 1 << 2, + EnumerableSet = 1 << 3, + AllFieldsSet = (NameSet | ConfigurableSet | EnumerableSet | 0) + }; + + PropertyDescriptorBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PropertyDescriptorBuilder &setValue(std::unique_ptr value) { + m_result->setValue(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder &setWritable(bool value) { + m_result->setWritable(value); + return *this; + } + + PropertyDescriptorBuilder &setGet(std::unique_ptr value) { + m_result->setGet(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder &setSet(std::unique_ptr value) { + m_result->setSet(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder &setConfigurable(bool value) { + static_assert(!(STATE & ConfigurableSet), "property configurable should not be set yet"); + m_result->setConfigurable(value); + return castState(); + } + + PropertyDescriptorBuilder &setEnumerable(bool value) { + static_assert(!(STATE & EnumerableSet), "property enumerable should not be set yet"); + m_result->setEnumerable(value); + return castState(); + } + + PropertyDescriptorBuilder &setWasThrown(bool value) { + m_result->setWasThrown(value); + return *this; + } + + PropertyDescriptorBuilder &setIsOwn(bool value) { + m_result->setIsOwn(value); + return *this; + } + + PropertyDescriptorBuilder &setSymbol(std::unique_ptr value) { + m_result->setSymbol(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PropertyDescriptor; + + PropertyDescriptorBuilder() : m_result(new PropertyDescriptor()) {} + + template PropertyDescriptorBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static PropertyDescriptorBuilder<0> create() { + return PropertyDescriptorBuilder<0>(); + } + +private: + PropertyDescriptor() { + m_configurable = false; + m_enumerable = false; + } + + std::string m_name; + Maybe m_value; + Maybe m_writable; + Maybe m_get; + Maybe m_set; + bool m_configurable; + bool m_enumerable; + Maybe m_wasThrown; + Maybe m_isOwn; + Maybe m_symbol; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PROPERTY_DESCRIPTOR_H diff --git a/plugins/devtools/bridge/inspector/protocol/property_preview.cc b/plugins/devtools/bridge/inspector/protocol/property_preview.cc new file mode 100644 index 0000000000..740c95310e --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/property_preview.cc @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "property_preview.h" +#include "inspector/protocol/object_preview.h" + +namespace kraken { +namespace debugger { + +const char *PropertyPreview::TypeEnum::Object = "object"; +const char *PropertyPreview::TypeEnum::Function = "function"; +const char *PropertyPreview::TypeEnum::Undefined = "undefined"; +const char *PropertyPreview::TypeEnum::String = "string"; +const char *PropertyPreview::TypeEnum::Number = "number"; +const char *PropertyPreview::TypeEnum::Boolean = "boolean"; +const char *PropertyPreview::TypeEnum::Symbol = "symbol"; +const char *PropertyPreview::TypeEnum::Accessor = "accessor"; +const char *PropertyPreview::TypeEnum::Bigint = "bigint"; + +const char *PropertyPreview::SubtypeEnum::Array = "array"; +const char *PropertyPreview::SubtypeEnum::Null = "null"; +const char *PropertyPreview::SubtypeEnum::Node = "node"; +const char *PropertyPreview::SubtypeEnum::Regexp = "regexp"; +const char *PropertyPreview::SubtypeEnum::Date = "date"; +const char *PropertyPreview::SubtypeEnum::Map = "map"; +const char *PropertyPreview::SubtypeEnum::Set = "set"; +const char *PropertyPreview::SubtypeEnum::Weakmap = "weakmap"; +const char *PropertyPreview::SubtypeEnum::Weakset = "weakset"; +const char *PropertyPreview::SubtypeEnum::Iterator = "iterator"; +const char *PropertyPreview::SubtypeEnum::Generator = "generator"; +const char *PropertyPreview::SubtypeEnum::Error = "error"; + +std::unique_ptr PropertyPreview::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new PropertyPreview()); + errors->push(); + + if (value->HasMember("name") && (*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->setName("name"); + errors->addError("name not found"); + } + + if (value->HasMember("type") && (*value)["type"].IsString()) { + result->m_type = (*value)["type"].GetString(); + } else { + errors->setName("type"); + errors->addError("type not found"); + } + + if (value->HasMember("value")) { + errors->setName("value"); + if ((*value)["value"].IsString()) { + result->m_value = (*value)["value"].GetString(); + } else { + errors->addError("value should be string"); + } + } + + if (value->HasMember("valuePreview")) { + errors->setName("valuePreview"); + if ((*value)["valuePreview"].IsObject()) { + rapidjson::Value _preview = (*value)["valuePreview"].GetObject(); + result->m_valuePreview = ObjectPreview::fromValue(&_preview, errors); + } else { + errors->addError("valuePreview should be object"); + } + } + + if (value->HasMember("subtype")) { + errors->setName("subtype"); + if ((*value)["subtype"].IsString()) { + result->m_subtype = (*value)["subtype"].GetString(); + } else { + errors->addError("subtype should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value PropertyPreview::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result(rapidjson::kObjectType); + result.AddMember("name", m_name, allocator); + result.AddMember("type", m_type, allocator); + if (m_value.isJust()) result.AddMember("value", m_value.fromJust(), allocator); + if (m_valuePreview.isJust()) + result.AddMember("valuePreview", m_valuePreview.fromJust()->toValue(allocator), allocator); + if (m_subtype.isJust()) result.AddMember("subtype", m_subtype.fromJust(), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/property_preview.h b/plugins/devtools/bridge/inspector/protocol/property_preview.h new file mode 100644 index 0000000000..3e4f0753c2 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/property_preview.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PROPERTY_PREVIEW_H +#define KRAKEN_DEBUGGER_PROPERTY_PREVIEW_H + +#include +#include + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include + +namespace kraken { +namespace debugger { +class ObjectPreview; +class PropertyPreview { + KRAKEN_DISALLOW_COPY(PropertyPreview); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~PropertyPreview() {} + + std::string getName() { + return m_name; + } + + void setName(const std::string &value) { + m_name = value; + } + + struct TypeEnum { + static const char *Object; + static const char *Function; + static const char *Undefined; + static const char *String; + static const char *Number; + static const char *Boolean; + static const char *Symbol; + static const char *Accessor; + static const char *Bigint; + }; // TypeEnum + + std::string getType() { + return m_type; + } + + void setType(const std::string &value) { + m_type = value; + } + + bool hasValue() { + return m_value.isJust(); + } + + std::string getValue(const std::string &defaultValue) { + return m_value.isJust() ? m_value.fromJust() : defaultValue; + } + + void setValue(const std::string &value) { + m_value = value; + } + + bool hasValuePreview() { + return m_valuePreview.isJust(); + } + + ObjectPreview *getValuePreview(ObjectPreview *defaultValue) { + return m_valuePreview.isJust() ? m_valuePreview.fromJust() : defaultValue; + } + + void setValuePreview(std::unique_ptr value) { + m_valuePreview = std::move(value); + } + + struct SubtypeEnum { + static const char *Array; + static const char *Null; + static const char *Node; + static const char *Regexp; + static const char *Date; + static const char *Map; + static const char *Set; + static const char *Weakmap; + static const char *Weakset; + static const char *Iterator; + static const char *Generator; + static const char *Error; + }; // SubtypeEnum + + bool hasSubtype() { + return m_subtype.isJust(); + } + + std::string getSubtype(const std::string &defaultValue) { + return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; + } + + void setSubtype(const std::string &value) { + m_subtype = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class PropertyPreviewBuilder { + public: + enum { NoFieldsSet = 0, NameSet = 1 << 1, TypeSet = 1 << 2, AllFieldsSet = (NameSet | TypeSet | 0) }; + + PropertyPreviewBuilder &setName(const std::string &value) { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PropertyPreviewBuilder &setType(const std::string &value) { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + PropertyPreviewBuilder &setValue(const std::string &value) { + m_result->setValue(value); + return *this; + } + + PropertyPreviewBuilder &setValuePreview(std::unique_ptr value) { + m_result->setValuePreview(std::move(value)); + return *this; + } + + PropertyPreviewBuilder &setSubtype(const std::string &value) { + m_result->setSubtype(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PropertyPreview; + + PropertyPreviewBuilder() : m_result(new PropertyPreview()) {} + + template PropertyPreviewBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static PropertyPreviewBuilder<0> create() { + return PropertyPreviewBuilder<0>(); + } + +private: + PropertyPreview() {} + + std::string m_name; + std::string m_type; + Maybe m_value; + Maybe m_valuePreview; + Maybe m_subtype; +}; + +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PROPERTY_PREVIEW_H diff --git a/plugins/devtools/bridge/inspector/protocol/remote_object.cc b/plugins/devtools/bridge/inspector/protocol/remote_object.cc new file mode 100644 index 0000000000..864b9597d5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/remote_object.cc @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "remote_object.h" + +namespace kraken { +namespace debugger { + +const char *RemoteObject::TypeEnum::Object = "object"; +const char *RemoteObject::TypeEnum::Function = "function"; +const char *RemoteObject::TypeEnum::Undefined = "undefined"; +const char *RemoteObject::TypeEnum::String = "string"; +const char *RemoteObject::TypeEnum::Number = "number"; +const char *RemoteObject::TypeEnum::Boolean = "boolean"; +const char *RemoteObject::TypeEnum::Symbol = "symbol"; +const char *RemoteObject::TypeEnum::Bigint = "bigint"; + +const char *RemoteObject::SubtypeEnum::Array = "array"; +const char *RemoteObject::SubtypeEnum::Null = "null"; +const char *RemoteObject::SubtypeEnum::Node = "node"; +const char *RemoteObject::SubtypeEnum::Regexp = "regexp"; +const char *RemoteObject::SubtypeEnum::Date = "date"; +const char *RemoteObject::SubtypeEnum::Map = "map"; +const char *RemoteObject::SubtypeEnum::Set = "set"; +const char *RemoteObject::SubtypeEnum::Weakmap = "weakmap"; +const char *RemoteObject::SubtypeEnum::Weakset = "weakset"; +const char *RemoteObject::SubtypeEnum::Iterator = "iterator"; +const char *RemoteObject::SubtypeEnum::Generator = "generator"; +const char *RemoteObject::SubtypeEnum::Error = "error"; +const char *RemoteObject::SubtypeEnum::Proxy = "proxy"; +const char *RemoteObject::SubtypeEnum::Promise = "promise"; +const char *RemoteObject::SubtypeEnum::Typedarray = "typedarray"; +const char *RemoteObject::SubtypeEnum::Arraybuffer = "arraybuffer"; +const char *RemoteObject::SubtypeEnum::Dataview = "dataview"; + +std::unique_ptr RemoteObject::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new RemoteObject()); + errors->push(); + errors->setName("type"); + if (value->HasMember("type") && (*value)["type"].IsString()) { + result->m_type = (*value)["type"].GetString(); + } else { + errors->addError("type not found or not string"); + } + + if (value->HasMember("subtype")) { + errors->setName("subtype"); + if ((*value)["subtype"].IsString()) { + result->m_subtype = (*value)["subtype"].GetString(); + } else { + errors->addError("subtype should be string"); + } + } + + if (value->HasMember("className")) { + errors->setName("className"); + if ((*value)["className"].IsString()) { + result->m_className = (*value)["className"].GetString(); + } else { + errors->addError("className should be string"); + } + } + + if (value->HasMember("value")) { + result->m_value = std::make_unique((*value)["value"], result->m_holder.GetAllocator()); + } + + if (value->HasMember("unserializableValue")) { + errors->setName("unserializableValue"); + if ((*value)["unserializableValue"].IsString()) { + result->m_unserializableValue = (*value)["unserializableValue"].GetString(); + } else { + errors->addError("unserializableValue should be string"); + } + } + + if (value->HasMember("description")) { + errors->setName("description"); + if ((*value)["description"].IsString()) { + result->m_description = (*value)["description"].GetString(); + } else { + errors->addError("description should be string"); + } + } + + if (value->HasMember("objectId")) { + errors->setName("objectId"); + if ((*value)["objectId"].IsString()) { + result->m_objectId = (*value)["objectId"].GetString(); + } else { + errors->addError("objectId should be string"); + } + } + + if (value->HasMember("preview")) { + if ((*value)["preview"].IsObject()) { + rapidjson::Value _preview = (*value)["preview"].GetObject(); + result->m_preview = ObjectPreview::fromValue(&_preview, errors); + } else { + errors->setName("preview"); + errors->addError("preview not found"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value RemoteObject::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("type", m_type, allocator); + + if (m_subtype.isJust()) { + result.AddMember("subtype", m_subtype.fromJust(), allocator); + } + if (m_className.isJust()) { + result.AddMember("className", m_className.fromJust(), allocator); + } + if (m_value.isJust()) { + result.AddMember("value", *m_value.fromJust(), allocator); + } + if (m_unserializableValue.isJust()) { + result.AddMember("unserializableValue", m_unserializableValue.fromJust(), allocator); + } + if (m_description.isJust()) { + result.AddMember("description", m_description.fromJust(), allocator); + } + if (m_objectId.isJust()) { + result.AddMember("objectId", m_objectId.fromJust(), allocator); + } + if (m_preview.isJust()) { + result.AddMember("preview", m_preview.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/remote_object.h b/plugins/devtools/bridge/inspector/protocol/remote_object.h new file mode 100644 index 0000000000..c4f3c47e31 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/remote_object.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_REMOTE_OBJECT_H +#define KRAKEN_DEBUGGER_REMOTE_OBJECT_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/object_preview.h" +#include "kraken_foundation.h" +#include +#include + +namespace kraken { +namespace debugger { +class RemoteObject { + KRAKEN_DISALLOW_COPY(RemoteObject); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~RemoteObject() {} + + struct TypeEnum { + static const char *Object; + static const char *Function; + static const char *Undefined; + static const char *String; + static const char *Number; + static const char *Boolean; + static const char *Symbol; + static const char *Bigint; + }; // TypeEnum + + std::string getType() { + return m_type; + } + + void setType(const std::string &value) { + m_type = value; + } + + struct SubtypeEnum { + static const char *Array; + static const char *Null; + static const char *Node; + static const char *Regexp; + static const char *Date; + static const char *Map; + static const char *Set; + static const char *Weakmap; + static const char *Weakset; + static const char *Iterator; + static const char *Generator; + static const char *Error; + static const char *Proxy; + static const char *Promise; + static const char *Typedarray; + static const char *Arraybuffer; + static const char *Dataview; + }; // SubtypeEnum + + bool hasSubtype() { + return m_subtype.isJust(); + } + + std::string getSubtype(const std::string &defaultValue) { + return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; + } + + void setSubtype(const std::string &value) { + m_subtype = value; + } + + bool hasClassName() { + return m_className.isJust(); + } + + std::string getClassName(const std::string &defaultValue) { + return m_className.isJust() ? m_className.fromJust() : defaultValue; + } + + void setClassName(const std::string &value) { + m_className = value; + } + + bool hasValue() { + return m_value.isJust(); + } + + rapidjson::Value *getValue(rapidjson::Value *defaultValue) { + return m_value.isJust() ? m_value.fromJust() : defaultValue; + } + + void setValue(std::unique_ptr value) { + m_value = std::move(value); + } + + bool hasUnserializableValue() { + return m_unserializableValue.isJust(); + } + + std::string getUnserializableValue(const std::string &defaultValue) { + return m_unserializableValue.isJust() ? m_unserializableValue.fromJust() : defaultValue; + } + + void setUnserializableValue(const std::string &value) { + m_unserializableValue = value; + } + + bool hasDescription() { + return m_description.isJust(); + } + + std::string getDescription(const std::string &defaultValue) { + return m_description.isJust() ? m_description.fromJust() : defaultValue; + } + + void setDescription(const std::string &value) { + m_description = value; + } + + bool hasObjectId() { + return m_objectId.isJust(); + } + + std::string getObjectId(const std::string &defaultValue) { + return m_objectId.isJust() ? m_objectId.fromJust() : defaultValue; + } + + void setObjectId(const std::string &value) { + m_objectId = value; + } + + bool hasPreview() { + return m_preview.isJust(); + } + + ObjectPreview *getPreview(ObjectPreview *defaultValue) { + return m_preview.isJust() ? m_preview.fromJust() : defaultValue; + } + + void setPreview(std::unique_ptr value) { + m_preview = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class RemoteObjectBuilder { + public: + enum { NoFieldsSet = 0, TypeSet = 1 << 1, AllFieldsSet = (TypeSet | 0) }; + + RemoteObjectBuilder &setType(const std::string &value) { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + RemoteObjectBuilder &setSubtype(const std::string &value) { + m_result->setSubtype(value); + return *this; + } + + RemoteObjectBuilder &setClassName(const std::string &value) { + m_result->setClassName(value); + return *this; + } + + RemoteObjectBuilder &setValue(std::unique_ptr value) { + m_result->setValue(std::move(value)); + return *this; + } + + RemoteObjectBuilder &setUnserializableValue(const std::string &value) { + m_result->setUnserializableValue(value); + return *this; + } + + RemoteObjectBuilder &setDescription(const std::string &value) { + m_result->setDescription(value); + return *this; + } + + RemoteObjectBuilder &setObjectId(const std::string &value) { + m_result->setObjectId(value); + return *this; + } + + RemoteObjectBuilder &setPreview(std::unique_ptr value) { + m_result->setPreview(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class RemoteObject; + + RemoteObjectBuilder() : m_result(new RemoteObject()) {} + + template RemoteObjectBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static RemoteObjectBuilder<0> create() { + return RemoteObjectBuilder<0>(); + } + +private: + RemoteObject() {} + + std::string m_type; + Maybe m_subtype; + Maybe m_className; + Maybe m_value; + Maybe m_unserializableValue; + Maybe m_description; + Maybe m_objectId; + rapidjson::Document m_holder; + Maybe m_preview; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_REMOTE_OBJECT_H diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_backend.h b/plugins/devtools/bridge/inspector/protocol/runtime_backend.h new file mode 100644 index 0000000000..0ece3817e4 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_backend.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_RUNTIME_BACKEND_H +#define KRAKEN_DEBUGGER_RUNTIME_BACKEND_H + +#include "inspector/protocol/call_argument.h" +#include "inspector/protocol/dispatch_response.h" +#include "inspector/protocol/exception_details.h" +#include "inspector/protocol/internal_property_descriptor.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/private_property_descriptor.h" +#include "inspector/protocol/property_descriptor.h" +#include "inspector/protocol/remote_object.h" + +#include +#include + +namespace kraken { +namespace debugger { +class RuntimeBackend { +public: + virtual ~RuntimeBackend() {} + + class AwaitPromiseCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse &) = 0; + virtual void fallThrough() = 0; + virtual ~AwaitPromiseCallback() {} + }; + virtual void awaitPromise(const std::string &in_promiseObjectId, Maybe in_returnByValue, + Maybe in_generatePreview, std::unique_ptr callback) = 0; + + class CallFunctionOnCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse &) = 0; + virtual void fallThrough() = 0; + virtual ~CallFunctionOnCallback() {} + }; + + virtual void callFunctionOn(const std::string &in_functionDeclaration, Maybe in_objectId, + Maybe>> in_arguments, Maybe in_silent, + Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, + Maybe in_awaitPromise, Maybe in_executionContextId, + Maybe in_objectGroup, std::unique_ptr callback) = 0; + + virtual DispatchResponse compileScript(const std::string &in_expression, const std::string &in_sourceURL, + bool in_persistScript, Maybe in_executionContextId, + Maybe *out_scriptId, + Maybe *out_exceptionDetails) = 0; + + virtual DispatchResponse disable() = 0; + + virtual DispatchResponse discardConsoleEntries() = 0; + + virtual DispatchResponse enable() = 0; + + class EvaluateCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse &) = 0; + virtual void fallThrough() = 0; + virtual ~EvaluateCallback() {} + }; + + virtual void evaluate(const std::string &in_expression, Maybe in_objectGroup, + Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_contextId, + Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, + Maybe in_awaitPromise, Maybe in_throwOnSideEffect, Maybe in_timeout, + std::unique_ptr callback) = 0; + + virtual DispatchResponse getIsolateId(std::string *out_id) = 0; + + virtual DispatchResponse getHeapUsage(double *out_usedSize, double *out_totalSize) = 0; + + virtual DispatchResponse + getProperties(const std::string &in_objectId, Maybe in_ownProperties, Maybe in_accessorPropertiesOnly, + Maybe in_generatePreview, + std::unique_ptr>> *out_result, + Maybe>> *out_internalProperties, + Maybe>> *out_privateProperties, + Maybe *out_exceptionDetails) = 0; + + virtual DispatchResponse globalLexicalScopeNames(Maybe in_executionContextId, + std::unique_ptr> *out_names) = 0; + + virtual DispatchResponse queryObjects(const std::string &in_prototypeObjectId, Maybe in_objectGroup, + std::unique_ptr *out_objects) = 0; + + virtual DispatchResponse releaseObject(const std::string &in_objectId) = 0; + + virtual DispatchResponse releaseObjectGroup(const std::string &in_objectGroup) = 0; + + virtual DispatchResponse runIfWaitingForDebugger() = 0; + + class RunScriptCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + + virtual void sendFailure(const DispatchResponse &) = 0; + + virtual void fallThrough() = 0; + + virtual ~RunScriptCallback() {} + }; + + virtual void runScript(const std::string &in_scriptId, Maybe in_executionContextId, + Maybe in_objectGroup, Maybe in_silent, Maybe in_includeCommandLineAPI, + Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_awaitPromise, + std::unique_ptr callback) = 0; + + virtual DispatchResponse setCustomObjectFormatterEnabled(bool in_enabled) = 0; + + virtual DispatchResponse setMaxCallStackSizeToCapture(int in_size) = 0; + + class TerminateExecutionCallback { + public: + virtual void sendSuccess() = 0; + virtual void sendFailure(const DispatchResponse &) = 0; + virtual void fallThrough() = 0; + virtual ~TerminateExecutionCallback() {} + }; + + virtual void terminateExecution(std::unique_ptr callback) = 0; + + virtual DispatchResponse addBinding(const std::string &in_name, Maybe in_executionContextId) = 0; + + virtual DispatchResponse removeBinding(const std::string &in_name) = 0; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_RUNTIME_BACKEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.cc b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.cc new file mode 100644 index 0000000000..a093b1552c --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/runtime_dispatcher_contract.h" +#include "inspector/protocol/runtime_dispatcher_impl.h" + +namespace kraken { +namespace debugger { +void RuntimeDispatcherContract::wire(kraken::debugger::UberDispatcher *uber, + kraken::debugger::RuntimeBackend *backend) { + std::unique_ptr dispatcher(new RuntimeDispatcherImpl(uber->channel(), backend)); + uber->setupRedirects(dispatcher->redirects()); + uber->registerBackend("Runtime", std::move(dispatcher)); +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.h b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.h new file mode 100644 index 0000000000..e4a7a190e9 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_contract.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_CONTRACT_H +#define KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_CONTRACT_H + +#include "inspector/protocol/runtime_backend.h" +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken { +namespace debugger { +class RuntimeDispatcherContract { +public: + static void wire(UberDispatcher *, RuntimeBackend *); + +private: + RuntimeDispatcherContract() {} +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_CONTRACT_H diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.cc b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.cc new file mode 100644 index 0000000000..6d2b80d10b --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.cc @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +#include "inspector/protocol/runtime_dispatcher_impl.h" + +namespace kraken { +namespace debugger { + +bool RuntimeDispatcherImpl::canDispatch(const std::string &method) { + return m_dispatchMap.find(method) != m_dispatchMap.end(); +} + +void RuntimeDispatcherImpl::dispatch(uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message) { + std::unordered_map::iterator it = m_dispatchMap.find(method); + if (it == m_dispatchMap.end()) { + return; + } + ErrorSupport errors; + (it->second)(callId, method, std::move(message), &errors); +} + +///////////// COMMANDS ////////////////////// + +void RuntimeDispatcherImpl::enable(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void RuntimeDispatcherImpl::disable(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void RuntimeDispatcherImpl::awaitPromise(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +class CallFunctionOnCallbackImpl : public RuntimeBackend::CallFunctionOnCallback, public DispatcherBase::Callback { +public: + CallFunctionOnCallbackImpl(std::unique_ptr backendImpl, uint64_t callId, + const std::string &method, kraken::debugger::JSONObject message, + rapidjson::Document::AllocatorType &allocator) + : DispatcherBase::Callback(std::move(backendImpl), callId, method, std::move(message)), m_allocator(allocator) {} + + void sendSuccess(std::unique_ptr result, + Maybe exceptionDetails) override { + rapidjson::Value resultObject(rapidjson::kObjectType); + resultObject.AddMember("result", result.get()->toValue(m_allocator), m_allocator); + if (exceptionDetails.isJust()) { + resultObject.AddMember("exceptionDetails", exceptionDetails.fromJust()->toValue(m_allocator), m_allocator); + } + sendIfActive(std::move(resultObject), DispatchResponse::OK()); + } + + void fallThrough() override { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse &response) override { + // DCHECK(response.status() == DispatchResponse::kError); + sendIfActive(rapidjson::Value(rapidjson::kObjectType), response); + } + +private: + rapidjson::Document::AllocatorType &m_allocator; +}; + +void RuntimeDispatcherImpl::callFunctionOn(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_functionDeclaration = ""; + if (message.HasMember("functionDeclaration") && message["functionDeclaration"].IsString()) { + in_functionDeclaration = message["functionDeclaration"].GetString(); + } else { + errors->setName("functionDeclaration"); + errors->addError("functionDeclaration should be string"); + } + + Maybe in_objectId; + if (message.HasMember("objectId")) { + if (message["objectId"].IsString()) { + in_objectId = message["objectId"].GetString(); + } else { + errors->setName("objectId"); + errors->addError("objectId should be string"); + } + } + + Maybe>> in_arguments; + if (message.HasMember("arguments") && message["arguments"].IsArray()) { + in_arguments = std::make_unique>>(); + auto array = message["arguments"].GetArray(); + for (auto &item : array) { + std::unique_ptr argument = debugger::CallArgument::fromValue(&item, errors); + in_arguments.fromJust()->emplace_back(std::move(argument)); + } + } else { + errors->setName("arguments"); + errors->addError("arguments not found"); + } + + Maybe in_silent; + if (message.HasMember("silent")) { + if (message["silent"].IsBool()) { + in_silent = message["silent"].GetBool(); + } else { + errors->setName("silent"); + errors->addError("silent should be bool"); + } + } + + Maybe in_returnByValue; + if (message.HasMember("returnByValue")) { + if (message["returnByValue"].IsBool()) { + in_returnByValue = message["returnByValue"].GetBool(); + } else { + errors->setName("returnByValue"); + errors->addError("returnByValue should be bool"); + } + } + + Maybe in_generatePreview; + if (message.HasMember("generatePreview")) { + if (message["generatePreview"].IsBool()) { + in_generatePreview = message["generatePreview"].GetBool(); + } else { + errors->setName("generatePreview"); + errors->addError("generatePreview should be bool"); + } + } + + Maybe in_userGesture; + if (message.HasMember("userGesture")) { + if (message["userGesture"].IsBool()) { + in_userGesture = message["userGesture"].GetBool(); + } else { + errors->setName("userGesture"); + errors->addError("userGesture should be bool"); + } + } + + Maybe in_awaitPromise; + if (message.HasMember("awaitPromise")) { + if (message["awaitPromise"].IsBool()) { + in_awaitPromise = message["awaitPromise"].GetBool(); + } else { + errors->setName("awaitPromise"); + errors->addError("awaitPromise should be bool"); + } + } + + Maybe in_executionContextId; + if (message.HasMember("executionContextId")) { + if (message["executionContextId"].IsInt()) { + in_executionContextId = message["executionContextId"].GetInt(); + } else { + errors->setName("executionContextId"); + errors->addError("executionContextId should be bool"); + } + } + + Maybe in_objectGroup; + if (message.HasMember("objectGroup")) { + if (message["objectGroup"].IsString()) { + in_objectGroup = message["objectGroup"].GetString(); + } else { + errors->setName("objectGroup"); + errors->addError("objectGroup should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + std::unique_ptr callback( + new CallFunctionOnCallbackImpl(weakPtr(), callId, method, std::move(message), m_json_doc.GetAllocator())); + m_backend->callFunctionOn(in_functionDeclaration, std::move(in_objectId), std::move(in_arguments), + std::move(in_silent), std::move(in_returnByValue), std::move(in_generatePreview), + std::move(in_userGesture), std::move(in_awaitPromise), std::move(in_executionContextId), + std::move(in_objectGroup), std::move(callback)); + return; +} + +void RuntimeDispatcherImpl::compileScript(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_expression = ""; + if (message.HasMember("expression") && message["expression"].IsString()) { + in_expression = message["expression"].GetString(); + } else { + errors->setName("expression"); + errors->addError("expression should be string"); + } + + std::string in_sourceURL = ""; + if (message.HasMember("sourceURL") && message["sourceURL"].IsString()) { + in_sourceURL = message["sourceURL"].GetString(); + } else { + errors->setName("sourceURL"); + errors->addError("sourceURL should be string"); + } + + bool in_persistScript = false; + if (message.HasMember("persistScript") && message["persistScript"].IsBool()) { + in_persistScript = message["persistScript"].GetBool(); + } else { + errors->setName("persistScript"); + errors->addError("persistScript should be bool"); + } + + Maybe in_executionContextId; + if (message.HasMember("executionContextId")) { + if (message["executionContextId"].IsInt()) { + in_executionContextId = message["executionContextId"].GetInt(); + } else { + errors->setName("executionContextId"); + errors->addError("executionContextId should be int"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + // Declare output parameters. + Maybe out_scriptId; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = + m_backend->compileScript(in_expression, in_sourceURL, in_persistScript, std::move(in_executionContextId), + &out_scriptId, &out_exceptionDetails); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + if (response.status() == DispatchResponse::kSuccess) { + if (out_scriptId.isJust()) { + result.AddMember("scriptId", out_scriptId.fromJust(), m_json_doc.GetAllocator()); + } + if (out_exceptionDetails.isJust()) { + result.AddMember("exceptionDetails", out_exceptionDetails.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void RuntimeDispatcherImpl::discardConsoleEntries(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *) {} + +class EvaluateCallbackImpl : public RuntimeBackend::EvaluateCallback, public DispatcherBase::Callback { +public: + EvaluateCallbackImpl(std::unique_ptr backendImpl, uint64_t callId, const std::string &method, + kraken::debugger::JSONObject message, rapidjson::Document::AllocatorType &allocator) + : DispatcherBase::Callback(std::move(backendImpl), callId, method, std::move(message)), m_allocator(allocator) {} + + void sendSuccess(std::unique_ptr result, + Maybe exceptionDetails) override { + rapidjson::Value resultObject(rapidjson::kObjectType); + resultObject.AddMember("result", result->toValue(m_allocator), m_allocator); + if (exceptionDetails.isJust()) + resultObject.AddMember("exceptionDetails", exceptionDetails.fromJust()->toValue(m_allocator), m_allocator); + sendIfActive(std::move(resultObject), DispatchResponse::OK()); + } + + void fallThrough() override { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse &response) override { + // DCHECK(response.status() == DispatchResponse::kError); + sendIfActive(rapidjson::Value(rapidjson::kObjectType), response); + } + +private: + rapidjson::Document::AllocatorType &m_allocator; +}; + +void RuntimeDispatcherImpl::evaluate(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + + std::string in_expression = ""; + if (message.HasMember("expression") && message["expression"].IsString()) { + in_expression = message["expression"].GetString(); + } else { + errors->setName("expression"); + errors->addError("expression should be string"); + } + + Maybe in_objectGroup; + if (message.HasMember("objectGroup")) { + if (message["objectGroup"].IsString()) { + in_objectGroup = message["objectGroup"].GetString(); + } else { + errors->setName("objectGroup"); + errors->addError("objectGroup should be string"); + } + } + + Maybe in_includeCommandLineAPI; + if (message.HasMember("includeCommandLineAPI")) { + if (message["includeCommandLineAPI"].IsBool()) { + in_includeCommandLineAPI = message["includeCommandLineAPI"].GetBool(); + } else { + errors->setName("includeCommandLineAPI"); + errors->addError("includeCommandLineAPI should be bool"); + } + } + + Maybe in_silent; + if (message.HasMember("silent")) { + if (message["silent"].IsBool()) { + in_silent = message["silent"].GetBool(); + } else { + errors->setName("silent"); + errors->addError("silent should be bool"); + } + } + + Maybe in_contextId; + if (message.HasMember("contextId")) { + if (message["contextId"].IsInt()) { + in_contextId = message["contextId"].GetInt(); + } else { + errors->setName("contextId"); + errors->addError("contextId should be int"); + } + } + + Maybe in_returnByValue; + if (message.HasMember("returnByValue")) { + if (message["returnByValue"].IsBool()) { + in_returnByValue = message["returnByValue"].GetBool(); + } else { + errors->setName("returnByValue"); + errors->addError("returnByValue should be bool"); + } + } + + Maybe in_generatePreview; + if (message.HasMember("generatePreview")) { + if (message["generatePreview"].IsBool()) { + in_generatePreview = message["generatePreview"].GetBool(); + } else { + errors->setName("generatePreview"); + errors->addError("generatePreview should be bool"); + } + } + + Maybe in_userGesture; + if (message.HasMember("userGesture")) { + if (message["userGesture"].IsBool()) { + in_userGesture = message["userGesture"].GetBool(); + } else { + errors->setName("userGesture"); + errors->addError("userGesture should be bool"); + } + } + + Maybe in_awaitPromise; + if (message.HasMember("awaitPromise")) { + if (message["awaitPromise"].IsBool()) { + in_awaitPromise = message["awaitPromise"].GetBool(); + } else { + errors->setName("awaitPromise"); + errors->addError("awaitPromise should be bool"); + } + } + + Maybe in_throwOnSideEffect; + if (message.HasMember("throwOnSideEffect")) { + if (message["throwOnSideEffect"].IsBool()) { + in_throwOnSideEffect = message["throwOnSideEffect"].GetBool(); + } else { + errors->setName("throwOnSideEffect"); + errors->addError("throwOnSideEffect should be bool"); + } + } + + Maybe in_timeout; + if (message.HasMember("timeout")) { + if (message["timeout"].IsDouble()) { + in_timeout = message["timeout"].GetDouble(); + } else { + errors->setName("timeout"); + errors->addError("timeout should be double"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + std::unique_ptr callback( + new EvaluateCallbackImpl(weakPtr(), callId, method, std::move(message), m_json_doc.GetAllocator())); + m_backend->evaluate(in_expression, std::move(in_objectGroup), std::move(in_includeCommandLineAPI), + std::move(in_silent), std::move(in_contextId), std::move(in_returnByValue), + std::move(in_generatePreview), std::move(in_userGesture), std::move(in_awaitPromise), + std::move(in_throwOnSideEffect), std::move(in_timeout), std::move(callback)); + return; +} + +void RuntimeDispatcherImpl::getIsolateId(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + // Declare output parameters. + std::string out_id; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getIsolateId(&out_id); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("id", out_id, m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void RuntimeDispatcherImpl::getHeapUsage(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) { + // Declare output parameters. + double out_usedSize; + double out_totalSize; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getHeapUsage(&out_usedSize, &out_totalSize); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + rapidjson::Value result(rapidjson::kObjectType); + if (response.status() == DispatchResponse::kSuccess) { + result.AddMember("usedSize", out_usedSize, m_json_doc.GetAllocator()); + result.AddMember("totalSize", out_totalSize, m_json_doc.GetAllocator()); + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +/** + * Returns properties of a given object. Object group of the result is inherited from the target object. + * + * @param objectId Identifier of the object to return properties for. + * @param ownProperties If true, returns properties belonging only to the element itself, not to its prototype chain. + * @param accessorPropertiesOnly If true, returns accessor properties (with getter/setter) only; internal properties are + * not returned either. + * @param generatePreview Whether preview should be generated for the results. + * + * @return + * - result: Object properties. + * - internalProperties: Internal object properties (only of the element itself). + * - privateProperties: Object private properties. + * - exceptionDetails + * */ +void RuntimeDispatcherImpl::getProperties(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::string in_objectId = ""; + if (message.HasMember("objectId") && message["objectId"].IsString()) { + in_objectId = message["objectId"].GetString(); + } else { + errors->setName("objectId"); + errors->addError("objectId not found"); + } + + Maybe in_ownProperties; + if (message.HasMember("ownProperties")) { + errors->setName("ownProperties"); + if (message["ownProperties"].IsBool()) { + in_ownProperties = message["ownProperties"].GetBool(); + } else { + errors->addError("ownProperties should be bool"); + } + } + + Maybe in_accessorPropertiesOnly; + if (message.HasMember("accessorPropertiesOnly")) { + errors->setName("accessorPropertiesOnly"); + if (message["accessorPropertiesOnly"].IsBool()) { + in_accessorPropertiesOnly = message["accessorPropertiesOnly"].GetBool(); + } else { + errors->addError("accessorPropertiesOnly should be bool"); + } + } + + Maybe in_generatePreview; + if (message.HasMember("generatePreview")) { + errors->setName("generatePreview"); + if (message["generatePreview"].IsBool()) { + in_generatePreview = message["generatePreview"].GetBool(); + } else { + errors->addError("generatePreview should be bool"); + } + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + // Declare output parameters. + std::unique_ptr>> out_result; + Maybe>> out_internalProperties; + Maybe>> out_privateProperties; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getProperties( + in_objectId, std::move(in_ownProperties), std::move(in_accessorPropertiesOnly), std::move(in_generatePreview), + &out_result, &out_internalProperties, &out_privateProperties, &out_exceptionDetails); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + + rapidjson::Value result(rapidjson::kObjectType); + result.SetObject(); + + if (response.status() == DispatchResponse::kSuccess) { + rapidjson::Value _result(rapidjson::kArrayType); + for (const auto &propertyDesc : *out_result) { + _result.PushBack(propertyDesc->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("result", _result, m_json_doc.GetAllocator()); + + if (out_internalProperties.isJust()) { + rapidjson::Value _privateProperties(rapidjson::kArrayType); + for (const auto &internalProp : *out_internalProperties.fromJust()) { + _privateProperties.PushBack(internalProp->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("internalProperties", _privateProperties, m_json_doc.GetAllocator()); + } + + if (out_privateProperties.isJust()) { + rapidjson::Value _out_privateProperties(rapidjson::kArrayType); + for (const auto &privateProp : *out_privateProperties.fromJust()) { + _out_privateProperties.PushBack(privateProp->toValue(m_json_doc.GetAllocator()), m_json_doc.GetAllocator()); + } + result.AddMember("privateProperties", _out_privateProperties, m_json_doc.GetAllocator()); + } + + if (out_exceptionDetails.isJust()) { + result.AddMember("exceptionDetails", out_exceptionDetails.fromJust()->toValue(m_json_doc.GetAllocator()), + m_json_doc.GetAllocator()); + } + } + if (weak->get()) weak->get()->sendResponse(callId, response, std::move(result)); + return; +} + +void RuntimeDispatcherImpl::globalLexicalScopeNames(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *) {} + +void RuntimeDispatcherImpl::queryObjects(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +void RuntimeDispatcherImpl::releaseObject(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::string in_objectId = ""; + if (message.HasMember("objectId") && message["objectId"].IsString()) { + in_objectId = message["objectId"].GetString(); + } else { + errors->setName("objectId"); + errors->addError("objectId not found"); + } + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->releaseObject(in_objectId); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void RuntimeDispatcherImpl::releaseObjectGroup(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *errors) { + // Prepare input parameters. + errors->push(); + std::string in_objectGroup = ""; + if (message.HasMember("objectGroup") && message["objectGroup"].IsString()) { + in_objectGroup = message["objectGroup"].GetString(); + } else { + errors->setName("objectGroup"); + errors->addError("objectGroup not found"); + } + + errors->pop(); + if (errors->hasErrors()) { + reportProtocolError(callId, kInvalidParams, kInvalidParamsString, errors); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->releaseObjectGroup(in_objectGroup); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void RuntimeDispatcherImpl::runIfWaitingForDebugger(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *) { + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->runIfWaitingForDebugger(); + if (response.status() == DispatchResponse::kFallThrough) { + channel()->fallThrough(callId, method, std::move(message)); + return; + } + if (weak->get()) weak->get()->sendResponse(callId, response); + return; +} + +void RuntimeDispatcherImpl::runScript(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +void RuntimeDispatcherImpl::setCustomObjectFormatterEnabled(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *) {} + +void RuntimeDispatcherImpl::setMaxCallStackSizeToCapture(uint64_t callId, const std::string &method, + JSONObject message, ErrorSupport *) {} + +void RuntimeDispatcherImpl::terminateExecution(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +void RuntimeDispatcherImpl::addBinding(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +void RuntimeDispatcherImpl::removeBinding(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *) {} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.h b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.h new file mode 100644 index 0000000000..6aac129728 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_dispatcher_impl.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_IMPL_H +#define KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_IMPL_H + +#include "inspector/protocol/dispatcher_base.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/frontend_channel.h" +#include "inspector/protocol/runtime_backend.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { +class RuntimeDispatcherImpl : public DispatcherBase { +public: + RuntimeDispatcherImpl(FrontendChannel *frontendChannel, RuntimeBackend *backend) + : DispatcherBase(frontendChannel), m_backend(backend) { + m_dispatchMap["Runtime.awaitPromise"] = + std::bind(&RuntimeDispatcherImpl::awaitPromise, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.callFunctionOn"] = + std::bind(&RuntimeDispatcherImpl::callFunctionOn, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.compileScript"] = + std::bind(&RuntimeDispatcherImpl::compileScript, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.disable"] = std::bind(&RuntimeDispatcherImpl::disable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.discardConsoleEntries"] = + std::bind(&RuntimeDispatcherImpl::discardConsoleEntries, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.enable"] = std::bind(&RuntimeDispatcherImpl::enable, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.evaluate"] = std::bind(&RuntimeDispatcherImpl::evaluate, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.getIsolateId"] = + std::bind(&RuntimeDispatcherImpl::getIsolateId, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.getHeapUsage"] = + std::bind(&RuntimeDispatcherImpl::getHeapUsage, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.getProperties"] = + std::bind(&RuntimeDispatcherImpl::getProperties, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.globalLexicalScopeNames"] = + std::bind(&RuntimeDispatcherImpl::globalLexicalScopeNames, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.queryObjects"] = + std::bind(&RuntimeDispatcherImpl::queryObjects, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.releaseObject"] = + std::bind(&RuntimeDispatcherImpl::releaseObject, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.releaseObjectGroup"] = + std::bind(&RuntimeDispatcherImpl::releaseObjectGroup, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.runIfWaitingForDebugger"] = + std::bind(&RuntimeDispatcherImpl::runIfWaitingForDebugger, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.runScript"] = std::bind(&RuntimeDispatcherImpl::runScript, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_redirects["Runtime.setAsyncCallStackDepth"] = "Debugger.setAsyncCallStackDepth"; + m_dispatchMap["Runtime.setCustomObjectFormatterEnabled"] = + std::bind(&RuntimeDispatcherImpl::setCustomObjectFormatterEnabled, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.setMaxCallStackSizeToCapture"] = + std::bind(&RuntimeDispatcherImpl::setMaxCallStackSizeToCapture, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.terminateExecution"] = + std::bind(&RuntimeDispatcherImpl::terminateExecution, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.addBinding"] = + std::bind(&RuntimeDispatcherImpl::addBinding, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + m_dispatchMap["Runtime.removeBinding"] = + std::bind(&RuntimeDispatcherImpl::removeBinding, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4); + } + + ~RuntimeDispatcherImpl() override {} + bool canDispatch(const std::string &method) override; + void dispatch(uint64_t callId, const std::string &method, JSONObject message) override; + + std::unordered_map &redirects() { + return m_redirects; + } + +protected: + using CallHandler = std::function; + using DispatchMap = std::unordered_map; + + DispatchMap m_dispatchMap; + std::unordered_map m_redirects; + + RuntimeBackend *m_backend; + rapidjson::Document m_json_doc; + + /*runtime commands*/ + void awaitPromise(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void callFunctionOn(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void compileScript(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void disable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void discardConsoleEntries(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void enable(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void evaluate(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getIsolateId(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getHeapUsage(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void getProperties(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void globalLexicalScopeNames(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void queryObjects(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void releaseObject(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void releaseObjectGroup(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void runIfWaitingForDebugger(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void runScript(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void setCustomObjectFormatterEnabled(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *); + void setMaxCallStackSizeToCapture(uint64_t callId, const std::string &method, JSONObject message, + ErrorSupport *); + void terminateExecution(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void addBinding(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); + void removeBinding(uint64_t callId, const std::string &method, JSONObject message, ErrorSupport *); +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_RUNTIME_DISPATCHER_IMPL_H diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_frontend.cc b/plugins/devtools/bridge/inspector/protocol/runtime_frontend.cc new file mode 100644 index 0000000000..4031f3898d --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_frontend.cc @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/runtime_frontend.h" +#include "inspector/protocol/execution_context_created_notification.h" + +namespace kraken { +namespace debugger { +void RuntimeFrontend::bindingCalled(const std::string &name, const std::string &payload, int executionContextId) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = BindingCalledNotification::create() + // .setName(name) + // .setPayload(payload) + // .setExecutionContextId(executionContextId) + // .build(); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.bindingCalled", + // std::move(messageData))); +} + +void RuntimeFrontend::consoleAPICalled(const std::string &type, + std::unique_ptr>> args, + int executionContextId, double timestamp, Maybe stackTrace, + Maybe context) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = ConsoleAPICalledNotification::create() + // .setType(type) + // .setArgs(std::move(args)) + // .setExecutionContextId(executionContextId) + // .setTimestamp(timestamp) + // .build(); + // if (stackTrace.isJust()) + // messageData->setStackTrace(std::move(stackTrace).takeJust()); + // if (context.isJust()) + // messageData->setContext(std::move(context).takeJust()); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.consoleAPICalled", + // std::move(messageData))); +} + +void RuntimeFrontend::exceptionRevoked(const std::string &reason, int exceptionId) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = ExceptionRevokedNotification::create() + // .setReason(reason) + // .setExceptionId(exceptionId) + // .build(); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.exceptionRevoked", + // std::move(messageData))); +} + +void RuntimeFrontend::exceptionThrown(double timestamp, std::unique_ptr exceptionDetails) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = ExceptionThrownNotification::create() + // .setTimestamp(timestamp) + // .setExceptionDetails(std::move(exceptionDetails)) + // .build(); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.exceptionThrown", + // std::move(messageData))); +} + +void RuntimeFrontend::executionContextCreated(std::unique_ptr context) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = + ExecutionContextCreatedNotification::create().setContext(std::move(context)).build(); + rapidjson::Document doc; + m_frontendChannel->sendProtocolNotification( + {"Runtime.executionContextCreated", messageData->toValue(doc.GetAllocator())}); +} + +void RuntimeFrontend::executionContextDestroyed(int executionContextId) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = + // ExecutionContextDestroyedNotification::create() + // .setExecutionContextId(executionContextId) + // .build(); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.executionContextDestroyed", + // std::move(messageData))); +} + +void RuntimeFrontend::executionContextsCleared(std::unique_ptr context) { + if (!m_frontendChannel) return; + std::unique_ptr messageData = + ExecutionContextCreatedNotification::create().setContext(std::move(context)).build(); + rapidjson::Document doc; + m_frontendChannel->sendProtocolNotification( + {"Runtime.executionContextCreated", messageData->toValue(doc.GetAllocator())}); +} + +void RuntimeFrontend::inspectRequested(std::unique_ptr object, std::unique_ptr hints) { + // if (!m_frontendChannel) + // return; + // std::unique_ptr messageData = InspectRequestedNotification::create() + // .setObject(std::move(object)) + // .setHints(std::move(hints)) + // .build(); + // m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("Runtime.inspectRequested", + // std::move(messageData))); +} + +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/runtime_frontend.h b/plugins/devtools/bridge/inspector/protocol/runtime_frontend.h new file mode 100644 index 0000000000..a0b3f41f92 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/runtime_frontend.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_RUNTIME_FRONTEND_H +#define KRAKEN_DEBUGGER_RUNTIME_FRONTEND_H + +#include "inspector/protocol/exception_details.h" +#include "inspector/protocol/execution_context_description.h" +#include "inspector/protocol/frontend_channel.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/remote_object.h" +#include "inspector/protocol/stacktrace.h" + +#include +#include +#include + +namespace kraken { +namespace debugger { +class RuntimeFrontend { +public: + explicit RuntimeFrontend(FrontendChannel *frontendChannel) : m_frontendChannel(frontendChannel) {} + + void bindingCalled(const std::string &name, const std::string &payload, int executionContextId); + void consoleAPICalled(const std::string &type, std::unique_ptr>> args, + int executionContextId, double timestamp, Maybe stackTrace = Maybe(), + Maybe context = Maybe()); + + void exceptionRevoked(const std::string &reason, int exceptionId); + void exceptionThrown(double timestamp, std::unique_ptr exceptionDetails); + void executionContextCreated(std::unique_ptr context); + void executionContextDestroyed(int executionContextId); + void executionContextsCleared(std::unique_ptr context); + void inspectRequested(std::unique_ptr object, std::unique_ptr hints); + +private: + FrontendChannel *m_frontendChannel; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_RUNTIME_FRONTEND_H diff --git a/plugins/devtools/bridge/inspector/protocol/scope.cc b/plugins/devtools/bridge/inspector/protocol/scope.cc new file mode 100644 index 0000000000..313737e5d2 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/scope.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "scope.h" + +namespace kraken { +namespace debugger { + +const char *Scope::TypeEnum::Global = "global"; +const char *Scope::TypeEnum::Local = "local"; +const char *Scope::TypeEnum::With = "with"; +const char *Scope::TypeEnum::Closure = "closure"; +const char *Scope::TypeEnum::Catch = "catch"; +const char *Scope::TypeEnum::Block = "block"; +const char *Scope::TypeEnum::Script = "script"; +const char *Scope::TypeEnum::Eval = "eval"; +const char *Scope::TypeEnum::Module = "module"; + +std::unique_ptr Scope::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new Scope()); + errors->push(); + + if (value->HasMember("type") && (*value)["type"].IsString()) { + result->m_type = (*value)["type"].GetString(); + } else { + errors->setName("type"); + errors->addError("type not found"); + } + + if (value->HasMember("object") && (*value)["object"].IsObject()) { + rapidjson::Value obj = rapidjson::Value((*value)["object"].GetObject()); + result->m_object = RemoteObject::fromValue(&obj, errors); + } else { + errors->setName("object"); + errors->addError("object not found"); + } + + if (value->HasMember("name")) { + errors->setName("name"); + if ((*value)["name"].IsString()) { + result->m_name = (*value)["name"].GetString(); + } else { + errors->addError("name shoud be string"); + } + } + + if (value->HasMember("startLocation")) { + errors->setName("startLocation"); + if ((*value)["startLocation"].IsObject()) { + rapidjson::Value start_loc = rapidjson::Value((*value)["startLocation"].GetObject()); + result->m_startLocation = Location::fromValue(&start_loc, errors); + } else { + errors->addError("startLocation should be object"); + } + } + + if (value->HasMember("endLocation")) { + errors->setName("startLocation"); + if ((*value)["endLocation"].IsObject()) { + rapidjson::Value end_loc = rapidjson::Value((*value)["endLocation"].GetObject()); + result->m_endLocation = Location::fromValue(&end_loc, errors); + } else { + errors->addError("endLocation should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value Scope::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("type", m_type, allocator); + result.AddMember("object", m_object->toValue(allocator), allocator); + + if (m_name.isJust()) { + result.AddMember("name", m_name.fromJust(), allocator); + } + if (m_startLocation.isJust()) { + result.AddMember("startLocation", m_startLocation.fromJust()->toValue(allocator), allocator); + } + if (m_endLocation.isJust()) { + result.AddMember("endLocation", m_endLocation.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/scope.h b/plugins/devtools/bridge/inspector/protocol/scope.h new file mode 100644 index 0000000000..2ebf5955b1 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/scope.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_SCOPE_H +#define KRAKEN_DEBUGGER_SCOPE_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/remote_object.h" +#include "kraken_foundation.h" +#include +#include +#include + +namespace kraken::debugger { +class Scope { + KRAKEN_DISALLOW_COPY(Scope); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~Scope() {} + + struct TypeEnum { + static const char *Global; + static const char *Local; + static const char *With; + static const char *Closure; + static const char *Catch; + static const char *Block; + static const char *Script; + static const char *Eval; + static const char *Module; + }; // TypeEnum + + std::string getType() { + return m_type; + } + void setType(const std::string &value) { + m_type = value; + } + + RemoteObject *getObject() { + return m_object.get(); + } + void setObject(std::unique_ptr value) { + m_object = std::move(value); + } + + bool hasName() { + return m_name.isJust(); + } + std::string getName(const std::string &defaultValue) { + return m_name.isJust() ? m_name.fromJust() : defaultValue; + } + void setName(const std::string &value) { + m_name = value; + } + + bool hasStartLocation() { + return m_startLocation.isJust(); + } + Location *getStartLocation(Location *defaultValue) { + return m_startLocation.isJust() ? m_startLocation.fromJust() : defaultValue; + } + void setStartLocation(std::unique_ptr value) { + m_startLocation = std::move(value); + } + + bool hasEndLocation() { + return m_endLocation.isJust(); + } + Location *getEndLocation(Location *defaultValue) { + return m_endLocation.isJust() ? m_endLocation.fromJust() : defaultValue; + } + void setEndLocation(std::unique_ptr value) { + m_endLocation = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ScopeBuilder { + public: + enum { NoFieldsSet = 0, TypeSet = 1 << 1, ObjectSet = 1 << 2, AllFieldsSet = (TypeSet | ObjectSet | 0) }; + + ScopeBuilder &setType(const std::string &value) { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + ScopeBuilder &setObject(std::unique_ptr value) { + static_assert(!(STATE & ObjectSet), "property object should not be set yet"); + m_result->setObject(std::move(value)); + return castState(); + } + + ScopeBuilder &setName(const std::string &value) { + m_result->setName(value); + return *this; + } + + ScopeBuilder &setStartLocation(std::unique_ptr value) { + m_result->setStartLocation(std::move(value)); + return *this; + } + + ScopeBuilder &setEndLocation(std::unique_ptr value) { + m_result->setEndLocation(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Scope; + ScopeBuilder() : m_result(new Scope()) {} + + template ScopeBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ScopeBuilder<0> create() { + return ScopeBuilder<0>(); + } + +private: + Scope() {} + + std::string m_type; + std::unique_ptr m_object; + Maybe m_name; + Maybe m_startLocation; + Maybe m_endLocation; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_SCOPE_H diff --git a/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.cc b/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.cc new file mode 100644 index 0000000000..919ce3fe40 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.cc @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_failed_to_parse_notification.h" + +namespace kraken { +namespace debugger { +std::unique_ptr +ScriptFailedToParseNotification::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ScriptFailedToParseNotification()); + errors->push(); + + if (value->HasMember("scriptId") && (*value)["scriptId"].IsString()) { + result->m_scriptId = (*value)["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + if (value->HasMember("url") && (*value)["url"].IsString()) { + result->m_url = (*value)["url"].GetString(); + } else { + errors->setName("url"); + errors->addError("url not found"); + } + + if (value->HasMember("startLine") && (*value)["startLine"].IsInt()) { + result->m_startLine = (*value)["startLine"].GetInt(); + } else { + errors->setName("startLine"); + errors->addError("startLine not found"); + } + + if (value->HasMember("startColumn") && (*value)["startColumn"].IsInt()) { + result->m_startColumn = (*value)["startColumn"].GetInt(); + } else { + errors->setName("startColumn"); + errors->addError("startColumn not found"); + } + + if (value->HasMember("endLine") && (*value)["endLine"].IsInt()) { + result->m_endLine = (*value)["endLine"].GetInt(); + } else { + errors->setName("endLine"); + errors->addError("endLine not found"); + } + + if (value->HasMember("endColumn") && (*value)["endColumn"].IsInt()) { + result->m_endColumn = (*value)["endColumn"].GetInt(); + } else { + errors->setName("endColumn"); + errors->addError("endColumn not found"); + } + + if (value->HasMember("executionContextId") && (*value)["executionContextId"].IsInt()) { + result->m_executionContextId = (*value)["executionContextId"].GetInt(); + } else { + errors->setName("executionContextId"); + errors->addError("executionContextId not found"); + } + + if (value->HasMember("hash") && (*value)["hash"].IsString()) { + result->m_hash = (*value)["hash"].GetString(); + } else { + errors->setName("hash"); + errors->addError("hash not found"); + } + + if (value->HasMember("executionContextAuxData")) { + result->m_executionContextAuxData = + std::make_unique((*value)["executionContextAuxData"], result->m_holder.GetAllocator()); + } + + if (value->HasMember("sourceMapURL")) { + errors->setName("sourceMapURL"); + if ((*value)["sourceMapURL"].IsString()) { + result->m_sourceMapURL = (*value)["sourceMapURL"].GetString(); + } else { + errors->addError("sourceMapURL should be string"); + } + } + + if (value->HasMember("hasSourceURL")) { + errors->setName("hasSourceURL"); + if ((*value)["hasSourceURL"].IsBool()) { + result->m_hasSourceURL = (*value)["hasSourceURL"].GetBool(); + } else { + errors->addError("hasSourceURL should be bool"); + } + } + + if (value->HasMember("isModule")) { + errors->setName("isModule"); + if ((*value)["isModule"].IsBool()) { + result->m_isModule = (*value)["isModule"].GetBool(); + } else { + errors->addError("isModule should be bool"); + } + } + + if (value->HasMember("length")) { + errors->setName("length"); + if ((*value)["length"].IsInt()) { + result->m_length = (*value)["length"].GetInt(); + } else { + errors->addError("length should be int"); + } + } + + if (value->HasMember("stackTrace")) { + errors->setName("stackTrace"); + if ((*value)["stackTrace"].IsObject()) { + rapidjson::Value _stacktrace = (*value)["stackTrace"].GetObject(); + result->m_stackTrace = StackTrace::fromValue(&_stacktrace, errors); + } else { + errors->addError("stackTrace should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ScriptFailedToParseNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("scriptId", m_scriptId, allocator); + result.AddMember("url", m_url, allocator); + result.AddMember("startLine", m_startLine, allocator); + result.AddMember("startColumn", m_startColumn, allocator); + result.AddMember("endLine", m_endLine, allocator); + result.AddMember("endColumn", m_endColumn, allocator); + result.AddMember("executionContextId", m_executionContextId, allocator); + result.AddMember("hash", m_hash, allocator); + + if (m_executionContextAuxData.isJust()) + result.AddMember("executionContextAuxData", *m_executionContextAuxData.fromJust(), allocator); + if (m_sourceMapURL.isJust()) result.AddMember("sourceMapURL", m_sourceMapURL.fromJust(), allocator); + if (m_hasSourceURL.isJust()) result.AddMember("hasSourceURL", m_hasSourceURL.fromJust(), allocator); + if (m_isModule.isJust()) result.AddMember("isModule", m_isModule.fromJust(), allocator); + if (m_length.isJust()) result.AddMember("length", m_length.fromJust(), allocator); + if (m_stackTrace.isJust()) result.AddMember("stackTrace", m_stackTrace.fromJust()->toValue(allocator), allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.h b/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.h new file mode 100644 index 0000000000..cb7b51d1ea --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_failed_to_parse_notification.h @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_SCRIPT_FAILED_TO_PARSE_NOTIFICATION_H +#define KRAKEN_DEBUGGER_SCRIPT_FAILED_TO_PARSE_NOTIFICATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/stacktrace.h" +#include +#include + +namespace kraken { +namespace debugger { +class ScriptFailedToParseNotification { + KRAKEN_DISALLOW_COPY(ScriptFailedToParseNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ScriptFailedToParseNotification() {} + + std::string getScriptId() { + return m_scriptId; + } + + void setScriptId(const std::string &value) { + m_scriptId = value; + } + + std::string getUrl() { + return m_url; + } + + void setUrl(const std::string &value) { + m_url = value; + } + + int getStartLine() { + return m_startLine; + } + + void setStartLine(int value) { + m_startLine = value; + } + + int getStartColumn() { + return m_startColumn; + } + + void setStartColumn(int value) { + m_startColumn = value; + } + + int getEndLine() { + return m_endLine; + } + + void setEndLine(int value) { + m_endLine = value; + } + + int getEndColumn() { + return m_endColumn; + } + + void setEndColumn(int value) { + m_endColumn = value; + } + + int getExecutionContextId() { + return m_executionContextId; + } + + void setExecutionContextId(int value) { + m_executionContextId = value; + } + + std::string getHash() { + return m_hash; + } + + void setHash(const std::string &value) { + m_hash = value; + } + + bool hasExecutionContextAuxData() { + return m_executionContextAuxData.isJust(); + } + + rapidjson::Value *getExecutionContextAuxData(rapidjson::Value *defaultValue) { + return m_executionContextAuxData.isJust() ? m_executionContextAuxData.fromJust() : defaultValue; + } + + void setExecutionContextAuxData(std::unique_ptr value) { + m_executionContextAuxData = std::move(value); + } + + bool hasSourceMapURL() { + return m_sourceMapURL.isJust(); + } + + std::string getSourceMapURL(const std::string &defaultValue) { + return m_sourceMapURL.isJust() ? m_sourceMapURL.fromJust() : defaultValue; + } + + void setSourceMapURL(const std::string &value) { + m_sourceMapURL = value; + } + + bool hasHasSourceURL() { + return m_hasSourceURL.isJust(); + } + + bool getHasSourceURL(bool defaultValue) { + return m_hasSourceURL.isJust() ? m_hasSourceURL.fromJust() : defaultValue; + } + + void setHasSourceURL(bool value) { + m_hasSourceURL = value; + } + + bool hasIsModule() { + return m_isModule.isJust(); + } + + bool getIsModule(bool defaultValue) { + return m_isModule.isJust() ? m_isModule.fromJust() : defaultValue; + } + + void setIsModule(bool value) { + m_isModule = value; + } + + bool hasLength() { + return m_length.isJust(); + } + + int getLength(int defaultValue) { + return m_length.isJust() ? m_length.fromJust() : defaultValue; + } + + void setLength(int value) { + m_length = value; + } + + bool hasStackTrace() { + return m_stackTrace.isJust(); + } + + StackTrace *getStackTrace(StackTrace *defaultValue) { + return m_stackTrace.isJust() ? m_stackTrace.fromJust() : defaultValue; + } + + void setStackTrace(std::unique_ptr value) { + m_stackTrace = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ScriptFailedToParseNotificationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + UrlSet = 1 << 2, + StartLineSet = 1 << 3, + StartColumnSet = 1 << 4, + EndLineSet = 1 << 5, + EndColumnSet = 1 << 6, + ExecutionContextIdSet = 1 << 7, + HashSet = 1 << 8, + AllFieldsSet = (ScriptIdSet | UrlSet | StartLineSet | StartColumnSet | EndLineSet | EndColumnSet | + ExecutionContextIdSet | HashSet | 0) + }; + + ScriptFailedToParseNotificationBuilder &setScriptId(const std::string &value) { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setUrl(const std::string &value) { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setStartLine(int value) { + static_assert(!(STATE & StartLineSet), "property startLine should not be set yet"); + m_result->setStartLine(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setStartColumn(int value) { + static_assert(!(STATE & StartColumnSet), "property startColumn should not be set yet"); + m_result->setStartColumn(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setEndLine(int value) { + static_assert(!(STATE & EndLineSet), "property endLine should not be set yet"); + m_result->setEndLine(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setEndColumn(int value) { + static_assert(!(STATE & EndColumnSet), "property endColumn should not be set yet"); + m_result->setEndColumn(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setExecutionContextId(int value) { + static_assert(!(STATE & ExecutionContextIdSet), "property executionContextId should not be set yet"); + m_result->setExecutionContextId(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setHash(const std::string &value) { + static_assert(!(STATE & HashSet), "property hash should not be set yet"); + m_result->setHash(value); + return castState(); + } + + ScriptFailedToParseNotificationBuilder &setExecutionContextAuxData(std::unique_ptr value) { + m_result->setExecutionContextAuxData(std::move(value)); + return *this; + } + + ScriptFailedToParseNotificationBuilder &setSourceMapURL(const std::string &value) { + m_result->setSourceMapURL(value); + return *this; + } + + ScriptFailedToParseNotificationBuilder &setHasSourceURL(bool value) { + m_result->setHasSourceURL(value); + return *this; + } + + ScriptFailedToParseNotificationBuilder &setIsModule(bool value) { + m_result->setIsModule(value); + return *this; + } + + ScriptFailedToParseNotificationBuilder &setLength(int value) { + m_result->setLength(value); + return *this; + } + + ScriptFailedToParseNotificationBuilder &setStackTrace(std::unique_ptr value) { + m_result->setStackTrace(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ScriptFailedToParseNotification; + + ScriptFailedToParseNotificationBuilder() : m_result(new ScriptFailedToParseNotification()) {} + + template ScriptFailedToParseNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ScriptFailedToParseNotificationBuilder<0> create() { + return ScriptFailedToParseNotificationBuilder<0>(); + } + +private: + ScriptFailedToParseNotification() { + m_startLine = 0; + m_startColumn = 0; + m_endLine = 0; + m_endColumn = 0; + m_executionContextId = 0; + } + + std::string m_scriptId; + std::string m_url; + int m_startLine; + int m_startColumn; + int m_endLine; + int m_endColumn; + int m_executionContextId; + std::string m_hash; + Maybe m_executionContextAuxData; + Maybe m_sourceMapURL; + Maybe m_hasSourceURL; + Maybe m_isModule; + Maybe m_length; + Maybe m_stackTrace; + + rapidjson::Document m_holder; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_SCRIPT_FAILED_TO_PARSE_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.cc b/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.cc new file mode 100644 index 0000000000..c82601d293 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.cc @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_parsed_notification.h" + +namespace kraken { +namespace debugger { +std::unique_ptr ScriptParsedNotification::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ScriptParsedNotification()); + errors->push(); + + if (value->HasMember("scriptId") && (*value)["scriptId"].IsString()) { + result->m_scriptId = (*value)["scriptId"].GetString(); + } else { + errors->setName("scriptId"); + errors->addError("scriptId not found"); + } + + if (value->HasMember("url") && (*value)["url"].IsString()) { + result->m_url = (*value)["url"].GetString(); + } else { + errors->setName("url"); + errors->addError("url not found"); + } + + if (value->HasMember("startLine") && (*value)["startLine"].IsInt()) { + result->m_startLine = (*value)["startLine"].GetInt(); + } else { + errors->setName("startLine"); + errors->addError("startLine not found"); + } + + if (value->HasMember("startColumn") && (*value)["startColumn"].IsInt()) { + result->m_startColumn = (*value)["startColumn"].GetInt(); + } else { + errors->setName("startColumn"); + errors->addError("startColumn not found"); + } + + if (value->HasMember("endLine") && (*value)["endLine"].IsInt()) { + result->m_endLine = (*value)["endLine"].GetInt(); + } else { + errors->setName("endLine"); + errors->addError("endLine not found"); + } + + if (value->HasMember("endColumn") && (*value)["endColumn"].IsInt()) { + result->m_endColumn = (*value)["endColumn"].GetInt(); + } else { + errors->setName("endColumn"); + errors->addError("endColumn not found"); + } + + if (value->HasMember("executionContextId") && (*value)["executionContextId"].IsInt()) { + result->m_executionContextId = (*value)["executionContextId"].GetInt(); + } else { + errors->setName("executionContextId"); + errors->addError("executionContextId not found"); + } + + if (value->HasMember("hash") && (*value)["hash"].IsString()) { + result->m_hash = (*value)["hash"].GetString(); + } else { + errors->setName("hash"); + errors->addError("hash not found"); + } + + if (value->HasMember("executionContextAuxData")) { + result->m_executionContextAuxData = + std::make_unique((*value)["executionContextAuxData"], result->m_holder.GetAllocator()); + } + + if (value->HasMember("isLiveEdit")) { + errors->setName("isLiveEdit"); + if ((*value)["isLiveEdit"].IsBool()) { + result->m_isLiveEdit = (*value)["isLiveEdit"].GetBool(); + } else { + errors->addError("isLiveEdit should be bool"); + } + } + + if (value->HasMember("sourceMapURL")) { + errors->setName("sourceMapURL"); + if ((*value)["sourceMapURL"].IsString()) { + result->m_sourceMapURL = (*value)["sourceMapURL"].GetString(); + } else { + errors->addError("sourceMapURL should be string"); + } + } + + if (value->HasMember("hasSourceURL")) { + errors->setName("hasSourceURL"); + if ((*value)["hasSourceURL"].IsBool()) { + result->m_hasSourceURL = (*value)["hasSourceURL"].GetBool(); + } else { + errors->addError("hasSourceURL should be boolean"); + } + } + + if (value->HasMember("isModule")) { + errors->setName("isModule"); + if ((*value)["isModule"].IsBool()) { + result->m_isModule = (*value)["isModule"].GetBool(); + } else { + errors->addError("isModule should be boolean"); + } + } + + if (value->HasMember("length")) { + errors->setName("length"); + if ((*value)["length"].IsInt()) { + result->m_length = (*value)["length"].GetInt(); + } else { + errors->addError("length should be int"); + } + } + + if (value->HasMember("stackTrace")) { + errors->setName("stackTrace"); + if ((*value)["stackTrace"].IsObject()) { + rapidjson::Value _stack = (*value)["stackTrace"].GetObject(); + result->m_stackTrace = StackTrace::fromValue(&_stack, errors); + } else { + errors->addError("stackTrace should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ScriptParsedNotification::toValue(rapidjson::Document::AllocatorType &allocator) const { + + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("scriptId", m_scriptId, allocator); + result.AddMember("url", m_url, allocator); + result.AddMember("startLine", m_startLine, allocator); + result.AddMember("startColumn", m_startColumn, allocator); + result.AddMember("endLine", m_endLine, allocator); + result.AddMember("endColumn", m_endColumn, allocator); + result.AddMember("executionContextId", m_executionContextId, allocator); + result.AddMember("hash", m_hash, allocator); + if (m_executionContextAuxData.isJust()) + result.AddMember("executionContextAuxData", *m_executionContextAuxData.fromJust(), allocator); + if (m_isLiveEdit.isJust()) result.AddMember("isLiveEdit", m_isLiveEdit.fromJust(), allocator); + if (m_sourceMapURL.isJust()) result.AddMember("sourceMapURL", m_sourceMapURL.fromJust(), allocator); + if (m_hasSourceURL.isJust()) result.AddMember("hasSourceURL", m_hasSourceURL.fromJust(), allocator); + if (m_isModule.isJust()) result.AddMember("isModule", m_isModule.fromJust(), allocator); + if (m_length.isJust()) result.AddMember("length", m_length.fromJust(), allocator); + if (m_stackTrace.isJust()) { + result.AddMember("stackTrace", m_stackTrace.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.h b/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.h new file mode 100644 index 0000000000..c366fa1d6a --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_parsed_notification.h @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PARSED_NOTIFICATION_H +#define KRAKEN_DEBUGGER_PARSED_NOTIFICATION_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/location.h" +#include "inspector/protocol/stacktrace.h" +#include +#include + +namespace kraken { +namespace debugger { +class ScriptParsedNotification { + KRAKEN_DISALLOW_COPY(ScriptParsedNotification); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ScriptParsedNotification() {} + + std::string getScriptId() { + return m_scriptId; + } + void setScriptId(const std::string &value) { + m_scriptId = value; + } + + std::string getUrl() { + return m_url; + } + void setUrl(const std::string &value) { + m_url = value; + } + + int getStartLine() { + return m_startLine; + } + void setStartLine(int value) { + m_startLine = value; + } + + int getStartColumn() { + return m_startColumn; + } + void setStartColumn(int value) { + m_startColumn = value; + } + + int getEndLine() { + return m_endLine; + } + void setEndLine(int value) { + m_endLine = value; + } + + int getEndColumn() { + return m_endColumn; + } + void setEndColumn(int value) { + m_endColumn = value; + } + + int getExecutionContextId() { + return m_executionContextId; + } + void setExecutionContextId(int value) { + m_executionContextId = value; + } + + std::string getHash() { + return m_hash; + } + void setHash(const std::string &value) { + m_hash = value; + } + + bool hasExecutionContextAuxData() { + return m_executionContextAuxData.isJust(); + } + rapidjson::Value *getExecutionContextAuxData(rapidjson::Value *defaultValue) { + return m_executionContextAuxData.isJust() ? m_executionContextAuxData.fromJust() : defaultValue; + } + void setExecutionContextAuxData(std::unique_ptr value) { + m_executionContextAuxData = std::move(value); + } + + bool hasIsLiveEdit() { + return m_isLiveEdit.isJust(); + } + bool getIsLiveEdit(bool defaultValue) { + return m_isLiveEdit.isJust() ? m_isLiveEdit.fromJust() : defaultValue; + } + void setIsLiveEdit(bool value) { + m_isLiveEdit = value; + } + + bool hasSourceMapURL() { + return m_sourceMapURL.isJust(); + } + std::string getSourceMapURL(const std::string &defaultValue) { + return m_sourceMapURL.isJust() ? m_sourceMapURL.fromJust() : defaultValue; + } + void setSourceMapURL(const std::string &value) { + m_sourceMapURL = value; + } + + bool hasHasSourceURL() { + return m_hasSourceURL.isJust(); + } + bool getHasSourceURL(bool defaultValue) { + return m_hasSourceURL.isJust() ? m_hasSourceURL.fromJust() : defaultValue; + } + void setHasSourceURL(bool value) { + m_hasSourceURL = value; + } + + bool hasIsModule() { + return m_isModule.isJust(); + } + bool getIsModule(bool defaultValue) { + return m_isModule.isJust() ? m_isModule.fromJust() : defaultValue; + } + void setIsModule(bool value) { + m_isModule = value; + } + + bool hasLength() { + return m_length.isJust(); + } + int getLength(int defaultValue) { + return m_length.isJust() ? m_length.fromJust() : defaultValue; + } + void setLength(int value) { + m_length = value; + } + + bool hasStackTrace() { + return m_stackTrace.isJust(); + } + StackTrace *getStackTrace(StackTrace *defaultValue) { + return m_stackTrace.isJust() ? m_stackTrace.fromJust() : defaultValue; + } + void setStackTrace(std::unique_ptr value) { + m_stackTrace = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ScriptParsedNotificationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + UrlSet = 1 << 2, + StartLineSet = 1 << 3, + StartColumnSet = 1 << 4, + EndLineSet = 1 << 5, + EndColumnSet = 1 << 6, + ExecutionContextIdSet = 1 << 7, + HashSet = 1 << 8, + AllFieldsSet = (ScriptIdSet | UrlSet | StartLineSet | StartColumnSet | EndLineSet | EndColumnSet | + ExecutionContextIdSet | HashSet | 0) + }; + + ScriptParsedNotificationBuilder &setScriptId(const std::string &value) { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setUrl(const std::string &value) { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setStartLine(int value) { + static_assert(!(STATE & StartLineSet), "property startLine should not be set yet"); + m_result->setStartLine(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setStartColumn(int value) { + static_assert(!(STATE & StartColumnSet), "property startColumn should not be set yet"); + m_result->setStartColumn(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setEndLine(int value) { + static_assert(!(STATE & EndLineSet), "property endLine should not be set yet"); + m_result->setEndLine(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setEndColumn(int value) { + static_assert(!(STATE & EndColumnSet), "property endColumn should not be set yet"); + m_result->setEndColumn(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setExecutionContextId(int value) { + static_assert(!(STATE & ExecutionContextIdSet), "property executionContextId should not be set yet"); + m_result->setExecutionContextId(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setHash(const std::string &value) { + static_assert(!(STATE & HashSet), "property hash should not be set yet"); + m_result->setHash(value); + return castState(); + } + + ScriptParsedNotificationBuilder &setExecutionContextAuxData(std::unique_ptr value) { + m_result->setExecutionContextAuxData(std::move(value)); + return *this; + } + + ScriptParsedNotificationBuilder &setIsLiveEdit(bool value) { + m_result->setIsLiveEdit(value); + return *this; + } + + ScriptParsedNotificationBuilder &setSourceMapURL(const std::string &value) { + m_result->setSourceMapURL(value); + return *this; + } + + ScriptParsedNotificationBuilder &setHasSourceURL(bool value) { + m_result->setHasSourceURL(value); + return *this; + } + + ScriptParsedNotificationBuilder &setIsModule(bool value) { + m_result->setIsModule(value); + return *this; + } + + ScriptParsedNotificationBuilder &setLength(int value) { + m_result->setLength(value); + return *this; + } + + ScriptParsedNotificationBuilder &setStackTrace(std::unique_ptr value) { + m_result->setStackTrace(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ScriptParsedNotification; + ScriptParsedNotificationBuilder() : m_result(new ScriptParsedNotification()) {} + + template ScriptParsedNotificationBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ScriptParsedNotificationBuilder<0> create() { + return ScriptParsedNotificationBuilder<0>(); + } + +private: + ScriptParsedNotification() { + m_startLine = 0; + m_startColumn = 0; + m_endLine = 0; + m_endColumn = 0; + m_executionContextId = 0; + } + + std::string m_scriptId; + std::string m_url; + int m_startLine; + int m_startColumn; + int m_endLine; + int m_endColumn; + int m_executionContextId; + std::string m_hash; + Maybe m_executionContextAuxData; + Maybe m_isLiveEdit; + Maybe m_sourceMapURL; + Maybe m_hasSourceURL; + Maybe m_isModule; + Maybe m_length; + Maybe m_stackTrace; + + rapidjson::Document m_holder; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PARSED_NOTIFICATION_H diff --git a/plugins/devtools/bridge/inspector/protocol/script_position.cc b/plugins/devtools/bridge/inspector/protocol/script_position.cc new file mode 100644 index 0000000000..93615ce3f9 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_position.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_position.h" + +namespace kraken { +namespace debugger { +std::unique_ptr ScriptPosition::fromValue(rapidjson::Value *value, + kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new ScriptPosition()); + + errors->push(); + errors->setName("lineNumber"); + if (value->HasMember("lineNumber") && (*value)["lineNumber"].IsInt()) { + result->m_lineNumber = (*value)["lineNumber"].GetInt(); + } else { + errors->addError("lineNumber not found"); + } + + errors->setName("columnNumber"); + if (value->HasMember("columnNumber") && (*value)["columnNumber"].IsInt()) { + result->m_columnNumber = (*value)["columnNumber"].GetInt(); + } else { + errors->addError("columnNumber not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value ScriptPosition::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.AddMember("lineNumber", m_lineNumber, allocator); + result.AddMember("columnNumber", m_columnNumber, allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/script_position.h b/plugins/devtools/bridge/inspector/protocol/script_position.h new file mode 100644 index 0000000000..f61d478d00 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/script_position.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_SCRIPT_POSITION_H +#define KRAKEN_DEBUGGER_SCRIPT_POSITION_H + +#include "inspector/protocol/error_support.h" +#include "kraken_foundation.h" +#include +#include +#include + +namespace kraken::debugger { +class ScriptPosition { + KRAKEN_DISALLOW_COPY(ScriptPosition); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~ScriptPosition() {} + + int getLineNumber() { + return m_lineNumber; + } + + void setLineNumber(int value) { + m_lineNumber = value; + } + + int getColumnNumber() { + return m_columnNumber; + } + + void setColumnNumber(int value) { + m_columnNumber = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class ScriptPositionBuilder { + public: + enum { + NoFieldsSet = 0, + LineNumberSet = 1 << 1, + ColumnNumberSet = 1 << 2, + AllFieldsSet = (LineNumberSet | ColumnNumberSet | 0) + }; + + ScriptPositionBuilder &setLineNumber(int value) { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + ScriptPositionBuilder &setColumnNumber(int value) { + static_assert(!(STATE & ColumnNumberSet), "property columnNumber should not be set yet"); + m_result->setColumnNumber(value); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ScriptPosition; + + ScriptPositionBuilder() : m_result(new ScriptPosition()) {} + + template ScriptPositionBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static ScriptPositionBuilder<0> create() { + return ScriptPositionBuilder<0>(); + } + +private: + ScriptPosition() { + m_lineNumber = 0; + m_columnNumber = 0; + } + + int m_lineNumber; + int m_columnNumber; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_SCRIPT_POSITION_H diff --git a/plugins/devtools/bridge/inspector/protocol/search_match.cc b/plugins/devtools/bridge/inspector/protocol/search_match.cc new file mode 100644 index 0000000000..8bd390862b --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/search_match.cc @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "search_match.h" + +namespace kraken { +namespace debugger { +std::unique_ptr SearchMatch::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new SearchMatch()); + errors->push(); + + if (value->HasMember("lineNumber") && (*value)["lineNumber"].IsDouble()) { + result->m_lineNumber = (*value)["lineNumber"].GetDouble(); + } else { + errors->setName("lineNumber"); + errors->addError("lineNumber not found"); + } + + if (value->HasMember("lineContent") && (*value)["lineContent"].IsString()) { + result->m_lineContent = (*value)["lineContent"].GetString(); + } else { + errors->setName("lineContent"); + errors->addError("lineContent not found"); + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value SearchMatch::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + result.AddMember("lineNumber", m_lineNumber, allocator); + result.AddMember("lineContent", m_lineContent, allocator); + return result; +} +} // namespace debugger +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/search_match.h b/plugins/devtools/bridge/inspector/protocol/search_match.h new file mode 100644 index 0000000000..c0cba70690 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/search_match.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_SEARCH_MATCH_H +#define KRAKEN_DEBUGGER_SEARCH_MATCH_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include +#include +#include +#include + +namespace kraken::debugger { +class SearchMatch { + KRAKEN_DISALLOW_COPY(SearchMatch); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~SearchMatch() {} + + double getLineNumber() { + return m_lineNumber; + } + + void setLineNumber(double value) { + m_lineNumber = value; + } + + std::string getLineContent() { + return m_lineContent; + } + + void setLineContent(const std::string &value) { + m_lineContent = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class SearchMatchBuilder { + public: + enum { + NoFieldsSet = 0, + LineNumberSet = 1 << 1, + LineContentSet = 1 << 2, + AllFieldsSet = (LineNumberSet | LineContentSet | 0) + }; + + SearchMatchBuilder &setLineNumber(double value) { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + SearchMatchBuilder &setLineContent(const std::string &value) { + static_assert(!(STATE & LineContentSet), "property lineContent should not be set yet"); + m_result->setLineContent(value); + return castState(); + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class SearchMatch; + + SearchMatchBuilder() : m_result(new SearchMatch()) {} + + template SearchMatchBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static SearchMatchBuilder<0> create() { + return SearchMatchBuilder<0>(); + } + +private: + SearchMatch() { + m_lineNumber = 0; + } + + double m_lineNumber; + std::string m_lineContent; +}; + +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_SEARCH_MATCH_H diff --git a/plugins/devtools/bridge/inspector/protocol/stacktrace.cc b/plugins/devtools/bridge/inspector/protocol/stacktrace.cc new file mode 100644 index 0000000000..7d818d6f87 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/stacktrace.cc @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "stacktrace.h" + +namespace kraken::debugger { +std::unique_ptr StackTrace::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new StackTrace()); + + errors->push(); + + if (value->HasMember("description")) { + errors->setName("description"); + if ((*value)["description"].IsString()) { + result->m_description = (*value)["description"].GetString(); + } else { + errors->addError("description should be string"); + } + } + + if (value->HasMember("callFrames") && (*value)["callFrames"].IsArray()) { + auto arr = std::make_unique>>(); + for (auto &v : (*value)["callFrames"].GetArray()) { + if (v.IsObject()) { + rapidjson::Value _callFrames = rapidjson::Value(v.GetObject()); + arr->push_back(CallFrame::fromValue(&_callFrames, errors)); + } + } + result->m_callFrames = std::move(arr); + } else { + errors->setName("callFrames"); + errors->addError("callFrames not found"); + } + + if (value->HasMember("parent")) { + errors->setName("parent"); + if ((*value)["parent"].IsObject()) { + rapidjson::Value _parent = rapidjson::Value((*value)["parent"].GetObject()); + result->m_parent = StackTrace::fromValue(&_parent, errors); + } else { + errors->addError("parent should be object"); + } + } + + if (value->HasMember("parentId")) { + errors->setName("parentId"); + if ((*value)["parentId"].IsObject()) { + rapidjson::Value _parent_id = rapidjson::Value((*value)["parentId"].GetObject()); + result->m_parentId = StackTraceId::fromValue(&_parent_id, errors); + } else { + errors->addError("parentId should be object"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value StackTrace::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.SetObject(); + + if (m_description.isJust()) { + result.AddMember("description", m_description.fromJust(), allocator); + } + rapidjson::Value arr = rapidjson::Value(rapidjson::kArrayType); + arr.SetArray(); + + for (const auto &val : *m_callFrames.get()) { + arr.PushBack(val->toValue(allocator), allocator); + } + result.AddMember("callFrames", arr, allocator); + + if (m_parent.isJust()) { + result.AddMember("parent", m_parent.fromJust()->toValue(allocator), allocator); + } + if (m_parentId.isJust()) { + result.AddMember("parentId", m_parentId.fromJust()->toValue(allocator), allocator); + } + return result; +} +} // namespace kraken diff --git a/plugins/devtools/bridge/inspector/protocol/stacktrace.h b/plugins/devtools/bridge/inspector/protocol/stacktrace.h new file mode 100644 index 0000000000..4a86b9c217 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/stacktrace.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_STACKTRACE_H +#define KRAKEN_DEBUGGER_STACKTRACE_H + +#include "inspector/protocol/call_frame.h" +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "inspector/protocol/stacktrace_id.h" +#include "kraken_foundation.h" +#include + +namespace kraken { +namespace debugger { +class StackTrace { + KRAKEN_DISALLOW_COPY(StackTrace); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~StackTrace() {} + + bool hasDescription() { + return m_description.isJust(); + } + + std::string getDescription(const std::string &defaultValue) { + return m_description.isJust() ? m_description.fromJust() : defaultValue; + } + + void setDescription(const std::string &value) { + m_description = value; + } + + std::vector> *getCallFrames() { + return m_callFrames.get(); + } + + void setCallFrames(std::unique_ptr>> value) { + m_callFrames = std::move(value); + } + + bool hasParent() { + return m_parent.isJust(); + } + + StackTrace *getParent(StackTrace *defaultValue) { + return m_parent.isJust() ? m_parent.fromJust() : defaultValue; + } + + void setParent(std::unique_ptr value) { + m_parent = std::move(value); + } + + bool hasParentId() { + return m_parentId.isJust(); + } + + StackTraceId *getParentId(StackTraceId *defaultValue) { + return m_parentId.isJust() ? m_parentId.fromJust() : defaultValue; + } + + void setParentId(std::unique_ptr value) { + m_parentId = std::move(value); + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class StackTraceBuilder { + public: + enum { NoFieldsSet = 0, CallFramesSet = 1 << 1, AllFieldsSet = (CallFramesSet | 0) }; + + StackTraceBuilder &setDescription(const std::string &value) { + m_result->setDescription(value); + return *this; + } + + StackTraceBuilder & + setCallFrames(std::unique_ptr>> value) { + static_assert(!(STATE & CallFramesSet), "property callFrames should not be set yet"); + m_result->setCallFrames(std::move(value)); + return castState(); + } + + StackTraceBuilder &setParent(std::unique_ptr value) { + m_result->setParent(std::move(value)); + return *this; + } + + StackTraceBuilder &setParentId(std::unique_ptr value) { + m_result->setParentId(std::move(value)); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class StackTrace; + + StackTraceBuilder() : m_result(new StackTrace()) {} + + template StackTraceBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static StackTraceBuilder<0> create() { + return StackTraceBuilder<0>(); + } + +private: + StackTrace() {} + + Maybe m_description; + std::unique_ptr>> m_callFrames; + Maybe m_parent; + Maybe m_parentId; +}; +} // namespace debugger +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_STACKTRACE_H diff --git a/plugins/devtools/bridge/inspector/protocol/stacktrace_id.cc b/plugins/devtools/bridge/inspector/protocol/stacktrace_id.cc new file mode 100644 index 0000000000..ea7f6deca9 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/stacktrace_id.cc @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "stacktrace_id.h" + +namespace kraken::debugger { +std::unique_ptr StackTraceId::fromValue(rapidjson::Value *value, kraken::debugger::ErrorSupport *errors) { + if (!value || !value->IsObject()) { + errors->addError("object expected"); + return nullptr; + } + + std::unique_ptr result(new StackTraceId()); + + errors->push(); + + if (value->HasMember("id") && (*value)["id"].IsString()) { + result->m_id = (*value)["id"].GetString(); + } else { + errors->setName("id"); + errors->addError("id not found"); + } + + if (value->HasMember("debuggerId")) { + errors->setName("debuggerId"); + if ((*value)["debuggerId"].IsString()) { + result->m_debuggerId = (*value)["debuggerId"].GetString(); + } else { + errors->addError("debuggerId should be string"); + } + } + + errors->pop(); + if (errors->hasErrors()) return nullptr; + return result; +} + +rapidjson::Value StackTraceId::toValue(rapidjson::Document::AllocatorType &allocator) const { + rapidjson::Value result = rapidjson::Value(rapidjson::kObjectType); + result.AddMember("id", m_id, allocator); + if (m_debuggerId.isJust()) { + result.AddMember("debuggerId", m_debuggerId.fromJust(), allocator); + } + return result; +} +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/protocol/stacktrace_id.h b/plugins/devtools/bridge/inspector/protocol/stacktrace_id.h new file mode 100644 index 0000000000..fbb6db50c1 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/stacktrace_id.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_STACKTRACE_ID_H +#define KRAKEN_DEBUGGER_STACKTRACE_ID_H + +#include "inspector/protocol/error_support.h" +#include "inspector/protocol/maybe.h" +#include "kraken_foundation.h" +#include + +#include + +namespace kraken::debugger { +class StackTraceId { + KRAKEN_DISALLOW_COPY(StackTraceId); + +public: + static std::unique_ptr fromValue(rapidjson::Value *value, ErrorSupport *errors); + + ~StackTraceId() {} + + std::string getId() { + return m_id; + } + + void setId(const std::string &value) { + m_id = value; + } + + bool hasDebuggerId() { + return m_debuggerId.isJust(); + } + + std::string getDebuggerId(const std::string &defaultValue) { + return m_debuggerId.isJust() ? m_debuggerId.fromJust() : defaultValue; + } + + void setDebuggerId(const std::string &value) { + m_debuggerId = value; + } + + rapidjson::Value toValue(rapidjson::Document::AllocatorType &allocator) const; + + template class StackTraceIdBuilder { + public: + enum { NoFieldsSet = 0, IdSet = 1 << 1, AllFieldsSet = (IdSet | 0) }; + + StackTraceIdBuilder &setId(const std::string &value) { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + StackTraceIdBuilder &setDebuggerId(const std::string &value) { + m_result->setDebuggerId(value); + return *this; + } + + std::unique_ptr build() { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class StackTraceId; + + StackTraceIdBuilder() : m_result(new StackTraceId()) {} + + template StackTraceIdBuilder &castState() { + return *reinterpret_cast *>(this); + } + + std::unique_ptr m_result; + }; + + static StackTraceIdBuilder<0> create() { + return StackTraceIdBuilder<0>(); + } + +private: + StackTraceId() {} + + std::string m_id; + Maybe m_debuggerId; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_STACKTRACE_ID_H diff --git a/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.cc b/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.cc new file mode 100644 index 0000000000..5ac6ec5dcb --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "inspector/protocol/uber_dispatcher.h" + +namespace kraken::debugger { +UberDispatcher::UberDispatcher(debugger::FrontendChannel *frontendChannel) : m_frontendChannel(frontendChannel) {} + +UberDispatcher::~UberDispatcher() = default; + +void UberDispatcher::registerBackend(const std::string &name, std::unique_ptr dispatcher) { + m_dispatchers[name] = std::move(dispatcher); +} + +void UberDispatcher::setupRedirects(const std::unordered_map &redirects) { + for (const auto &pair : redirects) { + m_redirects[pair.first] = pair.second; + } +} + +bool UberDispatcher::canDispatch(const std::string &in_method) { + std::string method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) { + method = redirectIt->second; + } + return findDispatcher(method) != nullptr; +} + +void UberDispatcher::dispatch(uint64_t callId, const std::string &in_method, debugger::JSONObject message) { + std::string method = in_method; + auto redirectIt = m_redirects.find(method); + if (redirectIt != m_redirects.end()) { + method = redirectIt->second; + } + auto dispatcher = findDispatcher(method); + if (!dispatcher) { + Internal::reportProtocolErrorTo(m_frontendChannel, callId, kMethodNotFound, + "'" + method + "' wasn't found", nullptr); + return; + } + dispatcher->dispatch(callId, method, std::move(message)); +} + +debugger::DispatcherBase *UberDispatcher::findDispatcher(const std::string &method) { + auto dotIndex = method.find('.'); + if (dotIndex == std::string::npos) { + return nullptr; + } + std::string domain = method.substr(0, dotIndex); + auto it = m_dispatchers.find(domain); + if (it == m_dispatchers.end()) { + return nullptr; + } + if (!it->second->canDispatch(method)) { + KRAKEN_LOG(ERROR) << "can not dispatch method: " << method; + return nullptr; + } + return it->second.get(); +} + +} // namespace kraken::debugger diff --git a/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.h b/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.h new file mode 100644 index 0000000000..ca81cd8a81 --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol/uber_dispatcher.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_UBER_DISPATCHER_H +#define KRAKEN_DEBUGGER_UBER_DISPATCHER_H + +#include "inspector/protocol/dispatcher_base.h" + +#include + +namespace kraken::debugger { +class UberDispatcher { +private: + KRAKEN_DISALLOW_COPY(UberDispatcher); + +public: + explicit UberDispatcher(debugger::FrontendChannel *); + + void registerBackend(const std::string &name, std::unique_ptr); + + void setupRedirects(const std::unordered_map &); + + bool canDispatch(const std::string &method); + + void dispatch(uint64_t callId, const std::string &method, + debugger::JSONObject message /*params. move only*/); + + FrontendChannel *channel() { + return m_frontendChannel; + } + + virtual ~UberDispatcher(); + +private: + debugger::DispatcherBase *findDispatcher(const std::string &method); + debugger::FrontendChannel *m_frontendChannel; + std::unordered_map m_redirects; + using DispatcherPointer = std::unique_ptr; + std::unordered_map m_dispatchers; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_UBER_DISPATCHER_H diff --git a/plugins/devtools/bridge/inspector/protocol_handler.h b/plugins/devtools/bridge/inspector/protocol_handler.h new file mode 100644 index 0000000000..0771a9957a --- /dev/null +++ b/plugins/devtools/bridge/inspector/protocol_handler.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEBUGGER_PROTOCOL_HANDLER_H +#define KRAKEN_DEBUGGER_PROTOCOL_HANDLER_H + +namespace kraken::debugger { +class ProtocolHandler { +public: + virtual ~ProtocolHandler() {} + virtual void handlePageReload() = 0; +}; +} // namespace kraken + +#endif // KRAKEN_DEBUGGER_PROTOCOL_HANDLER_H diff --git a/plugins/devtools/bridge/inspector/rpc_session.cc b/plugins/devtools/bridge/inspector/rpc_session.cc new file mode 100644 index 0000000000..5b7057c879 --- /dev/null +++ b/plugins/devtools/bridge/inspector/rpc_session.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "rpc_session.h" +#include "kraken_bridge.h" + +namespace kraken::debugger { + +void RPCSession::handleRequest(Request req) { + if (m_debug_session != nullptr) { + m_debug_session->dispatchProtocolMessage(std::move(req)); + } +} + +void RPCSession::_on_message(const std::string &message) { + rapidjson::Document doc; + doc.Parse(message.c_str()); + if (doc.HasParseError() || !doc.IsObject()) { + return; + } + if (doc.HasMember("method")) { + std::string method = doc["method"].GetString(); + auto dotIndex = method.find('.'); + if (dotIndex == std::string::npos) { + return; + } + std::string domain = method.substr(0, dotIndex); + std::string subMethod = method.substr(dotIndex + 1); + + if (m_debug_session->isDebuggerPaused()) { + if (domain == "Runtime" || (domain == "Debugger" && subMethod == "evaluateOnCallFrame")) { + auto *ctx = new SessionContext{this, message}; + registerUITask(_contextId, [](void *ptr) { + auto *ctx = reinterpret_cast(ptr); + rapidjson::Document doc; + doc.Parse(ctx->message.c_str()); + if (ctx->session->dispose()) return; + ctx->session->handleRequest(serializeRequest(std::move(doc))); + }, ctx); + } else { + this->handleRequest(serializeRequest(std::move(doc))); + } + } else { + if ((domain == "Runtime")) { + auto *ctx = new SessionContext{this, message}; + kraken::getInspectorDartMethod()->postTaskToUiThread(_contextId, reinterpret_cast(ctx), [](void *ptr) { + auto *ctx = reinterpret_cast(ptr); + rapidjson::Document doc; + doc.Parse(ctx->message.c_str()); + if (ctx->session->dispose()) return; + ctx->session->handleRequest(serializeRequest(std::move(doc))); + }); + } else { + this->handleRequest(serializeRequest(std::move(doc))); + } + } + } else { + KRAKEN_LOG(ERROR) << "[rpc] session " << _contextId << ":unknown JSON-RPC message -> " << message; + } +} + +void DartRPC::send(int32_t contextId, const std::string &msg) { + if (std::this_thread::get_id() == getUIThreadId()) { + auto ctx = new RPCContext{contextId, msg}; + kraken::getUIDartMethod()->postTaskToInspectorThread(contextId, reinterpret_cast(ctx), [](void *ptr) { + auto ctx = reinterpret_cast(ptr); + getInspectorDartMethod()->inspectorMessage(ctx->contextId, ctx->message.c_str()); + delete ctx; + }); + } else { + getInspectorDartMethod()->inspectorMessage(contextId, msg.c_str()); + } +} + +void DartRPC::setOnMessageCallback(int32_t contextId, void *rpcSession, InspectorMessageCallback callback) { + getInspectorDartMethod()->registerInspectorMessageCallback(contextId, rpcSession, callback); +} + +} diff --git a/plugins/devtools/bridge/inspector/rpc_session.h b/plugins/devtools/bridge/inspector/rpc_session.h new file mode 100644 index 0000000000..b68e3f26f5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/rpc_session.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_RPC_SESSION_H +#define KRAKENBRIDGE_RPC_SESSION_H + +#include + +#include "inspector/service/rpc/object_serializer.h" +#include "inspector/service/rpc/protocol.h" +#include "protocol_handler.h" +#include "dart_methods.h" +#include "inspector/inspector_session.h" +#include + +namespace kraken::debugger { + +class InspectorSession; + +class DartRPC { +public: + void send(int32_t contextId, const std::string &msg); + void setOnMessageCallback(int32_t contextId, void* rpcSession, InspectorMessageCallback callback); +private: + struct RPCContext { + int32_t contextId; + std::string message; + }; +}; + +class RPCSession { +public: + explicit RPCSession(size_t contextId, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, std::shared_ptr handler) : _contextId(contextId) { + m_debug_session = std::make_shared(this, ctx, globalObject, handler); + m_handler = std::make_shared(); + InspectorMessageCallback callback = [](void *rpcSession, const char *message) -> void { + auto session = reinterpret_cast_ptr(rpcSession); + if (session->dispose()) { + return; + } + session->_on_message(message); + }; + this->m_handler->setOnMessageCallback(contextId, this, callback); + } + + ~RPCSession() { + m_disposed = true; + } + + bool dispose() { + return m_disposed; + } + + void handleRequest(Request req); + + void sendRequest(Request req) { + auto message = deserializeRequest(std::move(req)); + this->_send_text(message); + }; + + void sendResponse(Response resp) { + auto message = deserializeResponse(std::move(resp)); + this->_send_text(message); + }; + + void sendError(Error err) { + auto message = deserializeError(std::move(err)); + this->_send_text(message); + }; + + void sendEvent(Event event) { + auto message = deserializeEvent(std::move(event)); + this->_send_text(message); + }; + + size_t sessionId() const { + return _contextId; + } + +private: + void _send_text(const std::string &message) { + if (this->m_handler) { + this->m_handler->send(_contextId, message); + } + } + + struct SessionContext { + RPCSession *session; + const std::string message; + }; + + void _on_message(const std::string &message); + +private: + std::shared_ptr m_handler; + std::shared_ptr m_debug_session; + size_t _contextId; + std::atomic m_disposed{false}; +}; + +} // namespace kraken::debugger::jsonRpc + +#endif // KRAKENBRIDGE_RPC_SESSION_H diff --git a/plugins/devtools/bridge/inspector/service/rpc/object_serializer.h b/plugins/devtools/bridge/inspector/service/rpc/object_serializer.h new file mode 100644 index 0000000000..55eec34fd5 --- /dev/null +++ b/plugins/devtools/bridge/inspector/service/rpc/object_serializer.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_JSON_RPC_OBJECT_MAPPBER_H +#define KRAKEN_JSON_RPC_OBJECT_MAPPBER_H + +#include +#include + +#include "inspector/service/rpc/protocol.h" + +namespace kraken::debugger { + +namespace { +JSONObject clone(rapidjson::Document *doc, JSONObject value) { + return JSONObject(value, doc->GetAllocator()); +} +} // namespace + +inline Request serializeRequest(rapidjson::Document document) { + uint64_t id = document.HasMember("id") ? document["id"].GetUint64() : -1; + std::string method = document.HasMember("method") ? document["method"].GetString() : "null"; + JSONObject params = + document.HasMember("params") ? document["params"].GetObject() : JSONObject(rapidjson::kObjectType); + return {id, method, std::move(params)}; +} + +inline Response serializeResponse(rapidjson::Document document) { + uint64_t id = document.HasMember("id") ? document["id"].GetUint64() : -1; + if (document.HasMember("result")) { + } + JSONObject result = + document.HasMember("result") ? document["result"].GetObject() : JSONObject(rapidjson::kObjectType); + JSONObject error = document.HasMember("error") ? document["error"].GetObject() : JSONObject(rapidjson::kObjectType); + return {id, std::move(result), std::move(error)}; +} + +inline std::string deserializeRequest(Request req) { + rapidjson::Document doc; + doc.SetObject(); + doc.AddMember("id", req.id, doc.GetAllocator()); + rapidjson::Value method; + method.SetString(req.method.c_str(), doc.GetAllocator()); + doc.AddMember("method", method, doc.GetAllocator()); + doc.AddMember("params", clone(&doc, std::move(req.params)), doc.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +inline std::string deserializeResponse(Response resp) { + rapidjson::Document doc; + doc.SetObject(); + doc.AddMember("id", resp.id, doc.GetAllocator()); + if (!resp.hasError) { + doc.AddMember("result", clone(&doc, std::move(resp.result)), doc.GetAllocator()); + } + + if (resp.hasError) { + doc.AddMember("error", clone(&doc, std::move(resp.error)), doc.GetAllocator()); + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +inline std::string deserializeError(Error error) { + rapidjson::Document doc; + doc.SetObject(); + + JSONObject content; + content.SetObject(); + content.AddMember("code", error.code, doc.GetAllocator()); + rapidjson::Value method; + method.SetString(error.message.c_str(), doc.GetAllocator()); + content.AddMember("message", method, doc.GetAllocator()); + content.AddMember("data", clone(&doc, std::move(error.data)), doc.GetAllocator()); + + doc.AddMember("error", content, doc.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +inline std::string deserializeEvent(Event event) { + rapidjson::Document doc; + doc.SetObject(); + + if (!event.method.empty()) { + rapidjson::Value method; + method.SetString(event.method.c_str(), doc.GetAllocator()); + doc.AddMember("method", method, doc.GetAllocator()); + } + doc.AddMember("params", clone(&doc, std::move(event.params)), doc.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +} // namespace kraken +#endif // KRAKEN_JSON_RPC_OBJECT_MAPPBER_H diff --git a/plugins/devtools/bridge/inspector/service/rpc/protocol.h b/plugins/devtools/bridge/inspector/service/rpc/protocol.h new file mode 100644 index 0000000000..4aef00964c --- /dev/null +++ b/plugins/devtools/bridge/inspector/service/rpc/protocol.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ +// JSON-RPC 2.0 Protocol +// https://www.jsonrpc.org/specification +// + +#ifndef KRAKEN_JSON_RPC_PROTOCOL_H +#define KRAKEN_JSON_RPC_PROTOCOL_H + +#include +#include +#include +#include "kraken_foundation.h" + +namespace kraken::debugger { + +typedef rapidjson::Value JSONObject; + +enum ErrorCode { + kParseError = -32700, + kInvalidRequest = -32600, + kMethodNotFound = -32601, + kInvalidParams = -32602, + kInternalError = -32603, + kServerError = -32000, +}; + +struct Message {}; + +struct Request : Message { +public: + uint64_t id; + std::string method; + JSONObject params; + + Request() {} + + Request(uint64_t id, std::string method, JSONObject params) + : id(id), method(std::move(method)), params(std::move(params)) {} + + Request(Request &&req) : id(req.id), method(std::move(req.method)), params(std::move(req.params)) {} + + Request &operator=(Request &&req) { + id = req.id; + method = std::move(req.method); + params = std::move(req.params); + return *this; + } + +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(Request); +}; + +struct Response : Message { +public: + uint64_t id; + JSONObject result; + JSONObject error; + bool hasError; + + Response() {} + + Response(uint64_t id, JSONObject result, JSONObject error, bool hasError = false) + : id(id), result(std::move(result)), error(std::move(error)), hasError(hasError) {} + + Response(Response &&resp) + : id(resp.id), result(std::move(resp.result)), error(std::move(resp.error)), hasError(resp.hasError) {} + + Response &operator=(Response &&resp) { + id = resp.id; + result = std::move(resp.result); + error = std::move(resp.error); + hasError = resp.hasError; + return *this; + } + +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(Response); +}; + +struct Error : Message { +public: + ErrorCode code; + std::string message; + JSONObject data; + + Error() {} + + Error(ErrorCode code, std::string message, JSONObject data) + : code(code), message(std::move(message)), data(std::move(data)) {} + + Error(Error &&err) : code(err.code), message(std::move(err.message)), data(std::move(err.data)) {} + + Error &operator=(Error &&err) { + code = err.code; + message = std::move(err.message); + data = std::move(err.data); + return *this; + } + +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(Error); +}; + +struct Event : Message { +public: + std::string method; + JSONObject params; + + Event() {} + + Event(std::string method, JSONObject params) : method(std::move(method)), params(std::move(params)) {} + + Event(Event &&event) : method(std::move(event.method)), params(std::move(event.params)) {} + + Event &operator=(Event &&event) { + method = std::move(event.method); + params = std::move(event.params); + return *this; + } + +private: + KRAKEN_DISALLOW_COPY_AND_ASSIGN(Event); +}; +} // namespace kraken + +#endif // KRAKEN_JSON_RPC_PROTOCOL_H diff --git a/plugins/devtools/bridge/kraken_devtools.cc b/plugins/devtools/bridge/kraken_devtools.cc new file mode 100644 index 0000000000..2588088692 --- /dev/null +++ b/plugins/devtools/bridge/kraken_devtools.cc @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "kraken_devtools.h" +#include "kraken_bridge.h" +#include "inspector/frontdoor.h" +#include "dart_methods.h" +#include + +void attachInspector(int32_t contextId) { + JSGlobalContextRef ctx = getGlobalContextRef(contextId); + std::shared_ptr handler = std::make_shared(); + JSC::ExecState* exec = toJS(ctx); + JSC::VM& vm = exec->vm(); + JSC::JSLockHolder locker(vm); + JSC::JSGlobalObject* globalObject = vm.vmEntryGlobalObject(exec); + auto *frontDoor = new kraken::debugger::FrontDoor(contextId, ctx, globalObject->globalObject(), handler); + registerContextDisposedCallbacks(contextId, [](void *ptr) { + delete reinterpret_cast(ptr); + }, frontDoor); + + setConsoleMessageHandler(kraken::debugger::FrontDoor::handleConsoleMessage); +} + +void dispatchInspectorTask(int32_t contextId, void *context, void *callback) { + assert(std::this_thread::get_id() != getUIThreadId()); + reinterpret_cast(callback)(context); +} + +void registerInspectorDartMethods(uint64_t *methodBytes, int32_t length) { + kraken::registerInspectorDartMethods(methodBytes, length); +} + +void registerUIDartMethods(uint64_t *methodBytes, int32_t length) { + kraken::registerUIDartMethods(methodBytes, length); +} + +namespace kraken::debugger { + +void BridgeProtocolHandler::handlePageReload() { + // FIXME: reload with devtolls are not full working yet (debugger not working). + // getDartMethod()->flushUICommand(); + // getDartMethod()->reloadApp(m_bridge->contextId); +} + +} diff --git a/plugins/devtools/bridge/kraken_devtools.h b/plugins/devtools/bridge/kraken_devtools.h new file mode 100644 index 0000000000..966ac7fa80 --- /dev/null +++ b/plugins/devtools/bridge/kraken_devtools.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKEN_DEVTOOLS_KRAKEN_DEVTOOLS_H +#define KRAKEN_DEVTOOLS_KRAKEN_DEVTOOLS_H + +#include +#include "kraken_bridge.h" +#include "inspector/protocol_handler.h" +#include +#include "dart_methods.h" + +namespace kraken::debugger { + +class BridgeProtocolHandler : public ProtocolHandler { +public: + BridgeProtocolHandler() {}; + + ~BridgeProtocolHandler() {}; + + void handlePageReload() override; + +private: +}; +} + +KRAKEN_EXPORT_C +void attachInspector(int32_t contextId); +KRAKEN_EXPORT_C +void registerInspectorDartMethods(uint64_t *methodBytes, int32_t length); + +KRAKEN_EXPORT_C +void registerUIDartMethods(uint64_t *methodBytes, int32_t length); + +KRAKEN_EXPORT_C +void dispatchInspectorTask(int32_t contextId, void *context, void *callback); + +#endif //KRAKEN_DEVTOOLS_KRAKEN_DEVTOOLS_H diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitattributes b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitattributes new file mode 100644 index 0000000000..6f598bb7f9 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitattributes @@ -0,0 +1,22 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text +*.h text +*.txt text +*.md text +*.cmake text +*.svg text +*.dot text +*.yml text +*.in text +*.sh text +*.autopkg text +Dockerfile text + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.json binary \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitignore b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitignore new file mode 100644 index 0000000000..e7e8fba9bb --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitignore @@ -0,0 +1,25 @@ +/bin/* +!/bin/data +!/bin/encodings +!/bin/jsonchecker +!/bin/types +/build +/doc/html +/doc/doxygen_*.db +*.a + +# Temporary files created during CMake build +CMakeCache.txt +CMakeFiles +cmake_install.cmake +CTestTestfile.cmake +Makefile +RapidJSON*.cmake +RapidJSON.pc +Testing +/googletest +install_manifest.txt +Doxyfile +Doxyfile.zh-cn +DartConfiguration.tcl +*.nupkg diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitmodules b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitmodules new file mode 100644 index 0000000000..5e41f7c975 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/gtest"] + path = thirdparty/gtest + url = https://github.com/google/googletest.git diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.travis.yml b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.travis.yml new file mode 100644 index 0000000000..f9319f2edb --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/.travis.yml @@ -0,0 +1,98 @@ +sudo: required +dist: precise + +language: cpp +cache: + - ccache + +env: + global: + - USE_CCACHE=1 + - CCACHE_SLOPPINESS=pch_defines,time_macros + - CCACHE_COMPRESS=1 + - CCACHE_MAXSIZE=100M + - ARCH_FLAGS_x86='-m32' # #266: don't use SSE on 32-bit + - ARCH_FLAGS_x86_64='-msse4.2' # use SSE4.2 on 64-bit + - GITHUB_REPO='miloyip/rapidjson' + - secure: "HrsaCb+N66EG1HR+LWH1u51SjaJyRwJEDzqJGYMB7LJ/bfqb9mWKF1fLvZGk46W5t7TVaXRDD5KHFx9DPWvKn4gRUVkwTHEy262ah5ORh8M6n/6VVVajeV/AYt2C0sswdkDBDO4Xq+xy5gdw3G8s1A4Inbm73pUh+6vx+7ltBbk=" + +before_install: + - sudo apt-add-repository -y ppa:ubuntu-toolchain-r/test + - sudo apt-get update -qq + - sudo apt-get install -y cmake valgrind g++-multilib libc6-dbg:i386 + +matrix: + include: + # gcc + - env: CONF=release ARCH=x86 CXX11=ON + compiler: gcc + - env: CONF=release ARCH=x86_64 CXX11=ON + compiler: gcc + - env: CONF=debug ARCH=x86 CXX11=OFF + compiler: gcc + - env: CONF=debug ARCH=x86_64 CXX11=OFF + compiler: gcc + # clang + - env: CONF=debug ARCH=x86 CXX11=ON CCACHE_CPP2=yes + compiler: clang + - env: CONF=debug ARCH=x86_64 CXX11=ON CCACHE_CPP2=yes + compiler: clang + - env: CONF=debug ARCH=x86 CXX11=OFF CCACHE_CPP2=yes + compiler: clang + - env: CONF=debug ARCH=x86_64 CXX11=OFF CCACHE_CPP2=yes + compiler: clang + - env: CONF=release ARCH=x86 CXX11=ON CCACHE_CPP2=yes + compiler: clang + - env: CONF=release ARCH=x86_64 CXX11=ON CCACHE_CPP2=yes + compiler: clang + # coverage report + - env: CONF=debug ARCH=x86 CXX11=ON GCOV_FLAGS='--coverage' + compiler: gcc + cache: + - ccache + - pip + after_success: + - pip install --user cpp-coveralls + - coveralls -r .. --gcov-options '\-lp' -e thirdparty -e example -e test -e build/CMakeFiles -e include/rapidjson/msinttypes -e include/rapidjson/internal/meta.h -e include/rapidjson/error/en.h + - env: CONF=debug ARCH=x86_64 GCOV_FLAGS='--coverage' + compiler: gcc + cache: + - ccache + - pip + after_success: + - pip install --user cpp-coveralls + - coveralls -r .. --gcov-options '\-lp' -e thirdparty -e example -e test -e build/CMakeFiles -e include/rapidjson/msinttypes -e include/rapidjson/internal/meta.h -e include/rapidjson/error/en.h + - script: # Documentation task + - cd build + - cmake .. -DRAPIDJSON_HAS_STDSTRING=ON -DCMAKE_VERBOSE_MAKEFILE=ON + - make travis_doc + cache: false + addons: + apt: + packages: + - doxygen + +before_script: + - ccache -s + # hack to avoid Valgrind bug (https://bugs.kde.org/show_bug.cgi?id=326469), + # exposed by merging PR#163 (using -march=native) + # TODO: Since this bug is already fixed. Remove this when valgrind can be upgraded. + - sed -i "s/-march=native//" CMakeLists.txt + - mkdir build + +script: + - if [ "$CXX" = "clang++" ]; then export CXXFLAGS="-stdlib=libc++ ${CXXFLAGS}"; fi + - > + eval "ARCH_FLAGS=\${ARCH_FLAGS_${ARCH}}" ; + (cd build && cmake + -DRAPIDJSON_HAS_STDSTRING=ON + -DRAPIDJSON_BUILD_CXX11=$CXX11 + -DCMAKE_VERBOSE_MAKEFILE=ON + -DCMAKE_BUILD_TYPE=$CONF + -DCMAKE_CXX_FLAGS="$ARCH_FLAGS $GCOV_FLAGS" + -DCMAKE_EXE_LINKER_FLAGS=$GCOV_FLAGS + ..) + - cd build + - make tests -j 2 + - make examples -j 2 + - ctest -j 2 -V `[ "$CONF" = "release" ] || echo "-E perftest"` diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CHANGELOG.md b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CHANGELOG.md new file mode 100644 index 0000000000..0205e7b89f --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CHANGELOG.md @@ -0,0 +1,158 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## 1.1.0 - 2016-08-25 + +### Added +* Add GenericDocument ctor overload to specify JSON type (#369) +* Add FAQ (#372, #373, #374, #376) +* Add forward declaration header `fwd.h` +* Add @PlatformIO Library Registry manifest file (#400) +* Implement assignment operator for BigInteger (#404) +* Add comments support (#443) +* Adding coapp definition (#460) +* documenttest.cpp: EXPECT_THROW when checking empty allocator (470) +* GenericDocument: add implicit conversion to ParseResult (#480) +* Use with C++ linkage on Windows ARM (#485) +* Detect little endian for Microsoft ARM targets +* Check Nan/Inf when writing a double (#510) +* Add JSON Schema Implementation (#522) +* Add iostream wrapper (#530) +* Add Jsonx example for converting JSON into JSONx (a XML format) (#531) +* Add optional unresolvedTokenIndex parameter to Pointer::Get() (#532) +* Add encoding validation option for Writer/PrettyWriter (#534) +* Add Writer::SetMaxDecimalPlaces() (#536) +* Support {0, } and {0, m} in Regex (#539) +* Add Value::Get/SetFloat(), Value::IsLossLessFloat/Double() (#540) +* Add stream position check to reader unit tests (#541) +* Add Templated accessors and range-based for (#542) +* Add (Pretty)Writer::RawValue() (#543) +* Add Document::Parse(std::string), Document::Parse(const char*, size_t length) and related APIs. (#553) +* Add move constructor for GenericSchemaDocument (#554) +* Add VS2010 and VS2015 to AppVeyor CI (#555) +* Add parse-by-parts example (#556, #562) +* Support parse number as string (#564, #589) +* Add kFormatSingleLineArray for PrettyWriter (#577) +* Added optional support for trailing commas (#584) +* Added filterkey and filterkeydom examples (#615) +* Added npm docs (#639) +* Allow options for writing and parsing NaN/Infinity (#641) +* Add std::string overload to PrettyWriter::Key() when RAPIDJSON_HAS_STDSTRING is defined (#698) + +### Fixed +* Fix gcc/clang/vc warnings (#350, #394, #397, #444, #447, #473, #515, #582, #589, #595, #667) +* Fix documentation (#482, #511, #550, #557, #614, #635, #660) +* Fix emscripten alignment issue (#535) +* Fix missing allocator to uses of AddMember in document (#365) +* CMake will no longer complain that the minimum CMake version is not specified (#501) +* Make it usable with old VC8 (VS2005) (#383) +* Prohibit C++11 move from Document to Value (#391) +* Try to fix incorrect 64-bit alignment (#419) +* Check return of fwrite to avoid warn_unused_result build failures (#421) +* Fix UB in GenericDocument::ParseStream (#426) +* Keep Document value unchanged on parse error (#439) +* Add missing return statement (#450) +* Fix Document::Parse(const Ch*) for transcoding (#478) +* encodings.h: fix typo in preprocessor condition (#495) +* Custom Microsoft headers are necessary only for Visual Studio 2012 and lower (#559) +* Fix memory leak for invalid regex (26e69ffde95ba4773ab06db6457b78f308716f4b) +* Fix a bug in schema minimum/maximum keywords for 64-bit integer (e7149d665941068ccf8c565e77495521331cf390) +* Fix a crash bug in regex (#605) +* Fix schema "required" keyword cannot handle duplicated keys (#609) +* Fix cmake CMP0054 warning (#612) +* Added missing include guards in istreamwrapper.h and ostreamwrapper.h (#634) +* Fix undefined behaviour (#646) +* Fix buffer overrun using PutN (#673) +* Fix rapidjson::value::Get() may returns wrong data (#681) +* Add Flush() for all value types (#689) +* Handle malloc() fail in PoolAllocator (#691) +* Fix builds on x32 platform. #703 + +### Changed +* Clarify problematic JSON license (#392) +* Move Travis to container based infrastructure (#504, #558) +* Make whitespace array more compact (#513) +* Optimize Writer::WriteString() with SIMD (#544) +* x86-64 48-bit pointer optimization for GenericValue (#546) +* Define RAPIDJSON_HAS_CXX11_RVALUE_REFS directly in clang (#617) +* Make GenericSchemaDocument constructor explicit (#674) +* Optimize FindMember when use std::string (#690) + +## [1.0.2] - 2015-05-14 + +### Added +* Add Value::XXXMember(...) overloads for std::string (#335) + +### Fixed +* Include rapidjson.h for all internal/error headers. +* Parsing some numbers incorrectly in full-precision mode (`kFullPrecisionParseFlag`) (#342) +* Fix some numbers parsed incorrectly (#336) +* Fix alignment of 64bit platforms (#328) +* Fix MemoryPoolAllocator::Clear() to clear user-buffer (0691502573f1afd3341073dd24b12c3db20fbde4) + +### Changed +* CMakeLists for include as a thirdparty in projects (#334, #337) +* Change Document::ParseStream() to use stack allocator for Reader (ffbe38614732af8e0b3abdc8b50071f386a4a685) + +## [1.0.1] - 2015-04-25 + +### Added +* Changelog following [Keep a CHANGELOG](https://github.com/olivierlacan/keep-a-changelog) suggestions. + +### Fixed +* Parsing of some numbers (e.g. "1e-00011111111111") causing assertion (#314). +* Visual C++ 32-bit compilation error in `diyfp.h` (#317). + +## [1.0.0] - 2015-04-22 + +### Added +* 100% [Coverall](https://coveralls.io/r/miloyip/rapidjson?branch=master) coverage. +* Version macros (#311) + +### Fixed +* A bug in trimming long number sequence (4824f12efbf01af72b8cb6fc96fae7b097b73015). +* Double quote in unicode escape (#288). +* Negative zero roundtrip (double only) (#289). +* Standardize behavior of `memcpy()` and `malloc()` (0c5c1538dcfc7f160e5a4aa208ddf092c787be5a, #305, 0e8bbe5e3ef375e7f052f556878be0bd79e9062d). + +### Removed +* Remove an invalid `Document::ParseInsitu()` API (e7f1c6dd08b522cfcf9aed58a333bd9a0c0ccbeb). + +## 1.0-beta - 2015-04-8 + +### Added +* RFC 7159 (#101) +* Optional Iterative Parser (#76) +* Deep-copy values (#20) +* Error code and message (#27) +* ASCII Encoding (#70) +* `kParseStopWhenDoneFlag` (#83) +* `kParseFullPrecisionFlag` (881c91d696f06b7f302af6d04ec14dd08db66ceb) +* Add `Key()` to handler concept (#134) +* C++11 compatibility and support (#128) +* Optimized number-to-string and vice versa conversions (#137, #80) +* Short-String Optimization (#131) +* Local stream optimization by traits (#32) +* Travis & Appveyor Continuous Integration, with Valgrind verification (#24, #242) +* Redo all documentation (English, Simplified Chinese) + +### Changed +* Copyright ownership transfered to THL A29 Limited (a Tencent company). +* Migrating from Premake to CMAKE (#192) +* Resolve all warning reports + +### Removed +* Remove other JSON libraries for performance comparison (#180) + +## 0.11 - 2012-11-16 + +## 0.1 - 2011-11-18 + +[Unreleased]: https://github.com/miloyip/rapidjson/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/miloyip/rapidjson/compare/v1.0.2...v1.1.0 +[1.0.2]: https://github.com/miloyip/rapidjson/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/miloyip/rapidjson/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/miloyip/rapidjson/compare/v1.0-beta...v1.0.0 diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeLists.txt b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeLists.txt new file mode 100644 index 0000000000..ceda71b1b6 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeLists.txt @@ -0,0 +1,173 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +if(POLICY CMP0025) + # detect Apple's Clang + cmake_policy(SET CMP0025 NEW) +endif() +if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules) + +PROJECT(RapidJSON CXX) + +set(LIB_MAJOR_VERSION "1") +set(LIB_MINOR_VERSION "1") +set(LIB_PATCH_VERSION "0") +set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}") + +# compile in release with debug info mode by default +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif() + +# Build all binaries in a separate directory +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +option(RAPIDJSON_BUILD_DOC "Build rapidjson documentation." ON) +option(RAPIDJSON_BUILD_EXAMPLES "Build rapidjson examples." ON) +option(RAPIDJSON_BUILD_TESTS "Build rapidjson perftests and unittests." ON) +option(RAPIDJSON_BUILD_THIRDPARTY_GTEST + "Use gtest installation in `thirdparty/gtest` by default if available" OFF) + +option(RAPIDJSON_BUILD_CXX11 "Build rapidjson with C++11 (gcc/clang)" ON) + +option(RAPIDJSON_BUILD_ASAN "Build rapidjson with address sanitizer (gcc/clang)" OFF) +option(RAPIDJSON_BUILD_UBSAN "Build rapidjson with undefined behavior sanitizer (gcc/clang)" OFF) + +option(RAPIDJSON_HAS_STDSTRING "" OFF) +if(RAPIDJSON_HAS_STDSTRING) + add_definitions(-DRAPIDJSON_HAS_STDSTRING) +endif() + +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics") + endif() +endif(CCACHE_FOUND) + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra -Werror") + if (RAPIDJSON_BUILD_CXX11) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.7.0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() + endif() + if (RAPIDJSON_BUILD_ASAN) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.0") + message(FATAL_ERROR "GCC < 4.8 doesn't support the address sanitizer") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + endif() + endif() + if (RAPIDJSON_BUILD_UBSAN) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0") + message(FATAL_ERROR "GCC < 4.9 doesn't support the undefined behavior sanitizer") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") + endif() + endif() +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra -Werror -Wno-missing-field-initializers") + if (RAPIDJSON_BUILD_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() + if (RAPIDJSON_BUILD_ASAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + endif() + if (RAPIDJSON_BUILD_UBSAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") + endif() + endif() +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") +endif() + +#add extra search paths for libraries and includes +SET(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The directory the headers are installed in") +SET(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE STRING "Directory where lib will install") +SET(DOC_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}" CACHE PATH "Path to the documentation") + +IF(UNIX OR CYGWIN) + SET(_CMAKE_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}") +ELSEIF(WIN32) + SET(_CMAKE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cmake") +ENDIF() +SET(CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" CACHE PATH "The directory cmake fiels are installed in") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +if(RAPIDJSON_BUILD_DOC) + add_subdirectory(doc) +endif() + +add_custom_target(travis_doc) +add_custom_command(TARGET travis_doc + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/travis-doxygen.sh) + +if(RAPIDJSON_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +if(RAPIDJSON_BUILD_TESTS) + if(MSVC11) + # required for VS2012 due to missing support for variadic templates + add_definitions(-D_VARIADIC_MAX=10) + endif(MSVC11) + add_subdirectory(test) + include(CTest) +endif() + +# pkg-config +IF (UNIX OR CYGWIN) + CONFIGURE_FILE (${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + @ONLY) + INSTALL (FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION "${LIB_INSTALL_DIR}/pkgconfig" + COMPONENT pkgconfig) +ENDIF() + +install(FILES readme.md + DESTINATION "${DOC_INSTALL_DIR}" + COMPONENT doc) + +install(DIRECTORY include/rapidjson + DESTINATION "${INCLUDE_INSTALL_DIR}" + COMPONENT dev) + +install(DIRECTORY example/ + DESTINATION "${DOC_INSTALL_DIR}/examples" + COMPONENT examples + # Following patterns are for excluding the intermediate/object files + # from an install of in-source CMake build. + PATTERN "CMakeFiles" EXCLUDE + PATTERN "Makefile" EXCLUDE + PATTERN "cmake_install.cmake" EXCLUDE) + +# Provide config and version files to be used by other applications +# =============================== + +export(PACKAGE ${PROJECT_NAME}) + +# cmake-modules +CONFIGURE_FILE(${PROJECT_NAME}Config.cmake.in + ${PROJECT_NAME}Config.cmake + @ONLY) +CONFIGURE_FILE(${PROJECT_NAME}ConfigVersion.cmake.in + ${PROJECT_NAME}ConfigVersion.cmake + @ONLY) +INSTALL(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + DESTINATION "${CMAKE_INSTALL_DIR}" + COMPONENT dev) diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeModules/FindGTestSrc.cmake b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeModules/FindGTestSrc.cmake new file mode 100644 index 0000000000..f3cb8c9908 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/CMakeModules/FindGTestSrc.cmake @@ -0,0 +1,30 @@ + +SET(GTEST_SEARCH_PATH + "${GTEST_SOURCE_DIR}" + "${CMAKE_CURRENT_LIST_DIR}/../thirdparty/gtest/googletest") + +IF(UNIX) + IF(RAPIDJSON_BUILD_THIRDPARTY_GTEST) + LIST(APPEND GTEST_SEARCH_PATH "/usr/src/gtest") + ELSE() + LIST(INSERT GTEST_SEARCH_PATH 1 "/usr/src/gtest") + ENDIF() +ENDIF() + +FIND_PATH(GTEST_SOURCE_DIR + NAMES CMakeLists.txt src/gtest_main.cc + PATHS ${GTEST_SEARCH_PATH}) + + +# Debian installs gtest include directory in /usr/include, thus need to look +# for include directory separately from source directory. +FIND_PATH(GTEST_INCLUDE_DIR + NAMES gtest/gtest.h + PATH_SUFFIXES include + HINTS ${GTEST_SOURCE_DIR} + PATHS ${GTEST_SEARCH_PATH}) + +INCLUDE(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GTestSrc DEFAULT_MSG + GTEST_SOURCE_DIR + GTEST_INCLUDE_DIR) diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSON.pc.in b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSON.pc.in new file mode 100644 index 0000000000..7467f9779b --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSON.pc.in @@ -0,0 +1,7 @@ +includedir=@INCLUDE_INSTALL_DIR@ + +Name: @PROJECT_NAME@ +Description: A fast JSON parser/generator for C++ with both SAX/DOM style API +Version: @LIB_VERSION_STRING@ +URL: https://github.com/miloyip/rapidjson +Cflags: -I${includedir} diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfig.cmake.in b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfig.cmake.in new file mode 100644 index 0000000000..9fa12186ab --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfig.cmake.in @@ -0,0 +1,3 @@ +get_filename_component(RAPIDJSON_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(RAPIDJSON_INCLUDE_DIRS "@INCLUDE_INSTALL_DIR@") +message(STATUS "RapidJSON found. Headers: ${RAPIDJSON_INCLUDE_DIRS}") diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfigVersion.cmake.in b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfigVersion.cmake.in new file mode 100644 index 0000000000..25741fc097 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/RapidJSONConfigVersion.cmake.in @@ -0,0 +1,10 @@ +SET(PACKAGE_VERSION "@LIB_VERSION_STRING@") + +IF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) + SET(PACKAGE_VERSION_EXACT "true") +ENDIF (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) +IF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_COMPATIBLE "true") +ELSE (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) + SET(PACKAGE_VERSION_UNSUITABLE "true") +ENDIF (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/appveyor.yml b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/appveyor.yml new file mode 100644 index 0000000000..dfedf9c297 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/appveyor.yml @@ -0,0 +1,41 @@ +os: Visual Studio 2015 CTP +version: 1.1.0.{build} + +configuration: +- Debug +- Release + +environment: + matrix: + # - VS_VERSION: 9 2008 + # VS_PLATFORM: win32 + # - VS_VERSION: 9 2008 + # VS_PLATFORM: x64 + - VS_VERSION: 10 2010 + VS_PLATFORM: win32 + - VS_VERSION: 10 2010 + VS_PLATFORM: x64 + - VS_VERSION: 11 2012 + VS_PLATFORM: win32 + - VS_VERSION: 11 2012 + VS_PLATFORM: x64 + - VS_VERSION: 12 2013 + VS_PLATFORM: win32 + - VS_VERSION: 12 2013 + VS_PLATFORM: x64 + - VS_VERSION: 14 2015 + VS_PLATFORM: win32 + - VS_VERSION: 14 2015 + VS_PLATFORM: x64 + +before_build: +- git submodule update --init --recursive +- cmake -H. -BBuild/VS -G "Visual Studio %VS_VERSION%" -DCMAKE_GENERATOR_PLATFORM=%VS_PLATFORM% -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=true -Wno-dev + +build: + project: Build\VS\RapidJSON.sln + parallel: true + verbosity: minimal + +test_script: +- cd Build\VS && if %CONFIGURATION%==Debug (ctest --verbose -E perftest --build-config %CONFIGURATION%) else (ctest --verbose --build-config %CONFIGURATION%) diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/glossary.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/glossary.json new file mode 100644 index 0000000000..d6e6ca1507 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/glossary.json @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/menu.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/menu.json new file mode 100644 index 0000000000..539c3af201 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/menu.json @@ -0,0 +1,27 @@ +{"menu": { + "header": "SVG Viewer", + "items": [ + {"id": "Open"}, + {"id": "OpenNew", "label": "Open New"}, + null, + {"id": "ZoomIn", "label": "Zoom In"}, + {"id": "ZoomOut", "label": "Zoom Out"}, + {"id": "OriginalView", "label": "Original View"}, + null, + {"id": "Quality"}, + {"id": "Pause"}, + {"id": "Mute"}, + null, + {"id": "Find", "label": "Find..."}, + {"id": "FindAgain", "label": "Find Again"}, + {"id": "Copy"}, + {"id": "CopyAgain", "label": "Copy Again"}, + {"id": "CopySVG", "label": "Copy SVG"}, + {"id": "ViewSVG", "label": "View SVG"}, + {"id": "ViewSource", "label": "View Source"}, + {"id": "SaveAs", "label": "Save As"}, + null, + {"id": "Help"}, + {"id": "About", "label": "About Adobe CVG Viewer..."} + ] +}} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/readme.txt b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/readme.txt new file mode 100644 index 0000000000..c53bfb8b72 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/readme.txt @@ -0,0 +1 @@ +sample.json is obtained from http://code.google.com/p/json-test-suite/downloads/detail?name=sample.zip diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/sample.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/sample.json new file mode 100644 index 0000000000..30930e765d --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/sample.json @@ -0,0 +1,3315 @@ +{ + "a": { + "6U閆崬밺뀫颒myj츥휘:$薈mY햚#rz飏+玭V㭢뾿愴YꖚX亥ᮉ푊\u0006垡㐭룝\"厓ᔧḅ^Sqpv媫\"⤽걒\"˽Ἆ?ꇆ䬔未tv{DV鯀Tἆl凸g\\㈭ĭ즿UH㽤": null, + "b茤z\\.N": [[ + "ZL:ᅣዎ*Y|猫劁櫕荾Oj为1糕쪥泏S룂w࡛Ᏺ⸥蚙)", + { + "\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\"": true, + "e浱up蔽Cr෠JK軵xCʨ<뜡癙Y獩ケ齈X/螗唻?<蘡+뷄㩤쳖3偑犾&\\첊xz坍崦ݻ鍴\"嵥B3㰃詤豺嚼aqJ⑆∥韼@\u000b㢊\u0015L臯.샥": false, + "l?Ǩ喳e6㔡$M꼄I,(3᝝縢,䊀疅뉲B㴔傳䂴\u0088㮰钘ꜵ!ᅛ韽>": -5514085325291784739, + "o㮚?\"춛㵉<\/﬊ࠃ䃪䝣wp6ἀ䱄[s*S嬈貒pᛥ㰉'돀": [{ + "(QP윤懊FI<ꃣ『䕷[\"珒嶮?%Ḭ壍಻䇟0荤!藲끹bd浶tl\u2049#쯀@僞": {"i妾8홫": { + ",M맃䞛K5nAㆴVN㒊햬$n꩑&ꎝ椞阫?/ṏ세뉪1x쥼㻤㪙`\"$쟒薟B煌܀쨝ଢ଼2掳7㙟鴙X婢\u0002": "Vዉ菈᧷⦌kﮞఈnz*﷜FM\"荭7ꍀ-VR<\/';䁙E9$䩉\f @s?퍪o3^衴cඎ䧪aK鼟q䆨c{䳠5mᒲՙ蘹ᮩ": { + "F㲷JGo⯍P덵x뒳p䘧☔\"+ꨲ吿JfR㔹)4n紬G练Q፞!C|": true, + "p^㫮솎oc.೚A㤠??r\u000f)⾽⌲們M2.䴘䩳:⫭胃\\፾@Fᭌ\\K": false, + "蟌Tk愙潦伩": { + "a<\/@ᾛ慂侇瘎": -7271305752851720826, + "艓藬/>၄ṯ,XW~㲆w": {"E痧郶)㜓ha朗!N赻瞉駠uC\u20ad辠x퓮⣫P1ࠫLMMX'M刼唳됤": null, + "P쓫晥%k覛ዩIUᇸ滨:噐혲lMR5䋈V梗>%幽u頖\\)쟟": null, + "eg+昉~矠䧞难\b?gQ쭷筝\\eꮠNl{ಢ哭|]Mn銌╥zꖘzⱷ⭤ᮜ^": [ + -1.30142114406914976E17, + -1.7555215491128452E-19, + null, + "渾㨝ߏ牄귛r?돌?w[⚞ӻ~廩輫㼧/", + -4.5737191805302129E18, + null, + "xy࿑M[oc셒竓Ⓔx?뜓y䊦>-D켍(&&?XKkc꩖ﺸᏋ뵞K伕6ী)딀P朁yW揙?훻魢傎EG碸9類៌g踲C⟌aEX舲:z꒸许", + 3808159498143417627, + null, + {"m試\u20df1{G8&뚈h홯J<\/": { + "3ஸ厠zs#1K7:rᥞoꅔꯧ&띇鵼鞫6跜#赿5l'8{7㕳(b/j\"厢aq籀ꏚ\u0015厼稥": [ + -2226135764510113982, + true, + null, + { + "h%'맞S싅Hs&dl슾W0j鿏MםD놯L~S-㇡R쭬%": null, + "⟓咔謡칲\u0000孺ꛭx旑檉㶆?": null, + "恇I転;￸B2Y`z\\獓w,놏濐撐埵䂄)!䶢D=ഭ㴟jyY": { + "$ࡘt厛毣ൢI芁<겿骫⫦6tr惺a": [ + 6.385779736989334E-20, + false, + true, + true, + [ + -6.891946211462334E-19, + null, + { + "]-\\Ꟑ1/薓❧Ὂ\\l牑\u0007A郃)阜ᇒᓌ-塯`W峬G}SDb㬨Q臉⮻빌O鞟톴첂B㺱<ƈmu챑J㴹㷳픷Oㆩs": { + "\"◉B\"pᶉt骔J꩸ᄇᛐi╰栛K쉷㉯鐩!㈐n칍䟅難>盥y铿e୔蒏M貹ヅ8嘋퀯䉶ጥ㏢殊뻳\"絧╿ꉑ䠥?∃蓊{}㣣Gk긔H1哵峱": false, + "6.瀫cN䇮F㧺?\\椯=ڈT䘆4␘8qv": -3.5687501019676885E-19, + "Q?yऴr혴{஀䳘p惭f1ﹸ䅷䕋贲<ྃᄊ繲hq\\b|#QSTs1c-7(䵢\u2069匏絘ꯉ:l毴汞t戀oෟᵶ뮱፣-醇Jx䙬䐁햢0࣫ᡁgrㄛ": "\u0011_xM/蘇Chv;dhA5.嗀绱V爤ﰦi뵲M", + "⏑[\"ugoy^儣횎~U\\섯겜論l2jw஌yD腅̂\u0019": true, + "ⵯɇ䐲᫿࢚!㯢l샅笶戮1꣖0Xe": null, + "劅f넀識b宁焊E찓橵G!ʱ獓뭔雩괛": [{"p⹣켙[q>燣䍃㞽ᩲx:쓤삘7玑퇼0<\/q璂ᑁ[Z\\3䅵䧳\u0011㤧|妱緒C['췓Yꞟ3Z鳱雼P錻BU씧U`ᢶg蓱>.1ӧ譫'L_5V䏵Ц": [ + false, + false, + {"22䂍盥N霂얢躰e9⑩_뵜斌n@B}$괻Yᐱ@䧋V\"☒-諯cV돯ʠ": true, + "Ű螧ᔼ檍鍎땒딜qꄃH뜣<獧ूCY吓⸏>XQ㵡趌o끬k픀빯a(ܵ甏끆୯/6Nᪧ}搚ᆚ짌P牰泱鈷^d꣟#L삀\"㕹襻;k㸊\\f+": true, + "쎣\",|⫝̸阊x庿k잣v庅$鈏괎炔k쬪O_": [ + "잩AzZGz3v愠ꉈⵎ?㊱}S尳௏p\r2>췝IP䘈M)w|\u000eE", + -9222726055990423201, + null, + [ + false, + {"´킮'뮤쯽Wx讐V,6ᩪ1紲aႈ\u205czD": [ + -930994432421097536, + 3157232031581030121, + "l貚PY䃛5@䭄귻m㎮琸f": 1.0318894506812084E-19, + "࢜⩢Ш䧔1肽씮+༎ᣰ闺馺窃䕨8Mƶq腽xc(៯夐J5굄䕁Qj_훨/~価.䢵慯틠퇱豠㼇Qﵘ$DuSp(8Uญ<\/ಟ룴𥳐ݩ$": 8350772684161555590, + "ㆎQ䄾\u001bpᩭ${[諟^^骴᤮b^ㅥI┧T㉇⾞\"绦r䰂f矩'-7䡭桥Dz兔V9谶居㺍ᔊ䩯덲.\u001eL0ὅㅷ釣": [{ + "<쯬J卷^숞u࠯䌗艞R9닪g㐾볎a䂈歖意:%鐔|ﵤ|y}>;2,覂⶚啵tb*仛8乒㓶B࿠㯉戩oX 貘5V嗆렽낁߼4h䧛ꍺM空\\b꿋貼": 8478577078537189402, + "VD*|吝z~h譺aᯒ": { + "YI췢K<\/濳xNne玗rJo쾘3핰鴊\"↱AR:ࢷ\"9?\"臁說)?誚ꊏe)_D翾W?&F6J@뺾ꍰNZ醊Z쾈വH嶿?炫㷱鬰M겈᭨b,⻁鈵P䕡䀠८ⱄ홎鄣": { + "@?k2鶖㋮\"Oರ K㨇廪儲\u0017䍾J?);\b*묀㗠섳햭1MC V": null, + "UIICP!BUA`ᢈ㋸~袩㗪⾒=fB﮴l1ꡛ죘R辂여ҳ7쮡<䩲`熕8頁": 4481809488267626463, + "Y?+8먙ᚔ鋳蜩럶1㥔y璜౩`": [ + null, + 1.2850335807501874E-19, + "~V2", + 2035406654801997866, + { + "<숻1>\"": -8062468865199390827, + "M㿣E]}qwG莎Gn᝶(ꔙ\\D⬲iꇲs寢t駇S뀡ꢜ": false, + "pꝤ㎏9W%>M;-U璏f(^j1?&RB隧 忓b똊E": "#G?C8.躬ꥯ'?냪#< 渟&헿란zpo왓Kj}鷧XﻘMツb䕖;㪻", + "vE풤幉xz뱕쫥Ug㦲aH} ᣟp:鬼YᰟH3镔ᴚ斦\\鏑r*2橱G⼔F/.j": true, + "RK좬뎂a홠f*f㱉ᮍ⦋潙㨋Gu곌SGI3I뿐\\F',)t`荁蘯囯ﮉ裲뇟쥼_ገ驪▵撏ᕤV": 1.52738225997956557E18, + "^k굲䪿꠹B逤%F㱢漥O披M㽯镞竇霒i꼂焅륓\u00059=皫之눃\u2047娤閍銤唫ၕb<\/w踲䔼u솆맚,䝒ᝳ'/it": "B餹饴is権ꖪ怯ꦂẉဎt\"!凢谵⧿0\\<=(uL䷍刨쑪>俆揓Cy襸Q힆䆭涷<\/ᐱ0ɧ䗾䚹\\ኜ?ꄢᇘ`䴢{囇}᠈䴥X4퓪檄]ꥷ/3謒ሴn+g騍X", + "GgG꽬[(嫓몍6\u0004궍宩㙻/>\u0011^辍dT腪hxǑ%ꊇk,8(W⧂結P鬜O": [{ + "M㴾c>\\ᓲ\u0019V{>ꤩ혙넪㭪躂TS-痴໸闓⍵/徯O.M㏥ʷD囎⧔쁳휤T??鉬뇙=#ꢫ숣BX䭼<\/d똬졬g榿)eꨋﯪ좇첻\u001a\u0011\";~쓆BH4坋攊7힪", + "iT:L闞椕윚*滛gI≀Wਟඊ'ꢆ縺뱹鮚Nꩁ᧬蕼21줧\\䋯``⍐\\㏱鳨": 1927052677739832894, + "쮁缦腃g]礿Y㬙 fヺSɪ꾾N㞈": [ + null, + null, + { + "!t,灝Y 1䗉罵?c饃호䉂Cᐭ쒘z(즽sZG㬣sഖE4뢜㓕䏞丮Qp簍6EZឪ겛fx'ꩱQ0罣i{k锩*㤴㯞r迎jTⲤ渔m炅肳": [ + -3.3325685522591933E18, + [{"㓁5]A䢕1룥BC?Ꙍ`r룔Ⳛ䙡u伲+\u0001്o": [ + null, + 4975309147809803991, + null, + null, + {"T팘8Dﯲ稟MM☻㧚䥧/8ﻥ⥯aXLaH\"顾S☟耲ît7fS෉놁뮔/ꕼ䓈쁺4\\霶䠴ᩢ<\/t4?죵>uD5➶༆쉌럮⢀秙䘥\u20972ETR3濡恆vB? ~鸆\u0005": { + "`閖m璝㥉b뜴?Wf;?DV콜\u2020퍉౓擝宏ZMj3mJ먡-傷뱙yח㸷꥿ ໘u=M읝!5吭L4v\\?ǎ7C홫": null, + "|": false, + "~Ztᛋ䚘\\擭㗝傪W陖+㗶qᵿ蘥ᙄp%䫎)}=⠔6ᮢS湟-螾-mXH?cp": 448751162044282216, + "\u209fad놹j檋䇌ᶾ梕㉝bוּ": {"?苴ꩠD䋓帘5騱qﱖPF?☸珗顒yU ᡫcb䫎 S@㥚gꮒ쎘泴멖\\:I鮱TZ듒ᶨQ3+f7캙\"?\f풾\\o杞紟﻽M.⏎靑OP": [ + -2.6990368911551596E18, + [{"䒖@<᰿<\/⽬tTr腞&G%᳊秩蜰擻f㎳?S㵧\r*k뎾-乢겹隷j軛겷0룁鮁": {")DO0腦:춍逿:1㥨่!蛍樋2": [{ + ",ꌣf侴笾m๫ꆽ?1?U?\u0011ꌈꂇ": { + "x捗甠nVq䅦w`CD⦂惺嘴0I#vỵ} \\귂S끴D얾?Ԓj溯\"v餄a": { + "@翙c⢃趚痋i\u0015OQ⍝lq돆Y0pࢥ3쉨䜩^<8g懥0w)]䊑n洺o5쭝QL댊랖L镈Qnt⪟㒅십q헎鳒⮤眉ᔹ梠@O縠u泌ㄘb榚癸XޔFtj;iC": false, + "I&뱋゘|蓔䔕측瓯%6ᗻHW\\N1貇#?僐ᗜgh᭪o'䗈꽹Rc욏/蔳迄༝!0邔䨷푪8疩)[쭶緄㇈୧ፐ": { + "B+:ꉰ`s쾭)빼C羍A䫊pMgjdx䐝Hf9᥸W0!C樃'蘿f䫤סи\u0017Jve? 覝f둀⬣퓉Whk\"஼=չﳐ皆笁BIW虨쫓F廰饞": -642906201042308791, + "sb,XcZ<\/m㉹ ;䑷@c䵀s奤⬷7`ꘖ蕘戚?Feb#輜}p4nH⬮eKL트}": [ + "RK鳗z=袤Pf|[,u욺", + "Ẏᏻ罯뉋⺖锅젯㷻{H䰞쬙-쩓D]~\u0013O㳢gb@揶蔉|kᦂ❗!\u001ebM褐sca쨜襒y⺉룓", + null, + null, + true, + -1.650777344339075E-19, + false, + "☑lꄆs힨꤇]'uTന⌳농].1⋔괁沰\"IWഩ\u0019氜8쟇䔻;3衲恋,窌z펏喁횗?4?C넁问?ᥙ橭{稻Ⴗ_썔", + "n?]讇빽嗁}1孅9#ꭨ靶v\u0014喈)vw祔}룼쮿I", + -2.7033457331882025E18, + { + ";⚃^㱋x:饬ኡj'꧵T☽O㔬RO婎?향ᒭ搩$渣y4i;(Q>꿘e8q": "j~錘}0g;L萺*;ᕭꄮ0l潛烢5H▄쳂ꏒוֹꙶT犘≫x閦웧v", + "~揯\u2018c4職렁E~ᑅቚꈂ?nq뎤.:慹`F햘+%鉎O瀜쟏敛菮⍌浢<\/㮺紿P鳆ࠉ8I-o?#jﮨ7v3Dt赻J9": null, + "ࣝW䌈0ꍎqC逖,횅c၃swj;jJS櫍5槗OaB>D踾Y": {"㒰䵝F%?59.㍈cᕨ흕틎ḏ㋩B=9IېⓌ{:9.yw}呰ㆮ肒᎒tI㾴62\"ዃ抡C﹬B<\/촋jo朣", + [ + -7675533242647793366, + {"ᙧ呃:[㒺쳀쌡쏂H稈㢤\u001dᶗGG-{GHྻຊꡃ哸䵬;$?&d\\⥬こN圴됤挨-'ꕮ$PU%?冕눖i魁q騎Q": [ + false, + [[ + 7929823049157504248, + [[ + true, + "Z菙\u0017'eꕤ᱕l,0\\X\u001c[=雿8蠬L<\/낲긯W99g톉4ퟋb㝺\u0007劁'!麕Q궈oW:@X၎z蘻m絙璩귓죉+3柚怫tS捇蒣䝠-擶D[0=퉿8)q0ٟ", + "唉\nFA椭穒巯\\䥴䅺鿤S#b迅獘 ﶗ꬘\\?q1qN犠pX꜅^䤊⛤㢌[⬛휖岺q唻ⳡ틍\"㙙Eh@oA賑㗠y必Nꊑᗘ", + -2154220236962890773, + -3.2442003245397908E18, + "Wᄿ筠:瘫퀩?o貸q⊻(᎞KWf宛尨h^残3[U(='橄", + -7857990034281549164, + 1.44283696979059942E18, + null, + {"ꫯAw跭喀 ?_9\"Aty背F=9缉ྦྷ@;?^鞀w:uN㘢Rỏ": [ + 7.393662029337442E15, + 3564680942654233068, + [ + false, + -5253931502642112194, + "煉\\辎ೆ罍5⒭1䪁䃑s䎢:[e5}峳ﴱn騎3?腳Hyꏃ膼N潭錖,Yᝋ˜YAၓ㬠bG렣䰣:", + true, + null, + { + "⒛'P&%죮|:⫶춞": -3818336746965687085, + "钖m<\/0ݎMtF2Pk=瓰୮洽겎.": [[ + -8757574841556350607, + -3045234949333270161, + null, + { + "Ꮬr輳>⫇9hU##w@귪A\\C 鋺㘓ꖐ梒뒬묹㹻+郸嬏윤'+g<\/碴,}ꙫ>손;情d齆J䬁ຩ撛챝탹/R澡7剌tꤼ?ặ!`⏲睤\u00002똥଴⟏": null, + "\u20f2ܹe\\tAꥍư\\x当뿖렉禛;G檳ﯪS૰3~㘠#[J<}{奲 5箉⨔{놁<\/釿抋,嚠/曳m&WaOvT赋皺璑텁": [[ + false, + null, + true, + -5.7131445659795661E18, + "萭m䓪D5|3婁ఞ>蠇晼6nﴺPp禽羱DS<睓닫屚삏姿", + true, + [ + -8759747687917306831, + { + ">ⓛ\t,odKr{䘠?b퓸C嶈=DyEᙬ@ᴔ쨺芛髿UT퓻春<\/yꏸ>豚W釺N뜨^?꽴﨟5殺ᗃ翐%>퍂ဿ䄸沂Ea;A_\u0005閹殀W+窊?Ꭼd\u0013P汴G5썓揘": 4.342729067882445E-18, + "Q^즾眆@AN\u0011Kb榰냎Y#䝀ꀒᳺ'q暇睵s\"!3#I⊆畼寤@HxJ9": false, + "⿾D[)袨㇩i]웪䀤ᛰMvR<蟏㣨": {"v퇓L㪱ꖣ豛톤\\곱#kDTN": [{ + "(쾴䡣,寴ph(C\"㳶w\"憳2s馆E!n!&柄<\/0Pꈗſ?㿳Qd鵔": {"娇堰孹L錮h嵅⛤躏顒?CglN束+쨣ﺜ\\MrH": {"獞䎇둃ቲ弭팭^ꄞ踦涟XK錆쳞ឌ`;੶S炥騞ଋ褂B៎{ڒ䭷ᶼ靜pI荗虶K$": [{"◖S~躘蒉꫿輜譝Q㽙闐@ᢗ¥E榁iء5┄^B[絮跉ᰥ遙PWi3wㄾⵀDJ9!w㞣ᄎ{듒ꓓb6\\篴??c⼰鶹⟧\\鮇ꮇ": [[ + 654120831325413520, + -1.9562073916357608E-19, + { + "DC(昐衵ἡ긙갵姭|֛[t": 7.6979110359897907E18, + "J␅))嫼❳9Xfd飉j7猬ᩉ+⤻眗벎E鰉Zᄊ63zၝ69}ZᶐL崭ᦥ⡦靚⋛ꎨ~i㨃咊ꧭo䰠阀3C(": -3.5844809362512589E17, + "p꣑팱쒬ꎑ뛡Ꙩ挴恍胔&7ᔈ묒4Hd硶훐㎖zꢼ豍㿢aሃ=<\/湉鵲EӅ%$F!퍶棌孼{O駍਺geu+": ")\u001b잓kŀX쩫A밁®ڣ癦狢)扔弒p}k縕ꩋ,䃉tࣼi", + "ァF肿輸<솄G-䢹䛸ꊏl`Tqꕗ蒞a氷⸅ᴉ蠰]S/{J왲m5{9.uέ~㕚㣹u>x8U讁B덺襪盎QhVS맅킃i识{벂磄Iහ䙅xZy/抍૭Z鲁-霳V据挦ℒ": null, + "㯛|Nꐸb7ⵐb?拠O\u0014ކ?-(EꞨ4ꕷᄤYᯕOW瞺~螸\"욿ќe㺰\"'㌢ƐW\u0004瞕>0?V鷵엳": true, + "뤥G\\迋䠿[庩'꼡\u001aiᩮV쯁ᳪ䦪Ô;倱ନ뛁誈": null, + "쥹䄆䚟Q榁䎐᢭<\/2㕣p}HW蟔|䃏꿈ꚉ锳2Pb7㙑Tⅹᵅ": { + "Y?֭$>#cVBꩨ:>eL蒁務": { + "86柡0po 䏚&-捑Ћ祌<\/휃-G*㶢הּ쩍s㶟餇c걺yu꽎還5*턧簕Og婥SꝐ": null, + "a+葞h٥ࠆ裈嗫ﵢ5輙퀟ᛜ,QDﹼ⟶Y騠锪E_|x죗j侵;m蜫轘趥?븅w5+mi콛L": { + ";⯭ﱢ!买F⽍柤鶂n䵣V㫚墱2렾ELEl⣆": [ + true, + -3.6479311868339015E-18, + -7270785619461995400, + 3.334081886177621E18, + 2.581457786298155E18, + -6.605252412954115E-20, + -3.9232347037744167E-20, + { + "B6㊕.k1": null, + "ZAꄮJ鮷ᳱo갘硥鈠䠒츼": { + "ᕅ}럡}.@y陪鶁r業'援퀉x䉴ﵴl퍘):씭脴ᥞhiꃰblﲂ䡲엕8߇M㶭0燋標挝-?PCwe⾕J碻Ᾱ䬈䈥뷰憵賣뵓痬+": {"a췩v礗X⋈耓ፊf罅靮!㔽YYᣓw澍33⎔芲F|\"䜏T↮輦挑6ᓘL侘?ᅥ]덆1R௯✎餘6ꏽ<\/௨\\?q喷ꁫj~@ulq": {"嗫欆뾔Xꆹ4H㌋F嵧]ࠎ]㠖1ꞤT<$m뫏O i댳0䲝i": {"?෩?\u20cd슮|ꯆjs{?d7?eNs⢚嫥氂䡮쎱:鑵롟2hJꎒﯭ鱢3춲亄:뼣v䊭諱Yj択cVmR䩃㘬T\"N홝*ै%x^F\\_s9보zz4淗?q": [ + null, + "?", + 2941869570821073737, + "{5{殇0䝾g6밖퍋臩綹R$䖭j紋釰7sXI繳漪행y", + false, + "aH磂?뛡#惇d婅?Fe,쐘+늵䍘\"3r瘆唊勐j⳧࠴ꇓ<\/唕윈x⬌讣䋵%拗ᛆⰿ妴᝔M2㳗必꧂淲?ゥ젯檢<8끒MidX䏒3᳻Q▮佐UT|⤪봦靏⊏", + [[{ + "颉(&뜸귙{y^\"P퟉춝Ჟ䮭D顡9=?}Y誱<$b뱣RvO8cH煉@tk~4ǂ⤧⩝屋SS;J{vV#剤餓ᯅc?#a6D,s": [ + -7.8781018564821536E16, + true, + [ + -2.28770899315832371E18, + false, + -1.0863912140143876E-20, + -6282721572097446995, + 6767121921199223078, + -2545487755405567831, + false, + null, + -9065970397975641765, + [ + -5.928721243413937E-20, + {"6촊\u001a홯kB0w撨燠룉{绎6⳹!턍贑y▾鱧ժ[;7ᨷ∀*땒䪮1x霆Hᩭ☔\"r䝐7毟ᝰr惃3ꉭE+>僒澐": [ + "Ta쎩aƝt쵯ⰪVb", + [ + -5222472249213580702, + null, + -2851641861541559595, + null, + 4808804630502809099, + 5657671602244269874, + "5犲﨣4mᥣ?yf젫꾯|䋬잁$`Iⳉﴷ扳兝,'c", + false, + [ + null, + { + "DyUIN쎾M仼惀⮥裎岶泭lh扠\u001e礼.tEC癯튻@_Qd4c5S熯A<\/\6U윲蹴Q=%푫汹\\\u20614b[௒C⒥Xe⊇囙b,服3ss땊뢍i~逇PA쇸1": -2.63273619193485312E17, + "Mq꺋貘k휕=nK硍뫞輩>㾆~἞ࡹ긐榵l⋙Hw뮢帋M엳뢯v⅃^": 1877913476688465125, + "ᶴ뻗`~筗免⚽টW˃⽝b犳䓺Iz篤p;乨A\u20ef쩏?疊m㝀컩뫡b탔鄃ᾈV(遢珳=뎲ିeF仢䆡谨8t0醄7㭧瘵⻰컆r厡궥d)a阄፷Ed&c﯄伮1p": null, + "⯁w4曢\"(欷輡": "\"M᭫]䣒頳B\\燧ࠃN㡇j姈g⊸⺌忉ꡥF矉স%^", + "㣡Oᄦ昵⫮Y祎S쐐級㭻撥>{I$": -378474210562741663, + "䛒掷留Q%쓗1*1J*끓헩ᦢ﫫哉쩧EↅIcꅡ\\?ⴊl귛顮4": false, + "寔愆샠5]䗄IH贈=d﯊/偶?ॊn%晥D視N򗘈'᫂⚦|X쵩넽z질tskxDQ莮Aoﱻ뛓": true, + "钣xp?&\u001e侉/y䴼~?U篔蘚缣/I畚?Q绊": -3034854258736382234, + "꺲໣眀)⿷J暘pИfAV삕쳭Nꯗ4々'唄ⶑ伻㷯騑倭D*Ok꧁3b␽_<\/챣Xm톰ၕ䆄`*fl㭀暮滠毡?": [ + "D男p`V뙸擨忝븪9c麺`淂⢦Yw⡢+kzܖ\fY1䬡H歁)벾Z♤溊-혰셢?1<-\u0005;搢Tᐁle\\ᛵߓﭩ榩訝-xJ;巡8깊蠝ﻓU$K": { + "Vꕡ諅搓W=斸s︪vﲜ츧$)iꡟ싉e寳?ጭムVથ嵬i楝Fg<\/Z|៪ꩆ-5'@ꃱ80!燱R쇤t糳]罛逇dṌ֣XHiͦ{": true, + "Ya矲C멗Q9膲墅携휻c\\딶G甔<\/.齵휴": -1.1456247877031811E-19, + "z#.OO￝J": -8263224695871959017, + "崍_3夼ᮟ1F븍뽯ᦓ鴭V豈Ь": [{ + "N蒬74": null, + "yuB?厅vK笗!ᔸcXQ旦컶P-녫mᄉ麟_": "1R@ 톘xa_|﩯遘s槞d!d껀筤⬫薐焵먑D{\\6k共倌☀G~AS_D\"딟쬚뮥馲렓쓠攥WTMܭ8nX㩴䕅檹E\u0007ﭨN 2 ℆涐ꥏ꠵3▙玽|됨_\u2048", + "恐A C䧩G": {":M큣5e들\\ꍀ恼ᔄ靸|I﨏$)n": { + "|U䬫㟯SKV6ꛤ㗮\bn봻䲄fXT:㾯쳤'笓0b/ೢC쳖?2浓uO.䰴": "ཐ꼋e?``,ᚇ慐^8ꜙNM䂱\u0001IᖙꝧM'vKdꌊH牮r\\O@䊷ᓵ쀆(fy聻i툺\"?<\/峧ࣞ⓺ᤤ쵒߯ꎺ騬?)刦\u2072l慪y꺜ﲖTj+u", + "뽫hh䈵w>1ⲏ쐭V[ⅎ\\헑벑F_㖝⠗㫇h恽;῝汰ᱼ瀖J옆9RR셏vsZ柺鶶툤r뢱橾/ꉇ囦FGm\"謗ꉦ⨶쒿⥡%]鵩#ᖣ_蹎 u5|祥?O", + null, + 2.0150326776036215E-19, + null, + true, + false, + true, + {"\fa᭶P捤WWc᠟f뚉ᬏ퓗ⳀW睹5:HXH=q7x찙X$)모r뚥ᆟ!Jﳸf": [ + -2995806398034583407, + [ + 6441377066589744683, + "Mﶒ醹i)Gἦ廃s6몞 KJ౹礎VZ螺费힀\u0000冺업{谥'꡾뱻:.ꘘ굄奉攼Di᷑K鶲y繈욊阓v㻘}枭캗e矮1c?휐\"4\u0005厑莔뀾墓낝⽴洗ṹ䇃糞@b1\u0016즽Y轹", + { + "1⽕⌰鉟픏M㤭n⧴ỼD#%鐘⊯쿼稁븣몐紧ᅇ㓕ᛖcw嬀~ഌ㖓(0r⧦Q䑕髍ര铂㓻R儮\"@ꇱm❈௿᦯頌8}㿹犴?xn잆꥽R": 2.07321075750427366E18, + "˳b18㗈䃟柵Z曆VTAu7+㛂cb0﯑Wp執<\/臋뭡뚋刼틮荋벲TLP预庰܈G\\O@VD'鱃#乖끺*鑪ꬳ?Mޞdﭹ{␇圯쇜㼞顄︖Y홡g": [{ + "0a,FZ": true, + "2z̬蝣ꧦ驸\u0006L↛Ḣ4๚뿀'?lcwᄧ㐮!蓚䃦-|7.飑挴.樵*+1ﮊ\u0010ꛌ%貨啺/JdM:똍!FBe?鰴㨗0O财I藻ʔWA᫓G쳛u`<\/I": [{ + "$τ5V鴐a뾆両環iZp頻යn븃v": -4869131188151215571, + "*즢[⦃b礞R◚nΰꕢH=귰燙[yc誘g䆌?ଜ臛": { + "洤湌鲒)⟻\\䥳va}PeAMnN[": "㐳ɪ/(軆lZR,Cp殍ȮN啷\"3B婴?i=r$펽ᤐ쀸", + "阄R4㒿㯔ڀ69ZᲦ2癁핌噗P崜#\\-쭍袛&鐑/$4童V꩑_ZHA澢fZ3": {"x;P{긳:G閉:9?活H": [ + "繺漮6?z犞焃슳\">ỏ[Ⳛ䌜녏䂹>聵⼶煜Y桥[泥뚩MvK$4jtロ", + "E#갶霠좭㦻ୗ먵F+䪀o蝒ba쮎4X㣵 h", + -335836610224228782, + null, + null, + [ + "r1᫩0>danjY짿bs{", + [ + -9.594464059325631E-23, + 1.0456894622831624E-20, + null, + 5.803973284253454E-20, + -8141787905188892123, + true, + -4735305442504973382, + 9.513150514479281E-20, + "7넳$螔忷㶪}䪪l짴\u0007鹁P鰚HF銏ZJﳴ/⍎1ᷓ忉睇ᜋ쓈x뵠m䷐窥Ꮤ^\u0019ᶌ偭#ヂt☆၃pᎍ臶䟱5$䰵&๵分숝]䝈뉍♂坎\u0011<>", + "C蒑貑藁lﰰ}X喇몛;t밿O7/᯹f\u0015kI嘦<ዴ㟮ᗎZ`GWퟩ瑹࡮ᅴB꿊칈??R校s脚", + { + "9珵戬+AU^洘拻ቒy柭床'粙XG鞕᠜繀伪%]hC,$輙?Ut乖Qm떚W8઼}~q⠪rU䤶CQ痗ig@#≲t샌f㈥酧l;y闥ZH斦e⸬]j⸗?ঢ拻퀆滌": null, + "畯}㧢J罚帐VX㨑>1ꢶkT⿄蘥㝑o|<嗸層沈挄GEOM@-䞚䧰$만峬輏䠱V✩5宸-揂D'㗪yP掶7b⠟J㕻SfP?d}v㼂Ꮕ'猘": { + "陓y잀v>╪": null, + "鬿L+7:됑Y=焠U;킻䯌잫!韎ஔ\f": { + "駫WmGጶ": { + "\\~m6狩K": -2586304199791962143, + "ႜࠀ%͑l⿅D.瑢Dk%0紪dḨTI픸%뗜☓s榗኉\"?V籄7w髄♲쟗翛歂E䤓皹t ?)ᄟ鬲鐜6C": { + "_췤a圷1\u000eB-XOy缿請∎$`쳌eZ~杁튻/蜞`塣৙\"⪰\"沒l}蕌\\롃荫氌.望wZ|o!)Hn獝qg}": null, + "kOSܧ䖨钨:಼鉝ꭝO醧S`십`ꓭ쭁ﯢN&Et㺪馻㍢ⅳ㢺崡ຊ蜚锫\\%ahx켨|ż劻ꎄ㢄쐟A躊᰹p譞綨Ir쿯\u0016ﵚOd럂*僨郀N*b㕷63z": { + ":L5r+T㡲": [{ + "VK泓돲ᮙRy㓤➙Ⱗ38oi}LJቨ7Ó㹡৘*q)1豢⛃e᫛뙪壥镇枝7G藯g㨛oI䄽 孂L缊ꋕ'EN`": -2148138481412096818, + "`⛝ᘑ$(खꊲ⤖ᄁꤒ䦦3=)]Y㢌跨NĴ驳줟秠++d孳>8ᎊ떩EꡣSv룃 쯫أ?#E|᭙㎐?zv:5祉^⋑V": [ + -1.4691944435285607E-19, + 3.4128661569395795E17, + "㐃촗^G9佭龶n募8R厞eEw⺡_ㆱ%⼨D뉄퉠2ꩵᛅⳍ搿L팹Lවn=\"慉념ᛮy>!`g!풲晴[/;?[v겁軇}⤳⤁핏∌T㽲R홓遉㓥", + "愰_⮹T䓒妒閤둥?0aB@㈧g焻-#~跬x<\/舁P݄ꐡ=\\׳P\u0015jᳪᢁq;㯏l%᭗;砢觨▝,謁ꍰGy?躤O黩퍋Y㒝a擯\n7覌똟_䔡]fJ晋IAS", + 4367930106786121250, + -4.9421193149720582E17, + null, + { + ";ᄌ똾柉곟ⰺKpፇ䱻ฺ䖝{o~h!eꁿ઻욄ښ\u0002y?xUd\u207c悜ꌭ": [ + 1.6010824122815255E-19, + [ + "宨︩9앉檥pr쇷?WxLb", + "氇9】J玚\u000f옛呲~ 輠1D嬛,*mW3?n휂糊γ虻*ᴫ꾠?q凐趗Ko↦GT铮", + "㶢ថmO㍔k'诔栀Z蛟}GZ钹D", + false, + -6.366995517736813E-20, + -4894479530745302899, + null, + "V%᫡II璅䅛䓎풹ﱢ/pU9se되뛞x梔~C)䨧䩻蜺(g㘚R?/Ự[忓C뾠ࢤc왈邠买?嫥挤풜隊枕", + ",v碍喔㌲쟚蔚톬៓ꭶ", + 3.9625444752577524E-19, + null, + [ + "kO8란뿒䱕馔b臻⍟隨\"㜮鲣Yq5m퐔K#ꢘug㼈ᝦ=P^6탲@䧔%$CqSw铜랊0&m⟭<\/a逎ym\u0013vᯗ": true, + "洫`|XN뤮\u0018詞=紩鴘_sX)㯅鿻Ố싹": 7.168252736947373E-20, + "ꛊ饤ﴏ袁(逊+~⽫얢鈮艬O힉7D筗S곯w操I斞᠈븘蓷x": [[[[ + -7.3136069426336952E18, + -2.13572396712722688E18, + { + "硢3㇩R:o칢行E<=\u0018ၬYuH!\u00044U%卝炼2>\u001eSi$⓷ꒈ'렢gᙫ番ꯒ㛹럥嶀澈v;葷鄕x蓎\\惩+稘UEᖸﳊ㊈壋N嫿⏾挎,袯苷ኢ\\x|3c": 7540762493381776411, + "?!*^ᢏ窯?\u0001ڔꙃw虜돳FgJ?&⨫*uo籤:?}ꃹ=ٴ惨瓜Z媊@ત戹㔏똩Ԛ耦Wt轁\\枒^\\ꩵ}}}ꀣD\\]6M_⌫)H豣:36섘㑜": { + ";홗ᰰU஋㙛`D왔ཿЃS회爁\u001b-㢈`봆?盂㛣듿ᦾ蒽_AD~EEຆ㊋(eNwk=Rɠ峭q\"5Ἠ婾^>'ls\n8QAK)- Q䲌mo펹L_칍樖庫9꩝쪹ᘹ䑖瀍aK ?*趤f뭓廝p=磕", + "哑z懅ᤏ-ꍹux쀭", + [ + true, + 3998739591332339511, + "ጻ㙙?᳸aK<\/囩U`B3袗ﱱ?\"/k鏔䍧2l@쿎VZ쨎/6ꃭ脥|B?31+on颼-ꮧ,O嫚m ࡭`KH葦:粘i]aSU쓙$쐂f+詛頖b", + [{"^<9<箝&絡;%i﫡2攑紴\\켉h쓙-柂䚝ven\u20f7浯-Ꮏ\r^훁䓚헬\u000e?\\ㅡֺJ떷VOt": [{ + "-௄卶k㘆혐஽y⎱㢬sS઄+^瞥h;ᾷj;抭\u0003밫f<\/5Ⱗ裏_朻%*[-撵䷮彈-芈": { + "㩩p3篊G|宮hz䑊o곥j^Co0": [ + 653239109285256503, + {"궲?|\":N1ۿ氃NZ#깩:쇡o8킗ࡊ[\"됸Po핇1(6鰏$膓}⽐*)渽J'DN<썙긘毦끲Ys칖": { + "2Pr?Xjㆠ?搮/?㓦柖馃5뚣Nᦼ|铢r衴㩖\"甝湗ܝ憍": "\"뾯i띇筝牻$珲/4ka $匝휴译zbAᩁꇸ瑅&뵲衯ꎀᆿ7@ꈋ'ᶨH@ᠴl+", + "7뢽뚐v?4^ꊥ_⪛.>pởr渲<\/⢕疻c\"g䇘vU剺dஔ鮥꒚(dv祴X⼹\\a8y5坆": true, + "o뼄B욞羁hr﷔폘뒚⿛U5pꪴfg!6\\\"爑쏍䢱W<ﶕ\\텣珇oI/BK뺡'谑♟[Ut븷亮g(\"t⡎有?ꬊ躺翁艩nl F⤿蠜": 1695826030502619742, + "ۊ깖>ࡹ햹^ⵕ쌾BnN〳2C䌕tʬ]찠?ݾ2饺蹳ぶꌭ訍\"◹ᬁD鯎4e滨T輀ﵣ੃3\u20f3킙D瘮g\\擦+泙ၧ 鬹ﯨַ肋7놷郟lP冝{ߒhড়r5,꓋": null, + "ΉN$y{}2\\N﹯ⱙK'8ɜͣwt,.钟廣䎘ꆚk媄_": null, + "䎥eᾆᝦ읉,Jުn岪㥐s搖謽䚔5t㯏㰳㱊ZhD䃭f絕s鋡篟a`Q鬃┦鸳n_靂(E4迠_觅뷝_宪D(NL疶hL追V熑%]v肫=惂!㇫5⬒\u001f喺4랪옑": { + "2a輍85먙R㮧㚪Sm}E2yꆣꫨrRym㐱膶ᔨ\\t綾A☰.焄뙗9<쫷챻䒵셴᭛䮜.<\/慌꽒9叻Ok䰊Z㥪幸k": [ + null, + true, + {"쌞쐍": { + "▟GL K2i뛱iQ\"̠.옛1X$}涺]靎懠ڦ늷?tf灟ݞゟ{": 1.227740268699265E-19, + "꒶]퓚%ฬK❅": [{ + "(ෛ@Ǯっ䧼䵤[aテൖvEnAdU렖뗈@볓yꈪ,mԴ|꟢캁(而첸죕CX4Y믅": "2⯩㳿ꢚ훀~迯?᪑\\啚;4X\u20c2襏B箹)俣eỻw䇄", + "75༂f詳䅫ꐧ鏿 }3\u20b5'∓䝱虀f菼Iq鈆﨤g퍩)BFa왢d0뮪痮M鋡nw∵謊;ꝧf美箈ḋ*\u001c`퇚퐋䳫$!V#N㹲抗ⱉ珎(V嵟鬒_b㳅\u0019": null, + "e_m@(i㜀3ꦗ䕯䭰Oc+-련0뭦⢹苿蟰ꂏSV䰭勢덥.ྈ爑Vd,ᕥ=퀍)vz뱊ꈊB_6듯\"?{㒲&㵞뵫疝돡믈%Qw限,?\r枮\"? N~癃ruࡗdn&": null, + "㉹&'Pfs䑜공j<\/?|8oc᧨L7\\pXᭁ 9᪘": -2.423073789014103E18, + "䝄瑄䢸穊f盈᥸,B뾧푗횵B1쟢f\u001f凄": "魖⚝2儉j꼂긾껢嗎0ࢇ纬xI4](੓`蕞;픬\fC\"斒\")2櫷I﹥迧", + "ퟯ詔x悝령+T?Bg⥄섅kOeQ큼㻴*{E靼6氿L缋\u001c둌๶-㥂2==-츫I즃㠐Lg踞ꙂEG貨鞠\"\u0014d'.缗gI-lIb䋱ᎂDy缦?": null, + "紝M㦁犿w浴詟棓쵫G:䜁?V2ힽ7N*n&㖊Nd-'ຊ?-樹DIv⊜)g䑜9뉂ㄹ푍阉~ꅐ쵃#R^\u000bB䌎䦾]p.䀳": [{"ϒ爛\"ꄱ︗竒G䃓-ま帳あ.j)qgu扐徣ਁZ鼗A9A鸦甈!k蔁喙:3T%&㠘+,䷞|챽v䚞문H<\/醯r셓㶾\\a볜卺zE䝷_죤ဵ뿰᎟CB": [ + 6233512720017661219, + null, + -1638543730522713294, + false, + -8901187771615024724, + [ + 3891351109509829590, + true, + false, + -1.03836679125188032E18, + { + "j랎:g曞ѕᘼ}链N", + -1.1103819473845426E-19, + true, + [ + true, + null, + -7.9091791735309888E17, + true, + {"}蔰鋈+ꐨ啵0?g*사%`J?*": [{ + "\"2wG?yn,癷BK\\龞䑞x?蠢": -3.7220345009853505E-19, + ";饹়❀)皋`噿焒j(3⿏w>偍5X薙婏聿3aFÆÝ": "2,ꓴg?_섦_>Y쪥션钺;=趘F~?D㨫\bX?㹤+>/믟kᠪ멅쬂Uzỵ]$珧`m雁瑊ඖ鯬cꙉ梢f묛bB", + "♽n$YjKiXX*GO贩鏃豮祴遞K醞眡}ꗨv嵎꼷0୸+M菋eH徸J꣆:⼐悥B켽迚㯃b諂\u000bjꠜ碱逮m8": [ + "푷᣺ﻯd8ﱖ嬇ភH鹎⡱᱅0g:果6$GQ췎{vᷧYy-脕x偹砡館⮸C蓼ꏚ=軄H犠G谖ES詤Z蠂3l봟hᅭ7䦹1GPQG癸숟~[#駥8zQ뛣J소obg,", + null, + 1513751096373485652, + null, + -6.851466660824754E-19, + {"䩂-⴮2ٰK솖풄꾚ႻP앳1H鷛wmR䗂皎칄?醜<\/&ࠧ㬍X濬䵈K`vJ륒Q/IC묛!;$vϑ": { + "@-ꚗxྐྵ@m瘬\u0010U絨ﮌ驐\\켑寛넆T=tQ㭤L연@脸삯e-:⩼u㎳VQ㋱襗ຓ<Ⅶ䌸cML3+\u001e_C)r\\9+Jn\\Pﺔ8蠱檾萅Pq鐳话T䄐I": -1.80683891195530061E18, + "ᷭዻU~ཷsgSJ`᪅'%㖔n5픆桪砳峣3獮枾䌷⊰呀": { + "Ş੉䓰邟自~X耤pl7间懑徛s첦5ਕXexh⬖鎥᐀nNr(J컗|ૃF\"Q겮葲놔엞^겄+㈆话〾희紐G'E?飕1f❼텬悚泬먐U睬훶Qs": false, + "(\u20dag8큽튣>^Y{뤋.袊䂓;_g]S\u202a꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\u001asD7IK5Wxo8\u0006p弊⼂ꯍ扵\u0003`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\": [ + true, + { + "\n鱿aK㝡␒㼙2촹f;`쾏qIࡔG}㝷䐍瓰w늮*粅9뒪ㄊCj倡翑閳R渚MiUO~仨䜶RꙀA僈㉋⦋n{㖥0딿벑逦⥻0h薓쯴Ꝼ": [ + 5188716534221998369, + 2579413015347802508, + 9.010794400256652E-21, + -6.5327297761238093E17, + 1.11635352494065523E18, + -6656281618760253655, + { + "": ")?", + "TWKLꑙ裑꺔UE俸塑炌Ũ᜕-o\"徚#": {"M/癟6!oI51ni퐚=댡>xꍨ\u0004 ?": { + "皭": {"⢫䋖>u%w잼<䕏꘍P䋵$魋拝U䮎緧皇Y훂&|羋ꋕ잿cJ䨈跓齳5\u001a삱籷I꿾뤔S8㌷繖_Yឯ䲱B턼O歵F\\l醴o_欬6籏=D": [ + false, + true, + {"Mt|ꏞD|F궣MQ뵕T,띺k+?㍵i": [ + 7828094884540988137, + false, + { + "!༦鯠,&aﳑ>[euJꏽ綷搐B.h": -7648546591767075632, + "-n켧嘰{7挐毄Y,>❏螵煫乌pv醑Q嶚!|⌝責0왾덢ꏅ蛨S\\)竰'舓Q}A釡5#v": 3344849660672723988, + "8閪麁V=鈢1녈幬6棉⪮둌\u207d᚛驉ꛃ'r䆉惏ै|bἧﺢᒙ<=穊强s혧eꮿ慩⌡ \\槳W븧J檀C,ᘉ의0俯퀉M;筷ࣴ瓿{늊埂鄧_4揸Nn阼Jੵ˥(社": true, + "o뼀vw)4A뢵(a䵢)p姃뛸\u000fK#KiQp\u0005ꅍ芅쏅": null, + "砥$ꥸ┇耽u斮Gc{z빔깎밇\\숰\u001e괷各㶇쵿_ᴄ+h穢p촀Ნ䃬z䝁酳ӂ31xꔄ1_砚W렘G#2葊P ": [ + -3709692921720865059, + null, + [ + 6669892810652602379, + -135535375466621127, + "뎴iO}Z? 馢녱稹ᄾ䐩rSt帤넆&7i騏멗畖9誧鄜'w{Ͻ^2窭외b㑎粖i矪ꦨ탪跣)KEㆹ\u0015V8[W?⽉>'kc$䨘ᮛ뉻٬M5", + 1.10439588726055846E18, + false, + -4349729830749729097, + null, + [ + false, + "_蠢㠝^䟪/D녒㡋ỎC䒈판\u0006એq@O펢%;鹐쏌o戥~A[ꡉ濽ỳ&虃᩾荣唙藍茨Ig楡꒻M窓冉?", + true, + 2.17220752996421728E17, + -5079714907315156164, + -9.960375974658589E-20, + "ᾎ戞༒", + true, + false, + [[ + "ⶉᖌX⧕홇)g엃⹪x뚐癟\u0002", + -5185853871623955469, + { + "L㜤9ợㇶK鐰⋓V뽋˖!斫as|9"፬䆪?7胜&n薑~": -2.11545634977136992E17, + "O8뀩D}캖q萂6༣㏗䈓煮吽ਆᎼDᣘ폛;": false, + "YTᡅ^L㗎cbY$pᣞ縿#fh!ꘂb삵玊颟샞ဢ$䁗鼒몁~rkH^:닮먖츸륈⪺쒉砉?㙓扫㆕꣒`R䢱B酂?C뇞<5Iޚ讳騕S瞦z": null, + "\\RB?`mG댵鉡幐物䵎有5*e骄T㌓ᛪ琾駒Ku\u001a[柆jUq8⋈5鿋츿myﻗ?雍ux঴?": 5828963951918205428, + "n0晅:黯 xu씪^퓞cB㎊ᬍ⺘٤փ~B岚3㥕擄vᲂ~F?C䶖@$m~忔S왖㲚?챴⊟W#벌{'㰝I䝠縁s樘\\X뢻9핡I6菍ㄛ8쯶]wॽ0L\"q": null, + "x增줖j⦦t䏢᎙㛿Yf鼘~꫓恄4惊\u209c": "oOhbᤃ᛽z&Bi犑\\3B㩬劇䄑oŁ쨅孥멁ຖacA㖫借㞝vg싰샂㐜#譞⢤@k]鋰嘘䜾L熶塥_<\/⍾屈ﮊ_mY菹t뙺}Ox=w鮮4S1ꐩמּ'巑", + "㗓蟵ꂾe蠅匳(JP䗏෸\u0089耀왲": [{ + "ᤃ㵥韎뤽\r?挥O쯡⇔㞚3伖\u0005P⋪\"D궣QLn(⚘罩䩢Ŏv䤘尗뼤됛O淽鋋闚r崩a{4箙{煷m6〈": { + "l곺1L": { + "T'ਤ?砅|੬Km]䄩\"(࿶<\/6U爢䫈倔郴l2㴱^줣k'L浖L鰄Rp今鎗⒗C얨M훁㡧ΘX粜뫈N꤇輊㌻켑#㮮샶-䍗룲蠝癜㱐V>=\\I尬癤t=": 7648082845323511446, + "鋞EP:<\/_`ၧe混ㇹBd⯢㮂驋\\q碽饩跓྿ᴜ+j箿렏㗑yK毢宸p謹h䦹乕U媣\\炤": [[ + "3", + [ + true, + 3.4058271399411134E-20, + true, + "揀+憱f逮@먻BpW曉\u001a㣐⎊$n劈D枤㡞좾\u001aᛁ苔౩闝1B䷒Ṋ݋➐ꀞꐃ磍$t੤_:蘺⮼(#N", + 697483894874368636, + [ + "vᘯ锴)0訶}䳅⩚0O壱韈ߜ\u0018*U鍾䏖=䧉뽑单휻ID쿇嘗?ꌸῬ07", + -5.4858784319382006E18, + 7.5467775182251151E18, + -8911128589670029195, + -7531052386005780140, + null, + [ + null, + true, + [[{ + "1欯twG<\/Q:0怯押殃탷聫사<ỗꕧ蚨䡁nDꌕ\u001c녬~蓩鲃g儊>ꏡl㻿/⑷*챳6㻜W毤緛ﹺᨪ4\u0013뺚J髬e3쳸䘦伧?恪&{L掾p+꬜M䏊d娘6": { + "2p첼양棜h䜢﮶aQ*c扦v︥뮓kC寵횂S銩&ǝ{O*य़iH`U큅ࡓr䩕5ꄸ?`\\᧫?ᮼ?t〟崾훈k薐ì/iy꤃뵰z1<\/AQ#뿩8jJ1z@u䕥": 1.82135747285215155E18, + "ZdN &=d년ᅆ'쑏ⅉ:烋5&៏ᄂ汎来L㯄固{钧u\\㊏튚e摑&t嗄ꖄUb❌?m䴘熚9EW": [{ + "ଛ{i*a(": -8.0314147546006822E17, + "⫾ꃆY\u000e+W`௸ \"M뒶+\\뷐lKE}(NT킶Yj選篒쁶'jNQ硾(똡\\\"逌ⴍy? IRꜘ὞鄬﨧:M\\f⠋Cꚜ쫊ᚴNV^D䕗ㅖἔIao꿬C⍏8": [ + 287156137829026547, + { + "H丞N逕⯲": {"": { + "7-;枮阕梒9ᑄZ": [[[[ + null, + { + "": [[[[ + -7.365909561486078E-19, + 2948694324944243408, + null, + [ + true, + "荒\"并孷䂡쵼9o䀘F\u0002龬7⮹Wz%厖/*? a*R枈㌦됾g뒠䤈q딄㺿$쮸tᶎ릑弣^鏎<\/Y鷇驜L鿽<\/춋9Mᲆឨ^<\/庲3'l낢", + "c鮦\u001b두\\~?眾ಢu݆綑෪蘛轋◜gȃ<\/ⴃcpkDt誩܅\"Y", + [[ + null, + null, + [ + 3113744396744005402, + true, + "v(y", + { + "AQ幆h쾜O+꺷铀ꛉ練A蚗⼺螔j㌍3꽂楎䥯뎸먩?": null, + "蠗渗iz鱖w]擪E": 1.2927828494783804E-17, + "튷|䀭n*曎b✿~杤U]Gz鄭kW|㴚#㟗ഠ8u擨": [[ + true, + null, + null, + {"⾪壯톽g7?㥜ώQꑐ㦀恃㧽伓\\*᧰閖樧뢇赸N휶䎈pI氇镊maᬠ탷#X?A+kНM ༑᩟؝?5꧎鰜ṚY즫궔 =ঈ;ﳈ?*s|켦蜌wM笙莔": [ + null, + -3808207793125626469, + [ + -469910450345251234, + 7852761921290328872, + -2.7979740127017492E18, + 1.4458504352519893E-20, + true, + "㽙깹?먏䆢:䴎ۻg殠JBTU⇞}ꄹꗣi#I뵣鉍r혯~脀쏃#釯:场:䔁>䰮o'㼽HZ擓௧nd", + [ + 974441101787238751, + null, + -2.1647718292441327E-19, + 1.03602824249831488E18, + [ + null, + 1.0311977941822604E-17, + false, + true, + { + "": -3.7019778830816707E18, + "E峾恆茍6xLIm縂0n2视֯J-ᤜz+ᨣ跐mYD豍繹⹺䊓몓ﴀE(@詮(!Y膽#᎙2䟓섣A䈀㟎,囪QbK插wcG湎ꤧtG엝x⥏俎j'A一ᯥ뛙6ㅑ鬀": 8999803005418087004, + "よ殳\\zD⧅%Y泥簳Uꈩ*wRL{3#3FYHା[d岀䉯T稉駅䞘礄P:闈W怏ElB㤍喬赔bG䠼U଄Nw鰯闀楈ePsDꥷ꭬⊊": [ + 6.77723657904486E-20, + null, + [ + "ཚ_뷎꾑蹝q'㾱ꂓ钚蘞慵렜떆`ⴹ⎼櫯]J?[t9Ⓢ !컶躔I᮸uz>3a㠕i,錃L$氰텰@7녫W㸮?羧W뇧ꃞ,N鋮숪2ɼ콏┍䁲6", + "&y?뢶=킕올Za惻HZk>c\u20b58i?ꦶcfBv잉ET9j䡡", + "im珊Ճb칧校\\뼾쯀", + 9.555715121193197E-20, + true, + { + "<㫚v6腓㨭e1㕔&&V∌ᗈT奄5Lጥ>탤?튣瑦㳆ꉰ!(ᙪ㿬擇_n쌯IMΉ㕨␰櫈ᱷ5풔蟹&L.첽e鰷쯃劼﫭b#ﭶ퓀7뷄Wr㢈๧Tʴશ㶑澕鍍%": -1810142373373748101, + "fg晌o?߲ꗄ;>C>?=鑰監侯Kt굅": true, + "䫡蓺ꑷ]C蒹㦘\"1ః@呫\u0014NL䏾eg呮፳,r$裢k>/\\?ㄤᇰﻛ쉕1஥'Ċ\" \\_?쨔\"ʾr: 9S䘏禺ᪧꄂ㲄", + [[{ + "*硙^+E쌺I1䀖ju?:⦈Ꞓl๴竣迃xKC/饉:\fl\"XTFᄄ蟭,芢<\/骡軺띜hꏘ\u001f銿<棔햳▨(궆*=乥b8\\媦䷀뫝}닶ꇭ(Kej䤑M": [{ + "1Ꮼ?>옿I╅C<ގ?ꊌ冉SV5A㢊㶆z-๎玶绢2F뵨@㉌뀌o嶔f9-庒茪珓뷳4": null, + ";lᰳ": "CbB+肻a䄷苝*/볳+/4fq=㰁h6瘉샴4铢Y骐.⌖@哼猎㦞+'gꋸ㒕ߤ㞑(䶒跲ti⑴a硂#No볔", + "t?/jE幸YHT셵⩎K!Eq糦ꗣv刴w\"l$ο:=6:移": { + "z]鑪醊嫗J-Xm銌翁絨c里됏炙Ep㣋鏣똼嚌䀓GP﹖cmf4鹭T䅿꣭姧␸wy6ꦶ;S&(}ᎧKxᾂQ|t뻳k\"d6\"|Ml췆hwLt꼼4$&8Պ褵婶鯀9": {"嵃닢ᒯ'd᧫䳳#NXe3-붋鸿ଢ떓%dK\u0013䲎ꖍYV.裸R⍉rR3蟛\\:젯:南ĺLʆ넕>|텩鴷矔ꋅⒹ{t孶㓑4_": [ + true, + null, + [ + false, + "l怨콈lᏒ", + { + "0w䲏嬧-:`䉅쉇漧\\܂yㄨb%㽄j7ᦶ涶<": 3.7899452730383747E-19, + "ꯛTẀq纤q嶏V⿣?\"g}ი艹(쥯B T騠I=仵및X": {"KX6颠+&ᅃ^f畒y[": { + "H?뱜^?꤂-⦲1a㋞&ꍃ精Ii᤾챪咽쬘唂쫷<땡劈훫놡o㥂\\ KⴙD秼F氮[{'좴:례晰Iq+I쭥_T綺砸GO煝䟪ᚪ`↹l羉q쐼D꽁ᜅ훦: vUV": true, + "u^yﳍ0㱓#[y뜌앸ꊬL㷩?蕶蘾⻍KӼ": -7931695755102841701, + "䤬轉車>\u001c鴵惋\"$쯃྆⇻n뽀G氠S坪]ಲꨍ捇Qxኻ椕駔\\9ࣼ﫻읜磡煮뺪ᶚ볝l㕆t+sζ": [[[ + true, + false, + [ + null, + 3363739578828074923, + true, + { + "\"鸣詩 볰㑵gL㯦῅춝旫}ED辗ﮈI쀤-ꧤ|㠦Z\"娑ᕸ4爏騍㣐\"]쳝Af]茛⬻싦o蚁k䢯䩐菽3廇喑ޅ": 4.5017999150704666E17, + "TYႇ7ʠ值4챳唤~Zo&ݛ": false, + "`塄J袛㭆끺㳀N㺣`꽐嶥KﯝSVᶔ∲퀠獾N딂X\"ᤏhNﬨvI": {"\u20bb㭘I䖵䰼?sw䂷쇪](泒f\"~;꼪Fԝsᝦ": {"p,'ꉂ軿=A蚶?bƉ㏵䅰諬'LYKL6B깯⋩겦뎙(ᜭ\u0006噣d꾆㗼Z;䄝䚔cd<情@䞂3苼㸲U{)<6&ꩻ钛\u001au〷N숨囖愙j=BXW욕^x芜堏Ῑ爂뛷꒻t✘Q\b": [[ + "籛&ଃ䩹.ꃩ㦔\\C颫#暪&!勹ꇶ놽攺J堬镙~軌C'꾖䣹㮅岃ᙴ鵣", + 4.317829988264744E15, + 6.013585322002147E-20, + false, + true, + null, + null, + -3.084633632357326E-20, + false, + null, + { + "\"짫愔昻 X\"藣j\"\"먁ཅѻ㘤㬯0晲DU꟒㸃d벀윒l䦾c੻*3": null, + "谈Wm陧阦咟ฯ歖擓N喴㋐銭rCCnVࢥ^♼Ⅾ젲씗刊S༝+_t赔\\b䚍뉨ꬫ6펛cL䊘᜼<\/澤pF懽&H": [ + null, + { + "W\"HDUuΌ퀟M'P4࿰H똆ⰱﮯ<\/凐蘲\"C鴫ﭒж}ꭩ쥾t5yd诪ﮡ퍉ⴰ@?氐醳rj4I6Qt": 6.9090159359219891E17, + "絛ﳛ⺂": {"諰P㗮聦`ZQ?ꫦh*റcb⧱}埌茥h{棩렛툽o3钛5鮁l7Q榛6_g)ὄ\u0013kj뤬^爖eO4Ⱈ槞鉨ͺ订%qX0T썗嫷$?\\\"봅늆'%": [ + -2.348150870600346E-19, + [[ + true, + -6619392047819511778, + false, + [[ + -1.2929189982356161E-20, + 1.7417192219309838E-19, + {"?嵲2࿐2\u0001啑㷳c縯": [ + null, + [ + false, + true, + 2578060295690793218, + { + "?\"殃呎#㑑F": true, + "}F炊_殛oU헢兔Ꝉ,赭9703.B数gTz3⏬": { + "5&t3,햓Mݸᵣ㴵;꣫䩍↳#@뫷䠅+W-ࣇzᓃ鿕ಔ梭?T䮑ꥬ旴]u뫵막bB讍:왳둛lEh=숾鱠p咐$짏#?g⹷ᗊv㷵.斈u頻\u0018-G.": "뽙m-ouࣤ஫牷\"`Ksꕞ筼3HlȨvC堈\"I]㖡玎r먞#'W賜鴇k'c룼髋䆿飉㗆xg巤9;芔cጐ/ax䊨♢큓r吓㸫೼䢗da᩾\"]屣`", + ":M딪<䢥喠\u0013㖅x9蕐㑂XO]f*Q呰瞊吭VP@9,㨣 D\\穎vˤƩs㜂-曱唅L걬/롬j㈹EB8g<\/섩o渀\"u0y&룣": ">氍緩L/䕑돯Ꟙ蕞^aB뒣+0jK⪄瑨痜LXK^힦1qK{淚t츔X:Vm{2r獁B뾄H첚7氥?쉟䨗ꠂv팳圎踁齀\\", + "D彤5㢷Gꪻ[lㄆ@὜⓰絳[ଃ獽쮹☒[*0ꑚ㜳": 9022717159376231865, + "ҖaV銣tW+$魿\u20c3亜~뫡ᙰ禿쨽㏡fṼzE/h": "5臐㋇Ჯ쮺? 昨탰Wム밎#'\"崲钅U?幫뺀⍾@4kh>騧\\0ҾEV=爐͌U捀%ꉼ 㮋<{j]{R>:gԩL\u001c瀈锌ﯲﳡꚒ'⫿E4暍㌗뵉X\"H᝜", + "ᱚגּ;s醒}犍SἿ㦣&{T$jkB\\\tḮ앾䤹o<避(tW": "vb⯽䴪䮢@|)", + "⥒퐁껉%惀뗌+녣迺顀q條g⚯i⤭룐M琹j̈́⽜A": -8385214638503106917, + "逨ꊶZ<\/W⫟솪㎮ᘇb?ꠔi\"H㧺x෷韒Xꫨฟ|]窽\u001a熑}Agn?Mᶖa9韲4$3Ỵ^=쏍煤ፐ돷2䣃%鷠/eQ9頸쥎", + 2398360204813891033, + false, + 3.2658897259932633E-19, + null, + "?ꚃ8Nn㞷幵d䲳䱲뀙ꪛQ瑓鎴]䩋-鰾捡䳡??掊", + false, + -1309779089385483661, + "ᦲxu_/yecR.6芏.ᜇ過 ~", + -5658779764160586501, + "쒌:曠=l썜䢜wk#s蕚\"互㮉m䉤~0듐䋙#G;h숄옥顇෤勹(C7㢅雚㐯L⠅VV簅<", + null, + -4.664877097240962E18, + -4.1931322262828017E18, + { + ",": { + "v㮟麑䄠뤵g{M띮.\u001bzt뢜뵡0Ǥ龍떟Ᾰ怷ϓRT@Lꀌ樂U㏠⾕e扉|bJg(뵒㠶唺~ꂿ(땉x⻫싉쁊;%0鎻V(o\f,N鏊%nk郼螺": -1.73631993428376141E18, + "쟧摑繮Q@Rᕾ㭚㾣4隅待㓎3蒟": [ + 4971487283312058201, + 8973067552274458613, + { + "`a揙ᣗ\u0015iBo¸": 4.3236479112537999E18, + "HW&퉡ぁ圍Y?瑡Qy훍q!帰敏s舠㫸zꚗaS歲v`G株巷Jp6킼 (귶鍔⾏⡈>M汐㞍ቴ꙲dv@i㳓ᇆ?黍": [ + null, + 4997607199327183467, + "E㻎蠫ᐾ高䙟蘬洼旾﫠텛㇛?'M$㣒蔸=A_亀绉앭rN帮", + null, + [{ + "Eᑞ)8餧A5u&㗾q?": [ + -1.969987519306507E-19, + null, + [ + 3.42437673373841E-20, + true, + "e걷M墁\"割P␛퍧厀R䱜3ﻴO퓫r﹉⹊", + [ + -8164221302779285367, + [ + true, + null, + "爘y^-?蘞Ⲽꪓa␅ꍨ}I", + 1.4645984996724427E-19, + [{ + "tY좗⧑mrzﺝ㿥ⴖ᥷j諅\u0000q賋譁Ꞅ⮱S\nࡣB/큃굪3Zɑ复o<\/;롋": null, + "彟h浠_|V4䦭Dᙣ♞u쿻=삮㍦\u001e哀鬌": [{"6횣楠,qʎꗇ鎆빙]㱭R굋鈌%栲j分僅ペ䇰w폦p蛃N溈ꡐꏀ?@(GI뉬$ﮄ9誁ꓚ2e甸ڋ[䁺,\u0011\u001cࢃ=\\+衪䷨ᯕ鬸K": [[ + "ㅩ拏鈩勥\u000etgWVXs陂規p狵w퓼{뮵_i\u0002ퟑႢ⬐d6鋫F~챿搟\u0096䚼1ۼ칥0꣯儏=鋷牋ⅈꍞ龐", + -7283717290969427831, + true, + [ + 4911644391234541055, + { + "I鈒첽P릜朸W徨觘-Hᎄ퐟⓺>8kr1{겵䍃〛ᬡ̨O귑o䝕'쿡鉕p5": "fv粖RN瞖蛐a?q꤄\u001d⸥}'ꣴ犿ꦼ?뤋?鵆쥴덋䡫s矷̄?ඣ/;괱絢oWfV<\/\u202cC,㖦0䑾%n賹g&T;|lj_欂N4w", + "짨䠗;䌕u i+r๏0": [{"9䥁\\఩8\"馇z䇔<\/ႡY3e狚쐡\"ุ6ﰆZ遖c\"Ll:ꮾ疣<\/᭙O◌납୕湞9⡳Und㫜\u0018^4pj1;䧐儂䗷ୗ>@e톬": { + "a⑂F鋻Q螰'<퇽Q贝瀧{ᘪ,cP&~䮃Z?gI彃": [ + -1.69158726118025933E18, + [ + "궂z簽㔛㮨瘥⤜䛖Gℤ逆Y⪾j08Sn昞ꘔ캻禀鴚P謦b{ꓮmN靐Mᥙ5\"睏2냑I\u0011.L&=?6ᄠ뻷X鸌t刑\"#z)o꫚n쳟줋", + null, + 7517598198523963704, + "ኑQp襟`uᩄr方]*F48ꔵn俺ሙ9뇒", + null, + null, + 6645782462773449868, + 1219168146640438184, + null, + { + ")ယ넌竀Sd䰾zq⫣⏌ʥ\u0010ΐ' |磪&p牢蔑mV蘸૰짬꺵;K": [ + -7.539062290108008E-20, + [ + true, + false, + null, + true, + 6574577753576444630, + [[ + 1.2760162530699766E-19, + [ + null, + [ + "顊\\憎zXB,", + [{ + "㇆{CVC9-MN㜋ઘR눽#{h@ퟨ!鼚׼XOvXS\u0017ᝣ=cS+梽៲綆16s덽휐y屬?ᇳG2ᴭ\u00054쫖y룇nKcW̭炦s/鰘ᬽ?J|퓀髣n勌\u0010홠P>j": false, + "箴": [ + false, + "鍞j\"ꮾ*엇칬瘫xṬ⭽쩁䃳\"-⋵?ᦽ댎Ĝ": true, + "Pg帯佃籛n㔠⭹࠳뷏≻࿟3㞱!-쒾!}쭪䃕!籿n涻J5ਲ਼yvy;Rኂ%ᔡጀ裃;M⣼)쵂쑈": 1.80447711803435366E18, + "ꈑC⡂ᑆ㤉壂뎃Xub<\/쀆༈憓ق쨐ק\\": [ + 7706977185172797197, + {"": {"K╥踮砆NWࡆFy韣7ä밥{|紒︧䃀榫rᩛꦡTSy잺iH8}ퟴ,M?Ʂ勺ᴹ@T@~꾂=I㙕뾰_涀쑜嫴曣8IY?ҿo줫fऒ}\\S\"ᦨ뵼#nDX": { + "♘k6?଱癫d68?㽚乳䬳-V顷\u0005蝕?\u0018䞊V{邾zじl]雏k臤~ൖH뒐iꢥ]g?.G碄懺䔛pR$䅒X觨l봜A刊8R梒',}u邩퉕?;91Ea䈈믁G⊶芔h袪&廣㺄j;㡏綽\u001bN頸쳘橆": -2272208444812560733, + "拑Wﵚj鵼駳Oࣿ)#㾅顂N傓纝y僱栜'Bꐍ-!KF*ꭇK¦?䈴^:啤wG逭w᧯": "xᣱmYe1ۏ@霄F$ě꧘푫O䤕퀐Pq52憬ꀜ兴㑗ᡚ?L鷝ퟐ뭐zJꑙ}╆ᅨJB]\"袌㺲u8䯆f", + "꿽၅㔂긱Ǧ?SI": -1669030251960539193, + "쇝ɨ`!葎>瞺瘡驷錶❤ﻮ酜=": -6961311505642101651, + "?f7♄꫄Jᡔ훮e읇퍾፣䭴KhखT;Qty}O\\|뫁IῒNe(5惁ꥶㆷY9ﮡ\\ oy⭖-䆩婁m#x봉>Y鈕E疣s驇↙ᙰm<": {"퉻:dꂁ&efᅫ쫢[\"돈늖꺙|Ô剐1͖-K:ʚ᭕/;쏖㷛]I痐职4gZ4⍜kเꛘZ⥺\\Bʫᇩ鄨魢弞&幟ᓮ2̊盜", + -9006004849098116748, + -3118404930403695681, + { + "_彃Y艘-\"Xx㤩㳷瑃?%2䐡鵛o귵옔夘v*탋职&㳈챗|O钧": [ + false, + "daꧺdᗹ羞쯧H㍤鄳頳<型孒ン냆㹀f4㹰\u000f|C*ሟ鰠(O<ꨭ峹ipຠ*y೧4VQ蔔hV淬{?ᵌEfrI_", + "j;ꗣ밷邍副]ᗓ", + -4299029053086432759, + -5610837526958786727, + [ + null, + [ + -1.3958390678662759E-19, + { + "lh좈T_믝Y\"伨\u001cꔌG爔겕ꫳ晚踍⿻읐T䯎]~e#฽燇\"5hٔ嶰`泯r;ᗜ쮪Q):/t筑,榄&5懶뎫狝(": [{ + "2ፁⓛ]r3C攟וּ9賵s⛔6'ஂ|\"ⵈ鶆䐹禝3\"痰ࢤ霏䵩옆䌀?栕r7O簂Isd?K᫜`^讶}z8?z얰T:X倫⨎ꑹ": -6731128077618251511, + "|︦僰~m漿햭\\Y1'Vvخ굇ቍ챢c趖": [null] + }], + "虌魿閆5⛔煊뎰㞤ᗴꥰF䮥蘦䂪樳-K᝷-(^\u20dd_": 2.11318679791770592E17 + } + ] + ] + ]}, + "묗E䀳㧯᳀逞GMc\b墹㓄끖Ơ&U??펌鑍 媋k))ᄊ": null, + "묥7콽벼諌J_DɯﮪM殴䣏,煚ྼ`Y:씧<\/⩫%yf䦀!1Ჶk춎Q米W∠WC跉鬽*ᛱi㴕L꘻ꀏ쓪\"_g鿄'#t⽙?,Wg㥖|D鑆e⥏쪸僬h鯔咼ඡ;4TK聎졠嫞" + } + ] + ] + } + ] + ] + ]}} + } + ]} + }, + "뿋뀾淣截䔲踀&XJ펖꙯^Xb訅ꫥgᬐ>棟S\"혧騾밫겁7-": "擹8C憎W\"쵮yR뢩浗絆䠣簿9䏈引Wcy䤶孖ꯥ;퐌]輩䍐3@{叝 뽸0ᡈ쵡Ⲇ\u001dL匁꧐2F~ݕ㪂@W^靽L襒ᦘ~沦zZ棸!꒲栬R" + } + ] + ], + "Z:덃൛5Iz찇䅄駠㭧蓡K1": "e8᧤좱U%?ⵇ䯿鿝\u0013縮R∱骒EO\u000fg?幤@֗퉙vU`", + "䐃쪈埽້=Ij,쭗쓇చ": false + }]}} + ] + } + ]} + } + ] + ] + ], + "咰긖VM]᝼6䓑쇎琺etDҌ?㞏ꩄ퇫밉gj8蠃\"⩐5䛹1ࣚ㵪": "ക蹊?⎲⧘⾚̀I#\"䈈⦞돷`wo窭戕෱휾䃼)앷嵃꾞稧,Ⴆ윧9S?೗EMk3Მ3+e{⹔Te驨7䵒?타Ulg悳o43" + } + ], + "zQᤚ纂땺6#ٽ﹧v￿#ࠫ휊冟蹧텈ꃊʆ?&a䥯De潝|쿓pt瓞㭻啹^盚2Ꝋf醪,얏T窧\\Di䕎谄nn父ꋊE": -2914269627845628872, + "䉩跐|㨻ᷢ㝉B{蓧瞸`I!℄욃힕#ೲᙾ竛ᔺCjk췒늕貭词\u0017署?W딚%(pꍁ⤼띳^=on뺲l䆼bzrﳨ[&j狸䠠=ᜑꦦ\u2061յnj=牲攑)M\\龏": false, + "뎕y絬᫡⥮Ϙᯑ㌔/NF*˓.,QEzvK!Iwz?|쥾\"ꩻL꼗Bꔧ賴緜s뉣隤茛>ロ?(?^`>冺飒=噸泥⺭Ᲊ婓鎔븜z^坷裮êⓅ໗jM7ﶕ找\\O": 1.376745434746303E-19 + }, + "䐛r滖w㏤,|Nዜ": false + } + ]], + "@꿙?薕尬 gd晆(띄5躕ﻫS蔺4)떒錸瓍?~": 1665108992286702624, + "w믍nᏠ=`঺ᅥC>'從됐槷䤝眷螄㎻揰扰XᅧC贽uჍ낟jKD03T!lDV쀉Ӊy뢖,袛!终캨G?鉮Q)⑗1쾅庅O4ꁉH7?d\u0010蠈줘월ސ粯Q!낇껉6텝|{": null, + "~˷jg쿤촖쉯y": -5.5527605669177098E18, + "펅Wᶺzꐆと푭e?4j仪열[D<鈑皶婆䵽ehS?袪;HꍨM뗎ば[(嗏M3q퍟g4y╸鰧茀[Bi盤~﫝唎鋆彺⦊q?B4쉓癚O洙킋툈䶯_?ퟲ": null + } + ] + ]] + ]], + "꟱Ԕ㍤7曁聯ಃ錐V䷰?v㪃૦~K\"$%请|ꇹn\"k䫛㏨鲨\u2023䄢\u0004[︊VJ?䶟ាꮈ䗱=깘U빩": -4863152493797013264 + } + ]}]} + ] + }}} + ], + "쏷쐲۹퉃~aE唙a챑,9㮹gLHd'䔏|킗㍞䎥&KZYT맵7䥺Nⱳ同莞鿧w\\༌疣n/+ꎥU\"封랾○ퟙAJᭌ?9䛝$?驔9讐짘魡T֯c藳`虉C읇쐦T" + } + ], + "谶개gTR￐>ၵ͚dt晑䉇陏滺}9㉸P漄": -3350307268584339381 + }] + ] + ] + ]] + ] + ], + "0y꟭馋X뱔瑇:䌚￐廿jg-懲鸭䷭垤㒬茭u賚찶ಽ+\\mT땱\u20821殑㐄J쩩䭛ꬿNS潔*d\\X,壠뒦e殟%LxG9:摸": 3737064585881894882, + "풵O^-⧧ⅶvѪ8廸鉵㈉ר↝Q㿴뺟EႳvNM:磇>w/៻唎뷭୥!냹D䯙i뵱貁C#⼉NH6`柴ʗ#\\!2䂗Ⱨf?諳.P덈-返I꘶6?8ꐘ": -8934657287877777844, + "溎-蘍寃i诖ര\"汵\"\ftl,?d⼡쾪⺋h匱[,෩I8MҧF{k瓿PA'橸ꩯ綷퉲翓": null + } + ] + ], + "ោ係؁<元": 1.7926963090826924E-18 + }}] + } + ] + ]]}] + }] + ] + ] + ] + ], + "ጩV<\"ڸsOᤘ": 2.0527167903723048E-19 + }] + ]} + ] + ]], + "∳㙰3젴p᧗䱙?`yZA8Ez0,^ᙛ4_0븢\u001ft:~䎼s.bb룦明yNP8弆C偯;⪾짍'蕴뮛": -6976654157771105701, + "큵ꦀ\\㇑:nv+뒤燻䀪ﴣ﷍9ᚈ኷K㚊誦撪䚛,ꮪxሲ쳊\u0005HSf?asg昱dqꬌVꙇ㼺'k*'㈈": -5.937042203633044E-20 + } + ] + }], + "?}\u20e0],s嶳菋@#2u쒴sQS䩗=ꥮ;烌,|ꘔ䘆": "ᅩ영N璠kZ먕眻?2ቲ芋眑D륟渂⸑ﴃIRE]啗`K'" + }}, + "쨀jmV賂ﰊ姐䂦玞㬙ᏪM᪟Վ씜~`uOn*ॠ8\u000ef6??\\@/?9見d筜ﳋB|S䝬葫㽁o": true + }, + "즛ꄤ酳艚␂㺘봿㎨iG৕ࡿ?1\"䘓您\u001fSኝ⺿溏zៀ뻤B\u0019?윐a䳵᭱䉺膷d:<\/": 3935553551038864272 + } + ] + ]} + ]] + ]] + ]} + } + ] + } + ]]}}, + "᥺3h↛!ꋰy\"攜(ெl䪕oUkc1A㘞ᡲ촾ᣫ<\/䒌E㛝潨i{v?W౾H\\RჅpz蝬R脾;v:碽✘↯삞鷱o㸧瑠jcmK7㶧뾥찲n": true, + "ⶸ?x䊺⬝-䰅≁!e쩆2ꎿ准G踌XXᩯ1߁}0?.헀Z馟;稄\baDꟹ{-寪⚈ꉷ鮸_L7ƽᾚ<\u001bጨA䧆송뇵⨔\\礍뗔d设룱㶉cq{HyぱR㥽吢ſtp": -7985372423148569301, + "緫#콮IB6<\/=5Eh礹\t8럭@饹韠r㰛斣$甝LV췐a갵'请o0g:^": "䔨(.", + "띳℡圤pン௄ĝ倧訜B쁟G䙔\"Sb⓮;$$▏S1J뢙SF|赡g*\"Vu䲌y": "䪈&틐),\\kT鬜1풥;뷴'Zေ䩹@J鞽NぼM?坥eWb6榀ƩZڮ淽⺞삳煳xჿ絯8eⶍ羷V}ჿ쎱䄫R뱃9Z>'\u20f1ⓕ䏜齮" + } + ] + ]]] + }} + } + ] + ]}, + "펮b.h粔폯2npX詫g錰鷇㇒<쐙S値bBi@?镬矉`剔}c2壧ଭfhY깨R()痩⺃a\\⍔?M&ﯟ<劜꺄멊ᄟA\"_=": null + }, + "~潹Rqn榢㆓aR鬨侅?䜑亡V_翅㭔(䓷w劸ၳDp䀅<\/ﰎ鶊m䵱팱긽ꆘ긓准D3掱;o:_ќ)껚콥8곤d矦8nP倥ꃸI": null, + "뾎/Q㣩㫸벯➡㠦◕挮a鶧⋓偼\u00001뱓fm覞n?㛅\"": 2.8515592202045408E17 + }], + ",": -5426918750465854828, + "2櫫@0柡g䢻/gꆑ6演&D稒肩Y?艘/놘p{f투`飷ᒉ챻돎<늛䘍ﴡ줰쫄": false, + "8(鸑嵀⵹ퟡ<9㣎Tߗ┘d슒ل蘯&㠦뮮eࠍk砝g 엻": false, + "d-\u208b?0ﳮ嵙'(J`蔿d^踅⤔榥\\J⵲v7": 6.8002426206715341E17, + "ཎ耰큓ꐕ㱷\u0013y=詽I\"盈xm{0쾽倻䉚ષso#鰑/8㸴짯%ꀄ떸b츟*\\鲷礬ZQ兩?np㋄椂榨kc᡹醅3": false, + "싊j20": false + }]] + ]], + "俛\u0017n緽Tu뫉蜍鼟烬.ꭠIⰓ\"Ἀ᜾uC쎆J@古%ꛍm뻨ᾀ画蛐휃T:錖㑸ዚ9죡$": true + } + ] + ], + "㍵⇘ꦖ辈s}㱮慀밒s`\"㞟j:`i픻Z섫^諎0Ok{켿歁෣胰a2﨤[탳뚬쎼嫭뉮m": 409440660915023105, + "w墄#*ᢄ峠밮jLa`ㆪ꺊漓Lで끎!Agk'ꁛ뢃㯐岬D#㒦": false, + "ଦPGI䕺L몥罭ꃑ궩﮶#⮈ᢓӢ䚬p7웼臧%~S菠␌힀6&t䳙y㪘냏\\*;鉏ᅧ鿵'嗕pa\"oL쇿꬈Cg": "㶽1灸D⟸䴅ᆤ뉎﷛渤csx 䝔цꬃ锚捬?ຽ+x~꘩uI࡞\u0007栲5呚ẓem?袝\")=㥴䨃pac!/揎Y", + "ᷱo\\||뎂몷r篙|#X䦜I#딌媸픕叞RD斳X4t⯩夬=[뭲r=绥jh뷱츝⪘%]⚋܈㖴スH텹m(WO曝劉0~K3c柢Ր㏉着逳~": false, + "煽_qb[첑\\륌wE❽ZtCNﭝ+餌ᕜOꛭ": "{ﳾ쉌&s惧ᭁⵆ3䢫;䨞팑꒪흘褀࢖Q䠿V5뭀䎂澻%받u5텸oA⮥U㎦;B䳌wz䕙$ឿ\\௅婺돵⪾퐆\\`Kyौꋟ._\u0006L챯l뇠Hi䧈偒5", + "艊佁ࣃ롇䱠爬!*;⨣捎慓q靓|儑ᨋL+迥=6㒺딉6弄3辅J-㕎뛄듘SG㆛(\noAzQꝱ䰩X*ぢO퀌%펠낌mo틮a^<\/F&_눊ᾉ㨦ы4\"8H": 2974648459619059400, + "鬙@뎣䫳ၮ끡?){y?5K;TA*k溱䫜J汃ꂯ싔썍\u001dA}룖(<\/^,": false, + "몏@QꋦFꊩᒐ뎶lXl垨4^郣|ꮇ;䝴ᝓ}쵲z珖": null + } + ]]]], + ":_=닧弗D䙋暨鏛. 㱻붘䂍J儒&ZK/녩䪜r囁⽯D喠죥7⹌䪥c\u001a\u2076￞妈朹oLk菮F౟覛쐧㮏7T;}蛙2{9\"崓bB<\/⡷룀;즮鿹)丒툃୤뷠5W⊢嶜(fb뭳갣": "E{响1WM" + }}, + "䘨tjJ驳豨?y輊M*᳑梵瞻઻ofQG瑮e": 2.222802939724948E-19, + "䮴=❑➶T෋w䞜\"垦ꃼUt\u001dx;B$뵣䙶E↌艣ᡥ!᧟;䱀[䔯k쬃`੍8饙른熏'2_'袻tGf蒭J땟as꯳╖&啒zWࡇᒫYSᏬ\u0014ℑ첥鈤|cG~Pᓮ\">\"": "ႆl\f7V儊㦬nHꄬꨧC{쐢~C⮃⛓嶦vꄎ1w鰠嘩뿠魄&\"_qMⵖ釔녮ꝇ 㝚{糍J哋 cv?-jkﻯྌ鹑L舟r", + "龧葆yB✱H盋夔ﶉ?n*0(": "ꧣኆ㢓氥qZZ酒ຜ)鮢樛)X䣆gTSґG텞k.J圬疝롫쯭z L:\\ྤ@w炋塜쿖ᾳy뢀䶃뱝N䥨㚔勇겁#p", + "도畎Q娡\"@S/뼋:䵏!P衅촚fVHQs✜ᐫi㻑殡B䜇%믚k*U#濨낄~": "ꍟዕ쳸ꍈ敋&l妏\u0005憡멗瘌uPgᅪm<\/To쯬锩h뒓k" + } + ] + }], + "墥홞r绚<\/⸹ⰃB}<躅\\Y;๑@䔸>韫䜲뱀X뗩鿥쩗SI%ﴞ㳕䛇?<\/\u00018x\\&侂9鋙a[LR㋭W胕)⡿8㞙0JF,}?허d1cDMᐃ␛鄝ⱕ%X)!XQ": "ⳍꗳ=橇a;3t⦾꼑仈ူaᚯ⯋ꕃAs鴷N⍕_䎃ꙎAz\u0016䯷\\<࿫>8q{}キ?ᣰ}'0ᴕ펓B┦lF#趤厃T?㕊#撹圂䆲" + }, + "܋닐龫論c웑": false, + "ㇿ/q\"6-co髨휝C큦#\u001b4~?3䐹E삇<<": 7.600917488140322E-20, + "䁝E6?㣖ꃁ间t祗*鑠{ḣV(浾h逇큞=W?ૉ?nꇽ8ꅉຉj으쮺@Ꚅ㰤u]Oyr": "v≁᫸_*όAඤԆl)ۓᦇQ}폠z༏q滚", + "ソ᥊/넺I": true + }]] + ] + ] + ] + ]] + }, + "䭑Ik攑\u0002QV烄:芩.麑㟴㘨≕": true, + "坄꿕C쇻풉~崍%碼\\8\"䬦꣙": null, + "欌L圬䅘Y8c(♺2?ON}o椳s宥2䉀eJ%闹r冁O^K諭%凞⺉⡻,掜?$ꥉ?略焕찳㯊艼誜4?\"﯎<゛XፈINT:詓 +": -1.0750456770694562E-19, + "獒àc뜭싼ﺳ뎤K`]p隨LtE": null, + "甙8䵊神EIꩤ鐯ᢀ,ﵮU䝑u疒ử驺䚿≚ഋ梶秓F`覤譐#짾蔀묊4<媍쬦靪_Yzgcࡶ4k紥`kc[Lﮗ簐*I瀑[⾰L殽鑥_mGȠ<\/|囹灠g桰iri": true, + "챓ꖙꟻ좝菇ou,嗠0\\jK핻뜠qwQ?ഩ㼕3Y彦b\u009bJ榶N棨f?됦鏖綃6鳵M[OE봨u햏.Ꮁ癜蟳뽲ꩌ뻾rM豈R嗀羫 uDꎚ%": null + }, + "V傜2<": 7175127699521359521 + }], + "铫aG切<\/\"ী⊆e<^g࢛)D顝nאַ饼\u008c猪繩嵿ﱚCꡬ㻊g엺A엦\u000f暿_f꿤볝㦕桦`蒦䎔j甬%岝rj 糏": "䚢偎눴Au<4箞7礦Iﱔ坠eȧ䪸u䵁p|逹$嗫쨘ꖾ﷐!胠z寓팢^㨔|u8Nሇe텔ꅦ抷]،鹎㳁#༔繁 ", + "낂乕ꃻ볨ϱ-ꇋ㖍fs⿫)zꜦ/K?솞♞ꑌ宭hJ᤭瑥Fu": false, + "쟰ぜ魛G\u0003u?`㾕ℾ㣭5螠烶這趩ꖢ:@咕ꐶx뒘느m䰨b痃렐0鳊喵熬딃$摉_~7*ⱦ녯1錾GKhJ惎秴6'H妈Tᧅ窹㺒疄矤铟wላ": null, + "쯆q4!3錕㲏ⵆ㇛꘷Z瑩뭆\\◪NH\u001d\\㽰U~㯶<\"쑣낞3ᵤ'峉eꢬ;鬹o꣒木X*長PXᘱu\"䠹n惞": null, + "ᅸ祊\"&ꥴCjࢼ﴿?䡉`U效5殼㮞V昽ꏪ#ﺸ\\&t6x꠹盥꣰a[\u001aꪍSpe鎿蠹": -1.1564713893659811E-19 + } + ]] + ] + ] + ], + "羵䥳H,6ⱎ겾|@t\"#햊1|稃 섭)띜=뻔ꡜ???櫎~*ῡ꫌/繣ﻠq": null + } + ]} + ]}, + "츤": false + }}, + "s": 3.7339341963399598E18 + } + ], + "N,I?1+㢓|ࣱ嶃쩥V2\u0012(4EE虪朶$|w颇v步": "~읢~_,Mzr㐫YB溓E淚\"ⅹ䈔ᏺ抙 b,nt5V㐒J檶ꏨ⻔?", + "Q껑ꡡ}$넎qH煔惍/ez^!ẳF댙䝌馻剁8": "梲;yt钰$i冄}AL%a j뜐奷걳뚾d꿽*ሬuDY3?뮟鼯뮟w㍪틱V", + "o{Q/K O胟㍏zUdꀐm&⨺J舕⾏魸訟㌥[T籨櫉唐킝 aṭ뱫촙莛>碶覆⧬짙쭰ׯdAiH໥벤퐥_恸[ 0e:죃TC弼荎뵁DA:w唵ꣁ": null, + "὏樎䵮軧|?౗aWH쩃1 ꅭsu": null + } + ] + }, + "勂\\&m鰈J釮=Ⲽ鳋+䂡郑": null, + "殣b綊倶5㥗惢⳷萢ᑀ䬄镧M^ﱴ3⣢翣n櫻1㨵}ኯ뗙顖Z.Q➷ꮨ뗇\u0004": "ꔙ䁼>n^[GीA䨟AM琢ᒊS쨲w?d㶣젊嘶纝麓+愣a%気ྞSc됓ᔘ:8bM7Xd8㶑臌]Ꙥ0ꐭ쒙䫣挵C薽Dfⵃ떼᷸", + "?紡.셪_෨j\u0013Ox┠$Xᶨ-ᅇo薹-}軫;y毝㪜K㣁?.EV쮱4둽⛻䤜'2盡\u001f60(|e쐰㼎ᦀ㒧-$l@ﻑ坳\u0003䭱响巗WFo5c㧆T턁Y맸♤(": -2.50917882560589088E17 + }} + ], + "侸\\릩.᳠뎠狣살cs项䭩畳H1s瀉븇19?.w骴崖㤊h痠볭㞳㞳䁮Ql怠㦵": "@䟴-=7f", + "鹟1x௢+d ;vi䭴FSDS\u0004hꎹ㚍?⒍⦏ў6u,扩@됷Su)Pag휛TᒗV痩!瞏釀ꖞ蘥&ೞ蘐ꭰꞇᝎ": "ah懱Ժ&\u20f7䵅♎඀䞧鿪굛ౕ湚粎蚵ᯋ幌YOE)५襦㊝Y*^\"R+ඈ咷蝶9ꥂ榨艦멎헦閝돶v좛咊E)K㓷ྭr", + "搆q쮦4綱켙셁.f4<\/g<籽늷?#蚴픘:fF\u00051㹉뀭.ᰖ풎f֦Hv蔎㧤.!䭽=鞽]음H:?\"-4": 8.740133984938656E-20 + }]} + } + ], + "tVKn딩꘥⊾蹓᤹{\u0003lR꼽ᄲQFᅏ傅ﱋ猢⤊ᔁ,E㓒秤nTතv`♛I\u0000]꫔ṞD\"麵c踝杰X&濿또꣹깳౥葂鿎\\aꡨ?": 3900062609292104525 + } + ], + "ਉ샒⊩Lu@S䧰^g": -1.1487677090371648E18, + "⎢k⑊꬗yᏫ7^err糎Dt\u000bJ礯확ㆍ沑サꋽe赔㝢^J\u0004笲㿋idra剰-᪉C錇/Ĝ䂾ညS지?~콮gR敉⬹'䧭": 1901472137232418266, + "灗k䶥:?촽贍쓉꓈㒸g獘[뵎\\胕?\u0014_榙p.j稶,$`糉妋0>Fᡰly㘽$?": "]ꙛO赎&#㠃돱剳\"<◆>0誉齐_|z|裵씪>ᐌ㼍\"Z[琕}O?G뚇諦cs⠜撺5cu痑U圲\u001c?鴴計l춥/╓哼䄗茏ꮅ뫈댽A돌롖뤫V窗讬sHd&\nOi;_u" + } + ], + "Uﺗ\\Y\\梷䄬~\u0002": null, + "k\"Y磓ᗔ휎@U冈<\/w컑)[": false, + "曏J蝷⌻덦\u001f㙳s꥓⍟邫P늮쥄c∬ྡྷ舆렮칤Z趣5콡넛A쳨\\뀙骫(棻.*&輛LiIfi{@EA婳KᬰTXT": -4.3088230431977587E17 + }]} + ] + ], + "곃㲧<\/dఓꂟs其ࡧ&N葶=?c㠤Ჴ'횠숄臼#\u001a~": false + } + ] + ]}] + }] + }} + ], + "2f`⽰E쵟>J笂裭!〛觬囀ۺ쟰#桊l鹛ⲋ|RA_Vx፭gE됓h﵀mfỐ|?juTU档[d⢼⺻p濚7E峿": 5613688852456817133 + }, + "濘끶g忮7㏵殬W팕Q曁 뫰)惃廊5%-蹚zYZ樭ﴷQ锘쯤崫gg": true, + "絥ᇑ⦏쒓븣爚H.㗊߄o蘵貆ꂚ(쎔O᥉ﮓ]姨Wꁓ!RMA|o퉢THx轮7M껁U즨'i뾘舯o": "跥f꜃?" + }} + ], + "鷰鹮K-9k;ﰰ?_ݦѷ-ꅣ䩨Zꥱ\"mꠟ屎/콑Y╘2&鸞脇㏢ꀇ࠺ⰼ拾喭틮L꽩bt俸墶 [l/웄\"꾦\u20d3iও-&+\u000fQ+໱뵞": -1.296494662286671E-19 + }, + "HX੹/⨇୕붷Uﮘ旧\\쾜͔3l鄈磣糂̖䟎Eᐳw橖b῀_딕hu葰窳闹вU颵|染H죶.fP䗮:j䫢\\b뎖i燕ꜚG⮠W-≚뉗l趕": "ଊ칭Oa᡺$IV㷧L\u0019脴셀붿餲햪$迳向쐯켂PqfT\" ?I屉鴼쿕@硙z^鏕㊵M}㚛T젣쓌-W⩐-g%⺵<뮱~빅╴瑿浂脬\u0005왦燲4Ⴭb|D堧 <\/oEQh", + "䘶#㥘੐캔f巋ἡAJ䢚쭈ࣨ뫒*mᇊK,ࣺAꑱ\u000bR<\/A\"1a6鵌㯀bh곿w(\"$ꘁ*rಐ趣.d࿩k/抶면䒎9W⊃9": "漩b挋Sw藎\u0000", + "畀e㨼mK꙼HglKb,\"'䤜": null + }]}] + ] + ] + }] + ]} + ] + ]} + ], + "歙>駿ꣂ숰Q`J΋方樛(d鱾뼣(뫖턭\u20f9lচ9歌8o]8윶l얶?镖G摄탗6폋폵+g:䱫홊<멀뀿/س|ꭺs걐跶稚W々c㫣⎖": "㣮蔊깚Cꓔ舊|XRf遻㆚︆'쾉췝\\&言", + "殭\"cށɨꝙ䞘:嬮e潽Y펪㳅/\"O@ࠗ겴]췖YǞ(t>R\"N?梳LD恭=n氯T豰2R諸#N}*灧4}㶊G䍣b얚": null, + "襞<\/啧 B|싞W瓇)6簭鼡艆lN쩝`|펭佡\\間邝[z릶&쭟愱ꅅ\\T᰽1鯯偐栈4̸s윜R7⒝/똽?치X": "⏊躖Cﱰ2Qẫ脐&இ?%냝悊", + ",鰧偵셣싹xᎹ힨᯳EṬH㹖9": -4604276727380542356 + } + } + ]]]], + "웺㚑xs}q䭵䪠馯8?LB犯zK'os䚛HZ\"L?셎s^㿧㴘Cv2": null + }] + ] + ] + ], + "Kd2Kv+|z": 7367845130646124107, + "ᦂⶨ?ᝢ 祂些ഷ牢㋇操\"腭䙾㖪\\(y4cE뽺ㆷ쫺ᔖ%zfۻ$ў1柦,㶢9r漢": -3.133230960444846E-20, + "琘M焀q%㢟f鸯O⣏蓑맕鯊$O噷|)z褫^㢦⠮ꚯ꫞`毕1qꢚ{ĭ䎀বώT\"뱘3G൴?^^of": null + } + ], + "a8V᯺?:ﺃ/8ꉿBq|9啓댚;*i2": null, + "cpT瀇H珰Ừpೃi鎪Rr␣숬-鹸ҩ䠚z脚цGoN8入y%趌I┽2ឪЀiJNcN)槣/▟6S숆牟\"箑X僛G殱娇葱T%杻:J諹昰qV쨰": 8331037591040855245 + }], + "G5ᩜ䄗巢껳": true + } + }, + "Ồ巢ゕ@_譙A`碫鄐㡥砄㠓(^K": "?܃B혢▦@犑ὺD~T⧁|醁;o=J牌9냚⢽㨘{4觍蚔9#$∺\u0016p囅\\3Xk阖⪚\"UzA穕롬✎➁㭒춺C㣌ဉ\"2瓑员ᅽꝶ뫍}꽚ꞇ鶂舟彺]ꍽJC蝧銉", + "␆Ě膝\"b-퉐ACR言J謈53~V튥x䜢?ꃽɄY뮩ꚜ": "K/↾e萃}]Bs⾿q룅鷦-膋?m+死^魊镲6", + "粡霦c枋AHퟁo礼Ke?qWcA趸㡔ꂏ?\u000e춂8iতᦜ婪\u0015㢼nﵿꍻ!ᐴ関\u001d5j㨻gfῩUK5Ju丝tかTI'?㓏t>⼟o a>i}ᰗ;뤕ܝ": false, + "ꄮ匴껢ꂰ涽+䜨B蛹H䛓-k蕞fu7kL谖,'涃V~챳逋穞cT\"vQ쓕ObaCRQ㓡Ⲯ?轭⫦輢墳?vA餽=h䮇킵n폲퉅喙?\"'1疬V嬗Qd灗'Lự": "6v!s믁㭟㣯獃!磸餠ቂh0C뿯봗F鷭gꖶ~コkK<ᦈTt\\跓w㭣횋钘ᆹ듡䑚W䟾X'ꅔ4FL勉Vܴ邨y)2'〚쭉⽵-鞣E,Q.?块", + "?(˧쩯@崟吋歄K": null + }, + "Gc럃녧>?2DYI鴿\\륨)澔0ᔬlx'觔7젘⤡縷螩%Sv׫묈/]↱&S h\u0006歋ᑛxi̘}ひY蔯_醨鯘煑橾8?䵎쨋z儬ꁏ*@츾:": null + } + } + } + ] + ] + ]} + }, + "HO츧G": 3.694949578823609E17, + "QC\u0012(翻曇Tf㷟bGBJ옉53\\嚇ᛎD/\u001b夾၉4\"핀@祎)쫆yD\"i먎Vn㿿V1W᨝䶀": -6150931500380982286, + "Z㓮P翸鍱鉼K䋞꘺튿⭁Y": -7704503411315138850, + "]모开ꬖP븣c霤<[3aΠ\"黁䖖䰑뮋ꤦ秽∼㑷冹T+YUt\"싳F↭䖏&鋌": -2.7231911483181824E18, + "tꎖ": -4.9517948741799555E-19, + "䋘즊.⬅IꬃۣQ챢ꄑ黐|f?C⾺|兕읯sC鬸섾整腨솷V": "旆柩l쪦sᖸMy㦅울썉瘗㎜檵9ꍂ駓ૉᚿ/u3씅徐拉[Z䞸ࡗ1ꆱ&Q풘?ǂ8\u0011BCDY2볨;鸏": null, + "幫 n煥s쁇펇 왊-$C\"衝:\u0014㣯舼.3뙗Yl⋇\"K迎멎[꽵s}9鉳UK8쐥\"掄㹖h㙈!얄સ?Ꜳ봺R伕UTD媚I䜘W鏨蔮": -4.150842714188901E-17, + "ﺯ^㄄\b죵@fྉkf颡팋Ꞧ{/Pm0V둳⻿/落韒ꊔᚬ@5螺G\\咸a谆⊪ቧ慷绖?财(鷇u錝F=r၍橢ឳn:^iᴵtD볠覅N赴": null + }] + }] + } + ] + ]} + ]}, + "謯?w厓奰T李헗聝ឍ貖o⪇弒L!캶$ᆅ": -4299324168507841322, + "뺊奉_垐浸延몏孄Z舰2i$q붿좾껇d▵餏\"v暜Ҭ섁m￴g>": -1.60911932510533427E18 + } + ] + } + ] + ]], + "퉝꺔㠦楶Pꅱ": 7517896876489142899, + "": false + } + ]}, + "是u&I狻餼|谖j\"7c됮sסּ-踳鉷`䣷쉄_A艣鳞凃*m⯾☦椿q㎭N溔铉tlㆈ^": 1.93547720203604352E18, + "kⲨ\\%vr#\u000bⒺY\\t<\/3﬌R訤='﹠8蝤Ꞵ렴曔r": false + } + ]}, + "阨{c?C\u001d~K?鎌Ԭ8烫#뙣P초遗t㭱E­돒䆺}甗[R*1!\\~h㕅᰺@<9JꏏષI䳖栭6綘걹ᅩM\"▯是∔v鬽顭⋊譬": "운ﶁK敂(欖C취پ℄爦賾" + } + }} + }], + "鷨赼鸙+\\䭣t圙ڹx᜾ČN<\/踘\"S_맶a鷺漇T彚⎲i㈥LT-xA캔$\u001cUH=a0츺l릦": "溣㣂0濕=鉵氬駘>Pꌢpb솇쬤h힊줎獪㪬CrQ矠a&脍꼬爼M茴/΅\u0017弝轼y#Ꞡc6둴=?R崏뷠麖w?" + }, + "閕ᘜ]CT)䵞l9z'xZF{:ؐI/躅匽졁:䟇AGF૸\u001cퟗ9)駬慟ꡒꆒRS״툋A<>\u0010\"ꂔ炃7g덚E৏bꅰ輤]o㱏_뷕ܘ暂\"u": "芢+U^+㢩^鱆8*1鈶鮀\u0002뺰9⬳ꪮlL䃣괟,G8\u20a8DF㉪錖0ㄤ瓶8Nଷd?眡GLc陓\\_죌V쁰ल二?c띦捱 \u0019JC\u0011b⤉zẒT볕\"绣蘨뚋cꡉkI\u001e鳴", + "ꃣI'{6u^㡃#཰Kq4逹y൒䧠䵮!㱙/n??{L풓ZET㙠퍿X2᩟綳跠葿㚙w཮x캽扳B唕S|尾}촕%N?o䪨": null, + "ⰴFjෟ셈[\u0018辷px?椯\\1<ﲻ栘ᣁ봢憠뉴p": -5263694954586507640 + } + ] + ]] + ]} + ]}] + ] + ], + "?#癘82禩鋆ꊝty?&": -1.9419029518535086E-19 + } + ] + ] + ]} + ] + ] + ], + "훊榲.|῕戄&.㚏Zꛦ2\"䢥ሆ⤢fV_摕婔?≍Fji冀탆꜕i㏬_ẑKᅢ꫄蔻XWc|饡Siẘ^㲦?羡2ぴ1縁ᙅ?쐉Ou": false + }]] + ]}}}, + "慂뗄卓蓔ᐓ匐嚖/颹蘯/翻ㆼL?뇊,텵<\\獷ごCボ": null + }, + "p溉ᑟi짣z:䒤棇r^٫%G9缑r砌롧.물农g?0׼ሩ4ƸO㣥㯄쩞ጩ": null, + "껎繥YxK\"F젷쨹뤤1wq轫o?鱑뜀瘊?뎃h灑\\ꛣ}K峐^ኖ⤐林ꉓhy": null + } + ], + "᱀n肓ㄛ\"堻2>m殮'1橌%Ꞵ군=Ӳ鯨9耛<\/n據0u彘8㬇៩f᏿诙]嚊": "䋯쪦S럶匏ㅛ#)O`ሀX_鐪渲⛀㨻宅闩➈ꢙஶDR⪍" + }, + "tA썓龇 ⋥bj왎录r땽✒롰;羋^\\?툳*┎?썀ma䵳넅U䳆૘〹䆀LQ0\b疀U~u$M}(鵸g⳾i抦뛹?䤈땚검.鹆?ꩡtⶥGĒ;!ቹHS峻B츪켏f5≺": 2366175040075384032, + "전pJjleb]ួ": -7.5418493141528422E18, + "n.鎖ጲ\n?,$䪘": true + }, + "欈Ar㉣螵᪚茩?O)": null + }, + "쫸M#x}D秱欐K=侫们丐.KꕾxẠ\u001e㿯䣛F܍캗qq8꟞ṢFD훎⵳簕꭛^鳜\u205c٫~⑟~冫ऊ2쫰<\/戲윱o<\"": true + }, + "㷝聥/T뱂\u0010锕|内䞇x侁≦㭖:M?iM᣿IJe煜dG࣯尃⚩gPt*辂.{磼럾䝪@a\\袛?}ᓺB珼": true + } + } + ]]}]}}, + "tn\"6ꫤ샾䄄;銞^%VBPwu묪`Y僑N.↺Ws?3C⤻9唩S䠮ᐴm;sᇷ냞඘B/;툥B?lB∤)G+O9m裢0kC햪䪤": -4.5941249382502277E18, + "ᚔt'\\愫?鵀@\\びꂕP큠<<]煹G-b!S?\nꖽ鼫,ݛ&頺y踦?E揆릱H}햧캡b@手.p탻>췽㣬ꒅ`qe佭P>ᓂ&?u}毚ᜉ蟶頳졪ᎏzl2wO": -2.53561440423275936E17 + }]} + } + ] + ]], + "潈촒⿂叡": 5495738871964062986 + } + ]] + } + ] + ]} + ]] + ]] + ]} + ] + ]}, + "ႁq킍蓅R`謈蟐ᦏ儂槐僻ﹶ9婌櫞釈~\"%匹躾ɢ뤥>࢟瀴愅?殕节/냔O✬H鲽엢?ᮈੁ⋧d␽㫐zCe*": 2.15062231586689536E17, + "㶵Ui曚珰鋪ᾼ臧P{䍏䷪쨑̟A뼿T渠誈䏚D1!잶<\/㡍7?)2l≣穷᛾稝{:;㡹nemיּ訊`G": null, + "䀕\"飕辭p圁f#뫆䶷뛮;⛴ᩍ3灚덏ᰝ쎓⦷詵%᜖Մfs⇫(\u001e~P|ﭗCⲾផv湟W첋(텪બT<บSꏉ੗⋲X婵i ӵ⇮?L䬇|ꈏ?졸": 1.548341247351782E-19 + } + ] + }, + "t;:N\u0015q鐦Rt缆{ꮐC?஛㷱敪\\+鲊㉫㓪몗릙竏(氵kYS": "XᰂT?൮ô", + "碕飦幑|+ 㚦鏶`镥ꁩ B<\/加륙": -4314053432419755959, + "秌孳(p!G?V傫%8ሽ8w;5鲗㦙LI檸\u2098": "zG N볞䆭鎍흘\\ONK3횙<\/樚立圌Q튅k쩎Ff쁋aׂJK銆ઘ즐狩6༥✙䩜篥CzP(聻駇HHퟲ讃%,ά{렍p而刲vy䦅ክ^톺M楒鍢㹳]Mdg2>䤉洞", + "踛M젧>忔芿㌜Zk": 2215369545966507819, + "씐A`$槭頰퍻^U覒\bG毲aᣴU;8!팲f꜇E⸃_卵{嫏羃X쀳C7뗮m(嚼u N܁谟D劯9]#": true, + "ﻩ!뵸-筚P᭛}ἰ履lPh?౮ⶹꆛ穉뎃g萑㑓溢CX뾇G㖬A錟]RKaꄘ]Yo+@䘁's섎襠$^홰}F": null + }, + "粘ꪒ4HXᕘ蹵.$區\r\u001d묁77pPc^y笲Q<\/ꖶ 訍䃍ᨕG?*": 1.73773035935040224E17 + }, + "婅拳?bkU;#D矠❴vVN쩆t㜷A풃갮娪a%鮏絪3dAv룒#tm쑬⌛qYwc4|L8KZ;xU⓭㳔밆拓EZ7襨eD|隰ऌ䧼u9Ԣ+]贴P荿": 2.9628516456987075E18 + }]}}] + ]} + }} + ]}] + ], + "|g翉F*湹̶\u0005⏐1脉̀eI쩓ᖂ㫱0碞l䴨ꑅ㵽7AtἈ턧yq䳥塑:z:遀ᄐX눔擉)`N3昛oQ셖y-ڨ⾶恢ꈵq^<\/": null, + "菹\\랓G^璬x৴뭸ゆUS겧﮷Bꮤ ┉銜᯻0%N7}~f洋坄Xꔼ<\/4妟Vꄟ9:౟곡t킅冩䧉笭裟炂4봋ⱳ叺怊t+怯涗\"0㖈Hq": false, + "졬믟'ﺇফ圪쓬멤m邸QLব䗁愍4jvs翙 ྍ꧀艳H-|": null, + "컮襱⣱뗠 R毪/鹙꾀%헳8&": -5770986448525107020 + } + ], + "B䔚bꐻ뙏姓展槰T-똌鷺tc灿᫽^㓟䏀o3o$꘭趙萬I顩)뇭Ἑ䓝\f@{ᣨ`x3蔛": null + } + ] + ] + }], + "⦖扚vWꃱ꥙㾠壢輓{-⎳鹷贏璿䜑bG倛⋐磎c皇皩7a~ﳫU╣Q࠭ꎉS摅姽OW.홌ೞ.": null, + "蚪eVlH献r}ᮏ믠ﰩꔄ@瑄ⲱ": null, + "퀭$JWoꩢg역쁍䖔㑺h&ୢtXX愰㱇?㾫I_6 OaB瑈q裿": null, + "꽦ﲼLyr纛Zdu珍B絟쬴糔?㕂짹䏵e": "ḱ\u2009cX9멀i䶛簆㳀k" + } + ]]]], + "(_ꏮg່澮?ᩑyM<艷\u001aꪽ\\庼뙭Z맷㰩Vm\\lY筺]3㋲2㌩㄀Eਟ䝵⨄쐨ᔟgङHn鐖⤇놋瓇Q탚單oY\"♆臾jHᶈ征ቄ??uㇰA?#1侓": null + }, + "觓^~ሢ&iI띆g륎ḱ캀.ᓡꀮ胙鈉": 1.0664523593012836E-19, + "y詭Gbᔶऽs댁U:杜⤎ϲ쁗⮼D醄诿q뙰I#즧v蔎xHᵿt᡽[**?崮耖p缫쿃L菝,봬ꤦC쯵#=X1瞻@OZc鱗CQTx": null + } + ] + }}], + "剘紁\u0004\\Xn⊠6,တױ;嵣崇}讃iႽ)d1\\䔓": null + }, + "脨z\"{X,1u찜<'k&@?1}Yn$\u0015Rd輲ーa쮂굄+B$l": true, + "諳>*쭮괐䵟Ґ+<箁}빀䅱⡔檏臒hIH脟ꩪC핝ଗP좕\"0i<\/C褻D۞恗+^5?'ꂱ䚫^7}㡠cq6\\쨪ꔞꥢ?纖䫀氮蒫侲빦敶q{A煲G": -6880961710038544266 + }}] + }, + "5s⨲JvಽῶꭂᄢI.a৊": null, + "?1q꽏쿻ꛋDR%U娝>DgN乭G": -1.2105047302732358E-19 + } + ] + ]}, + "qZz`撋뙹둣j碇쁏\\ꆥ\u0018@藴疰Wz)O{F䶛l᷂绘訥$]뮍夻䢋䩇萿獰樧猵⣭j萶q)$꬚⵷0馢W:Ⱍ!Qoe": -1666634370862219540, + "t": "=wp|~碎Q鬳Ӎ\\l-<\/^ﳊhn퐖}䍔t碵ḛ혷?靻䊗", + "邙쇡㯇%#=,E4勃驆V繚q[Y댻XV㡸[逹ᰏ葢B@u=JS5?bLRn얮㍉⏅ﰳ?a6[&큟!藈": 1.2722786745736667E-19 + }, + "X블땨4{ph鵋ꉯ웸 5p簂䦭s_E徔濧d稝~No穔噕뽲)뉈c5M윅>⚋[岦䲟懷恁?鎐꓆ฬ爋獠䜔s{\u001bm鐚儸煛%bﯿXT>ꗘ@8G": 1157841540507770724, + "媤娪Q杸\u0011SAyᡈ쿯": true, + "灚^ಸ%걁<\/蛯?\"祴坓\\\\'흍": -3.4614808555942579E18, + "釴U:O湛㴑䀣렑縓\ta)(j:숾却䗌gCiB뽬Oyuq輥厁/7)?今hY︺Q": null + } + ] + ]]]}] + ], + "I笔趠Ph!<ཛྷ㸞诘X$畉F\u0005笷菟.Esr릙!W☆䲖뗷莾뒭U\"䀸犜Uo3Gꯌx4r蔇᡹㧪쨢準<䂀%ࡡꟼ瑍8炝Xs0䀝销?fi쥱ꆝલBB": -8571484181158525797, + "L⦁o#J|\"⽩-㱢d㌛8d\\㶤傩儻E[Y熯)r噤὘勇 }": "e(濨쓌K䧚僒㘍蠤Vᛸ\"络QJL2,嬓왍伢㋒䴿考澰@(㏾`kX$끑эE斡,蜍&~y", + "vj.|统圪ᵮPL?2oŶ`밧\"勃+0ue%⿥绬췈체$6:qa렐Q;~晘3㙘鹑": true, + "ශؙ4獄⶿c︋i⚅:ん閝Ⳙ苆籦kw{䙞셕pC췃ꍬ␜꟯ꚓ酄b힝hwk꭭M鬋8B耳쑘WQ\\偙ac'唀x᪌\u2048*h짎#ፇ鮠뾏ឿ뀌": false, + "⎀jꄒ牺3Ⓝ컴~?親ꕽぼܓ喏瘘!@<튋㐌꿱⩦{a?Yv%⪧笯Uܱ栅E搚i뚬:ꄃx7䙳ꦋ&䓹vq☶I䁘ᾘ涜\\썉뺌Lr%Bc㍜3?ꝭ砿裞]": null, + "⭤뙓z(㡂%亳K䌽꫿AԾ岺㦦㼴輞낚Vꦴw냟鬓㹈뽈+o3譻K1잞": 2091209026076965894, + "ㇲ\t⋇轑ꠤ룫X긒\"zoY읇희wj梐쐑l侸`e%s": -9.9240075473576563E17, + "啸ꮑ㉰!ᚓ}銏": -4.0694813896301194E18, + ">]囋੽EK뇜>_ꀣ緳碖{쐐裔[<ನ\"䇅\"5L?#xTwv#罐\u0005래t应\\N?빗;": "v쮽瞭p뭃" + } + ]], + "斴槾?Z翁\"~慍弞ﻆ=꜡o5鐋dw\"?K蠡i샾ogDﲰ_C*⬟iㇷ4nય蟏[㟉U꽌娛苸 ঢ়操贻洞펻)쿗૊許X⨪VY츚Z䍾㶭~튃ᵦ<\/E臭tve猑x嚢": null, + "锡⛩<\/칥ꈙᬙ蝀&Ꚑ籬■865?_>L詏쿨䈌浿弥爫̫lj&zx<\/C쉾?覯n?": null, + "꾳鑤/꼩d=ᘈn挫ᑩ䰬ZC": "3錢爋6Ƹ䴗v⪿Wr益G韠[\u0010屗9쁡钁u?殢c䳀蓃樄욂NAq赟c튒瘁렶Aૡɚ捍" + } + ] + ] + ]} + ] + ] + }]]]}} + ]}], + "Ej䗳U<\/Q=灒샎䞦,堰頠@褙g_\u0003ꤾfⶽ?퇋!łB〙ד3CC䌴鈌U:뭔咎(Qો臃䡬荋BO7㢝䟸\"Yb": 2.36010731779814E-20, + "逸'0岔j\u000e눘먷翌C츊秦=ꭣ棭ှ;鳸=麱$XP⩉駚橄A\\좱⛌jqv䰞3Ь踌v㳆¹gT┌gvLB賖烡m?@E঳i": null + }, + "曺v찘ׁ?&绫O័": 9107241066550187880 + } + ] + ], + "(e屄\u0019昜훕琖b蓘ᬄ0/۲묇Z蘮ဏ⨏蛘胯뢃@㘉8ሪWᨮ⦬ᅳ䅴HI၇쨳z囕陻엣1赳o": true, + ",b刈Z,ၠ晐T솝ŕB⩆ou'퐼≃绗雗d譊": null, + "a唥KB\"ﳝ肕$u\n^⅄P䟼냉䞸⩪u윗瀱ꔨ#yşs꒬=1|ﲤ爢`t౐튼쳫_Az(Ṋ擬㦷좕耈6": 2099309172767331582, + "?㴸U<\/䢔ꯡ阽扆㐤q鐋?f㔫wM嬙-;UV죫嚔픞G&\"Cᗍ䪏풊Q": "VM7疹+陕枡툩窲}翡䖶8欞čsT뮐}璤:jﺋ鎴}HfA൝⧻Zd#Qu茅J髒皣Y-︴[?-~쉜v딏璮㹚䅊﩯<-#\u000e걀h\u0004u抱﵊㼃U<㱷⊱IC進" + }, + "숌dee節鏽邺p넱蹓+e罕U": true + } + ], + "b⧴룏??ᔠ3ぱ>%郿劃翐ꏬꠛW瞳᫏누躨狀ໄy੽\"ីuS=㨞馸k乆E": "トz݈^9R䬑<ﮛGRꨳ\u000fTT泠纷꽀MRᴱ纊:㠭볮?%N56%鈕1䗍䜁a䲗j陇=뿻偂衋࿘ᓸ?ᕵZ+<\/}H耢b䀁z^f$&㝒LkꢳI脚뙛u": 5.694374481577558E-20 + }] + } + ]], + "obj": {"key": "wrong value"}, + "퓲꽪m{㶩/뇿#⼢&᭙硞㪔E嚉c樱㬇1a綑᝖DḾ䝩": null + }, + "key": "6.908319653520691E8", + "z": { + "6U閆崬밺뀫颒myj츥휘:$薈mY햚#rz飏+玭V㭢뾿愴YꖚX亥ᮉ푊\u0006垡㐭룝\"厓ᔧḅ^Sqpv媫\"⤽걒\"˽Ἆ?ꇆ䬔未tv{DV鯀Tἆl凸g\\㈭ĭ즿UH㽤": null, + "b茤z\\.N": [[ + "ZL:ᅣዎ*Y|猫劁櫕荾Oj为1糕쪥泏S룂w࡛Ᏺ⸥蚙)", + { + "\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\"": true, + "e浱up蔽Cr෠JK軵xCʨ<뜡癙Y獩ケ齈X/螗唻?<蘡+뷄㩤쳖3偑犾&\\첊xz坍崦ݻ鍴\"嵥B3㰃詤豺嚼aqJ⑆∥韼@\u000b㢊\u0015L臯.샥": false, + "l?Ǩ喳e6㔡$M꼄I,(3᝝縢,䊀疅뉲B㴔傳䂴\u0088㮰钘ꜵ!ᅛ韽>": -5514085325291784739, + "o㮚?\"춛㵉<\/﬊ࠃ䃪䝣wp6ἀ䱄[s*S嬈貒pᛥ㰉'돀": [{ + "(QP윤懊FI<ꃣ『䕷[\"珒嶮?%Ḭ壍಻䇟0荤!藲끹bd浶tl\u2049#쯀@僞": {"i妾8홫": { + ",M맃䞛K5nAㆴVN㒊햬$n꩑&ꎝ椞阫?/ṏ세뉪1x쥼㻤㪙`\"$쟒薟B煌܀쨝ଢ଼2掳7㙟鴙X婢\u0002": "Vዉ菈᧷⦌kﮞఈnz*﷜FM\"荭7ꍀ-VR<\/';䁙E9$䩉\f @s?퍪o3^衴cඎ䧪aK鼟q䆨c{䳠5mᒲՙ蘹ᮩ": { + "F㲷JGo⯍P덵x뒳p䘧☔\"+ꨲ吿JfR㔹)4n紬G练Q፞!C|": true, + "p^㫮솎oc.೚A㤠??r\u000f)⾽⌲們M2.䴘䩳:⫭胃\\፾@Fᭌ\\K": false, + "蟌Tk愙潦伩": { + "a<\/@ᾛ慂侇瘎": -7271305752851720826, + "艓藬/>၄ṯ,XW~㲆w": {"E痧郶)㜓ha朗!N赻瞉駠uC\u20ad辠x퓮⣫P1ࠫLMMX'M刼唳됤": null, + "P쓫晥%k覛ዩIUᇸ滨:噐혲lMR5䋈V梗>%幽u頖\\)쟟": null, + "eg+昉~矠䧞难\b?gQ쭷筝\\eꮠNl{ಢ哭|]Mn銌╥zꖘzⱷ⭤ᮜ^": [ + -1.30142114406914976E17, + -1.7555215491128452E-19, + null, + "渾㨝ߏ牄귛r?돌?w[⚞ӻ~廩輫㼧/", + -4.5737191805302129E18, + null, + "xy࿑M[oc셒竓Ⓔx?뜓y䊦>-D켍(&&?XKkc꩖ﺸᏋ뵞K伕6ী)딀P朁yW揙?훻魢傎EG碸9類៌g踲C⟌aEX舲:z꒸许", + 3808159498143417627, + null, + {"m試\u20df1{G8&뚈h홯J<\/": { + "3ஸ厠zs#1K7:rᥞoꅔꯧ&띇鵼鞫6跜#赿5l'8{7㕳(b/j\"厢aq籀ꏚ\u0015厼稥": [ + -2226135764510113982, + true, + null, + { + "h%'맞S싅Hs&dl슾W0j鿏MםD놯L~S-㇡R쭬%": null, + "⟓咔謡칲\u0000孺ꛭx旑檉㶆?": null, + "恇I転;￸B2Y`z\\獓w,놏濐撐埵䂄)!䶢D=ഭ㴟jyY": { + "$ࡘt厛毣ൢI芁<겿骫⫦6tr惺a": [ + 6.385779736989334E-20, + false, + true, + true, + [ + -6.891946211462334E-19, + null, + { + "]-\\Ꟑ1/薓❧Ὂ\\l牑\u0007A郃)阜ᇒᓌ-塯`W峬G}SDb㬨Q臉⮻빌O鞟톴첂B㺱<ƈmu챑J㴹㷳픷Oㆩs": { + "\"◉B\"pᶉt骔J꩸ᄇᛐi╰栛K쉷㉯鐩!㈐n칍䟅難>盥y铿e୔蒏M貹ヅ8嘋퀯䉶ጥ㏢殊뻳\"絧╿ꉑ䠥?∃蓊{}㣣Gk긔H1哵峱": false, + "6.瀫cN䇮F㧺?\\椯=ڈT䘆4␘8qv": -3.5687501019676885E-19, + "Q?yऴr혴{஀䳘p惭f1ﹸ䅷䕋贲<ྃᄊ繲hq\\b|#QSTs1c-7(䵢\u2069匏絘ꯉ:l毴汞t戀oෟᵶ뮱፣-醇Jx䙬䐁햢0࣫ᡁgrㄛ": "\u0011_xM/蘇Chv;dhA5.嗀绱V爤ﰦi뵲M", + "⏑[\"ugoy^儣횎~U\\섯겜論l2jw஌yD腅̂\u0019": true, + "ⵯɇ䐲᫿࢚!㯢l샅笶戮1꣖0Xe": null, + "劅f넀識b宁焊E찓橵G!ʱ獓뭔雩괛": [{"p⹣켙[q>燣䍃㞽ᩲx:쓤삘7玑퇼0<\/q璂ᑁ[Z\\3䅵䧳\u0011㤧|妱緒C['췓Yꞟ3Z鳱雼P錻BU씧U`ᢶg蓱>.1ӧ譫'L_5V䏵Ц": [ + false, + false, + {"22䂍盥N霂얢躰e9⑩_뵜斌n@B}$괻Yᐱ@䧋V\"☒-諯cV돯ʠ": true, + "Ű螧ᔼ檍鍎땒딜qꄃH뜣<獧ूCY吓⸏>XQ㵡趌o끬k픀빯a(ܵ甏끆୯/6Nᪧ}搚ᆚ짌P牰泱鈷^d꣟#L삀\"㕹襻;k㸊\\f+": true, + "쎣\",|⫝̸阊x庿k잣v庅$鈏괎炔k쬪O_": [ + "잩AzZGz3v愠ꉈⵎ?㊱}S尳௏p\r2>췝IP䘈M)w|\u000eE", + -9222726055990423201, + null, + [ + false, + {"´킮'뮤쯽Wx讐V,6ᩪ1紲aႈ\u205czD": [ + -930994432421097536, + 3157232031581030121, + "l貚PY䃛5@䭄귻m㎮琸f": 1.0318894506812084E-19, + "࢜⩢Ш䧔1肽씮+༎ᣰ闺馺窃䕨8Mƶq腽xc(៯夐J5굄䕁Qj_훨/~価.䢵慯틠퇱豠㼇Qﵘ$DuSp(8Uญ<\/ಟ룴𥳐ݩ$": 8350772684161555590, + "ㆎQ䄾\u001bpᩭ${[諟^^骴᤮b^ㅥI┧T㉇⾞\"绦r䰂f矩'-7䡭桥Dz兔V9谶居㺍ᔊ䩯덲.\u001eL0ὅㅷ釣": [{ + "<쯬J卷^숞u࠯䌗艞R9닪g㐾볎a䂈歖意:%鐔|ﵤ|y}>;2,覂⶚啵tb*仛8乒㓶B࿠㯉戩oX 貘5V嗆렽낁߼4h䧛ꍺM空\\b꿋貼": 8478577078537189402, + "VD*|吝z~h譺aᯒ": { + "YI췢K<\/濳xNne玗rJo쾘3핰鴊\"↱AR:ࢷ\"9?\"臁說)?誚ꊏe)_D翾W?&F6J@뺾ꍰNZ醊Z쾈വH嶿?炫㷱鬰M겈᭨b,⻁鈵P䕡䀠८ⱄ홎鄣": { + "@?k2鶖㋮\"Oರ K㨇廪儲\u0017䍾J?);\b*묀㗠섳햭1MC V": null, + "UIICP!BUA`ᢈ㋸~袩㗪⾒=fB﮴l1ꡛ죘R辂여ҳ7쮡<䩲`熕8頁": 4481809488267626463, + "Y?+8먙ᚔ鋳蜩럶1㥔y璜౩`": [ + null, + 1.2850335807501874E-19, + "~V2", + 2035406654801997866, + { + "<숻1>\"": -8062468865199390827, + "M㿣E]}qwG莎Gn᝶(ꔙ\\D⬲iꇲs寢t駇S뀡ꢜ": false, + "pꝤ㎏9W%>M;-U璏f(^j1?&RB隧 忓b똊E": "#G?C8.躬ꥯ'?냪#< 渟&헿란zpo왓Kj}鷧XﻘMツb䕖;㪻", + "vE풤幉xz뱕쫥Ug㦲aH} ᣟp:鬼YᰟH3镔ᴚ斦\\鏑r*2橱G⼔F/.j": true, + "RK좬뎂a홠f*f㱉ᮍ⦋潙㨋Gu곌SGI3I뿐\\F',)t`荁蘯囯ﮉ裲뇟쥼_ገ驪▵撏ᕤV": 1.52738225997956557E18, + "^k굲䪿꠹B逤%F㱢漥O披M㽯镞竇霒i꼂焅륓\u00059=皫之눃\u2047娤閍銤唫ၕb<\/w踲䔼u솆맚,䝒ᝳ'/it": "B餹饴is権ꖪ怯ꦂẉဎt\"!凢谵⧿0\\<=(uL䷍刨쑪>俆揓Cy襸Q힆䆭涷<\/ᐱ0ɧ䗾䚹\\ኜ?ꄢᇘ`䴢{囇}᠈䴥X4퓪檄]ꥷ/3謒ሴn+g騍X", + "GgG꽬[(嫓몍6\u0004궍宩㙻/>\u0011^辍dT腪hxǑ%ꊇk,8(W⧂結P鬜O": [{ + "M㴾c>\\ᓲ\u0019V{>ꤩ혙넪㭪躂TS-痴໸闓⍵/徯O.M㏥ʷD囎⧔쁳휤T??鉬뇙=#ꢫ숣BX䭼<\/d똬졬g榿)eꨋﯪ좇첻\u001a\u0011\";~쓆BH4坋攊7힪", + "iT:L闞椕윚*滛gI≀Wਟඊ'ꢆ縺뱹鮚Nꩁ᧬蕼21줧\\䋯``⍐\\㏱鳨": 1927052677739832894, + "쮁缦腃g]礿Y㬙 fヺSɪ꾾N㞈": [ + null, + null, + { + "!t,灝Y 1䗉罵?c饃호䉂Cᐭ쒘z(즽sZG㬣sഖE4뢜㓕䏞丮Qp簍6EZឪ겛fx'ꩱQ0罣i{k锩*㤴㯞r迎jTⲤ渔m炅肳": [ + -3.3325685522591933E18, + [{"㓁5]A䢕1룥BC?Ꙍ`r룔Ⳛ䙡u伲+\u0001്o": [ + null, + 4975309147809803991, + null, + null, + {"T팘8Dﯲ稟MM☻㧚䥧/8ﻥ⥯aXLaH\"顾S☟耲ît7fS෉놁뮔/ꕼ䓈쁺4\\霶䠴ᩢ<\/t4?죵>uD5➶༆쉌럮⢀秙䘥\u20972ETR3濡恆vB? ~鸆\u0005": { + "`閖m璝㥉b뜴?Wf;?DV콜\u2020퍉౓擝宏ZMj3mJ먡-傷뱙yח㸷꥿ ໘u=M읝!5吭L4v\\?ǎ7C홫": null, + "|": false, + "~Ztᛋ䚘\\擭㗝傪W陖+㗶qᵿ蘥ᙄp%䫎)}=⠔6ᮢS湟-螾-mXH?cp": 448751162044282216, + "\u209fad놹j檋䇌ᶾ梕㉝bוּ": {"?苴ꩠD䋓帘5騱qﱖPF?☸珗顒yU ᡫcb䫎 S@㥚gꮒ쎘泴멖\\:I鮱TZ듒ᶨQ3+f7캙\"?\f풾\\o杞紟﻽M.⏎靑OP": [ + -2.6990368911551596E18, + [{"䒖@<᰿<\/⽬tTr腞&G%᳊秩蜰擻f㎳?S㵧\r*k뎾-乢겹隷j軛겷0룁鮁": {")DO0腦:춍逿:1㥨่!蛍樋2": [{ + ",ꌣf侴笾m๫ꆽ?1?U?\u0011ꌈꂇ": { + "x捗甠nVq䅦w`CD⦂惺嘴0I#vỵ} \\귂S끴D얾?Ԓj溯\"v餄a": { + "@翙c⢃趚痋i\u0015OQ⍝lq돆Y0pࢥ3쉨䜩^<8g懥0w)]䊑n洺o5쭝QL댊랖L镈Qnt⪟㒅십q헎鳒⮤眉ᔹ梠@O縠u泌ㄘb榚癸XޔFtj;iC": false, + "I&뱋゘|蓔䔕측瓯%6ᗻHW\\N1貇#?僐ᗜgh᭪o'䗈꽹Rc욏/蔳迄༝!0邔䨷푪8疩)[쭶緄㇈୧ፐ": { + "B+:ꉰ`s쾭)빼C羍A䫊pMgjdx䐝Hf9᥸W0!C樃'蘿f䫤סи\u0017Jve? 覝f둀⬣퓉Whk\"஼=չﳐ皆笁BIW虨쫓F廰饞": -642906201042308791, + "sb,XcZ<\/m㉹ ;䑷@c䵀s奤⬷7`ꘖ蕘戚?Feb#輜}p4nH⬮eKL트}": [ + "RK鳗z=袤Pf|[,u욺", + "Ẏᏻ罯뉋⺖锅젯㷻{H䰞쬙-쩓D]~\u0013O㳢gb@揶蔉|kᦂ❗!\u001ebM褐sca쨜襒y⺉룓", + null, + null, + true, + -1.650777344339075E-19, + false, + "☑lꄆs힨꤇]'uTന⌳농].1⋔괁沰\"IWഩ\u0019氜8쟇䔻;3衲恋,窌z펏喁횗?4?C넁问?ᥙ橭{稻Ⴗ_썔", + "n?]讇빽嗁}1孅9#ꭨ靶v\u0014喈)vw祔}룼쮿I", + -2.7033457331882025E18, + { + ";⚃^㱋x:饬ኡj'꧵T☽O㔬RO婎?향ᒭ搩$渣y4i;(Q>꿘e8q": "j~錘}0g;L萺*;ᕭꄮ0l潛烢5H▄쳂ꏒוֹꙶT犘≫x閦웧v", + "~揯\u2018c4職렁E~ᑅቚꈂ?nq뎤.:慹`F햘+%鉎O瀜쟏敛菮⍌浢<\/㮺紿P鳆ࠉ8I-o?#jﮨ7v3Dt赻J9": null, + "ࣝW䌈0ꍎqC逖,횅c၃swj;jJS櫍5槗OaB>D踾Y": {"㒰䵝F%?59.㍈cᕨ흕틎ḏ㋩B=9IېⓌ{:9.yw}呰ㆮ肒᎒tI㾴62\"ዃ抡C﹬B<\/촋jo朣", + [ + -7675533242647793366, + {"ᙧ呃:[㒺쳀쌡쏂H稈㢤\u001dᶗGG-{GHྻຊꡃ哸䵬;$?&d\\⥬こN圴됤挨-'ꕮ$PU%?冕눖i魁q騎Q": [ + false, + [[ + 7929823049157504248, + [[ + true, + "Z菙\u0017'eꕤ᱕l,0\\X\u001c[=雿8蠬L<\/낲긯W99g톉4ퟋb㝺\u0007劁'!麕Q궈oW:@X၎z蘻m絙璩귓죉+3柚怫tS捇蒣䝠-擶D[0=퉿8)q0ٟ", + "唉\nFA椭穒巯\\䥴䅺鿤S#b迅獘 ﶗ꬘\\?q1qN犠pX꜅^䤊⛤㢌[⬛휖岺q唻ⳡ틍\"㙙Eh@oA賑㗠y必Nꊑᗘ", + -2154220236962890773, + -3.2442003245397908E18, + "Wᄿ筠:瘫퀩?o貸q⊻(᎞KWf宛尨h^残3[U(='橄", + -7857990034281549164, + 1.44283696979059942E18, + null, + {"ꫯAw跭喀 ?_9\"Aty背F=9缉ྦྷ@;?^鞀w:uN㘢Rỏ": [ + 7.393662029337442E15, + 3564680942654233068, + [ + false, + -5253931502642112194, + "煉\\辎ೆ罍5⒭1䪁䃑s䎢:[e5}峳ﴱn騎3?腳Hyꏃ膼N潭錖,Yᝋ˜YAၓ㬠bG렣䰣:", + true, + null, + { + "⒛'P&%죮|:⫶춞": -3818336746965687085, + "钖m<\/0ݎMtF2Pk=瓰୮洽겎.": [[ + -8757574841556350607, + -3045234949333270161, + null, + { + "Ꮬr輳>⫇9hU##w@귪A\\C 鋺㘓ꖐ梒뒬묹㹻+郸嬏윤'+g<\/碴,}ꙫ>손;情d齆J䬁ຩ撛챝탹/R澡7剌tꤼ?ặ!`⏲睤\u00002똥଴⟏": null, + "\u20f2ܹe\\tAꥍư\\x当뿖렉禛;G檳ﯪS૰3~㘠#[J<}{奲 5箉⨔{놁<\/釿抋,嚠/曳m&WaOvT赋皺璑텁": [[ + false, + null, + true, + -5.7131445659795661E18, + "萭m䓪D5|3婁ఞ>蠇晼6nﴺPp禽羱DS<睓닫屚삏姿", + true, + [ + -8759747687917306831, + { + ">ⓛ\t,odKr{䘠?b퓸C嶈=DyEᙬ@ᴔ쨺芛髿UT퓻春<\/yꏸ>豚W釺N뜨^?꽴﨟5殺ᗃ翐%>퍂ဿ䄸沂Ea;A_\u0005閹殀W+窊?Ꭼd\u0013P汴G5썓揘": 4.342729067882445E-18, + "Q^즾眆@AN\u0011Kb榰냎Y#䝀ꀒᳺ'q暇睵s\"!3#I⊆畼寤@HxJ9": false, + "⿾D[)袨㇩i]웪䀤ᛰMvR<蟏㣨": {"v퇓L㪱ꖣ豛톤\\곱#kDTN": [{ + "(쾴䡣,寴ph(C\"㳶w\"憳2s馆E!n!&柄<\/0Pꈗſ?㿳Qd鵔": {"娇堰孹L錮h嵅⛤躏顒?CglN束+쨣ﺜ\\MrH": {"獞䎇둃ቲ弭팭^ꄞ踦涟XK錆쳞ឌ`;੶S炥騞ଋ褂B៎{ڒ䭷ᶼ靜pI荗虶K$": [{"◖S~躘蒉꫿輜譝Q㽙闐@ᢗ¥E榁iء5┄^B[絮跉ᰥ遙PWi3wㄾⵀDJ9!w㞣ᄎ{듒ꓓb6\\篴??c⼰鶹⟧\\鮇ꮇ": [[ + 654120831325413520, + -1.9562073916357608E-19, + { + "DC(昐衵ἡ긙갵姭|֛[t": 7.6979110359897907E18, + "J␅))嫼❳9Xfd飉j7猬ᩉ+⤻眗벎E鰉Zᄊ63zၝ69}ZᶐL崭ᦥ⡦靚⋛ꎨ~i㨃咊ꧭo䰠阀3C(": -3.5844809362512589E17, + "p꣑팱쒬ꎑ뛡Ꙩ挴恍胔&7ᔈ묒4Hd硶훐㎖zꢼ豍㿢aሃ=<\/湉鵲EӅ%$F!퍶棌孼{O駍਺geu+": ")\u001b잓kŀX쩫A밁®ڣ癦狢)扔弒p}k縕ꩋ,䃉tࣼi", + "ァF肿輸<솄G-䢹䛸ꊏl`Tqꕗ蒞a氷⸅ᴉ蠰]S/{J왲m5{9.uέ~㕚㣹u>x8U讁B덺襪盎QhVS맅킃i识{벂磄Iහ䙅xZy/抍૭Z鲁-霳V据挦ℒ": null, + "㯛|Nꐸb7ⵐb?拠O\u0014ކ?-(EꞨ4ꕷᄤYᯕOW瞺~螸\"욿ќe㺰\"'㌢ƐW\u0004瞕>0?V鷵엳": true, + "뤥G\\迋䠿[庩'꼡\u001aiᩮV쯁ᳪ䦪Ô;倱ନ뛁誈": null, + "쥹䄆䚟Q榁䎐᢭<\/2㕣p}HW蟔|䃏꿈ꚉ锳2Pb7㙑Tⅹᵅ": { + "Y?֭$>#cVBꩨ:>eL蒁務": { + "86柡0po 䏚&-捑Ћ祌<\/휃-G*㶢הּ쩍s㶟餇c걺yu꽎還5*턧簕Og婥SꝐ": null, + "a+葞h٥ࠆ裈嗫ﵢ5輙퀟ᛜ,QDﹼ⟶Y騠锪E_|x죗j侵;m蜫轘趥?븅w5+mi콛L": { + ";⯭ﱢ!买F⽍柤鶂n䵣V㫚墱2렾ELEl⣆": [ + true, + -3.6479311868339015E-18, + -7270785619461995400, + 3.334081886177621E18, + 2.581457786298155E18, + -6.605252412954115E-20, + -3.9232347037744167E-20, + { + "B6㊕.k1": null, + "ZAꄮJ鮷ᳱo갘硥鈠䠒츼": { + "ᕅ}럡}.@y陪鶁r業'援퀉x䉴ﵴl퍘):씭脴ᥞhiꃰblﲂ䡲엕8߇M㶭0燋標挝-?PCwe⾕J碻Ᾱ䬈䈥뷰憵賣뵓痬+": {"a췩v礗X⋈耓ፊf罅靮!㔽YYᣓw澍33⎔芲F|\"䜏T↮輦挑6ᓘL侘?ᅥ]덆1R௯✎餘6ꏽ<\/௨\\?q喷ꁫj~@ulq": {"嗫欆뾔Xꆹ4H㌋F嵧]ࠎ]㠖1ꞤT<$m뫏O i댳0䲝i": {"?෩?\u20cd슮|ꯆjs{?d7?eNs⢚嫥氂䡮쎱:鑵롟2hJꎒﯭ鱢3춲亄:뼣v䊭諱Yj択cVmR䩃㘬T\"N홝*ै%x^F\\_s9보zz4淗?q": [ + null, + "?", + 2941869570821073737, + "{5{殇0䝾g6밖퍋臩綹R$䖭j紋釰7sXI繳漪행y", + false, + "aH磂?뛡#惇d婅?Fe,쐘+늵䍘\"3r瘆唊勐j⳧࠴ꇓ<\/唕윈x⬌讣䋵%拗ᛆⰿ妴᝔M2㳗必꧂淲?ゥ젯檢<8끒MidX䏒3᳻Q▮佐UT|⤪봦靏⊏", + [[{ + "颉(&뜸귙{y^\"P퟉춝Ჟ䮭D顡9=?}Y誱<$b뱣RvO8cH煉@tk~4ǂ⤧⩝屋SS;J{vV#剤餓ᯅc?#a6D,s": [ + -7.8781018564821536E16, + true, + [ + -2.28770899315832371E18, + false, + -1.0863912140143876E-20, + -6282721572097446995, + 6767121921199223078, + -2545487755405567831, + false, + null, + -9065970397975641765, + [ + -5.928721243413937E-20, + {"6촊\u001a홯kB0w撨燠룉{绎6⳹!턍贑y▾鱧ժ[;7ᨷ∀*땒䪮1x霆Hᩭ☔\"r䝐7毟ᝰr惃3ꉭE+>僒澐": [ + "Ta쎩aƝt쵯ⰪVb", + [ + -5222472249213580702, + null, + -2851641861541559595, + null, + 4808804630502809099, + 5657671602244269874, + "5犲﨣4mᥣ?yf젫꾯|䋬잁$`Iⳉﴷ扳兝,'c", + false, + [ + null, + { + "DyUIN쎾M仼惀⮥裎岶泭lh扠\u001e礼.tEC癯튻@_Qd4c5S熯A<\/\6U윲蹴Q=%푫汹\\\u20614b[௒C⒥Xe⊇囙b,服3ss땊뢍i~逇PA쇸1": -2.63273619193485312E17, + "Mq꺋貘k휕=nK硍뫞輩>㾆~἞ࡹ긐榵l⋙Hw뮢帋M엳뢯v⅃^": 1877913476688465125, + "ᶴ뻗`~筗免⚽টW˃⽝b犳䓺Iz篤p;乨A\u20ef쩏?疊m㝀컩뫡b탔鄃ᾈV(遢珳=뎲ିeF仢䆡谨8t0醄7㭧瘵⻰컆r厡궥d)a阄፷Ed&c﯄伮1p": null, + "⯁w4曢\"(欷輡": "\"M᭫]䣒頳B\\燧ࠃN㡇j姈g⊸⺌忉ꡥF矉স%^", + "㣡Oᄦ昵⫮Y祎S쐐級㭻撥>{I$": -378474210562741663, + "䛒掷留Q%쓗1*1J*끓헩ᦢ﫫哉쩧EↅIcꅡ\\?ⴊl귛顮4": false, + "寔愆샠5]䗄IH贈=d﯊/偶?ॊn%晥D視N򗘈'᫂⚦|X쵩넽z질tskxDQ莮Aoﱻ뛓": true, + "钣xp?&\u001e侉/y䴼~?U篔蘚缣/I畚?Q绊": -3034854258736382234, + "꺲໣眀)⿷J暘pИfAV삕쳭Nꯗ4々'唄ⶑ伻㷯騑倭D*Ok꧁3b␽_<\/챣Xm톰ၕ䆄`*fl㭀暮滠毡?": [ + "D男p`V뙸擨忝븪9c麺`淂⢦Yw⡢+kzܖ\fY1䬡H歁)벾Z♤溊-혰셢?1<-\u0005;搢Tᐁle\\ᛵߓﭩ榩訝-xJ;巡8깊蠝ﻓU$K": { + "Vꕡ諅搓W=斸s︪vﲜ츧$)iꡟ싉e寳?ጭムVથ嵬i楝Fg<\/Z|៪ꩆ-5'@ꃱ80!燱R쇤t糳]罛逇dṌ֣XHiͦ{": true, + "Ya矲C멗Q9膲墅携휻c\\딶G甔<\/.齵휴": -1.1456247877031811E-19, + "z#.OO￝J": -8263224695871959017, + "崍_3夼ᮟ1F븍뽯ᦓ鴭V豈Ь": [{ + "N蒬74": null, + "yuB?厅vK笗!ᔸcXQ旦컶P-녫mᄉ麟_": "1R@ 톘xa_|﩯遘s槞d!d껀筤⬫薐焵먑D{\\6k共倌☀G~AS_D\"딟쬚뮥馲렓쓠攥WTMܭ8nX㩴䕅檹E\u0007ﭨN 2 ℆涐ꥏ꠵3▙玽|됨_\u2048", + "恐A C䧩G": {":M큣5e들\\ꍀ恼ᔄ靸|I﨏$)n": { + "|U䬫㟯SKV6ꛤ㗮\bn봻䲄fXT:㾯쳤'笓0b/ೢC쳖?2浓uO.䰴": "ཐ꼋e?``,ᚇ慐^8ꜙNM䂱\u0001IᖙꝧM'vKdꌊH牮r\\O@䊷ᓵ쀆(fy聻i툺\"?<\/峧ࣞ⓺ᤤ쵒߯ꎺ騬?)刦\u2072l慪y꺜ﲖTj+u", + "뽫hh䈵w>1ⲏ쐭V[ⅎ\\헑벑F_㖝⠗㫇h恽;῝汰ᱼ瀖J옆9RR셏vsZ柺鶶툤r뢱橾/ꉇ囦FGm\"謗ꉦ⨶쒿⥡%]鵩#ᖣ_蹎 u5|祥?O", + null, + 2.0150326776036215E-19, + null, + true, + false, + true, + {"\fa᭶P捤WWc᠟f뚉ᬏ퓗ⳀW睹5:HXH=q7x찙X$)모r뚥ᆟ!Jﳸf": [ + -2995806398034583407, + [ + 6441377066589744683, + "Mﶒ醹i)Gἦ廃s6몞 KJ౹礎VZ螺费힀\u0000冺업{谥'꡾뱻:.ꘘ굄奉攼Di᷑K鶲y繈욊阓v㻘}枭캗e矮1c?휐\"4\u0005厑莔뀾墓낝⽴洗ṹ䇃糞@b1\u0016즽Y轹", + { + "1⽕⌰鉟픏M㤭n⧴ỼD#%鐘⊯쿼稁븣몐紧ᅇ㓕ᛖcw嬀~ഌ㖓(0r⧦Q䑕髍ര铂㓻R儮\"@ꇱm❈௿᦯頌8}㿹犴?xn잆꥽R": 2.07321075750427366E18, + "˳b18㗈䃟柵Z曆VTAu7+㛂cb0﯑Wp執<\/臋뭡뚋刼틮荋벲TLP预庰܈G\\O@VD'鱃#乖끺*鑪ꬳ?Mޞdﭹ{␇圯쇜㼞顄︖Y홡g": [{ + "0a,FZ": true, + "2z̬蝣ꧦ驸\u0006L↛Ḣ4๚뿀'?lcwᄧ㐮!蓚䃦-|7.飑挴.樵*+1ﮊ\u0010ꛌ%貨啺/JdM:똍!FBe?鰴㨗0O财I藻ʔWA᫓G쳛u`<\/I": [{ + "$τ5V鴐a뾆両環iZp頻යn븃v": -4869131188151215571, + "*즢[⦃b礞R◚nΰꕢH=귰燙[yc誘g䆌?ଜ臛": { + "洤湌鲒)⟻\\䥳va}PeAMnN[": "㐳ɪ/(軆lZR,Cp殍ȮN啷\"3B婴?i=r$펽ᤐ쀸", + "阄R4㒿㯔ڀ69ZᲦ2癁핌噗P崜#\\-쭍袛&鐑/$4童V꩑_ZHA澢fZ3": {"x;P{긳:G閉:9?活H": [ + "繺漮6?z犞焃슳\">ỏ[Ⳛ䌜녏䂹>聵⼶煜Y桥[泥뚩MvK$4jtロ", + "E#갶霠좭㦻ୗ먵F+䪀o蝒ba쮎4X㣵 h", + -335836610224228782, + null, + null, + [ + "r1᫩0>danjY짿bs{", + [ + -9.594464059325631E-23, + 1.0456894622831624E-20, + null, + 5.803973284253454E-20, + -8141787905188892123, + true, + -4735305442504973382, + 9.513150514479281E-20, + "7넳$螔忷㶪}䪪l짴\u0007鹁P鰚HF銏ZJﳴ/⍎1ᷓ忉睇ᜋ쓈x뵠m䷐窥Ꮤ^\u0019ᶌ偭#ヂt☆၃pᎍ臶䟱5$䰵&๵分숝]䝈뉍♂坎\u0011<>", + "C蒑貑藁lﰰ}X喇몛;t밿O7/᯹f\u0015kI嘦<ዴ㟮ᗎZ`GWퟩ瑹࡮ᅴB꿊칈??R校s脚", + { + "9珵戬+AU^洘拻ቒy柭床'粙XG鞕᠜繀伪%]hC,$輙?Ut乖Qm떚W8઼}~q⠪rU䤶CQ痗ig@#≲t샌f㈥酧l;y闥ZH斦e⸬]j⸗?ঢ拻퀆滌": null, + "畯}㧢J罚帐VX㨑>1ꢶkT⿄蘥㝑o|<嗸層沈挄GEOM@-䞚䧰$만峬輏䠱V✩5宸-揂D'㗪yP掶7b⠟J㕻SfP?d}v㼂Ꮕ'猘": { + "陓y잀v>╪": null, + "鬿L+7:됑Y=焠U;킻䯌잫!韎ஔ\f": { + "駫WmGጶ": { + "\\~m6狩K": -2586304199791962143, + "ႜࠀ%͑l⿅D.瑢Dk%0紪dḨTI픸%뗜☓s榗኉\"?V籄7w髄♲쟗翛歂E䤓皹t ?)ᄟ鬲鐜6C": { + "_췤a圷1\u000eB-XOy缿請∎$`쳌eZ~杁튻/蜞`塣৙\"⪰\"沒l}蕌\\롃荫氌.望wZ|o!)Hn獝qg}": null, + "kOSܧ䖨钨:಼鉝ꭝO醧S`십`ꓭ쭁ﯢN&Et㺪馻㍢ⅳ㢺崡ຊ蜚锫\\%ahx켨|ż劻ꎄ㢄쐟A躊᰹p譞綨Ir쿯\u0016ﵚOd럂*僨郀N*b㕷63z": { + ":L5r+T㡲": [{ + "VK泓돲ᮙRy㓤➙Ⱗ38oi}LJቨ7Ó㹡৘*q)1豢⛃e᫛뙪壥镇枝7G藯g㨛oI䄽 孂L缊ꋕ'EN`": -2148138481412096818, + "`⛝ᘑ$(खꊲ⤖ᄁꤒ䦦3=)]Y㢌跨NĴ驳줟秠++d孳>8ᎊ떩EꡣSv룃 쯫أ?#E|᭙㎐?zv:5祉^⋑V": [ + -1.4691944435285607E-19, + 3.4128661569395795E17, + "㐃촗^G9佭龶n募8R厞eEw⺡_ㆱ%⼨D뉄퉠2ꩵᛅⳍ搿L팹Lවn=\"慉념ᛮy>!`g!풲晴[/;?[v겁軇}⤳⤁핏∌T㽲R홓遉㓥", + "愰_⮹T䓒妒閤둥?0aB@㈧g焻-#~跬x<\/舁P݄ꐡ=\\׳P\u0015jᳪᢁq;㯏l%᭗;砢觨▝,謁ꍰGy?躤O黩퍋Y㒝a擯\n7覌똟_䔡]fJ晋IAS", + 4367930106786121250, + -4.9421193149720582E17, + null, + { + ";ᄌ똾柉곟ⰺKpፇ䱻ฺ䖝{o~h!eꁿ઻욄ښ\u0002y?xUd\u207c悜ꌭ": [ + 1.6010824122815255E-19, + [ + "宨︩9앉檥pr쇷?WxLb", + "氇9】J玚\u000f옛呲~ 輠1D嬛,*mW3?n휂糊γ虻*ᴫ꾠?q凐趗Ko↦GT铮", + "㶢ថmO㍔k'诔栀Z蛟}GZ钹D", + false, + -6.366995517736813E-20, + -4894479530745302899, + null, + "V%᫡II璅䅛䓎풹ﱢ/pU9se되뛞x梔~C)䨧䩻蜺(g㘚R?/Ự[忓C뾠ࢤc왈邠买?嫥挤풜隊枕", + ",v碍喔㌲쟚蔚톬៓ꭶ", + 3.9625444752577524E-19, + null, + [ + "kO8란뿒䱕馔b臻⍟隨\"㜮鲣Yq5m퐔K#ꢘug㼈ᝦ=P^6탲@䧔%$CqSw铜랊0&m⟭<\/a逎ym\u0013vᯗ": true, + "洫`|XN뤮\u0018詞=紩鴘_sX)㯅鿻Ố싹": 7.168252736947373E-20, + "ꛊ饤ﴏ袁(逊+~⽫얢鈮艬O힉7D筗S곯w操I斞᠈븘蓷x": [[[[ + -7.3136069426336952E18, + -2.13572396712722688E18, + { + "硢3㇩R:o칢行E<=\u0018ၬYuH!\u00044U%卝炼2>\u001eSi$⓷ꒈ'렢gᙫ番ꯒ㛹럥嶀澈v;葷鄕x蓎\\惩+稘UEᖸﳊ㊈壋N嫿⏾挎,袯苷ኢ\\x|3c": 7540762493381776411, + "?!*^ᢏ窯?\u0001ڔꙃw虜돳FgJ?&⨫*uo籤:?}ꃹ=ٴ惨瓜Z媊@ત戹㔏똩Ԛ耦Wt轁\\枒^\\ꩵ}}}ꀣD\\]6M_⌫)H豣:36섘㑜": { + ";홗ᰰU஋㙛`D왔ཿЃS회爁\u001b-㢈`봆?盂㛣듿ᦾ蒽_AD~EEຆ㊋(eNwk=Rɠ峭q\"5Ἠ婾^>'ls\n8QAK)- Q䲌mo펹L_칍樖庫9꩝쪹ᘹ䑖瀍aK ?*趤f뭓廝p=磕", + "哑z懅ᤏ-ꍹux쀭", + [ + true, + 3998739591332339511, + "ጻ㙙?᳸aK<\/囩U`B3袗ﱱ?\"/k鏔䍧2l@쿎VZ쨎/6ꃭ脥|B?31+on颼-ꮧ,O嫚m ࡭`KH葦:粘i]aSU쓙$쐂f+詛頖b", + [{"^<9<箝&絡;%i﫡2攑紴\\켉h쓙-柂䚝ven\u20f7浯-Ꮏ\r^훁䓚헬\u000e?\\ㅡֺJ떷VOt": [{ + "-௄卶k㘆혐஽y⎱㢬sS઄+^瞥h;ᾷj;抭\u0003밫f<\/5Ⱗ裏_朻%*[-撵䷮彈-芈": { + "㩩p3篊G|宮hz䑊o곥j^Co0": [ + 653239109285256503, + {"궲?|\":N1ۿ氃NZ#깩:쇡o8킗ࡊ[\"됸Po핇1(6鰏$膓}⽐*)渽J'DN<썙긘毦끲Ys칖": { + "2Pr?Xjㆠ?搮/?㓦柖馃5뚣Nᦼ|铢r衴㩖\"甝湗ܝ憍": "\"뾯i띇筝牻$珲/4ka $匝휴译zbAᩁꇸ瑅&뵲衯ꎀᆿ7@ꈋ'ᶨH@ᠴl+", + "7뢽뚐v?4^ꊥ_⪛.>pởr渲<\/⢕疻c\"g䇘vU剺dஔ鮥꒚(dv祴X⼹\\a8y5坆": true, + "o뼄B욞羁hr﷔폘뒚⿛U5pꪴfg!6\\\"爑쏍䢱W<ﶕ\\텣珇oI/BK뺡'谑♟[Ut븷亮g(\"t⡎有?ꬊ躺翁艩nl F⤿蠜": 1695826030502619742, + "ۊ깖>ࡹ햹^ⵕ쌾BnN〳2C䌕tʬ]찠?ݾ2饺蹳ぶꌭ訍\"◹ᬁD鯎4e滨T輀ﵣ੃3\u20f3킙D瘮g\\擦+泙ၧ 鬹ﯨַ肋7놷郟lP冝{ߒhড়r5,꓋": null, + "ΉN$y{}2\\N﹯ⱙK'8ɜͣwt,.钟廣䎘ꆚk媄_": null, + "䎥eᾆᝦ읉,Jުn岪㥐s搖謽䚔5t㯏㰳㱊ZhD䃭f絕s鋡篟a`Q鬃┦鸳n_靂(E4迠_觅뷝_宪D(NL疶hL追V熑%]v肫=惂!㇫5⬒\u001f喺4랪옑": { + "2a輍85먙R㮧㚪Sm}E2yꆣꫨrRym㐱膶ᔨ\\t綾A☰.焄뙗9<쫷챻䒵셴᭛䮜.<\/慌꽒9叻Ok䰊Z㥪幸k": [ + null, + true, + {"쌞쐍": { + "▟GL K2i뛱iQ\"̠.옛1X$}涺]靎懠ڦ늷?tf灟ݞゟ{": 1.227740268699265E-19, + "꒶]퓚%ฬK❅": [{ + "(ෛ@Ǯっ䧼䵤[aテൖvEnAdU렖뗈@볓yꈪ,mԴ|꟢캁(而첸죕CX4Y믅": "2⯩㳿ꢚ훀~迯?᪑\\啚;4X\u20c2襏B箹)俣eỻw䇄", + "75༂f詳䅫ꐧ鏿 }3\u20b5'∓䝱虀f菼Iq鈆﨤g퍩)BFa왢d0뮪痮M鋡nw∵謊;ꝧf美箈ḋ*\u001c`퇚퐋䳫$!V#N㹲抗ⱉ珎(V嵟鬒_b㳅\u0019": null, + "e_m@(i㜀3ꦗ䕯䭰Oc+-련0뭦⢹苿蟰ꂏSV䰭勢덥.ྈ爑Vd,ᕥ=퀍)vz뱊ꈊB_6듯\"?{㒲&㵞뵫疝돡믈%Qw限,?\r枮\"? N~癃ruࡗdn&": null, + "㉹&'Pfs䑜공j<\/?|8oc᧨L7\\pXᭁ 9᪘": -2.423073789014103E18, + "䝄瑄䢸穊f盈᥸,B뾧푗횵B1쟢f\u001f凄": "魖⚝2儉j꼂긾껢嗎0ࢇ纬xI4](੓`蕞;픬\fC\"斒\")2櫷I﹥迧", + "ퟯ詔x悝령+T?Bg⥄섅kOeQ큼㻴*{E靼6氿L缋\u001c둌๶-㥂2==-츫I즃㠐Lg踞ꙂEG貨鞠\"\u0014d'.缗gI-lIb䋱ᎂDy缦?": null, + "紝M㦁犿w浴詟棓쵫G:䜁?V2ힽ7N*n&㖊Nd-'ຊ?-樹DIv⊜)g䑜9뉂ㄹ푍阉~ꅐ쵃#R^\u000bB䌎䦾]p.䀳": [{"ϒ爛\"ꄱ︗竒G䃓-ま帳あ.j)qgu扐徣ਁZ鼗A9A鸦甈!k蔁喙:3T%&㠘+,䷞|챽v䚞문H<\/醯r셓㶾\\a볜卺zE䝷_죤ဵ뿰᎟CB": [ + 6233512720017661219, + null, + -1638543730522713294, + false, + -8901187771615024724, + [ + 3891351109509829590, + true, + false, + -1.03836679125188032E18, + { + "j랎:g曞ѕᘼ}链N", + -1.1103819473845426E-19, + true, + [ + true, + null, + -7.9091791735309888E17, + true, + {"}蔰鋈+ꐨ啵0?g*사%`J?*": [{ + "\"2wG?yn,癷BK\\龞䑞x?蠢": -3.7220345009853505E-19, + ";饹়❀)皋`噿焒j(3⿏w>偍5X薙婏聿3aFÆÝ": "2,ꓴg?_섦_>Y쪥션钺;=趘F~?D㨫\bX?㹤+>/믟kᠪ멅쬂Uzỵ]$珧`m雁瑊ඖ鯬cꙉ梢f묛bB", + "♽n$YjKiXX*GO贩鏃豮祴遞K醞眡}ꗨv嵎꼷0୸+M菋eH徸J꣆:⼐悥B켽迚㯃b諂\u000bjꠜ碱逮m8": [ + "푷᣺ﻯd8ﱖ嬇ភH鹎⡱᱅0g:果6$GQ췎{vᷧYy-脕x偹砡館⮸C蓼ꏚ=軄H犠G谖ES詤Z蠂3l봟hᅭ7䦹1GPQG癸숟~[#駥8zQ뛣J소obg,", + null, + 1513751096373485652, + null, + -6.851466660824754E-19, + {"䩂-⴮2ٰK솖풄꾚ႻP앳1H鷛wmR䗂皎칄?醜<\/&ࠧ㬍X濬䵈K`vJ륒Q/IC묛!;$vϑ": { + "@-ꚗxྐྵ@m瘬\u0010U絨ﮌ驐\\켑寛넆T=tQ㭤L연@脸삯e-:⩼u㎳VQ㋱襗ຓ<Ⅶ䌸cML3+\u001e_C)r\\9+Jn\\Pﺔ8蠱檾萅Pq鐳话T䄐I": -1.80683891195530061E18, + "ᷭዻU~ཷsgSJ`᪅'%㖔n5픆桪砳峣3獮枾䌷⊰呀": { + "Ş੉䓰邟自~X耤pl7间懑徛s첦5ਕXexh⬖鎥᐀nNr(J컗|ૃF\"Q겮葲놔엞^겄+㈆话〾희紐G'E?飕1f❼텬悚泬먐U睬훶Qs": false, + "(\u20dag8큽튣>^Y{뤋.袊䂓;_g]S\u202a꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\u001asD7IK5Wxo8\u0006p弊⼂ꯍ扵\u0003`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\": [ + true, + { + "\n鱿aK㝡␒㼙2촹f;`쾏qIࡔG}㝷䐍瓰w늮*粅9뒪ㄊCj倡翑閳R渚MiUO~仨䜶RꙀA僈㉋⦋n{㖥0딿벑逦⥻0h薓쯴Ꝼ": [ + 5188716534221998369, + 2579413015347802508, + 9.010794400256652E-21, + -6.5327297761238093E17, + 1.11635352494065523E18, + -6656281618760253655, + { + "": ")?", + "TWKLꑙ裑꺔UE俸塑炌Ũ᜕-o\"徚#": {"M/癟6!oI51ni퐚=댡>xꍨ\u0004 ?": { + "皭": {"⢫䋖>u%w잼<䕏꘍P䋵$魋拝U䮎緧皇Y훂&|羋ꋕ잿cJ䨈跓齳5\u001a삱籷I꿾뤔S8㌷繖_Yឯ䲱B턼O歵F\\l醴o_欬6籏=D": [ + false, + true, + {"Mt|ꏞD|F궣MQ뵕T,띺k+?㍵i": [ + 7828094884540988137, + false, + { + "!༦鯠,&aﳑ>[euJꏽ綷搐B.h": -7648546591767075632, + "-n켧嘰{7挐毄Y,>❏螵煫乌pv醑Q嶚!|⌝責0왾덢ꏅ蛨S\\)竰'舓Q}A釡5#v": 3344849660672723988, + "8閪麁V=鈢1녈幬6棉⪮둌\u207d᚛驉ꛃ'r䆉惏ै|bἧﺢᒙ<=穊强s혧eꮿ慩⌡ \\槳W븧J檀C,ᘉ의0俯퀉M;筷ࣴ瓿{늊埂鄧_4揸Nn阼Jੵ˥(社": true, + "o뼀vw)4A뢵(a䵢)p姃뛸\u000fK#KiQp\u0005ꅍ芅쏅": null, + "砥$ꥸ┇耽u斮Gc{z빔깎밇\\숰\u001e괷各㶇쵿_ᴄ+h穢p촀Ნ䃬z䝁酳ӂ31xꔄ1_砚W렘G#2葊P ": [ + -3709692921720865059, + null, + [ + 6669892810652602379, + -135535375466621127, + "뎴iO}Z? 馢녱稹ᄾ䐩rSt帤넆&7i騏멗畖9誧鄜'w{Ͻ^2窭외b㑎粖i矪ꦨ탪跣)KEㆹ\u0015V8[W?⽉>'kc$䨘ᮛ뉻٬M5", + 1.10439588726055846E18, + false, + -4349729830749729097, + null, + [ + false, + "_蠢㠝^䟪/D녒㡋ỎC䒈판\u0006એq@O펢%;鹐쏌o戥~A[ꡉ濽ỳ&虃᩾荣唙藍茨Ig楡꒻M窓冉?", + true, + 2.17220752996421728E17, + -5079714907315156164, + -9.960375974658589E-20, + "ᾎ戞༒", + true, + false, + [[ + "ⶉᖌX⧕홇)g엃⹪x뚐癟\u0002", + -5185853871623955469, + { + "L㜤9ợㇶK鐰⋓V뽋˖!斫as|9"፬䆪?7胜&n薑~": -2.11545634977136992E17, + "O8뀩D}캖q萂6༣㏗䈓煮吽ਆᎼDᣘ폛;": false, + "YTᡅ^L㗎cbY$pᣞ縿#fh!ꘂb삵玊颟샞ဢ$䁗鼒몁~rkH^:닮먖츸륈⪺쒉砉?㙓扫㆕꣒`R䢱B酂?C뇞<5Iޚ讳騕S瞦z": null, + "\\RB?`mG댵鉡幐物䵎有5*e骄T㌓ᛪ琾駒Ku\u001a[柆jUq8⋈5鿋츿myﻗ?雍ux঴?": 5828963951918205428, + "n0晅:黯 xu씪^퓞cB㎊ᬍ⺘٤փ~B岚3㥕擄vᲂ~F?C䶖@$m~忔S왖㲚?챴⊟W#벌{'㰝I䝠縁s樘\\X뢻9핡I6菍ㄛ8쯶]wॽ0L\"q": null, + "x增줖j⦦t䏢᎙㛿Yf鼘~꫓恄4惊\u209c": "oOhbᤃ᛽z&Bi犑\\3B㩬劇䄑oŁ쨅孥멁ຖacA㖫借㞝vg싰샂㐜#譞⢤@k]鋰嘘䜾L熶塥_<\/⍾屈ﮊ_mY菹t뙺}Ox=w鮮4S1ꐩמּ'巑", + "㗓蟵ꂾe蠅匳(JP䗏෸\u0089耀왲": [{ + "ᤃ㵥韎뤽\r?挥O쯡⇔㞚3伖\u0005P⋪\"D궣QLn(⚘罩䩢Ŏv䤘尗뼤됛O淽鋋闚r崩a{4箙{煷m6〈": { + "l곺1L": { + "T'ਤ?砅|੬Km]䄩\"(࿶<\/6U爢䫈倔郴l2㴱^줣k'L浖L鰄Rp今鎗⒗C얨M훁㡧ΘX粜뫈N꤇輊㌻켑#㮮샶-䍗룲蠝癜㱐V>=\\I尬癤t=": 7648082845323511446, + "鋞EP:<\/_`ၧe混ㇹBd⯢㮂驋\\q碽饩跓྿ᴜ+j箿렏㗑yK毢宸p謹h䦹乕U媣\\炤": [[ + "3", + [ + true, + 3.4058271399411134E-20, + true, + "揀+憱f逮@먻BpW曉\u001a㣐⎊$n劈D枤㡞좾\u001aᛁ苔౩闝1B䷒Ṋ݋➐ꀞꐃ磍$t੤_:蘺⮼(#N", + 697483894874368636, + [ + "vᘯ锴)0訶}䳅⩚0O壱韈ߜ\u0018*U鍾䏖=䧉뽑单휻ID쿇嘗?ꌸῬ07", + -5.4858784319382006E18, + 7.5467775182251151E18, + -8911128589670029195, + -7531052386005780140, + null, + [ + null, + true, + [[{ + "1欯twG<\/Q:0怯押殃탷聫사<ỗꕧ蚨䡁nDꌕ\u001c녬~蓩鲃g儊>ꏡl㻿/⑷*챳6㻜W毤緛ﹺᨪ4\u0013뺚J髬e3쳸䘦伧?恪&{L掾p+꬜M䏊d娘6": { + "2p첼양棜h䜢﮶aQ*c扦v︥뮓kC寵횂S銩&ǝ{O*य़iH`U큅ࡓr䩕5ꄸ?`\\᧫?ᮼ?t〟崾훈k薐ì/iy꤃뵰z1<\/AQ#뿩8jJ1z@u䕥": 1.82135747285215155E18, + "ZdN &=d년ᅆ'쑏ⅉ:烋5&៏ᄂ汎来L㯄固{钧u\\㊏튚e摑&t嗄ꖄUb❌?m䴘熚9EW": [{ + "ଛ{i*a(": -8.0314147546006822E17, + "⫾ꃆY\u000e+W`௸ \"M뒶+\\뷐lKE}(NT킶Yj選篒쁶'jNQ硾(똡\\\"逌ⴍy? IRꜘ὞鄬﨧:M\\f⠋Cꚜ쫊ᚴNV^D䕗ㅖἔIao꿬C⍏8": [ + 287156137829026547, + { + "H丞N逕⯲": {"": { + "7-;枮阕梒9ᑄZ": [[[[ + null, + { + "": [[[[ + -7.365909561486078E-19, + 2948694324944243408, + null, + [ + true, + "荒\"并孷䂡쵼9o䀘F\u0002龬7⮹Wz%厖/*? a*R枈㌦됾g뒠䤈q딄㺿$쮸tᶎ릑弣^鏎<\/Y鷇驜L鿽<\/춋9Mᲆឨ^<\/庲3'l낢", + "c鮦\u001b두\\~?眾ಢu݆綑෪蘛轋◜gȃ<\/ⴃcpkDt誩܅\"Y", + [[ + null, + null, + [ + 3113744396744005402, + true, + "v(y", + { + "AQ幆h쾜O+꺷铀ꛉ練A蚗⼺螔j㌍3꽂楎䥯뎸먩?": null, + "蠗渗iz鱖w]擪E": 1.2927828494783804E-17, + "튷|䀭n*曎b✿~杤U]Gz鄭kW|㴚#㟗ഠ8u擨": [[ + true, + null, + null, + {"⾪壯톽g7?㥜ώQꑐ㦀恃㧽伓\\*᧰閖樧뢇赸N휶䎈pI氇镊maᬠ탷#X?A+kНM ༑᩟؝?5꧎鰜ṚY즫궔 =ঈ;ﳈ?*s|켦蜌wM笙莔": [ + null, + -3808207793125626469, + [ + -469910450345251234, + 7852761921290328872, + -2.7979740127017492E18, + 1.4458504352519893E-20, + true, + "㽙깹?먏䆢:䴎ۻg殠JBTU⇞}ꄹꗣi#I뵣鉍r혯~脀쏃#釯:场:䔁>䰮o'㼽HZ擓௧nd", + [ + 974441101787238751, + null, + -2.1647718292441327E-19, + 1.03602824249831488E18, + [ + null, + 1.0311977941822604E-17, + false, + true, + { + "": -3.7019778830816707E18, + "E峾恆茍6xLIm縂0n2视֯J-ᤜz+ᨣ跐mYD豍繹⹺䊓몓ﴀE(@詮(!Y膽#᎙2䟓섣A䈀㟎,囪QbK插wcG湎ꤧtG엝x⥏俎j'A一ᯥ뛙6ㅑ鬀": 8999803005418087004, + "よ殳\\zD⧅%Y泥簳Uꈩ*wRL{3#3FYHା[d岀䉯T稉駅䞘礄P:闈W怏ElB㤍喬赔bG䠼U଄Nw鰯闀楈ePsDꥷ꭬⊊": [ + 6.77723657904486E-20, + null, + [ + "ཚ_뷎꾑蹝q'㾱ꂓ钚蘞慵렜떆`ⴹ⎼櫯]J?[t9Ⓢ !컶躔I᮸uz>3a㠕i,錃L$氰텰@7녫W㸮?羧W뇧ꃞ,N鋮숪2ɼ콏┍䁲6", + "&y?뢶=킕올Za惻HZk>c\u20b58i?ꦶcfBv잉ET9j䡡", + "im珊Ճb칧校\\뼾쯀", + 9.555715121193197E-20, + true, + { + "<㫚v6腓㨭e1㕔&&V∌ᗈT奄5Lጥ>탤?튣瑦㳆ꉰ!(ᙪ㿬擇_n쌯IMΉ㕨␰櫈ᱷ5풔蟹&L.첽e鰷쯃劼﫭b#ﭶ퓀7뷄Wr㢈๧Tʴશ㶑澕鍍%": -1810142373373748101, + "fg晌o?߲ꗄ;>C>?=鑰監侯Kt굅": true, + "䫡蓺ꑷ]C蒹㦘\"1ః@呫\u0014NL䏾eg呮፳,r$裢k>/\\?ㄤᇰﻛ쉕1஥'Ċ\" \\_?쨔\"ʾr: 9S䘏禺ᪧꄂ㲄", + [[{ + "*硙^+E쌺I1䀖ju?:⦈Ꞓl๴竣迃xKC/饉:\fl\"XTFᄄ蟭,芢<\/骡軺띜hꏘ\u001f銿<棔햳▨(궆*=乥b8\\媦䷀뫝}닶ꇭ(Kej䤑M": [{ + "1Ꮼ?>옿I╅C<ގ?ꊌ冉SV5A㢊㶆z-๎玶绢2F뵨@㉌뀌o嶔f9-庒茪珓뷳4": null, + ";lᰳ": "CbB+肻a䄷苝*/볳+/4fq=㰁h6瘉샴4铢Y骐.⌖@哼猎㦞+'gꋸ㒕ߤ㞑(䶒跲ti⑴a硂#No볔", + "t?/jE幸YHT셵⩎K!Eq糦ꗣv刴w\"l$ο:=6:移": { + "z]鑪醊嫗J-Xm銌翁絨c里됏炙Ep㣋鏣똼嚌䀓GP﹖cmf4鹭T䅿꣭姧␸wy6ꦶ;S&(}ᎧKxᾂQ|t뻳k\"d6\"|Ml췆hwLt꼼4$&8Պ褵婶鯀9": {"嵃닢ᒯ'd᧫䳳#NXe3-붋鸿ଢ떓%dK\u0013䲎ꖍYV.裸R⍉rR3蟛\\:젯:南ĺLʆ넕>|텩鴷矔ꋅⒹ{t孶㓑4_": [ + true, + null, + [ + false, + "l怨콈lᏒ", + { + "0w䲏嬧-:`䉅쉇漧\\܂yㄨb%㽄j7ᦶ涶<": 3.7899452730383747E-19, + "ꯛTẀq纤q嶏V⿣?\"g}ი艹(쥯B T騠I=仵및X": {"KX6颠+&ᅃ^f畒y[": { + "H?뱜^?꤂-⦲1a㋞&ꍃ精Ii᤾챪咽쬘唂쫷<땡劈훫놡o㥂\\ KⴙD秼F氮[{'좴:례晰Iq+I쭥_T綺砸GO煝䟪ᚪ`↹l羉q쐼D꽁ᜅ훦: vUV": true, + "u^yﳍ0㱓#[y뜌앸ꊬL㷩?蕶蘾⻍KӼ": -7931695755102841701, + "䤬轉車>\u001c鴵惋\"$쯃྆⇻n뽀G氠S坪]ಲꨍ捇Qxኻ椕駔\\9ࣼ﫻읜磡煮뺪ᶚ볝l㕆t+sζ": [[[ + true, + false, + [ + null, + 3363739578828074923, + true, + { + "\"鸣詩 볰㑵gL㯦῅춝旫}ED辗ﮈI쀤-ꧤ|㠦Z\"娑ᕸ4爏騍㣐\"]쳝Af]茛⬻싦o蚁k䢯䩐菽3廇喑ޅ": 4.5017999150704666E17, + "TYႇ7ʠ值4챳唤~Zo&ݛ": false, + "`塄J袛㭆끺㳀N㺣`꽐嶥KﯝSVᶔ∲퀠獾N딂X\"ᤏhNﬨvI": {"\u20bb㭘I䖵䰼?sw䂷쇪](泒f\"~;꼪Fԝsᝦ": {"p,'ꉂ軿=A蚶?bƉ㏵䅰諬'LYKL6B깯⋩겦뎙(ᜭ\u0006噣d꾆㗼Z;䄝䚔cd<情@䞂3苼㸲U{)<6&ꩻ钛\u001au〷N숨囖愙j=BXW욕^x芜堏Ῑ爂뛷꒻t✘Q\b": [[ + "籛&ଃ䩹.ꃩ㦔\\C颫#暪&!勹ꇶ놽攺J堬镙~軌C'꾖䣹㮅岃ᙴ鵣", + 4.317829988264744E15, + 6.013585322002147E-20, + false, + true, + null, + null, + -3.084633632357326E-20, + false, + null, + { + "\"짫愔昻 X\"藣j\"\"먁ཅѻ㘤㬯0晲DU꟒㸃d벀윒l䦾c੻*3": null, + "谈Wm陧阦咟ฯ歖擓N喴㋐銭rCCnVࢥ^♼Ⅾ젲씗刊S༝+_t赔\\b䚍뉨ꬫ6펛cL䊘᜼<\/澤pF懽&H": [ + null, + { + "W\"HDUuΌ퀟M'P4࿰H똆ⰱﮯ<\/凐蘲\"C鴫ﭒж}ꭩ쥾t5yd诪ﮡ퍉ⴰ@?氐醳rj4I6Qt": 6.9090159359219891E17, + "絛ﳛ⺂": {"諰P㗮聦`ZQ?ꫦh*റcb⧱}埌茥h{棩렛툽o3钛5鮁l7Q榛6_g)ὄ\u0013kj뤬^爖eO4Ⱈ槞鉨ͺ订%qX0T썗嫷$?\\\"봅늆'%": [ + -2.348150870600346E-19, + [[ + true, + -6619392047819511778, + false, + [[ + -1.2929189982356161E-20, + 1.7417192219309838E-19, + {"?嵲2࿐2\u0001啑㷳c縯": [ + null, + [ + false, + true, + 2578060295690793218, + { + "?\"殃呎#㑑F": true, + "}F炊_殛oU헢兔Ꝉ,赭9703.B数gTz3⏬": { + "5&t3,햓Mݸᵣ㴵;꣫䩍↳#@뫷䠅+W-ࣇzᓃ鿕ಔ梭?T䮑ꥬ旴]u뫵막bB讍:왳둛lEh=숾鱠p咐$짏#?g⹷ᗊv㷵.斈u頻\u0018-G.": "뽙m-ouࣤ஫牷\"`Ksꕞ筼3HlȨvC堈\"I]㖡玎r먞#'W賜鴇k'c룼髋䆿飉㗆xg巤9;芔cጐ/ax䊨♢큓r吓㸫೼䢗da᩾\"]屣`", + ":M딪<䢥喠\u0013㖅x9蕐㑂XO]f*Q呰瞊吭VP@9,㨣 D\\穎vˤƩs㜂-曱唅L걬/롬j㈹EB8g<\/섩o渀\"u0y&룣": ">氍緩L/䕑돯Ꟙ蕞^aB뒣+0jK⪄瑨痜LXK^힦1qK{淚t츔X:Vm{2r獁B뾄H첚7氥?쉟䨗ꠂv팳圎踁齀\\", + "D彤5㢷Gꪻ[lㄆ@὜⓰絳[ଃ獽쮹☒[*0ꑚ㜳": 9022717159376231865, + "ҖaV銣tW+$魿\u20c3亜~뫡ᙰ禿쨽㏡fṼzE/h": "5臐㋇Ჯ쮺? 昨탰Wム밎#'\"崲钅U?幫뺀⍾@4kh>騧\\0ҾEV=爐͌U捀%ꉼ 㮋<{j]{R>:gԩL\u001c瀈锌ﯲﳡꚒ'⫿E4暍㌗뵉X\"H᝜", + "ᱚגּ;s醒}犍SἿ㦣&{T$jkB\\\tḮ앾䤹o<避(tW": "vb⯽䴪䮢@|)", + "⥒퐁껉%惀뗌+녣迺顀q條g⚯i⤭룐M琹j̈́⽜A": -8385214638503106917, + "逨ꊶZ<\/W⫟솪㎮ᘇb?ꠔi\"H㧺x෷韒Xꫨฟ|]窽\u001a熑}Agn?Mᶖa9韲4$3Ỵ^=쏍煤ፐ돷2䣃%鷠/eQ9頸쥎", + 2398360204813891033, + false, + 3.2658897259932633E-19, + null, + "?ꚃ8Nn㞷幵d䲳䱲뀙ꪛQ瑓鎴]䩋-鰾捡䳡??掊", + false, + -1309779089385483661, + "ᦲxu_/yecR.6芏.ᜇ過 ~", + -5658779764160586501, + "쒌:曠=l썜䢜wk#s蕚\"互㮉m䉤~0듐䋙#G;h숄옥顇෤勹(C7㢅雚㐯L⠅VV簅<", + null, + -4.664877097240962E18, + -4.1931322262828017E18, + { + ",": { + "v㮟麑䄠뤵g{M띮.\u001bzt뢜뵡0Ǥ龍떟Ᾰ怷ϓRT@Lꀌ樂U㏠⾕e扉|bJg(뵒㠶唺~ꂿ(땉x⻫싉쁊;%0鎻V(o\f,N鏊%nk郼螺": -1.73631993428376141E18, + "쟧摑繮Q@Rᕾ㭚㾣4隅待㓎3蒟": [ + 4971487283312058201, + 8973067552274458613, + { + "`a揙ᣗ\u0015iBo¸": 4.3236479112537999E18, + "HW&퉡ぁ圍Y?瑡Qy훍q!帰敏s舠㫸zꚗaS歲v`G株巷Jp6킼 (귶鍔⾏⡈>M汐㞍ቴ꙲dv@i㳓ᇆ?黍": [ + null, + 4997607199327183467, + "E㻎蠫ᐾ高䙟蘬洼旾﫠텛㇛?'M$㣒蔸=A_亀绉앭rN帮", + null, + [{ + "Eᑞ)8餧A5u&㗾q?": [ + -1.969987519306507E-19, + null, + [ + 3.42437673373841E-20, + true, + "e걷M墁\"割P␛퍧厀R䱜3ﻴO퓫r﹉⹊", + [ + -8164221302779285367, + [ + true, + null, + "爘y^-?蘞Ⲽꪓa␅ꍨ}I", + 1.4645984996724427E-19, + [{ + "tY좗⧑mrzﺝ㿥ⴖ᥷j諅\u0000q賋譁Ꞅ⮱S\nࡣB/큃굪3Zɑ复o<\/;롋": null, + "彟h浠_|V4䦭Dᙣ♞u쿻=삮㍦\u001e哀鬌": [{"6횣楠,qʎꗇ鎆빙]㱭R굋鈌%栲j分僅ペ䇰w폦p蛃N溈ꡐꏀ?@(GI뉬$ﮄ9誁ꓚ2e甸ڋ[䁺,\u0011\u001cࢃ=\\+衪䷨ᯕ鬸K": [[ + "ㅩ拏鈩勥\u000etgWVXs陂規p狵w퓼{뮵_i\u0002ퟑႢ⬐d6鋫F~챿搟\u0096䚼1ۼ칥0꣯儏=鋷牋ⅈꍞ龐", + -7283717290969427831, + true, + [ + 4911644391234541055, + { + "I鈒첽P릜朸W徨觘-Hᎄ퐟⓺>8kr1{겵䍃〛ᬡ̨O귑o䝕'쿡鉕p5": "fv粖RN瞖蛐a?q꤄\u001d⸥}'ꣴ犿ꦼ?뤋?鵆쥴덋䡫s矷̄?ඣ/;괱絢oWfV<\/\u202cC,㖦0䑾%n賹g&T;|lj_欂N4w", + "짨䠗;䌕u i+r๏0": [{"9䥁\\఩8\"馇z䇔<\/ႡY3e狚쐡\"ุ6ﰆZ遖c\"Ll:ꮾ疣<\/᭙O◌납୕湞9⡳Und㫜\u0018^4pj1;䧐儂䗷ୗ>@e톬": { + "a⑂F鋻Q螰'<퇽Q贝瀧{ᘪ,cP&~䮃Z?gI彃": [ + -1.69158726118025933E18, + [ + "궂z簽㔛㮨瘥⤜䛖Gℤ逆Y⪾j08Sn昞ꘔ캻禀鴚P謦b{ꓮmN靐Mᥙ5\"睏2냑I\u0011.L&=?6ᄠ뻷X鸌t刑\"#z)o꫚n쳟줋", + null, + 7517598198523963704, + "ኑQp襟`uᩄr方]*F48ꔵn俺ሙ9뇒", + null, + null, + 6645782462773449868, + 1219168146640438184, + null, + { + ")ယ넌竀Sd䰾zq⫣⏌ʥ\u0010ΐ' |磪&p牢蔑mV蘸૰짬꺵;K": [ + -7.539062290108008E-20, + [ + true, + false, + null, + true, + 6574577753576444630, + [[ + 1.2760162530699766E-19, + [ + null, + [ + "顊\\憎zXB,", + [{ + "㇆{CVC9-MN㜋ઘR눽#{h@ퟨ!鼚׼XOvXS\u0017ᝣ=cS+梽៲綆16s덽휐y屬?ᇳG2ᴭ\u00054쫖y룇nKcW̭炦s/鰘ᬽ?J|퓀髣n勌\u0010홠P>j": false, + "箴": [ + false, + "鍞j\"ꮾ*엇칬瘫xṬ⭽쩁䃳\"-⋵?ᦽ댎Ĝ": true, + "Pg帯佃籛n㔠⭹࠳뷏≻࿟3㞱!-쒾!}쭪䃕!籿n涻J5ਲ਼yvy;Rኂ%ᔡጀ裃;M⣼)쵂쑈": 1.80447711803435366E18, + "ꈑC⡂ᑆ㤉壂뎃Xub<\/쀆༈憓ق쨐ק\\": [ + 7706977185172797197, + {"": {"K╥踮砆NWࡆFy韣7ä밥{|紒︧䃀榫rᩛꦡTSy잺iH8}ퟴ,M?Ʂ勺ᴹ@T@~꾂=I㙕뾰_涀쑜嫴曣8IY?ҿo줫fऒ}\\S\"ᦨ뵼#nDX": { + "♘k6?଱癫d68?㽚乳䬳-V顷\u0005蝕?\u0018䞊V{邾zじl]雏k臤~ൖH뒐iꢥ]g?.G碄懺䔛pR$䅒X觨l봜A刊8R梒',}u邩퉕?;91Ea䈈믁G⊶芔h袪&廣㺄j;㡏綽\u001bN頸쳘橆": -2272208444812560733, + "拑Wﵚj鵼駳Oࣿ)#㾅顂N傓纝y僱栜'Bꐍ-!KF*ꭇK¦?䈴^:啤wG逭w᧯": "xᣱmYe1ۏ@霄F$ě꧘푫O䤕퀐Pq52憬ꀜ兴㑗ᡚ?L鷝ퟐ뭐zJꑙ}╆ᅨJB]\"袌㺲u8䯆f", + "꿽၅㔂긱Ǧ?SI": -1669030251960539193, + "쇝ɨ`!葎>瞺瘡驷錶❤ﻮ酜=": -6961311505642101651, + "?f7♄꫄Jᡔ훮e읇퍾፣䭴KhखT;Qty}O\\|뫁IῒNe(5惁ꥶㆷY9ﮡ\\ oy⭖-䆩婁m#x봉>Y鈕E疣s驇↙ᙰm<": {"퉻:dꂁ&efᅫ쫢[\"돈늖꺙|Ô剐1͖-K:ʚ᭕/;쏖㷛]I痐职4gZ4⍜kเꛘZ⥺\\Bʫᇩ鄨魢弞&幟ᓮ2̊盜", + -9006004849098116748, + -3118404930403695681, + { + "_彃Y艘-\"Xx㤩㳷瑃?%2䐡鵛o귵옔夘v*탋职&㳈챗|O钧": [ + false, + "daꧺdᗹ羞쯧H㍤鄳頳<型孒ン냆㹀f4㹰\u000f|C*ሟ鰠(O<ꨭ峹ipຠ*y೧4VQ蔔hV淬{?ᵌEfrI_", + "j;ꗣ밷邍副]ᗓ", + -4299029053086432759, + -5610837526958786727, + [ + null, + [ + -1.3958390678662759E-19, + { + "lh좈T_믝Y\"伨\u001cꔌG爔겕ꫳ晚踍⿻읐T䯎]~e#฽燇\"5hٔ嶰`泯r;ᗜ쮪Q):/t筑,榄&5懶뎫狝(": [{ + "2ፁⓛ]r3C攟וּ9賵s⛔6'ஂ|\"ⵈ鶆䐹禝3\"痰ࢤ霏䵩옆䌀?栕r7O簂Isd?K᫜`^讶}z8?z얰T:X倫⨎ꑹ": -6731128077618251511, + "|︦僰~m漿햭\\Y1'Vvخ굇ቍ챢c趖": [null] + }], + "虌魿閆5⛔煊뎰㞤ᗴꥰF䮥蘦䂪樳-K᝷-(^\u20dd_": 2.11318679791770592E17 + } + ] + ] + ]}, + "묗E䀳㧯᳀逞GMc\b墹㓄끖Ơ&U??펌鑍 媋k))ᄊ": null, + "묥7콽벼諌J_DɯﮪM殴䣏,煚ྼ`Y:씧<\/⩫%yf䦀!1Ჶk춎Q米W∠WC跉鬽*ᛱi㴕L꘻ꀏ쓪\"_g鿄'#t⽙?,Wg㥖|D鑆e⥏쪸僬h鯔咼ඡ;4TK聎졠嫞" + } + ] + ] + } + ] + ] + ]}} + } + ]} + }, + "뿋뀾淣截䔲踀&XJ펖꙯^Xb訅ꫥgᬐ>棟S\"혧騾밫겁7-": "擹8C憎W\"쵮yR뢩浗絆䠣簿9䏈引Wcy䤶孖ꯥ;퐌]輩䍐3@{叝 뽸0ᡈ쵡Ⲇ\u001dL匁꧐2F~ݕ㪂@W^靽L襒ᦘ~沦zZ棸!꒲栬R" + } + ] + ], + "Z:덃൛5Iz찇䅄駠㭧蓡K1": "e8᧤좱U%?ⵇ䯿鿝\u0013縮R∱骒EO\u000fg?幤@֗퉙vU`", + "䐃쪈埽້=Ij,쭗쓇చ": false + }]}} + ] + } + ]} + } + ] + ] + ], + "咰긖VM]᝼6䓑쇎琺etDҌ?㞏ꩄ퇫밉gj8蠃\"⩐5䛹1ࣚ㵪": "ക蹊?⎲⧘⾚̀I#\"䈈⦞돷`wo窭戕෱휾䃼)앷嵃꾞稧,Ⴆ윧9S?೗EMk3Მ3+e{⹔Te驨7䵒?타Ulg悳o43" + } + ], + "zQᤚ纂땺6#ٽ﹧v￿#ࠫ휊冟蹧텈ꃊʆ?&a䥯De潝|쿓pt瓞㭻啹^盚2Ꝋf醪,얏T窧\\Di䕎谄nn父ꋊE": -2914269627845628872, + "䉩跐|㨻ᷢ㝉B{蓧瞸`I!℄욃힕#ೲᙾ竛ᔺCjk췒늕貭词\u0017署?W딚%(pꍁ⤼띳^=on뺲l䆼bzrﳨ[&j狸䠠=ᜑꦦ\u2061յnj=牲攑)M\\龏": false, + "뎕y絬᫡⥮Ϙᯑ㌔/NF*˓.,QEzvK!Iwz?|쥾\"ꩻL꼗Bꔧ賴緜s뉣隤茛>ロ?(?^`>冺飒=噸泥⺭Ᲊ婓鎔븜z^坷裮êⓅ໗jM7ﶕ找\\O": 1.376745434746303E-19 + }, + "䐛r滖w㏤,|Nዜ": false + } + ]], + "@꿙?薕尬 gd晆(띄5躕ﻫS蔺4)떒錸瓍?~": 1665108992286702624, + "w믍nᏠ=`঺ᅥC>'從됐槷䤝眷螄㎻揰扰XᅧC贽uჍ낟jKD03T!lDV쀉Ӊy뢖,袛!终캨G?鉮Q)⑗1쾅庅O4ꁉH7?d\u0010蠈줘월ސ粯Q!낇껉6텝|{": null, + "~˷jg쿤촖쉯y": -5.5527605669177098E18, + "펅Wᶺzꐆと푭e?4j仪열[D<鈑皶婆䵽ehS?袪;HꍨM뗎ば[(嗏M3q퍟g4y╸鰧茀[Bi盤~﫝唎鋆彺⦊q?B4쉓癚O洙킋툈䶯_?ퟲ": null + } + ] + ]] + ]], + "꟱Ԕ㍤7曁聯ಃ錐V䷰?v㪃૦~K\"$%请|ꇹn\"k䫛㏨鲨\u2023䄢\u0004[︊VJ?䶟ាꮈ䗱=깘U빩": -4863152493797013264 + } + ]}]} + ] + }}} + ], + "쏷쐲۹퉃~aE唙a챑,9㮹gLHd'䔏|킗㍞䎥&KZYT맵7䥺Nⱳ同莞鿧w\\༌疣n/+ꎥU\"封랾○ퟙAJᭌ?9䛝$?驔9讐짘魡T֯c藳`虉C읇쐦T" + } + ], + "谶개gTR￐>ၵ͚dt晑䉇陏滺}9㉸P漄": -3350307268584339381 + }] + ] + ] + ]] + ] + ], + "0y꟭馋X뱔瑇:䌚￐廿jg-懲鸭䷭垤㒬茭u賚찶ಽ+\\mT땱\u20821殑㐄J쩩䭛ꬿNS潔*d\\X,壠뒦e殟%LxG9:摸": 3737064585881894882, + "풵O^-⧧ⅶvѪ8廸鉵㈉ר↝Q㿴뺟EႳvNM:磇>w/៻唎뷭୥!냹D䯙i뵱貁C#⼉NH6`柴ʗ#\\!2䂗Ⱨf?諳.P덈-返I꘶6?8ꐘ": -8934657287877777844, + "溎-蘍寃i诖ര\"汵\"\ftl,?d⼡쾪⺋h匱[,෩I8MҧF{k瓿PA'橸ꩯ綷퉲翓": null + } + ] + ], + "ោ係؁<元": 1.7926963090826924E-18 + }}] + } + ] + ]]}] + }] + ] + ] + ] + ], + "ጩV<\"ڸsOᤘ": 2.0527167903723048E-19 + }] + ]} + ] + ]], + "∳㙰3젴p᧗䱙?`yZA8Ez0,^ᙛ4_0븢\u001ft:~䎼s.bb룦明yNP8弆C偯;⪾짍'蕴뮛": -6976654157771105701, + "큵ꦀ\\㇑:nv+뒤燻䀪ﴣ﷍9ᚈ኷K㚊誦撪䚛,ꮪxሲ쳊\u0005HSf?asg昱dqꬌVꙇ㼺'k*'㈈": -5.937042203633044E-20 + } + ] + }], + "?}\u20e0],s嶳菋@#2u쒴sQS䩗=ꥮ;烌,|ꘔ䘆": "ᅩ영N璠kZ먕眻?2ቲ芋眑D륟渂⸑ﴃIRE]啗`K'" + }}, + "쨀jmV賂ﰊ姐䂦玞㬙ᏪM᪟Վ씜~`uOn*ॠ8\u000ef6??\\@/?9見d筜ﳋB|S䝬葫㽁o": true + }, + "즛ꄤ酳艚␂㺘봿㎨iG৕ࡿ?1\"䘓您\u001fSኝ⺿溏zៀ뻤B\u0019?윐a䳵᭱䉺膷d:<\/": 3935553551038864272 + } + ] + ]} + ]] + ]] + ]} + } + ] + } + ]]}}, + "᥺3h↛!ꋰy\"攜(ெl䪕oUkc1A㘞ᡲ촾ᣫ<\/䒌E㛝潨i{v?W౾H\\RჅpz蝬R脾;v:碽✘↯삞鷱o㸧瑠jcmK7㶧뾥찲n": true, + "ⶸ?x䊺⬝-䰅≁!e쩆2ꎿ准G踌XXᩯ1߁}0?.헀Z馟;稄\baDꟹ{-寪⚈ꉷ鮸_L7ƽᾚ<\u001bጨA䧆송뇵⨔\\礍뗔d设룱㶉cq{HyぱR㥽吢ſtp": -7985372423148569301, + "緫#콮IB6<\/=5Eh礹\t8럭@饹韠r㰛斣$甝LV췐a갵'请o0g:^": "䔨(.", + "띳℡圤pン௄ĝ倧訜B쁟G䙔\"Sb⓮;$$▏S1J뢙SF|赡g*\"Vu䲌y": "䪈&틐),\\kT鬜1풥;뷴'Zေ䩹@J鞽NぼM?坥eWb6榀ƩZڮ淽⺞삳煳xჿ絯8eⶍ羷V}ჿ쎱䄫R뱃9Z>'\u20f1ⓕ䏜齮" + } + ] + ]]] + }} + } + ] + ]}, + "펮b.h粔폯2npX詫g錰鷇㇒<쐙S値bBi@?镬矉`剔}c2壧ଭfhY깨R()痩⺃a\\⍔?M&ﯟ<劜꺄멊ᄟA\"_=": null + }, + "~潹Rqn榢㆓aR鬨侅?䜑亡V_翅㭔(䓷w劸ၳDp䀅<\/ﰎ鶊m䵱팱긽ꆘ긓准D3掱;o:_ќ)껚콥8곤d矦8nP倥ꃸI": null, + "뾎/Q㣩㫸벯➡㠦◕挮a鶧⋓偼\u00001뱓fm覞n?㛅\"": 2.8515592202045408E17 + }], + ",": -5426918750465854828, + "2櫫@0柡g䢻/gꆑ6演&D稒肩Y?艘/놘p{f투`飷ᒉ챻돎<늛䘍ﴡ줰쫄": false, + "8(鸑嵀⵹ퟡ<9㣎Tߗ┘d슒ل蘯&㠦뮮eࠍk砝g 엻": false, + "d-\u208b?0ﳮ嵙'(J`蔿d^踅⤔榥\\J⵲v7": 6.8002426206715341E17, + "ཎ耰큓ꐕ㱷\u0013y=詽I\"盈xm{0쾽倻䉚ષso#鰑/8㸴짯%ꀄ떸b츟*\\鲷礬ZQ兩?np㋄椂榨kc᡹醅3": false, + "싊j20": false + }]] + ]], + "俛\u0017n緽Tu뫉蜍鼟烬.ꭠIⰓ\"Ἀ᜾uC쎆J@古%ꛍm뻨ᾀ画蛐휃T:錖㑸ዚ9죡$": true + } + ] + ], + "㍵⇘ꦖ辈s}㱮慀밒s`\"㞟j:`i픻Z섫^諎0Ok{켿歁෣胰a2﨤[탳뚬쎼嫭뉮m": 409440660915023105, + "w墄#*ᢄ峠밮jLa`ㆪ꺊漓Lで끎!Agk'ꁛ뢃㯐岬D#㒦": false, + "ଦPGI䕺L몥罭ꃑ궩﮶#⮈ᢓӢ䚬p7웼臧%~S菠␌힀6&t䳙y㪘냏\\*;鉏ᅧ鿵'嗕pa\"oL쇿꬈Cg": "㶽1灸D⟸䴅ᆤ뉎﷛渤csx 䝔цꬃ锚捬?ຽ+x~꘩uI࡞\u0007栲5呚ẓem?袝\")=㥴䨃pac!/揎Y", + "ᷱo\\||뎂몷r篙|#X䦜I#딌媸픕叞RD斳X4t⯩夬=[뭲r=绥jh뷱츝⪘%]⚋܈㖴スH텹m(WO曝劉0~K3c柢Ր㏉着逳~": false, + "煽_qb[첑\\륌wE❽ZtCNﭝ+餌ᕜOꛭ": "{ﳾ쉌&s惧ᭁⵆ3䢫;䨞팑꒪흘褀࢖Q䠿V5뭀䎂澻%받u5텸oA⮥U㎦;B䳌wz䕙$ឿ\\௅婺돵⪾퐆\\`Kyौꋟ._\u0006L챯l뇠Hi䧈偒5", + "艊佁ࣃ롇䱠爬!*;⨣捎慓q靓|儑ᨋL+迥=6㒺딉6弄3辅J-㕎뛄듘SG㆛(\noAzQꝱ䰩X*ぢO퀌%펠낌mo틮a^<\/F&_눊ᾉ㨦ы4\"8H": 2974648459619059400, + "鬙@뎣䫳ၮ끡?){y?5K;TA*k溱䫜J汃ꂯ싔썍\u001dA}룖(<\/^,": false, + "몏@QꋦFꊩᒐ뎶lXl垨4^郣|ꮇ;䝴ᝓ}쵲z珖": null + } + ]]]], + ":_=닧弗D䙋暨鏛. 㱻붘䂍J儒&ZK/녩䪜r囁⽯D喠죥7⹌䪥c\u001a\u2076￞妈朹oLk菮F౟覛쐧㮏7T;}蛙2{9\"崓bB<\/⡷룀;즮鿹)丒툃୤뷠5W⊢嶜(fb뭳갣": "E{响1WM" + }}, + "䘨tjJ驳豨?y輊M*᳑梵瞻઻ofQG瑮e": 2.222802939724948E-19, + "䮴=❑➶T෋w䞜\"垦ꃼUt\u001dx;B$뵣䙶E↌艣ᡥ!᧟;䱀[䔯k쬃`੍8饙른熏'2_'袻tGf蒭J땟as꯳╖&啒zWࡇᒫYSᏬ\u0014ℑ첥鈤|cG~Pᓮ\">\"": "ႆl\f7V儊㦬nHꄬꨧC{쐢~C⮃⛓嶦vꄎ1w鰠嘩뿠魄&\"_qMⵖ釔녮ꝇ 㝚{糍J哋 cv?-jkﻯྌ鹑L舟r", + "龧葆yB✱H盋夔ﶉ?n*0(": "ꧣኆ㢓氥qZZ酒ຜ)鮢樛)X䣆gTSґG텞k.J圬疝롫쯭z L:\\ྤ@w炋塜쿖ᾳy뢀䶃뱝N䥨㚔勇겁#p", + "도畎Q娡\"@S/뼋:䵏!P衅촚fVHQs✜ᐫi㻑殡B䜇%믚k*U#濨낄~": "ꍟዕ쳸ꍈ敋&l妏\u0005憡멗瘌uPgᅪm<\/To쯬锩h뒓k" + } + ] + }], + "墥홞r绚<\/⸹ⰃB}<躅\\Y;๑@䔸>韫䜲뱀X뗩鿥쩗SI%ﴞ㳕䛇?<\/\u00018x\\&侂9鋙a[LR㋭W胕)⡿8㞙0JF,}?허d1cDMᐃ␛鄝ⱕ%X)!XQ": "ⳍꗳ=橇a;3t⦾꼑仈ူaᚯ⯋ꕃAs鴷N⍕_䎃ꙎAz\u0016䯷\\<࿫>8q{}キ?ᣰ}'0ᴕ펓B┦lF#趤厃T?㕊#撹圂䆲" + }, + "܋닐龫論c웑": false, + "ㇿ/q\"6-co髨휝C큦#\u001b4~?3䐹E삇<<": 7.600917488140322E-20, + "䁝E6?㣖ꃁ间t祗*鑠{ḣV(浾h逇큞=W?ૉ?nꇽ8ꅉຉj으쮺@Ꚅ㰤u]Oyr": "v≁᫸_*όAඤԆl)ۓᦇQ}폠z༏q滚", + "ソ᥊/넺I": true + }]] + ] + ] + ] + ]] + }, + "䭑Ik攑\u0002QV烄:芩.麑㟴㘨≕": true, + "坄꿕C쇻풉~崍%碼\\8\"䬦꣙": null, + "欌L圬䅘Y8c(♺2?ON}o椳s宥2䉀eJ%闹r冁O^K諭%凞⺉⡻,掜?$ꥉ?略焕찳㯊艼誜4?\"﯎<゛XፈINT:詓 +": -1.0750456770694562E-19, + "獒àc뜭싼ﺳ뎤K`]p隨LtE": null, + "甙8䵊神EIꩤ鐯ᢀ,ﵮU䝑u疒ử驺䚿≚ഋ梶秓F`覤譐#짾蔀묊4<媍쬦靪_Yzgcࡶ4k紥`kc[Lﮗ簐*I瀑[⾰L殽鑥_mGȠ<\/|囹灠g桰iri": true, + "챓ꖙꟻ좝菇ou,嗠0\\jK핻뜠qwQ?ഩ㼕3Y彦b\u009bJ榶N棨f?됦鏖綃6鳵M[OE봨u햏.Ꮁ癜蟳뽲ꩌ뻾rM豈R嗀羫 uDꎚ%": null + }, + "V傜2<": 7175127699521359521 + }], + "铫aG切<\/\"ী⊆e<^g࢛)D顝nאַ饼\u008c猪繩嵿ﱚCꡬ㻊g엺A엦\u000f暿_f꿤볝㦕桦`蒦䎔j甬%岝rj 糏": "䚢偎눴Au<4箞7礦Iﱔ坠eȧ䪸u䵁p|逹$嗫쨘ꖾ﷐!胠z寓팢^㨔|u8Nሇe텔ꅦ抷]،鹎㳁#༔繁 ", + "낂乕ꃻ볨ϱ-ꇋ㖍fs⿫)zꜦ/K?솞♞ꑌ宭hJ᤭瑥Fu": false, + "쟰ぜ魛G\u0003u?`㾕ℾ㣭5螠烶這趩ꖢ:@咕ꐶx뒘느m䰨b痃렐0鳊喵熬딃$摉_~7*ⱦ녯1錾GKhJ惎秴6'H妈Tᧅ窹㺒疄矤铟wላ": null, + "쯆q4!3錕㲏ⵆ㇛꘷Z瑩뭆\\◪NH\u001d\\㽰U~㯶<\"쑣낞3ᵤ'峉eꢬ;鬹o꣒木X*長PXᘱu\"䠹n惞": null, + "ᅸ祊\"&ꥴCjࢼ﴿?䡉`U效5殼㮞V昽ꏪ#ﺸ\\&t6x꠹盥꣰a[\u001aꪍSpe鎿蠹": -1.1564713893659811E-19 + } + ]] + ] + ] + ], + "羵䥳H,6ⱎ겾|@t\"#햊1|稃 섭)띜=뻔ꡜ???櫎~*ῡ꫌/繣ﻠq": null + } + ]} + ]}, + "츤": false + }}, + "s": 3.7339341963399598E18 + } + ], + "N,I?1+㢓|ࣱ嶃쩥V2\u0012(4EE虪朶$|w颇v步": "~읢~_,Mzr㐫YB溓E淚\"ⅹ䈔ᏺ抙 b,nt5V㐒J檶ꏨ⻔?", + "Q껑ꡡ}$넎qH煔惍/ez^!ẳF댙䝌馻剁8": "梲;yt钰$i冄}AL%a j뜐奷걳뚾d꿽*ሬuDY3?뮟鼯뮟w㍪틱V", + "o{Q/K O胟㍏zUdꀐm&⨺J舕⾏魸訟㌥[T籨櫉唐킝 aṭ뱫촙莛>碶覆⧬짙쭰ׯdAiH໥벤퐥_恸[ 0e:죃TC弼荎뵁DA:w唵ꣁ": null, + "὏樎䵮軧|?౗aWH쩃1 ꅭsu": null + } + ] + }, + "勂\\&m鰈J釮=Ⲽ鳋+䂡郑": null, + "殣b綊倶5㥗惢⳷萢ᑀ䬄镧M^ﱴ3⣢翣n櫻1㨵}ኯ뗙顖Z.Q➷ꮨ뗇\u0004": "ꔙ䁼>n^[GीA䨟AM琢ᒊS쨲w?d㶣젊嘶纝麓+愣a%気ྞSc됓ᔘ:8bM7Xd8㶑臌]Ꙥ0ꐭ쒙䫣挵C薽Dfⵃ떼᷸", + "?紡.셪_෨j\u0013Ox┠$Xᶨ-ᅇo薹-}軫;y毝㪜K㣁?.EV쮱4둽⛻䤜'2盡\u001f60(|e쐰㼎ᦀ㒧-$l@ﻑ坳\u0003䭱响巗WFo5c㧆T턁Y맸♤(": -2.50917882560589088E17 + }} + ], + "侸\\릩.᳠뎠狣살cs项䭩畳H1s瀉븇19?.w骴崖㤊h痠볭㞳㞳䁮Ql怠㦵": "@䟴-=7f", + "鹟1x௢+d ;vi䭴FSDS\u0004hꎹ㚍?⒍⦏ў6u,扩@됷Su)Pag휛TᒗV痩!瞏釀ꖞ蘥&ೞ蘐ꭰꞇᝎ": "ah懱Ժ&\u20f7䵅♎඀䞧鿪굛ౕ湚粎蚵ᯋ幌YOE)५襦㊝Y*^\"R+ඈ咷蝶9ꥂ榨艦멎헦閝돶v좛咊E)K㓷ྭr", + "搆q쮦4綱켙셁.f4<\/g<籽늷?#蚴픘:fF\u00051㹉뀭.ᰖ풎f֦Hv蔎㧤.!䭽=鞽]음H:?\"-4": 8.740133984938656E-20 + }]} + } + ], + "tVKn딩꘥⊾蹓᤹{\u0003lR꼽ᄲQFᅏ傅ﱋ猢⤊ᔁ,E㓒秤nTතv`♛I\u0000]꫔ṞD\"麵c踝杰X&濿또꣹깳౥葂鿎\\aꡨ?": 3900062609292104525 + } + ], + "ਉ샒⊩Lu@S䧰^g": -1.1487677090371648E18, + "⎢k⑊꬗yᏫ7^err糎Dt\u000bJ礯확ㆍ沑サꋽe赔㝢^J\u0004笲㿋idra剰-᪉C錇/Ĝ䂾ညS지?~콮gR敉⬹'䧭": 1901472137232418266, + "灗k䶥:?촽贍쓉꓈㒸g獘[뵎\\胕?\u0014_榙p.j稶,$`糉妋0>Fᡰly㘽$?": "]ꙛO赎&#㠃돱剳\"<◆>0誉齐_|z|裵씪>ᐌ㼍\"Z[琕}O?G뚇諦cs⠜撺5cu痑U圲\u001c?鴴計l춥/╓哼䄗茏ꮅ뫈댽A돌롖뤫V窗讬sHd&\nOi;_u" + } + ], + "Uﺗ\\Y\\梷䄬~\u0002": null, + "k\"Y磓ᗔ휎@U冈<\/w컑)[": false, + "曏J蝷⌻덦\u001f㙳s꥓⍟邫P늮쥄c∬ྡྷ舆렮칤Z趣5콡넛A쳨\\뀙骫(棻.*&輛LiIfi{@EA婳KᬰTXT": -4.3088230431977587E17 + }]} + ] + ], + "곃㲧<\/dఓꂟs其ࡧ&N葶=?c㠤Ჴ'횠숄臼#\u001a~": false + } + ] + ]}] + }] + }} + ], + "2f`⽰E쵟>J笂裭!〛觬囀ۺ쟰#桊l鹛ⲋ|RA_Vx፭gE됓h﵀mfỐ|?juTU档[d⢼⺻p濚7E峿": 5613688852456817133 + }, + "濘끶g忮7㏵殬W팕Q曁 뫰)惃廊5%-蹚zYZ樭ﴷQ锘쯤崫gg": true, + "絥ᇑ⦏쒓븣爚H.㗊߄o蘵貆ꂚ(쎔O᥉ﮓ]姨Wꁓ!RMA|o퉢THx轮7M껁U즨'i뾘舯o": "跥f꜃?" + }} + ], + "鷰鹮K-9k;ﰰ?_ݦѷ-ꅣ䩨Zꥱ\"mꠟ屎/콑Y╘2&鸞脇㏢ꀇ࠺ⰼ拾喭틮L꽩bt俸墶 [l/웄\"꾦\u20d3iও-&+\u000fQ+໱뵞": -1.296494662286671E-19 + }, + "HX੹/⨇୕붷Uﮘ旧\\쾜͔3l鄈磣糂̖䟎Eᐳw橖b῀_딕hu葰窳闹вU颵|染H죶.fP䗮:j䫢\\b뎖i燕ꜚG⮠W-≚뉗l趕": "ଊ칭Oa᡺$IV㷧L\u0019脴셀붿餲햪$迳向쐯켂PqfT\" ?I屉鴼쿕@硙z^鏕㊵M}㚛T젣쓌-W⩐-g%⺵<뮱~빅╴瑿浂脬\u0005왦燲4Ⴭb|D堧 <\/oEQh", + "䘶#㥘੐캔f巋ἡAJ䢚쭈ࣨ뫒*mᇊK,ࣺAꑱ\u000bR<\/A\"1a6鵌㯀bh곿w(\"$ꘁ*rಐ趣.d࿩k/抶면䒎9W⊃9": "漩b挋Sw藎\u0000", + "畀e㨼mK꙼HglKb,\"'䤜": null + }]}] + ] + ] + }] + ]} + ] + ]} + ], + "歙>駿ꣂ숰Q`J΋方樛(d鱾뼣(뫖턭\u20f9lচ9歌8o]8윶l얶?镖G摄탗6폋폵+g:䱫홊<멀뀿/س|ꭺs걐跶稚W々c㫣⎖": "㣮蔊깚Cꓔ舊|XRf遻㆚︆'쾉췝\\&言", + "殭\"cށɨꝙ䞘:嬮e潽Y펪㳅/\"O@ࠗ겴]췖YǞ(t>R\"N?梳LD恭=n氯T豰2R諸#N}*灧4}㶊G䍣b얚": null, + "襞<\/啧 B|싞W瓇)6簭鼡艆lN쩝`|펭佡\\間邝[z릶&쭟愱ꅅ\\T᰽1鯯偐栈4̸s윜R7⒝/똽?치X": "⏊躖Cﱰ2Qẫ脐&இ?%냝悊", + ",鰧偵셣싹xᎹ힨᯳EṬH㹖9": -4604276727380542356 + } + } + ]]]], + "웺㚑xs}q䭵䪠馯8?LB犯zK'os䚛HZ\"L?셎s^㿧㴘Cv2": null + }] + ] + ] + ], + "Kd2Kv+|z": 7367845130646124107, + "ᦂⶨ?ᝢ 祂些ഷ牢㋇操\"腭䙾㖪\\(y4cE뽺ㆷ쫺ᔖ%zfۻ$ў1柦,㶢9r漢": -3.133230960444846E-20, + "琘M焀q%㢟f鸯O⣏蓑맕鯊$O噷|)z褫^㢦⠮ꚯ꫞`毕1qꢚ{ĭ䎀বώT\"뱘3G൴?^^of": null + } + ], + "a8V᯺?:ﺃ/8ꉿBq|9啓댚;*i2": null, + "cpT瀇H珰Ừpೃi鎪Rr␣숬-鹸ҩ䠚z脚цGoN8入y%趌I┽2ឪЀiJNcN)槣/▟6S숆牟\"箑X僛G殱娇葱T%杻:J諹昰qV쨰": 8331037591040855245 + }], + "G5ᩜ䄗巢껳": true + } + }, + "Ồ巢ゕ@_譙A`碫鄐㡥砄㠓(^K": "?܃B혢▦@犑ὺD~T⧁|醁;o=J牌9냚⢽㨘{4觍蚔9#$∺\u0016p囅\\3Xk阖⪚\"UzA穕롬✎➁㭒춺C㣌ဉ\"2瓑员ᅽꝶ뫍}꽚ꞇ鶂舟彺]ꍽJC蝧銉", + "␆Ě膝\"b-퉐ACR言J謈53~V튥x䜢?ꃽɄY뮩ꚜ": "K/↾e萃}]Bs⾿q룅鷦-膋?m+死^魊镲6", + "粡霦c枋AHퟁo礼Ke?qWcA趸㡔ꂏ?\u000e춂8iতᦜ婪\u0015㢼nﵿꍻ!ᐴ関\u001d5j㨻gfῩUK5Ju丝tかTI'?㓏t>⼟o a>i}ᰗ;뤕ܝ": false, + "ꄮ匴껢ꂰ涽+䜨B蛹H䛓-k蕞fu7kL谖,'涃V~챳逋穞cT\"vQ쓕ObaCRQ㓡Ⲯ?轭⫦輢墳?vA餽=h䮇킵n폲퉅喙?\"'1疬V嬗Qd灗'Lự": "6v!s믁㭟㣯獃!磸餠ቂh0C뿯봗F鷭gꖶ~コkK<ᦈTt\\跓w㭣횋钘ᆹ듡䑚W䟾X'ꅔ4FL勉Vܴ邨y)2'〚쭉⽵-鞣E,Q.?块", + "?(˧쩯@崟吋歄K": null + }, + "Gc럃녧>?2DYI鴿\\륨)澔0ᔬlx'觔7젘⤡縷螩%Sv׫묈/]↱&S h\u0006歋ᑛxi̘}ひY蔯_醨鯘煑橾8?䵎쨋z儬ꁏ*@츾:": null + } + } + } + ] + ] + ]} + }, + "HO츧G": 3.694949578823609E17, + "QC\u0012(翻曇Tf㷟bGBJ옉53\\嚇ᛎD/\u001b夾၉4\"핀@祎)쫆yD\"i먎Vn㿿V1W᨝䶀": -6150931500380982286, + "Z㓮P翸鍱鉼K䋞꘺튿⭁Y": -7704503411315138850, + "]모开ꬖP븣c霤<[3aΠ\"黁䖖䰑뮋ꤦ秽∼㑷冹T+YUt\"싳F↭䖏&鋌": -2.7231911483181824E18, + "tꎖ": -4.9517948741799555E-19, + "䋘즊.⬅IꬃۣQ챢ꄑ黐|f?C⾺|兕읯sC鬸섾整腨솷V": "旆柩l쪦sᖸMy㦅울썉瘗㎜檵9ꍂ駓ૉᚿ/u3씅徐拉[Z䞸ࡗ1ꆱ&Q풘?ǂ8\u0011BCDY2볨;鸏": null, + "幫 n煥s쁇펇 왊-$C\"衝:\u0014㣯舼.3뙗Yl⋇\"K迎멎[꽵s}9鉳UK8쐥\"掄㹖h㙈!얄સ?Ꜳ봺R伕UTD媚I䜘W鏨蔮": -4.150842714188901E-17, + "ﺯ^㄄\b죵@fྉkf颡팋Ꞧ{/Pm0V둳⻿/落韒ꊔᚬ@5螺G\\咸a谆⊪ቧ慷绖?财(鷇u錝F=r၍橢ឳn:^iᴵtD볠覅N赴": null + }] + }] + } + ] + ]} + ]}, + "謯?w厓奰T李헗聝ឍ貖o⪇弒L!캶$ᆅ": -4299324168507841322, + "뺊奉_垐浸延몏孄Z舰2i$q붿좾껇d▵餏\"v暜Ҭ섁m￴g>": -1.60911932510533427E18 + } + ] + } + ] + ]], + "퉝꺔㠦楶Pꅱ": 7517896876489142899, + "": false + } + ]}, + "是u&I狻餼|谖j\"7c됮sסּ-踳鉷`䣷쉄_A艣鳞凃*m⯾☦椿q㎭N溔铉tlㆈ^": 1.93547720203604352E18, + "kⲨ\\%vr#\u000bⒺY\\t<\/3﬌R訤='﹠8蝤Ꞵ렴曔r": false + } + ]}, + "阨{c?C\u001d~K?鎌Ԭ8烫#뙣P초遗t㭱E­돒䆺}甗[R*1!\\~h㕅᰺@<9JꏏષI䳖栭6綘걹ᅩM\"▯是∔v鬽顭⋊譬": "운ﶁK敂(欖C취پ℄爦賾" + } + }} + }], + "鷨赼鸙+\\䭣t圙ڹx᜾ČN<\/踘\"S_맶a鷺漇T彚⎲i㈥LT-xA캔$\u001cUH=a0츺l릦": "溣㣂0濕=鉵氬駘>Pꌢpb솇쬤h힊줎獪㪬CrQ矠a&脍꼬爼M茴/΅\u0017弝轼y#Ꞡc6둴=?R崏뷠麖w?" + }, + "閕ᘜ]CT)䵞l9z'xZF{:ؐI/躅匽졁:䟇AGF૸\u001cퟗ9)駬慟ꡒꆒRS״툋A<>\u0010\"ꂔ炃7g덚E৏bꅰ輤]o㱏_뷕ܘ暂\"u": "芢+U^+㢩^鱆8*1鈶鮀\u0002뺰9⬳ꪮlL䃣괟,G8\u20a8DF㉪錖0ㄤ瓶8Nଷd?眡GLc陓\\_죌V쁰ल二?c띦捱 \u0019JC\u0011b⤉zẒT볕\"绣蘨뚋cꡉkI\u001e鳴", + "ꃣI'{6u^㡃#཰Kq4逹y൒䧠䵮!㱙/n??{L풓ZET㙠퍿X2᩟綳跠葿㚙w཮x캽扳B唕S|尾}촕%N?o䪨": null, + "ⰴFjෟ셈[\u0018辷px?椯\\1<ﲻ栘ᣁ봢憠뉴p": -5263694954586507640 + } + ] + ]] + ]} + ]}] + ] + ], + "?#癘82禩鋆ꊝty?&": -1.9419029518535086E-19 + } + ] + ] + ]} + ] + ] + ], + "훊榲.|῕戄&.㚏Zꛦ2\"䢥ሆ⤢fV_摕婔?≍Fji冀탆꜕i㏬_ẑKᅢ꫄蔻XWc|饡Siẘ^㲦?羡2ぴ1縁ᙅ?쐉Ou": false + }]] + ]}}}, + "慂뗄卓蓔ᐓ匐嚖/颹蘯/翻ㆼL?뇊,텵<\\獷ごCボ": null + }, + "p溉ᑟi짣z:䒤棇r^٫%G9缑r砌롧.물农g?0׼ሩ4ƸO㣥㯄쩞ጩ": null, + "껎繥YxK\"F젷쨹뤤1wq轫o?鱑뜀瘊?뎃h灑\\ꛣ}K峐^ኖ⤐林ꉓhy": null + } + ], + "᱀n肓ㄛ\"堻2>m殮'1橌%Ꞵ군=Ӳ鯨9耛<\/n據0u彘8㬇៩f᏿诙]嚊": "䋯쪦S럶匏ㅛ#)O`ሀX_鐪渲⛀㨻宅闩➈ꢙஶDR⪍" + }, + "tA썓龇 ⋥bj왎录r땽✒롰;羋^\\?툳*┎?썀ma䵳넅U䳆૘〹䆀LQ0\b疀U~u$M}(鵸g⳾i抦뛹?䤈땚검.鹆?ꩡtⶥGĒ;!ቹHS峻B츪켏f5≺": 2366175040075384032, + "전pJjleb]ួ": -7.5418493141528422E18, + "n.鎖ጲ\n?,$䪘": true + }, + "欈Ar㉣螵᪚茩?O)": null + }, + "쫸M#x}D秱欐K=侫们丐.KꕾxẠ\u001e㿯䣛F܍캗qq8꟞ṢFD훎⵳簕꭛^鳜\u205c٫~⑟~冫ऊ2쫰<\/戲윱o<\"": true + }, + "㷝聥/T뱂\u0010锕|内䞇x侁≦㭖:M?iM᣿IJe煜dG࣯尃⚩gPt*辂.{磼럾䝪@a\\袛?}ᓺB珼": true + } + } + ]]}]}}, + "tn\"6ꫤ샾䄄;銞^%VBPwu묪`Y僑N.↺Ws?3C⤻9唩S䠮ᐴm;sᇷ냞඘B/;툥B?lB∤)G+O9m裢0kC햪䪤": -4.5941249382502277E18, + "ᚔt'\\愫?鵀@\\びꂕP큠<<]煹G-b!S?\nꖽ鼫,ݛ&頺y踦?E揆릱H}햧캡b@手.p탻>췽㣬ꒅ`qe佭P>ᓂ&?u}毚ᜉ蟶頳졪ᎏzl2wO": -2.53561440423275936E17 + }]} + } + ] + ]], + "潈촒⿂叡": 5495738871964062986 + } + ]] + } + ] + ]} + ]] + ]] + ]} + ] + ]}, + "ႁq킍蓅R`謈蟐ᦏ儂槐僻ﹶ9婌櫞釈~\"%匹躾ɢ뤥>࢟瀴愅?殕节/냔O✬H鲽엢?ᮈੁ⋧d␽㫐zCe*": 2.15062231586689536E17, + "㶵Ui曚珰鋪ᾼ臧P{䍏䷪쨑̟A뼿T渠誈䏚D1!잶<\/㡍7?)2l≣穷᛾稝{:;㡹nemיּ訊`G": null, + "䀕\"飕辭p圁f#뫆䶷뛮;⛴ᩍ3灚덏ᰝ쎓⦷詵%᜖Մfs⇫(\u001e~P|ﭗCⲾផv湟W첋(텪બT<บSꏉ੗⋲X婵i ӵ⇮?L䬇|ꈏ?졸": 1.548341247351782E-19 + } + ] + }, + "t;:N\u0015q鐦Rt缆{ꮐC?஛㷱敪\\+鲊㉫㓪몗릙竏(氵kYS": "XᰂT?൮ô", + "碕飦幑|+ 㚦鏶`镥ꁩ B<\/加륙": -4314053432419755959, + "秌孳(p!G?V傫%8ሽ8w;5鲗㦙LI檸\u2098": "zG N볞䆭鎍흘\\ONK3횙<\/樚立圌Q튅k쩎Ff쁋aׂJK銆ઘ즐狩6༥✙䩜篥CzP(聻駇HHퟲ讃%,ά{렍p而刲vy䦅ክ^톺M楒鍢㹳]Mdg2>䤉洞", + "踛M젧>忔芿㌜Zk": 2215369545966507819, + "씐A`$槭頰퍻^U覒\bG毲aᣴU;8!팲f꜇E⸃_卵{嫏羃X쀳C7뗮m(嚼u N܁谟D劯9]#": true, + "ﻩ!뵸-筚P᭛}ἰ履lPh?౮ⶹꆛ穉뎃g萑㑓溢CX뾇G㖬A錟]RKaꄘ]Yo+@䘁's섎襠$^홰}F": null + }, + "粘ꪒ4HXᕘ蹵.$區\r\u001d묁77pPc^y笲Q<\/ꖶ 訍䃍ᨕG?*": 1.73773035935040224E17 + }, + "婅拳?bkU;#D矠❴vVN쩆t㜷A풃갮娪a%鮏絪3dAv룒#tm쑬⌛qYwc4|L8KZ;xU⓭㳔밆拓EZ7襨eD|隰ऌ䧼u9Ԣ+]贴P荿": 2.9628516456987075E18 + }]}}] + ]} + }} + ]}] + ], + "|g翉F*湹̶\u0005⏐1脉̀eI쩓ᖂ㫱0碞l䴨ꑅ㵽7AtἈ턧yq䳥塑:z:遀ᄐX눔擉)`N3昛oQ셖y-ڨ⾶恢ꈵq^<\/": null, + "菹\\랓G^璬x৴뭸ゆUS겧﮷Bꮤ ┉銜᯻0%N7}~f洋坄Xꔼ<\/4妟Vꄟ9:౟곡t킅冩䧉笭裟炂4봋ⱳ叺怊t+怯涗\"0㖈Hq": false, + "졬믟'ﺇফ圪쓬멤m邸QLব䗁愍4jvs翙 ྍ꧀艳H-|": null, + "컮襱⣱뗠 R毪/鹙꾀%헳8&": -5770986448525107020 + } + ], + "B䔚bꐻ뙏姓展槰T-똌鷺tc灿᫽^㓟䏀o3o$꘭趙萬I顩)뇭Ἑ䓝\f@{ᣨ`x3蔛": null + } + ] + ] + }], + "⦖扚vWꃱ꥙㾠壢輓{-⎳鹷贏璿䜑bG倛⋐磎c皇皩7a~ﳫU╣Q࠭ꎉS摅姽OW.홌ೞ.": null, + "蚪eVlH献r}ᮏ믠ﰩꔄ@瑄ⲱ": null, + "퀭$JWoꩢg역쁍䖔㑺h&ୢtXX愰㱇?㾫I_6 OaB瑈q裿": null, + "꽦ﲼLyr纛Zdu珍B絟쬴糔?㕂짹䏵e": "ḱ\u2009cX9멀i䶛簆㳀k" + } + ]]]], + "(_ꏮg່澮?ᩑyM<艷\u001aꪽ\\庼뙭Z맷㰩Vm\\lY筺]3㋲2㌩㄀Eਟ䝵⨄쐨ᔟgङHn鐖⤇놋瓇Q탚單oY\"♆臾jHᶈ征ቄ??uㇰA?#1侓": null + }, + "觓^~ሢ&iI띆g륎ḱ캀.ᓡꀮ胙鈉": 1.0664523593012836E-19, + "y詭Gbᔶऽs댁U:杜⤎ϲ쁗⮼D醄诿q뙰I#즧v蔎xHᵿt᡽[**?崮耖p缫쿃L菝,봬ꤦC쯵#=X1瞻@OZc鱗CQTx": null + } + ] + }}], + "剘紁\u0004\\Xn⊠6,တױ;嵣崇}讃iႽ)d1\\䔓": null + }, + "脨z\"{X,1u찜<'k&@?1}Yn$\u0015Rd輲ーa쮂굄+B$l": true, + "諳>*쭮괐䵟Ґ+<箁}빀䅱⡔檏臒hIH脟ꩪC핝ଗP좕\"0i<\/C褻D۞恗+^5?'ꂱ䚫^7}㡠cq6\\쨪ꔞꥢ?纖䫀氮蒫侲빦敶q{A煲G": -6880961710038544266 + }}] + }, + "5s⨲JvಽῶꭂᄢI.a৊": null, + "?1q꽏쿻ꛋDR%U娝>DgN乭G": -1.2105047302732358E-19 + } + ] + ]}, + "qZz`撋뙹둣j碇쁏\\ꆥ\u0018@藴疰Wz)O{F䶛l᷂绘訥$]뮍夻䢋䩇萿獰樧猵⣭j萶q)$꬚⵷0馢W:Ⱍ!Qoe": -1666634370862219540, + "t": "=wp|~碎Q鬳Ӎ\\l-<\/^ﳊhn퐖}䍔t碵ḛ혷?靻䊗", + "邙쇡㯇%#=,E4勃驆V繚q[Y댻XV㡸[逹ᰏ葢B@u=JS5?bLRn얮㍉⏅ﰳ?a6[&큟!藈": 1.2722786745736667E-19 + }, + "X블땨4{ph鵋ꉯ웸 5p簂䦭s_E徔濧d稝~No穔噕뽲)뉈c5M윅>⚋[岦䲟懷恁?鎐꓆ฬ爋獠䜔s{\u001bm鐚儸煛%bﯿXT>ꗘ@8G": 1157841540507770724, + "媤娪Q杸\u0011SAyᡈ쿯": true, + "灚^ಸ%걁<\/蛯?\"祴坓\\\\'흍": -3.4614808555942579E18, + "釴U:O湛㴑䀣렑縓\ta)(j:숾却䗌gCiB뽬Oyuq輥厁/7)?今hY︺Q": null + } + ] + ]]]}] + ], + "I笔趠Ph!<ཛྷ㸞诘X$畉F\u0005笷菟.Esr릙!W☆䲖뗷莾뒭U\"䀸犜Uo3Gꯌx4r蔇᡹㧪쨢準<䂀%ࡡꟼ瑍8炝Xs0䀝销?fi쥱ꆝલBB": -8571484181158525797, + "L⦁o#J|\"⽩-㱢d㌛8d\\㶤傩儻E[Y熯)r噤὘勇 }": "e(濨쓌K䧚僒㘍蠤Vᛸ\"络QJL2,嬓왍伢㋒䴿考澰@(㏾`kX$끑эE斡,蜍&~y", + "vj.|统圪ᵮPL?2oŶ`밧\"勃+0ue%⿥绬췈체$6:qa렐Q;~晘3㙘鹑": true, + "ශؙ4獄⶿c︋i⚅:ん閝Ⳙ苆籦kw{䙞셕pC췃ꍬ␜꟯ꚓ酄b힝hwk꭭M鬋8B耳쑘WQ\\偙ac'唀x᪌\u2048*h짎#ፇ鮠뾏ឿ뀌": false, + "⎀jꄒ牺3Ⓝ컴~?親ꕽぼܓ喏瘘!@<튋㐌꿱⩦{a?Yv%⪧笯Uܱ栅E搚i뚬:ꄃx7䙳ꦋ&䓹vq☶I䁘ᾘ涜\\썉뺌Lr%Bc㍜3?ꝭ砿裞]": null, + "⭤뙓z(㡂%亳K䌽꫿AԾ岺㦦㼴輞낚Vꦴw냟鬓㹈뽈+o3譻K1잞": 2091209026076965894, + "ㇲ\t⋇轑ꠤ룫X긒\"zoY읇희wj梐쐑l侸`e%s": -9.9240075473576563E17, + "啸ꮑ㉰!ᚓ}銏": -4.0694813896301194E18, + ">]囋੽EK뇜>_ꀣ緳碖{쐐裔[<ನ\"䇅\"5L?#xTwv#罐\u0005래t应\\N?빗;": "v쮽瞭p뭃" + } + ]], + "斴槾?Z翁\"~慍弞ﻆ=꜡o5鐋dw\"?K蠡i샾ogDﲰ_C*⬟iㇷ4nય蟏[㟉U꽌娛苸 ঢ়操贻洞펻)쿗૊許X⨪VY츚Z䍾㶭~튃ᵦ<\/E臭tve猑x嚢": null, + "锡⛩<\/칥ꈙᬙ蝀&Ꚑ籬■865?_>L詏쿨䈌浿弥爫̫lj&zx<\/C쉾?覯n?": null, + "꾳鑤/꼩d=ᘈn挫ᑩ䰬ZC": "3錢爋6Ƹ䴗v⪿Wr益G韠[\u0010屗9쁡钁u?殢c䳀蓃樄욂NAq赟c튒瘁렶Aૡɚ捍" + } + ] + ] + ]} + ] + ] + }]]]}} + ]}], + "Ej䗳U<\/Q=灒샎䞦,堰頠@褙g_\u0003ꤾfⶽ?퇋!łB〙ד3CC䌴鈌U:뭔咎(Qો臃䡬荋BO7㢝䟸\"Yb": 2.36010731779814E-20, + "逸'0岔j\u000e눘먷翌C츊秦=ꭣ棭ှ;鳸=麱$XP⩉駚橄A\\좱⛌jqv䰞3Ь踌v㳆¹gT┌gvLB賖烡m?@E঳i": null + }, + "曺v찘ׁ?&绫O័": 9107241066550187880 + } + ] + ], + "(e屄\u0019昜훕琖b蓘ᬄ0/۲묇Z蘮ဏ⨏蛘胯뢃@㘉8ሪWᨮ⦬ᅳ䅴HI၇쨳z囕陻엣1赳o": true, + ",b刈Z,ၠ晐T솝ŕB⩆ou'퐼≃绗雗d譊": null, + "a唥KB\"ﳝ肕$u\n^⅄P䟼냉䞸⩪u윗瀱ꔨ#yşs꒬=1|ﲤ爢`t౐튼쳫_Az(Ṋ擬㦷좕耈6": 2099309172767331582, + "?㴸U<\/䢔ꯡ阽扆㐤q鐋?f㔫wM嬙-;UV죫嚔픞G&\"Cᗍ䪏풊Q": "VM7疹+陕枡툩窲}翡䖶8欞čsT뮐}璤:jﺋ鎴}HfA൝⧻Zd#Qu茅J髒皣Y-︴[?-~쉜v딏璮㹚䅊﩯<-#\u000e걀h\u0004u抱﵊㼃U<㱷⊱IC進" + }, + "숌dee節鏽邺p넱蹓+e罕U": true + } + ], + "b⧴룏??ᔠ3ぱ>%郿劃翐ꏬꠛW瞳᫏누躨狀ໄy੽\"ីuS=㨞馸k乆E": "トz݈^9R䬑<ﮛGRꨳ\u000fTT泠纷꽀MRᴱ纊:㠭볮?%N56%鈕1䗍䜁a䲗j陇=뿻偂衋࿘ᓸ?ᕵZ+<\/}H耢b䀁z^f$&㝒LkꢳI脚뙛u": 5.694374481577558E-20 + }] + } + ]], + "obj": {"key": "wrong value"}, + "퓲꽪m{㶩/뇿#⼢&᭙硞㪔E嚉c樱㬇1a綑᝖DḾ䝩": null + } +} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/webapp.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/webapp.json new file mode 100644 index 0000000000..ee7b0f8bab --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/webapp.json @@ -0,0 +1,88 @@ +{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/widget.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/widget.json new file mode 100644 index 0000000000..32690e8b76 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/data/widget.json @@ -0,0 +1,26 @@ +{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16be.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16be.json new file mode 100644 index 0000000000..e46dbfb9dd Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16be.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16bebom.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16bebom.json new file mode 100644 index 0000000000..0a23ae205c Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16bebom.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16le.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16le.json new file mode 100644 index 0000000000..92d504530c Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16le.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16lebom.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16lebom.json new file mode 100644 index 0000000000..eaba00132c Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf16lebom.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32be.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32be.json new file mode 100644 index 0000000000..9cbb522279 Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32be.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32bebom.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32bebom.json new file mode 100644 index 0000000000..bde6a99ab4 Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32bebom.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32le.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32le.json new file mode 100644 index 0000000000..b00f290a64 Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32le.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32lebom.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32lebom.json new file mode 100644 index 0000000000..d3db39bf73 Binary files /dev/null and b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf32lebom.json differ diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8.json new file mode 100644 index 0000000000..c500c943f6 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8.json @@ -0,0 +1,7 @@ +{ + "en":"I can eat glass and it doesn't hurt me.", + "zh-Hant":"我能吞下玻璃而不傷身體。", + "zh-Hans":"我能吞下玻璃而不伤身体。", + "ja":"私はガラスを食べられます。それは私を傷つけません。", + "ko":"나는 유리를 먹을 수 있어요. 그래도 아프지 않아요" +} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8bom.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8bom.json new file mode 100644 index 0000000000..b9839fe2fa --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/encodings/utf8bom.json @@ -0,0 +1,7 @@ +{ + "en":"I can eat glass and it doesn't hurt me.", + "zh-Hant":"我能吞下玻璃而不傷身體。", + "zh-Hans":"我能吞下玻璃而不伤身体。", + "ja":"私はガラスを食べられます。それは私を傷つけません。", + "ko":"나는 유리를 먹을 수 있어요. 그래도 아프지 않아요" +} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail1.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail1.json new file mode 100644 index 0000000000..6216b865f1 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail1.json @@ -0,0 +1 @@ +"A JSON payload should be an object or array, not a string." \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail10.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail10.json new file mode 100644 index 0000000000..5d8c0047bd --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail11.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail11.json new file mode 100644 index 0000000000..76eb95b458 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail12.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail12.json new file mode 100644 index 0000000000..77580a4522 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail13.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail13.json new file mode 100644 index 0000000000..379406b59b --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail14.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail14.json new file mode 100644 index 0000000000..0ed366b38a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail15.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail15.json new file mode 100644 index 0000000000..fc8376b605 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail16.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail16.json new file mode 100644 index 0000000000..3fe21d4b53 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail16.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail17.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail17.json new file mode 100644 index 0000000000..62b9214aed --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail18.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail18.json new file mode 100644 index 0000000000..edac92716f --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail19.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail19.json new file mode 100644 index 0000000000..3b9c46fa9a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail2.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail2.json new file mode 100644 index 0000000000..6b7c11e5a5 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail2.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail20.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail20.json new file mode 100644 index 0000000000..27c1af3e72 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail21.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail21.json new file mode 100644 index 0000000000..62474573b2 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail22.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail22.json new file mode 100644 index 0000000000..a7752581bc --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail23.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail23.json new file mode 100644 index 0000000000..494add1ca1 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail23.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail24.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail24.json new file mode 100644 index 0000000000..caff239bfc --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail24.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail25.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail25.json new file mode 100644 index 0000000000..8b7ad23e01 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail25.json @@ -0,0 +1 @@ +[" tab character in string "] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail26.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail26.json new file mode 100644 index 0000000000..845d26a6a5 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail27.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail27.json new file mode 100644 index 0000000000..6b01a2ca4a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail27.json @@ -0,0 +1,2 @@ +["line +break"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail28.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail28.json new file mode 100644 index 0000000000..621a0101c6 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail29.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail29.json new file mode 100644 index 0000000000..47ec421bb6 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail29.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail3.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail3.json new file mode 100644 index 0000000000..168c81eb78 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail3.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail30.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail30.json new file mode 100644 index 0000000000..8ab0bc4b8b --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail30.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail31.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail31.json new file mode 100644 index 0000000000..1cce602b51 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail31.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail32.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail32.json new file mode 100644 index 0000000000..45cba7396f --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail33.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail33.json new file mode 100644 index 0000000000..ca5eb19dc9 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail33.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail4.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail4.json new file mode 100644 index 0000000000..9de168bf34 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail4.json @@ -0,0 +1 @@ +["extra comma",] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail5.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail5.json new file mode 100644 index 0000000000..ddf3ce3d24 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail5.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail6.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail6.json new file mode 100644 index 0000000000..ed91580e1b --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail6.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail7.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail7.json new file mode 100644 index 0000000000..8a96af3e4e --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail7.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail8.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail8.json new file mode 100644 index 0000000000..b28479c6ec --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail8.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail9.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail9.json new file mode 100644 index 0000000000..5815574f36 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/fail9.json @@ -0,0 +1 @@ +{"Extra comma": true,} \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass1.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass1.json new file mode 100644 index 0000000000..70e2685436 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass1.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass2.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass2.json new file mode 100644 index 0000000000..d3c63c7ad8 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass2.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass3.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass3.json new file mode 100644 index 0000000000..4528d51f1a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/pass3.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/readme.txt b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/readme.txt new file mode 100644 index 0000000000..321d89d998 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/jsonchecker/readme.txt @@ -0,0 +1,3 @@ +Test suite from http://json.org/JSON_checker/. + +If the JSON_checker is working correctly, it must accept all of the pass*.json files and reject all of the fail*.json files. diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/booleans.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/booleans.json new file mode 100755 index 0000000000..2dcbb5fe87 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/booleans.json @@ -0,0 +1,102 @@ +[ + true, + true, + false, + false, + true, + true, + true, + false, + false, + true, + false, + false, + true, + false, + false, + false, + true, + false, + false, + true, + true, + false, + true, + true, + true, + false, + false, + false, + true, + false, + true, + false, + false, + true, + true, + true, + true, + true, + true, + false, + false, + true, + false, + false, + false, + true, + true, + false, + true, + true, + false, + true, + false, + true, + true, + true, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + true, + false, + false, + false, + true, + true, + true, + false, + false, + true, + false +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/floats.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/floats.json new file mode 100755 index 0000000000..12b94a11dc --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/floats.json @@ -0,0 +1,102 @@ +[ + 135.747111636, + 123.377054008, + 140.527504552, + -72.299143906, + -23.851678949, + 73.586193519, + -158.299382442, + 177.477876032, + 32.268518982, + -139.560009969, + 115.203105183, + -106.025823607, + 167.224138231, + 103.378383732, + -97.498486285, + 18.184723416, + 69.137075711, + 33.849002681, + -120.185228215, + -20.841408615, + -172.659492727, + -2.691464061, + 22.426164066, + -98.416909437, + -31.603082708, + -85.072296561, + 108.620987395, + -43.127078238, + -126.473562057, + -158.595489097, + -57.890678254, + -13.254016573, + -85.024504709, + 171.663552644, + -146.495558248, + -10.606748276, + -118.786969354, + 153.352057804, + -45.215545083, + 37.038725288, + 106.344071897, + -64.607402031, + 85.148030911, + 28.897784566, + 39.51082061, + 20.450382102, + -113.174943618, + 71.60785784, + -168.202648062, + -157.338200017, + 10.879588527, + -114.261694831, + -5.622927072, + -173.330830616, + -29.47002003, + -39.829034201, + 50.031545162, + 82.815735508, + -119.188760828, + -48.455928081, + 163.964263034, + 46.30378861, + -26.248889762, + -47.354615322, + 155.388677633, + -166.710356904, + 42.987233558, + 144.275297374, + 37.394383186, + -122.550388725, + 177.469945914, + 101.104677413, + 109.429869885, + -104.919625624, + 147.522756541, + -81.294703727, + 122.744731363, + 81.803603684, + 26.321556167, + 147.045441354, + 147.256895816, + -174.211095908, + 52.518769316, + -78.58250334, + -173.356685435, + -107.728209264, + -69.982325771, + -113.776095893, + -35.785267074, + -105.748545976, + -30.206523864, + -76.185311723, + -126.400112781, + -26.864958639, + 56.840053629, + 93.781553535, + -116.002949803, + -46.617140948, + 176.846840093, + -144.24821335 +] diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/guids.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/guids.json new file mode 100755 index 0000000000..9d7f5dbc8f --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/guids.json @@ -0,0 +1,102 @@ +[ + "d35bf0d4-8d8f-4e17-a5c3-ad9bfd675266", + "db402774-eeb6-463b-9986-c458c44d8b5a", + "2a2e4101-b5f2-40b8-8750-e03f01661e60", + "76787cfa-f4eb-4d62-aaad-e1d588d00ad5", + "fd73894b-b500-4a7c-888c-06b5bd9cec65", + "cce1862a-cf31-4ef2-9e23-f1d23b4e6163", + "00a98bb0-2b6e-4368-8512-71c21aa87db7", + "ab9a8d69-cec7-4550-bd35-3ed678e22782", + "f18b48e1-5114-4fbe-9652-579e8d66950e", + "4efe3baa-7ac5-4d6a-a839-6b9cfe825764", + "b4aec119-5b0a-434c-b388-109816c482a5", + "e0ef0cbb-127a-4a28-9831-5741b4295275", + "d50286a5-cb7b-4c9e-be99-f214439bae8c", + "a981094c-f1ac-42ed-a9fa-86404c7210ff", + "2a34ee57-5815-4829-b77b-eeebaa8fe340", + "a0530d44-48f8-4eff-b9ea-8810c4308351", + "c6f91509-83e1-4ea1-9680-e667fbfd56ee", + "cab11402-dcdd-4454-b190-6da124947395", + "283d159c-2b18-4856-b4c7-5059252eaa15", + "146157c6-72a8-4051-9991-cb6ea6743d81", + "aef6f269-7306-4bd2-83f7-6d5605b5dc9a", + "37fe6027-d638-4017-80a9-e7b0567b278e", + "5003d731-33fb-4159-af61-d76348a44079", + "e0e06979-5f80-4713-9fe0-8a4d60dc89f8", + "7e85bdc3-0345-4cb6-9398-ccab06e79976", + "f2ebf5af-6568-4ffe-a46d-403863fd4b66", + "e0b5bb1c-b4dd-4535-9a9e-3c73f1167d46", + "c852d20b-6bcb-4b12-bd57-308296c64c5a", + "7ac3ae82-1818-49cd-a8a4-5ac77dfafd46", + "138004a9-76e2-4ad7-bd42-e74dabdbb803", + "ab25b5be-96be-45b0-b765-947b40ec36a6", + "08404734-fd57-499e-a4cf-71e9ec782ede", + "8dfdeb16-248b-4a21-bf89-2e22b11a4101", + "a0e44ef0-3b09-41e8-ad5d-ed8e6a1a2a67", + "a7981e49-188d-414a-9779-b1ad91e599d1", + "329186c0-bf27-4208-baf7-c0a0a5a2d5b7", + "cb5f3381-d33e-4b30-b1a9-f482623cad33", + "15031262-ca73-4e3c-bd0a-fcf89bdf0caf", + "6d7333d1-2e8c-4d78-bfde-5be47e70eb13", + "acaa160c-670a-4e8f-ac45-49416e77d5f9", + "228f87eb-cde4-4106-808b-2dbf3c7b6d2e", + "2ff830a3-5445-4d8e-b161-bddd30666697", + "f488bedd-ff6e-4108-b9a7-07f6da62f476", + "2e12b846-0a34-478e-adf7-a438493803e6", + "6686b8ef-7446-4d86-bd8c-df24119e3bfe", + "e474a5c5-5793-4d41-b4ab-5423acc56ef1", + "ac046573-e718-44dc-a0dc-9037eeaba6a9", + "6b0e9099-cf53-4d5a-8a71-977528628fcf", + "d51a3f22-0ff9-4087-ba9b-fcee2a2d8ade", + "bdc01286-3511-4d22-bfb8-76d01203d366", + "ca44eb84-17ff-4f27-8f1e-1bd25f4e8725", + "4e9a8c2f-be0b-4913-92d2-c801b9a50d04", + "7685d231-dadd-4041-9165-898397438ab7", + "86f0bf26-d66a-44d8-99f5-d6768addae3b", + "2ca1167c-72ba-45a0-aa42-faf033db0d0b", + "199a1182-ea55-49ff-ba51-71c29cdd0aac", + "be6a4dd2-c821-4aa0-8b83-d64d6644b5b2", + "4c5f4781-7f80-4daa-9c20-76b183000514", + "513b31bd-54fb-4d12-a427-42a7c13ff8e1", + "8e211bcb-d76c-4012-83ad-74dd7d23b687", + "44d5807e-0501-4f66-8779-e244d4fdca0a", + "db8cd555-0563-4b7b-b00c-eada300a7065", + "cb14d0c9-46cc-4797-bd3a-752b05629f07", + "4f68b3ef-ac9b-47a0-b6d7-57f398a5c6a5", + "77221aae-1bcf-471c-be45-7f31f733f9d6", + "42a7cac8-9e80-4c45-8c71-511d863c98ea", + "f9018d22-b82c-468c-bdb5-8864d5964801", + "75f4e9b8-62a2-4f21-ad8a-e19eff0419bc", + "9b7385c8-8653-4184-951c-b0ac1b36b42e", + "571018aa-ffbf-4b42-a16d-07b57a7f5f0e", + "35de4a2f-6bf1-45aa-b820-2a27ea833e44", + "0b8edb20-3bb4-4cb4-b089-31957466dbab", + "97da4778-9a7b-4140-a545-968148c81fb7", + "969f326c-8f2a-47c5-b41c-d9c2f06c9b9d", + "ae211037-8b53-4b17-bfc8-c06fc7774409", + "12c5c3c4-0bd5-45d3-bc1d-d04a3c65d3e6", + "ec02024f-ce43-4dd3-8169-a59f7baee043", + "5b6afe77-ce48-47ca-90a0-25cd10ca5ffd", + "2e3a61d4-6b8f-4d2f-ba86-878b4012efd8", + "19a88a67-a5d3-4647-898f-1cde07bce040", + "6db6f420-b5c8-48b9-bbb2-8864fe6fed65", + "5a45dbde-7b53-4f6b-b864-e3b63be3708a", + "c878321b-8a02-4239-9981-15760c2e7d15", + "4e36687f-8bf6-4b12-b496-3a8e382d067e", + "a59a63cd-43c0-4c6e-b208-6dbca86f8176", + "303308c4-2e4a-45b5-8bf3-3e66e9ad05a1", + "8b58fdf1-43a6-4c98-9547-6361b50791af", + "a3563591-72ed-42b5-8e41-bac1d76d70cf", + "38db8c78-3739-4f6e-8313-de4138082114", + "86615bea-7e73-4daf-95da-ae6b9eee1bbb", + "35d38e3e-076e-40dd-9aa8-05be2603bd59", + "9f84c62d-b454-4ba3-8c19-a01878985cdc", + "6721bbae-d765-4a06-8289-6fe46a1bf943", + "0837796f-d0dd-4e50-9b7c-1983e6cc7c48", + "021eb7d7-e869-49b9-80c3-9dd16ce2d981", + "819c56f8-e040-475d-aad5-c6d5e98b20aa", + "3a61ef02-735e-4229-937d-b3777a3f4e1f", + "79dfab84-12e6-4ec8-bfc8-460ae71e4eca", + "a106fabf-e149-476c-8053-b62388b6eb57", + "9a3900a5-bfb4-4de0-baa5-253a8bd0b634" +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/integers.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/integers.json new file mode 100755 index 0000000000..5dd05e097a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/integers.json @@ -0,0 +1,102 @@ +[ + 8125686, + 8958709, + 5976222, + 1889524, + 7968493, + 1357486, + 118415, + 7081097, + 4635968, + 7555332, + 2270233, + 3428352, + 8699968, + 2087333, + 7861337, + 7554440, + 2017031, + 7981692, + 6060687, + 1877715, + 3297474, + 8373177, + 6158629, + 7853641, + 3004441, + 9650406, + 2695251, + 1180761, + 4988426, + 6043805, + 8063373, + 6103218, + 2848339, + 8188690, + 9235573, + 5949816, + 6116081, + 6471138, + 3354531, + 4787414, + 9660600, + 942529, + 7278535, + 7967399, + 554292, + 1436493, + 267319, + 2606657, + 7900601, + 4276634, + 7996757, + 8544466, + 7266469, + 3301373, + 4005350, + 6437652, + 7717672, + 7126292, + 8588394, + 2127902, + 7410190, + 1517806, + 4583602, + 3123440, + 7747613, + 5029464, + 9834390, + 3087227, + 4913822, + 7550487, + 4518144, + 5862588, + 1778599, + 9493290, + 5588455, + 3638706, + 7394293, + 4294719, + 3837830, + 6381878, + 7175866, + 8575492, + 1415229, + 1453733, + 6972404, + 9782571, + 4234063, + 7117418, + 7293130, + 8057071, + 9345285, + 7626648, + 3358911, + 4574537, + 9371826, + 7627107, + 6154093, + 5392367, + 5398105, + 6956377 +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/mixed.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/mixed.json new file mode 100755 index 0000000000..43e9a1d7be --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/mixed.json @@ -0,0 +1,592 @@ +[ + { + "favoriteFruit": "banana", + "greeting": "Hello, Kim! You have 10 unread messages.", + "friends": [ + { + "name": "Higgins Rodriquez", + "id": 0 + }, + { + "name": "James Floyd", + "id": 1 + }, + { + "name": "Gay Stewart", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "pariatur", + "ad", + "eiusmod", + "sit", + "et", + "velit", + "culpa" + ], + "longitude": -57.919246, + "latitude": -36.022812, + "registered": "Friday, March 21, 2014 9:13 PM", + "about": "Laborum nulla aliquip ullamco proident excepteur est officia ipsum. Eiusmod exercitation minim ex do labore reprehenderit aliqua minim qui excepteur reprehenderit cupidatat. Sint enim exercitation duis id consequat nisi enim magna. Commodo aliqua id ipsum sit magna enim. Veniam officia in labore fugiat veniam ea laboris ex veniam duis.\r\n", + "address": "323 Pulaski Street, Ronco, North Carolina, 7701", + "phone": "+1 (919) 438-2678", + "email": "kim.griffith@cipromox.biz", + "company": "CIPROMOX", + "name": { + "last": "Griffith", + "first": "Kim" + }, + "eyeColor": "green", + "age": 26, + "picture": "http://placehold.it/32x32", + "balance": "$1,283.55", + "isActive": false, + "guid": "10ab0392-c5e2-48a3-9473-aa725bad892d", + "index": 0, + "_id": "551b91198238a0bcf9a41133" + }, + { + "favoriteFruit": "banana", + "greeting": "Hello, Skinner! You have 9 unread messages.", + "friends": [ + { + "name": "Rhonda Justice", + "id": 0 + }, + { + "name": "Audra Castaneda", + "id": 1 + }, + { + "name": "Vicky Chavez", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "dolore", + "enim", + "sit", + "non", + "exercitation", + "fugiat", + "adipisicing" + ], + "longitude": -60.291407, + "latitude": -84.619318, + "registered": "Friday, February 7, 2014 3:17 AM", + "about": "Consectetur eiusmod laboris dolore est ullamco nulla in velit quis esse Lorem. Amet aliqua sunt aute occaecat veniam officia in duis proident aliqua cupidatat mollit. Sint eu qui anim duis ut anim duis eu cillum. Cillum nostrud adipisicing tempor Lorem commodo sit in ad qui non et irure qui. Labore eu aliquip id duis eiusmod veniam.\r\n", + "address": "347 Autumn Avenue, Fidelis, Puerto Rico, 543", + "phone": "+1 (889) 457-2319", + "email": "skinner.maddox@moltonic.co.uk", + "company": "MOLTONIC", + "name": { + "last": "Maddox", + "first": "Skinner" + }, + "eyeColor": "green", + "age": 22, + "picture": "http://placehold.it/32x32", + "balance": "$3,553.10", + "isActive": false, + "guid": "cfbc2fb6-2641-4388-b06d-ec0212cfac1e", + "index": 1, + "_id": "551b91197e0abe92d6642700" + }, + { + "favoriteFruit": "strawberry", + "greeting": "Hello, Reynolds! You have 5 unread messages.", + "friends": [ + { + "name": "Brady Valdez", + "id": 0 + }, + { + "name": "Boyer Golden", + "id": 1 + }, + { + "name": "Gladys Knapp", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "commodo", + "eiusmod", + "cupidatat", + "et", + "occaecat", + "proident", + "Lorem" + ], + "longitude": 140.866287, + "latitude": 1.401032, + "registered": "Monday, October 20, 2014 8:01 AM", + "about": "Deserunt elit consequat ea dolor pariatur aute consectetur et nulla ipsum ad. Laboris occaecat ipsum ad duis et esse ea ut voluptate. Ex magna consequat pariatur amet. Quis excepteur non mollit dolore cillum dolor ex esse veniam esse deserunt non occaecat veniam. Sit amet proident proident amet. Nisi est id ut ut adipisicing esse fugiat non dolor aute.\r\n", + "address": "872 Montague Terrace, Haena, Montana, 3106", + "phone": "+1 (974) 410-2655", + "email": "reynolds.sanford@combot.biz", + "company": "COMBOT", + "name": { + "last": "Sanford", + "first": "Reynolds" + }, + "eyeColor": "green", + "age": 21, + "picture": "http://placehold.it/32x32", + "balance": "$3,664.47", + "isActive": true, + "guid": "f9933a9c-c41a-412f-a18d-e727c569870b", + "index": 2, + "_id": "551b91197f170b65413a06e3" + }, + { + "favoriteFruit": "banana", + "greeting": "Hello, Neva! You have 7 unread messages.", + "friends": [ + { + "name": "Clara Cotton", + "id": 0 + }, + { + "name": "Ray Gates", + "id": 1 + }, + { + "name": "Jacobs Reese", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "magna", + "labore", + "incididunt", + "velit", + "ea", + "et", + "eiusmod" + ], + "longitude": -133.058479, + "latitude": 87.803677, + "registered": "Friday, May 9, 2014 5:41 PM", + "about": "Do duis occaecat ut officia occaecat officia nostrud reprehenderit ex excepteur aute anim in reprehenderit. Cupidatat nulla eiusmod nulla non minim veniam aute nulla deserunt adipisicing consectetur veniam. Sit consequat ex laboris aliqua labore consectetur tempor proident consequat est. Fugiat quis esse culpa aliquip. Excepteur laborum aliquip sunt eu cupidatat magna eiusmod amet nisi labore aliquip. Ut consectetur esse aliquip exercitation nulla ex occaecat elit do ex eiusmod deserunt. Ex eu voluptate minim deserunt fugiat minim est occaecat ad Lorem nisi.\r\n", + "address": "480 Eagle Street, Fostoria, Oklahoma, 2614", + "phone": "+1 (983) 439-3000", + "email": "neva.barker@pushcart.us", + "company": "PUSHCART", + "name": { + "last": "Barker", + "first": "Neva" + }, + "eyeColor": "brown", + "age": 36, + "picture": "http://placehold.it/32x32", + "balance": "$3,182.24", + "isActive": true, + "guid": "52489849-78e1-4b27-8b86-e3e5ab2b7dc8", + "index": 3, + "_id": "551b9119a13061c083c878d5" + }, + { + "favoriteFruit": "banana", + "greeting": "Hello, Rodgers! You have 6 unread messages.", + "friends": [ + { + "name": "Marguerite Conway", + "id": 0 + }, + { + "name": "Margarita Cunningham", + "id": 1 + }, + { + "name": "Carmela Gallagher", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "ipsum", + "magna", + "amet", + "elit", + "sit", + "occaecat", + "elit" + ], + "longitude": -125.436981, + "latitude": 19.868524, + "registered": "Tuesday, July 8, 2014 8:09 PM", + "about": "In cillum esse tempor do magna id ad excepteur ex nostrud mollit deserunt aliqua. Minim aliqua commodo commodo consectetur exercitation nulla nisi dolore aliqua in. Incididunt deserunt mollit nostrud excepteur. Ipsum fugiat anim deserunt Lorem aliquip nisi consequat eu minim in ex duis.\r\n", + "address": "989 Varanda Place, Duryea, Palau, 3972", + "phone": "+1 (968) 578-2974", + "email": "rodgers.conner@frenex.net", + "company": "FRENEX", + "name": { + "last": "Conner", + "first": "Rodgers" + }, + "eyeColor": "blue", + "age": 23, + "picture": "http://placehold.it/32x32", + "balance": "$1,665.17", + "isActive": true, + "guid": "ed3b2374-5afe-4fca-9325-8a7bbc9f81a0", + "index": 4, + "_id": "551b91197bcedb1b56a241ce" + }, + { + "favoriteFruit": "strawberry", + "greeting": "Hello, Mari! You have 10 unread messages.", + "friends": [ + { + "name": "Irwin Boyd", + "id": 0 + }, + { + "name": "Dejesus Flores", + "id": 1 + }, + { + "name": "Lane Mcmahon", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "esse", + "aliquip", + "excepteur", + "dolor", + "ex", + "commodo", + "anim" + ], + "longitude": -17.038176, + "latitude": 17.154663, + "registered": "Sunday, April 6, 2014 4:46 AM", + "about": "Excepteur veniam occaecat sint nulla magna in in officia elit. Eiusmod qui dolor fugiat tempor in minim esse officia minim consequat. Lorem ullamco labore proident ipsum id pariatur fugiat consectetur anim cupidatat qui proident non ipsum.\r\n", + "address": "563 Hendrickson Street, Westwood, South Dakota, 4959", + "phone": "+1 (980) 434-3976", + "email": "mari.fleming@beadzza.org", + "company": "BEADZZA", + "name": { + "last": "Fleming", + "first": "Mari" + }, + "eyeColor": "blue", + "age": 21, + "picture": "http://placehold.it/32x32", + "balance": "$1,948.04", + "isActive": true, + "guid": "6bd02166-3b1f-4ed8-84c9-ed96cbf12abc", + "index": 5, + "_id": "551b9119b359ff6d24846f77" + }, + { + "favoriteFruit": "strawberry", + "greeting": "Hello, Maxine! You have 7 unread messages.", + "friends": [ + { + "name": "Sullivan Stark", + "id": 0 + }, + { + "name": "Underwood Mclaughlin", + "id": 1 + }, + { + "name": "Kristy Carlson", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "commodo", + "ipsum", + "quis", + "non", + "est", + "mollit", + "exercitation" + ], + "longitude": -105.40635, + "latitude": 37.197993, + "registered": "Tuesday, January 20, 2015 12:30 AM", + "about": "Proident ullamco Lorem est consequat consectetur non eiusmod esse nostrud pariatur eiusmod enim exercitation eiusmod. Consequat duis elit elit minim ullamco et dolor eu minim do tempor esse consequat excepteur. Mollit dolor do voluptate nostrud quis anim cillum velit tempor eiusmod adipisicing tempor do culpa. Eu magna dolor sit amet nisi do laborum dolore nisi. Deserunt ipsum et deserunt non nisi.\r\n", + "address": "252 Boulevard Court, Brenton, Tennessee, 9444", + "phone": "+1 (950) 466-3377", + "email": "maxine.moreno@zentia.tv", + "company": "ZENTIA", + "name": { + "last": "Moreno", + "first": "Maxine" + }, + "eyeColor": "brown", + "age": 24, + "picture": "http://placehold.it/32x32", + "balance": "$1,200.24", + "isActive": false, + "guid": "ce307a37-ca1f-43f5-b637-dca2605712be", + "index": 6, + "_id": "551b91195a6164b2e35f6dc8" + }, + { + "favoriteFruit": "strawberry", + "greeting": "Hello, Helga! You have 5 unread messages.", + "friends": [ + { + "name": "Alicia Vance", + "id": 0 + }, + { + "name": "Vinson Phelps", + "id": 1 + }, + { + "name": "Francisca Kelley", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "nostrud", + "eiusmod", + "dolore", + "officia", + "sint", + "non", + "qui" + ], + "longitude": -7.275151, + "latitude": 75.54202, + "registered": "Wednesday, October 1, 2014 6:35 PM", + "about": "Quis duis ullamco velit qui. Consectetur non adipisicing id magna anim. Deserunt est officia qui esse. Et do pariatur incididunt anim ad mollit non. Et eiusmod sunt fugiat elit mollit ad excepteur anim nisi laboris eiusmod aliquip aliquip.\r\n", + "address": "981 Bush Street, Beaulieu, Vermont, 3775", + "phone": "+1 (956) 506-3807", + "email": "helga.burch@synkgen.name", + "company": "SYNKGEN", + "name": { + "last": "Burch", + "first": "Helga" + }, + "eyeColor": "blue", + "age": 22, + "picture": "http://placehold.it/32x32", + "balance": "$3,827.89", + "isActive": false, + "guid": "ff5dfea0-1052-4ef2-8b66-4dc1aad0a4fb", + "index": 7, + "_id": "551b911946be8358ae40e90e" + }, + { + "favoriteFruit": "banana", + "greeting": "Hello, Shaw! You have 5 unread messages.", + "friends": [ + { + "name": "Christian Cardenas", + "id": 0 + }, + { + "name": "Cohen Pennington", + "id": 1 + }, + { + "name": "Mary Lindsay", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "occaecat", + "ut", + "occaecat", + "magna", + "exercitation", + "incididunt", + "irure" + ], + "longitude": -89.102972, + "latitude": 89.489596, + "registered": "Thursday, August 21, 2014 5:00 PM", + "about": "Amet cupidatat quis velit aute Lorem consequat pariatur mollit deserunt et sint culpa excepteur duis. Enim proident duis qui ex tempor sunt nostrud occaecat. Officia sit veniam mollit eiusmod minim do aute eiusmod fugiat qui anim adipisicing in laboris. Do tempor reprehenderit sunt laborum esse irure dolor ad consectetur aute sit id ipsum. Commodo et voluptate anim consequat do. Minim laborum ad veniam ad minim incididunt excepteur excepteur aliqua.\r\n", + "address": "237 Pierrepont Street, Herbster, New York, 3490", + "phone": "+1 (976) 455-2880", + "email": "shaw.zamora@shadease.me", + "company": "SHADEASE", + "name": { + "last": "Zamora", + "first": "Shaw" + }, + "eyeColor": "blue", + "age": 38, + "picture": "http://placehold.it/32x32", + "balance": "$3,440.82", + "isActive": false, + "guid": "ac5fdb0e-e1fb-427e-881d-da461be0d1ca", + "index": 8, + "_id": "551b9119af0077bc28a2de25" + }, + { + "favoriteFruit": "apple", + "greeting": "Hello, Melissa! You have 5 unread messages.", + "friends": [ + { + "name": "Marion Villarreal", + "id": 0 + }, + { + "name": "Kate Rose", + "id": 1 + }, + { + "name": "Hines Simon", + "id": 2 + } + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "tags": [ + "amet", + "veniam", + "mollit", + "ad", + "cupidatat", + "deserunt", + "Lorem" + ], + "longitude": -52.735052, + "latitude": 16.258838, + "registered": "Wednesday, April 16, 2014 7:56 PM", + "about": "Aute ut culpa eiusmod tempor duis dolor tempor incididunt. Nisi non proident excepteur eiusmod incididunt nisi minim irure sit. In veniam commodo deserunt proident reprehenderit et consectetur ullamco quis nulla cupidatat.\r\n", + "address": "642 Halsey Street, Blandburg, Kansas, 6761", + "phone": "+1 (941) 539-3851", + "email": "melissa.vaughn@memora.io", + "company": "MEMORA", + "name": { + "last": "Vaughn", + "first": "Melissa" + }, + "eyeColor": "brown", + "age": 24, + "picture": "http://placehold.it/32x32", + "balance": "$2,399.44", + "isActive": true, + "guid": "1769f022-a7f1-4a69-bf4c-f5a5ebeab2d1", + "index": 9, + "_id": "551b9119b607c09c7ffc3b8a" + } +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/nulls.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/nulls.json new file mode 100755 index 0000000000..7a636ec87c --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/nulls.json @@ -0,0 +1,102 @@ +[ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/paragraphs.json b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/paragraphs.json new file mode 100755 index 0000000000..8ab3e1c561 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/paragraphs.json @@ -0,0 +1,102 @@ +[ + "Commodo ullamco cupidatat nisi sit proident ex. Cillum pariatur occaecat in officia do commodo nisi cillum tempor minim. Ad dolor ut et aliquip fugiat eu officia cupidatat occaecat consectetur eiusmod veniam enim officia.\r\n", + "Adipisicing cillum laborum nisi irure. Cillum dolor proident duis nulla qui mollit dolore reprehenderit mollit. Irure nulla dolor ipsum irure nulla quis laboris do.\r\n", + "Est adipisicing consectetur incididunt in. Occaecat ea magna ex consequat irure sit laborum cillum officia magna sunt do exercitation aliquip. Laboris id aute in dolore reprehenderit voluptate non deserunt laborum.\r\n", + "Consectetur eu aute est est occaecat adipisicing sint enim dolor eu. Tempor amet id non mollit eu consectetur cillum duis. Eu labore velit nulla ipsum commodo consequat aliquip. Cupidatat commodo dolore mollit enim sit excepteur nisi duis laboris deserunt esse.\r\n", + "Incididunt ullamco est fugiat enim fugiat. Do sit mollit anim ad excepteur eu laboris exercitation officia labore nulla ut. Voluptate non voluptate cillum sit et voluptate anim duis velit consequat aliquip dolor. Elit et et esse laboris consectetur officia eiusmod aliquip nisi est. Qui labore dolore ad dolor.\r\n", + "Anim adipisicing est irure proident sit officia ullamco voluptate sunt consectetur duis mollit excepteur veniam. Nostrud ut duis aute exercitation officia et quis elit commodo elit tempor aute aliquip enim. Est officia non cillum consequat voluptate ipsum sit voluptate nulla id.\r\n", + "Ipsum enim consectetur aliquip nulla commodo ut ex aliqua elit duis do. Officia et sunt aliqua dolor minim voluptate veniam esse elit enim. Adipisicing reprehenderit duis ex magna non in fugiat sunt ipsum nostrud fugiat aliquip. Labore voluptate id officia voluptate eu. Magna do nostrud excepteur sunt aliqua adipisicing qui.\r\n", + "Est occaecat non non cupidatat laborum qui. Veniam sit est voluptate labore sit irure consectetur fugiat. Anim enim enim fugiat exercitation anim ad proident esse in aliqua. Laboris ut aute culpa ullamco.\r\n", + "Sit et aliquip cupidatat deserunt eiusmod sint aliquip occaecat nostrud aliqua elit commodo ut magna. Amet sit est deserunt id duis in officia pariatur cupidatat ex. Mollit duis est consequat nulla aute velit ipsum sit consectetur pariatur ut non ex ipsum. Tempor esse velit pariatur reprehenderit et nostrud commodo laborum mollit labore.\r\n", + "Aliquip irure quis esse aliquip. Ex non deserunt culpa aliqua ad anim occaecat ad. Lorem consectetur mollit eu consectetur est non nisi non ipsum. Qui veniam ullamco officia est ut excepteur. Nulla elit dolore cupidatat aliqua enim Lorem elit consequat eiusmod non aliqua eu in. Pariatur in culpa labore sint ipsum consectetur occaecat ad ex ipsum laboris aliquip officia. Non officia eiusmod nisi officia id id laboris deserunt sunt enim magna mollit sit.\r\n", + "Mollit velit laboris laborum nulla aliquip consequat Lorem non incididunt irure. Eu voluptate sint do consectetur tempor sit Lorem in. Laborum eiusmod nisi Lorem ipsum dolore do aute laborum occaecat aute sunt. Sit laborum in ea do ipsum officia irure cillum irure nisi laboris. Ad anim deserunt excepteur ea veniam eiusmod culpa velit veniam. Commodo incididunt ea Lorem eu enim esse nisi incididunt mollit.\r\n", + "Velit proident sunt aute dolore reprehenderit culpa. Pariatur reprehenderit commodo ad ea voluptate anim nulla ipsum eu irure fugiat aliqua et. Adipisicing incididunt anim excepteur voluptate minim qui culpa. Sunt veniam enim reprehenderit magna magna. Sit ad amet deserunt ut aute dolore ad minim.\r\n", + "Esse ullamco sunt mollit mollit. Eu enim dolore laboris cupidatat. Cupidatat adipisicing non aute exercitation fugiat. Non ut cillum labore fugiat aliquip ex duis quis consectetur ut nisi Lorem amet qui. Proident veniam amet qui reprehenderit duis qui. Nisi culpa sit occaecat ullamco occaecat laborum fugiat ut. Non duis deserunt culpa duis.\r\n", + "Id ipsum eiusmod laboris non est ipsum deserunt labore duis reprehenderit deserunt. Sint tempor fugiat eiusmod nostrud in ut laborum esse in nostrud sit deserunt nostrud reprehenderit. Cupidatat aliqua qui anim consequat eu quis consequat consequat elit ipsum pariatur. Cupidatat in dolore velit quis. Exercitation cillum ullamco ex consectetur commodo tempor incididunt exercitation labore ad dolore. Minim incididunt consequat adipisicing esse eu eu voluptate.\r\n", + "Anim sint eiusmod nisi anim do deserunt voluptate ut cillum eiusmod esse ex reprehenderit laborum. Dolore nulla excepteur duis excepteur. Magna nisi nostrud duis non commodo velit esse ipsum Lorem incididunt. Nulla enim consequat ad aliqua. Incididunt irure culpa nostrud ea aute ex sit non ad esse.\r\n", + "Ullamco nostrud cupidatat adipisicing anim fugiat mollit eu. Et ut eu in nulla consequat. Sunt do pariatur culpa non est.\r\n", + "Pariatur incididunt reprehenderit non qui excepteur cillum exercitation nisi occaecat ad. Lorem aliquip laborum commodo reprehenderit sint. Laboris qui ut veniam magna quis et et ullamco voluptate. Tempor reprehenderit deserunt consequat nisi. Esse duis sint in tempor. Amet aute cupidatat in sint et.\r\n", + "Est officia nisi dolore consequat irure et excepteur. Sit qui elit tempor magna qui cillum anim amet proident exercitation proident. Eu cupidatat laborum consectetur duis ullamco irure nulla. Adipisicing culpa non reprehenderit anim aute.\r\n", + "Eu est laborum culpa velit dolore non sunt. Tempor magna veniam ea sit non qui Lorem qui exercitation aliqua aliqua et excepteur eiusmod. Culpa aute anim proident culpa adipisicing duis tempor elit aliquip elit nulla laboris esse dolore. Sit adipisicing non dolor eiusmod occaecat cupidatat.\r\n", + "Culpa velit eu esse sunt. Laborum irure aliqua reprehenderit velit ipsum fugiat officia dolor ut aute officia deserunt. Ipsum sit quis fugiat nostrud aliqua cupidatat ex pariatur et. Cillum proident est irure nisi dolor aliqua deserunt esse occaecat velit dolor.\r\n", + "Exercitation nulla officia sit eiusmod cillum eu incididunt officia exercitation qui Lorem deserunt. Voluptate Lorem minim commodo laborum esse in duis excepteur do duis aliquip nisi voluptate consectetur. Amet tempor officia enim ex esse minim reprehenderit.\r\n", + "Laboris sint deserunt ad aute incididunt. Anim officia sunt elit qui laborum labore commodo irure non. Mollit adipisicing ullamco do aute nulla eu laborum et quis sint aute adipisicing amet. Aliqua officia irure nostrud duis ex.\r\n", + "Eiusmod ipsum aliqua reprehenderit esse est non aute id veniam eiusmod. Elit consequat ad sit tempor elit eu incididunt quis irure ad. Eu incididunt veniam consequat Lorem nostrud cillum officia ea consequat ad cillum. Non nisi irure cupidatat incididunt pariatur incididunt. Duis velit officia ad cillum qui. Aliquip consequat sint aute nisi cillum. Officia commodo nisi incididunt laborum nisi voluptate aliquip Lorem cupidatat anim consequat sit laboris.\r\n", + "Veniam cupidatat et incididunt mollit do ex voluptate veniam nostrud labore esse. Eiusmod irure sint fugiat esse. Aute irure consectetur ut mollit nulla sint esse. Lorem ut quis ex proident nostrud mollit nostrud ea duis duis in magna anim consectetur.\r\n", + "Irure culpa esse qui do dolor fugiat veniam ad. Elit commodo aute elit magna incididunt tempor pariatur velit irure pariatur cillum et ea ad. Ad consequat ea et ad minim ut sunt qui commodo voluptate. Laboris est aliquip anim reprehenderit eu officia et exercitation. Occaecat laboris cupidatat Lorem ullamco in nostrud commodo ipsum in quis esse ex.\r\n", + "Incididunt officia quis voluptate eiusmod esse nisi ipsum quis commodo. Eiusmod dolore tempor occaecat sit exercitation aliqua minim consequat minim mollit qui ad nisi. Aute quis irure adipisicing veniam nisi nisi velit deserunt incididunt anim nostrud.\r\n", + "Voluptate exercitation exercitation id minim excepteur excepteur mollit. Fugiat aute proident nulla ullamco ea. Nisi ea culpa duis dolore veniam anim tempor officia in dolore exercitation exercitation. Dolore quis cillum adipisicing sunt do nulla esse proident ad sint.\r\n", + "Laborum ut mollit sint commodo nulla laborum deserunt Lorem magna commodo mollit tempor deserunt ut. Qui aliquip commodo ea id. Consectetur dolor fugiat dolor excepteur eiusmod. Eu excepteur ex aute ex ex elit ex esse officia cillum exercitation. Duis ut labore ea nostrud excepteur. Reprehenderit labore aute sunt nisi quis Lorem officia. Ad aliquip cupidatat voluptate exercitation voluptate ad irure magna quis.\r\n", + "Tempor velit veniam sit labore elit minim do elit cillum eiusmod sunt excepteur nisi. Aliquip est deserunt excepteur duis fugiat incididunt veniam fugiat. Pariatur sit irure labore et minim non. Cillum quis aute anim sint laboris laboris ullamco exercitation nostrud. Nulla pariatur id laborum minim nisi est adipisicing irure.\r\n", + "Irure exercitation laboris nostrud in do consectetur ad. Magna aliqua Lorem culpa exercitation sint do culpa incididunt mollit eu exercitation. Elit tempor Lorem dolore enim deserunt. Anim et ullamco sint ullamco mollit cillum officia et. Proident incididunt laboris aliquip laborum sint veniam deserunt eu consequat deserunt voluptate laboris. Anim Lorem non laborum exercitation voluptate. Cupidatat reprehenderit culpa Lorem fugiat enim minim consectetur tempor quis ad reprehenderit laboris irure.\r\n", + "Deserunt elit mollit nostrud occaecat labore reprehenderit laboris ex. Esse reprehenderit adipisicing cillum minim in esse aliquip excepteur ex et nisi cillum quis. Cillum labore ut ex sunt. Occaecat proident et mollit magna consequat irure esse. Dolor do enim esse nisi ad.\r\n", + "Pariatur est anim cillum minim elit magna adipisicing quis tempor proident nisi laboris incididunt cupidatat. Nulla est adipisicing sit adipisicing id nostrud amet qui consequat eiusmod tempor voluptate ad. Adipisicing non magna sit occaecat magna mollit ad ex nulla velit ea pariatur. Irure labore ad ea exercitation ex cillum.\r\n", + "Lorem fugiat eu eu cillum nulla tempor sint. Lorem id officia nulla velit labore ut duis ad tempor non. Excepteur quis aute adipisicing nisi nisi consectetur aliquip enim Lorem id ullamco cillum sint voluptate. Qui aliquip incididunt tempor aliqua voluptate labore reprehenderit. Veniam eiusmod elit occaecat voluptate tempor culpa consectetur ea ut exercitation eiusmod exercitation qui.\r\n", + "Aliqua esse pariatur nulla veniam velit ea. Aliquip consectetur tempor ex magna sit aliquip exercitation veniam. Dolor ullamco minim commodo pariatur. Et amet reprehenderit dolore proident elit tempor eiusmod eu incididunt enim ullamco. Adipisicing id officia incididunt esse dolor sunt cupidatat do deserunt mollit do non. Magna ut officia fugiat adipisicing quis ea cillum laborum dolore ad nostrud magna minim est. Dolor voluptate officia proident enim ea deserunt eu voluptate dolore proident laborum officia ea.\r\n", + "Culpa aute consequat esse fugiat cupidatat minim voluptate voluptate eiusmod irure anim elit. Do eiusmod culpa laboris consequat incididunt minim nostrud eiusmod commodo velit ea ullamco proident. Culpa pariatur magna ut mollit nisi. Ea officia do magna deserunt minim nisi tempor ea deserunt veniam cillum exercitation esse.\r\n", + "Anim ullamco nostrud commodo Lorem. Do sunt laborum exercitation proident proident magna. Lorem officia laborum laborum dolor sunt duis commodo Lorem. Officia aute adipisicing ea cupidatat ea dolore. Aliquip adipisicing pariatur consectetur aliqua sit amet officia reprehenderit laborum culpa. Occaecat Lorem eu nisi do Lorem occaecat enim eiusmod laboris id quis. Ad mollit adipisicing sunt adipisicing esse.\r\n", + "Laborum quis sit adipisicing cupidatat. Veniam Lorem eiusmod esse esse sint nisi labore elit et. Deserunt aliqua mollit ut commodo aliqua non incididunt ipsum reprehenderit consectetur. Eiusmod nulla minim laboris Lorem ea Lorem aute tempor pariatur in sit. Incididunt culpa ut do irure amet irure cupidatat est anim anim culpa occaecat. Est velit consectetur eiusmod veniam reprehenderit officia sunt occaecat eiusmod ut sunt occaecat amet.\r\n", + "Elit minim aute fugiat nulla ex quis. Labore fugiat sint nostrud amet quis culpa excepteur in. Consectetur exercitation cupidatat laborum sit. Aute nisi eu aliqua est deserunt eiusmod commodo dolor id. Mollit laborum esse sint ipsum voluptate reprehenderit velit et. Veniam aliquip enim in veniam Lorem voluptate quis deserunt consequat qui commodo ut excepteur aute.\r\n", + "Dolore deserunt veniam aute nisi labore sunt et voluptate irure nisi anim ea. Magna nisi quis anim mollit nisi est dolor do ex aliquip elit aliquip ipsum minim. Dolore est officia nostrud eiusmod ex laborum ea amet est. Officia culpa non est et tempor consectetur exercitation tempor eiusmod enim. Ea tempor laboris qui amet ex nisi culpa dolore consectetur incididunt sunt sunt. Lorem aliquip incididunt magna do et ullamco ex elit aliqua eiusmod qui. Commodo amet dolor sint incididunt ex veniam non Lorem fugiat.\r\n", + "Officia culpa enim voluptate dolore commodo. Minim commodo aliqua minim ex sint excepteur cupidatat adipisicing eu irure. Anim magna deserunt anim Lorem non.\r\n", + "Cupidatat aliquip nulla excepteur sunt cupidatat cupidatat laborum cupidatat exercitation. Laboris minim ex cupidatat culpa elit. Amet enim reprehenderit aliqua laborum est tempor exercitation cupidatat ex dolore do. Do incididunt labore fugiat commodo consectetur nisi incididunt irure sit culpa sit. Elit aute occaecat qui excepteur velit proident cillum qui aliqua ex do ex. Dolore irure ex excepteur veniam id proident mollit Lorem.\r\n", + "Ad commodo cillum duis deserunt elit officia consectetur veniam eiusmod. Reprehenderit et veniam ad commodo reprehenderit magna elit laboris sunt non quis. Adipisicing dolor aute proident ea magna sunt et proident in consectetur.\r\n", + "Veniam exercitation esse esse veniam est nisi. Minim velit incididunt sint aute dolor anim. Fugiat cupidatat id ad nisi in voluptate dolor culpa eiusmod magna eiusmod amet id. Duis aliquip labore et ex amet amet aliquip laborum eiusmod ipsum. Quis qui ut duis duis. Minim in voluptate reprehenderit aliqua.\r\n", + "Elit ut pariatur dolor veniam ipsum consequat. Voluptate Lorem mollit et esse dolore mollit Lorem ad. Elit nostrud eu Lorem labore mollit minim cupidatat officia quis minim dolore incididunt. In cillum aute cillum ut.\r\n", + "Commodo laborum deserunt ut cupidatat pariatur ullamco in esse anim exercitation cillum duis. Consectetur incididunt sit esse Lorem in aute. Eiusmod mollit Lorem consequat minim reprehenderit laborum enim excepteur irure nisi elit. Laborum esse proident aute aute proident adipisicing laborum. Pariatur tempor duis incididunt qui velit pariatur ut officia ea mollit labore dolore. Cillum pariatur minim ullamco sunt incididunt culpa id ullamco exercitation consectetur. Ea exercitation consequat reprehenderit ut ullamco velit eu ad velit magna excepteur eiusmod.\r\n", + "Eu deserunt magna laboris laborum laborum in consequat dolore. Officia proident consectetur proident do occaecat minim pariatur officia ipsum sit non velit officia cillum. Laborum excepteur labore eu minim eiusmod. Sit anim dolore cillum ad do minim culpa sit est ad.\r\n", + "Cupidatat dolor nostrud Lorem sint consequat quis. Quis labore sint incididunt officia tempor. Fugiat nostrud in elit reprehenderit dolor. Nisi sit enim officia minim est adipisicing nulla aute labore nulla nostrud cupidatat est. Deserunt dolore qui irure Lorem esse voluptate velit qui nostrud.\r\n", + "Fugiat Lorem amet nulla nisi qui amet laboris enim cillum. Dolore occaecat exercitation id labore velit do commodo ut cupidatat laborum velit fugiat mollit. Ut et aliqua pariatur occaecat. Lorem occaecat dolore quis esse enim cupidatat exercitation ut tempor sit laboris fugiat adipisicing. Est tempor ex irure consectetur ipsum magna labore. Lorem non quis qui minim nisi magna amet aliquip ex cillum fugiat tempor.\r\n", + "Aliquip eiusmod laborum ipsum deserunt velit esse do magna excepteur consectetur exercitation sit. Minim ullamco reprehenderit commodo nostrud exercitation id irure ex qui ullamco sit esse laboris. Nulla cillum non minim qui cillum nisi aute proident. Dolor anim culpa elit quis excepteur aliqua eiusmod. Elit ea est excepteur consectetur sunt eiusmod enim id commodo irure amet et pariatur laboris. Voluptate magna ad magna dolore cillum cillum irure laboris ipsum officia id Lorem veniam.\r\n", + "Esse sunt elit est aliquip cupidatat commodo deserunt. Deserunt pariatur ipsum qui ad esse esse magna qui cillum laborum. Exercitation veniam pariatur elit amet enim.\r\n", + "Esse quis in id elit nulla occaecat incididunt. Et amet Lorem mollit in veniam do. Velit mollit Lorem consequat commodo Lorem aliquip cupidatat. Minim consequat nostrud nulla in nostrud.\r\n", + "Cillum nulla et eu est nostrud quis elit cupidatat dolor enim excepteur exercitation nisi voluptate. Nulla dolore non ex velit et qui tempor proident id deserunt nisi eu. Tempor ad Lorem ipsum reprehenderit in anim. Anim dolore ullamco enim deserunt quis ex id exercitation velit. Magna exercitation fugiat mollit pariatur ipsum ex consectetur nostrud. Id dolore officia nostrud excepteur laborum. Magna incididunt elit ipsum pariatur adipisicing enim duis est qui commodo velit aute.\r\n", + "Quis esse ex qui nisi dolor. Ullamco laborum dolor esse laboris eiusmod ea magna laboris ea esse ut. Dolore ipsum pariatur veniam sint mollit. Lorem ea proident fugiat ullamco ut nisi culpa eu exercitation exercitation aliquip veniam laborum consectetur.\r\n", + "Pariatur veniam laboris sit aliquip pariatur tempor aute sunt id et ut. Laboris excepteur eiusmod nisi qui quis elit enim ut cupidatat. Et et laborum in fugiat veniam consectetur ipsum laboris duis excepteur ullamco aliqua dolor Lorem. Aliqua ex amet sint anim cupidatat nisi ipsum anim et sunt deserunt. Occaecat culpa ut tempor cillum pariatur ex tempor.\r\n", + "Dolor deserunt eiusmod magna do officia voluptate excepteur est cupidatat. Veniam qui cupidatat amet anim est qui consectetur sit commodo commodo ea ad. Enim ad adipisicing qui nostrud. Non nulla esse ullamco nulla et ex.\r\n", + "Id ullamco ea consectetur est incididunt deserunt et esse. Elit nostrud voluptate eiusmod ut. Excepteur adipisicing qui cupidatat consequat labore id. Qui dolor aliqua do dolore do cupidatat labore ex consectetur ea sit cillum. Sint veniam eiusmod in consectetur consequat fugiat et mollit ut fugiat esse dolor adipisicing.\r\n", + "Ea magna proident labore duis pariatur. Esse cillum aliquip dolor duis fugiat ea ex officia ea irure. Sint elit nisi pariatur sunt nostrud exercitation ullamco culpa magna do.\r\n", + "Minim aliqua voluptate dolor consequat sint tempor deserunt amet magna excepteur. Irure do voluptate magna velit. Nostrud in reprehenderit magna officia nostrud. Cupidatat nulla irure laboris non fugiat ex ex est cupidatat excepteur officia aute velit duis. Sit voluptate id ea exercitation deserunt culpa voluptate nostrud est adipisicing incididunt. Amet proident laborum commodo magna ipsum quis.\r\n", + "Ipsum consectetur consectetur excepteur tempor eiusmod ea fugiat aute velit magna in officia sunt. Sit ut sunt dolore cupidatat dolor adipisicing. Veniam nisi adipisicing esse reprehenderit amet aliqua voluptate ex commodo occaecat est voluptate mollit sunt. Pariatur aliqua qui qui in dolor. Fugiat reprehenderit sit nostrud do sint esse. Tempor sit irure adipisicing ea pariatur duis est sit est incididunt laboris quis do. Et voluptate anim minim aliquip excepteur consequat nisi anim pariatur aliquip ut ipsum dolor magna.\r\n", + "Cillum sit labore excepteur magna id aliqua exercitation consequat laborum Lorem id pariatur nostrud. Lorem qui est labore sint cupidatat sint excepteur nulla in eu aliqua et. Adipisicing velit do enim occaecat laboris quis excepteur ipsum dolor occaecat Lorem dolore id exercitation.\r\n", + "Incididunt in laborum reprehenderit eiusmod irure ex. Elit duis consequat minim magna. Esse consectetur aliquip cillum excepteur excepteur fugiat. Sint tempor consequat minim reprehenderit consectetur adipisicing dolor id Lorem elit non. Occaecat esse quis mollit ea et sint aute fugiat qui tempor. Adipisicing tempor duis non dolore irure elit deserunt qui do.\r\n", + "Labore fugiat eiusmod sint laborum sit duis occaecat. Magna in laborum non cillum excepteur nostrud sit proident pariatur voluptate voluptate adipisicing exercitation occaecat. Ad non dolor aute ex sint do do minim exercitation veniam laborum irure magna ea. Magna do non quis sit consequat Lorem aliquip.\r\n", + "Velit anim do laborum laboris laborum Lorem. Sunt do Lorem amet ipsum est sint velit sit do voluptate mollit veniam enim. Commodo do deserunt in pariatur ut elit sint elit deserunt ea. Ad dolor anim consequat aliquip ut mollit nostrud tempor sunt mollit elit. Reprehenderit laboris labore excepteur occaecat veniam adipisicing cupidatat esse. Ad enim aliquip ea minim excepteur magna. Sint velit veniam pariatur qui dolor est adipisicing ex laboris.\r\n", + "Ea cupidatat ex nulla in sunt est sit dolor enim ad. Eu tempor consequat cupidatat consequat ex incididunt sint culpa. Est Lorem Lorem non cupidatat sunt ut aliqua non nostrud do ullamco. Reprehenderit ad ad nulla nostrud do nulla in. Ipsum adipisicing commodo mollit ipsum exercitation. Aliqua ea anim anim est elit. Ea incididunt consequat minim ad sunt eu cillum.\r\n", + "Tempor quis excepteur eiusmod cupidatat ipsum occaecat id et occaecat. Eiusmod magna aliquip excepteur id amet elit. Ullamco dolore amet anim dolor enim ea magna magna elit. Occaecat magna pariatur in deserunt consectetur officia aliquip ullamco ex aute anim. Minim laborum eu sit elit officia esse do irure pariatur tempor et reprehenderit ullamco labore.\r\n", + "Sit tempor eu minim dolore velit pariatur magna duis reprehenderit ea nulla in. Amet est do consectetur commodo do adipisicing adipisicing in amet. Cillum id ut commodo do pariatur duis aliqua nisi sint ad irure officia reprehenderit. Mollit labore id enim fugiat ullamco irure mollit cupidatat. Quis nisi amet labore eu dolor occaecat commodo aliqua laboris deserunt excepteur deserunt officia. Aliqua non ut sit ad. Laborum veniam ad velit minim dolore ea id magna dolor qui in.\r\n", + "Dolore nostrud ipsum aliqua pariatur id reprehenderit enim ad eiusmod qui. Deserunt anim commodo pariatur excepteur velit eu irure nulla ex labore ipsum aliqua minim aute. Id consequat amet tempor aliquip ex elit adipisicing est do. Eu enim Lorem consectetur minim id irure nulla culpa. Consectetur do consequat aute tempor anim. Qui ad non elit dolor est adipisicing nisi amet cillum sunt quis anim laboris incididunt. Incididunt proident adipisicing labore Lorem.\r\n", + "Et reprehenderit ea officia veniam. Aliquip ullamco consequat elit nisi magna mollit id elit. Amet amet sint velit labore ad nisi. Consectetur tempor id dolor aliqua esse deserunt amet. Qui laborum enim proident voluptate aute eu aute aute sit sit incididunt eu. Sunt ullamco nisi nostrud labore commodo non consectetur quis do duis minim irure. Tempor sint dolor sint aliquip dolore nostrud fugiat.\r\n", + "Aute ullamco quis nisi ut excepteur nostrud duis elit. Veniam ex ad incididunt veniam voluptate. Commodo dolore ullamco sit sint adipisicing proident amet aute duis deserunt.\r\n", + "Labore velit eu cillum nisi. Laboris do cupidatat et non duis cillum. Ullamco dolor tempor cupidatat voluptate laborum ullamco ea duis.\r\n", + "Deserunt consequat aliqua duis aliquip nostrud nostrud dolore nisi. Culpa do sint laborum consectetur ipsum quis laborum laborum pariatur eiusmod. Consectetur laboris ad ad ut quis. Ullamco laboris qui velit id laborum voluptate qui aute nostrud aliquip ea.\r\n", + "Ad cillum anim ex est consectetur mollit id in. Non enim aliquip consequat qui deserunt commodo cillum ad laborum fugiat. Dolor deserunt amet laborum tempor adipisicing voluptate dolor pariatur dolor cillum. Eu mollit ex sunt officia veniam qui est sunt proident. Non aliqua qui elit eu cupidatat ex enim ex proident. Lorem sit minim ullamco officia cupidatat duis minim. Exercitation laborum deserunt voluptate culpa tempor quis nulla id pariatur.\r\n", + "Nostrud quis consectetur ut aliqua excepteur elit consectetur occaecat. Occaecat voluptate Lorem pariatur consequat ullamco fugiat minim. Anim voluptate eu eu cillum tempor dolore aliquip aliqua. Fugiat incididunt ut tempor amet minim. Voluptate nostrud minim pariatur non excepteur ullamco.\r\n", + "Dolore nulla velit officia exercitation irure laboris incididunt anim in laborum in fugiat ut proident. Fugiat aute id consequat fugiat officia ut. Labore sint amet proident amet sint nisi laboris amet id ullamco culpa quis consequat proident. Magna do fugiat veniam dolore elit irure minim. Esse ullamco excepteur labore tempor labore fugiat dolore nisi cupidatat irure dolor pariatur. Magna excepteur laboris nisi eiusmod sit pariatur mollit.\r\n", + "In enim aliquip officia ea ad exercitation cillum culpa occaecat dolore Lorem. Irure cillum commodo adipisicing sunt pariatur ea duis fugiat exercitation laboris culpa ullamco aute. Ut voluptate exercitation qui dolor. Irure et duis elit consequat deserunt proident.\r\n", + "Officia ea Lorem sunt culpa id et tempor excepteur enim deserunt proident. Dolore aliquip dolor laboris cillum proident velit. Et culpa occaecat exercitation cupidatat irure sint adipisicing excepteur pariatur incididunt ad occaecat. Qui proident ipsum cillum minim. Quis ut culpa irure aliqua minim fugiat. In voluptate cupidatat fugiat est laborum dolor esse in pariatur voluptate.\r\n", + "Voluptate enim ipsum officia aute ea adipisicing nisi ut ex do aliquip amet. Reprehenderit enim voluptate tempor ex adipisicing culpa. Culpa occaecat voluptate dolor mollit ipsum exercitation labore et tempor sit ea consectetur aliqua. Elit elit sit minim ea ea commodo do tempor cupidatat irure dolore. Occaecat esse adipisicing anim eiusmod commodo fugiat mollit amet. Incididunt tempor tempor qui occaecat cupidatat in.\r\n", + "Ut qui anim velit enim aliquip do ut nulla labore. Mollit ut commodo ut eiusmod consectetur laboris aliqua qui voluptate culpa fugiat incididunt elit. Lorem ullamco esse elit elit. Labore amet incididunt ea nulla aliquip eiusmod. Sit nulla est voluptate officia ipsum aute aute cillum tempor deserunt. Laboris commodo eiusmod labore sunt aute excepteur ea consectetur reprehenderit veniam nisi. Culpa nisi sint sunt sint tempor laboris dolore cupidatat.\r\n", + "Duis cillum qui nisi duis amet velit ad cillum ut elit aute sint ad. Amet laboris pariatur excepteur ipsum Lorem aliqua veniam Lorem quis mollit cupidatat aliqua exercitation. Pariatur ex ullamco sit commodo cillum eiusmod ut proident elit cillum. Commodo ut ipsum excepteur occaecat sint elit consequat ex dolor adipisicing consectetur id ut ad. Velit sit eiusmod est esse tempor incididunt consectetur eiusmod duis commodo veniam.\r\n", + "Ut sunt qui officia anim laboris exercitation Lorem quis laborum do eiusmod officia. Enim consectetur occaecat fugiat cillum cillum. Dolore dolore nostrud in commodo fugiat mollit consequat occaecat non et et elit ullamco. Sit voluptate minim ut est culpa velit nulla fugiat reprehenderit eu aliquip adipisicing labore. Sit minim minim do dolor dolor. Lorem Lorem labore exercitation magna veniam eiusmod do.\r\n", + "Fugiat dolor adipisicing quis aliquip aute dolore. Qui proident anim elit veniam ex aliquip eiusmod ipsum sunt pariatur est. Non fugiat duis do est officia adipisicing.\r\n", + "Nulla deserunt do laboris cupidatat veniam do consectetur ipsum elit veniam in mollit eu. Ea in consequat cupidatat laboris sint fugiat irure. In commodo esse reprehenderit deserunt minim velit ullamco enim eu cupidatat tempor ex. Ullamco in non id culpa amet occaecat culpa nostrud id. Non occaecat culpa magna incididunt.\r\n", + "Enim laboris ex mollit reprehenderit eiusmod exercitation magna. Exercitation Lorem ex mollit non non culpa labore enim. Adipisicing labore dolore incididunt do amet aliquip excepteur ad et nostrud officia aute veniam voluptate. Fugiat enim eiusmod Lorem esse. Minim ullamco commodo consequat ex commodo aliqua eu nulla eu. Veniam non enim nulla ut Lorem nostrud minim sint duis.\r\n", + "Enim duis consectetur in ullamco cillum veniam nulla amet. Exercitation nisi sunt sunt duis in culpa nisi magna ex id ipsum laboris reprehenderit qui. Officia pariatur qui ex fugiat veniam et sunt sit nostrud. Veniam ullamco tempor fugiat minim Lorem proident velit in eiusmod elit. Enim minim excepteur aute aliquip ex magna commodo dolore qui et labore. Proident eu aliquip cillum dolor. Nostrud ipsum ut irure consequat fugiat nulla proident occaecat laborum.\r\n", + "Amet duis eiusmod sunt adipisicing esse ex nostrud consectetur voluptate cillum. Ipsum occaecat sit et anim velit irure ea incididunt cupidatat ullamco in nisi quis. Esse officia ipsum commodo qui quis qui do. Commodo aliquip amet aute sit sit ut cupidatat elit nostrud.\r\n", + "Laboris laboris sit mollit cillum nulla deserunt commodo culpa est commodo anim id anim sit. Officia id consectetur velit incididunt est dolor sunt ipsum magna aliqua consectetur. Eiusmod pariatur minim deserunt cupidatat veniam Lorem aliquip sunt proident eu Lorem sit dolor fugiat. Proident qui ut ex in incididunt nulla nulla dolor ex laboris ea ad.\r\n", + "Ex incididunt enim labore nulla cupidatat elit. Quis ut incididunt incididunt non irure commodo do mollit cillum anim excepteur. Qui consequat laborum dolore elit tempor aute ut nulla pariatur eu ullamco veniam. Nisi non velit labore in commodo excepteur culpa nulla tempor cillum. Ipsum qui sit sint reprehenderit ut labore incididunt dolor aliquip sunt. Reprehenderit occaecat tempor nisi laborum.\r\n", + "Lorem officia ullamco eu occaecat in magna eiusmod consectetur nisi aliqua mollit esse. Ullamco ex aute nostrud pariatur do enim cillum sint do fugiat nostrud culpa tempor. Do aliquip excepteur nostrud culpa eu pariatur eiusmod cillum excepteur do. Est sunt non quis cillum voluptate ex.\r\n", + "Deserunt consectetur tempor irure mollit qui tempor et. Labore enim eu irure laboris in. Nisi in tempor ex occaecat amet cupidatat laboris occaecat amet minim ut magna incididunt id. Consequat cillum laborum commodo mollit. Et magna culpa sunt dolore consequat laboris et sit. Deserunt qui voluptate excepteur dolor. Eu qui amet est proident.\r\n", + "Eu elit minim eiusmod occaecat eu nostrud dolor qui ut elit. Sunt dolore proident ea eu do eiusmod fugiat incididunt pariatur duis amet Lorem nisi ut. Adipisicing quis veniam cupidatat Lorem sint culpa sunt veniam sint. Excepteur eu exercitation est magna pariatur veniam dolore qui fugiat labore proident eiusmod cillum. Commodo reprehenderit elit proident duis sint magna.\r\n", + "Ut aliquip pariatur deserunt nostrud commodo ad proident est exercitation. Sit minim do ea enim sint officia nisi incididunt laborum. Ex amet duis commodo fugiat. Ut aute tempor deserunt irure occaecat aliquip voluptate cillum aute elit qui nostrud.\r\n", + "Irure et quis consectetur sit est do sunt aliquip eu. Cupidatat pariatur consequat dolore consectetur. Adipisicing magna velit mollit occaecat do id. Nisi pariatur cupidatat cillum incididunt excepteur consectetur excepteur do laborum deserunt irure pariatur cillum.\r\n", + "Adipisicing esse incididunt cillum est irure consequat irure ad aute voluptate. Incididunt do occaecat nostrud do ipsum pariatur Lorem qui laboris et pariatur. Est exercitation dolor culpa ad velit ut et.\r\n", + "Sit eiusmod id enim ad ex dolor pariatur do. Ullamco occaecat quis dolor minim non elit labore amet est. Commodo velit eu nulla eiusmod ullamco. Incididunt anim pariatur aute eiusmod veniam tempor enim officia elit id. Elit Lorem est commodo dolore nostrud. Labore et consectetur do exercitation veniam laboris incididunt aliqua proident dolore ea officia cupidatat. Velit laboris aliquip deserunt labore commodo.\r\n", + "Proident nostrud labore eu nostrud. Excepteur ut in velit labore ea proident labore ea sint cillum. Incididunt ipsum consectetur officia irure sit pariatur veniam id velit officia mollit. Adipisicing magna voluptate velit excepteur enim consectetur incididunt voluptate tempor occaecat fugiat velit excepteur labore. Do do incididunt qui nisi voluptate enim. Laboris aute sit voluptate cillum pariatur minim excepteur ullamco mollit deserunt.\r\n", + "Excepteur laborum adipisicing nisi elit fugiat tempor. Elit laboris qui enim labore duis. Proident tempor in consectetur proident excepteur do ex laboris sit.\r\n", + "Dolore do ea incididunt do duis dolore eu labore nisi cupidatat voluptate amet incididunt minim. Nulla pariatur mollit cupidatat adipisicing nulla et. Dolor aliquip in ex magna excepteur. Nulla consequat minim consequat ullamco dolor laboris ullamco eu reprehenderit duis nostrud pariatur.\r\n", + "Id nisi labore duis qui. Incididunt laboris tempor aute do sit. Occaecat excepteur est mollit ea in mollit ullamco est amet reprehenderit.\r\n", + "Aute labore ipsum velit non voluptate eiusmod et reprehenderit cupidatat occaecat. Lorem tempor tempor consectetur exercitation qui nostrud sunt cillum quis ut non dolore. Reprehenderit consequat reprehenderit laborum qui pariatur anim et officia est cupidatat enim velit velit.\r\n", + "Commodo ex et fugiat cupidatat non adipisicing commodo. Minim ad dolore fugiat mollit cupidatat aliqua sunt dolor sit. Labore esse labore velit aute enim. Nulla duis incididunt est aliquip consectetur elit qui incididunt minim minim labore amet sit cillum.\r\n" +] \ No newline at end of file diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/readme.txt b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/readme.txt new file mode 100644 index 0000000000..da1dae675e --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/bin/types/readme.txt @@ -0,0 +1 @@ +Test data obtained from https://github.com/xpol/lua-rapidjson/tree/master/performance diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/CMakeLists.txt b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/CMakeLists.txt new file mode 100644 index 0000000000..c1f165a37a --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/CMakeLists.txt @@ -0,0 +1,25 @@ +find_package(Doxygen) + +IF(NOT DOXYGEN_FOUND) + MESSAGE(STATUS "No Doxygen found. Documentation won't be built") +ELSE() + file(GLOB SOURCES ${CMAKE_CURRENT_LIST_DIR}/../include/*) + file(GLOB MARKDOWN_DOC ${CMAKE_CURRENT_LIST_DIR}/../doc/*.md) + list(APPEND MARKDOWN_DOC ${CMAKE_CURRENT_LIST_DIR}/../readme.md) + + CONFIGURE_FILE(Doxyfile.in Doxyfile @ONLY) + CONFIGURE_FILE(Doxyfile.zh-cn.in Doxyfile.zh-cn @ONLY) + + add_custom_command(OUTPUT html + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.zh-cn + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/html + DEPENDS ${MARKDOWN_DOC} ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile* + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../ + ) + + add_custom_target(doc ALL DEPENDS html) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html + DESTINATION ${DOC_INSTALL_DIR} + COMPONENT doc) +ENDIF() diff --git a/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/Doxyfile.in b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/Doxyfile.in new file mode 100644 index 0000000000..ca14233960 --- /dev/null +++ b/plugins/devtools/bridge/third_party/rapidjson-1.1.0/doc/Doxyfile.in @@ -0,0 +1,2369 @@ +# Doxyfile 1.8.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = RapidJSON + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "A fast JSON parser/generator for C++ with both SAX/DOM style API" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = YES + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = $(RAPIDJSON_SECTIONS) + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = readme.md \ + CHANGELOG.md \ + include/rapidjson/rapidjson.h \ + include/ \ + doc/features.md \ + doc/tutorial.md \ + doc/pointer.md \ + doc/stream.md \ + doc/encoding.md \ + doc/dom.md \ + doc/sax.md \ + doc/schema.md \ + doc/performance.md \ + doc/internals.md \ + doc/faq.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.inc \ + *.md + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = ./include/rapidjson/msinttypes/ + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = internal + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = ./doc + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = readme.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# compiled with the --with-libclang option. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = ./doc/misc/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = ./doc/misc/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = ./doc/misc/doxygenextra.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /