node-gtk 0.14.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -196
- package/bin/node-gtk.js +31 -0
- package/lib/binding/node-v127-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v131-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v137-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v147-linux-x64/node_gtk.node +0 -0
- package/lib/bootstrap.js +22 -4
- package/lib/index.js +25 -0
- package/lib/overrides/Gtk-4.0.js +4 -2
- package/lib/register-class.js +1 -1
- package/package.json +12 -5
- package/scripts/build-test-fixtures.js +237 -0
- package/scripts/ci.sh +7 -3
- package/src/boxed.cc +47 -17
- package/src/boxed.h +15 -1
- package/src/callback.cc +14 -2
- package/src/callback.h +1 -0
- package/src/closure.cc +105 -10
- package/src/debug.cc +4 -3
- package/src/function.cc +97 -40
- package/src/function.h +4 -3
- package/src/gi.cc +70 -3
- package/src/gobject.cc +54 -36
- package/src/loop.cc +1 -1
- package/src/modules/cairo/glyph.cc +1 -0
- package/src/modules/cairo/text-cluster.cc +1 -0
- package/src/modules/system.cc +2 -2
- package/src/type.cc +3 -2
- package/src/value.cc +358 -44
- package/src/value.h +27 -5
- package/tools/README.md +91 -0
- package/tools/generate-types.js +1045 -0
- package/lib/binding/node-v102-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v108-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v115-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v93-linux-x64/node_gtk.node +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* build-test-fixtures.js
|
|
3
|
+
*
|
|
4
|
+
* Makes the gobject-introspection test libraries (Utility, GIMarshallingTests,
|
|
5
|
+
* Regress) available for node-gtk's test suite. These libraries systematically
|
|
6
|
+
* exercise marshalling of every GObject type in every direction
|
|
7
|
+
* (in/out/inout/return), so they let us test node-gtk's type conversions
|
|
8
|
+
* exhaustively instead of ad-hoc.
|
|
9
|
+
*
|
|
10
|
+
* Strategy: always build from a single pinned upstream source revision, on
|
|
11
|
+
* every platform. Distro-shipped copies (the gobject-introspection package's
|
|
12
|
+
* bundled tests, or prebuilt gjs `installed-tests` typelibs) vary wildly by
|
|
13
|
+
* version — functions and types present on one machine are absent on another —
|
|
14
|
+
* which makes the test suite non-portable. Pinning one revision of the
|
|
15
|
+
* canonical `gobject-introspection-tests` repo and compiling it ourselves gives
|
|
16
|
+
* every machine (dev, Linux CI, macOS CI) the exact same API surface.
|
|
17
|
+
*
|
|
18
|
+
* The pinned sources are downloaded once (as a tarball) and cached under
|
|
19
|
+
* tests/gi-fixtures/.src/; the compiled output goes to tests/gi-fixtures/
|
|
20
|
+
* ({NAME}-1.0.typelib + lib{name}.so). Both are git-ignored.
|
|
21
|
+
*
|
|
22
|
+
* Run directly (`node scripts/build-test-fixtures.js`) or via `npm test`
|
|
23
|
+
* (it runs as a pretest step). Idempotent: existing fixtures are reused unless
|
|
24
|
+
* --force is passed. To bump the upstream revision, change SOURCE_REF.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs')
|
|
28
|
+
const path = require('path')
|
|
29
|
+
const { execFileSync, execSync } = require('child_process')
|
|
30
|
+
|
|
31
|
+
const FIXTURES_DIR = path.join(__dirname, '..', 'tests', 'gi-fixtures')
|
|
32
|
+
const SRC_CACHE_DIR = path.join(FIXTURES_DIR, '.src')
|
|
33
|
+
const FORCE = process.argv.includes('--force')
|
|
34
|
+
const VERBOSE = process.argv.includes('--verbose') || process.env.VERBOSE
|
|
35
|
+
|
|
36
|
+
// Canonical upstream test sources, pinned to a specific revision so every
|
|
37
|
+
// machine builds the identical API. Bump SOURCE_REF to update.
|
|
38
|
+
const SOURCE_REPO = 'https://gitlab.gnome.org/GNOME/gobject-introspection-tests'
|
|
39
|
+
const SOURCE_REF = '5987255086f59ca271a3a0aa53fbbb15b189be65'
|
|
40
|
+
|
|
41
|
+
// Each fixture mirrors the upstream meson.build recipe: the GI namespace, its
|
|
42
|
+
// shared-library basename, the source/header files that make it up, the
|
|
43
|
+
// pkg-config packages it links, and the GI namespaces its typelib includes.
|
|
44
|
+
// Order matters: Regress's typelib includes Utility, so Utility is built first.
|
|
45
|
+
const FIXTURES = [
|
|
46
|
+
{
|
|
47
|
+
namespace: 'Utility',
|
|
48
|
+
library: 'utility',
|
|
49
|
+
identifierPrefix: 'Utility',
|
|
50
|
+
symbolPrefix: 'utility_',
|
|
51
|
+
sources: ['utility.c'],
|
|
52
|
+
headers: ['utility.h'],
|
|
53
|
+
packages: ['gobject-2.0'],
|
|
54
|
+
includes: ['GObject-2.0'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
namespace: 'GIMarshallingTests',
|
|
58
|
+
library: 'gimarshallingtests',
|
|
59
|
+
identifierPrefix: 'GIMarshallingTests',
|
|
60
|
+
symbolPrefix: 'gi_marshalling_tests_',
|
|
61
|
+
sources: ['gimarshallingtests.c', 'gimarshallingtestsextra.c'],
|
|
62
|
+
headers: ['gimarshallingtests.h', 'gimarshallingtestsextra.h'],
|
|
63
|
+
packages: ['gobject-2.0', 'gio-2.0'],
|
|
64
|
+
includes: ['Gio-2.0'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
namespace: 'Regress',
|
|
68
|
+
library: 'regress',
|
|
69
|
+
identifierPrefix: 'Regress',
|
|
70
|
+
symbolPrefix: 'regress_',
|
|
71
|
+
sources: ['annotation.c', 'drawable.c', 'foo.c', 'regress.c', 'regressextra.c'],
|
|
72
|
+
headers: ['annotation.h', 'drawable.h', 'foo.h', 'regress.h', 'regressextra.h'],
|
|
73
|
+
packages: ['gobject-2.0', 'gio-2.0', 'cairo', 'cairo-gobject'],
|
|
74
|
+
includes: ['Gio-2.0', 'cairo-1.0', 'Utility-1.0'],
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
function log(...args) {
|
|
79
|
+
console.log('[fixtures]', ...args)
|
|
80
|
+
}
|
|
81
|
+
function vlog(...args) {
|
|
82
|
+
if (VERBOSE) console.log('[fixtures]', ...args)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function pkgConfig(args) {
|
|
86
|
+
return execSync(`pkg-config ${args}`, { encoding: 'utf8' }).trim()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function which(bin) {
|
|
90
|
+
try {
|
|
91
|
+
return execFileSync('sh', ['-c', `command -v ${bin}`], { encoding: 'utf8' }).trim()
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fixtureIsPresent(fixture) {
|
|
98
|
+
const typelib = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.typelib`)
|
|
99
|
+
const lib = path.join(FIXTURES_DIR, `lib${fixture.library}.so`)
|
|
100
|
+
return fs.existsSync(typelib) && fs.existsSync(lib)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Download + extract the pinned upstream sources once; return the dir holding
|
|
104
|
+
// the .c/.h files. Cached under SRC_CACHE_DIR/<ref>, keyed by revision.
|
|
105
|
+
function ensureSources() {
|
|
106
|
+
const destDir = path.join(SRC_CACHE_DIR, SOURCE_REF)
|
|
107
|
+
// The repo's files live at the tarball root; a marker file confirms a
|
|
108
|
+
// complete previous extraction.
|
|
109
|
+
if (fs.existsSync(path.join(destDir, 'gimarshallingtests.c'))) {
|
|
110
|
+
vlog(`sources present at ${destDir}`)
|
|
111
|
+
return destDir
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fs.mkdirSync(SRC_CACHE_DIR, { recursive: true })
|
|
115
|
+
const tarball = path.join(SRC_CACHE_DIR, `${SOURCE_REF}.tar.gz`)
|
|
116
|
+
const url = `${SOURCE_REPO}/-/archive/${SOURCE_REF}/src-${SOURCE_REF}.tar.gz`
|
|
117
|
+
|
|
118
|
+
log(`downloading test sources @ ${SOURCE_REF.slice(0, 12)}`)
|
|
119
|
+
execFileSync('curl', ['-fsSL', url, '-o', tarball], { stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
120
|
+
|
|
121
|
+
// The tarball extracts to a single top-level dir; strip it into destDir.
|
|
122
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
123
|
+
execFileSync('tar', ['xzf', tarball, '-C', destDir, '--strip-components=1'],
|
|
124
|
+
{ stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
125
|
+
fs.unlinkSync(tarball)
|
|
126
|
+
return destDir
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildFixture(fixture, srcDir, tools) {
|
|
130
|
+
const sources = fixture.sources.map(s => path.join(srcDir, s))
|
|
131
|
+
const headers = fixture.headers.map(h => path.join(srcDir, h))
|
|
132
|
+
if (![...sources, ...headers].every(fs.existsSync)) {
|
|
133
|
+
throw new Error(`sources for ${fixture.namespace} missing in ${srcDir}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const cflags = pkgConfig(`--cflags ${fixture.packages.join(' ')}`)
|
|
137
|
+
const libs = pkgConfig(`--libs ${fixture.packages.join(' ')}`)
|
|
138
|
+
const libPath = path.join(FIXTURES_DIR, `lib${fixture.library}.so`)
|
|
139
|
+
const girPath = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.gir`)
|
|
140
|
+
const typelibPath = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.typelib`)
|
|
141
|
+
|
|
142
|
+
// 1. shared library
|
|
143
|
+
const cc = process.env.CC || 'cc'
|
|
144
|
+
const compileCmd =
|
|
145
|
+
`${cc} -shared -fPIC -I"${srcDir}" ${cflags} ` +
|
|
146
|
+
`${sources.map(s => `"${s}"`).join(' ')} ${libs} -o "${libPath}"`
|
|
147
|
+
vlog(compileCmd)
|
|
148
|
+
execSync(compileCmd, { stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
149
|
+
|
|
150
|
+
// 2. introspection data (.gir)
|
|
151
|
+
const scanArgs = [
|
|
152
|
+
...sources, ...headers,
|
|
153
|
+
'--warn-all',
|
|
154
|
+
'--namespace', fixture.namespace,
|
|
155
|
+
'--nsversion', '1.0',
|
|
156
|
+
'--identifier-prefix', fixture.identifierPrefix,
|
|
157
|
+
'--symbol-prefix', fixture.symbolPrefix,
|
|
158
|
+
'--library', fixture.library,
|
|
159
|
+
'--library-path', FIXTURES_DIR,
|
|
160
|
+
// So that --include of an already-built local fixture (e.g. Utility-1.0,
|
|
161
|
+
// which Regress depends on) resolves its .gir from our output dir.
|
|
162
|
+
'--add-include-path', FIXTURES_DIR,
|
|
163
|
+
...fixture.includes.flatMap(i => ['--include', i]),
|
|
164
|
+
...fixture.packages.flatMap(p => ['--pkg', p]),
|
|
165
|
+
'--cflags-begin', ...cflags.split(/\s+/).filter(Boolean), `-I${srcDir}`, '--cflags-end',
|
|
166
|
+
'--output', girPath,
|
|
167
|
+
]
|
|
168
|
+
vlog(tools.scanner, scanArgs.join(' '))
|
|
169
|
+
execFileSync(tools.scanner, scanArgs, {
|
|
170
|
+
stdio: VERBOSE ? 'inherit' : 'pipe',
|
|
171
|
+
env: { ...process.env, LD_LIBRARY_PATH: `${FIXTURES_DIR}:${process.env.LD_LIBRARY_PATH || ''}` },
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// 3. compiled typelib (--includedir resolves locally-built included girs)
|
|
175
|
+
execFileSync(tools.compiler, [girPath, '--includedir', FIXTURES_DIR, '--output', typelibPath], {
|
|
176
|
+
stdio: VERBOSE ? 'inherit' : 'pipe',
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
log(`built ${fixture.namespace}`)
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function main() {
|
|
184
|
+
fs.mkdirSync(FIXTURES_DIR, { recursive: true })
|
|
185
|
+
|
|
186
|
+
const allPresent = FIXTURES.every(fixtureIsPresent)
|
|
187
|
+
if (!FORCE && allPresent) {
|
|
188
|
+
vlog('all fixtures already present, skipping (use --force to rebuild)')
|
|
189
|
+
log(`available fixtures: ${FIXTURES.map(f => f.namespace).join(', ')}`)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const tools = {
|
|
194
|
+
scanner: which('g-ir-scanner'),
|
|
195
|
+
compiler: which('g-ir-compiler'),
|
|
196
|
+
}
|
|
197
|
+
if (!tools.scanner || !tools.compiler || !which('curl') || !which('tar')) {
|
|
198
|
+
log('WARNING: g-ir-scanner/g-ir-compiler/curl/tar not all available; ' +
|
|
199
|
+
'cannot build fixtures. Dependent tests will skip.')
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let srcDir
|
|
204
|
+
try {
|
|
205
|
+
srcDir = ensureSources()
|
|
206
|
+
} catch (e) {
|
|
207
|
+
log(`WARNING: could not fetch test sources: ${e.message.split('\n')[0]}`)
|
|
208
|
+
log('dependent tests will skip')
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const results = []
|
|
213
|
+
for (const fixture of FIXTURES) {
|
|
214
|
+
if (!FORCE && fixtureIsPresent(fixture)) {
|
|
215
|
+
vlog(`${fixture.namespace} already present, skipping`)
|
|
216
|
+
results.push({ fixture, ok: true })
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
let ok = false
|
|
220
|
+
try {
|
|
221
|
+
ok = buildFixture(fixture, srcDir, tools)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
log(`failed to build ${fixture.namespace}: ${e.message.split('\n')[0]}`)
|
|
224
|
+
}
|
|
225
|
+
if (!ok)
|
|
226
|
+
log(`WARNING: could not provide ${fixture.namespace}; dependent tests will skip`)
|
|
227
|
+
results.push({ fixture, ok })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const provided = results.filter(r => r.ok).map(r => r.fixture.namespace)
|
|
231
|
+
log(`available fixtures: ${provided.length ? provided.join(', ') : '(none)'}`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (require.main === module)
|
|
235
|
+
main()
|
|
236
|
+
|
|
237
|
+
module.exports = { FIXTURES_DIR, FIXTURES }
|
package/scripts/ci.sh
CHANGED
|
@@ -41,13 +41,17 @@ function npm_test() {
|
|
|
41
41
|
echo "### Running tests ###";
|
|
42
42
|
|
|
43
43
|
if [[ $(uname -s) == 'Darwin' ]]; then
|
|
44
|
-
export GST_PLUGIN_SYSTEM_PATH
|
|
44
|
+
export GST_PLUGIN_SYSTEM_PATH=$(brew --prefix gstreamer)/lib/gstreamer-1.0;
|
|
45
|
+
# This branch calls mocha directly (not `npm test`), so the pretest
|
|
46
|
+
# fixture build does not run automatically; do it here. Best-effort:
|
|
47
|
+
# marshalling tests skip if fixtures cannot be produced on macOS.
|
|
48
|
+
npm run build:test-fixtures || true;
|
|
45
49
|
npx mocha \
|
|
46
50
|
--skip=callback \
|
|
47
|
-
--skip=error \
|
|
48
51
|
tests/__run__.js
|
|
49
52
|
else
|
|
50
|
-
xvfb-run -a npm test --
|
|
53
|
+
xvfb-run -a npm test -- \
|
|
54
|
+
--skip=callback;
|
|
51
55
|
fi;
|
|
52
56
|
}
|
|
53
57
|
|
package/src/boxed.cc
CHANGED
|
@@ -43,6 +43,17 @@ size_t Boxed::GetSize (GIBaseInfo *boxed_info) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
gpointer AllocateBoxed (GType gtype, size_t size) {
|
|
47
|
+
// Registered boxed types are freed via g_boxed_free, which by GLib
|
|
48
|
+
// convention uses g_slice_free; match that with g_slice so freeing doesn't
|
|
49
|
+
// corrupt the slice allocator (#290, #213). Non-registered structs are
|
|
50
|
+
// freed with g_free, so allocate them with g_malloc0.
|
|
51
|
+
if (G_TYPE_IS_BOXED(gtype))
|
|
52
|
+
return g_slice_alloc0(size);
|
|
53
|
+
else
|
|
54
|
+
return g_malloc0(size);
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
static bool IsNoArgsConstructor (GIFunctionInfo *info) {
|
|
47
58
|
auto flags = g_function_info_get_flags (info);
|
|
48
59
|
return ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0
|
|
@@ -162,7 +173,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
162
173
|
return;
|
|
163
174
|
}
|
|
164
175
|
|
|
165
|
-
|
|
176
|
+
int n_constructor_args = -1;
|
|
166
177
|
|
|
167
178
|
void *boxed = NULL;
|
|
168
179
|
unsigned long size = 0;
|
|
@@ -174,11 +185,11 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
174
185
|
|
|
175
186
|
if (info[0]->IsExternal ()) {
|
|
176
187
|
/* The External case. This is how WrapperFromBoxed is called. */
|
|
177
|
-
|
|
188
|
+
auto ownership = (ResourceOwnership) Nan::To<int32_t> (info[1]).ToChecked();
|
|
178
189
|
|
|
179
190
|
boxed = External::Cast(*info[0])->Value();
|
|
180
191
|
|
|
181
|
-
if (
|
|
192
|
+
if (ownership == kCopy) {
|
|
182
193
|
if (gtype != G_TYPE_NONE) {
|
|
183
194
|
boxed = g_boxed_copy (gtype, boxed);
|
|
184
195
|
}
|
|
@@ -192,7 +203,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
192
203
|
}
|
|
193
204
|
}
|
|
194
205
|
else {
|
|
195
|
-
owns_memory =
|
|
206
|
+
owns_memory = ownership == kTransfer;
|
|
196
207
|
}
|
|
197
208
|
} else {
|
|
198
209
|
/* User code calling `new Pango.AttrList()` */
|
|
@@ -201,6 +212,8 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
201
212
|
|
|
202
213
|
if (constructorInfo != NULL) {
|
|
203
214
|
|
|
215
|
+
n_constructor_args = g_callable_info_get_n_args(constructorInfo);
|
|
216
|
+
|
|
204
217
|
FunctionInfo func(constructorInfo);
|
|
205
218
|
GIArgument return_value;
|
|
206
219
|
GError *error = NULL;
|
|
@@ -223,7 +236,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
223
236
|
boxed = return_value.v_pointer;
|
|
224
237
|
|
|
225
238
|
} else if ((size = Boxed::GetSize(gi_info)) != 0) {
|
|
226
|
-
boxed =
|
|
239
|
+
boxed = AllocateBoxed(gtype, size);
|
|
227
240
|
|
|
228
241
|
} else {
|
|
229
242
|
Nan::ThrowError("Boxed allocation failed: no constructor found");
|
|
@@ -250,7 +263,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
250
263
|
|
|
251
264
|
SET_OBJECT_GTYPE (self, gtype);
|
|
252
265
|
|
|
253
|
-
if (
|
|
266
|
+
if (n_constructor_args <= 0)
|
|
254
267
|
InitBoxedFromObject(self, info[0]);
|
|
255
268
|
}
|
|
256
269
|
|
|
@@ -286,12 +299,12 @@ static void BoxedDestroyed(const Nan::WeakCallbackInfo<Boxed> &info) {
|
|
|
286
299
|
delete box;
|
|
287
300
|
}
|
|
288
301
|
|
|
289
|
-
static void BoxedClassDestroyed(const
|
|
302
|
+
static void BoxedClassDestroyed(const Nan::WeakCallbackInfo<GIBaseInfo> &info) {
|
|
290
303
|
GIBaseInfo *gi_info = info.GetParameter ();
|
|
291
304
|
GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *) gi_info);
|
|
292
305
|
|
|
293
|
-
auto *persistent_template = (Persistent<FunctionTemplate> *) g_type_get_qdata (gtype, GNodeJS::template_quark());
|
|
294
|
-
auto *persistent_function = (Persistent<FunctionTemplate> *) g_type_get_qdata (gtype, GNodeJS::function_quark());
|
|
306
|
+
auto *persistent_template = (Nan::Persistent<FunctionTemplate> *) g_type_get_qdata (gtype, GNodeJS::template_quark());
|
|
307
|
+
auto *persistent_function = (Nan::Persistent<FunctionTemplate> *) g_type_get_qdata (gtype, GNodeJS::function_quark());
|
|
295
308
|
delete persistent_template;
|
|
296
309
|
delete persistent_function;
|
|
297
310
|
|
|
@@ -324,7 +337,7 @@ Local<FunctionTemplate> GetBoxedTemplate(GIBaseInfo *info, GType gtype) {
|
|
|
324
337
|
*/
|
|
325
338
|
|
|
326
339
|
if (data) {
|
|
327
|
-
auto *persistent = (Persistent<FunctionTemplate> *) data;
|
|
340
|
+
auto *persistent = (Nan::Persistent<FunctionTemplate> *) data;
|
|
328
341
|
auto tpl = Nan::New<FunctionTemplate> (*persistent);
|
|
329
342
|
return tpl;
|
|
330
343
|
}
|
|
@@ -353,7 +366,7 @@ Local<FunctionTemplate> GetBoxedTemplate(GIBaseInfo *info, GType gtype) {
|
|
|
353
366
|
if (gtype == G_TYPE_NONE)
|
|
354
367
|
return tpl;
|
|
355
368
|
|
|
356
|
-
auto *persistent = new Persistent<FunctionTemplate>(
|
|
369
|
+
auto *persistent = new Nan::Persistent<FunctionTemplate>(tpl);
|
|
357
370
|
persistent->SetWeak(
|
|
358
371
|
g_base_info_ref(info),
|
|
359
372
|
BoxedClassDestroyed,
|
|
@@ -380,7 +393,7 @@ Local<Function> GetBoxedFunction(GIBaseInfo *info, GType gtype) {
|
|
|
380
393
|
}
|
|
381
394
|
|
|
382
395
|
if (data) {
|
|
383
|
-
auto *persistent = (Persistent<Function> *) data;
|
|
396
|
+
auto *persistent = (Nan::Persistent<Function> *) data;
|
|
384
397
|
auto fn = Nan::New<Function> (*persistent);
|
|
385
398
|
return fn;
|
|
386
399
|
}
|
|
@@ -391,7 +404,7 @@ Local<Function> GetBoxedFunction(GIBaseInfo *info, GType gtype) {
|
|
|
391
404
|
if (gtype == G_TYPE_NONE)
|
|
392
405
|
return fn;
|
|
393
406
|
|
|
394
|
-
auto *persistent = new Persistent<Function>(
|
|
407
|
+
auto *persistent = new Nan::Persistent<Function>(fn);
|
|
395
408
|
|
|
396
409
|
g_type_set_qdata(gtype, GNodeJS::function_quark(), persistent);
|
|
397
410
|
|
|
@@ -419,15 +432,15 @@ Local<Function> MakeBoxedClass(GIBaseInfo *info) {
|
|
|
419
432
|
return GetBoxedFunction (info, gtype);
|
|
420
433
|
}
|
|
421
434
|
|
|
422
|
-
Local<Value> WrapperFromBoxed(GIBaseInfo *info, void *data,
|
|
435
|
+
Local<Value> WrapperFromBoxed(GIBaseInfo *info, void *data, ResourceOwnership ownership) {
|
|
423
436
|
if (data == NULL)
|
|
424
437
|
return Nan::Null();
|
|
425
438
|
|
|
426
439
|
Local<Function> constructor = MakeBoxedClass (info);
|
|
427
440
|
|
|
428
|
-
Local<Value>
|
|
429
|
-
Local<Value>
|
|
430
|
-
Local<Value> args[] = {
|
|
441
|
+
Local<Value> jsBoxed = Nan::New<External> (data);
|
|
442
|
+
Local<Value> jsOwnership = Nan::New<v8::Int32> ((int32_t) ownership);
|
|
443
|
+
Local<Value> args[] = { jsBoxed, jsOwnership };
|
|
431
444
|
|
|
432
445
|
MaybeLocal<Object> instance = Nan::NewInstance(constructor, 2, args);
|
|
433
446
|
|
|
@@ -445,4 +458,21 @@ void* PointerFromWrapper(Local<Value> value) {
|
|
|
445
458
|
return boxed;
|
|
446
459
|
}
|
|
447
460
|
|
|
461
|
+
void DisownBoxed(Local<Value> value) {
|
|
462
|
+
if (!value->IsObject())
|
|
463
|
+
return;
|
|
464
|
+
|
|
465
|
+
Local<Object> object = TO_OBJECT (value);
|
|
466
|
+
// Boxed wrappers store the data pointer in field 0 and the Boxed* in field 1.
|
|
467
|
+
if (object->InternalFieldCount() < 2)
|
|
468
|
+
return;
|
|
469
|
+
|
|
470
|
+
Boxed *box = static_cast<Boxed *>(object->GetAlignedPointerFromInternalField(1));
|
|
471
|
+
if (box != NULL) {
|
|
472
|
+
box->owns_memory = false;
|
|
473
|
+
box->data = NULL;
|
|
474
|
+
}
|
|
475
|
+
object->SetAlignedPointerInInternalField(0, NULL);
|
|
476
|
+
}
|
|
477
|
+
|
|
448
478
|
};
|
package/src/boxed.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
#pragma once
|
|
3
3
|
|
|
4
|
+
#include "value.h"
|
|
4
5
|
#include <nan.h>
|
|
5
6
|
#include <node.h>
|
|
6
7
|
#include <girepository.h>
|
|
@@ -28,9 +29,22 @@ public:
|
|
|
28
29
|
static size_t GetSize (GIBaseInfo *boxed_info) ;
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
// Allocate zero-filled backing memory for a boxed/struct instance. Registered
|
|
33
|
+
// boxed types are freed with g_boxed_free (which, by GLib convention, uses
|
|
34
|
+
// g_slice_free), so they must be allocated with g_slice — allocating them with
|
|
35
|
+
// g_malloc0 and freeing with g_slice_free corrupts the slice allocator on
|
|
36
|
+
// GLib builds where GSlice is a real slab allocator (#290, #213).
|
|
37
|
+
gpointer AllocateBoxed (GType gtype, size_t size);
|
|
38
|
+
|
|
31
39
|
Local<Function> MakeBoxedClass (GIBaseInfo *info);
|
|
32
40
|
Local<FunctionTemplate> GetBoxedTemplate (GIBaseInfo *info, GType gtype);
|
|
33
|
-
Local<Value> WrapperFromBoxed (GIBaseInfo *info, void *data,
|
|
41
|
+
Local<Value> WrapperFromBoxed (GIBaseInfo *info, void *data, ResourceOwnership ownership = kNone);
|
|
34
42
|
void * PointerFromWrapper (Local<Value>);
|
|
35
43
|
|
|
44
|
+
// Relinquish ownership of a boxed wrapper's memory: clears owns_memory so the
|
|
45
|
+
// GC finalizer won't free it, and nulls the data pointer so later use fails
|
|
46
|
+
// cleanly instead of touching freed memory. Used after an introspected method
|
|
47
|
+
// that frees the instance itself (e.g. *_free / *_unref) — see #429.
|
|
48
|
+
void DisownBoxed (Local<Value>);
|
|
49
|
+
|
|
36
50
|
};
|
package/src/callback.cc
CHANGED
|
@@ -34,8 +34,18 @@ Callback::Callback(Local<Function> fn, GICallableInfo* callback_info, GIScopeTyp
|
|
|
34
34
|
info = g_base_info_ref (callback_info);
|
|
35
35
|
#ifdef GI_AVAILABLE_IN_1_72
|
|
36
36
|
closure = g_callable_info_create_closure(info, &cif, Callback::Call, this);
|
|
37
|
+
/* On libffi 3.4+ the executable trampoline is a separate mapping from the
|
|
38
|
+
* writable ffi_closure, so the closure pointer is not itself callable and
|
|
39
|
+
* invoking it segfaults (#390, seen on Ubuntu 26 / libffi 3.5). Use the
|
|
40
|
+
* closure's native (executable) address instead. Fall back to the closure
|
|
41
|
+
* pointer if introspection can't supply one — passing NULL as the callback
|
|
42
|
+
* would break startup, since the bindings register callbacks at bootstrap. */
|
|
43
|
+
native_address = g_callable_info_get_closure_native_address(info, closure);
|
|
44
|
+
if (native_address == NULL)
|
|
45
|
+
native_address = (gpointer) closure;
|
|
37
46
|
#else
|
|
38
47
|
closure = g_callable_info_prepare_closure(info, &cif, Callback::Call, this);
|
|
48
|
+
native_address = (gpointer) closure;
|
|
39
49
|
#endif
|
|
40
50
|
scope_type = scope_type_;
|
|
41
51
|
}
|
|
@@ -117,14 +127,14 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
117
127
|
|
|
118
128
|
bool isOutArgument = g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT;
|
|
119
129
|
bool isPrimitive = Type::IsPrimitive(&arg_type);
|
|
120
|
-
|
|
130
|
+
ResourceOwnership ownership = isOutArgument ? kNone : kCopy;
|
|
121
131
|
|
|
122
132
|
if (isPrimitive && isOutArgument) {
|
|
123
133
|
n_return_values += 1;
|
|
124
134
|
primitive_out_arguments_mask |= 1 << i;
|
|
125
135
|
js_args[i] = Nan::Null();
|
|
126
136
|
} else {
|
|
127
|
-
js_args[i] = GIArgumentToV8 (&arg_type, args[i + args_offset], -1,
|
|
137
|
+
js_args[i] = GIArgumentToV8 (&arg_type, args[i + args_offset], -1, ownership);
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
|
|
@@ -171,6 +181,7 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
171
181
|
if (jsReturnArray->Length() != n_js_return_values) {
|
|
172
182
|
Throw::Error("Virtual function must return %u arguments but returned %u",
|
|
173
183
|
n_js_return_values, jsReturnArray->Length());
|
|
184
|
+
goto out;
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
if (hasVoidReturn)
|
|
@@ -194,6 +205,7 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
194
205
|
|
|
195
206
|
if (!success) {
|
|
196
207
|
Throw::InvalidReturnValue (&return_type_info, jsReturnValue);
|
|
208
|
+
goto out;
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
}
|
package/src/callback.h
CHANGED
|
@@ -16,6 +16,7 @@ namespace GNodeJS {
|
|
|
16
16
|
struct Callback {
|
|
17
17
|
ffi_cif cif;
|
|
18
18
|
ffi_closure *closure;
|
|
19
|
+
gpointer native_address; /* callable trampoline; may differ from closure on libffi 3.4+ */
|
|
19
20
|
Nan::Persistent<Function> persistent;
|
|
20
21
|
GICallableInfo *info;
|
|
21
22
|
GIScopeType scope_type;
|
package/src/closure.cc
CHANGED
|
@@ -19,6 +19,41 @@ using Nan::Persistent;
|
|
|
19
19
|
|
|
20
20
|
namespace GNodeJS {
|
|
21
21
|
|
|
22
|
+
/*
|
|
23
|
+
* An (out)/(inout) signal parameter arrives as a GValue holding a *pointer* to
|
|
24
|
+
* the value (e.g. a gint* for an `(inout) (type int)` parameter), not the value
|
|
25
|
+
* itself. Dereference it in place so the argument holds the pointed-to value.
|
|
26
|
+
*/
|
|
27
|
+
static void LoadGIArgumentFromPointer (GITypeInfo *type_info, GIArgument *arg) {
|
|
28
|
+
gpointer ptr = arg->v_pointer;
|
|
29
|
+
|
|
30
|
+
if (ptr == NULL) {
|
|
31
|
+
memset(arg, 0, sizeof(*arg));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (g_type_info_get_tag(type_info)) {
|
|
36
|
+
case GI_TYPE_TAG_BOOLEAN: arg->v_boolean = *(gboolean*) ptr; break;
|
|
37
|
+
case GI_TYPE_TAG_INT8: arg->v_int8 = *(gint8*) ptr; break;
|
|
38
|
+
case GI_TYPE_TAG_UINT8: arg->v_uint8 = *(guint8*) ptr; break;
|
|
39
|
+
case GI_TYPE_TAG_INT16: arg->v_int16 = *(gint16*) ptr; break;
|
|
40
|
+
case GI_TYPE_TAG_UINT16: arg->v_uint16 = *(guint16*) ptr; break;
|
|
41
|
+
case GI_TYPE_TAG_INT32: arg->v_int32 = *(gint32*) ptr; break;
|
|
42
|
+
case GI_TYPE_TAG_UNICHAR:
|
|
43
|
+
case GI_TYPE_TAG_UINT32: arg->v_uint32 = *(guint32*) ptr; break;
|
|
44
|
+
case GI_TYPE_TAG_INT64: arg->v_int64 = *(gint64*) ptr; break;
|
|
45
|
+
case GI_TYPE_TAG_UINT64: arg->v_uint64 = *(guint64*) ptr; break;
|
|
46
|
+
case GI_TYPE_TAG_FLOAT: arg->v_float = *(gfloat*) ptr; break;
|
|
47
|
+
case GI_TYPE_TAG_DOUBLE: arg->v_double = *(gdouble*) ptr; break;
|
|
48
|
+
case GI_TYPE_TAG_GTYPE: arg->v_size = *(gsize*) ptr; break;
|
|
49
|
+
default:
|
|
50
|
+
// Pointer-like types (utf8, interface, array, list, ...): the value
|
|
51
|
+
// is itself a pointer stored at *ptr.
|
|
52
|
+
arg->v_pointer = *(gpointer*) ptr;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
GClosure *Closure::New (Local<Function> function, GICallableInfo* info, guint signalId) {
|
|
23
58
|
Closure *closure = (Closure *) g_closure_new_simple (sizeof (*closure), GUINT_TO_POINTER(signalId));
|
|
24
59
|
closure->persistent.Reset(function);
|
|
@@ -63,26 +98,39 @@ void Closure::Execute(GICallableInfo *info, guint signal_id,
|
|
|
63
98
|
g_callable_info_load_arg(info, i - 1, &arg_info);
|
|
64
99
|
g_arg_info_load_type(&arg_info, &type_info);
|
|
65
100
|
|
|
66
|
-
|
|
101
|
+
ResourceOwnership ownership = kCopy;
|
|
67
102
|
|
|
68
103
|
if (signal_query.signal_id) {
|
|
69
|
-
|
|
104
|
+
if ((signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) {
|
|
105
|
+
ownership = kNone;
|
|
106
|
+
}
|
|
70
107
|
}
|
|
71
|
-
|
|
72
|
-
|
|
108
|
+
|
|
109
|
+
GIDirection direction = g_arg_info_get_direction(&arg_info);
|
|
110
|
+
if (direction == GI_DIRECTION_INOUT) {
|
|
111
|
+
// The GValue holds a pointer to the in/out value; dereference it
|
|
112
|
+
// so the handler receives the actual value (#405).
|
|
113
|
+
LoadGIArgumentFromPointer(&type_info, &argument);
|
|
114
|
+
ownership = kNone;
|
|
115
|
+
} else if (direction == GI_DIRECTION_OUT) {
|
|
116
|
+
// Pure out: there is no meaningful incoming value.
|
|
117
|
+
memset(&argument, 0, sizeof(argument));
|
|
118
|
+
ownership = kNone;
|
|
73
119
|
}
|
|
74
120
|
|
|
75
|
-
js_args[i - 1] = GIArgumentToV8(&type_info, &argument, -1,
|
|
121
|
+
js_args[i - 1] = GIArgumentToV8(&type_info, &argument, -1, ownership);
|
|
76
122
|
}
|
|
77
123
|
} else {
|
|
78
124
|
/* CallableInfo is not available: use GValueToV8 */
|
|
79
125
|
for (guint i = 1; i < n_param_values; i++) {
|
|
80
|
-
|
|
126
|
+
ResourceOwnership ownership = kCopy;
|
|
81
127
|
|
|
82
128
|
if (signal_query.signal_id) {
|
|
83
|
-
|
|
129
|
+
if ((signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) {
|
|
130
|
+
ownership = kNone;
|
|
131
|
+
}
|
|
84
132
|
}
|
|
85
|
-
js_args[i - 1] = GValueToV8(¶m_values[i],
|
|
133
|
+
js_args[i - 1] = GValueToV8(¶m_values[i], ownership);
|
|
86
134
|
}
|
|
87
135
|
}
|
|
88
136
|
|
|
@@ -100,8 +148,55 @@ void Closure::Execute(GICallableInfo *info, guint signal_id,
|
|
|
100
148
|
if (!try_catch.HasCaught()
|
|
101
149
|
&& result.ToLocal(&return_value)) {
|
|
102
150
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
151
|
+
if (info) {
|
|
152
|
+
// Distribute the handler's return value across the signal's return
|
|
153
|
+
// value and its (out)/(inout) parameters, writing each back (#405).
|
|
154
|
+
// When there is more than one output the handler returns an array,
|
|
155
|
+
// mirroring how out-arguments are returned from a function call.
|
|
156
|
+
GIArgInfo out_arg_info;
|
|
157
|
+
GITypeInfo out_type_info;
|
|
158
|
+
|
|
159
|
+
int n_outputs = (g_return_value != NULL) ? 1 : 0;
|
|
160
|
+
for (guint i = 1; i < n_param_values; i++) {
|
|
161
|
+
g_callable_info_load_arg(info, i - 1, &out_arg_info);
|
|
162
|
+
GIDirection d = g_arg_info_get_direction(&out_arg_info);
|
|
163
|
+
if (d == GI_DIRECTION_OUT || d == GI_DIRECTION_INOUT)
|
|
164
|
+
n_outputs++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
bool from_array = n_outputs > 1 && return_value->IsArray();
|
|
168
|
+
int output_index = 0;
|
|
169
|
+
|
|
170
|
+
#define NEXT_OUTPUT() ( \
|
|
171
|
+
n_outputs <= 1 \
|
|
172
|
+
? return_value \
|
|
173
|
+
: (from_array \
|
|
174
|
+
? Nan::Get(return_value.As<v8::Array>(), output_index++).ToLocalChecked() \
|
|
175
|
+
: (output_index++, Nan::Undefined().As<Value>())))
|
|
176
|
+
|
|
177
|
+
if (g_return_value) {
|
|
178
|
+
if (!V8ToGValue (g_return_value, NEXT_OUTPUT(), kCopy))
|
|
179
|
+
goto throw_exception;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (guint i = 1; i < n_param_values; i++) {
|
|
183
|
+
g_callable_info_load_arg(info, i - 1, &out_arg_info);
|
|
184
|
+
GIDirection d = g_arg_info_get_direction(&out_arg_info);
|
|
185
|
+
if (d != GI_DIRECTION_OUT && d != GI_DIRECTION_INOUT)
|
|
186
|
+
continue;
|
|
187
|
+
|
|
188
|
+
g_arg_info_load_type(&out_arg_info, &out_type_info);
|
|
189
|
+
|
|
190
|
+
GIArgument out_arg;
|
|
191
|
+
memcpy(&out_arg, ¶m_values[i].data[0], sizeof(GIArgument));
|
|
192
|
+
if (out_arg.v_pointer != NULL)
|
|
193
|
+
V8ToOutGIArgument(&out_type_info, &out_arg, NEXT_OUTPUT(), true);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#undef NEXT_OUTPUT
|
|
197
|
+
}
|
|
198
|
+
else if (g_return_value) {
|
|
199
|
+
if (!V8ToGValue (g_return_value, return_value, kCopy))
|
|
105
200
|
goto throw_exception;
|
|
106
201
|
}
|
|
107
202
|
|