cjs-128.1/0000775000175000017500000000000015116312211011221 5ustar fabiofabiocjs-128.1/.clang-format0000664000175000017500000000164115116312211013576 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento # Global Options Go Here IndentWidth: 4 ColumnLimit: 80 --- Language: Cpp BasedOnStyle: Google AccessModifierOffset: -3 # to match cpplint AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false CommentPragmas: '^ NOLINT' DerivePointerAlignment: false ForEachMacros: [] IncludeBlocks: Preserve IndentPPDirectives: AfterHash IndentWidth: 4 MacroBlockBegin: "^JSNATIVE_TEST_FUNC_BEGIN$" MacroBlockEnd: "^JSNATIVE_TEST_FUNC_END$" PointerAlignment: Left # Google style allows both, but clang-format doesn't SpacesBeforeTrailingComments: 2 --- # We should use eslint --fix instead, but we need to find a way to get that to # operate on diffs like clang-format does. Language: JavaScript DisableFormat: true SortIncludes: false # https://bugs.llvm.org/show_bug.cgi?id=27042 ... cjs-128.1/.clangd0000664000175000017500000000034615116312211012455 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Philip Chimento Diagnostics: ClangTidy: Remove: bugprone-sizeof-expression # Interferes with g_clear_pointer() cjs-128.1/.editorconfig0000664000175000017500000000053115116312211013675 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Sonny Piers # EditorConfig is awesome: https://EditorConfig.org root = true [*] indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true [*.js] quote_type = single cjs-128.1/.eslintignore0000664000175000017500000000045515116312211013730 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Claudio André installed-tests/js/jasmine.js installed-tests/js/modules/badOverrides/WarnLib.js installed-tests/js/modules/subBadInit/__init__.js modules/script/jsUnit.js /_build /builddir cjs-128.1/.eslintrc.yml0000664000175000017500000001542515116312211013654 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Claudio André env: es2021: true extends: 'eslint:recommended' plugins: - jsdoc rules: array-bracket-newline: - error - consistent array-bracket-spacing: - error - never array-callback-return: error arrow-parens: - error - as-needed arrow-spacing: error block-scoped-var: error block-spacing: error brace-style: error # Waiting for this to have matured a bit in eslint # camelcase: # - error # - properties: never # allow: [^vfunc_, ^on_, _instance_init] comma-dangle: - error - arrays: always-multiline objects: always-multiline imports: always-multiline functions: never comma-spacing: - error - before: false after: true comma-style: - error - last computed-property-spacing: error curly: - error - multi-or-nest - consistent dot-location: - error - property eol-last: error eqeqeq: error func-call-spacing: error func-name-matching: error func-style: - error - declaration - allowArrowFunctions: true indent: - error - 4 - ignoredNodes: # Allow not indenting the body of GObject.registerClass, since in the # future it's intended to be a decorator - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child' # Allow dedenting chained member expressions MemberExpression: 'off' jsdoc/check-alignment: error jsdoc/check-param-names: error jsdoc/check-tag-names: error jsdoc/check-types: error jsdoc/implements-on-classes: error jsdoc/require-jsdoc: error jsdoc/require-param: error jsdoc/require-param-description: error jsdoc/require-param-name: error jsdoc/require-param-type: error jsdoc/tag-lines: - error - always - count: 0 startLines: 1 key-spacing: - error - beforeColon: false afterColon: true keyword-spacing: - error - before: true after: true linebreak-style: - error - unix lines-between-class-members: - error - always - exceptAfterSingleLine: true max-nested-callbacks: error max-statements-per-line: error new-parens: error no-array-constructor: error no-await-in-loop: error no-caller: error no-constant-condition: - error - checkLoops: false no-div-regex: error no-empty: - error - allowEmptyCatch: true no-extra-bind: error no-extra-parens: - error - all - conditionalAssign: false nestedBinaryExpressions: false returnAssign: false no-implicit-coercion: - error - allow: - '!!' no-invalid-this: error no-iterator: error no-label-var: error no-lonely-if: error no-loop-func: error no-nested-ternary: error no-new-object: error no-new-wrappers: error no-octal-escape: error no-proto: error no-prototype-builtins: 'off' no-restricted-globals: [error, window] no-restricted-properties: - error - object: imports property: format message: Use template strings - object: pkg property: initFormat message: Use template strings - object: Lang property: copyProperties message: Use Object.assign() - object: Lang property: bind message: Use arrow notation or Function.prototype.bind() - object: Lang property: Class message: Use ES6 classes no-restricted-syntax: - error - selector: >- MethodDefinition[key.name="_init"] > FunctionExpression[params.length=1] > BlockStatement[body.length=1] CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] > Identifier:first-child message: _init() that only calls super._init() is unnecessary - selector: >- MethodDefinition[key.name="_init"] > FunctionExpression[params.length=0] > BlockStatement[body.length=1] CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"] message: _init() that only calls super._init() is unnecessary - selector: BinaryExpression[operator="instanceof"][right.name="Array"] message: Use Array.isArray() no-return-assign: error no-return-await: error no-self-compare: error no-shadow: error no-shadow-restricted-names: error no-spaced-func: error no-tabs: error no-template-curly-in-string: error no-throw-literal: error no-trailing-spaces: error no-undef-init: error no-unneeded-ternary: error no-unused-expressions: error no-unused-vars: - error # Vars use a suffix _ instead of a prefix because of file-scope private vars - varsIgnorePattern: (^unused|_$) argsIgnorePattern: ^(unused|_) no-useless-call: error no-useless-computed-key: error no-useless-concat: error no-useless-constructor: error no-useless-rename: error no-useless-return: error no-whitespace-before-property: error no-with: error nonblock-statement-body-position: - error - below object-curly-newline: - error - consistent: true multiline: true object-curly-spacing: error object-shorthand: error operator-assignment: error operator-linebreak: error padded-blocks: - error - never # These may be a bit controversial, we can try them out and enable them later # prefer-const: error # prefer-destructuring: error prefer-numeric-literals: error prefer-promise-reject-errors: error prefer-rest-params: error prefer-spread: error prefer-template: error quotes: - error - single - avoidEscape: true require-await: error rest-spread-spacing: error semi: - error - always semi-spacing: - error - before: false after: true semi-style: error space-before-blocks: error space-before-function-paren: - error - named: never # for `function ()` and `async () =>`, preserve space around keywords anonymous: always asyncArrow: always space-in-parens: error space-infix-ops: - error - int32Hint: false space-unary-ops: error spaced-comment: error switch-colon-spacing: error symbol-description: error template-curly-spacing: error template-tag-spacing: error unicode-bom: error wrap-iife: - error - inside yield-star-spacing: error yoda: error settings: jsdoc: mode: typescript globals: ARGV: readonly Debugger: readonly GIRepositoryGType: readonly globalThis: readonly imports: readonly Intl: readonly log: readonly logError: readonly print: readonly printerr: readonly window: readonly TextEncoder: readonly TextDecoder: readonly console: readonly setTimeout: readonly setInterval: readonly clearTimeout: readonly clearInterval: readonly parserOptions: ecmaVersion: 2022 cjs-128.1/.github/0000775000175000017500000000000015116312211012561 5ustar fabiofabiocjs-128.1/.github/workflows/0000775000175000017500000000000015116312211014616 5ustar fabiofabiocjs-128.1/.github/workflows/build.yml0000664000175000017500000000107215116312211016440 0ustar fabiofabioname: Build on: push: branches: - master pull_request: branches: - master workflow_dispatch: inputs: debug_enabled: type: boolean description: 'Start an SSH server on failure.' required: false default: false jobs: build: uses: linuxmint/github-actions/.github/workflows/do-builds.yml@master with: commit_id: '115.0' ############################## Comma separated list - like 'linuxmint/xapp, linuxmint/cinnamon-desktop' dependencies: ############################## cjs-128.1/.gitignore0000664000175000017500000000057315116312211013216 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Philip Chimento /tools/node_modules # artifact from ci-templates: /container-build-report.xml debian/tmp debian/libcjs0 debian/libcjs-dbg debian/libcjs-dev debian/cjs debian/.debhelper debian/*.substvars debian/*.debhelper.log debian/debhelper-build-stamp debian/filescjs-128.1/.gitmodules0000664000175000017500000000042115116312211013373 0ustar fabiofabio# SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2024 Philip Chimento [submodule "subprojects/gobject-introspection-tests"] path = subprojects/gobject-introspection-tests url = ../gobject-introspection-tests.git shallow = true cjs-128.1/CONTRIBUTING.md0000664000175000017500000003437115116312211013462 0ustar fabiofabio# Contributing to GJS # ## Introduction ## Thank you for considering contributing to GJS! As with any open source project, we can't make it as good as possible without help from you and others. We do have some guidelines for contributing, set out in this file. Following these guidelines helps communicate that you respect the time of the developers who work on GJS. In return, they should reciprocate that respect in addressing your issue, reviewing your work, and helping finalize your merge requests. ### What kinds of contributions we are looking for ### There are many ways to contribute to GJS, not only writing code. We encourage all of them. You can write example programs, tutorials, or blog posts; improve the documentation; [submit bug reports and feature requests][bugtracker]; triage existing bug reports; vote on issues with a thumbs-up or thumbs-down; or write code which is incorporated into [GJS itself][gjs]. ### What kinds of contributions we are not looking for ### Please don't use the [issue tracker][bugtracker] for support questions. Instead, check out the [#javascript][chat] chat channel on Matrix. You can also try the [GNOME Discourse][discourse] forum, or Stack Overflow. If you are writing code, please do not submit merge requests that only fix linter errors in code that you are not otherwise changing (unless you have discussed it in advance with a maintainer on [Matrix][chat].) When writing code or submitting a feature request, make sure to first read the section below titled "Roadmap". Contributions that run opposite to the roadmap are not likely to be accepted. ## Ground Rules ## Your responsibilities as a contributor: - Be welcoming and encouraging to newcomers. - Conduct yourself professionally; rude, abusive, harassing, or discriminatory behaviour is not tolerated. - For any major changes and enhancements you want to make, first create an issue in the [bugtracker], discuss things transparently, and get community feedback. - Ensure all jobs are green on GitLab CI for your merge requests. - Your code must pass the tests. Sometimes you can experience a runner system failure which can be fixed by re-running the job. - Your code must pass the linters; code should not introduce any new linting errors. - Your code should not cause any compiler warnings. - Add tests for any new functionality you write, and regression tests for any bugs you fix. ## Your First Contribution ## Unsure where to start? Try looking through the [issues labeled "Newcomers"][newcomers]. We try to have these issues contain some step-by-step explanation on how a newcomer might approach them. If that explanation is missing from an issue marked "Newcomers", feel free to leave a comment on there asking for help on how to get started. [Issues marked "Help Wanted"][helpwanted] may be a bit more involved than the Newcomers issues, but many of them still do not require in-depth familiarity with GJS. If you're applying to work on GJS for Outreachy or Summer of Code, see our [Internship Getting Started][internship] documentation. ## How to contribute documentation or tutorials ## If you don't have an account on [gitlab.gnome.org], first create one. Some contributions are done in different places than the main GJS repository. To contribute to the documentation, go to the [DevDocs][devdocs] repository. To contribute to tutorials, go to [GJS Guide][gjsguide]. Next, read the [workflow guide to contributing to GNOME][workflow]. (In short, create a fork of the repository, make your changes on a branch, push them to your fork, and create a merge request.) When you submit your merge request, make sure to click "Allow commits from contributors with push access". This is so that the maintainers can re-run the GitLab CI jobs, since there is currently a bug in the infrastructure that makes some of the jobs fail unnecessarily. !157 is an example of a small documentation bugfix in a merge request. That's all! ## How to contribute code ## To contribute code, follow the instructions above for contributing documentation. There are further instructions for how to set up a development environment and install the correct tools for GJS development in the [Hacking.md][hacking] file. ## How to report a bug ## If you don't have an account on [gitlab.gnome.org], first create one. Go to the [issue tracker][bugtracker] and click "New issue". Use the "bug" template when reporting a bug. Make sure to answer the questions in the template, as otherwise it might make your bug harder to track down. _If you find a security vulnerability,_ make sure to mark the issue as "confidential"! If in doubt, ask on [Matrix][chat] whether you should report a bug about something, but generally it's OK to just go ahead. Bug report #170 is a good example of a bug report with an independently runnable code snippet for testing, and lots of information, although it was written before the templates existed. ## How to suggest a feature or enhancement ## If you find yourself wishing for a feature that doesn't exist in GJS, you are probably not alone. Open an issue on our [issue tracker][bugtracker] which describes the feature you would like to see, why you need it, and how it should work. Use the "feature" template for this. However, for a new feature, the likelihood that it will be implemented goes way up if you or someone else plans to submit a merge request along with it. If the feature is small enough that you won't feel like your time was wasted if we decide not to adopt it, you can just submit a merge request rather than going to the issue tracker. Make sure to explain why you think it's a good feature to have! !213 is an example of a small feature suggestion that was submitted as a merge request. In cases where you've seen something that needs to be fixed or refactored in the code, it's OK not to use a template. It's OK to be less rigorous here, since this type of report is usually used by people who plan to fix the issue themselves later. ## How to triage bugs ## You can help the maintainers by examining the existing bug reports in the bugtracker and adding instructions to reproduce them, or categorizing them with the correct labels. For bugs that cause a crash (segmentation fault, not just a JS exception) use the "1. Crash" label. For other bugs, use the "1. Bug" label. Feature requests should get the "1. Feature" label. Any crashes, or bugs that prevent most or all users from using GJS or GNOME Shell, should also get the "To Do" label. If some information is missing from the bug (for example, you can't reproduce it based on their instructions,) add the "2. Needs information" label. Add any topic labels from the "5" group (e.g. "5. Performance") as you see fit. As for reproducer instructions, a small, self-contained JS program that exhibits the bug, to be run with the command-line `gjs` interpreter, is best. Instructions that provide code to be loaded as a GNOME Shell extension are less helpful, because they are more tedious to test. ## Code review process ## Once you have submitted your merge request, a maintainer will review it. You should get a first response within a few days. Sometimes maintainers are busy; if it's been a week and you've heard nothing, feel free to ping the maintainer and ask for an estimate of when they might be able to review the merge request. You might get a review even if some of the GitLab CI jobs have not yet succeeded. In that case, acceptance of the merge request is contingent on fixing whatever needs to be fixed to get all the jobs to turn green. In general, unless the merge request is very simple, it will not be ready to accept immediately. You should normally expect one to three rounds of code review, depending on the size and complexity of the merge request. Be prepared to accept constructive criticism on your code and to work on improving it before it's merged; code review comments don't mean it's bad. !242 is an example of a bug fix merge request with a few code review comments on it, if you want to get a feel for the process. Contributors with a GNOME developer account have automatic push access to the main GJS repository. However, even if you have this access, you are still expected to submit a merge request and have a GJS maintainer review it. The exception to this is if there is an emergency such as GNOME Continuous being broken. ## Community ## For general questions and support, visit the [#javascript][chat] channel on Matrix. The maintainers are listed in the [DOAP file][doap] in the root of the repository. ## Roadmap and Philosophy ## This section explains what kinds of changes we do and don't intend to make in GJS in the future and what direction we intend to take the project. Internally, GJS uses Firefox's Javascript engine, called SpiderMonkey. First of all, we will not consider switching GJS to use a different Javascript engine. If you believe that should be done, the best way to make it happen is to start a new project, copy GJS's regression test suite, and make sure all the tests pass and you can run GNOME Shell with it. Every year when a new ESR (extended support release) of Firefox appears, we try to upgrade GJS to use the accompanying version of SpiderMonkey as soon as possible. Sometimes upgrading SpiderMonkey requires breaking backwards compatibility, and in that case we try to make it as easy as possible for existing code to adapt. Other than the above exception, we avoid all changes that break existing code, even if they would be convenient. However, it is OK to break compatibility with GJS's documented behaviour if in practice the behaviour never actually worked as documented. (That happens more often than you might think.) We also try to avoid surprises for people who are used to modern ES standard Javascript, so custom GJS classes should not deviate from the behaviour that people would be used to in the standard. The Node.js ecosystem is quite popular and many Javascript developers are accustomed to it. In theory, we would like to move in the direction of providing all the same facilities as Node.js, but we do not necessarily want to copy the exact way things work in Node.js. The platforms are different and so the implementations sometimes need to be different too. The module system in GJS should be considered legacy. We don't want to make big changes to it or add any features. Instead, we want to enable ES6-style imports for the GJS platform. We do have some overrides for GNOME libraries such as GLib, to make their APIs more Javascript-like. However, we like to keep these to a minimum, so that GNOME code remains straightforward to read if you are used to using the GNOME libraries in other programming languages. GJS was originally written in C, and the current state of the C++ code reflects that. Gradually, we want to move the code to a more idiomatic C++ style, using smart pointer classes such as `GjsAutoChar` to help avoid memory leaks. Even farther in the future, we expect the Rust bindings for SpiderMonkey to mature as Mozilla's Servo browser engine progresses, and we may consider rewriting part or all of GJS in Rust. We believe in automating as much as possible to prevent human error. GJS is a complex program that powers a lot of GNOME, so breakages can be have far-reaching effects in other programs. We intend to move in the direction of having more static code checking in the future. We would also like to have more automated integration testing, for example trying to start a GNOME Shell session with each new change in GJS. Lastly, changes should in principle be compatible with other platforms than only Linux and GNOME. Although we don't have automated testing for other platforms, we will occasionally build and test things there, and gladly accept contributions to fix breakages on other platforms. ## Conventions ## ### Coding style ### We use the [Google style guide][googlestyle] for C++ code, with a few exceptions, 4-space indents being the main one. There is a handy git commit hook that will autoformat your code when you commit it; see the [Hacking.md][hacking] file. For C++ coding style concerns that can't be checked with a linter or an autoformatter, read the [CPP_Style_Guide.md][cppstyle] file. For Javascript code, an [ESLint configuration file][eslint] is included in the root of the GJS repository. This is not integrated with a git commit hook, so you need to manually make sure that all your code conforms to the style. Running `./tools/run_eslint.sh --fix` should autoformat most of your JavaScript code correctly. ### Commit messages ### The title of the commit should say what you changed, and the body of the commit message should explain why you changed it. We look in the commit history quite often to figure out why code was written a certain way, so it's important to justify each change so that in the future people will realize why it was needed. For further guidelines about line length and commit messages, read [this guide][commitmessages]. If the commit is related to an open issue in the issue tracker, note that on the last line of the commit message. For example, `See #153`, or `Closes #277` if the issue should be automatically closed when the merge request is accepted. Otherwise, creating a separate issue is not required. ## Thanks ## Thanks to [@nayafia][contributingtemplate] for the inspiration to write this guide! [gitlab.gnome.org]: https://gitlab.gnome.org [bugtracker]: https://gitlab.gnome.org/GNOME/gjs/issues [gjs]: https://gitlab.gnome.org/GNOME/gjs [chat]: https://matrix.to/#/#javascript:gnome.org [discourse]: https://discourse.gnome.org/ [newcomers]: https://gitlab.gnome.org/GNOME/gjs/issues?label_name%5B%5D=4.+Newcomers [helpwanted]: https://gitlab.gnome.org/GNOME/gjs/issues?label_name%5B%5D=4.+Help+Wanted [internship]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/Internship-Getting-Started.md [devdocs]: https://github.com/ptomato/devdocs [gjsguide]: https://gitlab.gnome.org/rockon999/gjs-guide [workflow]: https://wiki.gnome.org/GitLab#Using_a_fork_-_Non_GNOME_developer [hacking]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/Hacking.md [doap]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/gjs.doap [googlestyle]: https://google.github.io/styleguide/cppguide.html [cppstyle]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/CPP_Style_Guide.md [eslint]: https://eslint.org/ [commitmessages]: https://chris.beams.io/posts/git-commit/ [contributingtemplate]: https://github.com/nayafia/contributing-template cjs-128.1/COPYING0000664000175000017500000000021215116312211012247 0ustar fabiofabioThis project is dual-licensed as MIT and LGPLv2+. See the header of each file and .reuse/dep5 for files' individual license information. cjs-128.1/CPPLINT.cfg0000664000175000017500000000124615116312211013016 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento # This is the toplevel CPPLINT.cfg file set noparent # We give a limit to clang-format of 80, but we allow 100 here for cases where # it really is more readable to have a longer line linelength=100 # Exceptions to Google style # - build/include_order: We have a special order for include files, see "Header # inclusion order" in CPP_Style_Guide.md. # - build/c++11: This rule bans certain C++ standard library features, which # have their own alternatives in the Chromium codebase, doesn't apply to us. filter=-build/include_order,-build/c++11 cjs-128.1/LICENSES/0000775000175000017500000000000015116312211012426 5ustar fabiofabiocjs-128.1/LICENSES/BSD-3-Clause.txt0000664000175000017500000000271015116312211015151 0ustar fabiofabioCopyright (c) . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cjs-128.1/LICENSES/CC0-1.0.txt0000664000175000017500000001540415116312211014034 0ustar fabiofabioCreative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. cjs-128.1/LICENSES/GPL-2.0-or-later.txt0000664000175000017500000004232615116312211015640 0ustar fabiofabioGNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. cjs-128.1/LICENSES/GPL-3.0-or-later.txt0000664000175000017500000010324615116312211015640 0ustar fabiofabioGNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . cjs-128.1/LICENSES/LGPL-2.0-or-later.txt0000664000175000017500000006030615116312211015752 0ustar fabiofabioGNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the library's name and an idea of what it does. Copyright (C) year name of author This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. signature of Ty Coon, 1 April 1990 Ty Coon, President of Vice That's all there is to it! cjs-128.1/LICENSES/LGPL-2.1-or-later.txt0000664000175000017500000006245615116312211015763 0ustar fabiofabioGNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the library's name and an idea of what it does. Copyright (C) year name of author This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. signature of Ty Coon, 1 April 1990 Ty Coon, President of Vice That's all there is to it! cjs-128.1/LICENSES/MIT.txt0000664000175000017500000000212415116312211013617 0ustar fabiofabioMIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cjs-128.1/LICENSES/MPL-1.1.txt0000664000175000017500000005554715116312211014134 0ustar fabiofabioMozilla Public License Version 1.1 1. Definitions. 1.0.1. "Commercial Use" means distribution or otherwise making the Covered Code available to a third party. 1.1. "Contributor" means each entity that creates or contributes to the creation of Modifications. 1.2. "Contributor Version" means the combination of the Original Code, prior Modifications used by a Contributor, and the Modifications made by that particular Contributor. 1.3. "Covered Code" means the Original Code or Modifications or the combination of the Original Code and Modifications, in each case including portions thereof. 1.4. "Electronic Distribution Mechanism" means a mechanism generally accepted in the software development community for the electronic transfer of data. 1.5. "Executable" means Covered Code in any form other than Source Code. 1.6. "Initial Developer" means the individual or entity identified as the Initial Developer in the Source Code notice required by Exhibit A. 1.7. "Larger Work" means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. 1.8. "License" means this document. 1.8.1. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. 1.9. "Modifications" means any addition to or deletion from the substance or structure of either the Original Code or any previous Modifications. When Covered Code is released as a series of files, a Modification is: Any addition to or deletion from the contents of a file containing Original Code or previous Modifications. Any new file that contains any part of the Original Code or previous Modifications. 1.10. "Original Code" means Source Code of computer software code which is described in the Source Code notice required by Exhibit A as Original Code, and which, at the time of its release under this License is not already Covered Code governed by this License. 1.10.1. "Patent Claims" means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. 1.11. "Source Code" means the preferred form of the Covered Code for making modifications to it, including all modules it contains, plus any associated interface definition files, scripts used to control compilation and installation of an Executable, or source code differential comparisons against either the Original Code or another well known, available Covered Code of the Contributor's choice. The Source Code can be in a compressed or archival form, provided the appropriate decompression or de-archiving software is widely available for no charge. 1.12. "You" (or "Your") means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License or a future version of this License issued under Section 6.1. For legal entities, "You" includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. Source Code License. 2.1. The Initial Developer Grant. The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: a. under intellectual property rights (other than patent or trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, and/or as part of a Larger Work; and b. under Patents Claims infringed by the making, using or selling of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Code (or portions thereof). c. the licenses granted in this Section 2.1 (a) and (b) are effective on the date Initial Developer first distributes Original Code under the terms of this License. d. Notwithstanding Section 2.1 (b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices. 2.2. Contributor Grant. Subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license a. under intellectual property rights (other than patent or trademark) Licensable by Contributor, to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof) either on an unmodified basis, with other Modifications, as Covered Code and/or as part of a Larger Work; and b. under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: 1) Modifications made by that Contributor (or portions thereof); and 2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). c. the licenses granted in Sections 2.2 (a) and 2.2 (b) are effective on the date Contributor first makes Commercial Use of the Covered Code. d. Notwithstanding Section 2.2 (b) above, no patent license is granted: 1) for any code that Contributor has deleted from the Contributor Version; 2) separate from the Contributor Version; 3) for infringements caused by: i) third party modifications of Contributor Version or ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or 4) under Patent Claims infringed by Covered Code in the absence of Modifications made by that Contributor. 3. Distribution Obligations. 3.1. Application of License. The Modifications which You create or to which You contribute are governed by the terms of this License, including without limitation Section 2.2. The Source Code version of Covered Code may be distributed only under the terms of this License or a future version of this License released under Section 6.1, and You must include a copy of this License with every copy of the Source Code You distribute. You may not offer or impose any terms on any Source Code version that alters or restricts the applicable version of this License or the recipients' rights hereunder. However, You may include an additional document offering the additional rights described in Section 3.5. 3.2. Availability of Source Code. Any Modification which You create or to which You contribute must be made available in Source Code form under the terms of this License either on the same media as an Executable version or via an accepted Electronic Distribution Mechanism to anyone to whom you made an Executable version available; and if made available via Electronic Distribution Mechanism, must remain available for at least twelve (12) months after the date it initially became available, or at least six (6) months after a subsequent version of that particular Modification has been made available to such recipients. You are responsible for ensuring that the Source Code version remains available even if the Electronic Distribution Mechanism is maintained by a third party. 3.3. Description of Modifications. You must cause all Covered Code to which You contribute to contain a file documenting the changes You made to create that Covered Code and the date of any change. You must include a prominent statement that the Modification is derived, directly or indirectly, from Original Code provided by the Initial Developer and including the name of the Initial Developer in (a) the Source Code, and (b) in any notice in an Executable version or related documentation in which You describe the origin or ownership of the Covered Code. 3.4. Intellectual Property Matters (a) Third Party Claims If Contributor has knowledge that a license under a third party's intellectual property rights is required to exercise the rights granted by such Contributor under Sections 2.1 or 2.2, Contributor must include a text file with the Source Code distribution titled "LEGAL" which describes the claim and the party making the claim in sufficient detail that a recipient will know whom to contact. If Contributor obtains such knowledge after the Modification is made available as described in Section 3.2, Contributor shall promptly modify the LEGAL file in all copies Contributor makes available thereafter and shall take other steps (such as notifying appropriate mailing lists or newsgroups) reasonably calculated to inform those who received the Covered Code that new knowledge has been obtained. (b) Contributor APIs If Contributor's Modifications include an application programming interface and Contributor has knowledge of patent licenses which are reasonably necessary to implement that API, Contributor must also include this information in the LEGAL file. (c) Representations. Contributor represents that, except as disclosed pursuant to Section 3.4 (a) above, Contributor believes that Contributor's Modifications are Contributor's original creation(s) and/or Contributor has sufficient rights to grant the rights conveyed by this License. 3.5. Required Notices. You must duplicate the notice in Exhibit A in each file of the Source Code. If it is not possible to put such notice in a particular Source Code file due to its structure, then You must include such notice in a location (such as a relevant directory) where a user would be likely to look for such a notice. If You created one or more Modification(s) You may add your name as a Contributor to the notice described in Exhibit A. You must also duplicate this License in any documentation for the Source Code where You describe recipients' rights or ownership rights relating to Covered Code. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Code. However, You may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear than any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. 3.6. Distribution of Executable Versions. You may distribute Covered Code in Executable form only if the requirements of Sections 3.1, 3.2, 3.3, 3.4 and 3.5 have been met for that Covered Code, and if You include a notice stating that the Source Code version of the Covered Code is available under the terms of this License, including a description of how and where You have fulfilled the obligations of Section 3.2. The notice must be conspicuously included in any notice in an Executable version, related documentation or collateral in which You describe recipients' rights relating to the Covered Code. You may distribute the Executable version of Covered Code or ownership rights under a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable version does not attempt to limit or alter the recipient's rights in the Source Code version from the rights set forth in this License. If You distribute the Executable version under a different license You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or any Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. 3.7. Larger Works. You may create a Larger Work by combining Covered Code with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Code. 4. Inability to Comply Due to Statute or Regulation. If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Code due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be included in the LEGAL file described in Section 3.4 and must be included with all distributions of the Source Code. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Application of this License. This License applies to code to which the Initial Developer has attached the notice in Exhibit A and to related Covered Code. 6. Versions of the License. 6.1. New Versions Netscape Communications Corporation ("Netscape") may publish revised and/or new versions of the License from time to time. Each version will be given a distinguishing version number. 6.2. Effect of New Versions Once Covered Code has been published under a particular version of the License, You may always continue to use it under the terms of that version. You may also choose to use such Covered Code under the terms of any subsequent version of the License published by Netscape. No one other than Netscape has the right to modify the terms applicable to Covered Code created under this License. 6.3. Derivative Works If You create or use a modified version of this License (which you may only do in order to apply it to code which is not already Covered Code governed by this License), You must (a) rename Your license so that the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", "MPL", "NPL" or any confusingly similar phrase do not appear in your license (except to note that your license differs from this License) and (b) otherwise make it clear that Your version of the license contains terms which differ from the Mozilla Public License and Netscape Public License. (Filling in the name of the Initial Developer, Original Code or Contributor in the notice described in Exhibit A shall not of themselves be deemed to be modifications of this License.) 7. DISCLAIMER OF WARRANTY COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 8. Termination 8.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. All sublicenses to the Covered Code which are properly granted shall survive any termination of this License. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. 8.2. If You initiate litigation by asserting a patent infringement claim (excluding declatory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You file such action is referred to as "Participant") alleging that: a. such Participant's Contributor Version directly or indirectly infringes any patent, then any and all rights granted by such Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively, unless if within 60 days after receipt of notice You either: (i) agree in writing to pay Participant a mutually agreeable reasonable royalty for Your past and future use of Modifications made by such Participant, or (ii) withdraw Your litigation claim with respect to the Contributor Version against such Participant. If within 60 days of notice, a reasonable royalty and payment arrangement are not mutually agreed upon in writing by the parties or the litigation claim is not withdrawn, the rights granted by Participant to You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of the 60 day notice period specified above. b. any software, hardware, or device, other than such Participant's Contributor Version, directly or indirectly infringes any patent, then any rights granted to You by such Participant under Sections 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used, sold, distributed, or had made, Modifications made by that Participant. 8.3. If You assert a patent infringement claim against Participant alleging that such Participant's Contributor Version directly or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the initiation of patent infringement litigation, then the reasonable value of the licenses granted by such Participant under Sections 2.1 or 2.2 shall be taken into account in determining the amount or value of any payment or license. 8.4. In the event of termination under Sections 8.1 or 8.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or any distributor hereunder prior to termination shall survive termination. 9. LIMITATION OF LIABILITY UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 10. U.S. government end users The Covered Code is a "commercial item," as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial computer software documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Code with only those rights set forth herein. 11. Miscellaneous This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by California law provisions (except to the extent applicable law, if any, provides otherwise), excluding its conflict-of-law provisions. With respect to disputes in which at least one party is a citizen of, or an entity chartered or registered to do business in the United States of America, any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California, with venue lying in Santa Clara County, California, with the losing party responsible for costs, including without limitation, court costs and reasonable attorneys' fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. 12. Responsibility for claims As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. 13. Multiple-licensed code Initial Developer may designate portions of the Covered Code as "Multiple-Licensed". "Multiple-Licensed" means that the Initial Developer permits you to utilize portions of the Covered Code under Your choice of the MPL or the alternative licenses, if any, specified by the Initial Developer in the file described in Exhibit A. Exhibit A - Mozilla Public License. "The contents of this file are subject to the Mozilla Public License Version 1.1 (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.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is ______________________________________. The Initial Developer of the Original Code is ________________________. Portions created by ______________________ are Copyright (C) ______ _______________________. All Rights Reserved. Contributor(s): ______________________________________. Alternatively, the contents of this file may be used under the terms of the _____ license (the "[___] License"), in which case the provisions of [______] License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the [___] License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the [___] License." NOTE: The text of this Exhibit A may differ slightly from the text of the notices in the Source Code files of the Original Code. You should use the text of this Exhibit A rather than the text found in the Original Code Source Code for Your Modifications. cjs-128.1/LICENSES/MPL-2.0.txt0000664000175000017500000003520115116312211014115 0ustar fabiofabioMozilla Public License Version 2.0 1. Definitions 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 6. Disclaimer of Warranty Covered Software is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 7. Limitation of Liability Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. cjs-128.1/NEWS0000664000175000017500000051314115116312211011725 0ustar fabiofabioVersion 1.82.1 -------------- - Closed bugs and merge requests: * gnome-shell crash when switching user after upgrade from Fedora 40 to Fedora 41 [#647, !955, Philip Chimento] Version 1.82.0 -------------- - Closed bugs and merge requests: * installed tests are failing because they can't load internal typelibs from parent directory [#639, !953, Simon McVittie] * GIMarshalling test has 3 failures with 1.81.90 on i686 [#642, !954, Philip Chimento] Version 1.81.90 --------------- - Closed bugs and merge requests: * callbacks: fix sweeping check for incremental GC [!859, !950, Evan Welsh, Gary Li] * GJS doesn't handle query parameters in imports [#618, !944, Gary Li] * Integrate gobject-introspection-tests as submodule [!946, Philip Chimento] * module: Include full module specifier in import.meta.url [!947, Philip Chimento] * doap: Remove invalid maintainer entry [!948, Sophie Herold] * installed tests have the wrong libexecdir [#636, !949, Jeremy Bicha] * Inheriting final class crashes GJS [#640, !951, Gary Li] * Various maintenance [!952, Philip Chimento] Version 1.81.2 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 128, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 115. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New APIs + The new `Object.groupBy()` and `Map.groupBy()` static methods group the elements of an iterable according to the return value of a key function. + The new `Promise.withResolvers()` static method returns a Promise as well as its resolve and reject functions, shorthand for a common pattern used when promisifying event-based APIs. + Strings have gained the `isWellFormed()` and `toWellFormed()` methods which help when interoperating with strings that may have unpaired Unicode surrogates. This usually does not come up in the GNOME platform. + ArrayBuffers have gained the `transfer()` and `transferToFixedLength()` methods, which transfer ownership of a data buffer to a new ArrayBuffer object, without copying it, and invalidating ("detaching") any existing references to the buffer. There is also a new property, `detached`, which allows checking whether an ArrayBuffer is in the detached state. + The new `Intl.Segmenter` class allows splitting a string into graphemes, words, or sentences, in a locale-aware way. + `Intl.NumberFormat` has gained `formatRange()` and `formatRangeToParts()` methods, which allow formatting number ranges, like "3–5". + `Intl.PluralRules` has gained a `selectRange()` method, which allows selecting the proper plural form based on a range of numbers, like "30–50 feral hogs". * New behaviour + The `Intl.NumberFormat` and `Intl.PluralRules` constructors support new options: `roundingIncrement`, `roundingMode`, `roundingPriority`, and `trailingZeroDisplay`. + The `Intl.NumberFormat` constructor also supports the new option `useGrouping`. * Backwards-incompatible changes + The behaviour of `Date.parse()` has been changed to be more consistent with other JavaScript engines. (But don't use `Date.parse()`.) - Closed bugs and merge requests: * Invalid search paths cause failed assertions when printing imports.gi [#629, !935, Gary Li] * SpiderMonkey 128 [#630, !936, !945, Philip Chimento] * Pretty-printing byte array in cjs-console throws a type conversion error [#434, !937, Gary Li] * js: Add gjs_debug_callable() debug function [!940, Philip Chimento] * build: Build Cairo from subproject if not found [!941, Philip Chimento] * Bump CI image to Fedora 40 [!942, Philip Chimento] * CI tools updates [!943, Philip Chimento] Version 1.81.1 -------------- - Breaking change: When creating a GObject with the `new` operator, the constructor takes a single argument consisting of a property bag with GObject construct properties and their values. This was often confused with the `new` static method that may take arguments that are not interpreted as property bags. For example, Gio.FileIcon was one of the many affected APIs: new Gio.FileIcon({file: myFile}) vs Gio.FileIcon.new(myFile) Confusion between the two often lead to bug reports when confusing these two and calling `new Gio.FileIcon(myFile)` - the constructor would look for a nonexistent `file` property on `myFile`, causing an improperly initialized object. This is now no longer allowed. The argument to `new Gio.FileIcon(...)` must be a plain JS object, not a GObject. It's possible that existing code legitimately used a GObject here. If your code does this and a quick migration is impractical, please get in touch and we will revert this change before 1.82.0 in favour of a longer deprecation period. - The `get_data()`, `get_qdata()`, `set_data()`, `steal_data()`, `steal_qdata()`, `ref()`, `unref()`, `ref_sink()`, and `force_floating()` methods of GObject now throw if called. These methods never worked, but sometimes they would silently appear to succeed, then cause crashes or memory leaks later. If you were trying to use the `get_data()` family of methods, just set a JS property instead. If you were trying to modify the refcount of a GObject in JS, instead set the object as the value of a JS property on some other object. - Closed bugs and merge requests: * doc: Document how to get a stack trace [!864, Sonny Piers] * TextDecoder should accept GBytes [#587, !903, Sriyansh Shivam] * Possible use-after-free with GLib.Regex.match/GLib.MatchInfo [#589, !920, Philip Chimento] * method `get_line` of `Pango.Layout` doesn't work. [#547, !921, Philip Chimento] * Block calls to g_object_get_data and friends [#423, !922, Philip Chimento] * Crash when calling Pango.Layout.get_pixel_size() with a badly init:ed Pango.Layout [#580, !923, Philip Chimento] * doc: avoid reference to Gio.UnixInputStream [!925, Andy Holmes] * Add a CI check for config.h, and some other useful checks [#447, !926, Philip Chimento] * Incorrect UnixOutputStream warning [#610, !928, Philip Chimento] * Various maintenance [!929, !931, Philip Chimento] * Docs: Various markdown fixes [!930, Frank Dana] * Some build fixes for the main (and gnome-46) branches for Visual Studio [!932, Chun-wei Fan] * GJS doesn't log undefined values [#621, !933, Gary Li] * property objects are printed as empty js objects [#622, !934, Gary Li] Version 1.80.2 -------------- - Quick follow-up release to fix crash on ppc64. - Closed bugs and merge requests: * 1.79.90 failing tests on ppc64 [#605, !927, Daniel Kolesa] Version 1.80.1 -------------- - Quick follow-up release to fix build failure on MacPorts and Homebrew. - Closed bugs and merge requests: * 1.79.90: gi/arg-inl.h: expression is not assignable [#608, !924, Philip Chimento] Version 1.78.5 -------------- - You may have noticed that WeakRef and FinalizationRegistry... never actually worked as they were supposed to. They work now! - Closed bugs and merge requests: * Workspace switching performance degradation due to leaked WeakRefs in JS [#600, !913, Philip Chimento] Version 1.80.0 -------------- - In GNOME 46 and later, platform-specific GLib and Gio APIs have moved to the separate libraries GLibUnix, GioUnix, GLibWin32, and GioWin32. They are still available in the main GLib and Gio libraries, so your code will continue to work, but you will get a deprecation message. To migrate your code, import the new libraries (e.g., `import GioUnix from 'gi://GioUnix';`) and consider the 'Unix' or 'Win32' prefix part of the namespace, rather than class or function name: e.g., * Gio.UnixInputStream -> GioUnix.InputStream * GLib.unix_open_pipe -> GLibUnix.open_pipe Exceptions to the above rule are Gio.UnixConnection, Gio.UnixCredentialsMessage, Gio.UnixFDList, Gio.UnixSocketAddress, and Gio.UnixSocketAddressType. These remain in Gio, because they are actually cross-platform despite being named "Unix". - Closed bugs and merge requests: * meson: fix automagic dependency lookup for cairo [!917, Eli Schwartz] * Deprecate accessing GLibUnix/GLibWin32 APIs through GLib [#599, !918, Philip Chimento] * CI: Build newer GLib in debug Docker image [!919, Philip Chimento] Version 1.79.90 --------------- - You may have noticed that WeakRef and FinalizationRegistry... never actually worked as they were supposed to. They work now! - Closed bugs and merge requests: * Workspace switching performance degradation due to leaked WeakRefs in JS [#600, !913, Philip Chimento] * GTop.glibtop_get_mountlist invocation causes GNOME Shell Crash [#601, !914, Philip Chimento] * Progress towards some performance improvements in accessing GObject properties [!915, Marco Trevisan] * Various maintenance [!916, Philip Chimento] Version 1.79.3 -------------- - Closed bugs and merge requests: * Various maintenance [!912, Philip Chimento] Version 1.78.4 -------------- - Closed bugs and merge requests: * package: Specify GIRepository version [!910, !911, Florian Müllner] Version 1.76.3 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * gi/gerror: Fix version of the GIRepository typelib import [!906, Jordan Petridis] * package: Specify GIRepository version [!910, !911, Florian Müllner] Version 1.79.2 -------------- - Progress towards some performance improvements in accessing GObject properties [Marco Trevisan] - Regression fix also released in 1.78.3 [Philip Chimento] - Closed bugs and merge requests: * value, object: Honor signal arguments transfer annotation [!862, Marco Trevisan] Version 1.78.3 -------------- - Closed bugs and merge requests: * GJS 1.78.2 causes all Gnome extensions preference settings windows to disappears after 3-7 seconds [#598, !909, Philip Chimento] Version 1.79.1 -------------- - Closed bugs and merge requests: * Improve console output [#511, !890, Sriyansh Shivam] * Name the GC source [!897, Ivan Molodetskikh] * Various maintenance [!898, !907, Philip Chimento] * build: Fix meson deprecations [Rick Calixte] * doc: fix broken link in Mainloop.md [!899, Andy Holmes] * overrides: Make class object a parameter of register type hooks [!900, Philip Chimento] * Display correct stack trace on SyntaxError [#584, !901, Philip Chimento] * HTTP server stops listening [#569, !904, Akshay Warrier] Version 1.78.2 -------------- - Closed bugs and merge requests: * Uninitialized memory in float out values can lead to crashes in mozjs gc code later on [#591, !902, Philip Chimento] * Garbage collection of Gdk surfaces [#592, !905, Philip Chimento] * gi/gerror: Fix version of the GIRepository typelib import [!906, Jordan Petridis] Version 1.78.1 -------------- - Closed bugs and merge requests: * Gtk template signals cause a reference cycle that is not detected [#576, !891, James Westman] * Modules from resources may get loaded twice [#577, !892, Philip Chimento] * docs: add examples for creating cairo image surfaces [!894, Andy Holmes] * Deadlocks between GJS GC and dconf gsettings when a setting value is changed [#558, !895, msizanoen] * Gtk3: Fix leak in GtkBuilder template signal connections [!896, Philip Chimento] Version 1.78.0 -------------- - Closed bugs and merge requests: * Improved Console.log Output [!886, Sriyansh Shivam] * `gjs:dbus / Gtk4` unit test fails: Function Gtk.SectionModel.get_section() cannot be called [#575, !889, Matt Turner] Version 1.77.90 --------------- - Building GJS with -fno-exceptions is now the default. To retain the previous behaviour, invoke Meson with -Dcpp_eh=default. - Closed bugs and merge requests: * testEverything fails make check [#95, !858, Marco Trevisan] * Using a Gio.Appinfo().launch with context may crash gjs [#553, !858, Marco Trevisan] * Fixed-size and Zero-terminated arrays are leaked when used as in or inout arguments with transfer none [#561, !858, Marco Trevisan] * Crash due to bad memory usage when calling a function taking an inout array with length parameter and transfer full [#562, !858, Marco Trevisan] * Various maintenance [!875, !888, Philip Chimento, Marco Trevisan, Andy Holmes] * README.MSVC.md: Update for SpiderMonkey-115.x [!877, Chun-wei Fan] * GJS returns pointers instead of numbers for function with output parameters [#570, !878, Philip Chimento, Marco Trevisan] * Profiler spuriously records GJS.boxed_instance and GJS.boxed_prototype [#551, !879, Philip Chimento] * installed-tests/js/meson: Add tests dependencies to dbus tests [!880, Marco Trevisan] * eslint: Make multi-line imports to always include a trailing comma [!881, Marco Trevisan] * Make console.error format GError correctly [#572, !883, Sriyansh Shivam] * Gtk: Throw an error for an invalid Template string [!884, Andy Holmes] * Gtk: Attempt to load Template from a string, if it appears valid [!885, Andy Holmes] * global: Really enable non-mutating Array methods [!887, Philip Chimento] Version 1.77.2 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 115, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 102. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New APIs + Arrays and typed arrays have gained `findLast()` and `findLastIndex()` methods, which act like `find()` and `findIndex()` respectively, but start searching at the end of the array. + Arrays and typed arrays have gained the `with()` method, which returns a copy of the array with one element replaced. + Arrays and typed arrays have gained `toReversed()`, `toSorted()`, and `toSpliced()` methods, which act like `reverse()`, `sort()`, and `splice()` respectively, but return a copy of the array instead of modifying it in-place. + The `Array.fromAsync()` static method acts like `Array.from()` but with async iterables, and returns a Promise that fulfills to the new Array. - It is now possible to build GJS with -fno-exceptions, by invoking Meson with -Dcpp_eh=none. - Closed bugs and merge requests: * Port to mozjs115 [#556, !855, !871, !874, Xi Ruoyao, Philip Chimento] * Various maintenance [!856, Philip Chimento] * arg: Preserve transfer when freeing out arrays [!857, Marco Trevisan] * Some values leak fixes and cleanups [!860, Marco Trevisan] * Does not parse hash tables in signals [#488, !861, Marco Trevisan] * docs: fix minor URL mistakes and behavioural omissions [!865, Andy Holmes] * gjs: Listen to GMemoryMonitor::low-memory-warning to trigger GC [!870, Marco Trevisan] * GSettings override in Gio.js may fail on construction [#418, !873, Onur Şahin] * Gio: Fix constructing Settings with a SettingsSchema object [!876, James Westman, Philip Chimento] Version 1.77.1 -------------- - Includes all fixes from 1.76.1 and 1.76.2. - Many documentation improvements and cleanups. - New API for C programs embedding GJS: gjs_context_run_in_realm(). This allows using the SpiderMonkey API, for advanced use cases, while having entered the main realm where GJS code runs. Most programs will not need to use this. - Closed bugs and merge requests: * Cleanups: Use more autopointers [!763, Marco Trevisan] * bug(build, tests): broken dependency cycle associated with the `have_gtk4` variable [#532, !830, Dominik Opyd] * Better handling of callbacks during GC [!832, Sebastian Keller] * doc: Add Gio and GLib runAsync overrides [!833, Sonny Piers] * installed-tests/meson: Add tests dependencies on gjs console and CjsPrivate [!835, Marco Trevisan] * gi/arg: Cleanup handling of C arrays and GValue arrays [!836, Marco Trevisan] * Various maintenance [!838, !848, Philip Chimento] * doc: Fix http-client.js example [!840, Sonny Piers] * use `meson setup` instead of ambiguous `meson` [!842, Angelo Verlain] * docs: document `GObject.gtypeNameBasedOnJSPath` [!844, Andy Holmes] * docs: fix formatting for `Signals.md` [!845, Andy Holmes] * Provide API to get GTypes defined in a module [#536, !846, Philip Chimento] * doc: Update inroduction [!847, Sonny Piers] * gi/args.cpp: Fix build with Visual Studio [!854, Chun-wei Fan] Version 1.76.2 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * GJS freezes, program stops responding, error states Gtk4 EventController GestureClick returns incorrect state- Gdk.ModifierType on mouse button press in X11 [#507, !829, !850, Sundeep Mediratta] * Caller allocated boxed types or structs are not fully released [#543, !837, !849, Marco Trevisan] * Gjs console leaks invalid option errors [#544, !837, !849, Marco Trevisan] Version 1.76.1 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, Daniel van Vugt] * Memory leak with GError [#36, !837, Marco Trevisan] * GVariant return values leaked [#499, !837, Marco Trevisan] * GBytes's are leaked when passed as-is to a function [#539, !837, Marco Trevisan] * Transformed GValues are leaking temporary instances [#540, !837, Marco Trevisan] * GHash value infos are leaked [#541, !837, Marco Trevisan] * "flat" arrays of GObject's are leaked [#542, !837, Marco Trevisan] * gjs can't print null [#545, !841, Angelo Verlain] Version 1.74.3 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * Possible errors in cairo enums [#516, !811, !852, Vítor Vasconcellos] * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, !852, tuberry] * Handle transfer-none string return value from vfunc implemented in JS [#519, !821, !823, !852, Marco Trevisan, Daniel van Vugt] * GJS freezes, program stops responding, error states Gtk4 EventController GestureClick returns incorrect state- Gdk.ModifierType on mouse button press in X11 [#507, !829, !852, Sundeep Mediratta] * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, !852, Daniel van Vugt] * Memory leak with GError [#36, !837, !852, Marco Trevisan] * GVariant return values leaked [#499, !837, !852, Marco Trevisan] * GBytes's are leaked when passed as-is to a function [#539, !837, !852, Marco Trevisan] * Transformed GValues are leaking temporary instances [#540, !837, !852, Marco Trevisan] * GHash value infos are leaked [#541, !837, !852, Marco Trevisan] * "flat" arrays of GObject's are leaked [#542, !837, !852, Marco Trevisan] * Gjs console leaks invalid option errors [#544, !837, !852, Marco Trevisan] Version 1.72.4 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * log_set_writer_func is not safe to use [#481, !766, !851, Evan Welsh] * Gnome-Shell 42 - crash after login (general protection fault) [#479, !740, !851, Xi Ruoyao] * Static methods on classes from GObject introspection are now present on JS classes that inherit from those classes. [!851, Marco Trevisan] * Enabling window-list extension causes gnome-shell to crash when running "dconf update" as root [#510, !813, !851, Philip Chimento] * Possible errors in cairo enums [#516, !811, !851, Vítor Vasconcellos] * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, !851, tuberry] * Handle transfer-none string return value from vfunc implemented in JS [#519, !821, !823, !851, Marco Trevisan, Daniel van Vugt] * GJS freezes, program stops responding, error states Gtk4 EventController GestureClick returns incorrect state- Gdk.ModifierType on mouse button press in X11 [#507, !829, !851, Sundeep Mediratta] * gnome-shell crashes on exit in js::gc::Cell::storeBuffer [#472, !834, !851, Daniel van Vugt] * Memory leak with GError [#36, !837, !851, Marco Trevisan] * GVariant return values leaked [#499, !837, !851, Marco Trevisan] * GBytes's are leaked when passed as-is to a function [#539, !837, !851, Marco Trevisan] * Transformed GValues are leaking temporary instances [#540, !837, !851, Marco Trevisan] * GHash value infos are leaked [#541, !837, !851, Marco Trevisan] * "flat" arrays of GObject's are leaked [#542, !837, !851, Marco Trevisan] * Gjs console leaks invalid option errors [#544, !837, !851, Marco Trevisan] Version 1.76.0 -------------- - No changes from release candidate 1.75.90. Version 1.75.90 --------------- - Closed bugs and merge requests: * NEWS: fix a typo causing codespell to fail [!824, Marco Trevisan] * doc: Add more apps written in GJS [!822, Sonny Piers] * Gio: Use proper default priority on async generators [!827, Marco Trevisan] * gjs 1.75.2 GObjectValue build test failing on ARM [#529, !825, Marco Trevisan] * testGObjectValue: Enable creating object with a string property [!826, Marco Trevisan] * Handle transfer-none string return value from vfunc implemented in JS [#519, 823, Marco Trevisan, Daniel van Vugt] * Various maintenance, performance improvements [!828, Philip Chimento] Version 1.75.2 -------------- - There are new `Gio.Application.prototype.runAsync()` and `GLib.MainLoop.prototype.runAsync()` methods which do the same thing as `run()` but return a Promise which resolves when the main loop ends, instead of blocking while the main loop runs. Use one of these methods (by awaiting it) if you use async operations with Promises in your application. Previously, it was easy to get into a state where Promises never resolved if you didn't run the main loop inside a callback. [Evan Welsh] - There are new `Gio.InputStream.prototype.createSyncIterator()` and `Gio.InputStream.prototype.createAsyncIterator()` methods which allow easy iteration of input streams in consecutive chunks of bytes, either with a for-of loop or a for-await-of loop. [Sonny Piers] - DBus proxy wrapper classes now have a static `newAsync()` method, which returns a Promise that resolves to an instance of the proxy wrapper class on which `initAsync()` has completed. [Marco Trevisan] - DBus property getters can now return GLib.Variant instances directly, if they have the correct type, instead of returning JS values and having them be packed into GLib.Variants. [Andy Holmes] - Dramatic performance improvements in the legacy `imports.signals` module, which has also gained a `connectAfter()` method that works like the same-named method in GObject signals. (However, the signals module remains legacy, and is mostly there for historical reasons with GNOME Shell. Don't use it in new code.) [Marco Trevisan] - For years we have had a typo in `Cairo.LineCap.SQUARE`, incorrectly naming it `SQUASH`. This is fixed and the typoed name is retained as an alias. [Vítor Vasconcellos] - Also in Cairo, the value of `Cairo.Format.RGB16_565` was wrong. This was fixed with a breaking change, because anyone using it was probably already not getting the results they expected. [Vítor Vasconcellos] - Continuing the Cairo improvements, SVG surfaces have gained `Cairo.SVGSurface.prototype.finish()` and `Cairo.SVGSurface.prototype.flush()` because previously SVG surfaces were only written to disk when the SVGSurface object was garbage collected, making it uncertain to rely on them. [tuberry] - The debugger now handles Symbol values and Symbol property keys of objects. Previously, these were not displayed correctly. [Philip Chimento] - Various type-safety refactors [Marco Trevisan] - Many bug fixes and performance improvements. - Closed bugs and merge requests: * Promises in application.run do not fulfill until loop exit [#468, !732, Evan Welsh] * console: Various cleanups to tracing functions and increase structured logging metadata [!756, Marco Trevisan] * Legacy signals code optimizations [!757, Marco Trevisan] * meson: Depend on g-i 1.71 and enable newly supported tests [!761, Marco Trevisan] * Gio: Add support for initializing a DBus Proxy via a promise [#494, !794, Marco Trevisan, Philip Chimento] * Make GInputStream iterable and async iterable [!573, !797, Sonny Piers] * Gio: allow D-Bus implementations to return pre-packed variants [!796, Andy Holmes] * Update ESLint tooling [!798, Sonny Piers] * Various maintenance [!804, !814, !820, Philip Chimento] * Add legacy signals connectAfter method [!805, Marco Trevisan] * arg-cache: Add support passing caller-allocated C-arrays [!806, Marco Trevisan] * Crash when passing an introspected function as a callback argument [#518, !809, Philip Chimento] * CI: Upgrade CI images to F37 [!810, Philip Chimento] * Possible errors in cairo enums [#516, !811, Vítor Vasconcellos] * ci: Only run source check jobs if relevant files have been changed [!812, Marco Trevisan] * cairo.SVGSurface need finish() and flush() to finalize painting [#515, !816, tuberry] * signals: Fix bugs when multiple handlers are connected and disconnect is called [!818, Evan Welsh] * Handle Symbol values in pretty-printer and debugger [!819, Philip Chimento] Version 1.74.2 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * build error with clang [#514, !807, Philip Chimento] * can't compile current version with mozjs 102 [#503, !808, Philip Chimento] * Enabling window-list extension causes gnome-shell to crash when running "dconf update" as root [#510, !813, Philip Chimento] * log: Fix an off-by-one buffer overflow [!817, Valentin David] Version 1.75.1 -------------- - Static methods on classes from GObject introspection are now present on JS classes that inherit from those classes. [Marco Trevisan] Version 1.74.1 -------------- - Closed bugs and merge requests: * Problem calling promisified D-Bus wrappers with callback [#494, !790, Marco Trevisan] * docs: Fix link in issue template [!799, Jan Tojnar] * doc: Document Gio.FileEnumerator iteration [!800, Sonny Piers] * doc: Fix Markdown formatting in README.MSVC.md [!803, Kisaragi Hiu] Version 1.74.0 -------------- - Many improvements to the examples and documentation. - Build fixes for Windows. - Overrides to certain non-introspectable functions that will now gracefully throw an exception instead of crashing. - Closed bugs and merge requests: * Various maintenance [!786, Philip Chimento] * http example not reliable, relies on server provided content-length. [#498, !787, Andy Holmes] * Gio set_attribute SIGSEGV (Address boundary error) [#496, !788, Philip Chimento] * Fix Visual Studio builds after migration to SpiderMonkey 102.x [!789, Chun-wei Fan] * Update Visual Studio build instructions [!791, Chun-wei Fan] * doc: reformat for better scraping with DevDocs [!792, Andy Holmes] * doc: Update Home [!793, Sonny Piers] * GLib: override GThread functions [!795, Andy Holmes] Version 1.73.90 --------------- - Skipped. Version 1.72.3 -------------- - Fix for crash after build against libffi 3.4.2 ported from the development branch. Version 1.73.2 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 102, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 91. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New APIs + The `Object.hasOwn()` static method can be used as an easier replacement for `Object.prototype.hasOwnProperty.call(...)`. + `Intl.supportedValuesOf()` lets you enumerate which calendars, currencies, collation strategies, numbering systems, time zones, and units are available for internationalization. - It's now possible to use `GObject.BindingGroup.prototype.bind_full()` with JS functions. Previously this method was unusable in JS. - Gio.FileEnumerator is now iterable, both synchronously (with for-of or array spread syntax) and asynchronously (with for-await-of). - Performance improvements in the built-in `imports.signals` module. - Many improvements to the examples and documentation. - Closed bugs and merge requests: * Spidermonkey 102 [#487, !765, !785, Evan Welsh, Philip Chimento] * Object connections / signal emissions optimizations [#485, !758, Marco Trevisan] * tests/Gio: Cleanup Gio._promisify [!767, Marco Trevisan] * Include JUnit reports in builds [!768, Marco Trevisan] * Integrate pretty print to the debugger [!769, Nasah Kuma] * doc: Edit GJS description [!771, Sonny Piers] * doc: note the version `constructor()` became supported [!774, Andy Holmes] * build: disable sysprof agent for subproject fallback [!775, Christian Hergert] * Update CI images [!776, !777, !778, Philip Chimento] * GListModel.get_n_items returns garbage value [#493, !779, Florian Müllner] * Add override for g_binding_group_bind_full() [!780, Florian Müllner] * doc: Modernize examples [!781, Sonny Piers] * doc: Document byteArray deprecation and migration [!782, Sonny Piers] * doc: add simple Gtk.TickCallback example [!783, Andy Holmes] * Make GFileEnumerator iterable and async iterable [!784, Sonny Piers] Version 1.72.2 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * gi/arg-cache.cpp: Fix building on Visual Studio [!772, Chun-wei Fan] * doc: Reflect support for constructor with GObject [!773, Sonny Piers] Version 1.73.1 -------------- - The interactive interpreter now displays its output more intelligently, pretty-printing the properties and values of objects based on their type. This improvement also applies to the log() and logError() functions. - New API: DBus proxy classes now include methods named with the suffix 'Async', which perform async calls to DBus APIs and return Promises. This is in addition to the existing suffixes 'Sync' (for blocking calls) and 'Remote' (for async calls with callbacks.) - There is an override for Gio.ActionMap.prototype.add_action_entries(). Previously this method wouldn't work because it required an array of Gio.ActionEntry objects, which are not possible to construct in GJS. Now it can be used with an array of plain objects. (e.g. `this.add_action_entries([ {name: 'open', activate() { ... }}]);` - GJS is now compatible with libffi 3.4.2 and later. All earlier versions of GJS are not compatible with libffi 3.4.2 and later unless libffi is built with the --disable-exec-static-tramp flag. - GJS now requires Meson 0.54 to build. - Closed bugs and merge requests: * Verbose Object Print Output [#107, !587, Nasah Kuma] * Add support for JS async calls in DBusProxyWrapper [!731, Sergio Costas] * Crash after build against libffi 3.4.2 [#428, !737, Evan Welsh] * Handle reference cycles in new console pretty print function [#469, !739, Nasah Kuma] * Gnome-Shell 42 - crash after login (general protection fault) [#479, !740, Xi Ruoyao] * Various maintenance [!741, Philip Chimento] * jsapi-util-strings: Ignore locale to compute the upper case of a char (i.e. fix implicit properties on Turkish locale) [!742, Marco Trevisan] * Dockerfile: Install Turkish locale in CI for UTF-8 locale too [!743, Marco Trevisan] * Improve pretty-print output for GObject-introspected objects [#476, !744, Nasah Kuma] * Expose pretty print function to tests [!745, Nasah Kuma] * build: track changes to Sysprof meson options [!747, Christian Hergert] * Make Gio.ActionMap.add_action_entries work [#407, !749, Sonny Piers] * Make DBus session and system props non-enumerable [!750, Sonny Piers] * gi/arg-inl: Mark the arg functions as constexpr [!752, Marco Trevisan] * build: Do not use verbose GJS debug logging in tests by default [!753, Marco Trevisan] * minijasmine: Print test JS errors output if any [!754, Marco Trevisan] * doc: document the existence of the console object in GJS [!759, Andy Holmes] * arg-cache: Use a switch to select the not-introspectable error [!762, Marco Trevisan] * log_set_writer_func is not safe to use [#481, !766, Evan Welsh] Version 1.72.1 -------------- - Various fixes ported from the development branch. - Closed bugs and merge requests: * Compilation error: call to deleted function 'js_value_to_c' [#473, !738, Evan Miller] * jsapi-util-strings: Ignore locale to compute the upper case of a char (i.e. fix implicit properties on Turkish locale) [!742, Marco Trevisan] * Fix memory leak when passing a "transfer none" GBytes parameter to a native function [!746, msizanoen1] * arg-cache: Do not leak an interface info structures on Callbacks [!751, Marco Trevisan] * test-ci: Ignore safe directory errors on CI [!755, Marco Trevisan] Version 1.72.0 -------------- - No changes from release candidate 1.71.90. Version 1.70.2 -------------- - Build and compatibility fixes backported from the development branch. - Closed bugs and merge requests: * package: Reverse order of running-from-source checks [!734, Philip Chimento] - Fix build error on Darwin [Evan Miller] Version 1.68.6 -------------- - Build and compatibility fixes backported from the development branch. - Closed bugs and merge requests: * package: Reverse order of running-from-source checks [!734, Philip Chimento] - Fix build error on Darwin [Evan Miller] Version 1.71.90 --------------- - Closed bugs and merge requests: * Cairo test broken with commit ea52cf92 [#461, !724, Philip Chimento] * native: Convert to singleton class [!725, Nasah Kuma] * Checking `instanceof` for primitive types may lead to a crash or error [#464, !726, Marco Trevisan] * Change the GObject Introspection development branch [!727, Emmanuele Bassi] * gi_marshalling_tests_long_in_max test fails on i686 [#462, !728, Philip Chimento, Evan Welsh] * GNOME Shell crashes at startup with the AppIndicator extension enabled [#466, !729, Marco Trevisan] * Instances of classes implementing interfaces can override functions for all implementations of an interface [#467, !730, Evan Welsh] * package: Reverse order of running-from-source checks [!734, Philip Chimento] * Various maintenance [!735, Philip Chimento] * Various maintenance [!736, Evan Welsh] Version 1.71.1 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 91, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 78. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + Private class fields and methods are now supported. They start with `#` and are not accessible outside the class in which they are defined. + The `??=` logical nullish assignment operator, which assigns the right-hand side value to the left-hand side variable if the variable is null or undefined. + The `&&=` logical-and assignment operator, which assigns the right-hand side value to the left-hand side variable if the variable is truthy. + The `||=` logical-or assignment operator, which assigns the right-hand side value to the left-hand side variable if the variable is falsey. + `export * as ... from ...` can be used to aggregate modules. + Regular expressions add the `d` flag, which if defined causes the resulting match object to have an `indices` property giving the positions in the string where capturing and named groups matched. + `static { ... }` blocks in classes allow initialization of classes at the time of creation of the class. * New APIs + Arrays, strings, and typed arrays have gained the `at()` method, which does the same thing as indexing with square brackets but also allows negative numbers, which count from the end, as in Python. + `Promise.any()`, which is similar to `Promise.race()` but resolves on the first successful sub-promise, instead of the first to resolve. + `Error()` now takes an options object as its second parameter, which may contain a `cause` property. This option is used to indicate when an error is caused by another error, but the first error is caught during error handling. + `WeakRef`, which allows you to hold a reference to an object while still allowing it to be garbage collected. + `dateStyle`, `timeStyle`, `fractionalSecondDigits`, and `dayPeriod` are now accepted as options in `Intl.DateTimeFormat()` and `Date.prototype.toLocaleString()`. + `collation` is now accepted as an option in `Intl.Collator()`. + `Intl.DisplayNames` has been added, which allows you to get translations of language, region, currency, and script names. + `Intl.DateTimeFormat` has gained the `formatRange()` and `formatRangeToParts()` methods. * New behaviour + More numbering systems are supported in `Intl.NumberFormat`. + Top-level await (https://v8.dev/features/top-level-await) allows you to use `await` statements outside of an `async` function in an ES module. + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to existing ECMAScript standards and adopts new ones. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/79#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/80#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/81#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/82#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/83#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/84#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/85#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/86#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/87#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/88#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/89#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/90#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/91#JavaScript - It's now possible to pass BigInt values to GObject-introspected functions with 64-bit parameters. This way, you can finally work with large numbers that cannot be accurately stored as a JS Number value and pass them correctly into C. For example, `GLib.Variant.new_int64(2n ** 62n)`. - New API: GJS now has a standards-compliant `setTimeout()` and `setInterval()`. These can now be used as in web browsers, while still integrating with GLib's main loop. - New API: `Cairo.Context.prototype.textExtents()` which makes the `cairo_text_extents()` C function available to JavaScript. - New overrides: `GLib.MAXINT64_BIGINT`, `GLib.MININT64_BIGINT`, and `GLib.MAXUINT64_BIGINT` are BigInt-typed versions of `GLib.MAXINT64` etc. - It's now possible to use a regular `constructor()` in GObject classes instead of an `_init()` method. - It's now possible to use class fields in GObject classes. - `Gio._promisify()` now tries to guess the name of the finish function, if it is omitted. - It's now possible to monkeypatch methods on the prototype of a GObject interface. The most common use case for this is probably promisifying methods on `Gio.File`, so you can now do things like `Gio._promisify(Gio.File.prototype, 'read_async')` without resorting to the `Gio._LocalFilePrototype` workaround. - GObject interfaces are now enumerable, so you can now do things like `Object.keys(Gio.File.prototype)` and get a list of the methods, like you can with other GObject types. - Improvements to the performance of promises, making them more predictable under higher load. - Several performance and type-safety improvements. - Closed bugs and merge requests: * [Mainloop 1/3] Add custom GSource for promise queueing [#1, !557, Evan Welsh, Marco Trevisan] * Upgrade to SpiderMonkey 91 [#413, !632, !687, Evan Welsh, Philip Chimento, Chun-wei Fan] * Promise rejections from signal handlers are silent [#417, !632, Philip Chimento] * Add a binding for GObject.Object.new [#48, !664, Evan Welsh, Philip Chimento] * Object resolve should consider prototypes of GObject interfaces [#189, !665, Evan Welsh, Philip Chimento] * File corruption on file.replace_contents_async [#192, !665, Evan Welsh] * Overriding inherited interface vfuncs clobbers base class implementation [#89, !671, Evan Welsh] * Errors in __init__.js are silenced [#343, !672, Evan Welsh] * Allocate structs which contain pointers [!674, Evan Welsh] * [Mainloop 3/3] WHATWG Timers [!677, Evan Welsh] * [Mainloop 2/3] Implement "implicit" mainloop which only blocks on unresolved imports [!678, Evan Welsh] * Correctly chain constructor prototypes to enable static inheritance [!679, Evan Welsh] * Upgrade CI to Fedora 34 [!683, !684, Philip Chimento] * Various maintenance [!685, !691, !709, !719, Philip Chimento] * doc: Add Junction to applications written in GJS [!688, Sonny Piers] * C++ argument cache [!689, Marco Trevisan, Philip Chimento] * Gio: Make _promisify to guess the finish function by default [!692, Marco Trevisan] * Fails to build with Meson 0.60.2 [#446, !694, !705, Jan Beich, Eli Schwartz] * doc: Add Oh My SVG to standalone applications [!695, Sonny Piers] * ci: Ensure forever callbacks do not leak [!698, Evan Welsh] * gi: Refactor resolving prototypes in GIWrapperInstance constructors [!699, Evan Welsh] * Class fields don't work with GObject classes [#331, !700, Evan Welsh] * gi: Add enumeration hook for Interface prototypes [!701, Evan Welsh] * Fix Visual Studio builds [!706, Chun-wei Fan] * tools: Add iwyu-tool as a binary name for iwyu [!707, Evan Welsh] * gi: Allow GObject.Value boxed type in nested contexts [!708, Evan Welsh, Philip Chimento] * Implemented check for null out-params in some functions in context.cpp [!710, Nasah Kuma] * Broken links on the doc/Home.md file [#458, !711, Andy Holmes] * Accept BigInt values as input for 64-bit parameters to introspected functions [!712, Marco Trevisan, Philip Chimento] * Enable top-level await [!713, Evan Welsh] * modules: Remove double '//' from internal module URIs [!714, Evan Welsh] * modules: Ensure ImportError is an instance of globalThis.Error [!715, Evan Welsh] * global: Enable WeakRefs [!716, Evan Welsh] * global: Enable static class blocks [!717, Evan Welsh] * overrides: Allow users to implement construct-only props with getters [!718, Evan Welsh] * cairo: Add binding for cairo_text_extents() [!720, Philip Chimento] * Non-integer numbers can not be converted to (u)int64 [#459, !721, Philip Chimento] * Print error cause when logging an error [#454, !722, Philip Chimento] * GtkCustomSorter callbacks receives undefined params [#460, !723, Philip Chimento] Version 1.70.1 -------------- - Build and crash fixes backported from the development branch. - Closed bugs and merge requests: * Fix size_t/gsize conversion errors on 32-bit systems [!680, Evan Miller] * Handle optional out parameters in callbacks [#439, !681, Evan Welsh] * installed-tests: Install matchers.js [!682, Simon McVittie] * Link fails on Debian armel|mipsel|powerpc: needs more -latomic [#442, !686, Simon McVittie] * gjs/jsapi-util.cpp: fix build on gcc-12 [!697, Sergei Trofimovich] Version 1.68.5 -------------- - Crash fix backported from the development branch. [#439, !681, Evan Welsh] Version 1.70.0 -------------- - No changes from release candidate 1.69.90. Version 1.68.4 -------------- - Build fix backported from the development branch. [#436, !667, Evan Welsh] Version 1.69.90 --------------- - Closed bugs and merge requests: * Update ESLint to v8 [!657, Evan Welsh] * gi: Enable pending tests which are now correctly handled [!658, Evan Welsh] * gi: Return null if return argument is a pointer type [!659, Evan Welsh] * gi: Assume native enums are signed, avoid asserting. [!660, Evan Welsh] * Fix cppcheck failure [!661, Philip Chimento] * Strange behavior for strings with NUL character [#285, !662, Evan Welsh] * 64-bit int GObject properties have some problems with values > G_MAXINT32 [#92, !663, Evan Welsh] * Crash on dynamic import in interactive interpreter [#429, !666, Evan Welsh] * 1.69.1: gjs test suite is failing when gjs is build with -DG_DISABLE_ASSERT [#436, !667, Evan Welsh] * function: Warn about unhandled promise rejections in System.exit() [!669, Philip Chimento] * attempting to wrap a new GObject mid-construction blows up [#50, !675, Evan Welsh] * Fix IWYU CI job [!676, Evan Welsh] - Build fixes [Evan Welsh, Philip Chimento] Version 1.69.2 -------------- - The TextEncoder and TextDecoder global objects are now available. In most cases, these will be able to replace usage of the imports.byteArray module. We recommend that new code use TextEncoder and TextDecoder to convert strings to UTF-8 encoded Uint8Arrays and vice versa. MDN is a good source of information on how to use these APIs: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder - The 'console' global object is now available. This is for compatibility with Node.js and browser environments, and for familiarity for developers accustomed to them. The previously existing print(), printerr(), log(), logError() functions continue to exist, and are not deprecated. The console methods use GLib structured logging as their backend. - Cairo.Surface has gained getDeviceScale(), setDeviceScale(), getDeviceOffset(), and setDeviceOffset() methods. These wrap the corresponding C functions. - GLib.log_set_writer_func() and GObject.Object.bind_property_full() now work. Previously, they had introspection problems. - There is also a 'console' built-in module which exports functions setConsoleLogDomain() and getConsoleLogDomain(), for controlling the GLib log domain that the console methods use. - The debugger has gained a 'set ignoreCaughtExceptions (true/false)' option. Previously, when an exception was thrown, the debugger would stop, even if the exception was thrown intentionally in order to be caught. With this option, which is now the default, the debugger will keep going on exceptions that are thrown while inside the scope of a try-catch block. - Closed bugs and merge requests: * Implement WHATWG Encoding specification. [!534, Evan Welsh] * cairo-surface: Add setDevice{Offset,Scale} functions [!605, Daniel van Vugt, Philip Chimento] * WHATWG Console Implementation [!634, Evan Welsh] * Add support for GLib.log_set_writer_func [!637, Evan Welsh] * Various maintenance [!649, Philip Chimento] * examples: improve the gettext example [!651, Sonny Piers] * Unable to use bind_property_full [#241, !653, Florian Müllner] * Allow continuing for handled exceptions [#431, !655, Florian Müllner] * text-encoding.cpp: Fix builds on 64-bit Windows [!656, Chun-wei Fan] Version 1.68.3 -------------- - Crash and bug fixes backported from the development branch. - Build fixes [Philip Chimento] - Closed bugs and merge requests: * win32: Fix resource-based imports [!652, Evan Welsh] * overrides/GLib: Guard Error.new_literal against invalid domains [!654, Florian Müllner] Version 1.69.1 -------------- - Memory usage improvements and bug fixes. - Progress on TextEncoder/TextDecoder. - Closed bugs and merge requests: * Cleanup gjs_closure_invoke [#382, !592, Philip Chimento] * Various maintenance [!600, !616, !624, !630, Philip Chimento, Marco Trevisan, Evan Welsh] * doc: Add simple sysprof example [!606, Andy Holmes] * examples: add examples of GtkBuilder templates [!607, Andy Holmes] * doc: document shebang for ESModules [!608, Sonny Piers] * Gio.ListStore.insert_sorted's compare_func isn't handled correctly [#326, !610, Veena Nagar] * object: Block access to object only if it is finalized [!611, Marco Trevisan] * tests: Add unit tests for ToggleQueue and ObjectInstance usage of it [!615, Marco Trevisan] * gjs-test-tools: Throw error if we can't create threads [!618, Marco Trevisan] * build: Support meson unity builds [!619, Marco Trevisan] * build: Support building with precompiled headers [!620, Marco Trevisan] * Support GObject properties with GByteArray type [#276, !621, Veena Nagar] * Regression in running tests with log output redirected to file [#410, !622, Philip Chimento] * doc: add Commit and Almond to applications [!623, Sonny Piers] * closure (and trampoline): Reimplement to be a C++ class with custom heap allocator [!625, Marco Trevisan] * gjs-test-utils: Be more liberal in comparing values of different types [!626, Marco Trevisan] * [regression] gjs main can't build today [#414, !627, Daniel van Vugt] * Add memory counter profiling [#292, !629, Philip Chimento] * Promisify should complain if the async or finish function doesn't exist [#200, !631, Veena Nagar] * Add 'S' conversion specifier to gjs_parse_call_args [!638, Philip Chimento] * Fix builds on Windows/Visual Studio with the latest GIT main [!639, Chun-wei Fan] * meson: fix version check for precompiled headers [!640, Jordan Petridis] * GjsDBusImplementation.emit_property_changed(..., null): assertion failed [#427, !642, Andy Holmes] * gi: Only enumerate properties which GJS defines [!643, Evan Welsh] * Add Internship Getting Started documentation [!645, Philip Chimento] * arg-cache: Handle notified callbacks without destroy [!647, Florian Müllner] * esm/gi: Improve check for version conflicts [!650, Florian Müllner] Version 1.68.2 -------------- - Crash and regression fixes backported from the development branch. - Build fix to adjust to GLib renaming its main branch. - Closed bugs and merge requests: * Fix crash in ByteArray.fromGBytes / ByteArray.fromString with 0-length input [!628, Philip Chimento] * subprojects: Use GLib main branch [!633, Philip Withnall] * Construct-only properties and GTK Builder. [#422, !635, Carlos Garnacho] * Data corruption when passing a 0-terminated array of GVariant [#269, !636, Evan Welsh] * Fix race condition in dynamic module resolution. [!641, Evan Welsh] * Ensure the correct realm is entered in the async executor [!644, Evan Welsh] * Assertion failure in toggle refs with debug mozjs [#416, !646, Evan Welsh] Version 1.68.1 -------------- - Many stability fixes due to refactoring how disposed GObjects are handled. Special thanks to Marco Trevisan for the substantial effort. - Closed bugs and merge requests: * Accessing GLib.ByteArray throws [#386, !590, Philip Chimento] * Missing hyphen and camelCase getters for CONSTRUCT_ONLY GObject properties defined in JavaScript [#391, !591, Philip Chimento] * gnome-shell crashes on dereferencing a destroyed wrapper object [#395, !593, !617, Marco Trevisan] * GNOME crash "JS object wrapper for GObject 0x563bf88f5f50 (GSettings) is being released..." [#294, !593, !617, Marco Trevisan] * Finalizing wrapper for an already freed object [#399, !593, !617, Marco Trevisan] * Calling implemented methods or getters on disposed objects returns function pointers [#396, !594, Marco Trevisan] * overrides/Gio: Fix _LocalFilePrototype [!595, Florian Müllner] * doc: Fix documentation for dynamic imports [!596, Sonny Piers] * Added the meson installation command in dependencies [!597, Veena Nagar] * Upgrade codespell to 2.0.0 in CI [#367, !598, Kajal Sah] * cairo: Add missing semi-colons from dummy class declarations [!599, Matt Turner] * Fixed System.addressOfGObject and System.dumpHeap missing from System ES module [!600, Philip Chimento] * `Error: Failed to convert GValue to a fundamental instance` in Gtk.EventControllerLegacy [#398, !601, Marco Trevisan] * doc: add an example to get relative filename and dirname with import.meta.url [!603, Sonny Piers] * wrapperutils: Use native ostringstream pointer to string conversion [!604, Marco Trevisan] * testFundamental: Add more tests ensuring we properly handle subtypes [!602, Marco Trevisan] * Some simple Visual Studio fixes for main [!612, Chun-wei Fan] * Using GFileMonitor crashes GNOME Shell with toggling down object error [#297, !613, !617, Marco Trevisan] * Deadlock on toggle queue due to GWeakRef [#404, !613, !617, Marco Trevisan] * Using g_thread_join from JS is crashing [#406, !613, !617, Marco Trevisan] * GObject: Ensure to call setter methods for construct-only properties [!614, Carlos Garnacho] Version 1.68.0 -------------- - Closed bugs and merge requests: * 40.rc session crashes in gjs on unlocking (sometimes) [#387, !588, Marco Trevisan] * 40.rc: installed-tests installed despite explicitly disabled [#388, !589, Philip Chimento] Version 1.67.3 -------------- - Closed bugs and merge requests: * System.exit() doesn't work inside signal handler [#19, !565, Evan Welsh] * GdkEvent subtypes trigger assert in Gtk4 [#365, !566, Evan Welsh] * Replace g_memdup [#375, !567, Philip Chimento] * 1.67.2: build fails with gcc 11 [#376, !568, Philip Chimento] * Warnings introspecting array of boxed type as signal argument. [#377, !569, Carlos Garnacho] * Add list command to debugger [!571, Nasah Kuma] * Assertion failure in enqueuePromiseJob [#349, !572, Philip Chimento] * in interpreter Ctrl-c should exit inner shell if stuck [#98, !574, Philip Chimento] * Compiler ambiguity in enum-utils.h on operator overloading [#368, !576, Chun-wei Fan] * Fix GJS_DISABLE_JIT not fully disabling JIT [!575, Ivan Molodetskikh] * Error running gjs built with prefix: g_object_new_is_valid_property: object class 'GjsContext' has no property named 'program-path' [#381, !577, Sonny Piers] * Various maintenance [!578, !586, Philip Chimento] * Add some profiling labels [!579, Ivan Molodetskikh] * Some installed tests (introspection) segfault when GTK isn't available [#383, !580, Olivier Tilloy] * Installed tests do not install the js/modules subdir [#384, !581, Olivier Tilloy] * Installed tests fail because expected path doesn't include project name [#385, !582, Olivier Tilloy] * 1.67.2: Regress test hangs / timeouts on i686 [#379, !583, Marco Trevisan] * object: Do not call any function on disposed GObject pointers [!585, Marco Trevisan] Version 1.67.2 -------------- - New language features: Importing ES modules is now supported, both statically with import statements and dynamically with the import() function. For more information on how to use modules, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import Four built-in modules exist: cairo, gettext, gi, and system. Except for gi, they work similarly to the old-style modules imports.cairo, imports.gettext, and imports.system. Consult the documentation in doc/Modules.md on how to use them. - The debugger now has a "list" command which works very similarly to its GDB equivalent. - New API: GObject.ParamSpec.jsobject() works like the other GObject.ParamSpec types, and allows you to have a GObject property whose value is a JavaScript object (plain object, Date, Array, etc.) - New API: System.programPath is the name of the JS program that GJS is running, or null if there isn't one (for example, in the interactive interpreter.) - New API: System.programArgs is an array of arguments given to the JS program. It is the same as ARGV but is consistently always present. (ARGV was not defined in the interactive interpreter or when embedding GJS in a C program.) - Closed bugs and merge requests: * Support Native JSObject GType for Signals and Properties [!305, Marco Trevisan, Philip Chimento] * Add 'system.programPath' API. [!443, Evan Welsh] * ESM: Enable static imports. (Part 3) [!450, Evan Welsh, Philip Chimento] * Refactor ARGV handling and add `system.programArgs` [!455, Evan Welsh, Philip Chimento] * Function make the object more C++ friendly [!514, Marco Trevisan] * ESM: Enable dynamic imports. [!525, Evan Welsh, Philip Chimento] * Remove JSClass macros from Ns, GType, and Cairo types [!549, Philip Chimento] * various documentation improvements [!551, Sonny Piers] * Replace remaining mentions of window with globalThis [!552, Sonny Piers] * add .editorconfig file [!553, Sonny Piers] * Display current line of source code when displaying current frame in debugger [!554, Nasah Kuma] * doc: add Clapper and Flatseal to thirty party applications written in GJS [!555, Sonny Piers] * Multiline template literals are missing newlines when entered at interactive prompt [#371, !556, Ales Huzik] * function: Remove JSClass macros [!558, Philip Chimento, Marco Trevisan] * Missing classes on global. [#372, !559, Philip Chimento] * arg: fix build failure with glib main branch [!560, Michael Catanzaro] * Update to Jasmine 2.9.1 [!561, Evan Welsh] * Various maintenance [!562, Philip Chimento] * Add list command to debugger [!563, Nasah Kuma] * Upgrade to Jasmine 3.6.0 [!564, Evan Welsh] - Various refactors in preparation for BigInt support in gobject-introspection [Marco Trevisan] Version 1.67.1 -------------- - The debugger now has a "backtrace full" command which works very similarly to its GDB equivalent. - The GObject.ParamFlags.CONSTRUCT_ONLY flag is now correctly enforced, when using it on GObject classes defined in JavaScript. This might break code that was incorrectly trying to set a property that it had previously defined as construct-only. The workaround is to remove the CONSTRUCT_ONLY flag. - Fixed exception when calling GObject.Type(). - Several performance improvements. - Progress on ES Modules. - Closed bugs and merge requests: * gobject: Handle CONSTRUCT_ONLY flag [!377, Florian Müllner] * Add native module registry to global (Part 2) [!456, Evan Welsh] * testGIMarshalling: Expand test coverage for flags [!479, Simon McVittie] * Private Objects: Use native allocators and structs [!494, Marco Trevisan] * Pass-by-reference GValue arguments do not work right [#74, !496, !507, Marco Trevisan] * Templated-data-only GjsAutoPointer (and use it more around) [!504, Marco Trevisan] * Error in function "_init()" in module "modules/overrides/GObject.js" [#238, !508, Nina Pypchenko] * fails to build on 32-bit [#357, !511, Michael Catanzaro] * Revert "arg-cache: Save space by not caching GType" [!512, Jonas Dreßler] * gi/wrapperutils: Move gjs_get_string_id() into resolve() implementations [!513, Jonas Dreßler] * updates on eslint configuration [!517, Nasah Kuma] * Update CONTRIBUTING.md about the runner system failure [!518, Nasah Kuma] * Switch to eslint-plugin-jsdoc and remove lint-condo [!520, #359, Evan Welsh, Philip Chimento] * gi: Check property before access [!521, Florian Müllner] * testGIMarshalling: Actually run the GPtrArray utf8 tests [!522, Marco Trevisan] * Add more documents for "imports" and "imports.gi" [!526, wsgalaxy] * overrides/Gtk: Set BuilderScope in class init [!527, Florian Müllner] * gi/arg-cache: Only skip array length parameter once [!528, Florian Müllner] * Copyright conformance with Reuse Software spec [!529, Philip Chimento, Evan Welsh] * Remove JSClass macros [!530, !533, !537, Philip Chimento] * Avoid pulling from DockerHub in CI [!531, Philip Chimento, Marco Trevisan] * Use GNOME-specific rules with cppcheck [!532, Philip Chimento] * Fedora 33 CI images [!535, Philip Chimento] * Fix IWYU bugs [!536, Philip Chimento] * Reduce bandwidth usage in CI, and pick a more accurate base for diff checks [!538, Philip Chimento] * debugger: Make '$$' mean the last value [!539, Philip Chimento] * Add codespell CI job [#362, !540, !541, !547, Björn Daase] * Various maintenance [!542, !548, Philip Chimento] * fix readline build on certain systems [!543, Jakub Kulík] * build: Require gobject-introspection 1.66.0 [!546, Philip Chimento] * Add backtrace full command to debugger [#208, !550, Nasah Kuma] - Various refactors for type safety [Marco Trevisan] - Various maintenance [Philip Chimento] Version 1.66.2 -------------- - Performance improvements and crash fixes backported from the development branch. - Bug fixes enabling use of GTK 4. - Closed bugs and merge requests: * Error in function "_init()" in module "modules/overrides/GObject.js" [#238, !508, Nina Pypchenko] * Revert "arg-cache: Save space by not caching GType" [!512, Jonas Dreßler] * gi/wrapperutils: Move gjs_get_string_id() into resolve() implementations [!513, Jonas Dreßler] * overrides/Gtk: Set BuilderScope in class init [!527, Florian Müllner] * fix readline build on certain systems [!543, Jakub Kulík] Version 1.64.5 -------------- - Performance improvements and crash fixes backported from the development branch. - Bug fixes enabling use of GTK 4. - Closed bugs and merge requests: * Error in function "_init()" in module "modules/overrides/GObject.js" [#238, !508, Nina Pypchenko] * gi/wrapperutils: Move gjs_get_string_id() into resolve() implementations [!513, Jonas Dreßler] * overrides/Gtk: Set BuilderScope in class init [!527, Florian Müllner] * fix readline build on certain systems [!543, Jakub Kulík] Version 1.66.1 -------------- - Closed bugs and merge requests: * Throws on Unsupported caller allocates [!495, Marco Trevisan] * arg: Fix MIN/MAX safe big integer limits [!492, Marco Trevisan] * Fix leak when virtual function is unimplemented [!498, Evan Welsh] * Cannot compile GJS 1.66.0 on macOS with llvm/clang 10.0.1 [#347, !499, Marc-Antoine Perennou] * console: fix typo in command-line option [!500, Andy Holmes] * Prevent passing null pointers when not nullable [!503, Evan Welsh] * Passing fundamentals to functions no longer works [#353, !506, Evan Welsh] - Fixed examples/clutter.js to work with more recent Clutter [Philip Chimento] Version 1.66.0 -------------- - No change from 1.65.92. Version 1.65.92 --------------- - Closed bugs and merge requests: * CI: Make iwyu idempotent [!481, Simon McVittie] * Enum and flags test failing in s390x [#319, !480, Simon McVittie] * Bring back Visual Studio build support for GJS main branch [!482, Chun-wei Fan] * gjs_dbus_implementation_emit_signal: don't try to unref NULL [!482, Adam Williamson] * doc: add third party applications [!484, Sonny Piers] * boxed: Initialize all the private BoxedInstance members [!487, Marco Trevisan] * object: Fix GjsCallBackTrampoline's leaks [!490, Marco Trevisan] * Various maintenance [!485, Philip Chimento] * Crash using shell's looking glass [#344, !486, Marco Trevisan] Version 1.65.91 --------------- - Closed bugs and merge requests: * Crash in gjs_dbus_implementation_flush() [#332, !471, Andy Holmes] * eslint: Bump ecmaScript version [!473, Florian Müllner] * Documentation: add documentation for ENV variables [!474, Andy Holmes] * Fix build for the main branch on Windows (due to SpiderMonkey-78.x upgrade) [!475, Chun-wei Fan] * Argument cache causes test failure in armhf [#342, !476, Marco Trevisan] * Argument cache causes test regressions in s390x [#341, !477, Simon McVittie] * ByteArray.toString use-after-free [#339, !472, Evan Welsh] * Crash accessing `vfunc_` methods of `Clutter.Actor`s [#313, !478, Evan Welsh] - Various refactors for type safety [Marco Trevisan] Version 1.65.90 --------------- - GJS now has an optional, Linux-only, dependency on libsysprof-capture-4 instead of libsysprof-capture-3 for the profiler functionality. - New API: gjs_coverage_enable() allows the collection of code coverage metrics. If you are using GjsCoverage, it is now required to call gjs_coverage_enable() before you create the first GjsContext. Previously this was not necessary, but due to changes in SpiderMonkey 78 you must now indicate in advance if you want to collect code coverage metrics. - New JavaScript features! This version of GJS is based on SpiderMonkey 78, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 68. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + A new regular expression engine, supporting lookbehind and named capture groups, among other things * New syntax + The ?? operator ("nullish coalescing operator") is now supported + The ?. operator ("optional chaining operator") is now supported + Public static class fields are now supported + Separators in numeric literals are now supported: for example, 1_000_000 * New APIs + String.replaceAll() for replacing all instances of a string inside another string + Promise.allSettled() for awaiting until all Promises in an array have either fulfilled or rejected + Intl.Locale + Intl.ListFormat + Intl.RelativeTimeFormat.formatToParts() * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to existing ECMAScript standards and adopts new ones. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/69#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/70#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/71#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/72#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/73#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/74#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/75#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/76#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/77#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/78#JavaScript * Backwards-incompatible changes + The Object.toSource() method has been removed + The uneval() global function has been removed + A leading zero is now never allowed for BigInt literals, making 08n and 09n invalid similar to the existing error when legacy octal numbers like 07n are used + The Function.caller property now has the value of null if the caller is a strict, async, or generator function, instead of throwing a TypeError - Backwards-incompatible change: Paths specified on the command line with the --coverage-prefix argument, are now always interpreted as paths. If they are relative paths, they will be resolved relative to the current working directory. In previous versions, they would be treated as string prefixes, which led to unexpected behaviour when the path of the script was absolute and the coverage prefix relative, or vice versa. - Closed bugs and merge requests: * Port to libsysprof-capture-4.a [!457, Philip Withnall, Philip Chimento] * CI: Switch ASAN jobs to runners tagged so [!461, Bartłomiej Piotrowski] * Rework global code to support multiple global "types". (Part 1) [!453, Evan Welsh] * SpiderMonkey 78 [#329, !462, !458, Evan Welsh, Philip Chimento] * GIArgument inlines [!460, Marco Trevisan, Philip Chimento] * gjs stopped building on 32 bits [#335, !463, Marco Trevisan, Philip Chimento] * Improve performance of argument marshalling [#70, !48, Giovanni Campagna, Philip Chimento] * Build failure on 32-bit [#336, !465, Michael Catanzaro] * Various maintenance [!464, Philip Chimento] * arg-cache.cpp: Fix build on Visual Studio [!466, Chun-wei Fan] * [regression] Super+A crashes gnome-shell [#338, !467, Philip Chimento] * Generating coverage information seems to be broken [#322, !470, Philip Chimento] - Various refactors for type safety [Marco Trevisan] - Various maintenance [Philip Chimento] Version 1.65.4 -------------- - New language features! Public class fields are now supported. See for more information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields - Closed bugs and merge requests: * arg.cpp: Add required messages for static_assert (fix building on pre-C++17) [!441, Chun-wei Fan] * Add include-what-you-use CI job [!448, !449, Philip Chimento] * Let's enable class fields! [!445, Evan Welsh] * examples: add GListModel implementation [!452, Andy Holmes] * Update ESLint CI image. [!451, Evan Welsh] * function: Only get function name if we actually warn [!454, Jonas Dreßler] * Split print into native library. [!444, Evan Welsh] * Various maintenance [!459, Philip Chimento] - Various refactors for type safety [Marco Trevisan] Version 1.64.4 -------------- - Closed bugs and merge requests: * Fix CI failure caused by GTK4 update [!447, Philip Chimento] Version 1.65.3 -------------- - In GTK 4, Gtk.Widget is now an iterable object which iterates through its child widgets. (`for (let child of widget) { ... }`) - Closed bugs and merge requests: * Installed tests are not in preferred directories [#318, !427, Ross Burton] * Build new test CI images with Buildah [!429, Philip Chimento] * CI fixes for new test images [!433, Philip Chimento] * Various maintenance [!428, Philip Chimento] * Fix dead link [!436, prnsml] * overrides/Gtk: Make GTK4 widgets iterable [!437, Florian Müllner] * arg.cpp: Fix building on Visual Studio [!439, Chun-wei Fan] * Separate closures and vfuncs [!438, Philip Chimento] * Improvements to IWYU script [!435, Philip Chimento] * Various refactors in preparation for ES modules [!440, Evan Welsh, Philip Chimento] - Various refactors for type safety [Marco Trevisan] Version 1.64.3 -------------- - Closed bugs and merge requests: * arg: Don't sink GClosure ref if it's a return value [!426, Philip Chimento] * overrides/Gtk: Adjust gtk_container_child_set_property() check [!431, Florian Müllner] * 1.63.3: test suite is failing [#298, !430, Philip Chimento] * Simplify private pointers [!434, Philip Chimento] - Various backports: * Use memory GSettings backend in tests [Philip Chimento] * Update debug message from trimLeft/trimRight to trimStart/trimEnd [Philip Chimento] * Various fixes for potential crash and memory issues [Philip Chimento] Version 1.58.8 -------------- - Various backports * 1.63.3: test suite is failing [Philip Chimento] * Various fixes for potential crash and memory issues [Philip Chimento] Version 1.65.2 -------------- - It's now possible to omit the getter and setter for a GObject property on your class, if you only need the default behaviour (reading and writing the property, respecting the default value if not set, and implementing property notifications if the setter changes the value.) This should cut down on boilerplate code and any mistakes made in it. - The log level of exception messages has changed. Previously, some exceptions would be logged as critical-level messages even when they were logged intentionally with logError(). Now, critical-level messages are only logged when an exception goes uncaught (programmer error) and in all other cases a warning-level message is logged. - Closed bugs and merge requests: * build: Use '!=' instead of 'is not' to compare string [Robert Mader, !414] * Various maintenance [Philip Chimento, !413, !425] * doc fixes [Sonny Piers, !415, !416] * jsapi-util: Make log levels of exceptions consistent [Philip Chimento, !418] * Too much recursion error accessing overridden gobject interface property from a subclass [Philip Chimento, #306, !408] * JS: migrate from the global `window` to `globalThis` [Andy Holmes, !423] * doc: Fix a typo [Matthew Leeds, !424] Version 1.64.2 -------------- - Closed bugs and merge requests: * GList of int not correctly demarshalled on 64-bit big-endian [Philip Chimento, Simon McVittie, #309, !417, !419] * Fix template use in GTK4 [Florian Müllner, !420] * Don't crash if a callback doesn't return an expected array of values [Marco Trevisan, !405] * Crash passing integer to strv in constructor [Evan Welsh, #315, !422] * Skip some tests if GTK can't be initialised [Ross Burton, !421] - Various backports: * Fix gjs_log_exception() for InternalError [Philip Chimento] * Fix signal match mechanism [Philip Chimento] Version 1.58.7 -------------- - Various backports: * Don't crash if a callback doesn't return an expected array of values [Marco Trevisan] * GList of int not correctly demarshalled on 64-bit big-endian [Philip Chimento, Simon McVittie] * Crash passing integer to strv in constructor [Evan Welsh] * Ignore format-nonliteral warning [Marco Trevisan] Version 1.65.1 -------------- - Closed bugs and merge requests: * boxed: Implement newEnumerate hook for boxed objects [Ole Jørgen Brønner, !400] * ns: Implement newEnumerate hook for namespaces [Ole Jørgen Brønner, !401] * CI: Tag sanitizer jobs as "privileged" [Philip Chimento, !407] * overrides/Gio: Allow promisifying static methods [Florian Müllner, !410] * overrides/Gio: Guard against repeated _promisify() calls [Florian Müllner, !411] Version 1.64.1 -------------- - The BigInt type is now _actually_ available, as it wasn't enabled in the 1.64.0 release even though it was mentioned in the release notes. - Closed bugs and merge requests: * testCommandLine's Unicode tests failing on Alpine Linux [Philip Chimento, #296, !399] * build: Various clean-ups [Jan Tojnar, !403] * Correctly handle vfunc inout parameters [Marco Trevisan, !404] * Fix failed redirect of output in CommandLine tests [Liban Parker, !409] Version 1.58.6 -------------- - Various backports: * Correctly handle vfunc inout parameters [Marco Trevisan] * Fix failed redirect of output in CommandLine tests [Liban Parker] * Avoid filename conflict when tests run in parallel [Philip Chimento] Version 1.64.0 -------------- - No change from 1.63.92. Version 1.63.92 --------------- - Closed bugs and merge requests: * object: Use g_irepository_get_object_gtype_interfaces [Colin Walters, Philip Chimento, #55, !52] * Add -fno-semantic-interposition to -Bsymbolic-functions [Jan Alexander Steffens (heftig), #303, !397] * examples: add a dbus-client and dbus-service example [Andy Holmes, !398] * Various GNOME Shell crashes during GC, mozjs68 regression [Jan Alexander Steffens (heftig), Philip Chimento, #301, !396] Version 1.63.91 --------------- - Closed bugs and merge requests: * [mozjs68] Reorganize modules for ESM. [Evan Welsh, Philip Chimento, !383] * Various maintenance [Philip Chimento, !388] * Fix building GJS main branch with Visual Studio and update build instructions [Chun-wei Fan, !389] * Resolve "Gnome Shell crash on GC run with mozjs68" [Philip Chimento, !391] * installed-tests/js: Add missing dep on warnlib_typelib [Jan Alexander Steffens, !393] * object: Cache known unresolvable properties [Daniel van Vugt, Philip Chimento, !394, #302] Version 1.58.5 -------------- - Closed bugs and merge requests: * Fix Visual Studio builds of gnome-3-34 (1.58.x) branch [Chun-wei Fan, !392] * Can not access GObject properties of classes without GI information [Juan Pablo Ugarte, !385, #299] Version 1.63.90 --------------- - New JS API: The GObject module has gained new overrides: GObject.signal_handler_find(), GObject.signal_handlers_block_matched(), GObject.signal_handlers_unblock_matched(), and GObject.signal_handlers_disconnect_matched(). These overrides replace the corresponding C API, which was not idiomatic for JavaScript and was not fully functional because it used bare C pointers for some of its functionality. See modules/overrides/GObject.js for API documentation. - New JavaScript features! This version of GJS is based on SpiderMonkey 68, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 60. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + The BigInt type, currently a stage 3 proposal in the ES standard, is now available. * New syntax + `globalThis` is now the ES-standard supported way to get the global object, no matter what kind of JS environment. The old way, `window`, will still work, but is no longer preferred. + BigInt literals are expressed by a number with "n" appended to it: for example, `1n`, `9007199254740992n`. * New APIs + String.prototype.trimStart() and String.prototype.trimEnd() now exist and are preferred instead of trimLeft() and trimRight() which are nonstandard. + String.prototype.matchAll() allows easier access to regex capture groups. + Array.prototype.flat() flattens nested arrays, well-known from lodash and similar libraries. + Array.prototype.flatMap() acts like a reverse filter(), allowing adding elements to an array while iterating functional-style. + Object.fromEntries() creates an object from iterable key-value pairs. + Intl.RelativeTimeFormat is useful for formatting time differences into human-readable strings such as "1 day ago". + BigInt64Array and BigUint64Array are two new typed array types. * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to existing ECMAScript standards and adopts new ones. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/61#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/62#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/63#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/64#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/65#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/66#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/67#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/68#JavaScript * Backwards-incompatible changes + The nonstandard String generics were removed. These had only ever been implemented by Mozilla and never made it into a standard. (An example of a String generic is calling a string method on something that might not be a string like this: `String.endsWith(foo, 5)`. The proper way is `String.prototype.endsWith.call(foo, 5)` or converting `foo` to a string.) This should not pose much of a problem for existing code, since in the previous version these would already print a deprecation warning whenever they were used. You can use `moz68tool` from mozjs-deprecation-tools (https://gitlab.gnome.org/ptomato/moz60tool) to scan your code for this nonstandard usage. - Closed bugs and merge requests: * invalid import on signal.h [#295, !382, Philip Chimento] * SpiderMonkey 68 [#270, !386, Philip Chimento] * GObject: Add override for GObject.handler_block_by_func [#290, !371, Philip Chimento] Version 1.63.3 -------------- - Closed bugs and merge requests: * JS ERROR: TypeError: this._rooms.get(...) is undefined [Philip Chimento, #289, !367] * Run CI build with --werror [Philip Chimento, #286, !365] * build: Remove Autotools build system [Philip Chimento, !364] * gjs-symlink script is incompatible with distro builds [Michael Catanzaro, Bastien Nocera, #291, !369, !370] * installed-tests: Don't hardcode the path of bash [Ting-Wei Lan, !372] * Update Visual Studio build instructions (after migrating to full Meson-based builds) [Chun-wei Fan, !375] * object: Warn when setting a deprecated property [Florian Müllner, !378] * CI: Create mozjs68 CI images [Philip Chimento, !379] * Various maintenance [Philip Chimento, !374, !380, !381] Version 1.58.4 -------------- - Now prints a warning when constructing an unregistered object inheriting from GObject (i.e. if you forgot to use GObject.registerClass.) In 1.58.2 this would throw an exception, which broke some existing code, so that change was reverted in 1.58.3. In this version the check is reinstated, but we log a warning instead of throwing an exception, so that people know to fix their code, but without breaking things. NOTE: In 1.64 (the next stable release) the warning will be changed back into an exception, because code with this problem can be subtly broken and cause unexpected errors elsewhere. So make sure to fix your code if you get this warning. - Closed bugs and merge requests: * GSettings crash fixes [Andy Holmes, !373] - Memory savings for Cairo objects [Philip Chimento, !374] - Fix for crash in debug functions [Philip Chimento, !374] Version 1.63.2 -------------- - There is an option for changing the generated GType name for GObject classes created in GJS to a new scheme that is less likely to have collisions. This scheme is not yet the default, but you can opt into it by setting `GObject.gtypeNameBasedOnJSPath = true;` as early as possible in your prograṁ. Doing this may require some changes in Glade files if you use composite widget templates. We recommend you make this change in your codebase as soon as possible, to avoid any surprises in the future. - New JS API: GObject.Object has gained a stop_emission_by_name() method which is a bit more idiomatic than calling GObject.signal_stop_emission_by_name(). - It's now supported to use the "object" attribute in a signal connection in a composite widget template in a Glade file. - Closed bugs and merge requests: * CI: Tweak eslint rule for unneeded parentheses [Florian Müllner, !353] * Smarter GType name computation [Marco Trevisan, !337] * Meson CI [Philip Chimento, !354] * Visual Studio builds using Meson [Chun-wei Fan, !355] * Hide internal symbols from ABI [Marco Trevisan, #194, !352] * Allow creating custom tree models [Giovanni Campagna, #71] * build: Fix dist files [Florian Müllner, !357] * GObject: Add convenience wrapper for signal_stop_emission_by_name() [Florian Müllner, !358] * Various maintenance [Philip Chimento, !356] * object_instance_props_to_g_parameters should do more check on argv [Philip Chimento, #63, !359] * Support flat C arrays of structures [Philip Chimento, !361] * Gtk Templates: support connectObj argument [Andy Holmes, !363] - Various build fixes [Philip Chimento] Version 1.58.2 -------------- - Closed bugs and merge requests: * GObject based class initialization checks [Marco Trevisan, Philip Chimento, !336] * Silently leaked return value of callbacks [Xavier Claessens, Philip Chimento, #86, !44] * Crash when calling Gio.Initable.async_init with not vfunc_async_init implementation [Philip Chimento, #287, !362] * [cairo] insufficient checking [Philip Chimento, #49, !360] - Various crash fixes backported from the development branch that didn't close a bug or merge request. Version 1.63.1 -------------- - Note that the 1.59, 1.60, 1.61, and 1.62 releases are hereby skipped, because we are calling the next stable series 1.64 to match gobject-introspection and GLib. - GJS now includes a Meson build system. This is now the preferred way to build it; however, the old Autotools build system is still available for a transitional period. - Closed bugs and merge requests: * GObject: Add convenience wrapper for signal_handler_(un)block() [Florian Müllner, !326] * GObject based class initialization checks [Marco Trevisan, Philip Chimento, !336] * Meson port [Philip Chimento, !338] * add http client example [Sonny Piers, !342] * Smaller CI, phase 2 [Philip Chimento, !343] * add websocket client example [Sonny Piers, !344] * Fix Docker images build [Philip Chimento, !345] * CI: Use new Docker images [Philip Chimento, !346] * docs: Update internal links [Andy Holmes, !348] * Don't pass generic marshaller to g_signal_newv() [Niels De Graef, !349] * tests: Fail debugger tests if command failed [Philip Chimento, !350] * Minor CI image fixes [Philip Chimento, !351] * Various fixes [Marco Trevisan, Philip Chimento] Version 1.58.1 -------------- - Closed bugs and merge requests: * Import wiki documentation [Sonny Piers, !341] * Smaller CI, phase 1 [Philip Chimento, !339] * Crashes after setting child property 'icon-name' on GtkStack then displaying another GtkStack [Florian Müllner, #284, !347] * GLib.strdelimit crashes [Philip Chimento, #283, !340] Version 1.58.0 -------------- - No change from 1.57.92. Version 1.57.92 --------------- - Closed bugs and merge requests: * tests: Enable regression test cases for GPtrArrays and GArrays of structures [Stéphane Seng, !334] * Various maintenance [Philip Chimento, !333, !335] Version 1.57.91 --------------- - GJS no longer links to libgtk-3. This makes it possible to load the Gtk-4.0 typelib in GJS and write programs that use GTK 4. - The heapgraph tool has gained some improvements; it is now possible to print a heap graph of multiple targets. You can also mark an object for better identification in the heap graph by assigning a magic property: for example, myObject.__heapgraph_name = 'Button' will make that object identify itself as "Button" in heap graphs. - Closed bugs and merge requests: * Remove usage of Lang in non legacy code [Sonny Piers, !322] * GTK4 [Florian Müllner, #99, !328, !330] * JS syntax fixes [Marco Trevisan, Philip Chimento, !306, !323] * gi: Avoid infinite recursion when converting GValues [Florian Müllner, !329] * Implement all GObject-introspection test suites [Philip Chimento, !327, !332] * Heapgraph improvements [Philip Chimento, !325] Version 1.57.90 --------------- - New JS API: GLib.Variant has gained a recursiveUnpack() method which transforms the variant entirely into a JS object, discarding all type information. This can be useful for dealing with a{sv} dictionaries, where deepUnpack() will keep the values as GLib.Variant instances in order to preserve the type information. - New JS API: GLib.Variant has gained a deepUnpack() method which is exactly the same as the already existing deep_unpack(), but fits with the other camelCase APIs that GJS adds. - Closed bugs and merge requests: * Marshalling of GPtrArray broken [#9, !311, Stéphane Seng] * Fix locale chooser [!313, Philip Chimento] * dbus-wrapper: Remove interface skeleton flush idle on dispose [!312, Marco Trevisan] * gobject: Use auto-compartment when getting property as well [!316, Florian Müllner] * modules/signals: Use array destructuring in _emit [!317, Jonas Dreßler] * GJS can't call glibtop_init function from libgtop [#259, !319, Philip Chimento] * GLib's VariantDict is missing lookup [#263, !320, Sonny Piers] * toString on an object implementing an interface fails [#252, !299, Marco Trevisan] * Regression in GstPbutils.Discoverer::discovered callback [#262, !318, Philip Chimento] * GLib.Variant.deep_unpack not working properly with a{sv} variants [#225, !321, Fabián Orccón, Philip Chimento] * Various maintenance [!315, Philip Chimento] - Various CI fixes [Philip Chimento] Version 1.57.4 -------------- - Closed bugs and merge requests: * gjs 1.57 requires a recent sysprof version for sysprof-capture-3 [#258, !309, Olivier Fourdan] - Misc documentation changes [Philip Chimento] Version 1.57.3 -------------- - The GJS profiler is now integrated directly into Sysprof 3, via the GJS_TRACE_FD environment variable. Call stack information and garbage collector timing will show up in Sysprof. See also GNOME/Initiatives#10 - New JS API: System.addressOfGObject(obj) will return a string with the hex address of the underlying GObject of `obj` if it is a GObject wrapper, or throw an exception if it is not. This is intended for debugging. - New JS API: It's now possible to pass a value from Gio.DBusProxyFlags to the constructor of a class created by Gio.DBusProxy.makeProxyWrapper(). - Backwards-incompatible change: Trying to read a write-only property on a DBus proxy object, or write a read-only property, will now throw an exception. Previously it would fail silently. It seems unlikely any code is relying on the old behaviour, and if so then it was probably masking a bug. - Closed bugs and merge requests: * Build failure on Continuous [#253, !300, Philip Chimento] * build: Bump glib requirement [!302, Florian Müllner] * profiler: avoid clearing 512 bytes of stack [!304, Christian Hergert] * system: add addressOfGObject method [!296, Marco Trevisan] * Add support for GJS_TRACE_FD [!295, Christian Hergert] * Gio: Make possible to pass DBusProxyFlags to proxy wrapper [!297, Marco Trevisan] * Various maintenance [!301, Philip Chimento] * Marshalling of GPtrArray broken [#9, !307, Stéphane Seng] * Build fix [!308, Philip Chimento] * Gio: sync dbus wrapper properties flags [!298, Marco Trevisan] * GjsMaybeOwned: Reduce allocation when used as Object member [!303, Marco Trevisan] Version 1.57.2 -------------- - There are now overrides for Gio.SettingsSchema and Gio.Settings which avoid aborting the whole process when trying to access a nonexistent key or child schema. The original API from GLib was intended for apps, since apps should have complete control over which settings keys they are allowed to access. However, it is not a good fit for shell extensions, which may need to access different settings keys depending on the version of GNOME shell they're running on. This feature is based on code from Cinnamon which the copyright holders have kindly agreed to relicense to GJS's license. - New JS API: It is now possible to pass GObject.TypeFlags to GObject.registerClass(). For example, passing `GTypeFlags: GObject.TypeFlags.ABSTRACT` in the class info object, will create a class that cannot be instantiated. This functionality was present in Lang.Class but has been missing from GObject.registerClass(). - Closed bugs and merge requests: * Document logging features [#230, !288, Andy Holmes] * Support optional GTypeFlags value in GObject subclasses [!290, Florian Müllner] * Ensure const-correctness in C++ objects [#105, !291, Onur Şahin] * Programmer errors with GSettings cause segfaults [#205, !284, Philip Chimento] * Various maintenance [!292, Philip Chimento] * debugger: Fix summary help [!293, Florian Müllner] * context: Use Heap pointers for GC objects stored in vectors [!294, Philip Chimento] Version 1.56.2 -------------- - Closed bugs and merge requests: * Crash in BoxedInstance when struct could not be allocated directly [#240, !285, Philip Chimento] * Cairo conversion bugs [!286, Philip Chimento] * Gjs crashes when binding inherited property to js added gobject-property [#246, !289, Marco Trevisan] * console: Don't accept --profile after the script name [!287, Philip Chimento] Version 1.57.1 -------------- - Closed bugs and merge requests: * Various maintenance [!279, Philip Chimento] * mainloop: Assign null to property instead of deleting [!280, Jason Hicks] * Added -d version note README.md [!282, Nauman Umer] * Extra help for debugger commands [#236, !283, Nauman Umer] * Crash in BoxedInstance when struct could not be allocated directly [#240, !285, Philip Chimento] * Cairo conversion bugs [!286, Philip Chimento] Version 1.56.1 -------------- - Closed bugs and merge requests: * Calling dumpHeap() on non-existent directory causes crash [#134, !277, Philip Chimento] * Using Gio.MemoryInputStream.new_from_data ("string") causes segfault [#221, !278, Philip Chimento] * Fix gjs_context_eval() for non-zero-terminated strings [!281, Philip Chimento] Version 1.56.0 -------------- - No change from 1.55.92. Version 1.55.92 --------------- - Closed bugs and merge requests: * Fix CI failures [!269, Philip Chimento] * Possible memory allocation/deallocation bug (possibly in js_free() in GJS) [!270, Chun-wei Fan, Philip Chimento] * cairo-context: Special-case 0-sized vector [!271, Florian Müllner] * Add some more eslint rules [!272, Florian Müllner] * win32/NMake: Fix introspection builds [!274, Chun-wei Fan] * NMake/libgjs-private: Export all the public symbols there [!275, Chun-wei Fan] Version 1.55.91 --------------- - The problem of freezing while running the tests using GCC's sanitizers was determined to be a bug in GCC, which was fixed in GCC 9.0.1. - Closed bugs and merge requests: * gnome-sound-recorder crashes deep inside libgjs [#223, !266, Philip Chimento] * Various maintenance [!267, Philip Chimento] * wrapperutils: Define $gtype property as non-enumerable [!268, Philip Chimento] Version 1.55.90 --------------- - New JS API: It's now possible to call and implement DBus methods whose parameters or return types include file descriptor lists (type signature 'h'.) This involves passing or receiving a Gio.UnixFDList instance along with the parameters or return values. To call a method with a file descriptor list, pass the Gio.UnixFDList along with the rest of the parameters, in any order, the same way you would pass a Gio.Cancellable or async callback. For return values, things are a little more complicated, in order to avoid breaking existing code. Previously, synchronously called DBus proxy methods would return an unpacked GVariant. Now, but only if called with a Gio.UnixFDList, they will return [unpacked GVariant, Gio.UnixFDList]. This does not break existing code because it was not possible to call a method with a Gio.UnixFDList before, and the return value is unchanged if not calling with a Gio.UnixFDList. This does mean, unfortunately, that if you have a method with an 'h' in its return signature but not in its argument signatures, you will have to call it with an empty FDList in order to receive an FDList with the return value, when calling synchronously. On the DBus service side, when receiving a method call, we now pass the Gio.UnixFDList received from DBus to the called method. Previously, sync methods were passed the parameters, and async methods were passed the parameters plus the Gio.DBusInvocation object. Appending the Gio.UnixFDList to those parameters also should not break existing code. See the new tests in installed-tests/js/testGDBus.js for examples of calling methods with FD lists. - We have observed on the CI server that GJS 1.55.90 will hang forever while running the test suite compiled with GCC 9.0.0 and configured with the --enable-asan and --enable-ubsan arguments. This should be addressed in one of the following 1.55.x releases. - Closed bugs and merge requests: * GDBus proxy overrides should support Gio.DBusProxy.call_with_unix_fd_list() [#204, !263, Philip Chimento] * Add regression tests for GObject vfuncs [!259, Jason Hicks] * CjsPrivate: Sources should be C files [!262, Philip Chimento] * build: Vendor last-good version of AX_CODE_COVERAGE [!264, Philip Chimento] Version 1.55.4 -------------- - Closed bugs and merge requests: * Various maintenance [!258, Philip Chimento] * Boxed copy constructor should not be called, split Boxed into prototype and instance structs [#215, !260, Philip Chimento] Version 1.55.3 -------------- - Closed bugs and merge requests: * Manually constructed ByteArray toString segfaults [#219, !254, Philip Chimento] * signals: Add _signalHandlerIsConnected method [!255, Jason Hicks] * Various maintenance [!257, Philip Chimento] Version 1.52.5 -------------- - This was a release consisting only of backports from the GNOME 3.30 branch to the GNOME 3.28 branch. - This release includes the "Big Hammer" patch from GNOME 3.30 to reduce memory usage. For more information, read the blog post at https://feaneron.com/2018/04/20/the-infamous-gnome-shell-memory-leak/ It was not originally intended to be backported to GNOME 3.28, but in practice several Linux distributions already backported it, and it has been working well to reduce memory usage, and the bugs have been ironed out of it. It does decrease performance somewhat, so if you don't want that then don't install this update. - Closed bugs and merge requests: * Ensure not to miss the force_gc flag [#150, !132, Carlos Garnacho] * Make GC much more aggressive [#62, !50, Giovanni Campagna, Georges Basile Stavracas Neto, Philip Chimento] * Queue GC when a GObject reference is toggled down [#140, !114, !127, Georges Basile Stavracas Neto] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * Use compacting GC on RSS size growth [!133, #151, Carlos Garnacho] * GType memleak fixes [!244, Marco Trevisan] Version 1.55.2 -------------- - Closed bugs and merge requests: * Gnome-shell crashes on destroying cached param specs [#213, !240, Marco Trevisan] * Various maintenance [!235, !250, Philip Chimento] * Auto pointers builder [!243, Marco Trevisan] * configure.ac: Update bug link [!245, Andrea Azzarone] * SIGSEGV when exiting gnome-shell [#212, !247, Andrea Azzarone, Philip Chimento] * Fix build with --enable-dtrace and create CI job to ensure it doesn't break in the future [#196, !237, !253, Philip Chimento] * Delay JSString-to-UTF8 conversion [!249, Philip Chimento] * Annotate return values [!251, Philip Chimento] * Fix a regression with GError toString() [!252, Philip Chimento] * GType memleak fixes [!244, Marco Trevisan] * Atoms refactor [!233, Philip Chimento, Marco Trevisan] * Write a "Code Hospitable" README file [#17, !248, Philip Chimento, Andy Holmes, Avi Zajac] * object: Method lookup repeatedly traverses introspection [#54, !53, Colin Walters, Philip Chimento] * Handler of GtkEditable::insert-text signal is not run [#147, !143, Tomasz Miąsko, Philip Chimento] Version 1.54.3 -------------- - Closed bugs and merge requests: * object: Fix write-only properties [!246, Philip Chimento] * SIGSEGV when exiting gnome-shell [#212, !247, Andrea Azzarone] * SelectionData.get_targets crashes with "Unable to resize vector" [#201, !241, Philip Chimento] * Gnome-shell crashes on destroying cached param specs [#213, !240, Marco Trevisan] * GType memleak fixes [!244, Marco Trevisan] * Fix build with --enable-dtrace and create CI job to ensure it doesn't break in the future [#196, !253, Philip Chimento] Version 1.54.2 -------------- - Closed bugs and merge requests: * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * fundamental: Check if gtype is valid before using it [!242, Georges Basile Stavracas Neto] - Backported a fix for a crash in the interactive interpreter when executing something like `throw "foo"` [Philip Chimento] - Backported various maintenance from 3.31 [Philip Chimento] Version 1.55.1 -------------- - New API for programs that embed GJS: gjs_memory_report(). This was already an internal API, but now it is exported. - Closed bugs and merge requests: * object: Implement newEnumerate hook for GObject [!155, Ole Jørgen Brønner] * Various maintenance [!228, Philip Chimento] * ByteArray.toString should stop at null bytes [#195, !232, Philip Chimento] * Byte arrays that represent encoded strings should be 0-terminated [#203, !232, Philip Chimento] * context: Defer and therefore batch forced GC runs [performance] [!236, Daniel van Vugt] * context: use timeout with seconds to schedule a gc trigger [!239, Marco Trevisan] * arg: Add special-case for byte arrays going to C [#67, !49, Jasper St. Pierre, Philip Chimento] Version 1.52.4 -------------- - This was a release consisting only of backports from the GNOME 3.30 branch to the GNOME 3.28 branch. - Closed bugs and merge requests: * `ARGV` encoding issues [#22, !108, Evan Welsh] * Segfault on enumeration of GjSFileImporter properties when a searchpath entry contains a symlink [#154, !144, Ole Jørgen Brønner] * Possible refcounting bug around GtkListbox signal handlers [#24, !154, Philip Chimento] * Fix up GJS_DISABLE_JIT flag now the JIT is enabled by default in SpiderMonkey [!159, Christopher Wheeldon] * Expose GObject static property symbols. [!197, Evan Welsh] * Do not run linters on tagged commits [!181, Claudio André] * gjs-1.52.0 fails to compile against x86_64 musl systems [#132, !214, Philip Chimento] * gjs no longer builds after recent autoconf-archive updates [#149, !217, Philip Chimento] Version 1.54.1 -------------- - Closed bugs and merge requests: * legacy: Ensure generated GType names are valid [!229, Florian Müllner] * Fix GJS profiler with MozJS 60 [!230, Georges Basile Stavracas Neto] * Regression with DBus proxies [#202, !231, Philip Chimento] Version 1.54.0 -------------- - Compatibility fix for byte arrays: the legacy toString() behaviour of byte arrays returned from GObject-introspected functions is now restored. If you use the functionality, a warning will be logged asking you to upgrade your code. - Closed bugs and merge requests: * byteArray: Add compatibility toString property [Philip Chimento, !227] Version 1.53.92 --------------- - Technology preview of a GNOME 3.32 feature: native Promises for GIO-style asynchronous operations. This is the result of Avi Zajac's summer internship. To use it, you can opt in once for each specific asynchronous method, by including code such as the following: Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async', 'read_bytes_finish'); After executing this, you will be able to use native Promises with the Gio.InputStream.prototype.read_async() method, simply by not passing a callback to it: try { let bytes = await stream.read_bytes_async(count, priority, cancel); } catch (e) { logError(e, 'Failed to read bytes'); } Note that any "success" boolean return values are deleted from the array of return values from the async method. That is, let [contents, etag] = file.load_contents_async(cancel); whereas the callback version still returns a useless [ok, contents, etag] that can never be false, since on false an exception would be thrown. In the callback version, we must keep this for compatibility reasons. Note that due to a bug in GJS (https://gitlab.gnome.org/GNOME/gjs/issues/189), promisifying methods on Gio.File.prototype and other interface prototypes will not work. We provide the API Gio._LocalFilePrototype on which you can promisify methods that will work on Gio.File instances on the local disk only: Gio._promisify(Gio._LocalFilePrototype, 'load_contents_async', 'load_contents_finish'); We estimate this will cover many common use cases. Since this is a technology preview, we do not guarantee API stability with the version coming in GNOME 3.32. These APIs are marked with underscores to emphasize that they are not stable yet. Use them at your own risk. - Closed bugs and merge requests: * Added promisify to GJS GIO overrides [!225, Avi Zajac] * Temporary fix for Gio.File.prototype [!226, Avi Zajac] Version 1.53.91 --------------- - Closed bugs and merge requests: * CI: add webkit and gtk-app tests [!222, Claudio André] * Fix example eslint errors [!207, Claudio André, Philip Chimento] * Fix more "lost" GInterface properties [!223, Florian Müllner] * Fix --enable-installed-tests when built from a tarball [!224, Simon McVittie] Version 1.53.90 --------------- - GJS now depends on SpiderMonkey 60 and requires a compiler capable of C++14. - GJS includes a simple debugger now. It has basic stepping, breaking, and printing commands, that work like GDB. Activate it by running the GJS console interpreter with the -d or --debugger flag before the name of the JS program on the command line. - New API for programs that embed GJS: gjs_context_setup_debugger_console(). To integrate the debugger into programs that embed the GJS interpreter, call this before executing the JS program. - New JavaScript features! This version of GJS is based on SpiderMonkey 60, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 52. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + `for await (... of ...)` syntax is used for async iteration. + The rest operator is now supported in object destructuring: e.g. `({a, b, ...cd} = {a: 1, b: 2, c: 3, d: 4});` + The spread operator is now supported in object literals: e.g. `mergedObject = {...obj1, ...obj2};` + Generator methods can now be async, using the `async function*` syntax, or `async* f() {...}` method shorthand. + It's now allowed to omit the variable binding from a catch statement, if you don't need to access the thrown exception: `try {...} catch {}` * New APIs + Promise.prototype.finally(), popular in many third-party Promise libraries, is now available natively. + String.prototype.toLocaleLowerCase() and String.prototype.toLocaleUpperCase() now take an optional locale or array of locales. + Intl.PluralRules is now available. + Intl.NumberFormat.prototype.formatToParts() is now available. + Intl.Collator now has a caseFirst option. + Intl.DateTimeFormat now has an hourCycle option. * New behaviour + There are a lot of minor behaviour changes as SpiderMonkey's JS implementation conforms ever closer to ECMAScript standards. For complete information, read the Firefox developer release notes: https://developer.mozilla.org/en-US/Firefox/Releases/53#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/54#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/55#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/56#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/57#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/58#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/59#JavaScript https://developer.mozilla.org/en-US/Firefox/Releases/60#JavaScript * Backwards-incompatible changes + Conditional catch clauses have been removed, as they were a Mozilla extension which will not be standardized. This requires some attention in GJS programs, as previously we condoned code like `catch (e if e.matches(Gio.IOError, Gio.IOError.EXISTS))` with a comment in overrides/GLib.js, so it's likely this is used in several places. + The nonstandard `for each (... in ...)` loop was removed. + The nonstandard legacy lambda syntax (`function(x) x*x`) was removed. + The nonstandard Mozilla iteration protocol was removed, as well as nonstandard Mozilla generators, including the Iterator and StopIteration objects, and the Function.prototype.isGenerator() method. + Array comprehensions and generator comprehensions have been removed. + Several nonstandard methods were removed: ArrayBuffer.slice() (but not the standard version, ArrayBuffer.prototype.slice()), Date.prototype.toLocaleFormat(), Function.prototype.isGenerator(), Object.prototype.watch(), and Object.prototype.unwatch(). - Many of the above backwards-incompatible changes can be caught by scanning your source code using https://gitlab.gnome.org/ptomato/moz60tool, or https://extensions.gnome.org/extension/1455/spidermonkey-60-migration-validator/ - Deprecation: the custom ByteArray is now discouraged. Instead of ByteArray, use Javascript's native Uint8Array. The ByteArray module still contains functions for converting between byte arrays, strings, and GLib.Bytes instances. The old ByteArray will continue to work as before, except that Uint8Array will now be returned from introspected functions that previously returned a ByteArray. To keep your old code working, change this: let byteArray = functionThatReturnsByteArray(); to this: let byteArray = new ByteArray.ByteArray(functionThatReturnsByteArray()); To port to the new code: * ByteArray.ByteArray -> Uint8Array * ByteArray.fromArray() -> Uint8Array.from() * ByteArray.ByteArray.prototype.toString() -> ByteArray.toString() * ByteArray.ByteArray.prototype.toGBytes() -> ByteArray.toGBytes() * ByteArray.fromString(), ByteArray.fromGBytes() remain the same * Unlike ByteArray, Uint8Array's length is fixed. Assigning an element past the end of a ByteArray would lengthen the array. Now, it is ignored. Instead use Uint8Array.of(), for example, this code: let a = ByteArray.fromArray([97, 98, 99, 100]); a[4] = 101; should be replaced by this code: let a = Uint8Array.from([97, 98, 99, 100]); a = Uint8Array.of(...a, 101); The length of the byte array must be set at creation time. This code will not work anymore: let a = new ByteArray.ByteArray(); a[0] = 255; Instead, use "new Uint8Array(1)" to reserve the correct length. - Closed bugs and merge requests: * Run tests using real software [#178, !192, Claudio André] * Script tests are missing some errors [#179, !192, Claudio André] * Create a '--disable-readline' option and use it [!196, Claudio André] * CI: stop using Fedora for clang builds [!198, Claudio André] * Expose GObject static property symbols. [!197, Evan Welsh] * CI fixes [!200, Claudio André] * Docker images creation [!201, Claudio André] * Get Docker images built and stored in GJS registry [#185, !203, !208, Claudio André, Philip Chimento] * Clear the static analysis image a bit more [!205, Claudio André] * Rename the packaging job to flatpak [!210, Claudio André] * Create SpiderMonkey 60 docker images [!202, Claudio André] * Debugger [#110, !204, Philip Chimento] * Add convenience g_object_set() replacement [!213, Florian Müllner] * Add dependencies of the real tests (examples) [!215, Claudio André] * CWE-126 [#174, !218, Philip Chimento] * gjs no longer builds after recent autoconf-archive updates [#149, !217, Philip Chimento] * gjs-1.52.0 fails to compile against x86_64 musl systems [#132, !214, Philip Chimento] * Run the GTK real tests (recently added) [!212, Claudio André] * Fix thorough tests failures [!220, Philip Chimento] * Port to SpiderMonkey 60 [#161, !199, Philip Chimento] * Replace ByteArray with native ES6 TypedArray [#5, !199, Philip Chimento] * Overriding GInterface properties broke [#186, !216, Florian Müllner, Philip Chimento] * Avoid segfault when checking for GByteArray [!221, Florian Müllner] - Various build fixes [Philip Chimento] Version 1.53.4 -------------- - Refactored the way GObject properties are accessed. This should be a bit more efficient, as property info (GParamSpec) is now cached for every object type. There may still be some regressions from this; please be on the lookout so we can fix them in the next release. - The memory usage for each object instance has been reduced, resulting in several dozens of megabytes less memory usage in GNOME Shell. - The CI pipeline was refactored, now runs a lot faster, detects more failure situations, builds on different architectures, uses the GitLab Docker registry, and publishes code coverage statistics to https://gnome.pages.gitlab.gnome.org/gjs/ - For contributors, the C++ style guide has been updated, and there is now a script in the tools/ directory that will install a Git hook to automatically format your code when you commit it. The configuration may not be perfect yet, so bear with us while we get it right. - Closed bugs and merge requests: * Define GObject properties and fields as JS properties [#160, !151, Philip Chimento] * Possible refcounting bug around GtkListbox signal handlers [#24, !154, Philip Chimento] * Fix up GJS_DISABLE_JIT flag now the JIT is enabled by default in SpiderMonkey [!159, Christopher Wheeldon] * Various CI maintenance [!160, !161, !162, !169, !172, !180, !191, !193, Claudio André] * Update GJS wiki URL [!157, Seth Woodworth] * Build failure in GNOME Continuous [#104, !156, Philip Chimento] * Refactor ObjectInstance into C++ class [!158, !164, Philip Chimento] * Use Ubuntu in the coverage job [!163, Claudio André] * Remove unused files [!165, Claudio André] * Add a ARMv8 build test [!166, Claudio André] * Make CI faster [!167, Claudio André] * Add a PPC4LE build test [!168, Claudio André] * gdbus: Fix deprecated API [!170, Philip Chimento] * Change Docker images names pattern [#173, !174, Claudio André] * Assert failure on a GC_ZEAL run [#165, !173, Philip Chimento] * Do not run linters on tagged commits [!181, Claudio André] * Fail on compiler warnings [!182, Claudio André] * Save code statistics in GitLab Pages [!183, Claudio André] * Build static analysis Docker image in GitLab [!184, !185, !187, !189, Claudio André] * GNOME Shell always crashes with SIGBUS [#171, !188, Georges Basile Stavracas Neto] * Coverage badge is no longer able to show its value [#177, !186, Claudio André] * Move the Docker images creation to GitLab [!190, Claudio André] * Cut the Gordian knot of coding style [#172, !171, Philip Chimento] * Some GObect/GInterface properties broke [#182, !195, Philip Chimento] Version 1.53.3 -------------- - This release was made from an earlier state of the main branch, before a bug was introduced, while we sort out how to fix it. As a result, we don't have too many changes this round. - Closed bugs and merge requests: * Adding multiple ESLint rules for spacing [!152, Avi Zajac] * Various maintenance [!153, Philip Chimento] Version 1.53.2 -------------- - The `Template` parameter passed to `GObject.registerClass()` now accepts file:/// URIs as well as resource:/// URIs and byte arrays. - New API: `gjs_get_js_version()` returns a string identifying the version of the underlying SpiderMonkey JS engine. The interpreter executable has also gained a `--jsversion` argument which will print this string. - Several fixes for memory efficiency and performance. - Once again we welcomed contributions from a number of first-time contributors! - Closed bugs and merge requests: * Add support for file:/// uri to glade template [#108, !41, Jesus Bermudez, Philip Chimento] * Reduce memory overhead of g_object_weak_ref() [#144, !122, Carlos Garnacho, Philip Chimento] * gjs: JS_GetContextPrivate(): cjs-console killed by SIGSEGV [#148, !121, Philip Chimento] * Use compacting GC on RSS size growth [#151, !133, Carlos Garnacho] * Segfault on enumeration of GjSFileImporter properties when a searchpath entry contains a symlink [#154, !144, Ole Jørgen Brønner] * Compare linter jobs to correct base [#156, !140, Claudio André] * Various maintenance [!141, Philip Chimento] * Support interface signal handlers [!142, Tomasz Miąsko] * Remove unnecessary inline [!145, Emmanuele Bassi] * Add badges to the readme [!146, !147, Claudio André] * Fix debug logging [!148, Philip Chimento] * CI: add a GC zeal test [!149, Claudio André] Version 1.53.1 -------------- - Improvements to garbage collection performance. Read for more information: https://feaneron.com/2018/04/20/the-infamous-gnome-shell-memory-leak/ - Now, when building a class from a UI template file (using the `Template` parameter passed to `GObject.registerClass()`, for example) signals defined in the UI template file will be automatically connected. - As an experimental feature, we now offer a flatpak built with each GJS commit, including branches. You can use this to test your apps with a particular GJS branch before it is merged. Look for it in the "Artifacts" section of the CI pipeline. - Closed bugs and merge requests: * Tweener: Add min/max properties [!67, Jason Hicks] * `ARGV` encoding issues [#22, !108, Evan Welsh] * Make GC much more aggressive [#62, !50, Giovanni Campagna, Georges Basile Stavracas Neto, Philip Chimento] * Queue GC when a GObject reference is toggled down [#140, !114, !127, Georges Basile Stavracas Neto] * overrides: support Gtk template callbacks [!124, Andy Holmes] * Ensure not to miss the force_gc flag [#150, !132, Carlos Garnacho] * Create a flatpak on CI [#153, !135, Claudio André] * Readme update [!138, Claudio André] Version 1.52.3 -------------- - Closed bugs and merge requests: * Include calc.js example from Seed [!130, William Barath, Philip Chimento] * CI: Un-pin the Fedora Docker image [#141, !131, Claudio André] * Reduce overhead of wrapped objects [#142, !121, Carlos Garnacho, Philip Chimento] * Various CI changes [!134, !136, Claudio André] Version 1.52.2 -------------- - This is an unscheuled release in order to revert a commit that causes a crash on exit, with some Cairo versions. - Closed bugs and merge requests: * CI: pinned Fedora to old tag [!119, Claudio André] * heapgraph.py: adjust terminal output style [!120, Andy Holmes] * CI: small tweaks [!123, Claudio André] * Warn about compilation warnings [!125, Claudio André] * Miscellaneous commits [Philip Chimento, Jason Hicks] Version 1.52.1 -------------- - This version has more changes than would normally be expected from a stable version. The intention of 1.52.1 is to deliver a version that runs cleaner under performance tools, in time for the upcoming GNOME Shell performance hackfest. We also wanted to deliver a stable CI pipeline before branching GNOME 3.28 off of the main branch. - Claudio André's work on the CI pipeline deserves a spotlight. We now have test jobs that run linters, sanitizers, Valgrind, and more; the tests are run on up-to-date Docker images; and the reliability errors that were plaguing the test runs are solved. - In addition to System.dumpHeap(), you can now dump a heap from a running Javascript program by starting it with the environment variable GJS_DEBUG_HEAP_OUTPUT=some_name, and sending it SIGUSR1. - heapgraph.py is a tool in the repository (not installed in distributions) for analyzing and graphing heap dumps, to aid with tracking down memory leaks. - The linter CI jobs will compare your branch against the main branch of GNOME/gjs, and fail if your branch added any new linter errors. There may be false positives, and the rules configuration is not perfect. If that's the case on your merge request, you can skip the appropriate linter job by adding the text "[skip (linter)]" in your commit message: e.g., "[skip cpplint]". - We welcomed first merge requests from several new contributors for this release. - Closed bugs and merge requests: * Crash when resolving promises if exception is pending [#18, !95, Philip Chimento] * gjs_byte_array_get_proto(JSContext*): assertion failed: (((void) "gjs_" "byte_array" "_define_proto() must be called before " "gjs_" "byte_array" "_get_proto()", !v_proto.isUndefined())) [#39, !92, Philip Chimento] * Tools for examining heap graph [#116, !61, !118, Andy Holmes, Tommi Komulainen, Philip Chimento] * Run analysis tools to prepare for release [#120, !88, Philip Chimento] * Add support for passing flags to Gio.DBusProxy in makeProxyWrapper [#122, !81, Florian Müllner] * Cannot instantiate Cairo.Context [#126, !91, Philip Chimento] * GISCAN CjsPrivate-1.0.gir fails [#128, !90, Philip Chimento] * Invalid read of g_object_finalized flag [#129, !117, Philip Chimento] * Fix race condition in coverage file test [#130, !99, Philip Chimento] * Linter jobs should only fail if new lint errors were added [#133, !94, Philip Chimento] * Disable all tests that depends on X if there is no XServer [#135, !109, Claudio André] * Pick a different C++ linter [#137, !102, Philip Chimento] * Create a CI test that builds using autotools only [!74, Claudio André] * CI: enable ASAN [!89, Claudio André] * CI: disable static analysis jobs using the commit message [!93, Claudio André] * profiler: Don't assume layout of struct sigaction [!96, James Cowgill] * Valgrind [!98, Claudio André] * Robustness of CI [!103, Claudio André] * CI: make a separate job for installed tests [!106, Claudio André] * Corrected Markdown format and added links to JHBuild in setup guide for GJS [!111, Avi Zajac] * Update tweener.js -- 48 eslint errors fixed [!112, Karen Medina] * Various maintenance [!100, !104, !105, !107, !110, !113, !116, Claudio André, Philip Chimento] Version 1.52.0 -------------- - No changes from 1.51.92 except for the continuous integration configuration. - Closed bugs and merge requests: * Various CI improvements [!84, !85, !86, !87, Claudio André] Version 1.51.92 --------------- - Closed bugs and merge requests: * abort if we are called back in a non-main thread [#75, !72, Philip Chimento] * 3.27.91 build failure on debian/Ubuntu [#122, !73, Tim Lunn] * Analyze project code quality with Code Climate inside CI [#10, !77, Claudio André] * Various CI improvements [!75, !76, !79, !80, !82, !83, Claudio André] Version 1.51.91 --------------- - Promises now resolve with a higher priority, so asynchronous code should be faster. [Jason Hicks] - Closed bugs and merge requests: * New build 'warnings' [#117, !62, !63, Claudio André, Philip Chimento] * Various CI maintenance [!64, !65, !66, Claudio André, Philip Chimento] * profiler: Don't include alloca.h when disabled [!69, Ting-Wei Lan] * GNOME crash with fatal error "Finalizing proxy for an object that's scheduled to be unrooted: Gio.Subprocess" in gjs [#26, !70, Philip Chimento] Version 1.51.90 --------------- - Note that all the old Bugzilla bug reports have been migrated over to GitLab. - GJS now, once again, includes a profiler, which outputs files that can be read with sysprof. To use it, simply run your program with the environment variable GJS_ENABLE_PROFILER=1 set. If your program is a JS script that is executed with the interpreter, you can also pass --profile to the interpreter. See "gjs --help" for more info. - New API: For programs that want more control over when to start and stop profiling, there is new API for GjsContext. When you create your GjsContext there are two construct-only properties available, "profiler-enabled" and "profiler-sigusr2". If you set profiler-sigusr2 to TRUE, then the profiler can be started and stopped while the program is running by sending SIGUSR2 to the process. You can also use gjs_context_get_profiler(), gjs_profiler_set_filename(), gjs_profiler_start(), and gjs_profiler_stop() for more explicit control. - New API: GObject.signal_connect(), GObject.signal_disconnect(), and GObject.signal_emit_by_name() are now available in case a GObject-derived class has conflicting connect(), disconnect() or emit() methods. - Closed bugs and merge requests: * Handle 0-valued GType gracefully [#11, !10, Philip Chimento] * Profiler [#31, !37, Christian Hergert, Philip Chimento] * Various maintenance [!40, !59, Philip Chimento, Giovanni Campagna] * Rename GObject.Object.connect/disconnect? [#65, !47, Giovanni Campagna] * Better debugging output for uncatchable exceptions [!39, Simon McVittie] * Update Docker images and various CI maintenance [!54, !56, !57, !58, Claudio André] * Install GJS suppression file for Valgrind [#2, !55, Philip Chimento] Version 1.50.4 -------------- - Closed bugs and merge requests: * Gnome Shell crash with places-status extension when you plug an USB device [#33, !38, Philip Chimento] Version 1.50.3 -------------- - GJS will now log a warning when a GObject is accessed in Javascript code after the underlying object has been freed in C. (This used to work most of the time, but crash unpredictably.) We now prevent this situation which, is usually caused by a memory management bug in the underlying C library. - Closed bugs and merge requests: * Add checks for GObjects that have been finalized [#21, #23, !25, !28, !33, Marco Trevisan] * Test "Cairo context has methods when created from a C function" fails [#27, !35, Valentín Barros] * Various fixes from the main branch for rare crashes [Philip Chimento] Version 1.51.4 -------------- - We welcomed code and documentation from several new contributors in this release! - GJS will now log a warning when a GObject is accessed in Javascript code after the underlying object has been freed in C. (This used to work most of the time, but crash unpredictably.) We now prevent this situation which, is usually caused by a memory management bug in the underlying C library. - APIs exposed through GObject Introspection that use the GdkAtom type are now usable from Javascript. Previously these did not work. On the Javascript side, a GdkAtom translates to a string, so there is no Gdk.Atom type that you can access. The special atom GDK_NONE translates to null in Javascript, and there is also no Gdk.NONE constant. - The GitLab CI tasks have continued to gradually become more and more sophisticated. - Closed bugs and merge requests: * Add checks for GObjects that have been finalized [#21, #23, !22, !27, Marco Trevisan] * Fail static analyzer if new warnings are found [!24, Claudio André] * Run code coverage on GitLab [!20, Claudio André] * Amend gtk.js and add gtk-application.js with suggestion [!32, Andy Holmes] * Improve GdkAtom support that is blocking clipboard APIs [#14, !29, makepost] * Test "Cairo context has methods when created from a C function" fails [#27, !35, Valentín Barros] * Various CI improvements [#6, !26, !34, Claudio André] * Various maintenance [!23, !36, Philip Chimento] Version 1.51.3 -------------- - This release was made from an earlier state of the main branch, before a breaking change was merged, while we decide whether to revert that change or not. - Closed bugs and merge requests: * CI improvements on GitLab [!14, !15, !19, Claudio André] * Fix CI build on Ubuntu [#16, !18, !21, Claudio André, Philip Chimento] Version 1.51.2 -------------- - Version 1.51.1 was skipped. - The home of GJS is now at GNOME's GitLab instance: https://gitlab.gnome.org/GNOME/gjs From now on we'll be taking GitLab merge requests instead of Bugzilla patches. If you want to report a bug, please report it at GitLab. - Closed bugs and merge requests: * Allow throwing GErrors from JS virtual functions [#682701, Giovanni Campagna] * [RFC] bootstrap system [#777724, Jasper St. Pierre, Philip Chimento] * Fix code coverage (and refactor it to take advantage of mozjs52 features) [#788166, !1, !3, Philip Chimento] * Various maintenance [!2, Philip Chimento] * Get GitLab CI working and various improvements [#6, !7, !9, !11, !13, Claudio André] * Add build status badge to README [!8, Claudio André] * Use Docker images for CI [!12, Claudio André] - Some changes in progress to improve garbage collection when signals are disconnected. See bug #679688 for more information [Giovanni Campagna] Version 1.50.2 -------------- - Closed bugs and merge requests: * tweener: Fix a couple of warnings [!5, Florian Müllner] * legacy: Allow ES6 classes to inherit from abstract Lang.Class class [!6, Florian Müllner] - Minor bugfixes [Philip Chimento] Version 1.50.1 -------------- - As a debugging aid, gjs_dumpstack() now works even during garbage collection. - Code coverage tools did not work so well in the last few 1.49 releases. The worst problems are now fixed, although even more improvements will be released in the next unstable version. Fixes include: * Specifying prefixes for code coverage files now works again * Code coverage now works on lines inside ES6 class definitions * The detection of which lines are executable has been improved a bit Version 1.50.0 -------------- - Closed bugs: * Relicense coverage.cpp and coverage.h to the same license as the rest of GJS [#787263, Philip Chimento; thanks to Dominique Leuenberger for pointing out the mistake] Version 1.49.92 --------------- - It's now possible to build GJS with sanitizers (ASan and UBSan) enabled; add "--enable-asan" and "--enable-ubsan" to your configure flags. This has already caught some memory leaks. - There's also a "make check-valgrind" target which will run GJS's test suite under Valgrind to catch memory leaks and threading races. - Many of the crashes in GNOME 3.24 were caused by GJS's closure invalidation code which had to change from the known-working state in 1.46 because of changes to SpiderMonkey's garbage collector. This code has been refactored to be less complicated, which will hopefully improve stability and debuggability. - Closed bugs: * Clean up the idle closure invalidation mess [#786668, Philip Chimento] * Add ASan and UBSan to GJS [#783220, Claudio André] * Run analysis tools on GJS to prepare for release [#786995, Philip Chimento] * Fix testLegacyGObject importing the GTK overrides [#787113, Philip Chimento] - Docs tweak [Philip Chimento] 1.49.91 ------- - Deprecation: The private "__name__" property on Lang.Class instances is now discouraged. Code should not have been using this anyway, but if it did then it should use the "name" property on the class (this.__name__ should become this.constructor.name), which is compatible with ES6 classes. - Closed bugs: * Use ES6 classes [#785652, Philip Chimento] * A few fixes for stack traces and error reporting [#786183, Philip Chimento] * /proc/self/stat is read for every frame if GC was not needed [#786017, Benjamin Berg] - Build fix [Philip Chimento] Version 1.49.90 --------------- - New API: GObject.registerClass(), intended for use with ES6 classes. When defining a GObject class using ES6 syntax, you must call GObject.registerClass() on the class object, with an optional metadata object as the first argument. (The metadata object works exactly like the meta properties from Lang.Class, except that Name and Extends are not present.) Old: var MyClass = new Lang.Class({ Name: 'MyClass', Extends: GObject.Object, Signals: { 'event': {} }, _init(props={}) { this._private = []; this.parent(props); }, }); New: var MyClass = GObject.registerClass({ Signals: { 'event': {} }, }, class MyClass extends GObject.Object { _init(props={}) { this._private = []; super._init(props); } }); It is forward compatible with the following syntax requiring decorators and class fields, which are not in the JS standard yet: @GObject.registerClass class MyClass extends GObject.Object { static [GObject.signals] = { 'event': {} } _init(props={}) { this._private = []; super._init(props); } } One limitation is that GObject ES6 classes can't have constructor() methods, they must do any setup in an _init() method. This may be able to be fixed in the future. - Closed bugs: * Misc 1.49 and mozjs52 enhancements [#785040, Philip Chimento] * Switch to native promises [#784713, Philip Chimento] * Can't call exports using top-level variable toString [#781623, Philip Chimento] * Properties no longer recognized when shadowed by a method [#785091, Philip Chimento, Rico Tzschichholz] * Patch: backport of changes required for use with mozjs-55 [#785424, Luke Jones] Version 1.48.6 -------------- - Closed bugs: * GJS crash in needsPostBarrier, possible access from wrong thread [#783935, Philip Chimento] (again) Version 1.49.4 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 52, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 38. GJS now uses the latest ESR in its engine and the plan is to upgrade again when SpiderMonkey 59 is released in March 2018, pending maintainer availability. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New language features + ES6 classes + Async functions and await operator + Reflect - built-in object with methods for interceptable operations * New syntax + Exponentiation operator: `**` + Variable-length Unicode code point escapes: `"\u{1f369}"` + Destructured default arguments: `function f([x, y]=[1, 2], {z: z}={z: 3})` + Destructured rest parameters: `function f(...[a, b, c])` + `new.target` allows a constructor access to the original constructor that was invoked + Unicode (u) flag for regular expressions, and corresponding RegExp.unicode property + Trailing comma in function parameter lists now allowed * New APIs + New Array, String, and TypedArray method: includes() + TypedArray sort(), toLocaleString(), and toString() methods, to correspond with regular arrays + New Object.getOwnPropertyDescriptors() and Object.values() methods + New Proxy traps: getPrototypeOf() and setPrototypeOf() + [Symbol.toPrimitive] property specifying how to convert an object to a primitive value + [Symbol.species] property allowing to override the default constructor for objects + [Symbol.match], [Symbol.replace], [Symbol.search], and [Symbol.split] properties allowing to customize matching behaviour in RegExp subclasses + [Symbol.hasInstance] property allowing to customize the behaviour of the instanceof operator for objects + [Symbol.toStringTag] property allowing to customize the message printed by Object.toString() without overriding it + [Symbol.isConcatSpreadable] property allowing to control the behaviour of an array subclass in an argument list to Array.concat() + [Symbol.unscopables] property allowing to control which object properties are lifted into the scope of a with statement + New Intl.getCanonicalLocales() method + Date.toString() and RegExp.toString() generic methods + Typed arrays can now be constructed from any iterable object + Array.toLocaleString() gained optional locales and options arguments, to correspond with other toLocaleString() methods * New behaviour + The "arguments" object is now iterable + Date.prototype, WeakMap.prototype, and WeakSet.prototype are now ordinary objects, not instances + Full ES6-compliant implementation of let keyword + RegExp.sticky ('y' flag) behaviour is ES6 standard, it used to be subject to a long-standing bug in Firefox + RegExp constructor with RegExp first argument and flags no longer throws an exception (`new RegExp(/ab+c/, 'i')` works now) + Generators are no longer constructible, as per ES6 (`function* f {}` followed by `new f` will not work) + It is now required to construct ArrayBuffer, TypedArray, Map, Set, and WeakMap with the new operator + Block-level functions (e.g. `{ function foo() {} }`) are now allowed in strict mode; they are scoped to their block + The options.timeZone argument to Date.toLocaleDateString(), Date.toLocaleString(), Date.toLocaleTimeString(), and the constructor of Intl.DateTimeFormat now understands IANA time zone names (such as "America/Vancouver") * Backwards-incompatible changes + Non-standard "let expressions" and "let blocks" (e.g., `let (x = 5) { use(x) }`) are not supported any longer + Non-standard flags argument to String.match(), String.replace(), and String.search() (e.g. `str.replace('foo', 'bar', 'g')`) is now ignored + Non-standard WeakSet.clear() method has been removed + Variables declared with let and const are now 'global lexical bindings', as per the ES6 standard, meaning that they will not be exported in modules. We are maintaining the old behaviour for the time being as a compatibility workaround, but please change "let" or "const" to "var" inside your module file. A warning will remind you of this. For more information, read: https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/ * Experimental features (may change in future versions) + String.padEnd(), String.padStart() methods (proposed in ES2017) + Intl.DateTimeFormat.formatToParts() method (proposed in ES2017) + Object.entries() method (proposed in ES2017) + Atomics, SharedArrayBuffer, and WebAssembly are disabled by default, but can be enabled if you compile mozjs yourself - Closed bugs: * Prepare for SpiderMonkey 45 and 52 [#781429, Philip Chimento] * Add a static analysis tool as a make target [#783214, Claudio André] * Fix the build with debug logs enabled [#784469, Tomas Popela] * Switch to SpiderMonkey 52 [#784196, Philip Chimento, Chun-wei Fan] * Test suite fails when run with JIT enabled [#616193, Philip Chimento] Version 1.48.5 -------------- - Closed bugs: * GJS crash in needsPostBarrier, possible access from wrong thread [#783935, Philip Chimento] - Fix format string, caught by static analysis [Claudio André] - Fixes for regression in 1.48.4 [Philip Chimento] Version 1.49.3 -------------- - This will be the last release using SpiderMonkey 38. - Fixes in preparation for SpiderMonkey 52 [Philip Chimento] - Use the Centricular fork of libffi to build on Windows [Chun-wei Fan] - Closed bugs: * [RFC] Use a C++ auto pointer instead of g_autofree [#777597, Chun-wei Fan, Daniel Boles, Philip Chimento] * Build failure in GNOME Continuous [#783031, Chun-wei Fan] Version 1.48.4 -------------- - Closed bugs: * gnome-shell 3.24.1 crash on wayland [#781799, Philip Chimento]; thanks to everyone who contributed clues Version 1.49.2 -------------- - New feature: When building an app with the Package module, using the Meson build system, you can now run the app with "ninja run" and all the paths will be set up correctly. - New feature: Gio.ListStore is now iterable. - New API: Package.requireSymbol(), a companion for the already existing Package.require(), that not only checks for a GIR library but also for a symbol defined in that library. - New API: Package.checkSymbol(), similar to Package.requireSymbol() but does not exit if the symbol was not found. Use this to support older versions of a GIR library with fallback functionality. - New API: System.dumpHeap(), for debugging only. Prints the state of the JS engine's heap to standard output. Takes an optional filename parameter which will dump to a file instead if given. - Closed bugs: * Make gjs build on Windows/Visual Studio [#775868, Chun-wei Fan] * Bring back fancy error reporter in cjs-console [#781882, Philip Chimento] * Add Meson running from source support to package.js [#781882, Patrick Griffis] * package: Fix initSubmodule() when running from source in Meson [#782065, Patrick Griffis] * package: Set GSETTINGS_SCHEMA_DIR when ran from source [#782069, Patrick Griffis] * Add imports.gi.has() to check for symbol availability [#779593, Florian Müllner] * overrides: Implement Gio.ListStore[Symbol.iterator] [#782310, Patrick Griffis] * tweener: Explicitly check for undefined properties [#781219, Debarshi Ray, Philip Chimento] * Add a way to dump the heap [#780106, Juan Pablo Ugarte] - Fixes in preparation for SpiderMonkey 52 [Philip Chimento] - Misc fixes [Philip Chimento] Version 1.48.3 -------------- - Closed bugs: * arg: don't crash when asked to convert a null strv to an array [#775679, Cosimo Cecchi, Sam Spilsbury] * gjs 1.48.0: does not compile on macOS with clang [#780350, Tom Schoonjans, Philip Chimento] * Modernize shell scripts [#781806, Claudio André] Version 1.49.1 -------------- - Closed bugs: * test GObject Class failure [#693676, Stef Walter] * Enable incremental GCs [#724797, Giovanni Campagna] * Don't silently accept extra arguments to C functions [#680215, Jasper St. Pierre, Philip Chimento] * Special case GValues in signals and properties [#688128, Giovanni Campagna, Philip Chimento] * [cairo] Instantiate wrappers properly [#614413, Philip Chimento, Johan Dahlin] * Warn if we're importing an unversioned namespace [#689654, Colin Walters, Philip Chimento] - Fixes in preparation for SpiderMonkey 45 [Philip Chimento] - Misc fixes [Philip Chimento, Chun-wei Fan, Dan Winship] Version 1.48.2 -------------- - Closed bugs: * Intermittent crash in gnome-shell, probably in weak pointer updating code [#781194, Georges Basile Stavracas Neto] * Add contributor's guide [#781297, Philip Chimento] - Misc fixes [Debarshi Ray, Philip Chimento] Version 1.48.1 -------------- - Closed bugs: * gjs crashed with SIGSEGV in gjs_object_from_g_object [#779918, Philip Chimento] - Misc bug fixes [Florian Müllner, Philip Chimento, Emmanuele Bassi] Version 1.48.0 -------------- - Closed bugs: * Memory leak in object_instance_resolve() [#780171, Philip Chimento]; thanks to Luke Jones and Hussam Al-Tayeb Version 1.47.92 --------------- - Closed bugs: * gjs 1.47.91 configure fails with Fedora's mozjs38 [#779412, Philip Chimento] * tests: Don't fail when Gtk+-4.0 is available [#779594, Florian Müllner] * gjs 1.47.91 test failures on non-amd64 [#779399, Philip Chimento] * gjs_eval_thread should always be set [#779693, Philip Chimento] * System.exit() should exit even across main loop iterations [#779692, Philip Chimento] * Fix a typo in testCommandLine.sh [#779772, Claudio André] * arg: Fix accidental fallthrough [#779838, Florian Müllner] * jsUnit: Explicitly check if tempTop.parent is defined [#779871, Iain Lane] - Misc bug fixes [Philip Chimento] Version 1.47.91 --------------- - Closed bugs: * overrides/Gio: Provide an empty array on error, rather than null [#677513, Jasper St. Pierre, Philip Chimento] * WithSignals parameter for Lang.Class [#664897, Philip Chimento] * add API to better support asynchronous code [#608450, Philip Chimento] * 1.47.90 tests are failing [#778780, Philip Chimento] * boxed: Plug a memory leak [#779036, Florian Müllner] * Don't crash when marshalling an unsafe integer from introspection [#778705, Philip Chimento] * Lang.Class should include symbol properties [#778718, Philip Chimento] * Console output of arrays should be UTF-8 aware [#778729, Philip Chimento] * Various fixes for 1.47.91 [#779293, Philip Chimento] - Progress towards a Visual Studio build of GJS on Windows [Chun-wei Fan] - Misc bug fixes [Chun-wei Fan, Philip Chimento] Version 1.47.90 --------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 38, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 31. Our plans are to continue upgrading to subsequent ESRs as maintainer availability allows. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + Shorthand syntax for method definitions: { foo() { return 5; } } + Shorthand syntax for object literals: let b = 42; let o = {b}; o.b === 42 + Computed property names for the above, as well as in getter and setter expressions and destructuring assignment: { ['b' + 'ar']() { return 6; } } + Spread operator in destructuring assignment: let [a, ...b] = [1, 2, 3]; + Template literals: `Hello, ${name}` with optional tags: tag`string` * New APIs + Symbol, a new fundamental type + WeakSet, a Set which does not prevent its members from being garbage-collected + [Symbol.iterator] properties for Array, Map, Set, String, TypedArray, and the arguments object + New Array and TypedArray functionality: Array.copyWithin(), Array.from() + New return() method for generators + New Number.isSafeInteger() method + New Object.assign() method which can replace Lang.copyProperties() in many cases + New Object.getOwnPropertySymbols() method + New RegExp flags, global, ignoreCase, multiline, sticky properties that give access to the flags that the regular expression was created with + String.raw, a tag for template strings similar to Python's r"" + New TypedArray methods for correspondence with Array: entries(), every(), fill(), filter(), find(), findIndex(), forEach(), indexOf(), join(), keys(), lastIndexOf(), map(), of(), reduce(), reduceRight(), reverse(), slice(), some(), values() * New behaviour + Temporal dead zone: print(x); let x = 5; no longer allowed + Full ES6-compliant implementation of const keyword + The Set, Map, and WeakMap constructors can now take null as their argument + The WeakMap constructor can now take an iterable as its argument + The Function.name and Function.length properties are configurable + When subclassing Map, WeakMap, and Set or using the constructors on generic objects, they will look for custom set() and add() methods. + RegExp.source and RegExp.toString() now deal with empty regexes, and escape their output. + Non-object arguments to Object.preventExtensions() now do not throw an exception, simply return the original object * Backwards-incompatible changes + It is now a syntax error to declare the same variable twice with "let" or "const" in the same scope. Existing code may need to be fixed, but the fix is trivial. + SpiderMonkey is now extra vocal about warning when you access an undefined property, and this causes some false positives. You can turn this warning off by setting GJS_DISABLE_EXTRA_WARNINGS=1. If it is overly annoying, let me know and I will consider making it disabled by default in a future release. + When enumerating the importer object (i.e., "for (let i in imports) {...}") you will now get the names of any built-in modules that have previously been imported. (But don't do this, anyway.) - Closed bugs: * SpiderMonkey 38 prep [#776966, Philip Chimento] * Misc fixes [#777205, Philip Chimento] * missing class name in error message [#642506, Philip Chimento] * Add continuous integration to GJS [#776549, Claudio André] * Switch to SpiderMonkey 38 [#777962, Philip Chimento] - Progress towards a build of GJS on Windows [Chun-wei Fan] - Progress towards increasing test coverage [Claudio André] - Misc bug fixes [Philip Chimento] Version 1.47.4 -------------- - New JavaScript feature: ES6 Promises. This release includes Lie [1], a small, self-contained Promise implementation, which is imported automatically to form the Promise global object [2]. In the future, Promises will be built into the SpiderMonkey engine and Lie will be removed, but any code using Promises will continue to work as before. [1] https://github.com/calvinmetcalf/lie [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise - News for GJS embedders such as gnome-shell: * New API: The GjsCoverage type and its methods are now exposed. Use this if you are embedding GJS and need to output code coverage statistics. - Closed bugs: * Add GjsCoverage to gjs-1.0 public API [#775776, Philip Chimento] * Should use uint32_t instead of u_int32_t in coverage.cpp [#776193, Shawn Walker, Alan Coopersmith] * Port tests to use an embedded copy of Jasmine [#775444, Philip Chimento] * support fields in GObject [#563391, Havoc Pennington, Philip Chimento] * Javascript errors in property getters and setter not always reported [#730101, Matt Watson, Philip Chimento] * Exception swallowed while importing Gom [#737607, Philip Chimento] * log a warning if addSignalMethods() replaces existing methods [#619710, Joe Shaw, Philip Chimento] * Provide a useful toString for importer and module objects [#636283, Jasper St. Pierre, Philip Chimento] * Fails to marshal out arrays [#697020, Paolo Borelli] * coverage: Don't warn about executing odd lines by default anymore [#751146, Sam Spilsbury, Philip Chimento] * coverage: Crash in EnterBaseline on SpiderMonkey when Ion is enabled during coverage mode. [#742852, Sam Spilsbury, Philip Chimento] * installed tests cannot load libregress.so [#776938, Philip Chimento] * Crash with subclassed fundamental with no introspection [#760057, Lionel Landwerlin] - Misc bug fixes [Philip Chimento, Claudio André] Version 1.47.3 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 31, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 24. Our plans are to continue upgrading to subsequent ESRs as maintainer availability allows. Here are the highlights of the new JavaScript features. For more information, look them up on MDN or devdocs.io. * New syntax + Spread operator in function calls: someFunction(arg1, arg2, ...iterableObj) + Generator functions: yield, function*, yield* + Binary and octal numeric literals: 0b10011100, 0o377 + Function arguments without defaults can now come after those with defaults: function f(x=1, y) {} * New standard library module + Intl - Locale-sensitive formatting and string comparison * New APIs + Iterator protocol - any object implementing certain methods is an "iterator" + New Array functionality: fill(), find(), findIndex(), of() + New String functionality for working with Unicode: codePointAt(), fromCodePoint(), normalize() + New Array methods for correspondence with Object: entries(), keys() + ES6 Generator methods to replace the old Firefox-specific generator API: next(), throw() + forEach() methods for Map and Set, for correspondence with Array + A bunch of new Math functions: acosh(), asinh(), atanh(), cbrt(), clz32(), cosh(), expm1(), fround(), hypot(), log10(), log1p(), log2(), sign(), sinh(), tanh(), trunc() + Some constants to tell information about float support on the platform: Number.EPSILON, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER + New Number.parseInt() and Number.parseFloat() which are now preferred over those in the global namespace + New Object.setPrototypeOf() which now is preferred over setting obj.prototype.__proto__ + New locales and options extra arguments to all toLocaleString() and related methods + Misc new functionality: ArrayBuffer.isView(), Proxy.handler.isExtensible, Proxy.revocable() * New behaviour + -0 and +0 are now considered equal as Map keys and Set values + On typed arrays, numerical indexed properties ignore the prototype object: Int8Array.prototype[20] = 'foo'; (new Int8Array(32))[20] == 0 * New non-standard Mozilla extensions + Array comprehensions + Generator comprehensions; both were originally proposed for ES6 but removed - Backwards-incompatible change: we have changed the way certain JavaScript values are marshalled into GObject introspection 32 or 64-bit signed integer values, to match the ECMA standard. Here is the relevant section of the standard: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toint32 Notable differences between the old and new behaviour are: * Floating-point numbers ending in 0.5 are rounded differently when marshalled into 32 or 64-bit signed integers. Previously they were rounded to floor(x + 0.5), now they are rounded to signum(x) * floor(abs(x)) as per the ECMA standard: http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toint32 Note that previously, GJS rounded according to the standard when converting to *unsigned* integers! * Objects whose number value is NaN (e.g, arrays of strings), would previously fail to convert, throwing a TypeError. Now they convert to 0 when marshalled into 32 or 64-bit signed integers. Note that the new behaviour is the behaviour you got all along when using pure JavaScript, without any GObject introspection: gjs> let a = new Int32Array(2); gjs> a[0] = 10.5; 10.5 gjs> a[1] = ['this', 'is', 'fine']; this,is,fine gjs> a[0] 10 gjs> a[1] 0 - News for GJS embedders such as gnome-shell: * New API: gjs_error_quark() is now exposed, and the error domain GJS_ERROR and codes GJS_ERROR_FAILED and GJS_ERROR_SYSTEM_EXIT. * Backwards-incompatible change: Calling System.exit() from JS code will now not abort the program immediately, but instead will return immediately from gjs_context_eval() so that you can unref your GjsContext before internal resources are released on exit. If gjs_context_eval() or gjs_context_eval_file() returns an error with code GJS_ERROR_SYSTEM_EXIT, it means that the JS code called System.exit(). The exit code will be found in the 'exit_status_p' out parameter to gjs_context_eval() or gjs_context_eval_file(). If you receive this error, you should do any cleanup needed and exit your program with the given exit code. - Closed bugs: * spidermonkey 31 prep [Philip Chimento, Tim Lunn, #742249] * Please use dbus-run-session to run dbus related tests [Philip Chimento, #771745] * don't abort gdb'd tests [Philip Chimento, Havoc Pennington, #605972] * Use Automake test suite runner [Philip Chimento, #775205] * please add doc/ directory to make dist tar file [Philip Chimento, #595439] * support new mozjs 31.5 [Philip Chimento, #751252] * SEGFAULT in js::NewObjectWithClassProtoCommon when instantiating a dynamic type defined in JS [Philip Chimento, Juan Pablo Ugarte, #770244] - Misc bug fixes [Philip Chimento, Alexander Larsson] Version 1.47.0 -------------- - New API: GLib.log_structured() is a convenience wrapper for the C function g_log_variant(), allowing you to do structured logging without creating GVariants by hand. For example: GLib.log_structured('test', GLib.LogLevelFlags.LEVEL_WARNING, { 'MESSAGE': 'Just a test', 'A_FIELD': 'A value', }); - Backwards-incompatible change: we have changed the way cjs-console interprets command-line arguments. Previously there was a heuristic to try to decide whether "--help" given on the command line was meant for GJS itself or for a script being launched. This did not work in some cases, for example: $ gjs -c 'if (ARGV.indexOf("--help") == -1) throw "ugh"' --help would print the GJS help. Now, all arguments meant for GJS itself must come _before_ the name of the script to execute or any script given with a "-c" argument. Any arguments _after_ the filename or script are passed on to the script. This is the way that Python handles its command line arguments as well. If you previously relied on any -I arguments after your script being added to the search path, then you should either reorder those arguments, use GJS_PATH, or handle -I inside your script, adding paths to imports.searchPath manually. In order to ease the pain of migration, GJS will continue to take those arguments into account for the time being, while still passing them on to the script. A warning will be logged if you are using the deprecated behaviour. - News for GJS embedders such as gnome-shell: * Removed API: The gjs-internals-1.0 API is now discontinued. Since gnome-shell was the only known user of this API, its pkg-config file has been removed completely and gnome-shell has been patched not to use it. - Closed bugs: * make check fails with --enable-debug / -DDEBUG spidermonkey [Philip Chimento, #573335] * Modernize autotools configuration [Philip Chimento, #772027] * overrides/GLib: Add log_structured() - wrapper for g_log_variant() [Jonh Wendell, #771598] * Remove gjs-internals-1.0 API [Philip Chimento, #772386] * Add support for boolean, gunichar, and 64-bit int arrays [Philip Chimento, #772790] * add a --version flag [Philip Chimento, #772033] * Switch to AX_COMPILER_FLAGS and compile warning-free [Philip Chimento, #773297] * Reinstate importer test [Philip Chimento, #773335] Version 1.46.0 -------------- - Be future proof against Format fixes in SpiderMonkey [Giovanni Campagna, #770111] Version 1.45.4 -------------- - Release out args before freeing caller-allocated structs [Florian Müllner, #768413] - Marshal variable array-typed signal arguments [Philip Chimento, Florian Müllner, #761659] - Marshal all structs in out arrays correctly [Philip Chimento, #761658] - Call setlocale() before processing arguments [Ting-Wei Lan, #760424] - Build fixes and improvements [Philip Chimento, #737702, #761072] [Hashem Nasarat, #761366] [Carlos Garnacho, #765905] [Simon McVittie, #767368] [Emmanuele Bassi] [Michael Catanzaro] [Matt Watson] Version 1.45.3 -------------- - Support external construction of gjs-defined GObjects [Florian Müllner, #681254] - Add new format.printf() API [Colin Walters, #689664] - Add new API to get the name of a repository [Jasper St. Pierre, #685413] - Add C to JS support for arrays of flat structures [Giovanni Campagna, #704842] - Add API to specify CSS node name [Florian Müllner, #758349] - Return value of default signal handler for "on_signal_name" methods [Philip Chimento, #729288] - Fix multiple emissions of onOverwrite in Tweener [Tommi Komulainen, #597927] - Misc bug fixes [Philip Chimento, #727370] [Owen Taylor, #623330] [Juan RP, #667908] [Ben Iofel, #757763] Version 1.44.0 -------------- - Add Lang.Interface and GObject.Interface [Philip Chimento, Roberto Goizueta, #751343, #752984] - Support callbacks with (transfer full) return types [Debarshi Ray, #750286] - Add binding for setlocale() [Philip Chimento, #753072] - Improve support to generate code coverage reports [Sam Spilsbury, #743009, #743007, #742362, #742535, #742797, #742466, #751732] - Report errors from JS property getters/setters [Matt Watson, #730101] - Fix crash when garbage collection triggers while inside an init function [Sam Spilsbury, #742517] - Port to CallReceiver/CallArgs [Tim Lunn, #742249] - Misc bug fixes [Ting-Wei Lan, #736979, #753072] [Iain Lane, #750688] [Jasper St. Pierre] [Philip Chimento] [Colin Walters] Version 1.43.3 -------------- - GTypeClass and GTypeInterface methods, such as g_object_class_list_properties(), are now available [#700347] - Added full automatic support for GTK widget templates [#700347, #737661] [Jonas Danielsson, #739739] - Added control of JS Date caches to system module [#739790] - Misc bug fixes and memory leak fixes [Owen Taylor, #738122] [Philip Chimento, #740696, #737701] Version 1.42.0 -------------- - Fix a regression caused by PPC fixes in 1.41.91 Version 1.41.91 --------------- - Added the ability to disable JS language warnings [Lionel Landwerlin, #734569] - Fixed crashes in PPC (and probably other arches) due to invalid callback signatures [Michel Danzer, #729554] - Fixed regressions with dbus 1.8.6 [Tim Lunn, #735358] - Readded file system paths to the default module search, to allow custom GI overrides for third party libraries [Jasper St. Pierre] Version 1.41.4 -------------- - Fixed memory management of GObject methods that unref their instance [#729545] - Added a package module implementing the https://wiki.gnome.org/Projects/Gjs/Package application conventions [#690136] - Misc bug fixes Version 1.41.3 -------------- - Fixed GObject and Gtk overrides [Mattias Bengtsson, #727781] [#727394] - Fixed crashes caused by reentrancy during finalization [#725024] - Added a wrapper type for cairo regions [Jasper St. Pierre, #682303] - Several cleanups to GC [#725024] - Thread-safe structures are now finalized in the background, for greater responsiveness [#725024] [#730030] - A full GC is now scheduled if after executing a piece of JS we see that the RSS has grown by over 150% [Cosimo Cecchi, #725099] [#728048] - ParamSpecs now support methods and static methods implemented by glib and exposed by gobject-introspection, in addition to the manually bound fields [#725282] - Protototypes no longer include static properties or constructors [#725282] - Misc cleanups and bugfixes [Mattias Bengtsson, #727786] [#725282] [Lionel Landwerlin, #728004] [Lionel Landwerlin, #727824] Version 1.40.0 -------------- - No changes Version 1.39.90 --------------- - Implemented fundamental object support [Lionel Landwerlin, #621716, #725061] - Fixed GIRepositoryGType prototype [#724925] - Moved GObject.prototype.disconnect() to a JS implementation [#698283] - Added support for enumeration methods [#725143] - Added pseudo-classes for fundamental types [#722554] - Build fixes [Ting-Wei Lan, #724853] Version 0.3 (03-Jul-2009) ------------------------- Changes: - DBus support At a high level there are three pieces. First, the "gjs-dbus" library is a C support library for DBus. Second, the modules/dbus*.[ch] implement parts of the JavaScript API in C. Third, the modules/dbus.js file fills out the rest of the JavaScript API and implementation. - Support simple fields for boxed types - Support "copy construction" of boxed types - Support simple structures not registered as boxed - Allow access to nested structures - Allow direct assignment to nested structure fields - Allow enum and flag structure fields - Allow creating boxed wrapper without copy - Support for non-default constructor (i.e. constructors like GdkPixbuf.Pixbuf.new_from_file(file)) - Add a Lang.bind function which binds the meaning of 'this' - Add an interactive console cjs-console - Allow code in directory modules (i.e. the code should reside in __init__.js files) - Fix handling of enum/flags return values - Handle non-gobject-registered flags - Add Tweener.registerSpecialProperty to tweener module - Add profiler for javascript code - Add gjs_context_get_all and gjs_dumpstack - useful to invoke from a debugger such as gdb - Support GHashTable - GHashTable return values/out parameters - Support GHashTable 'in' parameters - Convert JSON-style object to a GHashTable - Add support for UNIX shebang (i.e. #!/usr/bin/cjs-console) - Support new introspection short/ushort type tags - Support GI_TYPE_TAG_FILENAME - Improve support for machine-dependent integer types and arrays of integers - Fix several memory leaks Contributors: Colin Walters, C. Scott Ananian, Dan Winship, David Zeuthen, Havoc Pennington, Johan Bilien, Johan Dahlin, Lucas Rocha, Marco Pesenti Gritti, Marina Zhurakhinskaya, Owen Taylor, Tommi Komulainen Bugs fixed: Bug 560506 - linking problem Bug 560670 - Turn on compilation warnings Bug 560808 - Simple field supported for boxed types Bug 561514 - memory leak when skipping deprecated methods Bug 561516 - skipping deprecated methods shouldn't signal error case Bug 561849 - Alternate way of connecting signals. Bug 562892 - valgrind errors on get_obj_key Bug 564424 - Add an interactive console Bug 564664 - Expose JS_SetDebugErrorHook Bug 566185 - Allow code in directory modules Bug 567675 - Handle non-gobject-registered flags Bug 569178 - Add readline support to the console module Bug 570775 - array of parameters leaked on each new GObject Bug 570964 - Race when shutting down a context, r=hp Bug 580948 - Add DBus support Bug 584560 - Add support for UNIX shebang Bug 584850 - Clean up some __BIG definitions. Bug 584858 - Fix errors (and leftover debugging) from dbus merge. Bug 584858 - Move gjsdbus to gjs-dbus to match installed location. Bug 585386 - Add a flush() method to DBus bus object. Bug 585460 - Fix segfault when a non-existing node is introspected. Bug 586665 - Fix seg fault when attempting to free callback type. Bug 586760 - Support converting JavaScript doubles to DBus int64/uint64. Bug 561203 - Fix priv_from_js_with_typecheck() for dynamic types Bug 561573 - Add non-default constructors and upcoming static methods to "class" Bug 561585 - allow using self-built spidermonkey Bug 561664 - Closure support is broken Bug 561686 - Don't free prov->gboxed for "has_parent" Boxed Bug 561812 - Support time_t Bug 562575 - Set correct GC parameters and max memory usage Bug 565029 - Style guide - change recommendation for inheritance Bug 567078 - Don't keep an extra reference to closures Bug 569374 - Logging exceptions from tweener callbacks could be better Bug 572113 - add profiler Bug 572121 - Invalid read of size 1 Bug 572130 - memory leaks Bug 572258 - profiler hooks should be installed only when needed Bug 580865 - Call JS_SetLocaleCallbacks() Bug 580947 - Validate UTF-8 strings in gjs_string_to_utf8() Bug 580957 - Change the way we trigger a warning in testDebugger Bug 581277 - Call JS_SetScriptStackQuota on our contexts Bug 581384 - Propagate exceptions from load context Bug 581385 - Return false when gjs_g_arg_release_internal fails Bug 581389 - Fix arg.c to use 'interface' instead of 'symbol' Bug 582686 - Don't g_error() when failing to release an argument Bug 582704 - Don't depend on hash table order in test cases Bug 582707 - Fix problems with memory management for in parameters Bug 584849 - Kill warnings (uninitialized variables, strict aliasing) Bug 560808 - Structure support Version 0.2 (12-Nov-2008) ------------------------- Changes: - Compatible with gobject-introspection 0.6.0 - New modules: mainloop, signals, tweener - Support passing string arrays to gobject-introspection methods - Added jsUnit based unit testing framework - Improved error handling and error reporting Contributors: Colin Walters, Havoc Pennington, Johan Bilien, Johan Dahlin, Lucas Rocha, Owen Taylor, Tommi Komulainen Bugs fixed: Bug 557398 – Allow requiring namespace version Bug 557448 – Enum and Flags members should be uppercase Bug 557451 – Add search paths from environment variables Bug 557451 – Add search paths from environment variables Bug 557466 – Module name mangling considered harmful Bug 557579 – Remove use of GJS_USE_UNINSTALLED_FILES in favor of GI_TYPELIB_PATH Bug 557772 - gjs_invoke_c_function should work with union and boxed as well Bug 558114 – assertRaises should print return value Bug 558115 – Add test for basic types Bug 558148 – 'const char*' in arguments are leaked Bug 558227 – Memory leak if invoked function returns an error Bug 558741 – Mutual imports cause great confusion Bug 558882 – Bad error if you omit 'new' Bug 559075 – enumerating importer should skip hidden files Bug 559079 – generate code coverage report Bug 559194 - Fix wrong length buffer in get_obj_key() Bug 559612 - Ignore deprecated methods definitions. Bug 560244 - support for strv parameters Version 0.1 ----------- Initial version! Ha! cjs-128.1/README.MSVC.md0000664000175000017500000002142515116312211013253 0ustar fabiofabioInstructions for building GJS on Visual Studio or clang-cl ========================================================== Building the GJS on Windows is now supported using Visual Studio versions 2019 16.5.x or later with or without clang-cl in both 32-bit and 64-bit (x64) flavors, via Meson. It should be noted that a recent-enough Windows SDK from Microsoft is still required if using clang-cl, as we will still use items from the Windows SDK. Recent official binary installers of CLang (which contains clang-cl) from the LLVM website are known to work to build SpiderMonkey 128 and GJS. You will need the following items to build GJS using Visual Studio or clang-cl (they can be built with Visual Studio 2015 or later, unless otherwise noted): - SpiderMonkey 128.x (mozjs-128). This must be built with clang-cl as the Visual Studio compiler is no longer supported for building this. Please see the below section carefully on this... - GObject-Introspection (G-I) 1.66.x or later - GLib 2.66.x or later, (which includes GIO, GObject, and the associated tools) - Cairo including Cairo-GObject support (Optional) - GTK+-4.x or later (Optional) - and anything that the above items depend on. Note again that SpiderMonkey must be built using Visual Studio with clang-cl, and the rest should preferably be built with Visual Studio or clang-cl as well. The Visual Studio version used for building the other dependencies should preferably be the same across the board, or, if using Visual Studio 2015 or later, Visual Studio 2015 through 2022. Please also be aware that the Rust MSVC toolchains that correspond to the platform you are building for must also be present to build SpiderMonkey. Please refer to the Rust website on how to install the Rust compilers and toolchains for MSVC. This applies to clang-cl builds as well. Be aware that it is often hard to find a suitable source release for SpiderMonkey nowadays, so it may be helpful to look in ftp://ftp.gnome.org/pub/gnome/teams/releng/tarballs-needing-help/mozjs/ for the suitable release series of SpiderMonkey that corresponds to the GJS version that is being built, as GJS depends on ESR (Extended Service Release, a.k.a Long-term support) releases of SpiderMonkey. You may also be able to obtain the SpiderMonkey 128.x sources via the FireFox (ESR) or Thunderbird 128.x sources, in $(srcroot)/js. Please do note that the build must be done carefully, in addition to the official instructions that are posted on the Mozilla website: https://firefox-source-docs.mozilla.org/js/build.html You will need to create a .mozconfig file that will describe your build options for the build in the root directory of the Firefox/ThunderBird 128.x sources. A sample content of the .mozconfig file can be added as follows: ``` ac_add_options --enable-application=js mk_add_options MOZ_MAKE_FLAGS=-j12 ac_add_options --target=x86_64-pc-mingw32 ac_add_options --host=x86_64-pc-mingw32 ac_add_options --disable-tests ac_add_options --enable-optimize ac_add_options --disable-debug ac_add_options --disable-jemalloc ac_add_options --prefix=c:/software.b/mozjs128.bin ``` An explanation of the lines above: * `ac_add_options --enable-application=js`: This line is absolutely required, to build SpiderMonkey standalone * `mk_add_options MOZ_MAKE_FLAGS=-j12`: MOZ_MAKE_FLAGS=-jX means X number of parallel processes for the build * `ac_add_options --target=x86_64-pc-mingw32`: Target architecture, replace `x86_64` with `aarch64` for ARM64 builds, and with `i686` for 32-bit x86 builds. * `ac_add_options --host=x86_64-pc-mingw32`: Use this as-is, unless building on a 32-bit compiler (replace `x86_64` with `i686`; not recommended) * `ac_add_options --disable-tests`: Save some build time * `ac_add_options --enable-optimize`: Use for release builds of SpiderMonkey. Use `--disable-optimize` instead if building with `--enable-debug` * `ac_add_options --enable-debug`: Include debugging functions, for debug builds. Use `--disable-debug` instead if building with `--enable-optimize` * `ac_add_options --disable-jemalloc`: This is absolutely needed, otherwise GJS will not build and run correctly * `ac_add_options --prefix=c:/software.b/mozjs128.bin`: Some installation path, change as needed If your GJS build crashes upon launch, use Dependency Walker to ensure that mozjs-128.dll does not depend on mozglue.dll! If it does, or if GJS fails to link with missing arena_malloc() and friends symbols, you have built SpiderMoney incorrectly and will need to rebuild SpiderMonkey (with the build options as noted above) and retry the build. Please also check that `--enable-optimize` is *not* used with `--enable-debug`. You should explicitly enable one and disable the other, as `--enable-debug` will make the resulting build depend on the debug CRT, and mixing between the release and debug CRT in the same DLL is often a sign of trouble when using with GJS, meaning that you will need to rebuild SpiderMonkey with the appropriate options set in your `.mozconfig` file. Please note that for SpiderMonkey builds, PDB files are generated even if `--disable-debug` is used. You will need to check that `js-config.h` has the correct entries that correspond to your SpiderMonkey build, especially the following items: * `JS_64BIT`, `JS_PUNBOX64`: Should be defined for 64-bit builds, not 32-bit builds * `JS_NUNBOX32`: Should be defined for 32-bit builds, not 64-bit builds * `JS_DEBUG`, `JS_GC_ZEAL`: Should only be defined if `--enable-debug` is used Note in particular that a mozglue.dll should *not* be in $(builddir)/dist/bin, although there will be a mozglue.lib somewhere in the build tree (which, you can safely delete after building SpiderMonkey). The --host=... and --target=... are absolutely required for all builds, as per the Mozilla's SpiderMonkey build instructions, as Rust is being involved here. Run `./mach build` to carry out the build, and then `./mach build install` to copy the completed build to the directory specified by `ac_add_options --prefix=xxx`. If `./mach build install` does not work for you for some reason, the DLLs you need and js.exe can be found in $(buildroot)/dist/bin (you need *all* the DLLs, make sure that there is no mozglue.dll, otherwise you will need to redo your build as noted above), and the required headers are found in $(buildroot)/dist/include. Note that for PDB files and .lib files, you will need to search for them in $(buildroot), where the PDB file names match the filenames for the DLLs/EXEs in $(buildroot)/dist/bin, and you will need to look for the following .lib files: -mozjs-128.lib -js_static.lib (optional) You may want to put the .lib's and DLLs/EXEs into $(PREFIX)\lib and $(PREFIX)\bin respectively, and put the headers into $(PREFIX)\include\mozjs-128 for convenience. You will need to place the generated mozjs-128.pc pkg-config file into $(PREFIX)\lib\pkgconfig and ensure that pkg-config can find it by setting PKG_CONFIG_PATH. Ensure that the 'includedir' and 'libdir' in there is correct so that the mozjs-128.pc can be used correctly in Visual Studio/clang-cl builds, and replace the `-isystem` with `-I` if building GJS with Visual Studio. You will also need to ensure that the existing GObject-Introspection installation (if used) is on the same drive where the GJS sources are (and therefore where the GJS build is being carried out). To carry out the build ====================== If using clang-cl, you will need to set *both* the environment variables CC and CXX to: 'clang-cl [--target=]' (without the quotes); please see https://clang.llvm.org/docs/CrossCompilation.html on how the target triplet can be defined, which is used if using the cross-compilation capabilities of CLang. In this case, you need to ensure that 'clang-cl.exe' and 'lld-link.exe' (i.e. your LLVM bindir) are present in your PATH. You need to install Python 3.6.x or later, as well as the pkg-config tool, Meson (via pip) and Ninja. Perform a build by doing the following, in an appropriate Visual Studio command prompt in an empty build directory: ``` meson --buildtype=... --prefix= -Dskip_dbus_tests=true -Dprofiler=disabled ``` (Note that -Dskip_dbus_tests=true is required for MSVC/clang-cl builds; please see the Meson documentation for the values accepted by buildtype) You may want to view the build options after the configuration succeeds by using 'meson configure'. You may need to set the envvar: `SETUPTOOLS_USE_DISTUTILS=stdlib` for the introspection step to proceed successfully. A fix for this is being investigated. When the configuration succeeds, run: ninja You may choose to install the build results using 'ninja install' or running the 'install' project when the build succeeds. cjs-128.1/README.md0000664000175000017500000000766615116312211012517 0ustar fabiofabio[![Build Status](https://gitlab.gnome.org/GNOME/gjs/badges/master/pipeline.svg)](https://gitlab.gnome.org/GNOME/gjs/pipelines) [![Coverage report](https://gitlab.gnome.org/GNOME/gjs/badges/master/coverage.svg)](https://gnome.pages.gitlab.gnome.org/gjs/) [![Contributors](https://img.shields.io/github/contributors/GNOME/gjs.svg)](https://gitlab.gnome.org/GNOME/gjs/-/graphs/HEAD) [![Last commit](https://img.shields.io/github/last-commit/GNOME/gjs.svg)](https://gitlab.gnome.org/GNOME/gjs/commits/HEAD) [![Search hit](https://img.shields.io/github/search/GNOME/gjs/goto.svg?label=github%20hits)](https://github.com/search?utf8=%E2%9C%93&q=gjs&type=) [![License](https://img.shields.io/badge/License-LGPL%20v2%2B-blue.svg)](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/COPYING) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/COPYING) GNOME JavaScript ============================= GJS is a JavaScript runtime built on [Firefox's SpiderMonkey JavaScript engine](https://spidermonkey.dev/) and the [GNOME platform libraries](https://developer.gnome.org/). Use the GNOME platform libraries in your JavaScript programs. GJS powers GNOME Shell, Maps, Characters, Sound Recorder and many other apps. If you would like to learn more or get started with GJS, head over to the [documentation](./doc/Home.md). ## Installation Available as part of your GNOME distribution by default. In most package managers the package will be called `gjs`. ## Usage GJS includes a command-line interpreter, usually installed in `/usr/bin/gjs`. Type `gjs` to start it and test out your JavaScript statements interactively. Hit Ctrl+D to exit. `gjs filename.js` runs a whole program. `gjs -d filename.js` does that and starts a debugger as well. There are also facilities for generating code coverage reports. Type `gjs --help` for more information. `-d` only available in gjs >= 1.53.90 ## Contributing For instructions on how to get started contributing to GJS, please read the contributing guide, . ## History GJS probably started in August 2008 with [this blog post][havocp] and [this experimental code][gscript]. GJS in its current form was first developed in October 2008 at a company called litl, for their [litl webbook] product. It was soon adopted as the basis of [GNOME Shell]'s UI code and extensions system and debuted as a fundamental component of GNOME 3.0. In February 2013 at the GNOME Developer Experience Hackfest GJS was declared the ['first among equals'][treitter] of languages for GNOME application development. That proved controversial for many, and was later abandoned. At the time of writing (2018) GJS is used in many systems including Endless OS's [framework for offline content][eos-knowledge-lib] and, as a forked version, [Cinnamon]. ## Reading material ### Documentation * [Get started](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/CONTRIBUTING.md) * [Get started - Internship](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/Internship-Getting-Started.md) * [API documentation](https://gjs-docs.gnome.org/) ### JavaScript & SpiderMonkey * https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples ### GNOME Contribution * https://wiki.gnome.org/GitLab * https://wiki.gnome.org/Newcomers/ ## License Dual licensed under LGPL 2.0+ and MIT. ## Thanks ## The form of this README was inspired by [Nadia Odunayo][hospitable] on the Greater Than Code podcast. [havocp]: https://blog.ometer.com/2008/08/25/embeddable-languages/ [gscript]: https://gitlab.gnome.org/Archive/gscript/tree/HEAD/gscript [litl webbook]: https://en.wikipedia.org/wiki/Litl [GNOME Shell]: https://wiki.gnome.org/Projects/GnomeShell [treitter]: https://treitter.livejournal.com/14871.html [eos-knowledge-lib]: http://endlessm.github.io/eos-knowledge-lib/ [Cinnamon]: https://en.wikipedia.org/wiki/Cinnamon_(software) [hospitable]: https://www.greaterthancode.com/code-hospitality cjs-128.1/build/0000775000175000017500000000000015116312211012320 5ustar fabiofabiocjs-128.1/build/choose-tests-locale.sh0000664000175000017500000000164315116312211016535 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Endless Mobile, Inc. if ! which locale > /dev/null; then exit 1 fi locales=$(locale -a | xargs -n1) case $locales in # Prefer C.UTF-8 although it is only available with newer libc *C.UTF-8*) tests_locale=C.UTF-8 ;; # C.utf8 has also been observed in the wild *C.utf8*) tests_locale=C.utf8 ;; # Most systems will probably have this *en_US.UTF-8*) tests_locale=en_US.UTF-8 ;; *en_US.utf8*) tests_locale=en_US.utf8 ;; # If not, fall back to any English UTF-8 locale or any UTF-8 locale at all *en_*.UTF-8*) tests_locale=$(echo $locales | grep -m1 en_.\*\\.UTF-8) ;; *en_*.utf8*) tests_locale=$(echo $locales | grep -m1 en_.\*\\.utf8) ;; *.UTF-8*) tests_locale=$(echo $locales | grep -m1 \\.UTF-8) ;; *.utf8*) tests_locale=$(echo $locales | grep -m1 \\.utf8) ;; *) tests_locale=C ;; esac echo $tests_locale cjs-128.1/build/flatpak/0000775000175000017500000000000015116312211013742 5ustar fabiofabiocjs-128.1/build/flatpak/org.gnome.GjsConsole.json0000664000175000017500000000205015116312211020572 0ustar fabiofabio{ "comment": "----------------------------------------------------------------", "comment": "This manifest is intended for a quick start with GJS using", "comment": "GNOME Builder.", "comment": "If you are planning to make contributions over a longer period", "comment": "then consider following the setup guide in doc/Hacking.md.", "comment": "----------------------------------------------------------------", "app-id": "org.gnome.GjsConsole", "runtime": "org.gnome.Platform", "runtime-version": "master", "sdk": "org.gnome.Sdk", "command": "cjs-console", "finish-args": [ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", "--share=network", "--filesystem=host", "--filesystem=home", "--socket=session-bus", "--socket=system-bus", "--socket=pulseaudio" ], "modules": [ { "name": "gjs", "buildsystem": "meson", "sources": [ { "type": "git", "url": "https://gitlab.gnome.org/GNOME/gjs.git" } ] } ] } cjs-128.1/build/maintainer-upload-release.sh0000664000175000017500000000413415116312211017705 0ustar fabiofabio#!/bin/bash # SPDX-License-Identifier: GPL-2.0-or-later # SPDX-FileCopyrightText: Will Thompson # Automate the release process for stable branches. # https://blogs.gnome.org/wjjt/2022/06/07/release-semi-automation # gnome-initial-setup/build-aux/maintainer-upload-release set -ex : "${MESON_BUILD_ROOT:?}" : "${MESON_SOURCE_ROOT:?}" project_name="${1:?project name is required}" project_version="${2:?project version is required}" tarball_basename="${project_name}-${project_version}.tar.xz" tarball_path="${MESON_BUILD_ROOT}/meson-dist/${tarball_basename}" [[ -e "$tarball_path" ]] # ninja dist must have been successful # Don't forget to write release notes head -n1 "${MESON_SOURCE_ROOT}/NEWS" | grep "$project_version" case $project_version in 1.7[12].*) gnome_series=42 ;; 1.7[34].*) gnome_series=43 ;; 1.7[56].*) gnome_series=44 ;; 1.7[78].*) gnome_series=45 ;; 1.79.* | 1.80.*) gnome_series=46 ;; 1.8[12].*) gnome_series=47 ;; 1.8[34].*) gnome_series=48 ;; *) echo "Version $project_version not handled by this script" exit 1 ;; esac expected_branch=gnome-${gnome_series} pushd "$MESON_SOURCE_ROOT" branch=$(git rev-parse --abbrev-ref HEAD) if [[ "$branch" != "master" ]] && [[ "$branch" != "$expected_branch" ]]; then echo "Project version $project_version does not match branch $branch" >&2 exit 1 fi if git show-ref --tags "$project_version" --quiet; then # Tag already exists; verify that it points to HEAD [ "$(git rev-parse "$project_version"^{})" = "$(git rev-parse HEAD)" ] else if type git-evtag &>/dev/null; then # Can't specify tag message on command line # https://github.com/cgwalters/git-evtag/issues/9 EDITOR=true git evtag sign "$project_version" else git tag -s "$project_version" -m "Version $project_version" fi fi git push --atomic origin "$branch" "$project_version" popd scp "$tarball_path" "master.gnome.org:" # shellcheck disable=SC2029 ssh -t "master.gnome.org" ftpadmin install --unattended "$tarball_basename" cjs-128.1/build/symlink-cjs.py0000664000175000017500000000151015116312211015132 0ustar fabiofabio#!/usr/bin/env python3 # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Chun-wei Fan import os import shutil import sys import tempfile assert(len(sys.argv) == 2) installed_bin_dir = os.path.join(os.environ.get('MESON_INSTALL_DESTDIR_PREFIX'), sys.argv[1]) if os.name == 'nt': # Using symlinks on Windows often require administrative privileges, # which is not what we want. Instead, copy cjs-console.exe. shutil.copyfile('cjs-console.exe', os.path.join(installed_bin_dir, 'cjs.exe')) else: try: temp_link = tempfile.mktemp(dir=installed_bin_dir) os.symlink('cjs-console', temp_link) os.replace(temp_link, os.path.join(installed_bin_dir, 'cjs')) finally: if os.path.islink(temp_link): os.remove(temp_link) cjs-128.1/cjs/0000775000175000017500000000000015116312211012000 5ustar fabiofabiocjs-128.1/cjs/atoms.cpp0000664000175000017500000000314015116312211013625 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento // SPDX-FileCopyrightText: 2018 Marco Trevisan #define GJS_USE_ATOM_FOREACH #include #include #include #include #include #include #include #include "cjs/atoms.h" bool GjsAtom::init(JSContext* cx, const char* str) { JSString* s = JS_AtomizeAndPinString(cx, str); if (!s) return false; m_jsid = JS::Heap{JS::PropertyKey::fromPinnedString(s)}; return true; } bool GjsSymbolAtom::init(JSContext* cx, const char* str) { JS::RootedString descr(cx, JS_AtomizeAndPinString(cx, str)); if (!descr) return false; JS::Symbol* symbol = JS::NewSymbol(cx, descr); if (!symbol) return false; m_jsid = JS::Heap{JS::PropertyKey::Symbol(symbol)}; return true; } /* Requires a current realm. This can GC, so it needs to be done after the * tracing has been set up. */ bool GjsAtoms::init_atoms(JSContext* cx) { #define INITIALIZE_ATOM(identifier, str) \ if (!identifier.init(cx, str)) \ return false; FOR_EACH_ATOM(INITIALIZE_ATOM) FOR_EACH_SYMBOL_ATOM(INITIALIZE_ATOM) return true; } void GjsAtoms::trace(JSTracer* trc) { #define TRACE_ATOM(identifier, str) \ JS::TraceEdge(trc, identifier.id(), "Atom " str); FOR_EACH_ATOM(TRACE_ATOM) FOR_EACH_SYMBOL_ATOM(TRACE_ATOM) #undef TRACE_ATOM } cjs-128.1/cjs/atoms.h0000664000175000017500000000777715116312211013316 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento // SPDX-FileCopyrightText: 2018 Marco Trevisan #ifndef GJS_ATOMS_H_ #define GJS_ATOMS_H_ #include #include #include #include #include "cjs/macros.h" class JSTracer; // clang-format off #define FOR_EACH_ATOM(macro) \ macro(cause, "cause") \ macro(code, "code") \ macro(column_number, "columnNumber") \ macro(connect_after, "connect_after") \ macro(constructor, "constructor") \ macro(debuggee, "debuggee") \ macro(detail, "detail") \ macro(emit, "emit") \ macro(file, "__file__") \ macro(file_name, "fileName") \ macro(func, "func") \ macro(gc_bytes, "gcBytes") \ macro(gi, "gi") \ macro(gio, "Gio") \ macro(glib, "GLib") \ macro(gobject, "GObject") \ macro(gtype, "$gtype") \ macro(height, "height") \ macro(imports, "imports") \ macro(importSync, "importSync") \ macro(init, "_init") \ macro(instance_init, "_instance_init") \ macro(interact, "interact") \ macro(internal, "internal") \ macro(length, "length") \ macro(line_number, "lineNumber") \ macro(malloc_bytes, "mallocBytes") \ macro(message, "message") \ macro(module_init, "__init__") \ macro(module_name, "__moduleName__") \ macro(module_path, "__modulePath__") \ macro(name, "name") \ macro(new_, "new") \ macro(new_internal, "_new_internal") \ macro(override, "override") \ macro(overrides, "overrides") \ macro(param_spec, "ParamSpec") \ macro(parent_module, "__parentModule__") \ macro(program_args, "programArgs") \ macro(program_invocation_name, "programInvocationName") \ macro(program_path, "programPath") \ macro(prototype, "prototype") \ macro(search_path, "searchPath") \ macro(signal_id, "signalId") \ macro(stack, "stack") \ macro(to_string, "toString") \ macro(uri, "uri") \ macro(url, "url") \ macro(value_of, "valueOf") \ macro(version, "version") \ macro(versions, "versions") \ macro(width, "width") \ macro(window, "window") \ macro(x, "x") \ macro(y, "y") \ macro(zone, "zone") #define FOR_EACH_SYMBOL_ATOM(macro) \ macro(gobject_prototype, "__GObject__prototype") \ macro(hook_up_vfunc, "__GObject__hook_up_vfunc") \ macro(private_ns_marker, "__gjsPrivateNS") \ macro(signal_find, "__GObject__signal_find") \ macro(signals_block, "__GObject__signals_block") \ macro(signals_disconnect, "__GObject__signals_disconnect") \ macro(signals_unblock, "__GObject__signals_unblock") // clang-format on struct GjsAtom { GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx, const char* str); /* It's OK to return JS::HandleId here, to avoid an extra root, with the * caveat that you should not use this value after the GjsContext has been * destroyed.*/ [[nodiscard]] JS::HandleId operator()() const { return JS::HandleId::fromMarkedLocation(&m_jsid.get()); } [[nodiscard]] JS::Heap* id() { return &m_jsid; } protected: JS::Heap m_jsid; }; struct GjsSymbolAtom : GjsAtom { GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx, const char* str); }; class GjsAtoms { public: GjsAtoms(void) {} ~GjsAtoms(void) {} // prevents giant destructor from being inlined GJS_JSAPI_RETURN_CONVENTION bool init_atoms(JSContext* cx); void trace(JSTracer* trc); #define DECLARE_ATOM_MEMBER(identifier, str) GjsAtom identifier; #define DECLARE_SYMBOL_ATOM_MEMBER(identifier, str) GjsSymbolAtom identifier; FOR_EACH_ATOM(DECLARE_ATOM_MEMBER) FOR_EACH_SYMBOL_ATOM(DECLARE_SYMBOL_ATOM_MEMBER) #undef DECLARE_ATOM_MEMBER #undef DECLARE_SYMBOL_ATOM_MEMBER }; #if !defined(GJS_USE_ATOM_FOREACH) && !defined(USE_UNITY_BUILD) # undef FOR_EACH_ATOM # undef FOR_EACH_SYMBOL_ATOM #endif #endif // GJS_ATOMS_H_ cjs-128.1/cjs/byteArray.cpp0000664000175000017500000001615515116312211014456 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC #include #include #include // for copy_n #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include #include // for JS_NewPlainObject #include "gi/boxed.h" #include "cjs/atoms.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/text-encoding.h" GJS_JSAPI_RETURN_CONVENTION static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars encoding; JS::RootedObject byte_array(cx); if (!gjs_parse_call_args(cx, "toString", args, "o|s", "byteArray", &byte_array, "encoding", &encoding)) return false; const char* actual_encoding = encoding ? encoding.get() : "utf-8"; JS::RootedString str( cx, gjs_decode_from_uint8array(cx, byte_array, actual_encoding, GjsStringTermination::ZERO_TERMINATED, true)); if (!str) return false; args.rval().setString(str); return true; } /* Workaround to keep existing code compatible. This function is tacked onto * any Uint8Array instances created in situations where previously a ByteArray * would have been created. It logs a compatibility warning. */ GJS_JSAPI_RETURN_CONVENTION static bool instance_to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, this_obj); JS::UniqueChars encoding; _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId::ByteArrayInstanceToString); if (!gjs_parse_call_args(cx, "toString", args, "|s", "encoding", &encoding)) return false; const char* actual_encoding = encoding ? encoding.get() : "utf-8"; JS::RootedString str( cx, gjs_decode_from_uint8array(cx, this_obj, actual_encoding, GjsStringTermination::ZERO_TERMINATED, true)); if (!str) return false; args.rval().setString(str); return true; } GJS_JSAPI_RETURN_CONVENTION static bool define_legacy_tostring(JSContext* cx, JS::HandleObject array) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefineFunctionById(cx, array, atoms.to_string(), instance_to_string_func, 1, 0); } /* fromString() function implementation */ GJS_JSAPI_RETURN_CONVENTION static bool from_string_func(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedString str(cx); JS::UniqueChars encoding; if (!gjs_parse_call_args(cx, "fromString", args, "S|s", "string", &str, "encoding", &encoding)) return false; const char* actual_encoding = encoding ? encoding.get() : "utf-8"; JS::RootedObject uint8array( cx, gjs_encode_to_uint8array(cx, str, actual_encoding, GjsStringTermination::ZERO_TERMINATED)); if (!uint8array || !define_legacy_tostring(cx, uint8array)) return false; args.rval().setObject(*uint8array); return true; } GJS_JSAPI_RETURN_CONVENTION static bool from_gbytes_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); JS::RootedObject bytes_obj(context); GBytes *gbytes; if (!gjs_parse_call_args(context, "fromGBytes", argv, "o", "bytes", &bytes_obj)) return false; if (!BoxedBase::typecheck(context, bytes_obj, nullptr, G_TYPE_BYTES)) return false; gbytes = BoxedBase::to_c_ptr(context, bytes_obj); if (!gbytes) return false; size_t len; const void* data = g_bytes_get_data(gbytes, &len); if (len == 0) { JS::RootedObject empty_array(context, JS_NewUint8Array(context, 0)); if (!empty_array || !define_legacy_tostring(context, empty_array)) return false; argv.rval().setObject(*empty_array); return true; } JS::RootedObject array_buffer{context, JS::NewArrayBuffer(context, len)}; if (!array_buffer) return false; // Copy the data into the ArrayBuffer so that the copy is aligned, and // because the GBytes data pointer may point into immutable memory. { JS::AutoCheckCannotGC nogc; bool unused; uint8_t* storage = JS::GetArrayBufferData(array_buffer, &unused, nogc); std::copy_n(static_cast(data), len, storage); } JS::RootedObject obj( context, JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1)); if (!obj || !define_legacy_tostring(context, obj)) return false; argv.rval().setObject(*obj); return true; } JSObject* gjs_byte_array_from_data_copy(JSContext* cx, size_t nbytes, void* data) { JS::RootedObject array_buffer(cx); // a null data pointer takes precedence over whatever `nbytes` says if (data) { array_buffer = JS::NewArrayBuffer(cx, nbytes); JS::AutoCheckCannotGC nogc{}; bool unused; uint8_t* storage = JS::GetArrayBufferData(array_buffer, &unused, nogc); std::copy_n(static_cast(data), nbytes, storage); } else { array_buffer = JS::NewArrayBuffer(cx, 0); } if (!array_buffer) return nullptr; JS::RootedObject array(cx, JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_DefineFunctionById(cx, array, atoms.to_string(), instance_to_string_func, 1, 0)) return nullptr; return array; } JSObject* gjs_byte_array_from_byte_array(JSContext* cx, GByteArray* array) { return gjs_byte_array_from_data_copy(cx, array->len, array->data); } GBytes* gjs_byte_array_get_bytes(JSObject* obj) { bool is_shared_memory; size_t len; uint8_t* data; js::GetUint8ArrayLengthAndData(obj, &len, &is_shared_memory, &data); return g_bytes_new(data, len); } GByteArray* gjs_byte_array_get_byte_array(JSObject* obj) { return g_bytes_unref_to_array(gjs_byte_array_get_bytes(obj)); } static JSFunctionSpec gjs_byte_array_module_funcs[] = { JS_FN("fromString", from_string_func, 2, 0), JS_FN("fromGBytes", from_gbytes_func, 1, 0), JS_FN("toString", to_string_func, 2, 0), JS_FS_END}; bool gjs_define_byte_array_stuff(JSContext *cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); return JS_DefineFunctions(cx, module, gjs_byte_array_module_funcs); } cjs-128.1/cjs/byteArray.h0000664000175000017500000000172515116312211014120 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC #ifndef GJS_BYTEARRAY_H_ #define GJS_BYTEARRAY_H_ #include #include // for size_t #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_byte_array_stuff(JSContext *context, JS::MutableHandleObject module); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_byte_array_from_data_copy(JSContext* cx, size_t nbytes, void* data); GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_byte_array_from_byte_array (JSContext *context, GByteArray *array); [[nodiscard]] GByteArray* gjs_byte_array_get_byte_array(JSObject* obj); [[nodiscard]] GBytes* gjs_byte_array_get_bytes(JSObject* obj); #endif // GJS_BYTEARRAY_H_ cjs-128.1/cjs/cjs.stp.in0000664000175000017500000000143115116312211013713 0ustar fabiofabio/* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2010 Red Hat, Inc. */ probe cjs.object_wrapper_new = process("@EXPANDED_LIBDIR@/libcjs-gi.so.0.0.0").mark("object__wrapper__new") { wrapper_address = $arg1; gobject_address = $arg2; gi_namespace = user_string($arg3); gi_name = user_string($arg4); probestr = sprintf("cjs.object_wrapper_new(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } probe cjs.object_wrapper_finalize = process("@EXPANDED_LIBDIR@/libcjs-gi.so.0.0.0").mark("object__wrapper__finalize") { wrapper_address = $arg1; gobject_address = $arg2; gi_namespace = user_string($arg3); gi_name = user_string($arg4); probestr = sprintf("cjs.object_wrapper_finalize(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } cjs-128.1/cjs/cjs_pch.hh0000664000175000017500000000746015116312211013741 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Canonical Ltd. // SPDX-FileContributor: Authored by: Marco Trevisan #include "config.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 #ifdef G_OS_UNIX #include #endif #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 #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_READLINE_READLINE_H #include #include #endif #ifndef _WIN32 #include #endif #include #include #include #include #include #include #include #include #ifdef ENABLE_PROFILER #include #include #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include # include # include #endif cjs-128.1/cjs/console.cpp0000664000175000017500000003514615116312211014157 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include // for PACKAGE_STRING #include // for setlocale, LC_ALL #include #include // for EXIT_SUCCESS / EXIT_FAILURE #include // for strcmp, strlen #ifdef HAVE_UNISTD_H # include // for close #elif defined (_WIN32) # include #endif #include #include #include #include #include static GjsAutoStrv include_path; static GjsAutoStrv coverage_prefixes; static GjsAutoChar coverage_output_path; static GjsAutoChar profile_output_path; static GjsAutoChar command; static gboolean print_version = false; static gboolean print_js_version = false; static gboolean debugging = false; static gboolean exec_as_module = false; static bool enable_profiler = false; static gboolean parse_profile_arg(const char *, const char *, void *, GError **); using GjsAutoGOptionContext = GjsAutoPointer; // clang-format off static GOptionEntry entries[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, "Print GJS version and exit" }, { "jsversion", 0, 0, G_OPTION_ARG_NONE, &print_js_version, "Print version of the JS engine and exit" }, { "command", 'c', 0, G_OPTION_ARG_STRING, command.out(), "Program passed in as a string", "COMMAND" }, { "coverage-prefix", 'C', 0, G_OPTION_ARG_STRING_ARRAY, coverage_prefixes.out(), "Add the prefix PREFIX to the list of files to generate coverage info for", "PREFIX" }, { "coverage-output", 0, 0, G_OPTION_ARG_STRING, coverage_output_path.out(), "Write coverage output to a directory DIR. This option is mandatory when using --coverage-prefix", "DIR", }, { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, include_path.out(), "Add the directory DIR to the list of directories to search for js files.", "DIR" }, { "module", 'm', 0, G_OPTION_ARG_NONE, &exec_as_module, "Execute the file as a module." }, { "profile", 0, G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, reinterpret_cast(&parse_profile_arg), "Enable the profiler and write output to FILE (default: gjs-$PID.syscap)", "FILE" }, { "debugger", 'd', 0, G_OPTION_ARG_NONE, &debugging, "Start in debug mode" }, { NULL } }; // clang-format on [[nodiscard]] static GjsAutoStrv strndupv(int n, char* const* strv) { #if GLIB_CHECK_VERSION(2, 68, 0) GjsAutoPointer builder( g_strv_builder_new()); for (int i = 0; i < n; ++i) g_strv_builder_add(builder, strv[i]); return g_strv_builder_end(builder); #else int ix; if (n == 0) return NULL; char **retval = g_new(char *, n + 1); for (ix = 0; ix < n; ix++) retval[ix] = g_strdup(strv[ix]); retval[n] = NULL; return retval; #endif // GLIB_CHECK_VERSION(2, 68, 0) } [[nodiscard]] static GjsAutoStrv strcatv(char** strv1, char** strv2) { if (strv1 == NULL && strv2 == NULL) return NULL; if (strv1 == NULL) return g_strdupv(strv2); if (strv2 == NULL) return g_strdupv(strv1); #if GLIB_CHECK_VERSION(2, 70, 0) GjsAutoPointer builder( g_strv_builder_new()); g_strv_builder_addv(builder, const_cast(strv1)); g_strv_builder_addv(builder, const_cast(strv2)); return g_strv_builder_end(builder); #else unsigned len1 = g_strv_length(strv1); unsigned len2 = g_strv_length(strv2); char **retval = g_new(char *, len1 + len2 + 1); unsigned ix; for (ix = 0; ix < len1; ix++) retval[ix] = g_strdup(strv1[ix]); for (ix = 0; ix < len2; ix++) retval[len1 + ix] = g_strdup(strv2[ix]); retval[len1 + len2] = NULL; return retval; #endif // GLIB_CHECK_VERSION(2, 70, 0) } static gboolean parse_profile_arg(const char* option_name [[maybe_unused]], const char* value, void*, GError**) { enable_profiler = true; profile_output_path = GjsAutoChar(value, GjsAutoTakeOwnership()); return true; } static void check_script_args_for_stray_gjs_args(int argc, char * const *argv) { GjsAutoError error; GjsAutoStrv new_coverage_prefixes; GjsAutoChar new_coverage_output_path; GjsAutoStrv new_include_paths; // Don't add new entries here. This is only for arguments that were // previously accepted after the script name on the command line, for // backwards compatibility. // clang-format off GOptionEntry script_check_entries[] = { { "coverage-prefix", 'C', 0, G_OPTION_ARG_STRING_ARRAY, new_coverage_prefixes.out() }, { "coverage-output", 0, 0, G_OPTION_ARG_STRING, new_coverage_output_path.out() }, { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, new_include_paths.out() }, { NULL } }; // clang-format on GjsAutoStrv argv_copy = g_new(char*, argc + 2); int ix; argv_copy[0] = g_strdup("dummy"); /* Fake argv[0] for GOptionContext */ for (ix = 0; ix < argc; ix++) argv_copy[ix + 1] = g_strdup(argv[ix]); argv_copy[argc + 1] = NULL; GjsAutoGOptionContext script_options = g_option_context_new(NULL); g_option_context_set_ignore_unknown_options(script_options, true); g_option_context_set_help_enabled(script_options, false); g_option_context_add_main_entries(script_options, script_check_entries, NULL); if (!g_option_context_parse_strv(script_options, argv_copy.out(), &error)) { g_warning("Scanning script arguments failed: %s", error->message); return; } if (new_coverage_prefixes) { g_warning("You used the --coverage-prefix option after the script on " "the GJS command line. Support for this will be removed in a " "future version. Place the option before the script or use " "the GJS_COVERAGE_PREFIXES environment variable."); coverage_prefixes = strcatv(coverage_prefixes, new_coverage_prefixes); } if (new_include_paths) { g_warning("You used the --include-path option after the script on the " "GJS command line. Support for this will be removed in a " "future version. Place the option before the script or use " "the GJS_PATH environment variable."); include_path = strcatv(include_path, new_include_paths); } if (new_coverage_output_path) { g_warning( "You used the --coverage-output option after the script on " "the GJS command line. Support for this will be removed in a " "future version. Place the option before the script or use " "the GJS_COVERAGE_OUTPUT environment variable."); coverage_output_path = new_coverage_output_path; } } int define_argv_and_eval_script(GjsContext* js_context, int argc, char* const* argv, const char* script, size_t len, const char* filename) { gjs_context_set_argv(js_context, argc, const_cast(argv)); GjsAutoError error; /* evaluate the script */ int code = 0; if (exec_as_module) { GjsAutoUnref output = g_file_new_for_commandline_arg(filename); GjsAutoChar uri = g_file_get_uri(output); if (!gjs_context_register_module(js_context, uri, uri, &error)) { g_critical("%s", error->message); code = 1; } uint8_t code_u8 = 0; if (!code && !gjs_context_eval_module(js_context, uri, &code_u8, &error)) { code = code_u8; if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT)) g_critical("%s", error->message); } } else if (!gjs_context_eval(js_context, script, len, filename, &code, &error)) { if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT)) g_critical("%s", error->message); } return code; } int main(int argc, char** argv) { GjsAutoError error; const char *filename; const char *program_name; gsize len; int gjs_argc = argc, script_argc, ix; char * const *script_argv; const char *env_coverage_output_path; bool interactive_mode = false; setlocale(LC_ALL, ""); GjsAutoGOptionContext context = g_option_context_new(NULL); g_option_context_set_ignore_unknown_options(context, true); g_option_context_set_help_enabled(context, false); GjsAutoStrv argv_copy_addr(g_strdupv(argv)); char** argv_copy = argv_copy_addr; g_option_context_add_main_entries(context, entries, NULL); if (!g_option_context_parse_strv(context, &argv_copy, &error)) g_error("option parsing failed: %s", error->message); /* Split options so we pass unknown ones through to the JS script */ int argc_copy = g_strv_length(argv_copy); for (ix = 1; ix < argc; ix++) { /* Check if a file was given and split after it */ if (argc_copy >= 2 && strcmp(argv[ix], argv_copy[1]) == 0) { /* Filename given; split after this argument */ gjs_argc = ix + 1; break; } /* Check if -c or --command was given and split after following arg */ if (command && (strcmp(argv[ix], "-c") == 0 || strcmp(argv[ix], "--command") == 0)) { gjs_argc = ix + 2; break; } } GjsAutoStrv gjs_argv_addr = strndupv(gjs_argc, argv); char** gjs_argv = gjs_argv_addr; script_argc = argc - gjs_argc; script_argv = argv + gjs_argc; /* Parse again, only the GJS options this time */ include_path.release(); coverage_prefixes.release(); coverage_output_path.release(); command.release(); print_version = false; print_js_version = false; debugging = false; exec_as_module = false; g_option_context_set_ignore_unknown_options(context, false); g_option_context_set_help_enabled(context, true); if (!g_option_context_parse_strv(context, &gjs_argv, &error)) { GjsAutoChar help_text = g_option_context_get_help(context, true, nullptr); g_printerr("%s\n\n%s\n", error->message, help_text.get()); return EXIT_FAILURE; } if (print_version) { g_print("%s\n", PACKAGE_STRING); return EXIT_SUCCESS; } if (print_js_version) { g_print("%s\n", gjs_get_js_version()); return EXIT_SUCCESS; } GjsAutoChar program_path = nullptr; gjs_argc = g_strv_length(gjs_argv); GjsAutoChar script; if (command) { script = command; len = strlen(script); filename = ""; program_name = gjs_argv[0]; } else if (gjs_argc == 1) { if (exec_as_module) { g_warning( "'-m' requires a file argument.\nExample: gjs -m main.js"); return EXIT_FAILURE; } script = g_strdup("const Console = imports.console; Console.interact();"); len = strlen(script); filename = ""; program_name = gjs_argv[0]; interactive_mode = true; } else { /* All unprocessed options should be in script_argv */ g_assert(gjs_argc == 2); GjsAutoUnref input = g_file_new_for_commandline_arg(gjs_argv[1]); if (!g_file_load_contents(input, nullptr, script.out(), &len, nullptr, &error)) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } program_path = g_file_get_path(input); filename = gjs_argv[1]; program_name = gjs_argv[1]; } /* This should be removed after a suitable time has passed */ check_script_args_for_stray_gjs_args(script_argc, script_argv); /* Check for GJS_TRACE_FD for sysprof profiling */ const char* env_tracefd = g_getenv("GJS_TRACE_FD"); int tracefd = -1; if (env_tracefd) { tracefd = g_ascii_strtoll(env_tracefd, nullptr, 10); g_setenv("GJS_TRACE_FD", "", true); if (tracefd > 0) enable_profiler = true; } if (interactive_mode && enable_profiler) { g_message("Profiler disabled in interactive mode."); enable_profiler = false; g_unsetenv("GJS_ENABLE_PROFILER"); /* ignore env var in eval() */ g_unsetenv("GJS_TRACE_FD"); /* ignore env var in eval() */ } const char* env_coverage_prefixes = g_getenv("GJS_COVERAGE_PREFIXES"); if (env_coverage_prefixes) coverage_prefixes = g_strsplit(env_coverage_prefixes, ":", -1); if (coverage_prefixes) gjs_coverage_enable(); GjsAutoUnref js_context(GJS_CONTEXT(g_object_new( GJS_TYPE_CONTEXT, "search-path", include_path.get(), "program-name", program_name, "program-path", program_path.get(), "profiler-enabled", enable_profiler, "exec-as-module", exec_as_module, nullptr))); env_coverage_output_path = g_getenv("GJS_COVERAGE_OUTPUT"); if (env_coverage_output_path != NULL) { g_free(coverage_output_path); coverage_output_path = g_strdup(env_coverage_output_path); } GjsAutoUnref coverage; if (coverage_prefixes) { if (!coverage_output_path) g_error("--coverage-output is required when taking coverage statistics"); GjsAutoUnref output = g_file_new_for_commandline_arg(coverage_output_path); coverage = gjs_coverage_new(coverage_prefixes, js_context, output); } if (enable_profiler && profile_output_path) { GjsProfiler *profiler = gjs_context_get_profiler(js_context); gjs_profiler_set_filename(profiler, profile_output_path); } else if (enable_profiler && tracefd > -1) { GjsProfiler* profiler = gjs_context_get_profiler(js_context); gjs_profiler_set_fd(profiler, tracefd); tracefd = -1; } if (tracefd != -1) { close(tracefd); tracefd = -1; } /* If we're debugging, set up the debugger. It will break on the first * frame. */ if (debugging) gjs_context_setup_debugger_console(js_context); int code = define_argv_and_eval_script(js_context, script_argc, script_argv, script, len, filename); /* Probably doesn't make sense to write statistics on failure */ if (coverage && code == 0) gjs_coverage_write_statistics(coverage); if (debugging) g_print("Program exited with code %d\n", code); return code; } cjs-128.1/cjs/context-private.h0000664000175000017500000002502615116312211015312 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2014 Colin Walters #ifndef GJS_CONTEXT_PRIVATE_H_ #define GJS_CONTEXT_PRIVATE_H_ #include #include // for size_t #include #include #include #include #include #include // for pair #include #include // for GMemoryMonitor #include #include #include #include #include #include #include #include // for DefaultHasher #include #include #include #include #include #include // for UniqueChars, FreePolicy #include #include // for ScriptEnvironmentPreparer #include "gi/closure.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "cjs/jsapi-util-root.h" #include "cjs/macros.h" #include "cjs/mainloop.h" #include "cjs/profiler.h" #include "cjs/promise.h" class GjsAtoms; class JSTracer; using JobQueueStorage = JS::GCVector, 0, js::SystemAllocPolicy>; using ObjectInitList = JS::GCVector, 0, js::SystemAllocPolicy>; using FundamentalTable = JS::GCHashMap, js::DefaultHasher, js::SystemAllocPolicy>; using GTypeTable = JS::GCHashMap, js::DefaultHasher, js::SystemAllocPolicy>; using FunctionVector = JS::GCVector; class GjsContextPrivate : public JS::JobQueue { public: using DestroyNotify = void (*)(JSContext*, void* data); private: GjsContext* m_public_context; JSContext* m_cx; JS::Heap m_main_loop_hook; JS::Heap m_global; JS::Heap m_internal_global; std::thread::id m_owner_thread; char* m_program_name; char* m_program_path; char** m_search_path; unsigned m_auto_gc_id; GjsAtoms* m_atoms; std::vector m_args; JobQueueStorage m_job_queue; Gjs::PromiseJobDispatcher m_dispatcher; Gjs::MainLoop m_main_loop; GjsAutoUnref m_memory_monitor; std::vector> m_destroy_notifications; std::vector m_async_closures; std::unordered_map m_unhandled_rejection_stacks; FunctionVector m_cleanup_tasks; GjsProfiler* m_profiler; /* Environment preparer needed for debugger, taken from SpiderMonkey's * JS shell */ struct EnvironmentPreparer final : protected js::ScriptEnvironmentPreparer { JSContext* m_cx; explicit EnvironmentPreparer(JSContext* cx) : m_cx(cx) { js::SetScriptEnvironmentPreparer(m_cx, this); } void invoke(JS::HandleObject scope, Closure& closure) override; }; EnvironmentPreparer m_environment_preparer; // Weak pointer mapping from fundamental native pointer to JSObject JS::WeakCache* m_fundamental_table; JS::WeakCache* m_gtype_table; // List that holds JSObject GObject wrappers for JS-created classes, from // the time of their creation until their GObject instance init function is // called ObjectInitList m_object_init_list; uint8_t m_exit_code; /* flags */ std::atomic_bool m_destroying = ATOMIC_VAR_INIT(false); bool m_should_exit : 1; bool m_force_gc : 1; bool m_draining_job_queue : 1; bool m_should_profile : 1; bool m_exec_as_module : 1; bool m_unhandled_exception : 1; bool m_should_listen_sigusr2 : 1; void schedule_gc_internal(bool force_gc); static gboolean trigger_gc_if_needed(void* data); void on_garbage_collection(JSGCStatus, JS::GCReason); class SavedQueue; void start_draining_job_queue(void); void stop_draining_job_queue(void); void warn_about_unhandled_promise_rejections(); GJS_JSAPI_RETURN_CONVENTION bool run_main_loop_hook(); [[nodiscard]] bool handle_exit_code(bool no_sync_error_pending, const char* source_type, const char* identifier, uint8_t* exit_code, GError** error); [[nodiscard]] bool auto_profile_enter(void); void auto_profile_exit(bool status); class AutoResetExit { GjsContextPrivate* m_self; public: explicit AutoResetExit(GjsContextPrivate* self) { m_self = self; } ~AutoResetExit() { m_self->m_should_exit = false; m_self->m_exit_code = 0; } }; public: /* Retrieving a GjsContextPrivate from JSContext or GjsContext */ [[nodiscard]] static GjsContextPrivate* from_cx(JSContext* cx) { return static_cast(JS_GetContextPrivate(cx)); } [[nodiscard]] static GjsContextPrivate* from_object( GObject* public_context); [[nodiscard]] static GjsContextPrivate* from_object( GjsContext* public_context); [[nodiscard]] static GjsContextPrivate* from_current_context(); GjsContextPrivate(JSContext* cx, GjsContext* public_context); ~GjsContextPrivate(void); /* Accessors */ [[nodiscard]] GjsContext* public_context() const { return m_public_context; } [[nodiscard]] bool set_main_loop_hook(JSObject* callable); [[nodiscard]] bool has_main_loop_hook() { return !!m_main_loop_hook; } [[nodiscard]] JSContext* context() const { return m_cx; } [[nodiscard]] JSObject* global() const { return m_global.get(); } [[nodiscard]] JSObject* internal_global() const { return m_internal_global.get(); } void main_loop_hold() { m_main_loop.hold(); } void main_loop_release() { m_main_loop.release(); } [[nodiscard]] GjsProfiler* profiler() const { return m_profiler; } [[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; } [[nodiscard]] bool destroying() const { return m_destroying.load(); } [[nodiscard]] const char* program_name() const { return m_program_name; } void set_program_name(char* value) { m_program_name = value; } GJS_USE const char* program_path(void) const { return m_program_path; } void set_program_path(char* value) { m_program_path = value; } void set_search_path(char** value) { m_search_path = value; } void set_should_profile(bool value) { m_should_profile = value; } void set_execute_as_module(bool value) { m_exec_as_module = value; } void set_should_listen_sigusr2(bool value) { m_should_listen_sigusr2 = value; } void set_args(std::vector&& args); GJS_JSAPI_RETURN_CONVENTION JSObject* build_args_array(); [[nodiscard]] bool is_owner_thread() const { return m_owner_thread == std::this_thread::get_id(); } [[nodiscard]] JS::WeakCache& fundamental_table() { return *m_fundamental_table; } [[nodiscard]] JS::WeakCache& gtype_table() { return *m_gtype_table; } [[nodiscard]] ObjectInitList& object_init_list() { return m_object_init_list; } [[nodiscard]] static const GjsAtoms& atoms(JSContext* cx) { return *(from_cx(cx)->m_atoms); } [[nodiscard]] static JSObject* global(JSContext* cx) { return from_cx(cx)->global(); } [[nodiscard]] bool eval(const char* script, size_t script_len, const char* filename, int* exit_status_p, GError** error); GJS_JSAPI_RETURN_CONVENTION bool eval_with_scope(JS::HandleObject scope_object, const char* script, size_t script_len, const char* filename, JS::MutableHandleValue retval); [[nodiscard]] bool eval_module(const char* identifier, uint8_t* exit_code_p, GError** error); GJS_JSAPI_RETURN_CONVENTION bool call_function(JS::HandleObject this_obj, JS::HandleValue func_val, const JS::HandleValueArray& args, JS::MutableHandleValue rval); void schedule_gc(void) { schedule_gc_internal(true); } void schedule_gc_if_needed(void); void report_unhandled_exception() { m_unhandled_exception = true; } void exit(uint8_t exit_code); [[nodiscard]] bool should_exit(uint8_t* exit_code_p) const; [[noreturn]] void exit_immediately(uint8_t exit_code); // Implementations of JS::JobQueue virtual functions GJS_JSAPI_RETURN_CONVENTION JSObject* getIncumbentGlobal(JSContext* cx) override; GJS_JSAPI_RETURN_CONVENTION bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, JS::HandleObject job, JS::HandleObject allocation_site, JS::HandleObject incumbent_global) override; void runJobs(JSContext* cx) override; [[nodiscard]] bool empty() const override { return m_job_queue.empty(); } [[nodiscard]] bool isDrainingStopped() const override { return !m_draining_job_queue; } js::UniquePtr saveJobQueue( JSContext* cx) override; GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(); void register_unhandled_promise_rejection(uint64_t id, JS::UniqueChars&& stack); void unregister_unhandled_promise_rejection(uint64_t id); GJS_JSAPI_RETURN_CONVENTION bool queue_finalization_registry_cleanup( JSFunction* cleanup_task); GJS_JSAPI_RETURN_CONVENTION bool run_finalization_registry_cleanup(); void register_notifier(DestroyNotify notify_func, void* data); void unregister_notifier(DestroyNotify notify_func, void* data); void async_closure_enqueue_for_gc(Gjs::Closure*); [[nodiscard]] bool register_module(const char* identifier, const char* filename, GError** error); static void trace(JSTracer* trc, void* data); void free_profiler(void); void dispose(void); }; std::string gjs_dumpstack_string(); namespace Gjs { class AutoMainRealm : public JSAutoRealm { public: explicit AutoMainRealm(GjsContextPrivate* gjs); explicit AutoMainRealm(JSContext* cx); }; class AutoInternalRealm : public JSAutoRealm { public: explicit AutoInternalRealm(GjsContextPrivate* gjs); explicit AutoInternalRealm(JSContext* cx); }; } // namespace Gjs #endif // GJS_CONTEXT_PRIVATE_H_ cjs-128.1/cjs/context.cpp0000664000175000017500000017107515116312211014203 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include // for sigaction, SIGUSR1, sa_handler #include #include // for FILE, fclose, size_t #include // for exit #include // for memset #ifdef HAVE_UNISTD_H # include // for getpid #endif #ifdef G_OS_WIN32 # include # include #endif #include #include // for u16string #include // for get_id #include #include // for move #include #include #include #include #include #include // for SystemAllocPolicy #include // for Call, JS_CallFunctionValue #include // for UndefinedHandleValue #include #include #include #include #include #include // for StealPendingExceptionStack #include // for JS_GC, JS_AddExtraGCRootsTr... #include // for WeakCache #include // for RootedVector #include // for CurrentGlobalOrNull #include // for ExposeObjectToActiveJS #include #include #include // for JobQueue::SavedJobQueue #include #include // for JSPROP_PERMANENT, JSPROP_RE... #include #include #include #include #include #include #include #include // for DeletePolicy via WeakCache #include #include #include #include // for JS_GetFunctionObject, JS_Ge... #include // for ScriptEnvironmentPreparer #include // for UniquePtr::get #include "gi/closure.h" // for Closure::Ptr, Closure #include "gi/function.h" #include "gi/object.h" #include "gi/private.h" #include "gi/repo.h" #include "gi/utils-inl.h" #include "cjs/atoms.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/engine.h" #include "cjs/error-types.h" #include "cjs/global.h" #include "cjs/importer.h" #include "cjs/internal.h" #include "cjs/jsapi-util.h" #include "cjs/mainloop.h" #include "cjs/mem.h" #include "cjs/module.h" #include "cjs/native.h" #include "cjs/objectbox.h" #include "cjs/profiler-private.h" #include "cjs/profiler.h" #include "cjs/promise.h" #include "cjs/text-encoding.h" #include "modules/cairo-module.h" #include "modules/console.h" #include "modules/print.h" #include "modules/system.h" #include "util/log.h" namespace mozilla { union Utf8Unit; } static void gjs_context_dispose (GObject *object); static void gjs_context_finalize (GObject *object); static void gjs_context_constructed (GObject *object); static void gjs_context_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gjs_context_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); void GjsContextPrivate::EnvironmentPreparer::invoke(JS::HandleObject scope, Closure& closure) { g_assert(!JS_IsExceptionPending(m_cx)); JSAutoRealm ar(m_cx, scope); if (!closure(m_cx)) gjs_log_exception(m_cx); } struct _GjsContext { GObject parent; }; G_DEFINE_TYPE_WITH_PRIVATE(GjsContext, gjs_context, G_TYPE_OBJECT); GjsContextPrivate* GjsContextPrivate::from_object(GObject* js_context) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr); return static_cast( gjs_context_get_instance_private(GJS_CONTEXT(js_context))); } GjsContextPrivate* GjsContextPrivate::from_object(GjsContext* js_context) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr); return static_cast( gjs_context_get_instance_private(js_context)); } GjsContextPrivate* GjsContextPrivate::from_current_context() { return from_object(gjs_context_get_current()); } enum { PROP_CONTEXT_0, PROP_PROGRAM_PATH, PROP_SEARCH_PATH, PROP_PROGRAM_NAME, PROP_PROFILER_ENABLED, PROP_PROFILER_SIGUSR2, PROP_EXEC_AS_MODULE, }; static GMutex contexts_lock; static GList *all_contexts = NULL; static GjsAutoChar dump_heap_output; static unsigned dump_heap_idle_id = 0; #ifdef G_OS_UNIX // Currently heap dumping via SIGUSR1 is only supported on UNIX platforms! // This can reduce performance. See note in system.cpp on System.dumpHeap(). static void gjs_context_dump_heaps(void) { static unsigned counter = 0; gjs_memory_report("signal handler", false); /* dump to sequential files to allow easier comparisons */ GjsAutoChar filename = g_strdup_printf("%s.%jd.%u", dump_heap_output.get(), intmax_t(getpid()), counter); ++counter; FILE *fp = fopen(filename, "w"); if (!fp) return; for (GList *l = all_contexts; l; l = g_list_next(l)) { auto* gjs = static_cast(l->data); js::DumpHeap(gjs->context(), fp, js::CollectNurseryBeforeDump); } fclose(fp); } static gboolean dump_heap_idle(void*) { dump_heap_idle_id = 0; gjs_context_dump_heaps(); return false; } static void dump_heap_signal_handler(int signum [[maybe_unused]]) { if (dump_heap_idle_id == 0) dump_heap_idle_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, dump_heap_idle, nullptr, nullptr); } #endif static void setup_dump_heap(void) { static bool dump_heap_initialized = false; if (!dump_heap_initialized) { dump_heap_initialized = true; /* install signal handler only if environment variable is set */ const char *heap_output = g_getenv("GJS_DEBUG_HEAP_OUTPUT"); if (heap_output) { #ifdef G_OS_UNIX struct sigaction sa; dump_heap_output = g_strdup(heap_output); memset(&sa, 0, sizeof(sa)); sa.sa_handler = dump_heap_signal_handler; sigaction(SIGUSR1, &sa, nullptr); #else g_message( "heap dump is currently only supported on UNIX platforms"); #endif } } } static void gjs_context_init(GjsContext *js_context) { gjs_log_init(); gjs_context_make_current(js_context); } static void gjs_context_class_init(GjsContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; gjs_log_init(); object_class->dispose = gjs_context_dispose; object_class->finalize = gjs_context_finalize; object_class->constructed = gjs_context_constructed; object_class->get_property = gjs_context_get_property; object_class->set_property = gjs_context_set_property; pspec = g_param_spec_boxed("search-path", "Search path", "Path where modules to import should reside", G_TYPE_STRV, (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_SEARCH_PATH, pspec); g_param_spec_unref(pspec); pspec = g_param_spec_string("program-name", "Program Name", "The filename of the launched JS program", "", (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_PROGRAM_NAME, pspec); g_param_spec_unref(pspec); pspec = g_param_spec_string( "program-path", "Executed File Path", "The full path of the launched file or NULL if GJS was launched from " "the C API or interactive console.", nullptr, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_PROGRAM_PATH, pspec); g_param_spec_unref(pspec); /** * GjsContext:profiler-enabled: * * Set this property to profile any JS code run by this context. By * default, the profiler is started and stopped when you call * gjs_context_eval(). * * The value of this property is superseded by the GJS_ENABLE_PROFILER * environment variable. * * You may only have one context with the profiler enabled at a time. */ pspec = g_param_spec_boolean("profiler-enabled", "Profiler enabled", "Whether to profile JS code run by this context", FALSE, GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_PROFILER_ENABLED, pspec); g_param_spec_unref(pspec); /** * GjsContext:profiler-sigusr2: * * Set this property to install a SIGUSR2 signal handler that starts and * stops the profiler. This property also implies that * #GjsContext:profiler-enabled is set. */ pspec = g_param_spec_boolean("profiler-sigusr2", "Profiler SIGUSR2", "Whether to activate the profiler on SIGUSR2", FALSE, GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_PROFILER_SIGUSR2, pspec); g_param_spec_unref(pspec); pspec = g_param_spec_boolean( "exec-as-module", "Execute as module", "Whether to execute the file as a module", FALSE, GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_EXEC_AS_MODULE, pspec); g_param_spec_unref(pspec); /* For CjsPrivate */ if (!g_getenv("GJS_USE_UNINSTALLED_FILES")) { #ifdef G_OS_WIN32 extern HMODULE gjs_dll; GjsAutoChar basedir = g_win32_get_package_installation_directory_of_module(gjs_dll); GjsAutoChar priv_typelib_dir = g_build_filename( basedir, "lib", "gjs", "girepository-1.0", nullptr); #else GjsAutoChar priv_typelib_dir = g_build_filename(PKGLIBDIR, "girepository-1.0", nullptr); #endif g_irepository_prepend_search_path(priv_typelib_dir); } auto& registry = Gjs::NativeModuleDefineFuncs::get(); registry.add("_promiseNative", gjs_define_native_promise_stuff); registry.add("_byteArrayNative", gjs_define_byte_array_stuff); registry.add("_encodingNative", gjs_define_text_encoding_stuff); registry.add("_gi", gjs_define_private_gi_stuff); registry.add("gi", gjs_define_repo); registry.add("cairoNative", gjs_js_define_cairo_stuff); registry.add("system", gjs_js_define_system_stuff); registry.add("console", gjs_define_console_stuff); registry.add("_print", gjs_define_print_stuff); } void GjsContextPrivate::trace(JSTracer* trc, void* data) { auto* gjs = static_cast(data); JS::TraceEdge(trc, &gjs->m_global, "GJS global object"); JS::TraceEdge(trc, &gjs->m_internal_global, "GJS internal global object"); JS::TraceEdge(trc, &gjs->m_main_loop_hook, "GJS main loop hook"); gjs->m_atoms->trace(trc); gjs->m_job_queue.trace(trc); gjs->m_cleanup_tasks.trace(trc); gjs->m_object_init_list.trace(trc); } void GjsContextPrivate::warn_about_unhandled_promise_rejections(void) { for (auto& kv : m_unhandled_rejection_stacks) { const char* stack = kv.second.get(); g_warning("Unhandled promise rejection. To suppress this warning, add " "an error handler to your promise chain with .catch() or a " "try-catch block around your await expression. %s%s", stack ? "Stack trace of the failed promise:\n" : "Unfortunately there is no stack trace of the failed promise.", stack ? stack : ""); } m_unhandled_rejection_stacks.clear(); } static void gjs_context_dispose(GObject *object) { gjs_debug(GJS_DEBUG_CONTEXT, "JS shutdown sequence"); GjsContextPrivate* gjs = GjsContextPrivate::from_object(object); g_assert(gjs->is_owner_thread() && "Gjs Context disposed from another thread"); /* Profiler must be stopped and freed before context is shut down */ gjs->free_profiler(); /* Stop accepting entries in the toggle queue before running dispose * notifications, which causes all GjsMaybeOwned instances to unroot. * We don't want any objects to toggle down after that. */ gjs_debug(GJS_DEBUG_CONTEXT, "Shutting down toggle queue"); gjs_object_clear_toggles(); gjs_object_shutdown_toggle_queue(); if (gjs->context()) ObjectInstance::context_dispose_notify(nullptr, object); gjs_debug(GJS_DEBUG_CONTEXT, "Notifying external reference holders of GjsContext dispose"); G_OBJECT_CLASS(gjs_context_parent_class)->dispose(object); gjs->dispose(); } void GjsContextPrivate::free_profiler(void) { gjs_debug(GJS_DEBUG_CONTEXT, "Stopping profiler"); if (m_profiler) g_clear_pointer(&m_profiler, _gjs_profiler_free); } void GjsContextPrivate::register_notifier(DestroyNotify notify_func, void* data) { m_destroy_notifications.push_back({notify_func, data}); } void GjsContextPrivate::unregister_notifier(DestroyNotify notify_func, void* data) { auto target = std::make_pair(notify_func, data); Gjs::remove_one_from_unsorted_vector(&m_destroy_notifications, target); } void GjsContextPrivate::dispose(void) { if (m_cx) { stop_draining_job_queue(); gjs_debug(GJS_DEBUG_CONTEXT, "Notifying reference holders of GjsContext dispose"); for (auto const& destroy_notify : m_destroy_notifications) destroy_notify.first(m_cx, destroy_notify.second); gjs_debug(GJS_DEBUG_CONTEXT, "Checking unhandled promise rejections"); warn_about_unhandled_promise_rejections(); gjs_debug(GJS_DEBUG_CONTEXT, "Releasing cached JS wrappers"); m_fundamental_table->clear(); m_gtype_table->clear(); /* Do a full GC here before tearing down, since once we do * that we may not have the JS::GetReservedSlot(, 0) to access the * context */ gjs_debug(GJS_DEBUG_CONTEXT, "Final triggered GC"); JS_GC(m_cx, Gjs::GCReason::GJS_CONTEXT_DISPOSE); gjs_debug(GJS_DEBUG_CONTEXT, "Destroying JS context"); m_destroying.store(true); /* Now, release all native objects, to avoid recursion between * the JS teardown and the C teardown. The JSObject proxies * still exist, but point to NULL. */ gjs_debug(GJS_DEBUG_CONTEXT, "Releasing all native objects"); ObjectInstance::prepare_shutdown(); GjsCallbackTrampoline::prepare_shutdown(); gjs_debug(GJS_DEBUG_CONTEXT, "Disabling auto GC"); if (m_auto_gc_id > 0) { g_source_remove(m_auto_gc_id); m_auto_gc_id = 0; } gjs_debug(GJS_DEBUG_CONTEXT, "Ending trace on global object"); JS_RemoveExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this); m_global = nullptr; m_internal_global = nullptr; m_main_loop_hook = nullptr; gjs_debug(GJS_DEBUG_CONTEXT, "Freeing allocated resources"); delete m_fundamental_table; delete m_gtype_table; delete m_atoms; m_job_queue.clear(); m_object_init_list.clear(); /* Tear down JS */ JS_DestroyContext(m_cx); m_cx = nullptr; // don't use g_clear_pointer() as we want the pointer intact while we // destroy the context in case we dump stack gjs_debug(GJS_DEBUG_CONTEXT, "JS context destroyed"); } } GjsContextPrivate::~GjsContextPrivate(void) { g_clear_pointer(&m_search_path, g_strfreev); g_clear_pointer(&m_program_path, g_free); g_clear_pointer(&m_program_name, g_free); } static void gjs_context_finalize(GObject *object) { if (gjs_context_get_current() == (GjsContext*)object) gjs_context_make_current(NULL); g_mutex_lock(&contexts_lock); all_contexts = g_list_remove(all_contexts, object); g_mutex_unlock(&contexts_lock); GjsContextPrivate* gjs = GjsContextPrivate::from_object(object); gjs->~GjsContextPrivate(); G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object); g_mutex_lock(&contexts_lock); if (!all_contexts) gjs_log_cleanup(); g_mutex_unlock(&contexts_lock); } static void gjs_context_constructed(GObject *object) { GjsContext *js_context = GJS_CONTEXT(object); G_OBJECT_CLASS(gjs_context_parent_class)->constructed(object); GjsContextPrivate* gjs_location = GjsContextPrivate::from_object(object); JSContext* cx = gjs_create_js_context(gjs_location); if (!cx) g_error("Failed to create javascript context"); new (gjs_location) GjsContextPrivate(cx, js_context); g_mutex_lock(&contexts_lock); all_contexts = g_list_prepend(all_contexts, object); g_mutex_unlock(&contexts_lock); setup_dump_heap(); } static bool on_context_module_rejected_log_exception(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_debug(GJS_DEBUG_IMPORTER, "Module evaluation promise rejected: %s", gjs_debug_callable(&args.callee()).c_str()); JS::HandleValue error = args.get(0); GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx); gjs_cx->report_unhandled_exception(); gjs_log_exception_full(cx, error, nullptr, G_LOG_LEVEL_CRITICAL); gjs_cx->main_loop_release(); args.rval().setUndefined(); return true; } static bool on_context_module_resolved(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_debug(GJS_DEBUG_IMPORTER, "Module evaluation promise resolved: %s", gjs_debug_callable(&args.callee()).c_str()); args.rval().setUndefined(); GjsContextPrivate::from_cx(cx)->main_loop_release(); return true; } static bool add_promise_reactions(JSContext* cx, JS::HandleValue promise, JSNative resolve, JSNative reject, const std::string& debug_tag) { g_assert(promise.isObject() && "got weird value from JS::ModuleEvaluate"); JS::RootedObject promise_object(cx, &promise.toObject()); std::string resolved_tag = debug_tag + " async resolved"; std::string rejected_tag = debug_tag + " async rejected"; JS::RootedFunction on_rejected( cx, js::NewFunctionWithReserved(cx, reject, 1, 0, rejected_tag.c_str())); if (!on_rejected) return false; JS::RootedFunction on_resolved( cx, js::NewFunctionWithReserved(cx, resolve, 1, 0, resolved_tag.c_str())); if (!on_resolved) return false; JS::RootedObject resolved(cx, JS_GetFunctionObject(on_resolved)); JS::RootedObject rejected(cx, JS_GetFunctionObject(on_rejected)); return JS::AddPromiseReactions(cx, promise_object, resolved, rejected); } static void load_context_module(JSContext* cx, const char* uri, const char* debug_identifier) { JS::RootedObject loader(cx, gjs_module_load(cx, uri, uri)); if (!loader) { gjs_log_exception(cx); g_error("Failed to load %s module.", debug_identifier); } if (!JS::ModuleLink(cx, loader)) { gjs_log_exception(cx); g_error("Failed to instantiate %s module.", debug_identifier); } JS::RootedValue evaluation_promise(cx); if (!JS::ModuleEvaluate(cx, loader, &evaluation_promise)) { gjs_log_exception(cx); g_error("Failed to evaluate %s module.", debug_identifier); } GjsContextPrivate::from_cx(cx)->main_loop_hold(); bool ok = add_promise_reactions( cx, evaluation_promise, on_context_module_resolved, [](JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_debug(GJS_DEBUG_IMPORTER, "Module evaluation promise rejected: %s", gjs_debug_callable(&args.callee()).c_str()); JS::HandleValue error = args.get(0); // Abort because this module is required. gjs_log_exception_full(cx, error, nullptr, G_LOG_LEVEL_ERROR); GjsContextPrivate::from_cx(cx)->main_loop_release(); return false; }, debug_identifier); if (!ok) { gjs_log_exception(cx); g_error("Failed to load %s module.", debug_identifier); } } GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) : m_public_context(public_context), m_cx(cx), m_owner_thread(std::this_thread::get_id()), m_dispatcher(this), m_memory_monitor(g_memory_monitor_dup_default()), m_environment_preparer(cx) { JS_SetGCCallback( cx, [](JSContext*, JSGCStatus status, JS::GCReason reason, void* data) { static_cast(data)->on_garbage_collection( status, reason); }, this); const char *env_profiler = g_getenv("GJS_ENABLE_PROFILER"); if (env_profiler || m_should_listen_sigusr2) m_should_profile = true; if (m_should_profile) { m_profiler = _gjs_profiler_new(public_context); if (!m_profiler) { m_should_profile = false; } else { if (m_should_listen_sigusr2) _gjs_profiler_setup_signals(m_profiler, public_context); } } JSRuntime* rt = JS_GetRuntime(m_cx); m_fundamental_table = new JS::WeakCache(rt); m_gtype_table = new JS::WeakCache(rt); m_atoms = new GjsAtoms(); if (ObjectBox::gtype() == 0) g_error("Failed to initialize JSObject GType"); JS::RootedObject internal_global( m_cx, gjs_create_global_object(cx, GjsGlobalType::INTERNAL)); if (!internal_global) { gjs_log_exception(m_cx); g_error("Failed to initialize internal global object"); } m_internal_global = internal_global; Gjs::AutoInternalRealm ar{this}; JS_AddExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this); if (!m_atoms->init_atoms(m_cx)) { gjs_log_exception(m_cx); g_error("Failed to initialize global strings"); } if (!gjs_define_global_properties(m_cx, internal_global, GjsGlobalType::INTERNAL, "GJS internal global", "nullptr")) { gjs_log_exception(m_cx); g_error("Failed to define properties on internal global object"); } JS::RootedObject global( m_cx, gjs_create_global_object(cx, GjsGlobalType::DEFAULT, internal_global)); if (!global) { gjs_log_exception(m_cx); g_error("Failed to initialize global object"); } m_global = global; { Gjs::AutoMainRealm ar{this}; std::vector paths; if (m_search_path) paths = {m_search_path, m_search_path + g_strv_length(m_search_path)}; JS::RootedObject importer(m_cx, gjs_create_root_importer(m_cx, paths)); if (!importer) { gjs_log_exception(cx); g_error("Failed to create root importer"); } g_assert( gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS).isUndefined() && "Someone else already created root importer"); gjs_set_global_slot(global, GjsGlobalSlot::IMPORTS, JS::ObjectValue(*importer)); if (!gjs_define_global_properties(m_cx, global, GjsGlobalType::DEFAULT, "GJS", "default")) { gjs_log_exception(m_cx); g_error("Failed to define properties on global object"); } } JS::SetModuleResolveHook(rt, gjs_module_resolve); JS::SetModuleDynamicImportHook(rt, gjs_dynamic_module_resolve); JS::SetModuleMetadataHook(rt, gjs_populate_module_meta); if (!JS_DefineProperty(m_cx, internal_global, "moduleGlobalThis", global, JSPROP_PERMANENT)) { gjs_log_exception(m_cx); g_error("Failed to define module global in internal global."); } if (!gjs_load_internal_module(cx, "loader")) { gjs_log_exception(cx); g_error("Failed to load internal module loaders."); } { Gjs::AutoMainRealm ar{this}; load_context_module( cx, "resource:///org/cinnamon/cjs/modules/esm/_bootstrap/default.js", "ESM bootstrap"); } [[maybe_unused]] bool success = m_main_loop.spin(this); g_assert(success && "bootstrap should not call system.exit()"); g_signal_connect_object( m_memory_monitor, "low-memory-warning", G_CALLBACK(+[](GjsContext* js_cx, GMemoryMonitorWarningLevel level) { auto* cx = static_cast(gjs_context_get_native_context(js_cx)); JS::PrepareForFullGC(cx); JS::GCOptions gc_strength = JS::GCOptions::Normal; if (level > G_MEMORY_MONITOR_WARNING_LEVEL_LOW) gc_strength = JS::GCOptions::Shrink; JS::NonIncrementalGC(cx, gc_strength, Gjs::GCReason::LOW_MEMORY); }), m_public_context, G_CONNECT_SWAPPED); start_draining_job_queue(); } void GjsContextPrivate::set_args(std::vector&& args) { m_args = args; } JSObject* GjsContextPrivate::build_args_array() { return gjs_build_string_array(m_cx, m_args); } static void gjs_context_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(object); switch (prop_id) { case PROP_PROGRAM_NAME: g_value_set_string(value, gjs->program_name()); break; case PROP_PROGRAM_PATH: g_value_set_string(value, gjs->program_path()); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gjs_context_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(object); switch (prop_id) { case PROP_SEARCH_PATH: gjs->set_search_path(static_cast(g_value_dup_boxed(value))); break; case PROP_PROGRAM_NAME: gjs->set_program_name(g_value_dup_string(value)); break; case PROP_PROGRAM_PATH: gjs->set_program_path(g_value_dup_string(value)); break; case PROP_PROFILER_ENABLED: gjs->set_should_profile(g_value_get_boolean(value)); break; case PROP_PROFILER_SIGUSR2: gjs->set_should_listen_sigusr2(g_value_get_boolean(value)); break; case PROP_EXEC_AS_MODULE: gjs->set_execute_as_module(g_value_get_boolean(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } GjsContext* gjs_context_new(void) { return (GjsContext*) g_object_new (GJS_TYPE_CONTEXT, NULL); } GjsContext* gjs_context_new_with_search_path(char** search_path) { return (GjsContext*) g_object_new (GJS_TYPE_CONTEXT, "search-path", search_path, NULL); } gboolean GjsContextPrivate::trigger_gc_if_needed(void* data) { auto* gjs = static_cast(data); gjs->m_auto_gc_id = 0; if (gjs->m_force_gc) { gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Big Hammer hit"); JS_GC(gjs->m_cx, Gjs::GCReason::BIG_HAMMER); } else { gjs_gc_if_needed(gjs->m_cx); } gjs->m_force_gc = false; return G_SOURCE_REMOVE; } void GjsContextPrivate::schedule_gc_internal(bool force_gc) { m_force_gc |= force_gc; if (m_auto_gc_id > 0) return; if (force_gc) gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Big Hammer scheduled"); m_auto_gc_id = g_timeout_add_seconds_full(G_PRIORITY_LOW, 10, trigger_gc_if_needed, this, nullptr); if (force_gc) g_source_set_name_by_id(m_auto_gc_id, "[gjs] Garbage Collection (Big Hammer)"); else g_source_set_name_by_id(m_auto_gc_id, "[gjs] Garbage Collection"); } /* * GjsContextPrivate::schedule_gc_if_needed: * * Does a minor GC immediately if the JS engine decides one is needed, but also * schedules a full GC in the next idle time. */ void GjsContextPrivate::schedule_gc_if_needed(void) { // We call JS_MaybeGC immediately, but defer a check for a full GC cycle // to an idle handler. JS_MaybeGC(m_cx); schedule_gc_internal(false); } void GjsContextPrivate::on_garbage_collection(JSGCStatus status, JS::GCReason reason) { if (m_profiler) _gjs_profiler_set_gc_status(m_profiler, status, reason); switch (status) { case JSGC_BEGIN: gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Begin garbage collection because of %s", gjs_explain_gc_reason(reason)); // We finalize any pending toggle refs before doing any garbage // collection, so that we can collect the JS wrapper objects, and in // order to minimize the chances of objects having a pending toggle // up queued when they are garbage collected. gjs_object_clear_toggles(); m_async_closures.clear(); m_async_closures.shrink_to_fit(); break; case JSGC_END: m_destroy_notifications.shrink_to_fit(); gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "End garbage collection"); break; default: g_assert_not_reached(); } } void GjsContextPrivate::exit(uint8_t exit_code) { g_assert(!m_should_exit); m_should_exit = true; m_exit_code = exit_code; } bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const { if (exit_code_p != NULL) *exit_code_p = m_exit_code; return m_should_exit; } void GjsContextPrivate::exit_immediately(uint8_t exit_code) { warn_about_unhandled_promise_rejections(); ::exit(exit_code); } void GjsContextPrivate::start_draining_job_queue(void) { m_dispatcher.start(); } void GjsContextPrivate::stop_draining_job_queue(void) { m_draining_job_queue = false; m_dispatcher.stop(); } JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) { // This is equivalent to SpiderMonkey's behavior. return JS::CurrentGlobalOrNull(cx); } // See engine.cpp and JS::SetJobQueue(). bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]], JS::HandleObject promise, JS::HandleObject job, JS::HandleObject allocation_site, JS::HandleObject incumbent_global [[maybe_unused]]) { g_assert(cx == m_cx); g_assert(from_cx(cx) == this); gjs_debug(GJS_DEBUG_MAINLOOP, "Enqueue job %s, promise=%s, allocation site=%s", gjs_debug_object(job).c_str(), gjs_debug_object(promise).c_str(), gjs_debug_object(allocation_site).c_str()); if (!m_job_queue.append(job)) { JS_ReportOutOfMemory(m_cx); return false; } JS::JobQueueMayNotBeEmpty(m_cx); m_dispatcher.start(); return true; } // Override of JobQueue::runJobs(). Called by js::RunJobs(), and when execution // of the job queue was interrupted by the debugger and is resuming. void GjsContextPrivate::runJobs(JSContext* cx) { g_assert(cx == m_cx); g_assert(from_cx(cx) == this); if (!run_jobs_fallible()) gjs_log_exception(cx); } /* * GjsContext::run_jobs_fallible: * * Drains the queue of promise callbacks that the JS engine has reported * finished, calling each one and logging any exceptions that it throws. * * Adapted from js::RunJobs() in SpiderMonkey's default job queue * implementation. * * Returns: false if one of the jobs threw an uncatchable exception; * otherwise true. */ bool GjsContextPrivate::run_jobs_fallible() { bool retval = true; if (m_draining_job_queue || m_should_exit) return true; m_draining_job_queue = true; // Ignore reentrant calls JS::RootedObject job(m_cx); JS::HandleValueArray args(JS::HandleValueArray::empty()); JS::RootedValue rval(m_cx); if (m_job_queue.length() == 0) { // Check FinalizationRegistry cleanup tasks at least once if there are // no microtasks queued. This may enqueue more microtasks, which will be // appended to m_job_queue. if (!run_finalization_registry_cleanup()) retval = false; } /* Execute jobs in a loop until we've reached the end of the queue. * Since executing a job can trigger enqueueing of additional jobs, * it's crucial to recheck the queue length during each iteration. */ for (size_t ix = 0; ix < m_job_queue.length(); ix++) { /* A previous job might have set this flag. e.g., System.exit(). */ if (m_should_exit || !m_dispatcher.is_running()) { gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping jobs because of %s", m_should_exit ? "exit" : "main loop cancel"); break; } job = m_job_queue[ix]; /* It's possible that job draining was interrupted prematurely, * leaving the queue partly processed. In that case, slots for * already-executed entries will contain nullptrs, which we should * just skip. */ if (!job) continue; m_job_queue[ix] = nullptr; { JSAutoRealm ar(m_cx, job); gjs_debug(GJS_DEBUG_MAINLOOP, "handling job %zu, %s", ix, gjs_debug_object(job).c_str()); if (!JS::Call(m_cx, JS::UndefinedHandleValue, job, args, &rval)) { /* Uncatchable exception - return false so that * System.exit() works in the interactive shell and when * exiting the interpreter. */ if (!JS_IsExceptionPending(m_cx)) { /* System.exit() is an uncatchable exception, but does not * indicate a bug. Log everything else. */ if (!should_exit(nullptr)) g_critical("Promise callback terminated with uncatchable exception"); retval = false; continue; } /* There's nowhere for the exception to go at this point */ gjs_log_exception_uncaught(m_cx); } } gjs_debug(GJS_DEBUG_MAINLOOP, "Completed job %zu", ix); // Run FinalizationRegistry cleanup tasks after each job. Cleanup tasks // may enqueue more microtasks, which will be appended to m_job_queue. if (!run_finalization_registry_cleanup()) retval = false; } m_draining_job_queue = false; m_job_queue.clear(); warn_about_unhandled_promise_rejections(); JS::JobQueueIsEmpty(m_cx); return retval; } bool GjsContextPrivate::run_finalization_registry_cleanup() { bool retval = true; JS::Rooted tasks{m_cx}; std::swap(tasks.get(), m_cleanup_tasks); g_assert(m_cleanup_tasks.empty()); JS::RootedFunction task{m_cx}; JS::RootedValue unused_rval{m_cx}; for (JSFunction* func : tasks) { gjs_debug(GJS_DEBUG_MAINLOOP, "Running FinalizationRegistry cleanup callback"); task.set(func); JS::ExposeObjectToActiveJS(JS_GetFunctionObject(func)); JSAutoRealm ar{m_cx, JS_GetFunctionObject(func)}; if (!JS_CallFunction(m_cx, nullptr, task, JS::HandleValueArray::empty(), &unused_rval)) { // Same logic as above if (!JS_IsExceptionPending(m_cx)) { if (!should_exit(nullptr)) g_critical( "FinalizationRegistry callback terminated with " "uncatchable exception"); retval = false; continue; } gjs_log_exception_uncaught(m_cx); } gjs_debug(GJS_DEBUG_MAINLOOP, "Completed FinalizationRegistry cleanup callback"); } return retval; } class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue { private: GjsContextPrivate* m_gjs; JS::PersistentRooted m_queue; bool m_was_draining : 1; public: explicit SavedQueue(GjsContextPrivate* gjs) : m_gjs(gjs), m_queue(gjs->m_cx, std::move(gjs->m_job_queue)), m_was_draining(gjs->m_draining_job_queue) { gjs_debug(GJS_DEBUG_CONTEXT, "Pausing job queue"); gjs->stop_draining_job_queue(); } ~SavedQueue(void) { gjs_debug(GJS_DEBUG_CONTEXT, "Unpausing job queue"); m_gjs->m_job_queue = std::move(m_queue.get()); m_gjs->m_draining_job_queue = m_was_draining; m_gjs->start_draining_job_queue(); } }; js::UniquePtr GjsContextPrivate::saveJobQueue( JSContext* cx) { g_assert(cx == m_cx); g_assert(from_cx(cx) == this); auto saved_queue = js::MakeUnique(this); if (!saved_queue) { JS_ReportOutOfMemory(cx); return nullptr; } g_assert(m_job_queue.empty()); return saved_queue; } void GjsContextPrivate::register_unhandled_promise_rejection( uint64_t id, JS::UniqueChars&& stack) { m_unhandled_rejection_stacks[id] = std::move(stack); } void GjsContextPrivate::unregister_unhandled_promise_rejection(uint64_t id) { size_t erased = m_unhandled_rejection_stacks.erase(id); if (erased != 1) { g_critical("Promise %" G_GUINT64_FORMAT " Handler attached to rejected promise that wasn't " "previously marked as unhandled or that we wrongly reported " "as unhandled", id); } } bool GjsContextPrivate::queue_finalization_registry_cleanup( JSFunction* cleanup_task) { return m_cleanup_tasks.append(cleanup_task); } void GjsContextPrivate::async_closure_enqueue_for_gc(Gjs::Closure* trampoline) { // Because we can't free the mmap'd data for a callback // while it's in use, this list keeps track of ones that // will be freed the next time gc happens g_assert(!trampoline->context() || trampoline->context() == m_cx); m_async_closures.emplace_back(trampoline); } /** * gjs_context_maybe_gc: * @context: a #GjsContext * * Similar to the Spidermonkey JS_MaybeGC() call which * heuristically looks at JS runtime memory usage and * may initiate a garbage collection. * * This function always unconditionally invokes JS_MaybeGC(), but * additionally looks at memory usage from the system malloc() * when available, and if the delta has grown since the last run * significantly, also initiates a full JavaScript garbage * collection. The idea is that since GJS is a bridge between * JavaScript and system libraries, and JS objects act as proxies * for these system memory objects, GJS consumers need a way to * hint to the runtime that it may be a good idea to try a * collection. * * A good time to call this function is when your application * transitions to an idle state. */ void gjs_context_maybe_gc (GjsContext *context) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(context); gjs_maybe_gc(gjs->context()); } /** * gjs_context_gc: * @context: a #GjsContext * * Initiate a full GC; may or may not block until complete. This * function just calls Spidermonkey JS_GC(). */ void gjs_context_gc (GjsContext *context) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(context); JS_GC(gjs->context(), Gjs::GCReason::GJS_API_CALL); } /** * gjs_context_get_all: * * Returns a newly-allocated list containing all known instances of #GjsContext. * This is useful for operating on the contexts from a process-global situation * such as a debugger. * * Return value: (element-type GjsContext) (transfer full): Known #GjsContext instances */ GList* gjs_context_get_all(void) { GList *result; GList *iter; g_mutex_lock (&contexts_lock); result = g_list_copy(all_contexts); for (iter = result; iter; iter = iter->next) g_object_ref((GObject*)iter->data); g_mutex_unlock (&contexts_lock); return result; } /** * gjs_context_get_native_context: * * Returns a pointer to the underlying native context. For SpiderMonkey, this * is a JSContext * */ void* gjs_context_get_native_context (GjsContext *js_context) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), NULL); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); return gjs->context(); } bool gjs_context_eval(GjsContext *js_context, const char *script, gssize script_len, const char *filename, int *exit_status_p, GError **error) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); size_t real_len = script_len < 0 ? strlen(script) : script_len; GjsAutoUnref js_context_ref(js_context, GjsAutoTakeOwnership()); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); return gjs->eval(script, real_len, filename, exit_status_p, error); } bool gjs_context_eval_module(GjsContext* js_context, const char* identifier, uint8_t* exit_code, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); GjsAutoUnref js_context_ref(js_context, GjsAutoTakeOwnership()); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); return gjs->eval_module(identifier, exit_code, error); } bool gjs_context_register_module(GjsContext* js_context, const char* identifier, const char* uri, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); return gjs->register_module(identifier, uri, error); } bool GjsContextPrivate::auto_profile_enter() { bool auto_profile = m_should_profile; if (auto_profile && (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2)) auto_profile = false; Gjs::AutoMainRealm ar{this}; if (auto_profile) gjs_profiler_start(m_profiler); return auto_profile; } void GjsContextPrivate::auto_profile_exit(bool auto_profile) { if (auto_profile) gjs_profiler_stop(m_profiler); } bool GjsContextPrivate::handle_exit_code(bool no_sync_error_pending, const char* source_type, const char* identifier, uint8_t* exit_code, GError** error) { uint8_t code; if (should_exit(&code)) { /* exit_status_p is public API so can't be changed, but should be * uint8_t, not int */ g_set_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT, "Exit with code %d", code); *exit_code = code; return false; // Don't log anything } // Once the main loop exits an exception could // be pending even if the script returned // true synchronously if (JS_IsExceptionPending(m_cx)) { g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); gjs_log_exception_uncaught(m_cx); *exit_code = 1; return false; } if (m_unhandled_exception) { g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); *exit_code = 1; return false; } // Assume success if no error was thrown and should exit isn't // set if (no_sync_error_pending) { *exit_code = 0; return true; } g_critical("%s %s terminated with an uncatchable exception", source_type, identifier); g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "%s %s terminated with an uncatchable exception", source_type, identifier); gjs_log_exception_uncaught(m_cx); /* No exit code from script, but we don't want to exit(0) */ *exit_code = 1; return false; } bool GjsContextPrivate::set_main_loop_hook(JSObject* callable) { g_assert(JS::IsCallable(callable) && "main loop hook must be a callable object"); if (!callable) { m_main_loop_hook = nullptr; return true; } if (m_main_loop_hook) return false; m_main_loop_hook = callable; return true; } bool GjsContextPrivate::run_main_loop_hook() { JS::RootedObject hook(m_cx, m_main_loop_hook.get()); m_main_loop_hook = nullptr; gjs_debug(GJS_DEBUG_MAINLOOP, "Running and clearing main loop hook"); JS::RootedValue ignored_rval(m_cx); return JS::Call(m_cx, JS::NullHandleValue, hook, JS::HandleValueArray::empty(), &ignored_rval); } bool GjsContextPrivate::eval(const char* script, size_t script_len, const char* filename, int* exit_status_p, GError** error) { AutoResetExit reset(this); bool auto_profile = auto_profile_enter(); Gjs::AutoMainRealm ar{this}; JS::RootedValue retval(m_cx); bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval); // If there are no errors and the mainloop hook is set, call it. if (ok && m_main_loop_hook) ok = run_main_loop_hook(); bool exiting = false; // Spin the internal loop until the main loop hook is set or no holds // remain. // If the main loop returns false we cannot guarantee the state of our // promise queue (a module promise could be pending) so instead of draining // draining the queue we instead just exit. if (ok && !m_main_loop.spin(this)) { exiting = true; } // If the hook has been set again, enter a loop until an error is // encountered or the main loop is quit. while (ok && !exiting && m_main_loop_hook) { ok = run_main_loop_hook(); // Additional jobs could have been enqueued from the // main loop hook if (ok && !m_main_loop.spin(this)) { exiting = true; } } // The promise job queue should be drained even on error, to finish // outstanding async tasks before the context is torn down. Drain after // uncaught exceptions have been reported since draining runs callbacks. // We do not drain if we are exiting. if (!ok && !exiting) { JS::AutoSaveExceptionState saved_exc(m_cx); ok = run_jobs_fallible() && ok; } auto_profile_exit(auto_profile); uint8_t out_code; ok = handle_exit_code(ok, "Script", filename, &out_code, error); if (exit_status_p) { if (ok && retval.isInt32()) { int code = retval.toInt32(); gjs_debug(GJS_DEBUG_CONTEXT, "Script returned integer code %d", code); *exit_status_p = code; } else { *exit_status_p = out_code; } } return ok; } bool GjsContextPrivate::eval_module(const char* identifier, uint8_t* exit_status_p, GError** error) { AutoResetExit reset(this); bool auto_profile = auto_profile_enter(); Gjs::AutoMainRealm ar{this}; JS::RootedObject registry(m_cx, gjs_get_module_registry(m_global)); JS::RootedId key(m_cx, gjs_intern_string_to_id(m_cx, identifier)); JS::RootedObject obj(m_cx); if (!gjs_global_registry_get(m_cx, registry, key, &obj) || !obj) { g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "Cannot load module with identifier: '%s'", identifier); if (exit_status_p) *exit_status_p = 1; return false; } if (!JS::ModuleLink(m_cx, obj)) { gjs_log_exception(m_cx); g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "Failed to resolve imports for module: '%s'", identifier); if (exit_status_p) *exit_status_p = 1; return false; } JS::RootedValue evaluation_promise(m_cx); bool ok = JS::ModuleEvaluate(m_cx, obj, &evaluation_promise); if (ok) { GjsContextPrivate::from_cx(m_cx)->main_loop_hold(); ok = add_promise_reactions( m_cx, evaluation_promise, on_context_module_resolved, on_context_module_rejected_log_exception, identifier); } bool exiting = false; do { // If there are no errors and the mainloop hook is set, call it. if (ok && m_main_loop_hook) ok = run_main_loop_hook(); // Spin the internal loop until the main loop hook is set or no holds // remain. // // If the main loop returns false we cannot guarantee the state of our // promise queue (a module promise could be pending) so instead of // draining the queue we instead just exit. // // Additional jobs could have been enqueued from the // main loop hook if (ok && !m_main_loop.spin(this)) { exiting = true; } } while (ok && !exiting && m_main_loop_hook); // The promise job queue should be drained even on error, to finish // outstanding async tasks before the context is torn down. Drain after // uncaught exceptions have been reported since draining runs callbacks. // We do not drain if we are exiting. if (!ok && !exiting) { JS::AutoSaveExceptionState saved_exc(m_cx); ok = run_jobs_fallible() && ok; } auto_profile_exit(auto_profile); uint8_t out_code; ok = handle_exit_code(ok, "Module", identifier, &out_code, error); if (exit_status_p) *exit_status_p = out_code; return ok; } bool GjsContextPrivate::register_module(const char* identifier, const char* uri, GError** error) { Gjs::AutoMainRealm ar{this}; if (gjs_module_load(m_cx, identifier, uri)) return true; const char* msg = "unknown"; JS::ExceptionStack exn_stack(m_cx); JS::ErrorReportBuilder builder(m_cx); if (JS::StealPendingExceptionStack(m_cx, &exn_stack) && builder.init(m_cx, exn_stack, JS::ErrorReportBuilder::WithSideEffects)) { msg = builder.toStringResult().c_str(); } else { JS_ClearPendingException(m_cx); } g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "Failed to parse module '%s': %s", identifier, msg ? msg : "unknown"); return false; } bool gjs_context_eval_file(GjsContext *js_context, const char *filename, int *exit_status_p, GError **error) { GjsAutoChar script; size_t script_len; GjsAutoUnref file = g_file_new_for_commandline_arg(filename); if (!g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr, error)) return false; return gjs_context_eval(js_context, script, script_len, filename, exit_status_p, error); } bool gjs_context_eval_module_file(GjsContext* js_context, const char* filename, uint8_t* exit_status_p, GError** error) { GjsAutoUnref file = g_file_new_for_commandline_arg(filename); GjsAutoChar uri = g_file_get_uri(file); return gjs_context_register_module(js_context, uri, uri, error) && gjs_context_eval_module(js_context, uri, exit_status_p, error); } /* * GjsContextPrivate::eval_with_scope: * @scope_object: an object to use as the global scope, or nullptr * @source: JavaScript program encoded in UTF-8 * @source_len: length of @source, or -1 if @source is 0-terminated * @filename: filename to use as the origin of @source * @retval: location for the return value of @source * * Executes @source with a local scope so that nothing from the source code * leaks out into the global scope. * If @scope_object is given, then everything that @source placed in the global * namespace is defined on @scope_object. * Otherwise, the global definitions are just discarded. */ bool GjsContextPrivate::eval_with_scope(JS::HandleObject scope_object, const char* source, size_t source_len, const char* filename, JS::MutableHandleValue retval) { /* log and clear exception if it's set (should not be, normally...) */ if (JS_IsExceptionPending(m_cx)) { g_warning("eval_with_scope() called with a pending exception"); return false; } JS::RootedObject eval_obj(m_cx, scope_object); if (!eval_obj) eval_obj = JS_NewPlainObject(m_cx); JS::SourceText buf; if (!buf.init(m_cx, source, source_len, JS::SourceOwnership::Borrowed)) return false; JS::RootedObjectVector scope_chain(m_cx); if (!scope_chain.append(eval_obj)) { JS_ReportOutOfMemory(m_cx); return false; } JS::CompileOptions options(m_cx); options.setFileAndLine(filename, 1).setNonSyntacticScope(true); GjsAutoUnref file = g_file_new_for_commandline_arg(filename); GjsAutoChar uri = g_file_get_uri(file); JS::RootedObject priv(m_cx, gjs_script_module_build_private(m_cx, uri)); if (!priv) return false; JS::RootedScript script(m_cx); script.set(JS::Compile(m_cx, options, buf)); if (!script) return false; JS::SetScriptPrivate(script, JS::ObjectValue(*priv)); if (!JS_ExecuteScript(m_cx, scope_chain, script, retval)) return false; schedule_gc_if_needed(); if (JS_IsExceptionPending(m_cx)) { g_warning( "JS::Evaluate() returned true but exception was pending; " "did somebody call gjs_throw() without returning false?"); return false; } gjs_debug(GJS_DEBUG_CONTEXT, "Script evaluation succeeded"); return true; } /* * GjsContextPrivate::call_function: * @this_obj: Object to use as the 'this' for the function call * @func_val: Callable to call, as a JS value * @args: Arguments to pass to the callable * @rval: Location for the return value * * Use this instead of JS_CallFunctionValue(), because it schedules a GC if * one is needed. It's good practice to check if a GC should be run every time * we return from JS back into C++. */ bool GjsContextPrivate::call_function(JS::HandleObject this_obj, JS::HandleValue func_val, const JS::HandleValueArray& args, JS::MutableHandleValue rval) { if (!JS_CallFunctionValue(m_cx, this_obj, func_val, args, rval)) return false; schedule_gc_if_needed(); return true; } bool gjs_context_define_string_array(GjsContext *js_context, const char *array_name, gssize array_length, const char **array_values, GError **error) { g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); Gjs::AutoMainRealm ar{gjs}; std::vector strings; if (array_values) { if (array_length < 0) array_length = g_strv_length(const_cast(array_values)); strings = {array_values, array_values + array_length}; } // ARGV is a special case to preserve backwards compatibility. if (strcmp(array_name, "ARGV") == 0) { gjs->set_args(std::move(strings)); return true; } JS::RootedObject global_root(gjs->context(), gjs->global()); if (!gjs_define_string_array(gjs->context(), global_root, array_name, strings, JSPROP_READONLY | JSPROP_PERMANENT)) { gjs_log_exception(gjs->context()); g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED, "gjs_define_string_array() failed"); return false; } return true; } void gjs_context_set_argv(GjsContext* js_context, ssize_t array_length, const char** array_values) { g_return_if_fail(GJS_IS_CONTEXT(js_context)); GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context); std::vector args(array_values, array_values + array_length); gjs->set_args(std::move(args)); } static GjsContext *current_context; GjsContext * gjs_context_get_current (void) { return current_context; } void gjs_context_make_current (GjsContext *context) { g_assert (context == NULL || current_context == NULL); current_context = context; } namespace Gjs { /* * Gjs::AutoMainRealm: * @gjs: a #GjsContextPrivate * * Enters the realm of the "main global" for the context, and leaves when the * object is destructed at the end of the scope. The main global is the global * object under which all user JS code is executed. It is used as the root * object for the scope of modules loaded by GJS in this context. * * Only code in a different realm, such as the debugger code or the module * loader code, uses a different global object. */ AutoMainRealm::AutoMainRealm(GjsContextPrivate* gjs) : JSAutoRealm(gjs->context(), gjs->global()) {} AutoMainRealm::AutoMainRealm(JSContext* cx) : AutoMainRealm(static_cast(JS_GetContextPrivate(cx))) { } /* * Gjs::AutoInternalRealm: * @gjs: a #GjsContextPrivate * * Enters the realm of the "internal global" for the context, and leaves when * the object is destructed at the end of the scope. The internal global is only * used for executing the module loader code, to keep it separate from user * code. */ AutoInternalRealm::AutoInternalRealm(GjsContextPrivate* gjs) : JSAutoRealm(gjs->context(), gjs->internal_global()) {} AutoInternalRealm::AutoInternalRealm(JSContext* cx) : AutoInternalRealm( static_cast(JS_GetContextPrivate(cx))) {} } // namespace Gjs /** * gjs_context_get_profiler: * @self: the #GjsContext * * Returns the profiler's internal instance of #GjsProfiler for you to * customize, or %NULL if profiling is not enabled on this #GjsContext. * * Returns: (transfer none) (nullable): a #GjsProfiler */ GjsProfiler * gjs_context_get_profiler(GjsContext *self) { return GjsContextPrivate::from_object(self)->profiler(); } /** * gjs_get_js_version: * * Returns the underlying version of the JS engine. * * Returns: a string */ const char * gjs_get_js_version(void) { return JS_GetImplementationVersion(); } /** * gjs_context_run_in_realm: * @self: the #GjsContext * @func: (scope call): function to run * @user_data: (closure func): pointer to pass to @func * * Runs @func immediately, not asynchronously, after entering the JS context's * main realm. After @func completes, leaves the realm again. * * You only need this if you are using JSAPI calls from the SpiderMonkey API * directly. */ void gjs_context_run_in_realm(GjsContext* self, GjsContextInRealmFunc func, void* user_data) { g_return_if_fail(GJS_IS_CONTEXT(self)); g_return_if_fail(func); GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); Gjs::AutoMainRealm ar{gjs}; func(self, user_data); } cjs-128.1/cjs/context.h0000664000175000017500000001047515116312211013644 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2008 litl, LLC */ #ifndef GJS_CONTEXT_H_ #define GJS_CONTEXT_H_ #if !defined(INSIDE_GJS_H) && !defined(GJS_COMPILATION) # error "Only can be included directly." #endif #include /* IWYU pragma: keep */ #include #include /* for ssize_t */ #ifndef _WIN32 # include /* for siginfo_t */ #endif #include #include #include #include G_BEGIN_DECLS #define GJS_TYPE_CONTEXT (gjs_context_get_type ()) GJS_EXPORT GJS_USE G_DECLARE_FINAL_TYPE(GjsContext, gjs_context, GJS, CONTEXT, GObject); /* These class macros are not defined by G_DECLARE_FINAL_TYPE, but are kept for * backwards compatibility */ #define GJS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GJS_TYPE_CONTEXT, GjsContextClass)) #define GJS_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GJS_TYPE_CONTEXT)) #define GJS_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GJS_TYPE_CONTEXT, GjsContextClass)) typedef void (*GjsContextInRealmFunc)(GjsContext*, void*); GJS_EXPORT GJS_USE GjsContext* gjs_context_new(void); GJS_EXPORT GJS_USE GjsContext* gjs_context_new_with_search_path( char** search_path); GJS_EXPORT GJS_USE bool gjs_context_eval_file(GjsContext* js_context, const char* filename, int* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval_module_file(GjsContext* js_context, const char* filename, uint8_t* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval(GjsContext* js_context, const char* script, gssize script_len, const char* filename, int* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_register_module(GjsContext* context, const char* identifier, const char* uri, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval_module(GjsContext* context, const char* identifier, uint8_t* exit_code, GError** error); GJS_EXPORT GJS_USE bool gjs_context_define_string_array( GjsContext* js_context, const char* array_name, gssize array_length, const char** array_values, GError** error); GJS_EXPORT void gjs_context_set_argv(GjsContext* js_context, ssize_t array_length, const char** array_values); GJS_EXPORT GJS_USE GList* gjs_context_get_all(void); GJS_EXPORT GJS_USE GjsContext* gjs_context_get_current(void); GJS_EXPORT void gjs_context_make_current (GjsContext *js_context); GJS_EXPORT void* gjs_context_get_native_context (GjsContext *js_context); GJS_EXPORT void gjs_context_run_in_realm(GjsContext* gjs, GjsContextInRealmFunc func, void* user_data); GJS_EXPORT void gjs_context_print_stack_stderr (GjsContext *js_context); GJS_EXPORT void gjs_context_maybe_gc (GjsContext *context); GJS_EXPORT void gjs_context_gc (GjsContext *context); GJS_EXPORT GJS_USE GjsProfiler* gjs_context_get_profiler(GjsContext* self); GJS_EXPORT GJS_USE bool gjs_profiler_chain_signal(GjsContext* context, siginfo_t* info); GJS_EXPORT void gjs_dumpstack (void); GJS_EXPORT GJS_USE const char* gjs_get_js_version(void); GJS_EXPORT void gjs_context_setup_debugger_console(GjsContext* gjs); G_END_DECLS #endif /* GJS_CONTEXT_H_ */ cjs-128.1/cjs/coverage.cpp0000664000175000017500000004222315116312211014302 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2014 Endless Mobile, Inc. // SPDX-FileContributor: Authored By: Sam Spilsbury #include #include // for free, size_t #include // for strcmp, strlen #include #include #include #include // for JS_AddExtraGCRootsTracer, JS_Remove... #include #include #include #include #include #include // for UniqueChars #include #include // for EnableCodeCoverage #include // for JS_WrapObject #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/coverage.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" static bool s_coverage_enabled = false; struct _GjsCoverage { GObject parent; }; typedef struct { gchar **prefixes; GjsContext *context; JS::Heap global; GFile *output_dir; } GjsCoveragePrivate; G_DEFINE_TYPE_WITH_PRIVATE(GjsCoverage, gjs_coverage, G_TYPE_OBJECT) enum { PROP_COVERAGE_0, PROP_PREFIXES, PROP_CONTEXT, PROP_CACHE, PROP_OUTPUT_DIRECTORY, PROP_N }; static GParamSpec *properties[PROP_N] = { NULL, }; [[nodiscard]] static char* get_file_identifier(GFile* source_file) { char *path = g_file_get_path(source_file); if (!path) path = g_file_get_uri(source_file); return path; } [[nodiscard]] static bool write_source_file_header(GOutputStream* stream, GFile* source_file, GError** error) { GjsAutoChar path = get_file_identifier(source_file); return g_output_stream_printf(stream, NULL, NULL, error, "SF:%s\n", path.get()); } [[nodiscard]] static bool copy_source_file_to_coverage_output( GFile* source_file, GFile* destination_file, GError** error) { /* We need to recursively make the directory we * want to copy to, as g_file_copy doesn't do that */ GjsAutoUnref destination_dir = g_file_get_parent(destination_file); if (!g_file_make_directory_with_parents(destination_dir, NULL, error)) { if (!g_error_matches(*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) return false; g_clear_error(error); } return g_file_copy(source_file, destination_file, G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, error); } /* This function will strip a URI scheme and return * the string with the URI scheme stripped or NULL * if the path was not a valid URI */ [[nodiscard]] static char* strip_uri_scheme(const char* potential_uri) { char *uri_header = g_uri_parse_scheme(potential_uri); if (uri_header) { gsize offset = strlen(uri_header); g_free(uri_header); /* g_uri_parse_scheme only parses the name * of the scheme, we also need to strip the * characters ':///' */ return g_strdup(potential_uri + offset + 4); } return NULL; } /* This function will return a string of pathname * components from the first directory indicating * where two directories diverge. For instance: * * child: /a/b/c/d/e * parent: /a/b/d/ * * Will return: c/d/e * * If the directories are not at all similar then * the full dirname of the child_path effectively * be returned. * * As a special case, child paths that are a URI * automatically return the full URI path with * the URI scheme and leading slash stripped out. */ [[nodiscard]] static char* find_diverging_child_components(GFile* child, GFile* parent) { GjsAutoUnref ancestor(parent, GjsAutoTakeOwnership()); while (ancestor) { char *relpath = g_file_get_relative_path(ancestor, child); if (relpath) return relpath; ancestor = g_file_get_parent(ancestor); } /* This is a special case of getting the URI below. The difference is that * this gives you a regular path name; getting it through the URI would * give a URI-encoded path (%20 for spaces, etc.) */ GjsAutoUnref root = g_file_new_for_path("/"); char* child_path = g_file_get_relative_path(root, child); if (child_path) return child_path; GjsAutoChar child_uri = g_file_get_uri(child); return strip_uri_scheme(child_uri); } [[nodiscard]] static bool filename_has_coverage_prefixes(GjsCoverage* self, const char* filename) { auto priv = static_cast(gjs_coverage_get_instance_private(self)); GjsAutoChar workdir = g_get_current_dir(); GjsAutoChar abs_filename = g_canonicalize_filename(filename, workdir); for (const char * const *prefix = priv->prefixes; *prefix; prefix++) { GjsAutoChar abs_prefix = g_canonicalize_filename(*prefix, workdir); if (g_str_has_prefix(abs_filename, abs_prefix)) return true; } return false; } static inline bool write_line(GOutputStream *out, const char *line, GError **error) { return g_output_stream_printf(out, nullptr, nullptr, error, "%s\n", line); } [[nodiscard]] static GjsAutoUnref write_statistics_internal( GjsCoverage* coverage, JSContext* cx, GError** error) { if (!s_coverage_enabled) { g_critical( "Code coverage requested, but gjs_coverage_enable() was not called." " You must call this function before creating any GjsContext."); return nullptr; } GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); /* Create output directory if it doesn't exist */ if (!g_file_make_directory_with_parents(priv->output_dir, nullptr, error)) { if (!g_error_matches(*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) return nullptr; g_clear_error(error); } GFile *output_file = g_file_get_child(priv->output_dir, "coverage.lcov"); size_t lcov_length; JS::UniqueChars lcov = js::GetCodeCoverageSummary(cx, &lcov_length); GjsAutoUnref ostream = G_OUTPUT_STREAM(g_file_append_to(output_file, G_FILE_CREATE_NONE, NULL, error)); if (!ostream) return nullptr; GjsAutoStrv lcov_lines = g_strsplit(lcov.get(), "\n", -1); const char* test_name = NULL; bool ignoring_file = false; for (const char * const *iter = lcov_lines.get(); *iter; iter++) { if (ignoring_file) { if (strcmp(*iter, "end_of_record") == 0) ignoring_file = false; continue; } if (g_str_has_prefix(*iter, "TN:")) { /* Don't write the test name if the next line shows we are * ignoring the source file */ test_name = *iter; continue; } else if (g_str_has_prefix(*iter, "SF:")) { const char *filename = *iter + 3; if (!filename_has_coverage_prefixes(coverage, filename)) { ignoring_file = true; continue; } /* Now we can write the test name before writing the source file */ if (!write_line(ostream, test_name, error)) return nullptr; /* The source file could be a resource, so we must use * g_file_new_for_commandline_arg() to disambiguate between URIs and * filesystem paths. */ GjsAutoUnref source_file = g_file_new_for_commandline_arg(filename); GjsAutoChar diverged_paths = find_diverging_child_components(source_file, priv->output_dir); GjsAutoUnref destination_file = g_file_resolve_relative_path(priv->output_dir, diverged_paths); if (!copy_source_file_to_coverage_output(source_file, destination_file, error)) return nullptr; /* Rewrite the source file path to be relative to the output * dir so that genhtml will find it */ if (!write_source_file_header(ostream, destination_file, error)) return nullptr; continue; } if (!write_line(ostream, *iter, error)) return nullptr; } return output_file; } /** * gjs_coverage_write_statistics: * @coverage: A #GjsCoverage * @output_directory: A directory to write coverage information to. * * Scripts which were provided as part of the #GjsCoverage:prefixes * construction property will be written out to @output_directory, in the same * directory structure relative to the source dir where the tests were run. * * This function takes all available statistics and writes them out to either * the file provided or to files of the pattern (filename).info in the same * directory as the scanned files. It will provide coverage data for all files * ending with ".js" in the coverage directories. */ void gjs_coverage_write_statistics(GjsCoverage *coverage) { auto priv = static_cast(gjs_coverage_get_instance_private(coverage)); GjsAutoError error; auto cx = static_cast(gjs_context_get_native_context(priv->context)); Gjs::AutoMainRealm ar{cx}; GjsAutoUnref output_file = write_statistics_internal(coverage, cx, &error); if (!output_file) { g_critical("Error writing coverage data: %s", error->message); return; } GjsAutoChar output_file_path = g_file_get_path(output_file); g_message("Wrote coverage statistics to %s", output_file_path.get()); } static void gjs_coverage_init(GjsCoverage*) { if (!s_coverage_enabled) g_critical( "Code coverage requested, but gjs_coverage_enable() was not called." " You must call this function before creating any GjsContext."); } static void coverage_tracer(JSTracer *trc, void *data) { GjsCoverage *coverage = (GjsCoverage *) data; GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); JS::TraceEdge(trc, &priv->global, "Coverage global object"); } GJS_JSAPI_RETURN_CONVENTION static bool bootstrap_coverage(GjsCoverage *coverage) { GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); auto* gjs = GjsContextPrivate::from_object(priv->context); JSContext* context = gjs->context(); JS::RootedObject debugger_global( context, gjs_create_global_object(context, GjsGlobalType::DEBUGGER)); { JSAutoRealm ar(context, debugger_global); JS::RootedObject debuggee{context, gjs->global()}; if (!JS_WrapObject(context, &debuggee)) return false; JS::RootedValue v_debuggee{context, JS::ObjectValue(*debuggee)}; if (!JS_SetPropertyById(context, debugger_global, gjs->atoms().debuggee(), v_debuggee) || !gjs_define_global_properties(context, debugger_global, GjsGlobalType::DEBUGGER, "GJS coverage", "coverage")) return false; /* Add a tracer, as suggested by jdm on #jsapi */ JS_AddExtraGCRootsTracer(context, coverage_tracer, coverage); priv->global = debugger_global; } return true; } static void gjs_coverage_constructed(GObject *object) { G_OBJECT_CLASS(gjs_coverage_parent_class)->constructed(object); GjsCoverage *coverage = GJS_COVERAGE(object); GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); new (&priv->global) JS::Heap(); if (!bootstrap_coverage(coverage)) { JSContext *context = static_cast(gjs_context_get_native_context(priv->context)); Gjs::AutoMainRealm ar{context}; gjs_log_exception(context); } } static void gjs_coverage_set_property(GObject *object, unsigned int prop_id, const GValue *value, GParamSpec *pspec) { GjsCoverage *coverage = GJS_COVERAGE(object); GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); switch (prop_id) { case PROP_PREFIXES: g_assert(priv->prefixes == NULL); priv->prefixes = (char **) g_value_dup_boxed (value); break; case PROP_CONTEXT: priv->context = GJS_CONTEXT(g_value_dup_object(value)); break; case PROP_CACHE: break; case PROP_OUTPUT_DIRECTORY: priv->output_dir = G_FILE(g_value_dup_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gjs_coverage_dispose(GObject *object) { GjsCoverage *coverage = GJS_COVERAGE(object); GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); /* Decomission objects inside of the JSContext before * disposing of the context */ auto cx = static_cast(gjs_context_get_native_context(priv->context)); JS_RemoveExtraGCRootsTracer(cx, coverage_tracer, coverage); priv->global = nullptr; g_clear_object(&priv->context); G_OBJECT_CLASS(gjs_coverage_parent_class)->dispose(object); } static void gjs_coverage_finalize (GObject *object) { GjsCoverage *coverage = GJS_COVERAGE(object); GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage); g_strfreev(priv->prefixes); g_clear_object(&priv->output_dir); priv->global.~Heap(); G_OBJECT_CLASS(gjs_coverage_parent_class)->finalize(object); } static void gjs_coverage_class_init (GjsCoverageClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; object_class->constructed = gjs_coverage_constructed; object_class->dispose = gjs_coverage_dispose; object_class->finalize = gjs_coverage_finalize; object_class->set_property = gjs_coverage_set_property; properties[PROP_PREFIXES] = g_param_spec_boxed("prefixes", "Prefixes", "Prefixes of files on which to perform coverage analysis", G_TYPE_STRV, (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); properties[PROP_CONTEXT] = g_param_spec_object("context", "Context", "A context to gather coverage stats for", GJS_TYPE_CONTEXT, (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); properties[PROP_CACHE] = g_param_spec_object("cache", "Deprecated property", "Has no effect", G_TYPE_FILE, (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_DEPRECATED)); properties[PROP_OUTPUT_DIRECTORY] = g_param_spec_object("output-directory", "Output directory", "Directory handle at which to output coverage statistics", G_TYPE_FILE, (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties(object_class, PROP_N, properties); } /** * gjs_coverage_new: * @prefixes: A null-terminated strv of prefixes of files on which to record * code coverage * @context: A #GjsContext object * @output_dir: A #GFile handle to a directory in which to write coverage * information * * Creates a new #GjsCoverage object that collects coverage information for * any scripts run in @context. * * Scripts which were provided as part of @prefixes will be written out to * @output_dir, in the same directory structure relative to the source dir where * the tests were run. * * Returns: A #GjsCoverage object */ GjsCoverage * gjs_coverage_new (const char * const *prefixes, GjsContext *context, GFile *output_dir) { GjsCoverage *coverage = GJS_COVERAGE(g_object_new(GJS_TYPE_COVERAGE, "prefixes", prefixes, "context", context, "output-directory", output_dir, NULL)); return coverage; } /** * gjs_coverage_enable: * * This function must be called before creating any #GjsContext, if you intend * to use any #GjsCoverage APIs. * * Since: 1.66 */ void gjs_coverage_enable() { js::EnableCodeCoverage(); s_coverage_enabled = true; } cjs-128.1/cjs/coverage.h0000664000175000017500000000176315116312211013753 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2014 Endless Mobile, Inc. * SPDX-FileContributor: Authored By: Sam Spilsbury */ #ifndef GJS_COVERAGE_H_ #define GJS_COVERAGE_H_ #if !defined(INSIDE_GJS_H) && !defined(GJS_COMPILATION) # error "Only can be included directly." #endif #include #include #include /* for G_BEGIN_DECLS, G_END_DECLS */ #include #include G_BEGIN_DECLS #define GJS_TYPE_COVERAGE gjs_coverage_get_type() G_DECLARE_FINAL_TYPE(GjsCoverage, gjs_coverage, GJS, COVERAGE, GObject); GJS_EXPORT void gjs_coverage_enable(void); GJS_EXPORT void gjs_coverage_write_statistics(GjsCoverage *self); GJS_EXPORT GJS_USE GjsCoverage* gjs_coverage_new( const char* const* coverage_prefixes, GjsContext* coverage_context, GFile* output_dir); G_END_DECLS #endif /* GJS_COVERAGE_H_ */ cjs-128.1/cjs/debugger.cpp0000664000175000017500000001006315116312211014270 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento // SPDX-FileContributor: Philip Chimento #include // for HAVE_READLINE_READLINE_H, HAVE_UNISTD_H #include #include // for feof, fflush, fgets, stdin, stdout #ifdef HAVE_READLINE_READLINE_H # include # include #endif #include #include #include #include #include #include #include #include #include // for UniqueChars #include #include // for JS_WrapObject #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/global.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/console.h" GJS_JSAPI_RETURN_CONVENTION static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); int32_t exitcode; if (!gjs_parse_call_args(cx, "quit", args, "i", "exitcode", &exitcode)) return false; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); gjs->exit(exitcode); return false; // without gjs_throw() == "throw uncatchable exception" } GJS_JSAPI_RETURN_CONVENTION static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars prompt; if (!gjs_parse_call_args(cx, "readline", args, "|s", "prompt", &prompt)) return false; GjsAutoChar line; do { const char* real_prompt = prompt ? prompt.get() : "db> "; #ifdef HAVE_READLINE_READLINE_H if (gjs_console_is_tty(stdin_fd)) { line = readline(real_prompt); } else { #else { #endif // HAVE_READLINE_READLINE_H char buf[256]; g_print("%s", real_prompt); fflush(stdout); if (!fgets(buf, sizeof buf, stdin)) buf[0] = '\0'; line.reset(g_strdup(g_strchomp(buf))); if (!gjs_console_is_tty(stdin_fd)) { if (feof(stdin)) { g_print("[quit due to end of input]\n"); line.reset(g_strdup("quit")); } else { g_print("%s\n", line.get()); } } } /* EOF, return null */ if (!line) { args.rval().setNull(); return true; } } while (line && line.get()[0] == '\0'); /* Add line to history and convert it to a JSString so that we can pass it * back as the return value */ #ifdef HAVE_READLINE_READLINE_H add_history(line); #endif args.rval().setString(JS_NewStringCopyZ(cx, line)); return true; } // clang-format off static JSFunctionSpec debugger_funcs[] = { JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS), JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS), JS_FS_END }; // clang-format on void gjs_context_setup_debugger_console(GjsContext* self) { auto* gjs = GjsContextPrivate::from_object(self); JSContext* cx = gjs->context(); JS::RootedObject debugger_global( cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER)); // Enter realm of the debugger and initialize it with the debuggee JSAutoRealm ar(cx, debugger_global); JS::RootedObject debuggee{cx, gjs->global()}; if (!JS_WrapObject(cx, &debuggee)) { gjs_log_exception(cx); return; } JS::RootedValue v_debuggee(cx, JS::ObjectValue(*debuggee)); if (!JS_SetPropertyById(cx, debugger_global, gjs->atoms().debuggee(), v_debuggee) || !JS_DefineFunctions(cx, debugger_global, debugger_funcs) || !gjs_define_global_properties(cx, debugger_global, GjsGlobalType::DEBUGGER, "GJS debugger", "debugger")) gjs_log_exception(cx); } cjs-128.1/cjs/deprecation.cpp0000664000175000017500000001277015116312211015010 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento #include #include // for size_t #include // for hash #include #include // for string #include #include // for unordered_set #include // for move #include #include // for g_warning #include #include #include #include // for CaptureCurrentStack, MaxFrames #include #include // for UniqueChars #include #include #include "cjs/deprecation.h" #include "cjs/macros.h" const char* messages[] = { // None: "(invalid message)", // ByteArrayInstanceToString: "Some code called array.toString() on a Uint8Array instance. Previously " "this would have interpreted the bytes of the array as a string, but that " "is nonstandard. In the future this will return the bytes as " "comma-separated digits. For the time being, the old behavior has been " "preserved, but please fix your code anyway to use TextDecoder.\n" "(Note that array.toString() may have been called implicitly.)", // DeprecatedGObjectProperty: "The GObject property {}.{} is deprecated.", // ModuleExportedLetOrConst: "Some code accessed the property '{}' on the module '{}'. That property " "was defined with 'let' or 'const' inside the module. This was previously " "supported, but is not correct according to the ES6 standard. Any symbols " "to be exported from a module must be defined with 'var'. The property " "access will work as previously for the time being, but please fix your " "code anyway.", // PlatformSpecificTypelib: ("{} has been moved to a separate platform-specific library. Please update " "your code to use {} instead."), }; static_assert(G_N_ELEMENTS(messages) == GjsDeprecationMessageId::LastValue); struct DeprecationEntry { GjsDeprecationMessageId id; std::string loc; DeprecationEntry(GjsDeprecationMessageId an_id, const char* a_loc) : id(an_id), loc(a_loc) {} bool operator==(const DeprecationEntry& other) const { return id == other.id && loc == other.loc; } }; namespace std { template <> struct hash { size_t operator()(const DeprecationEntry& key) const { return hash()(key.id) ^ hash()(key.loc); } }; }; // namespace std static std::unordered_set logged_messages; GJS_JSAPI_RETURN_CONVENTION static JS::UniqueChars get_callsite(JSContext* cx, unsigned max_frames) { JS::RootedObject stack_frame(cx); if (!JS::CaptureCurrentStack(cx, &stack_frame, JS::StackCapture(JS::MaxFrames(max_frames))) || !stack_frame) return nullptr; JS::RootedValue v_frame(cx, JS::ObjectValue(*stack_frame)); JS::RootedString frame_string(cx, JS::ToString(cx, v_frame)); if (!frame_string) return nullptr; return JS_EncodeStringToUTF8(cx, frame_string); } static void warn_deprecated_unsafe_internal(JSContext* cx, const GjsDeprecationMessageId id, const char* msg, unsigned max_frames) { JS::UniqueChars callsite(get_callsite(cx, max_frames)); DeprecationEntry entry(id, callsite.get()); if (!logged_messages.count(entry)) { JS::UniqueChars stack_dump = JS::FormatStackDump(cx, false, false, false); g_warning("%s\n%s", msg, stack_dump.get()); logged_messages.insert(std::move(entry)); } } /* Note, this can only be called from the JS thread because it uses the full * stack dump API and not the "safe" gjs_dumpstack() which can only print to * stdout or stderr. Do not use this function during GC, for example. */ void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, const GjsDeprecationMessageId id, unsigned max_frames) { warn_deprecated_unsafe_internal(cx, id, messages[id], max_frames); } void _gjs_warn_deprecated_once_per_callsite( JSContext* cx, GjsDeprecationMessageId id, const std::vector& args, unsigned max_frames) { // In C++20, use std::format() for this std::string_view format_string{messages[id]}; std::stringstream message; size_t pos = 0; size_t copied = 0; size_t args_ptr = 0; size_t nargs_given = args.size(); while ((pos = format_string.find("{}", pos)) != std::string::npos) { if (args_ptr >= nargs_given) { g_critical("Only %zu format args passed for message ID %u", nargs_given, unsigned{id}); return; } message << format_string.substr(copied, pos - copied); message << args[args_ptr++]; pos = copied = pos + 2; // skip over braces } if (args_ptr != nargs_given) { g_critical("Excess %zu format args passed for message ID %u", nargs_given, unsigned{id}); return; } message << format_string.substr(copied, std::string::npos); std::string message_formatted = message.str(); warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str(), max_frames); } cjs-128.1/cjs/deprecation.h0000664000175000017500000000165515116312211014455 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento #ifndef GJS_DEPRECATION_H_ #define GJS_DEPRECATION_H_ #include #include struct JSContext; enum GjsDeprecationMessageId : unsigned { None, ByteArrayInstanceToString, DeprecatedGObjectProperty, ModuleExportedLetOrConst, PlatformSpecificTypelib, LastValue, // insert new elements before this one }; void _gjs_warn_deprecated_once_per_callsite(JSContext* cx, GjsDeprecationMessageId message, unsigned max_frames = 1); void _gjs_warn_deprecated_once_per_callsite( JSContext* cx, GjsDeprecationMessageId id, const std::vector& args, unsigned max_frames = 1); #endif // GJS_DEPRECATION_H_ cjs-128.1/cjs/engine.cpp0000664000175000017500000002056515116312211013761 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna #include #include #ifdef _WIN32 # include #endif #include // for move #include #include #include #include #include // for JS_SetGCParameter, JS_AddFin... #include // for JS_Init, JS_ShutDown #include #include #include #include // for JS_SetNativeStackQuota #include // for JS_WriteUint32Pair #include #include // for UniqueChars #include #include #include // for JS_SetGlobalJitCompilerOption #include // for Atomic in JSPrincipals #include #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/jsapi-util.h" #include "cjs/profiler-private.h" #include "util/log.h" struct JSStructuredCloneWriter; static void gjs_finalize_callback(JS::GCContext*, JSFinalizeStatus status, void* data) { auto* gjs = static_cast(data); if (gjs->profiler()) _gjs_profiler_set_finalize_status(gjs->profiler(), status); } static void on_promise_unhandled_rejection( JSContext* cx, bool mutedErrors [[maybe_unused]], JS::HandleObject promise, JS::PromiseRejectionHandlingState state, void* data) { auto gjs = static_cast(data); uint64_t id = JS::GetPromiseID(promise); if (state == JS::PromiseRejectionHandlingState::Handled) { /* This happens when catching an exception from an await expression. */ gjs->unregister_unhandled_promise_rejection(id); return; } JS::RootedObject allocation_site(cx, JS::GetPromiseAllocationSite(promise)); JS::UniqueChars stack = format_saved_frame(cx, allocation_site); gjs->register_unhandled_promise_rejection(id, std::move(stack)); } static void on_cleanup_finalization_registry(JSFunction* cleanup_task, JSObject* incumbent_global [[maybe_unused]], void* data) { auto* gjs = static_cast(data); if (!gjs->queue_finalization_registry_cleanup(cleanup_task)) g_critical("Out of memory queueing FinalizationRegistry cleanup task"); } bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src, size_t* length) { GjsAutoError error; const char* path = filename + 11; // len("resource://") GBytes* script_bytes = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); if (!script_bytes) return gjs_throw_gerror_message(cx, error); *src = static_cast(g_bytes_unref_to_data(script_bytes, length)); return true; } class GjsSourceHook : public js::SourceHook { bool load(JSContext* cx, const char* filename, char16_t** two_byte_source [[maybe_unused]], char** utf8_source, size_t* length) { // caller owns the source, per documentation of SourceHook return gjs_load_internal_source(cx, filename, utf8_source, length); } }; #ifdef G_OS_WIN32 HMODULE gjs_dll; static bool gjs_is_inited = false; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: { gjs_dll = hinstDLL; const char* reason = JS_InitWithFailureDiagnostic(); if (reason) g_error("Could not initialize JavaScript: %s", reason); gjs_is_inited = true; } break; case DLL_THREAD_DETACH: JS_ShutDown(); break; default: /* do nothing */ ; } return TRUE; } #else class GjsInit { public: GjsInit() { const char* reason = JS_InitWithFailureDiagnostic(); if (reason) g_error("Could not initialize JavaScript: %s", reason); } ~GjsInit() { JS_ShutDown(); } explicit operator bool() const { return true; } }; static GjsInit gjs_is_inited; #endif // JSPrincipals (basically a weird name for security callbacks) which are in // effect in the module loader's realm (GjsInternalGlobal). This prevents module // loader stack frames from showing up in public stack traces. class ModuleLoaderPrincipals final : public JSPrincipals { static constexpr uint32_t STRUCTURED_CLONE_TAG = JS_SCTAG_USER_MIN; bool write(JSContext* cx [[maybe_unused]], JSStructuredCloneWriter* writer) override { g_assert_not_reached(); return JS_WriteUint32Pair(writer, STRUCTURED_CLONE_TAG, 1); } bool isSystemOrAddonPrincipal() override { return true; } public: static bool subsumes(JSPrincipals* first, JSPrincipals* second) { if (first != &the_principals && second == &the_principals) return false; return true; } static void destroy(JSPrincipals* principals [[maybe_unused]]) { g_assert(principals == &the_principals && "Should not create other instances of ModuleLoaderPrinciples"); g_assert(principals->refcount == 0 && "Mismatched JS_HoldPrincipals/JS_DropPrincipals"); } // Singleton static ModuleLoaderPrincipals the_principals; }; ModuleLoaderPrincipals ModuleLoaderPrincipals::the_principals{}; JSPrincipals* get_internal_principals() { return &ModuleLoaderPrincipals::the_principals; } static const JSSecurityCallbacks security_callbacks = { /* contentSecurityPolicyAllows = */ nullptr, &ModuleLoaderPrincipals::subsumes, }; JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) { g_assert(gjs_is_inited); JSContext *cx = JS_NewContext(32 * 1024 * 1024 /* max bytes */); if (!cx) return nullptr; if (!JS::InitSelfHostedCode(cx)) { JS_DestroyContext(cx); return nullptr; } // For additional context on these options, see // https://searchfox.org/mozilla-esr91/rev/c49725508e97c1e2e2bb3bf9ed0ba14b2016abac/js/public/GCAPI.h#53 JS_SetNativeStackQuota(cx, 1024 * 1024); JS_SetGCParameter(cx, JSGC_MAX_BYTES, -1); JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, 1); JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10); /* ms */ /* set ourselves as the private data */ JS_SetContextPrivate(cx, uninitialized_gjs); JS_SetSecurityCallbacks(cx, &security_callbacks); JS_InitDestroyPrincipalsCallback(cx, &ModuleLoaderPrincipals::destroy); JS_AddFinalizeCallback(cx, gjs_finalize_callback, uninitialized_gjs); JS::SetWarningReporter(cx, gjs_warning_reporter); JS::SetJobQueue(cx, dynamic_cast(uninitialized_gjs)); JS::SetPromiseRejectionTrackerCallback(cx, on_promise_unhandled_rejection, uninitialized_gjs); JS::SetHostCleanupFinalizationRegistryCallback( cx, on_cleanup_finalization_registry, uninitialized_gjs); // We use this to handle "lazy sources" that SpiderMonkey doesn't need to // keep in memory. Most sources should be kept in memory, but we can skip // doing that for the realm bootstrap code, as it is already in memory in // the form of a GResource. Instead we use the "source hook" to retrieve it. auto hook = mozilla::MakeUnique(); js::SetSourceHook(cx, std::move(hook)); if (g_getenv("GJS_DISABLE_EXTRA_WARNINGS")) { g_warning( "GJS_DISABLE_EXTRA_WARNINGS has been removed, GJS no longer logs " "extra warnings."); } bool enable_jit = !(g_getenv("GJS_DISABLE_JIT")); if (enable_jit) { gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT"); } JS::ContextOptionsRef(cx).setAsmJS(enable_jit); uint32_t value = enable_jit ? 1 : 0; JS_SetGlobalJitCompilerOption( cx, JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, value); JS_SetGlobalJitCompilerOption( cx, JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, value); JS_SetGlobalJitCompilerOption( cx, JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, value); return cx; } cjs-128.1/cjs/engine.h0000664000175000017500000000117115116312211013416 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna #ifndef GJS_ENGINE_H_ #define GJS_ENGINE_H_ #include #include // for size_t class GjsContextPrivate; struct JSContext; struct JSPrincipals; JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs); bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src, size_t* length); JSPrincipals* get_internal_principals(); #endif // GJS_ENGINE_H_ cjs-128.1/cjs/enum-utils.h0000664000175000017500000000620215116312211014253 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include namespace GjsEnum { template constexpr bool is_class() { if constexpr (std::is_enum_v) { return !std::is_convertible_v>; } return false; } template struct WrapperImpl { EnumType e; constexpr explicit WrapperImpl(EnumType const& en) : e(en) {} constexpr explicit WrapperImpl(std::underlying_type_t const& en) : e(static_cast(en)) {} constexpr explicit operator bool() const { return static_cast(e); } constexpr operator EnumType() const { return e; } constexpr operator std::underlying_type_t() const { return std::underlying_type_t(e); } }; #if defined (__clang__) || defined (__GNUC__) template using Wrapper = std::conditional_t(), WrapperImpl, void>; #else template using Wrapper = std::conditional_t(), std::underlying_type_t, void>; #endif } // namespace GjsEnum template > constexpr std::enable_if_t(), Wrapped> operator&( EnumType const& first, EnumType const& second) { return static_cast(static_cast(first) & static_cast(second)); } template > constexpr std::enable_if_t(), Wrapped> operator|( EnumType const& first, EnumType const& second) { return static_cast(static_cast(first) | static_cast(second)); } template > constexpr std::enable_if_t(), Wrapped> operator^( EnumType const& first, EnumType const& second) { return static_cast(static_cast(first) ^ static_cast(second)); } template > constexpr std::enable_if_t(), Wrapped&> operator|=( EnumType& first, // NOLINT(runtime/references) EnumType const& second) { first = static_cast(first | second); return reinterpret_cast(first); } template > constexpr std::enable_if_t(), Wrapped&> operator&=( EnumType& first, // NOLINT(runtime/references) EnumType const& second) { first = static_cast(first & second); return reinterpret_cast(first); } template > constexpr std::enable_if_t(), EnumType> operator~( EnumType const& first) { return static_cast(~static_cast(first)); } cjs-128.1/cjs/error-types.cpp0000664000175000017500000000233615116312211015003 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include "cjs/error-types.h" // clang-format off G_DEFINE_QUARK(gjs-error-quark, gjs_error) G_DEFINE_QUARK(gjs-js-error-quark, gjs_js_error) // clang-format on GType gjs_js_error_get_type(void) { static const GEnumValue errors[] = { {GJS_JS_ERROR_ERROR, "Error", "error"}, {GJS_JS_ERROR_EVAL_ERROR, "EvalError", "eval-error"}, {GJS_JS_ERROR_INTERNAL_ERROR, "InternalError", "internal-error"}, {GJS_JS_ERROR_RANGE_ERROR, "RangeError", "range-error"}, {GJS_JS_ERROR_REFERENCE_ERROR, "ReferenceError", "reference-error"}, {GJS_JS_ERROR_STOP_ITERATION, "StopIteration", "stop-iteration"}, {GJS_JS_ERROR_SYNTAX_ERROR, "SyntaxError", "syntax-error"}, {GJS_JS_ERROR_TYPE_ERROR, "TypeError", "type-error"}, {GJS_JS_ERROR_URI_ERROR, "URIError", "uri-error"}, {0, nullptr, nullptr}}; // Initialization of static local variable guaranteed only once in C++11 static GType g_type_id = g_enum_register_static("GjsJSError", errors); return g_type_id; } cjs-128.1/cjs/error-types.h0000664000175000017500000000213215116312211014442 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2008 litl, LLC */ #ifndef GJS_ERROR_TYPES_H_ #define GJS_ERROR_TYPES_H_ #if !defined(INSIDE_GJS_H) && !defined(GJS_COMPILATION) # error "Only can be included directly." #endif #include #include #include G_BEGIN_DECLS GJS_EXPORT GQuark gjs_error_quark(void); #define GJS_ERROR gjs_error_quark() typedef enum { GJS_ERROR_FAILED, GJS_ERROR_SYSTEM_EXIT, } GjsError; GJS_EXPORT GQuark gjs_js_error_quark(void); #define GJS_JS_ERROR gjs_js_error_quark() GJS_EXPORT GType gjs_js_error_get_type(void); #define GJS_TYPE_JS_ERROR gjs_js_error_get_type() typedef enum { GJS_JS_ERROR_ERROR, GJS_JS_ERROR_EVAL_ERROR, GJS_JS_ERROR_INTERNAL_ERROR, GJS_JS_ERROR_RANGE_ERROR, GJS_JS_ERROR_REFERENCE_ERROR, GJS_JS_ERROR_STOP_ITERATION, GJS_JS_ERROR_SYNTAX_ERROR, GJS_JS_ERROR_TYPE_ERROR, GJS_JS_ERROR_URI_ERROR, } GjsJSError; G_END_DECLS #endif /* GJS_ERROR_TYPES_H_ */ cjs-128.1/cjs/gjs.h0000664000175000017500000000065215116312211012737 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2008 litl, LLC */ #ifndef GJS_GJS_H_ #define GJS_GJS_H_ #define INSIDE_GJS_H #include #include #include #include #include #include #undef INSIDE_GJS_H #endif /* GJS_GJS_H_ */ cjs-128.1/cjs/global.cpp0000664000175000017500000004772315116312211013761 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2009 Red Hat, Inc. // SPDX-FileCopyrightText: 2017 Philip Chimento // SPDX-FileCopyrightText: 2020 Evan Welsh #include #include // for size_t #include #include // for CallArgs, CallArgsFromVp #include // for JS_EncodeStringToUTF8 #include #include #include #include // for JS_DefineDebuggerObject #include // for CurrentGlobalOrNull, JS_NewGlobalObject #include #include #include #include #include // for JSPROP_PERMANENT, JSPROP_RE... #include #include // for GetObjectRealmOrNull, SetRealmPrivate #include #include #include #include #include // for UniqueChars #include // for JS_IdToValue, JS_InitReflectParse #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/global.h" #include "cjs/internal.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/native.h" namespace mozilla { union Utf8Unit; } class GjsBaseGlobal { GJS_JSAPI_RETURN_CONVENTION static JSObject* base(JSContext* cx, const JSClass* clasp, JS::RealmCreationOptions options, JSPrincipals* principals = nullptr) { JS::RealmBehaviors behaviors; JS::RealmOptions compartment_options(options, behaviors); JS::RootedObject global{cx, JS_NewGlobalObject(cx, clasp, principals, JS::FireOnNewGlobalHook, compartment_options)}; if (!global) return nullptr; JSAutoRealm ac(cx, global); if (!JS_InitReflectParse(cx, global) || !JS_DefineDebuggerObject(cx, global)) return nullptr; return global; } protected: GJS_JSAPI_RETURN_CONVENTION static JSObject* create( JSContext* cx, const JSClass* clasp, JS::RealmCreationOptions options = JS::RealmCreationOptions(), JSPrincipals* principals = nullptr) { options.setNewCompartmentAndZone(); return base(cx, clasp, options, principals); } GJS_JSAPI_RETURN_CONVENTION static JSObject* create_with_compartment( JSContext* cx, JS::HandleObject existing, const JSClass* clasp, JS::RealmCreationOptions options = JS::RealmCreationOptions(), JSPrincipals* principals = nullptr) { options.setExistingCompartment(existing); return base(cx, clasp, options, principals); } GJS_JSAPI_RETURN_CONVENTION static bool run_bootstrap(JSContext* cx, const char* bootstrap_script, JS::HandleObject global) { GjsAutoChar uri = g_strdup_printf( "resource:///org/cinnamon/cjs/modules/script/_bootstrap/%s.js", bootstrap_script); JSAutoRealm ar(cx, global); JS::CompileOptions options(cx); options.setFileAndLine(uri, 1).setSourceIsLazy(true); char* script; size_t script_len; if (!gjs_load_internal_source(cx, uri, &script, &script_len)) return false; JS::SourceText source; if (!source.init(cx, script, script_len, JS::SourceOwnership::TakeOwnership)) return false; JS::RootedValue ignored(cx); return JS::Evaluate(cx, options, source, &ignored); } GJS_JSAPI_RETURN_CONVENTION static bool load_native_module(JSContext* m_cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); // This function should never be directly exposed to user code, so we // can be strict. g_assert(argc == 1); g_assert(argv[0].isString()); JS::RootedString str(m_cx, argv[0].toString()); JS::UniqueChars id(JS_EncodeStringToUTF8(m_cx, str)); if (!id) return false; JS::RootedObject native_obj(m_cx); if (!Gjs::NativeModuleDefineFuncs::get().define(m_cx, id.get(), &native_obj)) { gjs_throw(m_cx, "Failed to load native module: %s", id.get()); return false; } argv.rval().setObject(*native_obj); return true; } }; const JSClassOps defaultclassops = JS::DefaultGlobalClassOps; class GjsGlobal : GjsBaseGlobal { static constexpr JSClass klass = { // Jasmine depends on the class name "GjsGlobal" to detect GJS' global // object. "GjsGlobal", JSCLASS_GLOBAL_FLAGS_WITH_SLOTS( static_cast(GjsGlobalSlot::LAST)), &defaultclassops, }; // clang-format off static constexpr JSPropertySpec static_props[] = { JS_STRING_SYM_PS(toStringTag, "GjsGlobal", JSPROP_READONLY), JS_PS_END}; // clang-format on static constexpr JSFunctionSpec static_funcs[] = { JS_FS_END}; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx) { return GjsBaseGlobal::create(cx, &klass); } GJS_JSAPI_RETURN_CONVENTION static JSObject* create_with_compartment(JSContext* cx, JS::HandleObject cmp_global) { return GjsBaseGlobal::create_with_compartment(cx, cmp_global, &klass); } GJS_JSAPI_RETURN_CONVENTION static bool define_properties(JSContext* cx, JS::HandleObject global, const char* realm_name, const char* bootstrap_script) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_DefinePropertyById(cx, global, atoms.window(), global, JSPROP_READONLY | JSPROP_PERMANENT) || !JS_DefineFunctions(cx, global, GjsGlobal::static_funcs) || !JS_DefineProperties(cx, global, GjsGlobal::static_props)) return false; JS::Realm* realm = JS::GetObjectRealmOrNull(global); g_assert(realm && "Global object must be associated with a realm"); // const_cast is allowed here if we never free the realm data JS::SetRealmPrivate(realm, const_cast(realm_name)); JS::RootedObject native_registry(cx, JS::NewMapObject(cx)); if (!native_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY, JS::ObjectValue(*native_registry)); JS::RootedObject module_registry(cx, JS::NewMapObject(cx)); if (!module_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY, JS::ObjectValue(*module_registry)); JS::Value v_importer = gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS); g_assert(((void) "importer should be defined before passing null " "importer to GjsGlobal::define_properties", v_importer.isObject())); JS::RootedObject root_importer(cx, &v_importer.toObject()); // Wrapping is a no-op if the importer is already in the same realm. if (!JS_WrapObject(cx, &root_importer) || !JS_DefinePropertyById(cx, global, atoms.imports(), root_importer, GJS_MODULE_PROP_FLAGS)) return false; if (bootstrap_script) { if (!run_bootstrap(cx, bootstrap_script, global)) return false; } return true; } }; class GjsDebuggerGlobal : GjsBaseGlobal { static constexpr JSClass klass = { "GjsDebuggerGlobal", JSCLASS_GLOBAL_FLAGS_WITH_SLOTS( static_cast(GjsDebuggerGlobalSlot::LAST)), &defaultclassops, }; static constexpr JSFunctionSpec static_funcs[] = { JS_FN("loadNative", &load_native_module, 1, 0), JS_FS_END}; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx) { JS::RealmCreationOptions options; options.setToSourceEnabled(true); // debugger uses uneval() return GjsBaseGlobal::create(cx, &klass, options); } GJS_JSAPI_RETURN_CONVENTION static JSObject* create_with_compartment(JSContext* cx, JS::HandleObject cmp_global) { return GjsBaseGlobal::create_with_compartment(cx, cmp_global, &klass); } GJS_JSAPI_RETURN_CONVENTION static bool define_properties(JSContext* cx, JS::HandleObject global, const char* realm_name, const char* bootstrap_script) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_DefinePropertyById(cx, global, atoms.window(), global, JSPROP_READONLY | JSPROP_PERMANENT) || !JS_DefineFunctions(cx, global, GjsDebuggerGlobal::static_funcs)) return false; JS::Realm* realm = JS::GetObjectRealmOrNull(global); g_assert(realm && "Global object must be associated with a realm"); // const_cast is allowed here if we never free the realm data JS::SetRealmPrivate(realm, const_cast(realm_name)); if (bootstrap_script) { if (!run_bootstrap(cx, bootstrap_script, global)) return false; } return true; } }; class GjsInternalGlobal : GjsBaseGlobal { static constexpr JSFunctionSpec static_funcs[] = { JS_FN("compileModule", gjs_internal_compile_module, 2, 0), JS_FN("compileInternalModule", gjs_internal_compile_internal_module, 2, 0), JS_FN("getRegistry", gjs_internal_get_registry, 1, 0), JS_FN("loadResourceOrFile", gjs_internal_load_resource_or_file, 1, 0), JS_FN("loadResourceOrFileAsync", gjs_internal_load_resource_or_file_async, 1, 0), JS_FN("parseURI", gjs_internal_parse_uri, 1, 0), JS_FN("resolveRelativeResourceOrFile", gjs_internal_resolve_relative_resource_or_file, 2, 0), JS_FN("setGlobalModuleLoader", gjs_internal_set_global_module_loader, 2, 0), JS_FN("setModulePrivate", gjs_internal_set_module_private, 2, 0), JS_FN("uriExists", gjs_internal_uri_exists, 1, 0), JS_FS_END}; static constexpr JSClass klass = { "GjsInternalGlobal", JSCLASS_GLOBAL_FLAGS_WITH_SLOTS( static_cast(GjsInternalGlobalSlot::LAST)), &defaultclassops, }; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx) { return GjsBaseGlobal::create(cx, &klass, {}, get_internal_principals()); } GJS_JSAPI_RETURN_CONVENTION static JSObject* create_with_compartment(JSContext* cx, JS::HandleObject cmp_global) { return GjsBaseGlobal::create_with_compartment( cx, cmp_global, &klass, {}, get_internal_principals()); } GJS_JSAPI_RETURN_CONVENTION static bool define_properties(JSContext* cx, JS::HandleObject global, const char* realm_name, const char* bootstrap_script [[maybe_unused]]) { JS::Realm* realm = JS::GetObjectRealmOrNull(global); g_assert(realm && "Global object must be associated with a realm"); // const_cast is allowed here if we never free the realm data JS::SetRealmPrivate(realm, const_cast(realm_name)); JSAutoRealm ar(cx, global); JS::RootedObject native_registry(cx, JS::NewMapObject(cx)); if (!native_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY, JS::ObjectValue(*native_registry)); JS::RootedObject module_registry(cx, JS::NewMapObject(cx)); if (!module_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY, JS::ObjectValue(*module_registry)); return JS_DefineFunctions(cx, global, static_funcs); } }; /** * gjs_create_global_object: * @cx: a #JSContext * * Creates a global object, and initializes it with the default API. * * Returns: the created global object on success, nullptr otherwise, in which * case an exception is pending on @cx */ JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type, JS::HandleObject current_global) { if (current_global) { switch (global_type) { case GjsGlobalType::DEFAULT: return GjsGlobal::create_with_compartment(cx, current_global); case GjsGlobalType::DEBUGGER: return GjsDebuggerGlobal::create_with_compartment( cx, current_global); case GjsGlobalType::INTERNAL: return GjsInternalGlobal::create_with_compartment( cx, current_global); default: return nullptr; } } switch (global_type) { case GjsGlobalType::DEFAULT: return GjsGlobal::create(cx); case GjsGlobalType::DEBUGGER: return GjsDebuggerGlobal::create(cx); case GjsGlobalType::INTERNAL: return GjsInternalGlobal::create(cx); default: return nullptr; } } /** * gjs_global_is_type: * * @param cx the current #JSContext * @param type the global type to test for * * @returns whether the current global is the same type as #type */ bool gjs_global_is_type(JSContext* cx, GjsGlobalType type) { JSObject* global = JS::CurrentGlobalOrNull(cx); g_assert(global && "gjs_global_is_type called before a realm was entered."); JS::Value global_type = gjs_get_global_slot(global, GjsBaseGlobalSlot::GLOBAL_TYPE); g_assert(global_type.isInt32()); return static_cast(global_type.toInt32()) == type; } GjsGlobalType gjs_global_get_type(JSContext* cx) { auto global = JS::CurrentGlobalOrNull(cx); g_assert(global && "gjs_global_get_type called before a realm was entered."); JS::Value global_type = gjs_get_global_slot(global, GjsBaseGlobalSlot::GLOBAL_TYPE); g_assert(global_type.isInt32()); return static_cast(global_type.toInt32()); } GjsGlobalType gjs_global_get_type(JSObject* global) { JS::Value global_type = gjs_get_global_slot(global, GjsBaseGlobalSlot::GLOBAL_TYPE); g_assert(global_type.isInt32()); return static_cast(global_type.toInt32()); } /** * gjs_global_registry_set: * * @brief This function inserts a module object into a global registry. * Global registries are JS Map objects for easy reuse and access * within internal JS. This function will assert if a module has * already been inserted at the given key. * @param cx the current #JSContext * @param registry a JS Map object * @param key a module identifier, typically a string or symbol * @param module a module object */ bool gjs_global_registry_set(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::HandleObject module) { JS::RootedValue v_key(cx); if (!JS_IdToValue(cx, key, &v_key)) return false; bool has_key; if (!JS::MapHas(cx, registry, v_key, &has_key)) return false; g_assert(!has_key && "Module key already exists in the registry"); JS::RootedValue v_value(cx, JS::ObjectValue(*module)); return JS::MapSet(cx, registry, v_key, v_value); } /** * gjs_global_registry_get: * * @brief This function retrieves a module record from the global registry, * or %NULL if the module record is not present. * Global registries are JS Map objects for easy reuse and access * within internal JS. * @param cx the current #JSContext * @param registry a JS Map object * @param key a module identifier, typically a string or symbol * @param module a module object */ bool gjs_global_registry_get(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::MutableHandleObject module_out) { JS::RootedValue v_key(cx), v_value(cx); if (!JS_IdToValue(cx, key, &v_key) || !JS::MapGet(cx, registry, v_key, &v_value)) return false; g_assert((v_value.isUndefined() || v_value.isObject()) && "Invalid value in module registry"); if (v_value.isObject()) { module_out.set(&v_value.toObject()); return true; } module_out.set(nullptr); return true; } /** * gjs_define_global_properties: * @cx: a #JSContext * @global: a JS global object that has not yet been passed to this function * @realm_name: (nullable): name of the realm, for debug output * @bootstrap_script: (nullable): name of a bootstrap script (found at * resource://org/cinnamon/cjs/modules/script/_bootstrap/@bootstrap_script) or * %NULL for none * * Defines properties on the global object such as 'window' and 'imports', and * runs a bootstrap JS script on the global object to define any properties * that can be defined from JS. * This function completes the initialization of a new global object, but it * is separate from gjs_create_global_object() because all globals share the * same root importer. * The code creating the main global for the JS context needs to create the * root importer in between calling gjs_create_global_object() and * gjs_define_global_properties(). * * The caller of this function should be in the realm for @global. * If the root importer object belongs to a different realm, this function will * create a wrapper for it. * * Returns: true on success, false otherwise, in which case an exception is * pending on @cx */ bool gjs_define_global_properties(JSContext* cx, JS::HandleObject global, GjsGlobalType global_type, const char* realm_name, const char* bootstrap_script) { gjs_set_global_slot(global.get(), GjsBaseGlobalSlot::GLOBAL_TYPE, JS::Int32Value(static_cast(global_type))); switch (global_type) { case GjsGlobalType::DEFAULT: return GjsGlobal::define_properties(cx, global, realm_name, bootstrap_script); case GjsGlobalType::DEBUGGER: return GjsDebuggerGlobal::define_properties(cx, global, realm_name, bootstrap_script); case GjsGlobalType::INTERNAL: return GjsInternalGlobal::define_properties(cx, global, realm_name, bootstrap_script); } // Global type does not handle define_properties g_assert_not_reached(); } void detail::set_global_slot(JSObject* global, uint32_t slot, JS::Value value) { JS::SetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot, value); } JS::Value detail::get_global_slot(JSObject* global, uint32_t slot) { return JS::GetReservedSlot(global, JSCLASS_GLOBAL_SLOT_COUNT + slot); } decltype(GjsGlobal::klass) constexpr GjsGlobal::klass; decltype(GjsGlobal::static_funcs) constexpr GjsGlobal::static_funcs; decltype(GjsGlobal::static_props) constexpr GjsGlobal::static_props; decltype(GjsDebuggerGlobal::klass) constexpr GjsDebuggerGlobal::klass; decltype( GjsDebuggerGlobal::static_funcs) constexpr GjsDebuggerGlobal::static_funcs; decltype(GjsInternalGlobal::klass) constexpr GjsInternalGlobal::klass; decltype( GjsInternalGlobal::static_funcs) constexpr GjsInternalGlobal::static_funcs; cjs-128.1/cjs/global.h0000664000175000017500000000762015116312211013416 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento // SPDX-FileCopyrightText: 2020 Evan Welsh #ifndef GJS_GLOBAL_H_ #define GJS_GLOBAL_H_ #include #include #include // for Handle #include #include #include "cjs/macros.h" namespace JS { struct PropertyKey; } enum class GjsGlobalType { DEFAULT, DEBUGGER, INTERNAL, }; enum class GjsBaseGlobalSlot : uint32_t { GLOBAL_TYPE = 0, LAST, }; enum class GjsDebuggerGlobalSlot : uint32_t { LAST = static_cast(GjsBaseGlobalSlot::LAST), }; enum class GjsGlobalSlot : uint32_t { IMPORTS = static_cast(GjsBaseGlobalSlot::LAST), // Stores an object with methods to resolve and load modules MODULE_LOADER, // Stores the module registry (a Map object) MODULE_REGISTRY, NATIVE_REGISTRY, // prettyPrint() function defined in JS but used internally in C++ PRETTY_PRINT_FUNC, PROTOTYPE_gtype, PROTOTYPE_importer, PROTOTYPE_function, PROTOTYPE_ns, PROTOTYPE_cairo_context, PROTOTYPE_cairo_gradient, PROTOTYPE_cairo_image_surface, PROTOTYPE_cairo_linear_gradient, PROTOTYPE_cairo_path, PROTOTYPE_cairo_pattern, PROTOTYPE_cairo_pdf_surface, PROTOTYPE_cairo_ps_surface, PROTOTYPE_cairo_radial_gradient, PROTOTYPE_cairo_region, PROTOTYPE_cairo_solid_pattern, PROTOTYPE_cairo_surface, PROTOTYPE_cairo_surface_pattern, PROTOTYPE_cairo_svg_surface, LAST, }; enum class GjsInternalGlobalSlot : uint32_t { LAST = static_cast(GjsGlobalSlot::LAST), }; bool gjs_global_is_type(JSContext* cx, GjsGlobalType type); GjsGlobalType gjs_global_get_type(JSContext* cx); GjsGlobalType gjs_global_get_type(JSObject* global); GJS_JSAPI_RETURN_CONVENTION bool gjs_global_registry_set(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::HandleObject value); GJS_JSAPI_RETURN_CONVENTION bool gjs_global_registry_get(JSContext* cx, JS::HandleObject registry, JS::PropertyKey key, JS::MutableHandleObject value); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_global_object(JSContext* cx, GjsGlobalType global_type, JS::HandleObject existing_global = nullptr); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_global_properties(JSContext* cx, JS::HandleObject global, GjsGlobalType global_type, const char* realm_name, const char* bootstrap_script); namespace detail { void set_global_slot(JSObject* global, uint32_t slot, JS::Value value); JS::Value get_global_slot(JSObject* global, uint32_t slot); } // namespace detail template inline void gjs_set_global_slot(JSObject* global, Slot slot, JS::Value value) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "Must use a GJS global slot enum"); detail::set_global_slot(global, static_cast(slot), value); } template inline JS::Value gjs_get_global_slot(JSObject* global, Slot slot) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "Must use a GJS global slot enum"); return detail::get_global_slot(global, static_cast(slot)); } #endif // GJS_GLOBAL_H_ cjs-128.1/cjs/importer.cpp0000664000175000017500000007601215116312211014353 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008-2010 litl, LLC #include #include // for size_t, strcmp, strlen #ifdef _WIN32 # include #endif #include #include // for vector #include #include #include #include #include #include #include #include #include // for JS_ReportOutOfMemory, JSEXN_ERR #include #include // for StackGCVector #include // for CurrentGlobalOrNull #include // for PropertyKey #include // for GetClass #include #include #include #include #include #include #include #include // for UniqueChars #include #include // for JS_NewPlainObject, IdVector, JS_... #include #include #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/importer.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/module.h" #include "cjs/native.h" #include "util/log.h" #define MODULE_INIT_FILENAME "__init__.js" extern const JSClass gjs_importer_class; GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_define_importer(JSContext*, JS::HandleObject, const char*, const std::vector&, bool); GJS_JSAPI_RETURN_CONVENTION static bool importer_to_string(JSContext *cx, unsigned argc, JS::Value *vp) { GJS_GET_THIS(cx, argc, vp, args, importer); GjsAutoChar output; const JSClass* klass = JS::GetClass(importer); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue module_path(cx); if (!JS_GetPropertyById(cx, importer, atoms.module_path(), &module_path)) return false; if (module_path.isNull()) { output = g_strdup_printf("[%s root]", klass->name); } else { g_assert(module_path.isString() && "Bad importer.__modulePath__"); JS::UniqueChars path = gjs_string_to_utf8(cx, module_path); if (!path) return false; output = g_strdup_printf("[%s %s]", klass->name, path.get()); } args.rval().setString(JS_NewStringCopyZ(cx, output)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool define_meta_properties(JSContext *context, JS::HandleObject module_obj, const char *parse_name, const char *module_name, JS::HandleObject parent) { bool parent_is_module; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); /* For these meta-properties, don't set ENUMERATE since we wouldn't want to * copy these symbols to any other object for example. RESOLVING is used to * make sure we don't try to invoke a "resolve" operation, since this * function may be called from inside one. */ unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING; /* We define both __moduleName__ and __parentModule__ to null * on the root importer */ parent_is_module = parent && JS_InstanceOf(context, parent, &gjs_importer_class, nullptr); gjs_debug(GJS_DEBUG_IMPORTER, "Defining parent %p of %p '%s' is mod %d", parent.get(), module_obj.get(), module_name ? module_name : "", parent_is_module); if (parse_name != nullptr) { JS::RootedValue file(context); if (!gjs_string_from_utf8(context, parse_name, &file)) return false; if (!JS_DefinePropertyById(context, module_obj, atoms.file(), file, attrs)) return false; } /* Null is used instead of undefined for backwards compatibility with code * that explicitly checks for null. */ JS::RootedValue module_name_val(context, JS::NullValue()); JS::RootedValue parent_module_val(context, JS::NullValue()); JS::RootedValue module_path(context, JS::NullValue()); JS::RootedValue to_string_tag(context); if (parent_is_module) { if (!gjs_string_from_utf8(context, module_name, &module_name_val)) return false; parent_module_val.setObject(*parent); JS::RootedValue parent_module_path(context); if (!JS_GetPropertyById(context, parent, atoms.module_path(), &parent_module_path)) return false; GjsAutoChar module_path_buf; if (parent_module_path.isNull()) { module_path_buf = g_strdup(module_name); } else { JS::UniqueChars parent_path = gjs_string_to_utf8(context, parent_module_path); if (!parent_path) return false; module_path_buf = g_strdup_printf("%s.%s", parent_path.get(), module_name); } if (!gjs_string_from_utf8(context, module_path_buf, &module_path)) return false; GjsAutoChar to_string_tag_buf = g_strdup_printf("GjsModule %s", module_path_buf.get()); if (!gjs_string_from_utf8(context, to_string_tag_buf, &to_string_tag)) return false; } else { to_string_tag.setString(JS_AtomizeString(context, "GjsModule")); } if (!JS_DefinePropertyById(context, module_obj, atoms.module_name(), module_name_val, attrs)) return false; if (!JS_DefinePropertyById(context, module_obj, atoms.parent_module(), parent_module_val, attrs)) return false; if (!JS_DefinePropertyById(context, module_obj, atoms.module_path(), module_path, attrs)) return false; JS::RootedId to_string_tag_name( context, JS::PropertyKey::Symbol(JS::GetWellKnownSymbol( context, JS::SymbolCode::toStringTag))); return JS_DefinePropertyById(context, module_obj, to_string_tag_name, to_string_tag, attrs); } GJS_JSAPI_RETURN_CONVENTION static bool import_directory(JSContext* context, JS::HandleObject obj, const char* name, const std::vector& full_paths) { gjs_debug(GJS_DEBUG_IMPORTER, "Importing directory '%s'", name); // We define a sub-importer that has only the given directories on its // search path. return !!gjs_define_importer(context, obj, name, full_paths, false); } /* Make the property we set in gjs_module_import() permanent; * we do this after the import successfully completes. */ GJS_JSAPI_RETURN_CONVENTION static bool seal_import(JSContext *cx, JS::HandleObject obj, JS::HandleId id, const char *name) { JS::Rooted> maybe_descr(cx); if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &maybe_descr) || maybe_descr.isNothing()) { gjs_debug(GJS_DEBUG_IMPORTER, "Failed to get attributes to seal '%s' in importer", name); return false; } JS::Rooted descr(cx, maybe_descr.value()); descr.setConfigurable(false); if (!JS_DefinePropertyById(cx, obj, id, descr)) { gjs_debug(GJS_DEBUG_IMPORTER, "Failed to redefine attributes to seal '%s' in importer", name); return false; } return true; } /* An import failed. Delete the property pointing to the import * from the parent namespace. In complicated situations this might * not be sufficient to get us fully back to a sane state. If: * * - We import module A * - module A imports module B * - module B imports module A, storing a reference to the current * module A module object * - module A subsequently throws an exception * * Then module B is left imported, but the imported module B has * a reference to the failed module A module object. To handle this * we could could try to track the entire "import operation" and * roll back *all* modifications made to the namespace objects. * It's not clear that the complexity would be worth the small gain * in robustness. (You can still come up with ways of defeating * the attempt to clean up.) */ static void cancel_import(JSContext *context, JS::HandleObject obj, const char *name) { gjs_debug(GJS_DEBUG_IMPORTER, "Cleaning up from failed import of '%s'", name); if (!JS_DeleteProperty(context, obj, name)) { gjs_debug(GJS_DEBUG_IMPORTER, "Failed to delete '%s' in importer", name); } } /* * gjs_import_native_module: * @cx: the #JSContext * @importer: the root importer * @id_str: Name under which the module was registered with add() * * Imports a builtin native-code module so that it is available to JS code as * `imports[id_str]`. * * Returns: true on success, false if an exception was thrown. */ bool gjs_import_native_module(JSContext* cx, JS::HandleObject importer, const char* id_str) { gjs_debug(GJS_DEBUG_IMPORTER, "Importing '%s'", id_str); JS::RootedObject native_registry( cx, gjs_get_native_registry(JS::CurrentGlobalOrNull(cx))); JS::RootedId id(cx, gjs_intern_string_to_id(cx, id_str)); if (id.isVoid()) return false; JS::RootedObject module(cx); if (!gjs_global_registry_get(cx, native_registry, id, &module)) return false; if (!module && (!Gjs::NativeModuleDefineFuncs::get().define(cx, id_str, &module) || !gjs_global_registry_set(cx, native_registry, id, module))) return false; return define_meta_properties(cx, module, nullptr, id_str, importer) && JS_DefineProperty(cx, importer, id_str, module, GJS_MODULE_PROP_FLAGS); } GJS_JSAPI_RETURN_CONVENTION static bool import_module_init(JSContext *context, GFile *file, JS::HandleObject module_obj) { gsize script_len = 0; GjsAutoError error; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); JS::RootedValue ignored(context); GjsAutoChar script; if (!g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr, &error)) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { gjs_throw_gerror_message(context, error); return false; } return true; } g_assert(script); GjsAutoChar full_path = g_file_get_parse_name(file); return gjs->eval_with_scope(module_obj, script, script_len, full_path, &ignored); } GJS_JSAPI_RETURN_CONVENTION static JSObject* load_module_init(JSContext* cx, JS::HandleObject in_object, GFile* file) { bool found; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); /* First we check if js module has already been loaded */ if (!JS_HasPropertyById(cx, in_object, atoms.module_init(), &found)) return nullptr; if (found) { JS::RootedValue v_module(cx); if (!JS_GetPropertyById(cx, in_object, atoms.module_init(), &v_module)) return nullptr; if (v_module.isObject()) return &v_module.toObject(); GjsAutoChar full_path = g_file_get_parse_name(file); gjs_throw(cx, "Unexpected non-object module __init__ imported from %s", full_path.get()); return nullptr; } JS::RootedObject module_obj(cx, JS_NewPlainObject(cx)); if (!module_obj) return nullptr; if (!import_module_init(cx, file, module_obj)) return nullptr; if (!JS_DefinePropertyById(cx, in_object, atoms.module_init(), module_obj, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) return nullptr; return module_obj; } GJS_JSAPI_RETURN_CONVENTION static bool load_module_elements(JSContext* cx, JS::HandleObject in_object, JS::MutableHandleIdVector prop_ids, GFile* file) { JS::RootedObject module_obj(cx, load_module_init(cx, in_object, file)); if (!module_obj) return false; JS::Rooted ids(cx, cx); if (!JS_Enumerate(cx, module_obj, &ids)) return false; if (!prop_ids.appendAll(ids)) { JS_ReportOutOfMemory(cx); return false; } return true; } /* If error, returns false. If not found, returns true but does not touch * the value at *result. If found, returns true and sets *result = true. */ GJS_JSAPI_RETURN_CONVENTION static bool import_symbol_from_init_js(JSContext* cx, JS::HandleObject importer, GFile* directory, const char* name, bool* result) { bool found; GjsAutoUnref file = g_file_get_child(directory, MODULE_INIT_FILENAME); JS::RootedObject module_obj(cx, load_module_init(cx, importer, file)); if (!module_obj || !JS_AlreadyHasOwnProperty(cx, module_obj, name, &found)) return false; if (!found) return true; JS::RootedValue obj_val(cx); if (!JS_GetProperty(cx, module_obj, name, &obj_val)) return false; if (obj_val.isUndefined()) return true; if (!JS_DefineProperty(cx, importer, name, obj_val, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) return false; *result = true; return true; } GJS_JSAPI_RETURN_CONVENTION static bool attempt_import(JSContext* cx, JS::HandleObject obj, JS::HandleId module_id, const char* module_name, GFile* file) { JS::RootedObject module_obj( cx, gjs_module_import(cx, obj, module_id, module_name, file)); if (!module_obj) return false; GjsAutoChar full_path = g_file_get_parse_name(file); return define_meta_properties(cx, module_obj, full_path, module_name, obj) && seal_import(cx, obj, module_id, module_name); } GJS_JSAPI_RETURN_CONVENTION static bool import_file_on_module(JSContext *context, JS::HandleObject obj, JS::HandleId id, const char *name, GFile *file) { if (!attempt_import(context, obj, id, name, file)) { cancel_import(context, obj, name); return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool do_import(JSContext* context, JS::HandleObject obj, JS::HandleId id) { JS::RootedObject search_path(context); guint32 search_path_len; guint32 i; bool exists, is_array; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, obj, "importer", atoms.search_path(), &search_path)) return false; if (!JS::IsArrayObject(context, search_path, &is_array)) return false; if (!is_array) { gjs_throw(context, "searchPath property on importer is not an array"); return false; } if (!JS::GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return false; } JS::UniqueChars name; if (!gjs_get_string_id(context, id, &name)) return false; if (!name) { gjs_throw(context, "Importing invalid module name"); return false; } // null if this is the root importer JS::RootedValue parent(context); if (!JS_GetPropertyById(context, obj, atoms.parent_module(), &parent)) return false; /* First try importing an internal module like gi */ if (parent.isNull() && Gjs::NativeModuleDefineFuncs::get().is_registered(name.get())) { if (!gjs_import_native_module(context, obj, name.get())) return false; gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name.get()); return true; } GjsAutoChar filename = g_strdup_printf("%s.js", name.get()); std::vector directories; JS::RootedValue elem(context); JS::RootedString str(context); for (i = 0; i < search_path_len; ++i) { elem.setUndefined(); if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem.isUndefined() * means no element found */ return false; } if (elem.isUndefined()) continue; if (!elem.isString()) { gjs_throw(context, "importer searchPath contains non-string"); return false; } str = elem.toString(); JS::UniqueChars dirname(JS_EncodeStringToUTF8(context, str)); if (!dirname) return false; /* Ignore empty path elements */ if (dirname[0] == '\0') continue; GjsAutoUnref directory = g_file_new_for_commandline_arg(dirname.get()); /* Try importing __init__.js and loading the symbol from it */ bool found = false; if (!import_symbol_from_init_js(context, obj, directory, name.get(), &found)) return false; if (found) return true; /* Second try importing a directory (a sub-importer) */ GjsAutoUnref file = g_file_get_child(directory, name.get()); if (g_file_query_file_type(file, GFileQueryInfoFlags(0), nullptr) == G_FILE_TYPE_DIRECTORY) { GjsAutoChar full_path = g_file_get_parse_name(file); gjs_debug(GJS_DEBUG_IMPORTER, "Adding directory '%s' to child importer '%s'", full_path.get(), name.get()); directories.push_back(full_path.get()); } /* If we just added to directories, we know we don't need to * check for a file. If we added to directories on an earlier * iteration, we want to ignore any files later in the * path. So, always skip the rest of the loop block if we have * directories. */ if (!directories.empty()) continue; /* Third, if it's not a directory, try importing a file */ file = g_file_get_child(directory, filename.get()); exists = g_file_query_exists(file, nullptr); if (!exists) { GjsAutoChar full_path = g_file_get_parse_name(file); gjs_debug(GJS_DEBUG_IMPORTER, "JS import '%s' not found in %s at %s", name.get(), dirname.get(), full_path.get()); continue; } if (import_file_on_module(context, obj, id, name.get(), file)) { gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name.get()); return true; } /* Don't keep searching path if we fail to load the file for * reasons other than it doesn't exist... i.e. broken files * block searching for nonbroken ones */ return false; } if (!directories.empty()) { if (!import_directory(context, obj, name.get(), directories)) return false; gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported directory '%s'", name.get()); return true; } /* If no exception occurred, the problem is just that we got to the * end of the path. Be sure an exception is set. */ g_assert(!JS_IsExceptionPending(context)); gjs_throw_custom(context, JSEXN_ERR, "ImportError", "No JS module '%s' found in search path", name.get()); return false; } GJS_JSAPI_RETURN_CONVENTION static bool importer_new_enumerate(JSContext* context, JS::HandleObject object, JS::MutableHandleIdVector properties, bool enumerable_only [[maybe_unused]]) { guint32 search_path_len; guint32 i; bool is_array; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject search_path(context); if (!gjs_object_require_property(context, object, "importer", atoms.search_path(), &search_path)) return false; if (!JS::IsArrayObject(context, search_path, &is_array)) return false; if (!is_array) { gjs_throw(context, "searchPath property on importer is not an array"); return false; } if (!JS::GetArrayLength(context, search_path, &search_path_len)) { gjs_throw(context, "searchPath array has no length"); return false; } JS::RootedValue elem(context); JS::RootedString str(context); for (i = 0; i < search_path_len; ++i) { elem.setUndefined(); if (!JS_GetElement(context, search_path, i, &elem)) { /* this means there was an exception, while elem.isUndefined() * means no element found */ return false; } if (elem.isUndefined()) continue; if (!elem.isString()) { gjs_throw(context, "importer searchPath contains non-string"); return false; } str = elem.toString(); JS::UniqueChars dirname(JS_EncodeStringToUTF8(context, str)); if (!dirname) return false; GjsAutoUnref directory = g_file_new_for_commandline_arg(dirname.get()); GjsAutoUnref file = g_file_get_child(directory, MODULE_INIT_FILENAME); if (!load_module_elements(context, object, properties, file)) return false; /* new_for_commandline_arg handles resource:/// paths */ GjsAutoUnref direnum = g_file_enumerate_children( directory, "standard::name,standard::type", G_FILE_QUERY_INFO_NONE, nullptr, nullptr); while (true) { GFileInfo *info; GFile *file; if (!direnum || !g_file_enumerator_iterate(direnum, &info, &file, NULL, NULL)) break; if (info == NULL || file == NULL) break; GjsAutoChar filename = g_file_get_basename(file); /* skip hidden files and directories (.svn, .git, ...) */ if (filename.get()[0] == '.') continue; /* skip module init file */ if (strcmp(filename, MODULE_INIT_FILENAME) == 0) continue; if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY) { jsid id = gjs_intern_string_to_id(context, filename); if (id.isVoid()) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(context); return false; } } else if (g_str_has_suffix(filename, ".js")) { GjsAutoChar filename_noext = g_strndup(filename, strlen(filename) - 3); jsid id = gjs_intern_string_to_id(context, filename_noext); if (id.isVoid()) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(context); return false; } } } } return true; } /* The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool importer_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { if (!id.isString()) { *resolved = false; return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (id == atoms.module_init() || id == atoms.to_string() || id == atoms.value_of()) { *resolved = false; return true; } gjs_debug_jsprop(GJS_DEBUG_IMPORTER, "Resolve prop '%s' hook, obj %s", gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str()); if (!id.isString()) { *resolved = false; return true; } if (!do_import(context, obj, id)) return false; *resolved = true; return true; } static const JSClassOps gjs_importer_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate importer_new_enumerate, importer_resolve, }; const JSClass gjs_importer_class = { "GjsFileImporter", 0, &gjs_importer_class_ops, }; static const JSPropertySpec gjs_importer_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "GjsFileImporter", JSPROP_READONLY), JS_PS_END}; JSFunctionSpec gjs_importer_proto_funcs[] = { JS_FN("toString", importer_to_string, 0, 0), JS_FS_END}; [[nodiscard]] static const std::vector& gjs_get_search_path() { static std::vector gjs_search_path; static bool search_path_initialized = false; /* not thread safe */ if (!search_path_initialized) { const char* const* system_data_dirs; const char *envstr; gsize i; /* in order of priority */ /* $GJS_PATH */ envstr = g_getenv("GJS_PATH"); if (envstr) { char **dirs, **d; dirs = g_strsplit(envstr, G_SEARCHPATH_SEPARATOR_S, 0); for (d = dirs; *d != NULL; d++) gjs_search_path.push_back(*d); /* we assume the array and strings are allocated separately */ g_free(dirs); } gjs_search_path.push_back("resource:///org/cinnamon/cjs/modules/script/"); gjs_search_path.push_back("resource:///org/cinnamon/cjs/modules/core/"); /* $XDG_DATA_DIRS /gjs-1.0 */ system_data_dirs = g_get_system_data_dirs(); for (i = 0; system_data_dirs[i] != NULL; ++i) { GjsAutoChar s = g_build_filename(system_data_dirs[i], "cjs-1.0", nullptr); gjs_search_path.push_back(s.get()); } /* ${datadir}/share/gjs-1.0 */ #ifdef G_OS_WIN32 extern HMODULE gjs_dll; char *basedir = g_win32_get_package_installation_directory_of_module (gjs_dll); GjsAutoChar gjs_data_dir = g_build_filename(basedir, "share", "cjs-1.0", nullptr); gjs_search_path.push_back(gjs_data_dir.get()); g_free (basedir); #else gjs_search_path.push_back(GJS_JS_DIR); #endif search_path_initialized = true; } return gjs_search_path; } GJS_JSAPI_RETURN_CONVENTION static bool no_construct(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_throw_abstract_constructor_error(cx, args); return false; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_importer_define_proto(JSContext* cx) { JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); g_assert(global && "Must enter a realm before defining importer"); // If we've been here more than once, we already have the proto JS::Value v_proto = gjs_get_global_slot(global, GjsGlobalSlot::PROTOTYPE_importer); if (!v_proto.isUndefined()) { g_assert(v_proto.isObject() && "Someone stored some weird value in a global slot"); return &v_proto.toObject(); } JS::RootedObject proto(cx, JS_NewPlainObject(cx)); if (!proto || !JS_DefineFunctions(cx, proto, gjs_importer_proto_funcs) || !JS_DefineProperties(cx, proto, gjs_importer_proto_props)) return nullptr; gjs_set_global_slot(global, GjsGlobalSlot::PROTOTYPE_importer, JS::ObjectValue(*proto)); // For backwards compatibility JSFunction* constructor = JS_NewFunction( cx, no_construct, 0, JSFUN_CONSTRUCTOR, "GjsFileImporter"); JS::RootedObject ctor_obj(cx, JS_GetFunctionObject(constructor)); if (!JS_LinkConstructorAndPrototype(cx, ctor_obj, proto) || !JS_DefineProperty(cx, global, "GjsFileImporter", ctor_obj, 0)) return nullptr; gjs_debug(GJS_DEBUG_CONTEXT, "Initialized class %s prototype %p", gjs_importer_class.name, proto.get()); return proto; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_create_importer( JSContext* context, const char* importer_name, const std::vector& initial_search_path, bool add_standard_search_path, JS::HandleObject in_object) { std::vector search_paths = initial_search_path; if (add_standard_search_path) { /* Stick the "standard" shared search path after the provided one. */ const std::vector& gjs_search_path = gjs_get_search_path(); search_paths.insert(search_paths.end(), gjs_search_path.begin(), gjs_search_path.end()); } JS::RootedObject proto(context, gjs_importer_define_proto(context)); if (!proto) return nullptr; JS::RootedObject importer( context, JS_NewObjectWithGivenProto(context, &gjs_importer_class, proto)); if (!importer) return nullptr; gjs_debug_lifecycle(GJS_DEBUG_IMPORTER, "importer constructor, obj %p", importer.get()); /* API users can replace this property from JS, is the idea */ if (!gjs_define_string_array( context, importer, "searchPath", search_paths, // settable (no READONLY) but not deletable (PERMANENT) JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; if (!define_meta_properties(context, importer, NULL, importer_name, in_object)) return nullptr; return importer; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_define_importer( JSContext* context, JS::HandleObject in_object, const char* importer_name, const std::vector& initial_search_path, bool add_standard_search_path) { JS::RootedObject importer( context, gjs_create_importer(context, importer_name, initial_search_path, add_standard_search_path, in_object)); if (!JS_DefineProperty(context, in_object, importer_name, importer, GJS_MODULE_PROP_FLAGS)) return nullptr; gjs_debug(GJS_DEBUG_IMPORTER, "Defined importer '%s' %p in %p", importer_name, importer.get(), in_object.get()); return importer; } JSObject* gjs_create_root_importer( JSContext* cx, const std::vector& search_path) { return gjs_create_importer(cx, "imports", search_path, true, nullptr); } cjs-128.1/cjs/importer.h0000664000175000017500000000127415116312211014016 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GJS_IMPORTER_H_ #define GJS_IMPORTER_H_ #include #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_root_importer(JSContext* cx, const std::vector& search_path); GJS_JSAPI_RETURN_CONVENTION bool gjs_import_native_module(JSContext *cx, JS::HandleObject importer, const char *name); #endif // GJS_IMPORTER_H_ cjs-128.1/cjs/internal.cpp0000664000175000017500000004604515116312211014331 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh #include #include // for size_t #include #include // for unique_ptr #include #include #include #include // for JS_CallFunction #include #include #include #include #include // for JSEXN_ERR #include #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include #include #include // for JS_NewPlainObject, JS_ObjectIsFunction #include // for JS_GetObjectFunction, SetFunctionNativeReserved #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/global.h" #include "cjs/internal.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/module.h" #include "util/log.h" #include "util/misc.h" namespace mozilla { union Utf8Unit; } // NOTE: You have to be very careful in this file to only do operations within // the correct global! /** * gjs_load_internal_module: * * @brief Loads a module source from an internal resource, * resource:///org/cinnamon/cjs/modules/internal/{#identifier}.js, registers it in * the internal global's module registry, and proceeds to compile, initialize, * and evaluate the module. * * @param cx the current JSContext * @param identifier the identifier of the internal module * * @returns whether an error occurred while loading or evaluating the module. */ bool gjs_load_internal_module(JSContext* cx, const char* identifier) { GjsAutoChar full_path(g_strdup_printf( "resource:///org/cinnamon/cjs/modules/internal/%s.js", identifier)); gjs_debug(GJS_DEBUG_IMPORTER, "Loading internal module '%s' (%s)", identifier, full_path.get()); GjsAutoChar script; size_t script_len; if (!gjs_load_internal_source(cx, full_path, script.out(), &script_len)) return false; JS::SourceText buf; if (!buf.init(cx, script.get(), script_len, JS::SourceOwnership::Borrowed)) return false; JS::CompileOptions options(cx); options.setIntroductionType("Internal Module Bootstrap"); options.setFileAndLine(full_path, 1); options.setSelfHostingMode(false); Gjs::AutoInternalRealm ar{cx}; JS::RootedValue ignored(cx); return JS::Evaluate(cx, options, buf, &ignored); } static bool handle_wrong_args(JSContext* cx) { gjs_log_exception(cx); g_error("Wrong invocation of internal code"); return false; } /** * gjs_internal_set_global_module_loader: * * @brief Sets the MODULE_LOADER slot of the passed global object. * The second argument should be an instance of ModuleLoader or * InternalModuleLoader. Its moduleResolveHook and moduleLoadHook properties * will be called. * * @returns guaranteed to return true or assert. */ bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject global(cx), loader(cx); if (!gjs_parse_call_args(cx, "setGlobalModuleLoader", args, "oo", "global", &global, "loader", &loader)) return handle_wrong_args(cx); gjs_set_global_slot(global, GjsGlobalSlot::MODULE_LOADER, JS::ObjectValue(*loader)); args.rval().setUndefined(); return true; } /** * compile_module: * * @brief Compiles the a module source text into an internal #Module object * given the module's URI as the first argument. * * @param cx the current JSContext * @param uri The URI of the module * @param source The source text of the module * @param v_module_out Return location for the module as a JS value * * @returns whether an error occurred while compiling the module. */ static bool compile_module(JSContext* cx, const JS::UniqueChars& uri, JS::HandleString source, JS::MutableHandleValue v_module_out) { JS::CompileOptions options(cx); options.setFileAndLine(uri.get(), 1).setSourceIsLazy(false); size_t text_len; char16_t* text; if (!gjs_string_get_char16_data(cx, source, &text, &text_len)) return false; JS::SourceText buf; if (!buf.init(cx, text, text_len, JS::SourceOwnership::TakeOwnership)) return false; JS::RootedObject new_module(cx, JS::CompileModule(cx, options, buf)); if (!new_module) return false; v_module_out.setObject(*new_module); return true; } /** * gjs_internal_compile_internal_module: * * @brief Compiles a module source text within the internal global's realm. * * NOTE: Modules compiled with this function can only be executed * within the internal global's realm. * * @param cx the current JSContext * @param argc * @param vp * * @returns whether an error occurred while compiling the module. */ bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); Gjs::AutoInternalRealm ar{cx}; JS::UniqueChars uri; JS::RootedString source(cx); if (!gjs_parse_call_args(cx, "compileInternalModule", args, "sS", "uri", &uri, "source", &source)) return handle_wrong_args(cx); return compile_module(cx, uri, source, args.rval()); } /** * gjs_internal_compile_module: * * @brief Compiles a module source text within the main realm. * * NOTE: Modules compiled with this function can only be executed * within the main realm. * * @param cx the current JSContext * @param argc * @param vp * * @returns whether an error occurred while compiling the module. */ bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); Gjs::AutoMainRealm ar{cx}; JS::UniqueChars uri; JS::RootedString source(cx); if (!gjs_parse_call_args(cx, "compileModule", args, "sS", "uri", &uri, "source", &source)) return handle_wrong_args(cx); return compile_module(cx, uri, source, args.rval()); } /** * gjs_internal_set_module_private: * * @brief Sets the private object of an internal #Module object. * The private object must be a #JSObject. * * @param cx the current JSContext * @param argc * @param vp * * @returns whether an error occurred while setting the private. */ bool gjs_internal_set_module_private(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject module(cx), private_obj(cx); if (!gjs_parse_call_args(cx, "setModulePrivate", args, "oo", "module", &module, "private", &private_obj)) return handle_wrong_args(cx); JS::SetModulePrivate(module, JS::ObjectValue(*private_obj)); return true; } /** * gjs_internal_get_registry: * * @brief Retrieves the module registry for the passed global object. * * @param cx the current JSContext * @param argc * @param vp * * @returns whether an error occurred while retrieving the registry. */ bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject global(cx); if (!gjs_parse_call_args(cx, "getRegistry", args, "o", "global", &global)) return handle_wrong_args(cx); JSAutoRealm ar(cx, global); JS::RootedObject registry(cx, gjs_get_module_registry(global)); args.rval().setObject(*registry); return true; } bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { using AutoHashTable = GjsAutoPointer; using AutoURI = GjsAutoPointer; JS::CallArgs args = CallArgsFromVp(argc, vp); JS::RootedString string_arg(cx); if (!gjs_parse_call_args(cx, "parseUri", args, "S", "uri", &string_arg)) return handle_wrong_args(cx); JS::UniqueChars uri = JS_EncodeStringToUTF8(cx, string_arg); if (!uri) return false; GjsAutoError error; AutoURI parsed = g_uri_parse(uri.get(), G_URI_FLAGS_NONE, &error); if (!parsed) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Attempted to import invalid URI: %s (%s)", uri.get(), error->message); return false; } JS::RootedObject query_obj(cx, JS_NewPlainObject(cx)); if (!query_obj) return false; const char* raw_query = g_uri_get_query(parsed); if (raw_query) { AutoHashTable query = g_uri_parse_params(raw_query, -1, "&", G_URI_PARAMS_NONE, &error); if (!query) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Attempted to import invalid URI: %s (%s)", uri.get(), error->message); return false; } GHashTableIter iter; g_hash_table_iter_init(&iter, query); void* key_ptr; void* value_ptr; while (g_hash_table_iter_next(&iter, &key_ptr, &value_ptr)) { auto* key = static_cast(key_ptr); auto* value = static_cast(value_ptr); JS::ConstUTF8CharsZ value_chars{value, strlen(value)}; JS::RootedString value_str(cx, JS_NewStringCopyUTF8Z(cx, value_chars)); if (!value_str || !JS_DefineProperty(cx, query_obj, key, value_str, JSPROP_ENUMERATE)) return false; } } JS::RootedObject return_obj(cx, JS_NewPlainObject(cx)); if (!return_obj) return false; // JS_NewStringCopyZ() used here and below because the URI components are // %-encoded, meaning ASCII-only JS::RootedString scheme(cx, JS_NewStringCopyZ(cx, g_uri_get_scheme(parsed))); if (!scheme) return false; JS::RootedString host(cx, JS_NewStringCopyZ(cx, g_uri_get_host(parsed))); if (!host) return false; JS::RootedString path(cx, JS_NewStringCopyZ(cx, g_uri_get_path(parsed))); if (!path) return false; GjsAutoChar no_query_str = g_uri_to_string_partial(parsed, G_URI_HIDE_QUERY); JS::RootedString uri_no_query{cx, JS_NewStringCopyZ(cx, no_query_str)}; if (!uri_no_query) return false; if (!JS_DefineProperty(cx, return_obj, "uri", uri_no_query, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "uriWithQuery", string_arg, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "scheme", scheme, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "host", host, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "path", path, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, return_obj, "query", query_obj, JSPROP_ENUMERATE)) return false; args.rval().setObject(*return_obj); return true; } bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars uri, relative_path; if (!gjs_parse_call_args(cx, "resolveRelativeResourceOrFile", args, "ss", "uri", &uri, "relativePath", &relative_path)) return handle_wrong_args(cx); GjsAutoUnref module_file = g_file_new_for_uri(uri.get()); if (module_file) { GjsAutoChar output_uri = g_uri_resolve_relative( uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr); JS::ConstUTF8CharsZ uri_chars(output_uri, strlen(output_uri)); JS::RootedString retval(cx, JS_NewStringCopyUTF8Z(cx, uri_chars)); if (!retval) return false; args.rval().setString(retval); return true; } args.rval().setNull(); return true; } bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); JS::UniqueChars uri; if (!gjs_parse_call_args(cx, "loadResourceOrFile", args, "s", "uri", &uri)) return handle_wrong_args(cx); GjsAutoUnref file = g_file_new_for_uri(uri.get()); char* contents; size_t length; GjsAutoError error; if (!g_file_load_contents(file, /* cancellable = */ nullptr, &contents, &length, /* etag_out = */ nullptr, &error)) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Unable to load file from: %s (%s)", uri.get(), error->message); return false; } JS::ConstUTF8CharsZ contents_chars{contents, length}; JS::RootedString contents_str(cx, JS_NewStringCopyUTF8Z(cx, contents_chars)); g_free(contents); if (!contents_str) return false; args.rval().setString(contents_str); return true; } bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); JS::UniqueChars uri; if (!gjs_parse_call_args(cx, "uriExists", args, "!s", "uri", &uri)) return handle_wrong_args(cx); GjsAutoUnref file = g_file_new_for_uri(uri.get()); args.rval().setBoolean(g_file_query_exists(file, nullptr)); return true; } class PromiseData { public: JSContext* cx; private: JS::PersistentRooted m_resolve; JS::PersistentRooted m_reject; JS::HandleFunction resolver() { return m_resolve; } JS::HandleFunction rejecter() { return m_reject; } public: explicit PromiseData(JSContext* a_cx, JSFunction* resolve, JSFunction* reject) : cx(a_cx), m_resolve(cx, resolve), m_reject(cx, reject) {} static PromiseData* from_ptr(void* ptr) { return static_cast(ptr); } // Adapted from SpiderMonkey js::RejectPromiseWithPendingError() // https://searchfox.org/mozilla-central/rev/95cf843de977805a3951f9137f5ff1930599d94e/js/src/builtin/Promise.cpp#4435 void reject_with_pending_exception() { JS::RootedValue exception(cx); bool ok GJS_USED_ASSERT = JS_GetPendingException(cx, &exception); g_assert(ok && "Cannot reject a promise with an uncatchable exception"); JS::RootedValueArray<1> args(cx); args[0].set(exception); JS::RootedValue ignored_rval(cx); ok = JS_CallFunction(cx, /* this_obj = */ nullptr, rejecter(), args, &ignored_rval); g_assert(ok && "Failed rejecting promise"); } void resolve(JS::Value result) { JS::RootedValueArray<1> args(cx); args[0].set(result); JS::RootedValue ignored_rval(cx); bool ok GJS_USED_ASSERT = JS_CallFunction( cx, /* this_obj = */ nullptr, resolver(), args, &ignored_rval); g_assert(ok && "Failed resolving promise"); } }; static void load_async_callback(GObject* file, GAsyncResult* res, void* data) { std::unique_ptr promise(PromiseData::from_ptr(data)); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(promise->cx); gjs->main_loop_release(); Gjs::AutoMainRealm ar{gjs}; char* contents; size_t length; GjsAutoError error; if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length, /* etag_out = */ nullptr, &error)) { GjsAutoChar uri = g_file_get_uri(G_FILE(file)); gjs_throw_custom(promise->cx, JSEXN_ERR, "ImportError", "Unable to load file async from: %s (%s)", uri.get(), error->message); promise->reject_with_pending_exception(); return; } JS::RootedValue text(promise->cx); bool ok = gjs_string_from_utf8_n(promise->cx, contents, length, &text); g_free(contents); if (!ok) { promise->reject_with_pending_exception(); return; } promise->resolve(text); } GJS_JSAPI_RETURN_CONVENTION static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); JS::RootedObject resolve(cx), reject(cx); if (!gjs_parse_call_args(cx, "executor", args, "oo", "resolve", &resolve, "reject", &reject)) return handle_wrong_args(cx); g_assert(JS_ObjectIsFunction(resolve) && "Executor called weirdly"); g_assert(JS_ObjectIsFunction(reject) && "Executor called weirdly"); JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0); g_assert(!priv_value.isNull() && "Executor called twice"); GjsAutoUnref file = G_FILE(priv_value.toPrivate()); g_assert(file && "Executor called twice"); // We now own the GFile, and will pass the reference to the GAsyncResult, so // remove it from the executor's private slot so it doesn't become dangling js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue()); auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve), JS_GetObjectFunction(reject)); // Hold the main loop until this function resolves... GjsContextPrivate::from_cx(cx)->main_loop_hold(); g_file_load_contents_async(file, nullptr, load_async_callback, data); args.rval().setUndefined(); return true; } bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); JS::UniqueChars uri; if (!gjs_parse_call_args(cx, "loadResourceOrFileAsync", args, "s", "uri", &uri)) return handle_wrong_args(cx); GjsAutoUnref file = g_file_new_for_uri(uri.get()); JS::RootedObject executor(cx, JS_GetFunctionObject(js::NewFunctionWithReserved( cx, load_async_executor, 2, 0, "loadResourceOrFileAsync executor"))); if (!executor) return false; // Stash the file object for the callback to find later; executor owns it js::SetFunctionNativeReserved(executor, 0, JS::PrivateValue(file.copy())); JSObject* promise = JS::NewPromiseObject(cx, executor); if (!promise) return false; args.rval().setObject(*promise); return true; } cjs-128.1/cjs/internal.h0000664000175000017500000000342415116312211013770 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh #ifndef GJS_INTERNAL_H_ #define GJS_INTERNAL_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_load_internal_module(JSContext* cx, const char* identifier); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_compile_module(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_compile_internal_module(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_get_registry(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_set_global_module_loader(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_set_module_private(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_load_resource_or_file_async(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_uri_exists(JSContext* cx, unsigned argc, JS::Value* vp); #endif // GJS_INTERNAL_H_ cjs-128.1/cjs/jsapi-class.h0000664000175000017500000000532015116312211014362 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento #ifndef GJS_JSAPI_CLASS_H_ #define GJS_JSAPI_CLASS_H_ #include #include #include #include // for JSNative #include #include #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_init_class_dynamic( JSContext* cx, JS::HandleObject in_object, JS::HandleObject parent_proto, const char* ns_name, const char* class_name, const JSClass* clasp, JSNative constructor_native, unsigned nargs, JSPropertySpec* ps, JSFunctionSpec* fs, JSPropertySpec* static_ps, JSFunctionSpec* static_fs, JS::MutableHandleObject prototype, JS::MutableHandleObject constructor); [[nodiscard]] bool gjs_typecheck_instance(JSContext* cx, JS::HandleObject obj, const JSClass* static_clasp, bool throw_error); GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_construct_object_dynamic(JSContext *cx, JS::HandleObject proto, const JS::HandleValueArray& args); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_property_dynamic(JSContext*, JS::HandleObject proto, const char* prop_name, JS::HandleId, const char* func_namespace, JSNative getter, JS::HandleValue getter_slot, JSNative setter, JS::HandleValue setter_slot, unsigned flags); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_define_property_dynamic(JSContext* cx, JS::HandleObject proto, const char* prop_name, JS::HandleId id, const char* func_namespace, JSNative getter, JSNative setter, JS::HandleValue private_slot, unsigned flags) { return gjs_define_property_dynamic(cx, proto, prop_name, id, func_namespace, getter, private_slot, setter, private_slot, flags); } [[nodiscard]] JS::Value gjs_dynamic_property_private_slot( JSObject* accessor_obj); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_in_prototype_chain(JSContext* cx, JS::HandleObject proto, JS::HandleObject check_obj, bool* is_in_chain); #endif // GJS_JSAPI_CLASS_H_ cjs-128.1/cjs/jsapi-dynamic-class.cpp0000664000175000017500000002415715116312211016350 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Giovanni Campagna #include #include // for strlen #include #include #include // for JSNative #include #include #include // for JSEXN_TYPEERR #include // for GetClass #include // for JS_DefineFunctions, JS_DefinePro... #include // for GetRealmObjectPrototype #include #include #include #include // for JS_GetFunctionObject, JS_GetPrototype #include // for GetFunctionNativeReserved, NewFun... #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" struct JSFunctionSpec; struct JSPropertySpec; namespace JS { class HandleValueArray; } /* Reserved slots of JSNative accessor wrappers */ enum { DYNAMIC_PROPERTY_PRIVATE_SLOT, }; bool gjs_init_class_dynamic(JSContext* context, JS::HandleObject in_object, JS::HandleObject parent_proto, const char* ns_name, const char* class_name, const JSClass* clasp, JSNative constructor_native, unsigned nargs, JSPropertySpec* proto_ps, JSFunctionSpec* proto_fs, JSPropertySpec* static_ps, JSFunctionSpec* static_fs, JS::MutableHandleObject prototype, JS::MutableHandleObject constructor) { /* Without a name, JS_NewObject fails */ g_assert (clasp->name != NULL); /* gjs_init_class_dynamic only makes sense for instantiable classes, use JS_InitClass for static classes like Math */ g_assert (constructor_native != NULL); /* Class initialization consists of five parts: - building a prototype - defining prototype properties and functions - building a constructor and defining it on the right object - defining constructor properties and functions - linking the constructor and the prototype, so that JS_NewObjectForConstructor can find it */ if (parent_proto) { prototype.set(JS_NewObjectWithGivenProto(context, clasp, parent_proto)); } else { /* JS_NewObject will use Object.prototype as the prototype if the * clasp's constructor is not a built-in class. */ prototype.set(JS_NewObject(context, clasp)); } if (!prototype) return false; if (proto_ps && !JS_DefineProperties(context, prototype, proto_ps)) return false; if (proto_fs && !JS_DefineFunctions(context, prototype, proto_fs)) return false; GjsAutoChar full_function_name = g_strdup_printf("%s_%s", ns_name, class_name); JSFunction* constructor_fun = JS_NewFunction(context, constructor_native, nargs, JSFUN_CONSTRUCTOR, full_function_name); if (!constructor_fun) return false; constructor.set(JS_GetFunctionObject(constructor_fun)); if (static_ps && !JS_DefineProperties(context, constructor, static_ps)) return false; if (static_fs && !JS_DefineFunctions(context, constructor, static_fs)) return false; if (!JS_LinkConstructorAndPrototype(context, constructor, prototype)) return false; /* The constructor defined by JS_InitClass has no property attributes, but this is a more useful default for gjs */ return JS_DefineProperty(context, in_object, class_name, constructor, GJS_MODULE_PROP_FLAGS); } [[nodiscard]] static const char* format_dynamic_class_name(const char* name) { if (g_str_has_prefix(name, "_private_")) return name + strlen("_private_"); else return name; } bool gjs_typecheck_instance(JSContext *context, JS::HandleObject obj, const JSClass *static_clasp, bool throw_error) { if (!JS_InstanceOf(context, obj, static_clasp, NULL)) { if (throw_error) { const JSClass* obj_class = JS::GetClass(obj); gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, "Object %p is not a subclass of %s, it's a %s", obj.get(), static_clasp->name, format_dynamic_class_name(obj_class->name)); } return false; } return true; } JSObject* gjs_construct_object_dynamic(JSContext *context, JS::HandleObject proto, const JS::HandleValueArray& args) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject constructor(context); if (!gjs_object_require_property(context, proto, "prototype", atoms.constructor(), &constructor)) return NULL; JS::RootedValue v_constructor(context, JS::ObjectValue(*constructor)); JS::RootedObject object(context); if (!JS::Construct(context, v_constructor, args, &object)) return nullptr; return object; } GJS_JSAPI_RETURN_CONVENTION static JSObject * define_native_accessor_wrapper(JSContext *cx, JSNative call, unsigned nargs, const char *func_name, JS::HandleValue private_slot) { JSFunction *func = js::NewFunctionWithReserved(cx, call, nargs, 0, func_name); if (!func) return nullptr; JSObject *func_obj = JS_GetFunctionObject(func); js::SetFunctionNativeReserved(func_obj, DYNAMIC_PROPERTY_PRIVATE_SLOT, private_slot); return func_obj; } /** * gjs_define_property_dynamic: * @cx: the #JSContext * @proto: the prototype of the object, on which to define the property * @prop_name: name of the property or field in GObject, visible to JS code * @func_namespace: string from which the internal names for the getter and * setter functions are built, not visible to JS code * @getter: getter function * @setter: setter function * @private_slot: private data in the form of a #JS::Value that the getter and * setter will have access to * @flags: additional flags to define the property with (other than the ones * required for a property with native getter/setter) * * When defining properties in a GBoxed or GObject, we can't have a separate * getter and setter for each one, since the properties are defined dynamically. * Therefore we must have one getter and setter for all the properties we define * on all the types. In order to have that, we must provide the getter and * setter with private data, e.g. the field index for GBoxed, in a "reserved * slot" for which we must unfortunately use the jsfriendapi. * * Returns: %true on success, %false if an exception is pending on @cx. */ bool gjs_define_property_dynamic(JSContext* cx, JS::HandleObject proto, const char* prop_name, JS::HandleId id, const char* func_namespace, JSNative getter, JS::HandleValue getter_slot, JSNative setter, JS::HandleValue setter_slot, unsigned flags) { GjsAutoChar getter_name = g_strconcat(func_namespace, "_get::", prop_name, nullptr); GjsAutoChar setter_name = g_strconcat(func_namespace, "_set::", prop_name, nullptr); JS::RootedObject getter_obj( cx, define_native_accessor_wrapper(cx, getter, 0, getter_name, getter_slot)); if (!getter_obj) return false; JS::RootedObject setter_obj( cx, define_native_accessor_wrapper(cx, setter, 1, setter_name, setter_slot)); if (!setter_obj) return false; if (id.isVoid()) { return JS_DefineProperty(cx, proto, prop_name, getter_obj, setter_obj, flags); } return JS_DefinePropertyById(cx, proto, id, getter_obj, setter_obj, flags); } /** * gjs_dynamic_property_private_slot: * @accessor_obj: the getter or setter as a function object, i.e. * `&args.callee()` in the #JSNative function * * For use in dynamic property getters and setters (see * gjs_define_property_dynamic()) to retrieve the private data passed there. * * Returns: the JS::Value that was passed to gjs_define_property_dynamic(). */ JS::Value gjs_dynamic_property_private_slot(JSObject *accessor_obj) { return js::GetFunctionNativeReserved(accessor_obj, DYNAMIC_PROPERTY_PRIVATE_SLOT); } /** * gjs_object_in_prototype_chain: * @cx: * @proto: The prototype which we are checking if @check_obj has in its chain * @check_obj: The object to check * @is_in_chain: (out): Whether @check_obj has @proto in its prototype chain * * Similar to JS_HasInstance() but takes into account abstract classes defined * with JS_InitClass(), which JS_HasInstance() does not. Abstract classes don't * have constructors, and JS_HasInstance() requires a constructor. * * Returns: false if an exception was thrown, true otherwise. */ bool gjs_object_in_prototype_chain(JSContext* cx, JS::HandleObject proto, JS::HandleObject check_obj, bool* is_in_chain) { JS::RootedObject object_prototype(cx, JS::GetRealmObjectPrototype(cx)); if (!object_prototype) return false; JS::RootedObject proto_iter(cx); if (!JS_GetPrototype(cx, check_obj, &proto_iter)) return false; while (proto_iter != object_prototype) { if (proto_iter == proto) { *is_in_chain = true; return true; } if (!JS_GetPrototype(cx, proto_iter, &proto_iter)) return false; } *is_in_chain = false; return true; } cjs-128.1/cjs/jsapi-util-args.h0000664000175000017500000003436415116312211015176 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. // SPDX-FileContributor: Authored by: Philip Chimento #ifndef GJS_JSAPI_UTIL_ARGS_H_ #define GJS_JSAPI_UTIL_ARGS_H_ #include #include #include // for enable_if, is_enum, is_same #include // for move #include #include #include #include #include #include #include #include // for UniqueChars #include #include // for GenericErrorResult #include // IWYU pragma: keep #include "cjs/jsapi-util.h" #include "cjs/macros.h" namespace detail { [[nodiscard]] GJS_ALWAYS_INLINE static inline bool check_nullable( const char*& fchar, const char*& fmt_string) { if (*fchar != '?') return false; fchar++; fmt_string++; g_assert(((void) "Invalid format string, parameter required after '?'", *fchar != '\0')); return true; } class ParseArgsErr { GjsAutoChar m_message; public: explicit ParseArgsErr(const char* literal_msg) : m_message(literal_msg, GjsAutoTakeOwnership{}) {} template ParseArgsErr(const char* format_string, F param) : m_message(g_strdup_printf(format_string, param)) {} const char* message() const { return m_message.get(); } }; template inline constexpr auto Err(Args... args) { return mozilla::GenericErrorResult{ ParseArgsErr{std::forward(args)...}}; } using ParseArgsResult = JS::Result; /* This preserves the previous behaviour of gjs_parse_args(), but maybe we want * to use JS::ToBoolean instead? */ GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext*, char c, bool nullable, JS::HandleValue value, bool* ref) { if (c != 'b') return Err("Wrong type for %c, got bool*", c); if (!value.isBoolean()) return Err("Not a boolean"); if (nullable) return Err("Invalid format string combination ?b"); *ref = value.toBoolean(); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext*, char c, bool nullable, JS::HandleValue value, JS::MutableHandleObject ref) { if (c != 'o') return Err("Wrong type for %c, got JS::MutableHandleObject", c); if (nullable && value.isNull()) { ref.set(nullptr); return JS::Ok(); } if (!value.isObject()) return Err("Not an object"); ref.set(&value.toObject()); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, JS::UniqueChars* ref) { if (c != 's') return Err("Wrong type for %c, got JS::UniqueChars*", c); if (nullable && value.isNull()) { ref->reset(); return JS::Ok(); } JS::UniqueChars tmp = gjs_string_to_utf8(cx, value); if (!tmp) return Err("Couldn't convert to string"); *ref = std::move(tmp); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, GjsAutoChar* ref) { if (c != 'F') return Err("Wrong type for %c, got GjsAutoChar*", c); if (nullable && value.isNull()) { ref->release(); return JS::Ok(); } if (!gjs_string_to_filename(cx, value, ref)) return Err("Couldn't convert to filename"); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext*, char c, bool nullable, JS::HandleValue value, JS::MutableHandleString ref) { if (c != 'S') return Err("Wrong type for %c, got JS::MutableHandleString", c); if (nullable && value.isNull()) { ref.set(nullptr); return JS::Ok(); } if (!value.isString()) return Err("Not a string"); ref.set(value.toString()); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, int32_t* ref) { if (c != 'i') return Err("Wrong type for %c, got int32_t*", c); if (nullable) return Err("Invalid format string combination ?i"); if (!JS::ToInt32(cx, value, ref)) return Err("Couldn't convert to integer"); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, uint32_t* ref) { double num; if (c != 'u') return Err("Wrong type for %c, got uint32_t*", c); if (nullable) return Err("Invalid format string combination ?u"); if (!value.isNumber() || !JS::ToNumber(cx, value, &num)) return Err("Couldn't convert to unsigned integer"); if (num > G_MAXUINT32 || num < 0) return Err("Value %f is out of range", num); *ref = num; return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, int64_t* ref) { if (c != 't') return Err("Wrong type for %c, got int64_t*", c); if (nullable) return Err("Invalid format string combination ?t"); if (!JS::ToInt64(cx, value, ref)) return Err("Couldn't convert to 64-bit integer"); return JS::Ok(); } GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, double* ref) { if (c != 'f') return Err("Wrong type for %c, got double*", c); if (nullable) return Err("Invalid format string combination ?f"); if (!JS::ToNumber(cx, value, ref)) return Err("Couldn't convert to double"); return JS::Ok(); } /* Special case: treat pointer-to-enum as pointer-to-int, but use enable_if to * prevent instantiation for any other types besides pointer-to-enum */ template , int> = 0> GJS_ALWAYS_INLINE static inline ParseArgsResult assign(JSContext* cx, char c, bool nullable, JS::HandleValue value, T* ref) { /* Sadly, we cannot use std::underlying_type here; the underlying type of * an enum is implementation-defined, so it would not be clear what letter * to use in the format string. For the same reason, we can only support * enum types that are the same width as int. * Additionally, it would be nice to be able to check whether the resulting * value was in range for the enum, but that is not possible (yet?) */ static_assert(sizeof(T) == sizeof(int), "Short or wide enum types not supported"); return assign(cx, c, nullable, value, reinterpret_cast(ref)); } template static inline void free_if_necessary(T param_ref [[maybe_unused]]) {} template GJS_ALWAYS_INLINE static inline void free_if_necessary( JS::Rooted* param_ref) { // This is not exactly right, since before we consumed a JS::Value there may // have been something different inside the handle. But it has already been // clobbered at this point anyhow. JS::MutableHandle(param_ref).set(nullptr); } template GJS_JSAPI_RETURN_CONVENTION static bool parse_call_args_helper( JSContext* cx, const char* function_name, const JS::CallArgs& args, const char*& fmt_required, const char*& fmt_optional, unsigned param_ix, const char* param_name, T param_ref) { bool nullable = false; const char *fchar = fmt_required; g_return_val_if_fail(param_name, false); if (*fchar != '\0') { nullable = check_nullable(fchar, fmt_required); fmt_required++; } else { /* No more args passed in JS, only optional formats left */ if (args.length() <= param_ix) return true; fchar = fmt_optional; g_assert(((void) "Wrong number of parameters passed to gjs_parse_call_args()", *fchar != '\0')); nullable = check_nullable(fchar, fmt_optional); fmt_optional++; } ParseArgsResult res = assign(cx, *fchar, nullable, args[param_ix], param_ref); if (res.isErr()) { /* Our error messages are going to be more useful than whatever was * thrown by the various conversion functions */ const char* message = res.inspectErr().message(); JS_ClearPendingException(cx); gjs_throw(cx, "Error invoking %s, at argument %d (%s): %s", function_name, param_ix, param_name, message); return false; } return true; } template GJS_JSAPI_RETURN_CONVENTION static bool parse_call_args_helper( JSContext* cx, const char* function_name, const JS::CallArgs& args, const char*& fmt_required, const char*& fmt_optional, unsigned param_ix, const char* param_name, T param_ref, Args... params) { if (!parse_call_args_helper(cx, function_name, args, fmt_required, fmt_optional, param_ix, param_name, param_ref)) return false; bool retval = parse_call_args_helper(cx, function_name, args, fmt_required, fmt_optional, ++param_ix, params...); // We still own JSString/JSObject in the error case, free any we converted if (!retval) free_if_necessary(param_ref); return retval; } } // namespace detail /* Empty-args version of the template */ GJS_JSAPI_RETURN_CONVENTION [[maybe_unused]] static bool gjs_parse_call_args( JSContext* cx, const char* function_name, const JS::CallArgs& args, const char* format) { bool ignore_trailing_args = false; if (*format == '!') { ignore_trailing_args = true; format++; } g_assert(((void) "Wrong number of parameters passed to gjs_parse_call_args()", *format == '\0')); if (!ignore_trailing_args && args.length() > 0) { gjs_throw(cx, "Error invoking %s: Expected 0 arguments, got %d", function_name, args.length()); return false; } return true; } /** * gjs_parse_call_args: * @context: * @function_name: The name of the function being called * @args: #JS::CallArgs from #JSNative function * @format: Printf-like format specifier containing the expected arguments * @params: for each character in @format, a pair of const char * which is the * name of the argument, and a location to store the value. The type of * location argument depends on the format character, as described below. * * This function is inspired by Python's PyArg_ParseTuple for those * familiar with it. It takes a format specifier which gives the * types of the expected arguments, and a list of argument names and * value location pairs. The currently accepted format specifiers are: * * b: A boolean (pass a bool *) * s: A string, converted into UTF-8 (pass a JS::UniqueChars*) * F: A string, converted into "filename encoding" (i.e. active locale) (pass * a GjsAutoChar *) * S: A string, no conversion (pass a JS::MutableHandleString) * i: A number, will be converted to a 32-bit int (pass an int32_t * or a * pointer to an enum type) * u: A number, converted into a 32-bit unsigned int (pass a uint32_t *) * t: A 64-bit number, converted into a 64-bit int (pass an int64_t *) * f: A number, will be converted into a double (pass a double *) * o: A JavaScript object (pass a JS::MutableHandleObject) * * If the first character in the format string is a '!', then JS is allowed * to pass extra arguments that are ignored, to the function. * * The '|' character introduces optional arguments. All format specifiers * after a '|' when not specified, do not cause any changes in the C * value location. * * A prefix character '?' in front of 's', 'F', 'S', or 'o' means that the next * value may be null. For 's' or 'F' a null pointer is returned, for 'S' or 'o' * the handle is set to null. */ template GJS_JSAPI_RETURN_CONVENTION static bool gjs_parse_call_args( JSContext* cx, const char* function_name, const JS::CallArgs& args, const char* format, Args... params) { const char *fmt_iter, *fmt_required, *fmt_optional; unsigned n_required = 0, n_total = 0; bool optional_args = false, ignore_trailing_args = false; if (*format == '!') { ignore_trailing_args = true; format++; } for (fmt_iter = format; *fmt_iter; fmt_iter++) { switch (*fmt_iter) { case '|': n_required = n_total; optional_args = true; continue; case '?': continue; default: n_total++; } } if (!optional_args) n_required = n_total; g_assert(((void) "Wrong number of parameters passed to gjs_parse_call_args()", sizeof...(Args) / 2 == n_total)); if (!args.requireAtLeast(cx, function_name, n_required)) return false; if (!ignore_trailing_args && args.length() > n_total) { if (n_required == n_total) { gjs_throw(cx, "Error invoking %s: Expected %d arguments, got %d", function_name, n_required, args.length()); } else { gjs_throw(cx, "Error invoking %s: Expected minimum %d arguments (and %d optional), got %d", function_name, n_required, n_total - n_required, args.length()); } return false; } GjsAutoStrv parts = g_strsplit(format, "|", 2); fmt_required = parts.get()[0]; fmt_optional = parts.get()[1]; // may be null return detail::parse_call_args_helper(cx, function_name, args, fmt_required, fmt_optional, 0, params...); } #endif // GJS_JSAPI_UTIL_ARGS_H_ cjs-128.1/cjs/jsapi-util-error.cpp0000664000175000017500000002154615116312211015724 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include #include #include #include #include #include #include #include // for GCHashSet #include // for DefaultHasher #include #include #include #include // for BuildStackString #include // for JS_NewStringCopyUTF8Z #include #include // for UniqueChars #include #include #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" #include "util/misc.h" using CauseSet = JS::GCHashSet, js::SystemAllocPolicy>; GJS_JSAPI_RETURN_CONVENTION static bool get_last_cause(JSContext* cx, JS::HandleValue v_exc, JS::MutableHandleObject last_cause, JS::MutableHandle seen_causes) { if (!v_exc.isObject()) { last_cause.set(nullptr); return true; } JS::RootedObject exc(cx, &v_exc.toObject()); CauseSet::AddPtr entry = seen_causes.lookupForAdd(exc); if (entry) { last_cause.set(nullptr); return true; } if (!seen_causes.add(entry, exc)) { JS_ReportOutOfMemory(cx); return false; } JS::RootedValue v_cause(cx); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_GetPropertyById(cx, exc, atoms.cause(), &v_cause)) return false; if (v_cause.isUndefined()) { last_cause.set(exc); return true; } return get_last_cause(cx, v_cause, last_cause, seen_causes); } GJS_JSAPI_RETURN_CONVENTION static bool append_new_cause(JSContext* cx, JS::HandleValue thrown, JS::HandleValue new_cause, bool* appended) { g_assert(appended && "forgot out parameter"); *appended = false; JS::Rooted seen_causes(cx); JS::RootedObject last_cause{cx}; if (!get_last_cause(cx, thrown, &last_cause, &seen_causes)) return false; if (!last_cause) return true; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_SetPropertyById(cx, last_cause, atoms.cause(), new_cause)) return false; *appended = true; return true; } [[gnu::format(printf, 4, 0)]] static void gjs_throw_valist( JSContext* cx, JSExnType error_kind, const char* error_name, const char* format, va_list args) { GjsAutoChar s = g_strdup_vprintf(format, args); auto fallback = mozilla::MakeScopeExit([cx, &s]() { // try just reporting it to error handler? should not // happen though pretty much JS_ReportErrorUTF8(cx, "Failed to throw exception '%s'", s.get()); }); JS::ConstUTF8CharsZ chars{s.get(), strlen(s.get())}; JS::RootedString message{cx, JS_NewStringCopyUTF8Z(cx, chars)}; if (!message) return; JS::RootedObject saved_frame{cx}; if (!JS::CaptureCurrentStack(cx, &saved_frame)) return; JS::RootedString source_string{cx}; JS::GetSavedFrameSource(cx, /* principals = */ nullptr, saved_frame, &source_string); uint32_t line_num; JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line_num); JS::TaggedColumnNumberOneOrigin tagged_column; JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &tagged_column); JS::ColumnNumberOneOrigin column_num{tagged_column.toLimitedColumnNumber()}; // asserts that this isn't a WASM frame JS::RootedValue v_exc{cx}; if (!JS::CreateError(cx, error_kind, saved_frame, source_string, line_num, column_num, /* report = */ nullptr, message, /* cause = */ JS::NothingHandleValue, &v_exc)) return; if (error_name) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v_name{cx}; JS::RootedObject exc{cx, &v_exc.toObject()}; if (!gjs_string_from_utf8(cx, error_name, &v_name) || !JS_SetPropertyById(cx, exc, atoms.name(), v_name)) return; } if (JS_IsExceptionPending(cx)) { // Often it's unclear whether a given jsapi.h function will throw an // exception, so we will throw ourselves "just in case"; in those cases, // we append the new exception as the cause of the original exception. // The second exception may add more info. JS::RootedValue pending(cx); JS_GetPendingException(cx, &pending); JS::AutoSaveExceptionState saved_exc{cx}; bool appended; if (!append_new_cause(cx, pending, v_exc, &appended)) saved_exc.restore(); if (!appended) gjs_debug(GJS_DEBUG_CONTEXT, "Ignoring second exception: '%s'", s.get()); } else { JS_SetPendingException(cx, v_exc); } fallback.release(); } /* Throws an exception, like "throw new Error(message)" * * If an exception is already set in the context, this will * NOT overwrite it. That's an important semantic since * we want the "root cause" exception. To overwrite, * use JS_ClearPendingException() first. */ void gjs_throw(JSContext *context, const char *format, ...) { va_list args; va_start(args, format); gjs_throw_valist(context, JSEXN_ERR, nullptr, format, args); va_end(args); } /* * Like gjs_throw, but allows to customize the error * class and 'name' property. Mainly used for throwing TypeError instead of * error. */ void gjs_throw_custom(JSContext *cx, JSExnType kind, const char *error_name, const char *format, ...) { va_list args; va_start(args, format); gjs_throw_valist(cx, kind, error_name, format, args); va_end(args); } /** * gjs_throw_literal: * * Similar to gjs_throw(), but does not treat its argument as * a format string. */ void gjs_throw_literal(JSContext *context, const char *string) { gjs_throw(context, "%s", string); } /** * gjs_throw_gerror_message: * * Similar to gjs_throw_gerror(), but does not marshal the GError structure into * JavaScript. Instead, it creates a regular JavaScript Error object and copies * the GError's message into it. * * Use this when handling a GError in an internal function, where the error code * and domain don't matter. So, for example, don't use it to throw errors * around calling from JS into C code. */ bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const& error) { g_return_val_if_fail(error, false); gjs_throw_literal(cx, error->message); return false; } /** * format_saved_frame: * @cx: the #JSContext * @saved_frame: a SavedFrame #JSObject * @indent: (optional): spaces of indentation * * Formats a stack trace as a UTF-8 string. If there are errors, ignores them * and returns null. * If you print this to stderr, you will need to re-encode it in filename * encoding with g_filename_from_utf8(). * * Returns (nullable) (transfer full): unique string */ JS::UniqueChars format_saved_frame(JSContext* cx, JS::HandleObject saved_frame, size_t indent /* = 0 */) { JS::AutoSaveExceptionState saved_exc(cx); JS::RootedString stack_trace(cx); JS::UniqueChars stack_utf8; if (JS::BuildStackString(cx, nullptr, saved_frame, &stack_trace, indent)) stack_utf8 = JS_EncodeStringToUTF8(cx, stack_trace); saved_exc.restore(); return stack_utf8; } void gjs_warning_reporter(JSContext*, JSErrorReport* report) { const char *warning; GLogLevelFlags level; g_assert(report); if (gjs_environment_variable_is_set("GJS_ABORT_ON_OOM") && !report->isWarning() && report->errorNumber == 137) { /* 137, JSMSG_OUT_OF_MEMORY */ g_error("GJS ran out of memory at %s: %i.", report->filename.c_str(), report->lineno); } if (report->isWarning()) { warning = "WARNING"; level = G_LOG_LEVEL_MESSAGE; /* suppress bogus warnings. See mozilla/js/src/js.msg */ if (report->errorNumber == 162) { /* 162, JSMSG_UNDEFINED_PROP: warns every time a lazy property * is resolved, since the property starts out * undefined. When this is a real bug it should usually * fail somewhere else anyhow. */ return; } } else { warning = "REPORTED"; level = G_LOG_LEVEL_WARNING; } g_log(G_LOG_DOMAIN, level, "JS %s: [%s %d]: %s", warning, report->filename.c_str(), report->lineno, report->message().c_str()); } cjs-128.1/cjs/jsapi-util-root.h0000664000175000017500000001773415116312211015227 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. // SPDX-FileCopyrightText: 2019 Canonical, Ltd. #ifndef GJS_JSAPI_UTIL_ROOT_H_ #define GJS_JSAPI_UTIL_ROOT_H_ #include #include // for nullptr_t #include #include #include #include #include #include // for ExposeObjectToActiveJS, GetGCThingZone #include // for SafelyInitialized #include #include #include "util/log.h" namespace JS { template struct GCPolicy; } /* jsapi-util-root.h - Utilities for dealing with the lifetime and ownership of * JS Objects and other things that can be collected by the garbage collector * (collectively called "GC things.") * * GjsMaybeOwned is a multi-purpose wrapper for a JSObject. You can * wrap a thing in one of three ways: * * - trace the object (tie it to the lifetime of another GC thing), * - root the object (keep it alive as long as the wrapper is in existence), * - maintain a weak pointer to the object (not keep it alive at all and have it * possibly be finalized out from under you). * * To trace or maintain a weak pointer, simply assign an object to the * GjsMaybeOwned wrapper. For tracing, you must call the trace() method when * your other GC thing is traced. * * Rooting requires a JSContext so can't just assign a thing of type T. Instead * you need to call the root() method to set up rooting. * * If the thing is rooted, it will be unrooted when the GjsMaybeOwned is * destroyed. * * To switch between one of the three modes, you must first call reset(). This * drops all references to any object and leaves the GjsMaybeOwned in the * same state as if it had just been constructed. */ /* GjsMaybeOwned is intended for use as a member of classes that are allocated * on the heap. Do not allocate GjsMaybeOwned on the stack, and do not allocate * any instances of classes that have it as a member on the stack either. */ class GjsMaybeOwned { private: /* m_root value controls which of these members we can access. When switching * from one to the other, be careful to call the constructor and destructor * of JS::Heap, since they use post barriers. */ JS::Heap m_heap; std::unique_ptr m_root; /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */ inline void debug(const char* what GJS_USED_VERBOSE_LIFECYCLE) { gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "GjsMaybeOwned %p %s", this, what); } void teardown_rooting() { debug("teardown_rooting()"); g_assert(m_root); m_root.reset(); new (&m_heap) JS::Heap(); } public: GjsMaybeOwned() { debug("created"); } ~GjsMaybeOwned() { debug("destroyed"); } // COMPAT: constexpr in C++23 [[nodiscard]] JSObject* get() const { return m_root ? m_root->get() : m_heap.get(); } // Use debug_addr() only for debug logging, because it is unbarriered. // COMPAT: constexpr in C++23 [[nodiscard]] const void* debug_addr() const { return m_root ? m_root->get() : m_heap.unbarrieredGet(); } // COMPAT: constexpr in C++23 bool operator==(JSObject* other) const { if (m_root) return m_root->get() == other; return m_heap == other; } bool operator!=(JSObject* other) const { return !(*this == other); } // We can access the pointer without a read barrier if the only thing we are // are doing with it is comparing it to nullptr. // COMPAT: constexpr in C++23 bool operator==(std::nullptr_t) const { if (m_root) return m_root->get() == nullptr; return m_heap.unbarrieredGet() == nullptr; } bool operator!=(std::nullptr_t) const { return !(*this == nullptr); } // Likewise the truth value does not require a read barrier // COMPAT: constexpr in C++23 explicit operator bool() const { return *this != nullptr; } // You can get a Handle if the thing is rooted, so that you can use this // wrapper with stack rooting. However, you must not do this if the // JSContext can be destroyed while the Handle is live. */ // COMPAT: constexpr in C++23 [[nodiscard]] JS::HandleObject handle() { g_assert(m_root); return *m_root; } /* Roots the GC thing. You must not use this if you're already using the * wrapper to store a non-rooted GC thing. */ void root(JSContext* cx, JSObject* thing) { debug("root()"); g_assert(!m_root); g_assert(!m_heap); m_heap.~Heap(); m_root = std::make_unique(cx, thing); } /* You can only assign directly to the GjsMaybeOwned wrapper in the * non-rooted case. */ void operator=(JSObject* thing) { g_assert(!m_root); m_heap = thing; } /* Marks an object as reachable for one GC with ExposeObjectToActiveJS(). * Use to avoid stopping tracing an object during GC. This makes no sense * in the rooted case. */ void prevent_collection() { debug("prevent_collection()"); g_assert(!m_root); JSObject* obj = m_heap.unbarrieredGet(); // If the object has been swept already, then the zone is nullptr if (!obj || !JS::GetGCThingZone(JS::GCCellPtr(obj))) return; if (!JS::RuntimeHeapIsCollecting()) JS::ExposeObjectToActiveJS(obj); } void reset() { debug("reset()"); if (!m_root) { m_heap = nullptr; return; } teardown_rooting(); } void switch_to_rooted(JSContext* cx) { debug("switch to rooted"); g_assert(!m_root); /* Prevent the thing from being garbage collected while it is in neither * m_heap nor m_root */ JS::RootedObject thing{cx, m_heap}; reset(); root(cx, thing); g_assert(m_root); } void switch_to_unrooted(JSContext* cx) { debug("switch to unrooted"); g_assert(m_root); /* Prevent the thing from being garbage collected while it is in neither * m_heap nor m_root */ JS::RootedObject thing{cx, *m_root}; reset(); m_heap = thing; g_assert(!m_root); } /* Tracing makes no sense in the rooted case, because JS::PersistentRooted * already takes care of that. */ void trace(JSTracer *tracer, const char *name) { debug("trace()"); g_assert(!m_root); JS::TraceEdge(tracer, &m_heap, name); } /* If not tracing, then you must call this method during GC in order to * update the object's location if it was moved, or null it out if it was * finalized. If the object was finalized, returns true. */ bool update_after_gc(JSTracer* trc) { debug("update_after_gc()"); g_assert(!m_root); JS_UpdateWeakPointerAfterGC(trc, &m_heap); return !m_heap; } // COMPAT: constexpr in C++23 [[nodiscard]] bool rooted() const { return m_root != nullptr; } }; namespace Gjs { template class WeakPtr : public JS::Heap { public: using JS::Heap::Heap; using JS::Heap::operator=; }; } // namespace Gjs namespace JS { template struct GCPolicy> { static void trace(JSTracer* trc, Gjs::WeakPtr* thingp, const char* name) { return JS::TraceEdge(trc, thingp, name); } static bool traceWeak(JSTracer* trc, Gjs::WeakPtr* thingp) { return js::gc::TraceWeakEdge(trc, thingp); } static bool needsSweep(JSTracer* trc, const Gjs::WeakPtr* thingp) { Gjs::WeakPtr thing{*thingp}; return !js::gc::TraceWeakEdge(trc, &thing); } }; } // namespace JS #endif // GJS_JSAPI_UTIL_ROOT_H_ cjs-128.1/cjs/jsapi-util-string.cpp0000664000175000017500000005051715116312211016101 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include // for size_t, strlen #include // for ssize_t #include // for copy #include // for operator<<, setfill, setw #include // for operator<<, basic_ostream, ostring... #include // for allocator, char_traits #include #include #include #include #include #include // for AutoCheckCannotGC #include #include // for GetClass #include #include #include #include #include #include // for UniqueChars #include #include // for JS_GetFunctionDisplayId #include // for IdToValue, IsFunctionObject, ... #include #include #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/misc.h" // for _gjs_memdup2 class JSLinearString; GjsAutoChar gjs_hyphen_to_underscore(const char* str) { char *s = g_strdup(str); char *retval = s; while (*(s++) != '\0') { if (*s == '-') *s = '_'; } return retval; } GjsAutoChar gjs_hyphen_to_camel(const char* str) { GjsAutoChar retval = static_cast(g_malloc(strlen(str) + 1)); const char* input_iter = str; char* output_iter = retval.get(); bool uppercase_next = false; while (*input_iter != '\0') { if (*input_iter == '-') { uppercase_next = true; } else if (uppercase_next) { *output_iter++ = g_ascii_toupper(*input_iter); uppercase_next = false; } else { *output_iter++ = *input_iter; } input_iter++; } *output_iter = '\0'; return retval; } /** * gjs_string_to_utf8: * @cx: JSContext * @value: a JS::Value containing a string * * Converts the JSString in @value to UTF-8 and puts it in @utf8_string_p. * * This function is a convenience wrapper around JS_EncodeStringToUTF8() that * typechecks the JS::Value and throws an exception if it's the wrong type. * Don't use this function if you already have a JS::RootedString, or if you * know the value already holds a string; use JS_EncodeStringToUTF8() instead. * * Returns: Unique UTF8 chars, empty on exception throw. */ JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) { if (!value.isString()) { gjs_throw(cx, "Value is not a string, cannot convert to UTF-8"); return nullptr; } JS::RootedString str(cx, value.toString()); return JS_EncodeStringToUTF8(cx, str); } /** * gjs_string_to_utf8_n: * @param cx: the current #JSContext * @param str: a handle to a JSString * @param output a pointer to a JS::UniqueChars * @param output_len a pointer for the length of output * * @brief Converts a JSString to UTF-8 and puts the char array in #output and * its length in #output_len. * * This function handles the boilerblate for unpacking a JSString, determining its * length, and returning the appropriate JS::UniqueChars. This function should generally * be preferred over using JS::DeflateStringToUTF8Buffer directly as it correctly * handles allocation in a JS_Free compatible manner. */ bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output, size_t* output_len) { JSLinearString* linear = JS_EnsureLinearString(cx, str); if (!linear) return false; size_t length = JS::GetDeflatedUTF8StringLength(linear); char* bytes = js_pod_malloc(length + 1); if (!bytes) return false; // Append a zero-terminator to the string. bytes[length] = '\0'; size_t deflated_length [[maybe_unused]] = JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length)); g_assert(deflated_length == length); *output_len = length; *output = JS::UniqueChars(bytes); return true; } /** * gjs_lossy_string_from_utf8: * * @brief Converts an array of UTF-8 characters to a JS string. * Instead of throwing, any invalid characters will be converted * to the UTF-8 invalid character fallback. * * @param cx the current #JSContext * @param utf8_string an array of UTF-8 characters * @param value_p a value to store the resulting string in */ JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string) { JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string)); size_t outlen; JS::UniqueTwoByteChars twobyte_chars( JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen, js::MallocArena) .get()); if (!twobyte_chars) return nullptr; return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen); } /** * gjs_lossy_string_from_utf8_n: * * @brief Provides the same conversion behavior as gjs_lossy_string_from_utf8 * with a fixed length. See gjs_lossy_string_from_utf8() */ JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string, size_t len) { JS::UTF8Chars chars(utf8_string, len); size_t outlen; JS::UniqueTwoByteChars twobyte_chars( JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, chars, &outlen, js::MallocArena) .get()); if (!twobyte_chars) return nullptr; return JS_NewUCStringCopyN(cx, twobyte_chars.get(), outlen); } bool gjs_string_from_utf8(JSContext *context, const char *utf8_string, JS::MutableHandleValue value_p) { JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string)); JS::RootedString str(context, JS_NewStringCopyUTF8Z(context, chars)); if (!str) return false; value_p.setString(str); return true; } bool gjs_string_from_utf8_n(JSContext *cx, const char *utf8_chars, size_t len, JS::MutableHandleValue out) { JS::UTF8Chars chars(utf8_chars, len); JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars)); if (str) out.setString(str); return !!str; } bool gjs_string_to_filename(JSContext *context, const JS::Value filename_val, GjsAutoChar *filename_string) { GjsAutoError error; /* gjs_string_to_filename verifies that filename_val is a string */ JS::UniqueChars tmp = gjs_string_to_utf8(context, filename_val); if (!tmp) return false; error = NULL; *filename_string = g_filename_from_utf8(tmp.get(), -1, nullptr, nullptr, &error); if (!*filename_string) return gjs_throw_gerror_message(context, error); return true; } bool gjs_string_from_filename(JSContext *context, const char *filename_string, ssize_t n_bytes, JS::MutableHandleValue value_p) { gsize written; GjsAutoError error; error = NULL; GjsAutoChar utf8_string = g_filename_to_utf8(filename_string, n_bytes, nullptr, &written, &error); if (error) { gjs_throw(context, "Could not convert UTF-8 string '%s' to a filename: '%s'", filename_string, error->message); return false; } return gjs_string_from_utf8_n(context, utf8_string, written, value_p); } /* Converts a JSString's array of Latin-1 chars to an array of a wider integer * type, by what the compiler believes is the most efficient method possible */ template GJS_JSAPI_RETURN_CONVENTION static bool from_latin1(JSContext* cx, JSString* str, T** data_p, size_t* len_p) { /* No garbage collection should be triggered while we are using the string's * chars. Crash if that happens. */ JS::AutoCheckCannotGC nogc; const JS::Latin1Char *js_data = JS_GetLatin1StringCharsAndLength(cx, nogc, str, len_p); if (js_data == NULL) return false; /* Unicode codepoints 0x00-0xFF are the same as Latin-1 * codepoints, so we can preserve the string length and simply * copy the codepoints to an array of different-sized ints */ *data_p = g_new(T, *len_p); /* This will probably use a loop, unfortunately */ std::copy(js_data, js_data + *len_p, *data_p); return true; } /** * gjs_string_get_char16_data: * @context: js context * @str: a rooted JSString * @data_p: address to return allocated data buffer * @len_p: address to return length of data (number of 16-bit characters) * * Get the binary data (as a sequence of 16-bit characters) in @str. * * Returns: false if exception thrown **/ bool gjs_string_get_char16_data(JSContext *context, JS::HandleString str, char16_t **data_p, size_t *len_p) { if (JS::StringHasLatin1Chars(str)) return from_latin1(context, str, data_p, len_p); /* From this point on, crash if a GC is triggered while we are using * the string's chars */ JS::AutoCheckCannotGC nogc; const char16_t *js_data = JS_GetTwoByteStringCharsAndLength(context, nogc, str, len_p); if (js_data == NULL) return false; mozilla::CheckedInt len_bytes = mozilla::CheckedInt(*len_p) * sizeof(*js_data); if (!len_bytes.isValid()) { JS_ReportOutOfMemory(context); // cannot call gjs_throw, it may GC return false; } *data_p = static_cast(_gjs_memdup2(js_data, len_bytes.value())); return true; } /** * gjs_string_to_ucs4: * @cx: a #JSContext * @str: rooted JSString * @ucs4_string_p: return location for a #gunichar array * @len_p: return location for @ucs4_string_p length * * Returns: true on success, false otherwise in which case a JS error is thrown */ bool gjs_string_to_ucs4(JSContext *cx, JS::HandleString str, gunichar **ucs4_string_p, size_t *len_p) { if (ucs4_string_p == NULL) return true; size_t len; GjsAutoError error; if (JS::StringHasLatin1Chars(str)) return from_latin1(cx, str, ucs4_string_p, len_p); /* From this point on, crash if a GC is triggered while we are using * the string's chars */ JS::AutoCheckCannotGC nogc; const char16_t *utf16 = JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len); if (utf16 == NULL) { gjs_throw(cx, "Failed to get UTF-16 string data"); return false; } if (ucs4_string_p != NULL) { long length; *ucs4_string_p = g_utf16_to_ucs4(reinterpret_cast(utf16), len, NULL, &length, &error); if (*ucs4_string_p == NULL) { gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s", error->message); return false; } if (len_p != NULL) *len_p = (size_t) length; } return true; } /** * gjs_string_from_ucs4: * @cx: a #JSContext * @ucs4_string: string of #gunichar * @n_chars: number of characters in @ucs4_string or -1 for zero-terminated * @value_p: JS::Value that will be filled with a string * * Returns: true on success, false otherwise in which case a JS error is thrown */ bool gjs_string_from_ucs4(JSContext *cx, const gunichar *ucs4_string, ssize_t n_chars, JS::MutableHandleValue value_p) { // a null array pointer takes precedence over whatever `n_chars` says if (!ucs4_string) { value_p.setString(JS_GetEmptyString(cx)); return true; } long u16_string_length; GjsAutoError error; gunichar2* u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, nullptr, &u16_string_length, &error); if (!u16_string) { gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16: %s", error->message); return false; } // Sadly, must copy, because js::UniquePtr forces that chars passed to // JS_NewUCString() must have been allocated by the JS engine. JS::RootedString str( cx, JS_NewUCStringCopyN(cx, reinterpret_cast(u16_string), u16_string_length)); g_free(u16_string); if (!str) { gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16"); return false; } value_p.setString(str); return true; } /** * gjs_get_string_id: * @cx: a #JSContext * @id: a jsid that is an object hash key (could be an int or string) * @name_p place to store ASCII string version of key * * If the id is not a string ID, return true and set *name_p to nullptr. * Otherwise, return true and fill in *name_p with ASCII name of id. * * Returns: false on error, otherwise true **/ bool gjs_get_string_id(JSContext* cx, jsid id, JS::UniqueChars* name_p) { if (!id.isString()) { name_p->reset(); return true; } JSLinearString* lstr = id.toLinearString(); JS::RootedString s(cx, JS_FORGET_STRING_LINEARNESS(lstr)); *name_p = JS_EncodeStringToUTF8(cx, s); return !!*name_p; } /** * gjs_unichar_from_string: * @string: A string * @result: (out): A unicode character * * If successful, @result is assigned the Unicode codepoint * corresponding to the first full character in @string. This * function handles characters outside the BMP. * * If @string is empty, @result will be 0. An exception will * be thrown if @string can not be represented as UTF-8. */ bool gjs_unichar_from_string (JSContext *context, JS::Value value, gunichar *result) { JS::UniqueChars utf8_str = gjs_string_to_utf8(context, value); if (utf8_str) { *result = g_utf8_get_char(utf8_str.get()); return true; } return false; } jsid gjs_intern_string_to_id(JSContext *cx, const char *string) { JS::RootedString str(cx, JS_AtomizeAndPinString(cx, string)); if (!str) return JS::PropertyKey::Void(); return JS::PropertyKey::fromPinnedString(str); } std::string gjs_debug_bigint(JS::BigInt* bi) { // technically this prints the value % INT64_MAX, cast into an int64_t if // the value is negative, otherwise cast into uint64_t std::ostringstream out; if (JS::BigIntIsNegative(bi)) out << JS::ToBigInt64(bi); else out << JS::ToBigUint64(bi); out << "n (modulo 2^64)"; return out.str(); } enum Quotes { DoubleQuotes, NoQuotes, }; [[nodiscard]] static std::string gjs_debug_linear_string(JSLinearString* str, Quotes quotes) { size_t len = JS::GetLinearStringLength(str); std::ostringstream out; if (quotes == DoubleQuotes) out << '"'; JS::AutoCheckCannotGC nogc; if (JS::LinearStringHasLatin1Chars(str)) { const JS::Latin1Char* chars = JS::GetLatin1LinearStringChars(nogc, str); out << std::string(reinterpret_cast(chars), len); if (quotes == DoubleQuotes) out << '"'; return out.str(); } const char16_t* chars = JS::GetTwoByteLinearStringChars(nogc, str); for (size_t ix = 0; ix < len; ix++) { char16_t c = chars[ix]; if (c == '\n') out << "\\n"; else if (c == '\t') out << "\\t"; else if (c >= 32 && c < 127) out << c; else if (c <= 255) out << "\\x" << std::setfill('0') << std::setw(2) << unsigned(c); else out << "\\x" << std::setfill('0') << std::setw(4) << unsigned(c); } if (quotes == DoubleQuotes) out << '"'; return out.str(); } std::string gjs_debug_string(JSString *str) { if (!str) return ""; if (!JS_StringIsLinear(str)) { std::ostringstream out("'; return out.str(); } return gjs_debug_linear_string(JS_ASSERT_STRING_IS_LINEAR(str), DoubleQuotes); } std::string gjs_debug_symbol(JS::Symbol * const sym) { if (!sym) return ""; /* This is OK because JS::GetSymbolCode() and JS::GetSymbolDescription() * can't cause a garbage collection */ JS::HandleSymbol handle = JS::HandleSymbol::fromMarkedLocation(&sym); JS::SymbolCode code = JS::GetSymbolCode(handle); JSString *descr = JS::GetSymbolDescription(handle); if (size_t(code) < JS::WellKnownSymbolLimit) return gjs_debug_string(descr); std::ostringstream out; if (code == JS::SymbolCode::InSymbolRegistry) { out << "Symbol.for("; if (descr) out << gjs_debug_string(descr); else out << "undefined"; out << ")"; return out.str(); } if (code == JS::SymbolCode::UniqueSymbol) { if (descr) out << "Symbol(" << gjs_debug_string(descr) << ")"; else out << ""; return out.str(); } out << ""; return out.str(); } std::string gjs_debug_object(JSObject * const obj) { if (!obj) return ""; std::ostringstream out; if (js::IsFunctionObject(obj)) { JSFunction* fun = JS_GetObjectFunction(obj); JSString* display_name = JS_GetMaybePartialFunctionDisplayId(fun); if (display_name && JS_GetStringLength(display_name)) out << "'; return out.str(); } // This is OK because the promise methods can't cause a garbage collection JS::HandleObject handle = JS::HandleObject::fromMarkedLocation(&obj); if (JS::IsPromiseObject(handle)) { out << '<'; JS::PromiseState state = JS::GetPromiseState(handle); if (state == JS::PromiseState::Pending) out << "pending "; out << "promise " << JS::GetPromiseID(handle) << " at " << obj; if (state != JS::PromiseState::Pending) { out << ' '; out << (state == JS::PromiseState::Rejected ? "rejected" : "resolved"); out << " with " << gjs_debug_value(JS::GetPromiseResult(handle)); } out << '>'; return out.str(); } const JSClass* clasp = JS::GetClass(obj); out << "name << " at " << obj << '>'; return out.str(); } std::string gjs_debug_callable(JSObject* callable) { if (JSFunction* fn = JS_GetObjectFunction(callable)) { if (JSString* display_id = JS_GetMaybePartialFunctionDisplayId(fn)) return {"function " + gjs_debug_string(display_id)}; return {"unnamed function"}; } return {"callable object " + gjs_debug_object(callable)}; } std::string gjs_debug_value(JS::Value v) { if (v.isNull()) return "null"; if (v.isUndefined()) return "undefined"; if (v.isInt32()) { std::ostringstream out; out << v.toInt32(); return out.str(); } if (v.isDouble()) { std::ostringstream out; out << v.toDouble(); return out.str(); } if (v.isBigInt()) return gjs_debug_bigint(v.toBigInt()); if (v.isString()) return gjs_debug_string(v.toString()); if (v.isSymbol()) return gjs_debug_symbol(v.toSymbol()); if (v.isObject()) return gjs_debug_object(&v.toObject()); if (v.isBoolean()) return (v.toBoolean() ? "true" : "false"); if (v.isMagic()) return ""; return "unexpected value"; } std::string gjs_debug_id(jsid id) { if (id.isString()) return gjs_debug_linear_string(id.toLinearString(), NoQuotes); return gjs_debug_value(js::IdToValue(id)); } cjs-128.1/cjs/jsapi-util.cpp0000664000175000017500000004733615116312211014602 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2009 Red Hat, Inc. #include #include // for sscanf #include // for strlen #ifdef _WIN32 # include #endif #include #include #include // for move #include #include #include #include #include #include #include #include #include #include // for JS_MaybeGC, NonIncrementalGC, GCRe... #include // for GCHashSet #include // for RootedVector #include // for DefaultHasher #include // for GetClass #include #include #include #include #include #include #include // for JS_InstanceOf #include // for ProtoKeyToClass #include // for JSProto_InternalError, JSProto_SyntaxError #include #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" static void throw_property_lookup_error(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, const char *reason) { /* remember gjs_throw() is a no-op if JS_GetProperty() * already set an exception */ if (description) gjs_throw(cx, "No property '%s' in %s (or %s)", gjs_debug_id(property_name).c_str(), description, reason); else gjs_throw(cx, "No property '%s' in object %p (or %s)", gjs_debug_id(property_name).c_str(), obj.get(), reason); } /* Returns whether the object had the property; if the object did * not have the property, always sets an exception. Treats * "the property's value is undefined" the same as "no such property,". * Guarantees that *value_p is set to something, if only JS::UndefinedValue(), * even if an exception is set and false is returned. * * SpiderMonkey will emit a warning if the property is not present, so don't * use this if you expect the property not to be present some of the time. */ bool gjs_object_require_property(JSContext *context, JS::HandleObject obj, const char *obj_description, JS::HandleId property_name, JS::MutableHandleValue value) { value.setUndefined(); if (G_UNLIKELY(!JS_GetPropertyById(context, obj, property_name, value))) return false; if (G_LIKELY(!value.isUndefined())) return true; throw_property_lookup_error(context, obj, obj_description, property_name, "its value was undefined"); return false; } bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, bool *value) { JS::RootedValue prop_value(cx); if (JS_GetPropertyById(cx, obj, property_name, &prop_value) && prop_value.isBoolean()) { *value = prop_value.toBoolean(); return true; } throw_property_lookup_error(cx, obj, description, property_name, "it was not a boolean"); return false; } bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, int32_t *value) { JS::RootedValue prop_value(cx); if (JS_GetPropertyById(cx, obj, property_name, &prop_value) && prop_value.isInt32()) { *value = prop_value.toInt32(); return true; } throw_property_lookup_error(cx, obj, description, property_name, "it was not a 32-bit integer"); return false; } /* Converts JS string value to UTF-8 string. */ bool gjs_object_require_property(JSContext* cx, JS::HandleObject obj, const char* description, JS::HandleId property_name, JS::UniqueChars* value) { JS::RootedValue prop_value(cx); if (JS_GetPropertyById(cx, obj, property_name, &prop_value)) { JS::UniqueChars tmp = gjs_string_to_utf8(cx, prop_value); if (tmp) { *value = std::move(tmp); return true; } } throw_property_lookup_error(cx, obj, description, property_name, "it was not a valid string"); return false; } bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, JS::MutableHandleObject value) { JS::RootedValue prop_value(cx); if (JS_GetPropertyById(cx, obj, property_name, &prop_value) && prop_value.isObject()) { value.set(&prop_value.toObject()); return true; } throw_property_lookup_error(cx, obj, description, property_name, "it was not an object"); return false; } bool gjs_object_require_converted_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, uint32_t *value) { JS::RootedValue prop_value(cx); if (JS_GetPropertyById(cx, obj, property_name, &prop_value) && JS::ToUint32(cx, prop_value, value)) { return true; } throw_property_lookup_error(cx, obj, description, property_name, "it couldn't be converted to uint32"); return false; } void gjs_throw_constructor_error(JSContext *context) { gjs_throw(context, "Constructor called as normal method. Use 'new SomeObject()' not 'SomeObject()'"); } void gjs_throw_abstract_constructor_error(JSContext* context, const JS::CallArgs& args) { const JSClass *proto_class; const char *name = "anonymous"; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject callee(context, &args.callee()); JS::RootedValue prototype(context); if (JS_GetPropertyById(context, callee, atoms.prototype(), &prototype)) { proto_class = JS::GetClass(&prototype.toObject()); name = proto_class->name; } gjs_throw(context, "You cannot construct new instances of '%s'", name); } JSObject* gjs_build_string_array(JSContext* context, const std::vector& strings) { JS::RootedValueVector elems(context); if (!elems.reserve(strings.size())) { JS_ReportOutOfMemory(context); return nullptr; } for (const std::string& string : strings) { JS::ConstUTF8CharsZ chars(string.c_str(), string.size()); JS::RootedValue element(context, JS::StringValue(JS_NewStringCopyUTF8Z(context, chars))); elems.infallibleAppend(element); } return JS::NewArrayObject(context, elems); } JSObject* gjs_define_string_array(JSContext* context, JS::HandleObject in_object, const char* array_name, const std::vector& strings, unsigned attrs) { JS::RootedObject array(context, gjs_build_string_array(context, strings)); if (!array) return nullptr; if (!JS_DefineProperty(context, in_object, array_name, array, attrs)) return nullptr; return array; } // Helper function: perform ToString on an exception (which may not even be an // object), except if it is an InternalError, which would throw in ToString. GJS_JSAPI_RETURN_CONVENTION static JSString* exception_to_string(JSContext* cx, JS::HandleValue exc) { if (exc.isObject()) { JS::RootedObject exc_obj(cx, &exc.toObject()); const JSClass* internal_error = js::ProtoKeyToClass(JSProto_InternalError); if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) { JSErrorReport* report = JS_ErrorFromException(cx, exc_obj); if (!report->message()) return JS_NewStringCopyZ(cx, "(unknown internal error)"); return JS_NewStringCopyUTF8Z(cx, report->message()); } } return JS::ToString(cx, exc); } // Helper function: format the error's stack property. static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) { JS::AutoSaveExceptionState saved_exc(cx); auto restore = mozilla::MakeScopeExit([&saved_exc]() { saved_exc.restore(); }); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); std::ostringstream out; // Check both the internal SavedFrame object and the stack property. // GErrors will not have the former, and internal errors will not // have the latter. JS::RootedObject saved_frame{cx, JS::ExceptionStackOrNull(exc)}; if (saved_frame) { JS::UniqueChars utf8_stack{format_saved_frame(cx, saved_frame)}; if (!utf8_stack) return {}; out << '\n' << utf8_stack.get(); return out.str(); } JS::RootedValue stack{cx}; if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) || !stack.isString()) return {}; JS::RootedString str{cx, stack.toString()}; bool is_empty; if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty) return {}; JS::UniqueChars utf8_stack{JS_EncodeStringToUTF8(cx, str)}; if (!utf8_stack) return {}; out << '\n' << utf8_stack.get(); return out.str(); } // Helper function: format the file name, line number, and column number where a // SyntaxError occurred. static std::string format_syntax_error_location(JSContext* cx, JS::HandleObject exc) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue property(cx); int32_t line = 0; if (JS_GetPropertyById(cx, exc, atoms.line_number(), &property)) { if (property.isInt32()) line = property.toInt32(); } JS_ClearPendingException(cx); int32_t column = 0; if (JS_GetPropertyById(cx, exc, atoms.column_number(), &property)) { if (property.isInt32()) column = property.toInt32(); } JS_ClearPendingException(cx); JS::UniqueChars utf8_filename; if (JS_GetPropertyById(cx, exc, atoms.file_name(), &property)) { if (property.isString()) { JS::RootedString str(cx, property.toString()); utf8_filename = JS_EncodeStringToUTF8(cx, str); } } JS_ClearPendingException(cx); std::ostringstream out; out << " @ "; if (utf8_filename) out << utf8_filename.get(); else out << ""; out << ":" << line << ":" << column; return out.str(); } using CauseSet = JS::GCHashSet, js::SystemAllocPolicy>; static std::string format_exception_with_cause( JSContext* cx, JS::HandleObject exc_obj, JS::MutableHandle seen_causes) { std::ostringstream out; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); out << format_exception_stack(cx, exc_obj); JS::RootedValue v_cause(cx); if (!JS_GetPropertyById(cx, exc_obj, atoms.cause(), &v_cause)) JS_ClearPendingException(cx); if (v_cause.isUndefined()) return out.str(); JS::RootedObject cause(cx); if (v_cause.isObject()) { cause = &v_cause.toObject(); CauseSet::AddPtr entry = seen_causes.lookupForAdd(cause); if (entry) return out.str(); // cause has been printed already, ref cycle if (!seen_causes.add(entry, cause)) return out.str(); // out of memory, just stop here } out << "\nCaused by: "; JS::RootedString exc_str(cx, exception_to_string(cx, v_cause)); if (exc_str) { JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str); if (utf8_exception) out << utf8_exception.get(); } JS_ClearPendingException(cx); if (v_cause.isObject()) out << format_exception_with_cause(cx, cause, seen_causes); return out.str(); } static std::string format_exception_log_message(JSContext* cx, JS::HandleValue exc, JS::HandleString message) { std::ostringstream out; if (message) { JS::UniqueChars utf8_message = JS_EncodeStringToUTF8(cx, message); JS_ClearPendingException(cx); if (utf8_message) out << utf8_message.get() << ": "; } JS::RootedString exc_str(cx, exception_to_string(cx, exc)); if (exc_str) { JS::UniqueChars utf8_exception = JS_EncodeStringToUTF8(cx, exc_str); if (utf8_exception) out << utf8_exception.get(); } JS_ClearPendingException(cx); if (!exc.isObject()) return out.str(); JS::RootedObject exc_obj(cx, &exc.toObject()); const JSClass* syntax_error = js::ProtoKeyToClass(JSProto_SyntaxError); if (JS_InstanceOf(cx, exc_obj, syntax_error, nullptr)) { // We log syntax errors differently, because the stack for those // includes only the referencing module, but we want to print out the // file name, line number, and column number from the exception. // We assume that syntax errors have no cause property, and are not the // cause of other exceptions, so no recursion. out << format_syntax_error_location(cx, exc_obj) << format_exception_stack(cx, exc_obj); return out.str(); } JS::Rooted seen_causes(cx); seen_causes.putNew(exc_obj); out << format_exception_with_cause(cx, exc_obj, &seen_causes); return out.str(); } /** * gjs_log_exception_full: * @cx: the #JSContext * @exc: the exception value to be logged * @message: a string to prepend to the log message * @level: the severity level at which to log the exception * * Currently, uses %G_LOG_LEVEL_WARNING if the exception is being printed after * being caught, and %G_LOG_LEVEL_CRITICAL if it was not caught by user code. */ void gjs_log_exception_full(JSContext* cx, JS::HandleValue exc, JS::HandleString message, GLogLevelFlags level) { JS::AutoSaveExceptionState saved_exc(cx); std::string log_msg = format_exception_log_message(cx, exc, message); g_log(G_LOG_DOMAIN, level, "JS ERROR: %s", log_msg.c_str()); saved_exc.restore(); } /** * gjs_log_exception: * @cx: the #JSContext * * Logs the exception pending on @cx, if any, in response to an exception being * thrown that user code cannot catch or has already caught. * * Returns: %true if an exception was logged, %false if there was none pending. */ bool gjs_log_exception(JSContext *context) { JS::RootedValue exc(context); if (!JS_GetPendingException(context, &exc)) return false; JS_ClearPendingException(context); gjs_log_exception_full(context, exc, nullptr, G_LOG_LEVEL_WARNING); return true; } /** * gjs_log_exception_uncaught: * @cx: the #JSContext * * Logs the exception pending on @cx, if any, indicating an uncaught exception * in the running JS program. * (Currently, due to main loop boundaries, uncaught exceptions may not bubble * all the way back up to the top level, so this doesn't necessarily mean the * program exits with an error.) * * Returns: %true if an exception was logged, %false if there was none pending. */ bool gjs_log_exception_uncaught(JSContext* cx) { JS::RootedValue exc(cx); if (!JS_GetPendingException(cx, &exc)) return false; JS_ClearPendingException(cx); gjs_log_exception_full(cx, exc, nullptr, G_LOG_LEVEL_CRITICAL); return true; } #ifdef __linux__ // This type has to be long and not int32_t or int64_t, because of the %ld // sscanf specifier mandated in "man proc". The NOLINT comment is because // cpplint will ask you to avoid long in favour of defined bit width types. static void _linux_get_self_process_size(long* rss_size) // NOLINT(runtime/int) { char *iter; gsize len; int i; *rss_size = 0; GjsAutoChar contents; if (!g_file_get_contents("/proc/self/stat", contents.out(), &len, nullptr)) return; iter = contents; // See "man proc" for where this 23 comes from for (i = 0; i < 23; i++) { iter = strchr (iter, ' '); if (!iter) return; iter++; } sscanf(iter, " %ld", rss_size); } // We initiate a GC if RSS has grown by this much static uint64_t linux_rss_trigger; static int64_t last_gc_check_time; #endif void gjs_gc_if_needed (JSContext *context) { #ifdef __linux__ { long rss_size; // NOLINT(runtime/int) gint64 now; /* We rate limit GCs to at most one per 5 frames. One frame is 16666 microseconds (1000000/60)*/ now = g_get_monotonic_time(); if (now - last_gc_check_time < 5 * 16666) return; last_gc_check_time = now; _linux_get_self_process_size(&rss_size); /* linux_rss_trigger is initialized to 0, so currently * we always do a full GC early. * * Here we see if the RSS has grown by 25% since * our last look; if so, initiate a full GC. In * theory using RSS is bad if we get swapped out, * since we may be overzealous in GC, but on the * other hand, if swapping is going on, better * to GC. */ if (rss_size < 0) return; // doesn't make sense uint64_t rss_usize = rss_size; if (rss_usize > linux_rss_trigger) { linux_rss_trigger = MIN(G_MAXUINT32, rss_usize * 1.25); JS::NonIncrementalGC(context, JS::GCOptions::Shrink, Gjs::GCReason::LINUX_RSS_TRIGGER); } else if (rss_size < (0.75 * linux_rss_trigger)) { /* If we've shrunk by 75%, lower the trigger */ linux_rss_trigger = rss_usize * 1.25; } } #else // !__linux__ (void)context; #endif } /** * gjs_maybe_gc: * * Low level version of gjs_context_maybe_gc(). */ void gjs_maybe_gc (JSContext *context) { JS_MaybeGC(context); gjs_gc_if_needed(context); } const char* gjs_explain_gc_reason(JS::GCReason reason) { if (JS::InternalGCReason(reason)) return JS::ExplainGCReason(reason); static const char* reason_strings[] = { // clang-format off "RSS above threshold", "GjsContext disposed", "Big Hammer hit", "gjs_context_gc() called", "Memory usage is low", // clang-format on }; static_assert(G_N_ELEMENTS(reason_strings) == Gjs::GCReason::N_REASONS, "Explanations must match the values in Gjs::GCReason"); g_assert(size_t(reason) < size_t(JS::GCReason::FIRST_FIREFOX_REASON) + Gjs::GCReason::N_REASONS && "Bad Gjs::GCReason"); return reason_strings[size_t(reason) - size_t(JS::GCReason::FIRST_FIREFOX_REASON)]; } cjs-128.1/cjs/jsapi-util.h0000664000175000017500000005673615116312211014253 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2018-2020 Canonical, Ltd #ifndef GJS_JSAPI_UTIL_H_ #define GJS_JSAPI_UTIL_H_ #include #include #include // for free #include // for ssize_t #include #include // for string, u16string #include // for enable_if_t, add_pointer_t, add_const_t #include // IWYU pragma: keep #include #include #include #include #include #include // for JSExnType #include #include // for IgnoreGCPolicy #include #include #include // for UniqueChars #include "cjs/macros.h" #include "util/log.h" #if GJS_VERBOSE_ENABLE_MARSHAL # include "gi/arg-types-inl.h" // for static_type_name #endif namespace JS { class CallArgs; struct Dummy {}; using GTypeNotUint64 = std::conditional_t, GType, Dummy>; // The GC sweep method should ignore FundamentalTable and GTypeTable's key types // Forward declarations template <> struct GCPolicy : public IgnoreGCPolicy {}; // We need GCPolicy for GTypeTable. SpiderMonkey already defines // GCPolicy which is equal to GType on some systems; for others we // need to define it. (macOS's uint64_t is unsigned long long, which is a // different type from unsigned long, even if they are the same width) template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS struct GjsAutoTakeOwnership {}; template using GjsAutoPointerRefFunction = F* (*)(F*); template using GjsAutoPointerFreeFunction = void (*)(F*); template free_func = free, GjsAutoPointerRefFunction ref_func = nullptr> struct GjsAutoPointer { using Tp = std::conditional_t, std::remove_extent_t, T>; using Ptr = std::add_pointer_t; using ConstPtr = std::add_pointer_t>; using RvalueRef = std::add_lvalue_reference_t; protected: using BaseType = GjsAutoPointer; private: template static constexpr bool has_function() { using NullType = std::integral_constant; using ActualType = std::integral_constant; return !std::is_same_v; } public: static constexpr bool has_free_function() { return has_function, free_func>(); } static constexpr bool has_ref_function() { return has_function, ref_func>(); } constexpr GjsAutoPointer(Ptr ptr = nullptr) // NOLINT(runtime/explicit) : m_ptr(ptr) {} template && std::is_array_v>> explicit constexpr GjsAutoPointer(U ptr[]) : m_ptr(ptr) {} constexpr GjsAutoPointer(Ptr ptr, const GjsAutoTakeOwnership&) : GjsAutoPointer(ptr) { m_ptr = copy(); } constexpr GjsAutoPointer(ConstPtr ptr, const GjsAutoTakeOwnership& o) : GjsAutoPointer(const_cast(ptr), o) {} constexpr GjsAutoPointer(GjsAutoPointer&& other) : GjsAutoPointer() { this->swap(other); } constexpr GjsAutoPointer(GjsAutoPointer const& other) : GjsAutoPointer() { *this = other; } constexpr GjsAutoPointer& operator=(Ptr ptr) { reset(ptr); return *this; } constexpr GjsAutoPointer& operator=(GjsAutoPointer&& other) { this->swap(other); return *this; } constexpr GjsAutoPointer& operator=(GjsAutoPointer const& other) { GjsAutoPointer dup(other.get(), GjsAutoTakeOwnership()); this->swap(dup); return *this; } template constexpr std::enable_if_t, Ptr> operator->() { return m_ptr; } template constexpr std::enable_if_t, ConstPtr> operator->() const { return m_ptr; } template constexpr std::enable_if_t, RvalueRef> operator[]( int index) { return m_ptr[index]; } template constexpr std::enable_if_t, std::add_const_t> operator[](int index) const { return m_ptr[index]; } constexpr Tp operator*() const { return *m_ptr; } constexpr operator Ptr() { return m_ptr; } constexpr operator Ptr() const { return m_ptr; } constexpr operator ConstPtr() const { return m_ptr; } constexpr operator bool() const { return m_ptr != nullptr; } constexpr Ptr get() const { return m_ptr; } constexpr Ptr* out() { return &m_ptr; } constexpr ConstPtr* out() const { return const_cast(&m_ptr); } constexpr Ptr release() { auto* ptr = m_ptr; m_ptr = nullptr; return ptr; } constexpr void reset(Ptr ptr = nullptr) { Ptr old_ptr = m_ptr; m_ptr = ptr; if constexpr (has_free_function()) { if (old_ptr) free_func(reinterpret_cast(old_ptr)); } } constexpr void swap(GjsAutoPointer& other) { std::swap(this->m_ptr, other.m_ptr); } /* constexpr */ ~GjsAutoPointer() { // one day, with -std=c++2a reset(); } template [[nodiscard]] constexpr std::enable_if_t, Ptr> copy() const { static_assert(has_ref_function(), "No ref function provided"); return m_ptr ? reinterpret_cast( ref_func(reinterpret_cast(m_ptr))) : nullptr; } template [[nodiscard]] constexpr C* as() const { return const_cast(reinterpret_cast(m_ptr)); } private: Ptr m_ptr; }; template free_func = free, GjsAutoPointerRefFunction ref_func = nullptr> struct GjsAutoPointerSimple : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; }; template free_func, GjsAutoPointerRefFunction ref_func> constexpr bool operator==( GjsAutoPointer const& lhs, GjsAutoPointer const& rhs) { return lhs.get() == rhs.get(); } template using GjsAutoFree = GjsAutoPointer; struct GjsAutoCharFuncs { static char* dup(char* str) { return g_strdup(str); } static void free(char* str) { g_free(str); } }; using GjsAutoChar = GjsAutoPointer; using GjsAutoChar16 = GjsAutoPointer; struct GjsAutoErrorFuncs { static GError* error_copy(GError* error) { return g_error_copy(error); } }; struct GjsAutoError : GjsAutoPointer { using BaseType::BaseType; using BaseType::operator=; constexpr BaseType::ConstPtr* operator&() // NOLINT(runtime/operator) const { return out(); } constexpr BaseType::Ptr* operator&() { // NOLINT(runtime/operator) return out(); } }; using GjsAutoStrv = GjsAutoPointer; template using GjsAutoUnref = GjsAutoPointer; using GjsAutoGVariant = GjsAutoPointer; template constexpr void GjsAutoPointerDeleter(T v) { if constexpr (std::is_array_v) delete[] reinterpret_cast*>(v); else delete v; } template using GjsAutoCppPointer = GjsAutoPointer>; template struct GjsAutoTypeClass : GjsAutoPointer { GjsAutoTypeClass(gpointer ptr = nullptr) // NOLINT(runtime/explicit) : GjsAutoPointer(static_cast(ptr)) {} explicit GjsAutoTypeClass(GType gtype) : GjsAutoTypeClass(g_type_class_ref(gtype)) {} }; // Use this class for owning a GIBaseInfo* of indeterminate type. Any type (e.g. // GIFunctionInfo*, GIObjectInfo*) will fit. If you know that the info is of a // certain type (e.g. you are storing the return value of a function that // returns GIFunctionInfo*,) use one of the derived classes below. struct GjsAutoBaseInfo : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; [[nodiscard]] const char* name() const { return g_base_info_get_name(*this); } [[nodiscard]] const char* ns() const { return g_base_info_get_namespace(*this); } [[nodiscard]] GIInfoType type() const { return g_base_info_get_type(*this); } }; // Use GjsAutoInfo, preferably its typedefs below, when you know for sure that // the info is either of a certain type or null. template struct GjsAutoInfo : GjsAutoBaseInfo { using GjsAutoBaseInfo::GjsAutoBaseInfo; // Normally one-argument constructors should be explicit, but we are trying // to conform to the interface of std::unique_ptr here. GjsAutoInfo(GIBaseInfo* ptr = nullptr) // NOLINT(runtime/explicit) : GjsAutoBaseInfo(ptr) { #ifndef G_DISABLE_CAST_CHECKS validate(); #endif } void reset(GIBaseInfo* other = nullptr) { GjsAutoBaseInfo::reset(other); #ifndef G_DISABLE_CAST_CHECKS validate(); #endif } // You should not need this method, because you already know the answer. GIInfoType type() = delete; private: void validate() const { if (GIBaseInfo* base = *this) g_assert(g_base_info_get_type(base) == TAG); } }; using GjsAutoArgInfo = GjsAutoInfo; using GjsAutoEnumInfo = GjsAutoInfo; using GjsAutoFieldInfo = GjsAutoInfo; using GjsAutoFunctionInfo = GjsAutoInfo; using GjsAutoInterfaceInfo = GjsAutoInfo; using GjsAutoObjectInfo = GjsAutoInfo; using GjsAutoPropertyInfo = GjsAutoInfo; using GjsAutoStructInfo = GjsAutoInfo; using GjsAutoSignalInfo = GjsAutoInfo; using GjsAutoTypeInfo = GjsAutoInfo; using GjsAutoValueInfo = GjsAutoInfo; using GjsAutoVFuncInfo = GjsAutoInfo; // GICallableInfo can be one of several tags, so we have to have a separate // class, and use GI_IS_CALLABLE_INFO() to validate. struct GjsAutoCallableInfo : GjsAutoBaseInfo { using GjsAutoBaseInfo::GjsAutoBaseInfo; GjsAutoCallableInfo(GIBaseInfo* ptr = nullptr) // NOLINT(runtime/explicit) : GjsAutoBaseInfo(ptr) { validate(); } void reset(GIBaseInfo* other = nullptr) { GjsAutoBaseInfo::reset(other); validate(); } private: void validate() const { if (*this) g_assert(GI_IS_CALLABLE_INFO(get())); } }; template struct GjsSmartPointer : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; }; template <> struct GjsSmartPointer : GjsAutoStrv { using GjsAutoStrv::GjsAutoPointer; }; template <> struct GjsSmartPointer : GjsAutoStrv { using GjsAutoStrv::GjsAutoPointer; }; template <> struct GjsSmartPointer : GjsAutoUnref { using GjsAutoUnref::GjsAutoUnref; }; template <> struct GjsSmartPointer : GjsAutoBaseInfo { using GjsAutoBaseInfo::GjsAutoBaseInfo; }; template <> struct GjsSmartPointer : GjsAutoError { using GjsAutoError::GjsAutoError; using GjsAutoError::operator=; using GjsAutoError::operator&; }; template <> struct GjsSmartPointer : GjsAutoGVariant { using GjsAutoGVariant::GjsAutoPointer; }; template <> struct GjsSmartPointer : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; }; template <> struct GjsSmartPointer : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; }; /* For use of GjsAutoInfo in GC hash maps */ namespace JS { template struct GCPolicy> : public IgnoreGCPolicy> {}; } // namespace JS using GjsAutoParam = GjsAutoPointer; /* For use of GjsAutoParam in GC hash maps */ namespace JS { template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS /* Flags that should be set on properties exported from native code modules. * Basically set these on API, but do NOT set them on data. * * PERMANENT: forbid deleting the prop * ENUMERATE: allows copyProperties to work among other reasons to have it */ #define GJS_MODULE_PROP_FLAGS (JSPROP_PERMANENT | JSPROP_ENUMERATE) /* * GJS_GET_THIS: * @cx: JSContext pointer passed into JSNative function * @argc: Number of arguments passed into JSNative function * @vp: Argument value array passed into JSNative function * @args: Name for JS::CallArgs variable defined by this code snippet * @to: Name for JS::RootedObject variable referring to function's this * * A convenience macro for getting the 'this' object a function was called with. * Use in any JSNative function. */ #define GJS_GET_THIS(cx, argc, vp, args, to) \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JS::RootedObject to(cx); \ if (!args.computeThis(cx, &to)) \ return false; void gjs_throw_constructor_error (JSContext *context); void gjs_throw_abstract_constructor_error(JSContext* cx, const JS::CallArgs& args); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_build_string_array(JSContext* cx, const std::vector& strings); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_string_array(JSContext* cx, JS::HandleObject obj, const char* array_name, const std::vector& strings, unsigned attrs); [[gnu::format(printf, 2, 3)]] void gjs_throw(JSContext* cx, const char* format, ...); [[gnu::format(printf, 4, 5)]] void gjs_throw_custom(JSContext* cx, JSExnType error_kind, const char* error_name, const char* format, ...); void gjs_throw_literal (JSContext *context, const char *string); bool gjs_throw_gerror_message(JSContext* cx, GjsAutoError const&); bool gjs_log_exception (JSContext *context); bool gjs_log_exception_uncaught(JSContext* cx); void gjs_log_exception_full(JSContext* cx, JS::HandleValue exc, JS::HandleString message, GLogLevelFlags level); void gjs_warning_reporter(JSContext*, JSErrorReport* report); GJS_JSAPI_RETURN_CONVENTION JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value string_val); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output, size_t* output_len); GJS_JSAPI_RETURN_CONVENTION JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string); GJS_JSAPI_RETURN_CONVENTION JSString* gjs_lossy_string_from_utf8_n(JSContext* cx, const char* utf8_string, size_t len); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_utf8(JSContext *context, const char *utf8_string, JS::MutableHandleValue value_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_utf8_n(JSContext *cx, const char *utf8_chars, size_t len, JS::MutableHandleValue out); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_filename(JSContext *cx, const JS::Value string_val, GjsAutoChar *filename_string); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_filename(JSContext *context, const char *filename_string, ssize_t n_bytes, JS::MutableHandleValue value_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_get_char16_data(JSContext *cx, JS::HandleString str, char16_t **data_p, size_t *len_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_ucs4(JSContext *cx, JS::HandleString value, gunichar **ucs4_string_p, size_t *len_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_ucs4(JSContext *cx, const gunichar *ucs4_string, ssize_t n_chars, JS::MutableHandleValue value_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_get_string_id(JSContext* cx, jsid id, JS::UniqueChars* name_p); GJS_JSAPI_RETURN_CONVENTION jsid gjs_intern_string_to_id (JSContext *context, const char *string); GJS_JSAPI_RETURN_CONVENTION bool gjs_unichar_from_string (JSContext *context, JS::Value string, gunichar *result); /* Functions intended for more "internal" use */ void gjs_maybe_gc (JSContext *context); void gjs_gc_if_needed(JSContext *cx); GJS_JSAPI_RETURN_CONVENTION JS::UniqueChars format_saved_frame(JSContext* cx, JS::HandleObject saved_frame, size_t indent = 0); /* Overloaded functions, must be outside G_DECLS. More types are intended to be * added as the opportunity arises. */ GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext *context, JS::HandleObject obj, const char *obj_description, JS::HandleId property_name, JS::MutableHandleValue value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, bool *value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, int32_t *value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext* cx, JS::HandleObject obj, const char* description, JS::HandleId property_name, JS::UniqueChars* value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext *cx, JS::HandleObject obj, const char *description, JS::HandleId property_name, JS::MutableHandleObject value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_converted_property(JSContext *context, JS::HandleObject obj, const char *description, JS::HandleId property_name, uint32_t *value); [[nodiscard]] std::string gjs_debug_bigint(JS::BigInt* bi); [[nodiscard]] std::string gjs_debug_string(JSString* str); [[nodiscard]] std::string gjs_debug_symbol(JS::Symbol* const sym); [[nodiscard]] std::string gjs_debug_object(JSObject* obj); [[nodiscard]] std::string gjs_debug_callable(JSObject* callable); [[nodiscard]] std::string gjs_debug_value(JS::Value v); [[nodiscard]] std::string gjs_debug_id(jsid id); [[nodiscard]] GjsAutoChar gjs_hyphen_to_underscore(const char* str); [[nodiscard]] GjsAutoChar gjs_hyphen_to_camel(const char* str); #if defined(G_OS_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1900)) [[nodiscard]] std::wstring gjs_win32_vc140_utf8_to_utf16(const char* str); #endif // Custom GC reasons; SpiderMonkey includes a bunch of "Firefox reasons" which // don't apply when embedding the JS engine, so we repurpose them for our own // reasons. // clang-format off #define FOREACH_GC_REASON(macro) \ macro(LINUX_RSS_TRIGGER, 0) \ macro(GJS_CONTEXT_DISPOSE, 1) \ macro(BIG_HAMMER, 2) \ macro(GJS_API_CALL, 3) \ macro(LOW_MEMORY, 4) // clang-format on namespace Gjs { struct GCReason { #define DEFINE_GC_REASON(name, ix) \ static constexpr JS::GCReason name = JS::GCReason( \ static_cast(JS::GCReason::FIRST_FIREFOX_REASON) + ix); FOREACH_GC_REASON(DEFINE_GC_REASON); #undef DEFINE_GC_REASON #define COUNT_GC_REASON(name, ix) +1 static constexpr size_t N_REASONS = 0 FOREACH_GC_REASON(COUNT_GC_REASON); #undef COUNT_GC_REASON }; template [[nodiscard]] bool bigint_is_out_of_range(JS::BigInt* bi, T* clamped) { static_assert(sizeof(T) == 8, "64-bit types only"); g_assert(bi && "bigint cannot be null"); g_assert(clamped && "forgot out parameter"); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Checking if BigInt %s is out of range for type %s", gjs_debug_bigint(bi).c_str(), Gjs::static_type_name()); if (JS::BigIntFits(bi, clamped)) { gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "BigInt %s is in the range of type %s", std::to_string(*clamped).c_str(), Gjs::static_type_name()); return false; } if (JS::BigIntIsNegative(bi)) { *clamped = std::numeric_limits::min(); } else { *clamped = std::numeric_limits::max(); } gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "BigInt %s is not in the range of type %s, clamped to %s", gjs_debug_bigint(bi).c_str(), Gjs::static_type_name(), std::to_string(*clamped).c_str()); return true; } } // namespace Gjs [[nodiscard]] const char* gjs_explain_gc_reason(JS::GCReason reason); #endif // GJS_JSAPI_UTIL_H_ cjs-128.1/cjs/macros.h0000664000175000017500000000334615116312211013443 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2017 Chun-wei Fan */ #ifndef GJS_MACROS_H_ #define GJS_MACROS_H_ #include #ifdef G_OS_WIN32 # ifdef GJS_COMPILATION # define GJS_EXPORT __declspec(dllexport) # else # define GJS_EXPORT __declspec(dllimport) # endif # define siginfo_t void #else # define GJS_EXPORT __attribute__((visibility("default"))) #endif /** * GJS_USE: * * Indicates a return value must be used, or the compiler should log a warning. * Equivalent to [[nodiscard]], but this macro is for use in external headers * which are not necessarily compiled with a C++ compiler. */ #if defined(__GNUC__) || defined(__clang__) # define GJS_USE __attribute__((warn_unused_result)) #else # define GJS_USE #endif /** * GJS_JSAPI_RETURN_CONVENTION: * * Same as [[nodiscard]], but indicates that a return value of true or non-null * means that no exception must be pending on the passed-in #JSContext. * Conversely, a return value of false or nullptr means that an exception must * be pending, or else an uncatchable exception has been thrown. * * It's intended for use by static analysis tools to do better consistency * checks. If not using them, then it has the same effect as [[nodiscard]]. * It's also intended as documentation for the programmer. */ #ifdef __clang_analyzer__ # define GJS_JSAPI_RETURN_CONVENTION \ [[nodiscard]] __attribute__((annotate("jsapi_return_convention"))) #else # define GJS_JSAPI_RETURN_CONVENTION [[nodiscard]] #endif #ifdef __GNUC__ # define GJS_ALWAYS_INLINE __attribute__((always_inline)) #else # define GJS_ALWAYS_INLINE #endif #endif /* GJS_MACROS_H_ */ cjs-128.1/cjs/mainloop.cpp0000664000175000017500000000316715116312211014331 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh #include #include #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/mainloop.h" namespace Gjs { bool MainLoop::spin(GjsContextPrivate* gjs) { if (m_exiting) return false; // Check if System.exit() has been called. if (gjs->should_exit(nullptr)) { // Return false to indicate the loop is exiting due to an exit call, // the queue is likely not empty debug("Not spinning loop because System.exit called"); exit(); return false; } GjsAutoPointer main_context(g_main_context_ref_thread_default()); debug("Spinning loop until released or hook cleared"); do { bool blocking = can_block(); // Only run the loop if there are pending jobs. if (g_main_context_pending(main_context)) g_main_context_iteration(main_context, blocking); // If System.exit() has not been called if (gjs->should_exit(nullptr)) { debug("Stopped spinning loop because System.exit called"); exit(); return false; } } while ( // and there is not a pending main loop hook !gjs->has_main_loop_hook() && // and there are pending sources or the job queue is not empty // continue spinning the event loop. (can_block() || !gjs->empty())); return true; } }; // namespace Gjs cjs-128.1/cjs/mainloop.h0000664000175000017500000000402015116312211013763 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh #pragma once #include #include #include "util/log.h" class GjsContextPrivate; namespace Gjs { class MainLoop { // grefcounts start at one and become invalidated when they are decremented // to zero. So the actual hold count is equal to the "ref" count minus 1. // We nonetheless use grefcount here because it takes care of dealing with // integer overflow for us. grefcount m_hold_count; bool m_exiting; void debug(const char* msg) { gjs_debug(GJS_DEBUG_MAINLOOP, "Main loop instance %p: %s", this, msg); } [[nodiscard]] bool can_block() { // Don't block if exiting if (m_exiting) return false; g_assert(!g_ref_count_compare(&m_hold_count, 0) && "main loop released too many times"); // If the reference count is not zero or one, the loop is being held. return !g_ref_count_compare(&m_hold_count, 1); } void exit() { m_exiting = true; // Reset the reference count to 1 to exit g_ref_count_init(&m_hold_count); } public: MainLoop() : m_exiting(false) { g_ref_count_init(&m_hold_count); } ~MainLoop() { g_assert(g_ref_count_compare(&m_hold_count, 1) && "mismatched hold/release on main loop"); } void hold() { // Don't allow new holds after exit() is called if (m_exiting) return; debug("hold"); g_ref_count_inc(&m_hold_count); } void release() { // Ignore releases after exit(), exit() resets the refcount if (m_exiting) return; debug("release"); bool zero [[maybe_unused]] = g_ref_count_dec(&m_hold_count); g_assert(!zero && "main loop released too many times"); } [[nodiscard]] bool spin(GjsContextPrivate*); }; }; // namespace Gjs cjs-128.1/cjs/mem-private.h0000664000175000017500000000547115116312211014406 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2021 Canonical, Ltd #ifndef GJS_MEM_PRIVATE_H_ #define GJS_MEM_PRIVATE_H_ #include #include // for size_t #include // clang-format off #define GJS_FOR_EACH_COUNTER(macro) \ macro(boxed_instance, 0) \ macro(boxed_prototype, 1) \ macro(closure, 2) \ macro(function, 3) \ macro(fundamental_instance, 4) \ macro(fundamental_prototype, 5) \ macro(gerror_instance, 6) \ macro(gerror_prototype, 7) \ macro(interface, 8) \ macro(module, 9) \ macro(ns, 10) \ macro(object_instance, 11) \ macro(object_prototype, 12) \ macro(param, 13) \ macro(union_instance, 14) \ macro(union_prototype, 15) // clang-format on namespace Gjs { namespace Memory { struct Counter { explicit Counter(const char* n) : name(n) {} std::atomic_int64_t value = ATOMIC_VAR_INIT(0); const char* name; }; namespace Counters { #define GJS_DECLARE_COUNTER(name, ix) extern Counter name; GJS_DECLARE_COUNTER(everything, -1) GJS_FOR_EACH_COUNTER(GJS_DECLARE_COUNTER) #undef GJS_DECLARE_COUNTER template constexpr void inc() { everything.value++; counter->value++; } template constexpr void dec() { counter->value--; everything.value--; } } // namespace Counters } // namespace Memory } // namespace Gjs #define COUNT(name, ix) +1 static constexpr size_t GJS_N_COUNTERS = 0 GJS_FOR_EACH_COUNTER(COUNT); #undef COUNT static constexpr const char GJS_COUNTER_DESCRIPTIONS[GJS_N_COUNTERS][52] = { // max length of description string ---------------v "Number of boxed type wrapper objects", "Number of boxed type prototype objects", "Number of signal handlers", "Number of introspected functions", "Number of fundamental type wrapper objects", "Number of fundamental type prototype objects", "Number of GError wrapper objects", "Number of GError prototype objects", "Number of GObject interface objects", "Number of modules", "Number of GI namespace objects", "Number of GObject wrapper objects", "Number of GObject prototype objects", "Number of GParamSpec wrapper objects", "Number of C union wrapper objects", "Number of C union prototype objects", }; #define GJS_INC_COUNTER(name) \ (Gjs::Memory::Counters::inc<&Gjs::Memory::Counters::name>()); #define GJS_DEC_COUNTER(name) \ (Gjs::Memory::Counters::dec<&Gjs::Memory::Counters::name>()); #define GJS_GET_COUNTER(name) (Gjs::Memory::Counters::name.value.load()) #endif // GJS_MEM_PRIVATE_H_ cjs-128.1/cjs/mem.cpp0000664000175000017500000000331015116312211013257 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include "cjs/mem-private.h" #include "cjs/mem.h" #include "util/log.h" namespace Gjs { namespace Memory { namespace Counters { #define GJS_DEFINE_COUNTER(name, ix) Counter name(#name); GJS_DEFINE_COUNTER(everything, -1) GJS_FOR_EACH_COUNTER(GJS_DEFINE_COUNTER) } // namespace Counters } // namespace Memory } // namespace Gjs #define GJS_LIST_COUNTER(name, ix) &Gjs::Memory::Counters::name, static Gjs::Memory::Counter* counters[] = { GJS_FOR_EACH_COUNTER(GJS_LIST_COUNTER)}; void gjs_memory_report(const char *where, bool die_if_leaks) { int i; int n_counters; int64_t total_objects; gjs_debug(GJS_DEBUG_MEMORY, "Memory report: %s", where); n_counters = G_N_ELEMENTS(counters); total_objects = 0; for (i = 0; i < n_counters; ++i) { total_objects += counters[i]->value; } if (total_objects != GJS_GET_COUNTER(everything)) { gjs_debug(GJS_DEBUG_MEMORY, "Object counts don't add up!"); } gjs_debug(GJS_DEBUG_MEMORY, " %" G_GINT64_FORMAT " objects currently alive", GJS_GET_COUNTER(everything)); if (GJS_GET_COUNTER(everything) != 0) { for (i = 0; i < n_counters; ++i) { gjs_debug(GJS_DEBUG_MEMORY, " %24s = %" G_GINT64_FORMAT, counters[i]->name, counters[i]->value.load()); } if (die_if_leaks) g_error("%s: JavaScript objects were leaked.", where); } } cjs-128.1/cjs/mem.h0000664000175000017500000000107515116312211012732 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2008 litl, LLC */ #ifndef GJS_MEM_H_ #define GJS_MEM_H_ #if !defined(INSIDE_GJS_H) && !defined(GJS_COMPILATION) # error "Only can be included directly." #endif #include /* IWYU pragma: keep */ #include #include "cjs/macros.h" G_BEGIN_DECLS GJS_EXPORT void gjs_memory_report(const char *where, bool die_if_leaks); G_END_DECLS #endif // GJS_MEM_H_ cjs-128.1/cjs/module.cpp0000664000175000017500000006422015116312211013775 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento #include #include // for size_t #include #include // for vector #include #include #include #include #include #include // for ConstUTF8CharsZ #include #include #include #include #include // for JS_ReportOutOfMemory #include #include // for RootedVector #include // for CurrentGlobalOrNull #include #include #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include #include #include // for JS_GetFunctionObject, JS_Ne... #include // for NewFunctionWithReserved #include #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/global.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "cjs/module.h" #include "cjs/native.h" #include "util/log.h" #include "util/misc.h" namespace mozilla { union Utf8Unit; } class GjsScriptModule { GjsAutoChar m_name; // Reserved slots static const size_t POINTER = 0; GjsScriptModule(const char* name) : m_name(g_strdup(name)) { GJS_INC_COUNTER(module); } ~GjsScriptModule() { GJS_DEC_COUNTER(module); } GjsScriptModule(GjsScriptModule&) = delete; GjsScriptModule& operator=(GjsScriptModule&) = delete; /* Private data accessors */ [[nodiscard]] static inline GjsScriptModule* priv(JSObject* module) { return JS::GetMaybePtrFromReservedSlot( module, GjsScriptModule::POINTER); } /* Creates a JS module object. Use instead of the class's constructor */ [[nodiscard]] static JSObject* create(JSContext* cx, const char* name) { JSObject* module = JS_NewObject(cx, &GjsScriptModule::klass); JS::SetReservedSlot(module, GjsScriptModule::POINTER, JS::PrivateValue(new GjsScriptModule(name))); return module; } /* Defines the empty module as a property on the importer */ GJS_JSAPI_RETURN_CONVENTION bool define_import(JSContext *cx, JS::HandleObject module, JS::HandleObject importer, JS::HandleId name) const { if (!JS_DefinePropertyById(cx, importer, name, module, GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) { gjs_debug(GJS_DEBUG_IMPORTER, "Failed to define '%s' in importer", m_name.get()); return false; } return true; } /* Carries out the actual execution of the module code */ GJS_JSAPI_RETURN_CONVENTION bool evaluate_import(JSContext* cx, JS::HandleObject module, const char* source, size_t source_len, const char* filename, const char* uri) { JS::SourceText buf; if (!buf.init(cx, source, source_len, JS::SourceOwnership::Borrowed)) return false; JS::RootedObjectVector scope_chain(cx); if (!scope_chain.append(module)) { JS_ReportOutOfMemory(cx); return false; } JS::CompileOptions options(cx); options.setFileAndLine(filename, 1).setNonSyntacticScope(true); JS::RootedObject priv(cx, build_private(cx, uri)); if (!priv) return false; JS::RootedScript script(cx, JS::Compile(cx, options, buf)); if (!script) return false; JS::SetScriptPrivate(script, JS::ObjectValue(*priv)); JS::RootedValue ignored_retval(cx); if (!JS_ExecuteScript(cx, scope_chain, script, &ignored_retval)) return false; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); gjs->schedule_gc_if_needed(); gjs_debug(GJS_DEBUG_IMPORTER, "Importing module %s succeeded", m_name.get()); return true; } /* Loads JS code from a file and imports it */ GJS_JSAPI_RETURN_CONVENTION bool import_file(JSContext *cx, JS::HandleObject module, GFile *file) { GjsAutoError error; GjsAutoChar script; size_t script_len = 0; if (!(g_file_load_contents(file, nullptr, script.out(), &script_len, nullptr, &error))) return gjs_throw_gerror_message(cx, error); g_assert(script); GjsAutoChar full_path = g_file_get_parse_name(file); GjsAutoChar uri = g_file_get_uri(file); return evaluate_import(cx, module, script, script_len, full_path, uri); } /* JSClass operations */ GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext *cx, JS::HandleObject module, JS::HandleId id, bool *resolved) { JS::RootedObject lexical(cx, JS_ExtensibleLexicalEnvironment(module)); if (!lexical) { *resolved = false; return true; /* nothing imported yet */ } JS::Rooted> maybe_desc(cx); JS::RootedObject holder(cx); if (!JS_GetPropertyDescriptorById(cx, lexical, id, &maybe_desc, &holder)) return false; if (maybe_desc.isNothing()) return true; /* The property is present in the lexical environment. This should not * be supported according to ES6. For compatibility with earlier GJS, * we treat it as if it were a real property, but warn about it. */ _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId::ModuleExportedLetOrConst, {gjs_debug_id(id).c_str(), m_name}); JS::Rooted desc(cx, maybe_desc.value()); return JS_DefinePropertyById(cx, module, id, desc); } GJS_JSAPI_RETURN_CONVENTION static bool resolve(JSContext *cx, JS::HandleObject module, JS::HandleId id, bool *resolved) { return priv(module)->resolve_impl(cx, module, id, resolved); } static void finalize(JS::GCContext*, JSObject* module) { delete priv(module); } static constexpr JSClassOps class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &GjsScriptModule::resolve, nullptr, // mayResolve &GjsScriptModule::finalize, }; static constexpr JSClass klass = { "GjsScriptModule", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &GjsScriptModule::class_ops, }; public: /* * Creates a JS object to pass to JS::SetScriptPrivate as a script's * private. */ GJS_JSAPI_RETURN_CONVENTION static JSObject* build_private(JSContext* cx, const char* script_uri) { JS::RootedObject priv(cx, JS_NewPlainObject(cx)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue val(cx); if (!gjs_string_from_utf8(cx, script_uri, &val) || !JS_SetPropertyById(cx, priv, atoms.uri(), val)) return nullptr; return priv; } /* Carries out the import operation */ GJS_JSAPI_RETURN_CONVENTION static JSObject * import(JSContext *cx, JS::HandleObject importer, JS::HandleId id, const char *name, GFile *file) { JS::RootedObject module(cx, GjsScriptModule::create(cx, name)); if (!module || !priv(module)->define_import(cx, module, importer, id) || !priv(module)->import_file(cx, module, file)) return nullptr; return module; } }; /** * gjs_script_module_build_private: * @param cx the #JSContext * @param uri the URI this script module is loaded from * * @brief To support dynamic imports from scripts, we need to provide private * data when we compile scripts which is compatible with our module resolution * hooks in modules/internal/loader.js * * @returns a JSObject which can be used for a JSScript's private data. */ JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri) { return GjsScriptModule::build_private(cx, uri); } /** * gjs_module_import: * @cx: the JS context * @importer: the JS importer object, parent of the module to be imported * @id: module name in the form of a jsid * @name: module name, used for logging and identification * @file: location of the file to import * * Carries out an import of a GJS module. * Defines a property @name on @importer pointing to the module object, which * is necessary in the case of cyclic imports. * This property is not permanent; the caller is responsible for making it * permanent if the import succeeds. * * Returns: the JS module object, or nullptr on failure. */ JSObject * gjs_module_import(JSContext *cx, JS::HandleObject importer, JS::HandleId id, const char *name, GFile *file) { return GjsScriptModule::import(cx, importer, id, name, file); } decltype(GjsScriptModule::klass) constexpr GjsScriptModule::klass; decltype(GjsScriptModule::class_ops) constexpr GjsScriptModule::class_ops; /** * gjs_get_native_registry: * * @brief Retrieves a global's native registry from the NATIVE_REGISTRY slot. * Registries are JS Map objects created with JS::NewMapObject instead * of GCHashMaps (used elsewhere in GJS) because the objects need to be * exposed to internal JS code and accessed from native C++ code. * * @param global a global #JSObject * * @returns the registry map as a #JSObject */ JSObject* gjs_get_native_registry(JSObject* global) { JS::Value native_registry = gjs_get_global_slot(global, GjsGlobalSlot::NATIVE_REGISTRY); g_assert(native_registry.isObject()); return &native_registry.toObject(); } /** * gjs_get_module_registry: * * @brief Retrieves a global's module registry from the MODULE_REGISTRY slot. * Registries are JS Maps. See gjs_get_native_registry for more detail. * * @param cx the current #JSContext * @param global a global #JSObject * * @returns the registry map as a #JSObject */ JSObject* gjs_get_module_registry(JSObject* global) { JS::Value esm_registry = gjs_get_global_slot(global, GjsGlobalSlot::MODULE_REGISTRY); g_assert(esm_registry.isObject()); return &esm_registry.toObject(); } /** * gjs_module_load: * * Loads and registers a module given a specifier and * URI. * * @returns whether an error occurred while resolving the specifier. */ JSObject* gjs_module_load(JSContext* cx, const char* identifier, const char* file_uri) { g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) || gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) && "gjs_module_load can only be called from module-enabled " "globals."); JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); JS::RootedValue v_loader( cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)); g_assert(v_loader.isObject()); JS::RootedObject loader(cx, &v_loader.toObject()); JS::ConstUTF8CharsZ id_chars(identifier, strlen(identifier)); JS::ConstUTF8CharsZ uri_chars(file_uri, strlen(file_uri)); JS::RootedString id(cx, JS_NewStringCopyUTF8Z(cx, id_chars)); if (!id) return nullptr; JS::RootedString uri(cx, JS_NewStringCopyUTF8Z(cx, uri_chars)); if (!uri) return nullptr; JS::RootedValueArray<2> args(cx); args[0].setString(id); args[1].setString(uri); gjs_debug(GJS_DEBUG_IMPORTER, "Module resolve hook for module '%s' (%s), global %p", identifier, file_uri, global.get()); JS::RootedValue result(cx); if (!JS::Call(cx, loader, "moduleLoadHook", args, &result)) return nullptr; g_assert(result.isObject() && "Module hook failed to return an object!"); return &result.toObject(); } /** * import_native_module_sync: * * @brief Synchronously imports native "modules" from the import global's * native registry. This function does not do blocking I/O so it is * safe to call it synchronously for accessing native "modules" within * modules. This function is always called within the import global's * realm. * * Compare gjs_import_native_module() for the legacy importer. * * @param cx the current JSContext * @param argc * @param vp * * @returns whether an error occurred while importing the native module. */ static bool import_native_module_sync(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars id; if (!gjs_parse_call_args(cx, "importSync", args, "s", "identifier", &id)) return false; Gjs::AutoMainRealm ar{cx}; JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)}; JS::AutoSaveExceptionState exc_state(cx); JS::RootedObject native_registry(cx, gjs_get_native_registry(global)); JS::RootedObject v_module(cx); JS::RootedId key(cx, gjs_intern_string_to_id(cx, id.get())); if (!gjs_global_registry_get(cx, native_registry, key, &v_module)) return false; if (v_module) { args.rval().setObject(*v_module); return true; } JS::RootedObject native_obj(cx); if (!Gjs::NativeModuleDefineFuncs::get().define(cx, id.get(), &native_obj)) { gjs_throw(cx, "Failed to load native module: %s", id.get()); return false; } if (!gjs_global_registry_set(cx, native_registry, key, native_obj)) return false; args.rval().setObject(*native_obj); return true; } /** * gjs_populate_module_meta: * * Hook SpiderMonkey calls to populate the import.meta object. * Defines a property "import.meta.url", and additionally a method * "import.meta.importSync" if this is an internal module. * * @param private_ref the private value for the #Module object * @param meta the import.meta object * * @returns whether an error occurred while populating the module meta. */ bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref, JS::HandleObject meta) { g_assert(private_ref.isObject()); JS::RootedObject module(cx, &private_ref.toObject()); gjs_debug(GJS_DEBUG_IMPORTER, "Module metadata hook for module %p", &private_ref.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue specifier{cx}; if (!JS_GetProperty(cx, module, "id", &specifier) || !JS_DefinePropertyById(cx, meta, atoms.url(), specifier, GJS_MODULE_PROP_FLAGS)) return false; JS::RootedValue v_internal(cx); if (!JS_GetPropertyById(cx, module, atoms.internal(), &v_internal)) return false; if (JS::ToBoolean(v_internal)) { gjs_debug(GJS_DEBUG_IMPORTER, "Defining meta.importSync for module %p", &private_ref.toObject()); if (!JS_DefineFunctionById(cx, meta, atoms.importSync(), import_native_module_sync, 1, GJS_MODULE_PROP_FLAGS)) return false; } return true; } // Canonicalize specifier so that differently-spelled specifiers referring to // the same module don't result in duplicate entries in the registry static bool canonicalize_specifier(JSContext* cx, JS::MutableHandleString specifier) { JS::UniqueChars specifier_utf8 = JS_EncodeStringToUTF8(cx, specifier); if (!specifier_utf8) return false; GjsAutoChar scheme, host, path, query; if (!g_uri_split(specifier_utf8.get(), G_URI_FLAGS_NONE, scheme.out(), nullptr, host.out(), nullptr, path.out(), query.out(), nullptr, nullptr)) return false; if (g_strcmp0(scheme, "gi")) { // canonicalize without the query portion to avoid it being encoded GjsAutoChar for_file_uri = g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1, path.get(), nullptr, nullptr); GjsAutoUnref file = g_file_new_for_uri(for_file_uri.get()); for_file_uri = g_file_get_uri(file); host.reset(); path.reset(); if (!g_uri_split(for_file_uri.get(), G_URI_FLAGS_NONE, nullptr, nullptr, host.out(), nullptr, path.out(), nullptr, nullptr, nullptr)) return false; } GjsAutoChar canonical_specifier = g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1, path.get(), query.get(), nullptr); JS::ConstUTF8CharsZ chars{canonical_specifier, strlen(canonical_specifier)}; JS::RootedString new_specifier{cx, JS_NewStringCopyUTF8Z(cx, chars)}; if (!new_specifier) return false; specifier.set(new_specifier); return true; } /** * gjs_module_resolve: * * Hook SpiderMonkey calls to resolve import specifiers. * * @param importingModulePriv the private value of the #Module object initiating * the import, or a JS null value * @param specifier the import specifier to resolve * * @returns whether an error occurred while resolving the specifier. */ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv, JS::HandleObject module_request) { g_assert((gjs_global_is_type(cx, GjsGlobalType::DEFAULT) || gjs_global_is_type(cx, GjsGlobalType::INTERNAL)) && "gjs_module_resolve can only be called from module-enabled " "globals."); JS::RootedString specifier( cx, JS::GetModuleRequestSpecifier(cx, module_request)); JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); JS::RootedValue v_loader( cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)); g_assert(v_loader.isObject()); JS::RootedObject loader(cx, &v_loader.toObject()); if (!canonicalize_specifier(cx, &specifier)) return nullptr; JS::RootedValueArray<2> args(cx); args[0].set(importingModulePriv); args[1].setString(specifier); gjs_debug(GJS_DEBUG_IMPORTER, "Module resolve hook for module %s (relative to %s), global %p", gjs_debug_string(specifier).c_str(), gjs_debug_value(importingModulePriv).c_str(), global.get()); JS::RootedValue result(cx); if (!JS::Call(cx, loader, "moduleResolveHook", args, &result)) return nullptr; g_assert(result.isObject() && "resolve hook failed to return an object!"); return &result.toObject(); } // Call JS::FinishDynamicModuleImport() with the values stashed in the function. // Can fail in JS::FinishDynamicModuleImport(), but will assert if anything // fails in fetching the stashed values, since that would be a serious GJS bug. GJS_JSAPI_RETURN_CONVENTION static bool finish_import(JSContext* cx, JS::HandleObject evaluation_promise, const JS::CallArgs& args) { GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx); priv->main_loop_release(); JS::Value callback_priv = js::GetFunctionNativeReserved(&args.callee(), 0); g_assert(callback_priv.isObject() && "Wrong private value"); JS::RootedObject callback_data(cx, &callback_priv.toObject()); JS::RootedValue importing_module_priv(cx); JS::RootedValue v_module_request(cx); JS::RootedValue v_internal_promise(cx); bool ok GJS_USED_ASSERT = JS_GetProperty(cx, callback_data, "priv", &importing_module_priv) && JS_GetProperty(cx, callback_data, "promise", &v_internal_promise) && JS_GetProperty(cx, callback_data, "module_request", &v_module_request); g_assert(ok && "Wrong properties on private value"); g_assert(v_module_request.isObject() && "Wrong type for module request"); g_assert(v_internal_promise.isObject() && "Wrong type for promise"); JS::RootedObject module_request(cx, &v_module_request.toObject()); JS::RootedObject internal_promise(cx, &v_internal_promise.toObject()); args.rval().setUndefined(); return JS::FinishDynamicModuleImport(cx, evaluation_promise, importing_module_priv, module_request, internal_promise); } // Failing a JSAPI function may result either in an exception pending on the // context, in which case we must call JS::FinishDynamicModuleImport() to reject // the internal promise; or in an uncatchable exception such as OOM, in which // case we must not call JS::FinishDynamicModuleImport(). GJS_JSAPI_RETURN_CONVENTION static bool fail_import(JSContext* cx, const JS::CallArgs& args) { if (JS_IsExceptionPending(cx)) return finish_import(cx, nullptr, args); return false; } GJS_JSAPI_RETURN_CONVENTION static bool import_rejected(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise rejected"); // Throw the value that the promise is rejected with, so that // FinishDynamicModuleImport will reject the internal_promise with it. JS_SetPendingException(cx, args.get(0), JS::ExceptionStackBehavior::DoNotCapture); return finish_import(cx, nullptr, args); } GJS_JSAPI_RETURN_CONVENTION static bool import_resolved(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_debug(GJS_DEBUG_IMPORTER, "Async import promise resolved"); Gjs::AutoMainRealm ar{cx}; g_assert(args[0].isObject()); JS::RootedObject module(cx, &args[0].toObject()); JS::RootedValue evaluation_promise(cx); if (!JS::ModuleLink(cx, module) || !JS::ModuleEvaluate(cx, module, &evaluation_promise)) return fail_import(cx, args); g_assert(evaluation_promise.isObject() && "got weird value from JS::ModuleEvaluate"); JS::RootedObject evaluation_promise_object(cx, &evaluation_promise.toObject()); return finish_import(cx, evaluation_promise_object, args); } bool gjs_dynamic_module_resolve(JSContext* cx, JS::HandleValue importing_module_priv, JS::HandleObject module_request, JS::HandleObject internal_promise) { g_assert(gjs_global_is_type(cx, GjsGlobalType::DEFAULT) && "gjs_dynamic_module_resolve can only be called from the default " "global."); JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); g_assert(global && "gjs_dynamic_module_resolve must be in a realm"); JS::RootedValue v_loader( cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)); g_assert(v_loader.isObject()); JS::RootedObject loader(cx, &v_loader.toObject()); JS::RootedString specifier( cx, JS::GetModuleRequestSpecifier(cx, module_request)); if (!canonicalize_specifier(cx, &specifier)) return false; JS::RootedObject callback_data(cx, JS_NewPlainObject(cx)); if (!callback_data || !JS_DefineProperty(cx, callback_data, "module_request", module_request, JSPROP_PERMANENT) || !JS_DefineProperty(cx, callback_data, "promise", internal_promise, JSPROP_PERMANENT) || !JS_DefineProperty(cx, callback_data, "priv", importing_module_priv, JSPROP_PERMANENT)) return false; if (importing_module_priv.isObject()) { gjs_debug(GJS_DEBUG_IMPORTER, "Async module resolve hook for module %s (relative to %p), " "global %p", gjs_debug_string(specifier).c_str(), &importing_module_priv.toObject(), global.get()); } else { gjs_debug(GJS_DEBUG_IMPORTER, "Async module resolve hook for module %s (unknown path), " "global %p", gjs_debug_string(specifier).c_str(), global.get()); } JS::RootedValueArray<2> args(cx); args[0].set(importing_module_priv); args[1].setString(specifier); JS::RootedValue result(cx); if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result)) return JS::FinishDynamicModuleImport(cx, nullptr, importing_module_priv, module_request, internal_promise); // Release in finish_import GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx); priv->main_loop_hold(); JS::RootedObject resolved( cx, JS_GetFunctionObject(js::NewFunctionWithReserved( cx, import_resolved, 1, 0, "async import resolved"))); if (!resolved) return false; JS::RootedObject rejected( cx, JS_GetFunctionObject(js::NewFunctionWithReserved( cx, import_rejected, 1, 0, "async import rejected"))); if (!rejected) return false; js::SetFunctionNativeReserved(resolved, 0, JS::ObjectValue(*callback_data)); js::SetFunctionNativeReserved(rejected, 0, JS::ObjectValue(*callback_data)); JS::RootedObject promise(cx, &result.toObject()); // Calling JS::FinishDynamicModuleImport() at the end of the resolve and // reject handlers will also call the module resolve hook. The module will // already have been resolved, but that is how SpiderMonkey obtains the // module object. return JS::AddPromiseReactions(cx, promise, resolved, rejected); } cjs-128.1/cjs/module.h0000664000175000017500000000317615116312211013445 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento #ifndef GJS_MODULE_H_ #define GJS_MODULE_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_module_import(JSContext *cx, JS::HandleObject importer, JS::HandleId id, const char *name, GFile *file); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_get_native_registry(JSObject* global); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_get_module_registry(JSObject* global); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_load(JSContext* cx, const char* identifier, const char* uri); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importing_module_priv, JS::HandleObject module_request); GJS_JSAPI_RETURN_CONVENTION bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref, JS::HandleObject meta_object); GJS_JSAPI_RETURN_CONVENTION bool gjs_dynamic_module_resolve(JSContext* cx, JS::HandleValue importing_module_priv, JS::HandleObject module_request, JS::HandleObject internal_promise); #endif // GJS_MODULE_H_ cjs-128.1/cjs/native.cpp0000664000175000017500000000401015116312211013765 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008-2010 litl, LLC #include #include #include // for tie #include #include // for pair #include #include #include #include "cjs/jsapi-util.h" #include "cjs/native.h" #include "util/log.h" void Gjs::NativeModuleDefineFuncs::add(const char* module_id, GjsDefineModuleFunc func) { bool inserted; std::tie(std::ignore, inserted) = m_modules.insert({module_id, func}); if (!inserted) { g_warning("A second native module tried to register the same id '%s'", module_id); return; } gjs_debug(GJS_DEBUG_NATIVE, "Registered native JS module '%s'", module_id); } /** * is_registered: * @name: name of the module * * Checks if a native module corresponding to @name has already * been registered. This is used to check to see if a name is a * builtin module without starting to try and load it. */ bool Gjs::NativeModuleDefineFuncs::is_registered(const char* name) const { return m_modules.count(name) > 0; } /** * define: * @context: the #JSContext * @id: Name under which the module was registered with add() * @module_out: Return location for a #JSObject * * Loads a builtin native-code module called @name into @module_out by calling * the function to define it. * * Returns: true on success, false if an exception was thrown. */ bool Gjs::NativeModuleDefineFuncs::define( JSContext* context, const char* id, JS::MutableHandleObject module_out) const { gjs_debug(GJS_DEBUG_NATIVE, "Defining native module '%s'", id); const auto& iter = m_modules.find(id); if (iter == m_modules.end()) { gjs_throw(context, "No native module '%s' has registered itself", id); return false; } return iter->second(context, module_out); } cjs-128.1/cjs/native.h0000664000175000017500000000232615116312211013442 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GJS_NATIVE_H_ #define GJS_NATIVE_H_ #include #include #include #include #include "cjs/macros.h" namespace Gjs { class NativeModuleDefineFuncs { NativeModuleDefineFuncs() {} typedef bool (*GjsDefineModuleFunc)(JSContext* context, JS::MutableHandleObject module_out); std::unordered_map m_modules; public: static NativeModuleDefineFuncs& get() { static NativeModuleDefineFuncs the_singleton; return the_singleton; } /* called on context init */ void add(const char* module_id, GjsDefineModuleFunc func); // called by importer.cpp to to check for already loaded modules [[nodiscard]] bool is_registered(const char* name) const; // called by importer.cpp to load a built-in native module GJS_JSAPI_RETURN_CONVENTION bool define(JSContext* cx, const char* name, JS::MutableHandleObject module_out) const; }; }; // namespace Gjs #endif // GJS_NATIVE_H_ cjs-128.1/cjs/objectbox.cpp0000664000175000017500000000736515116312211014476 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Marco Trevisan #include #include // for find #include #include #include #include // for JS_ReportOutOfMemory #include // for GCPolicy (ptr only), NonGCPointe... #include #include #include #include #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/objectbox.h" #include "util/log.h" /* cjs/objectbox.cpp - GObject boxed type used to "box" a JS object so that it * can be passed to or returned from a GObject signal, or used as the type of a * GObject property. */ namespace JS { template <> struct GCPolicy : NonGCPointerPolicy {}; } // namespace JS namespace { JS::PersistentRooted> m_wrappers; } struct ObjectBox::impl { impl(ObjectBox* parent, JSObject* obj) : m_parent(parent), m_root(obj) { g_atomic_ref_count_init(&m_refcount); debug("Constructed"); } GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx) { if (!m_wrappers.append(m_parent)) { JS_ReportOutOfMemory(cx); return false; } return true; } ~impl() { auto it = std::find(m_wrappers.begin(), m_wrappers.end(), m_parent); m_wrappers.erase(it); debug("Finalized"); } void ref() { debug("incref"); g_atomic_ref_count_inc(&m_refcount); } void unref() { debug("decref"); if (g_atomic_ref_count_dec(&m_refcount)) delete m_parent; } void debug(const char* what GJS_USED_VERBOSE_LIFECYCLE) { gjs_debug_lifecycle(GJS_DEBUG_GBOXED, "%s: ObjectBox %p, JSObject %s", what, m_parent, gjs_debug_object(m_root).c_str()); } ObjectBox* m_parent; JS::Heap m_root; gatomicrefcount m_refcount; }; ObjectBox::ObjectBox(JSObject* obj) : m_impl(new ObjectBox::impl(this, obj)) {} void ObjectBox::destroy(ObjectBox* object) { object->m_impl->unref(); } void ObjectBox::destroy_impl(ObjectBox::impl* impl) { delete impl; } ObjectBox::Ptr ObjectBox::boxed(JSContext* cx, JSObject* obj) { ObjectBox::Ptr box; ObjectBox** found = std::find_if(m_wrappers.begin(), m_wrappers.end(), [obj](ObjectBox* b) { return b->m_impl->m_root == obj; }); if (found != m_wrappers.end()) { box = *found; box->m_impl->ref(); box->m_impl->debug("Reusing box"); } else { box = new ObjectBox(obj); if (!box->m_impl->init(cx)) return nullptr; } return box; } JSObject* ObjectBox::object_for_c_ptr(JSContext* cx, ObjectBox* box) { if (!box) { gjs_throw(cx, "Cannot get JSObject for null ObjectBox pointer"); return nullptr; } box->m_impl->debug("retrieved JSObject"); return box->m_impl->m_root.get(); } void* ObjectBox::boxed_copy(void* boxed) { auto* box = static_cast(boxed); box->m_impl->ref(); return box; } void ObjectBox::boxed_free(void* boxed) { auto* box = static_cast(boxed); box->m_impl->unref(); } GType ObjectBox::gtype() { // Initialization of static local variable guaranteed only once in C++11 static GType type_id = g_boxed_type_register_static( "JSObject", &ObjectBox::boxed_copy, &ObjectBox::boxed_free); return type_id; } void ObjectBox::trace(JSTracer* trc) { JS::TraceEdge(trc, &m_impl->m_root, "object in ObjectBox"); } cjs-128.1/cjs/objectbox.h0000664000175000017500000000172715116312211014137 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Marco Trevisan #pragma once #include #include #include #include "cjs/jsapi-util.h" #include "cjs/macros.h" class JSTracer; class ObjectBox { static void destroy(ObjectBox*); public: using Ptr = GjsAutoPointer; [[nodiscard]] static GType gtype(); GJS_JSAPI_RETURN_CONVENTION static ObjectBox::Ptr boxed(JSContext*, JSObject*); GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext*, ObjectBox*); void trace(JSTracer* trc); private: explicit ObjectBox(JSObject*); static void* boxed_copy(void*); static void boxed_free(void*); struct impl; static void destroy_impl(impl*); GjsAutoPointer m_impl; }; cjs-128.1/cjs/profiler-private.h0000664000175000017500000000406515116312211015450 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Endless Mobile, Inc. #ifndef GJS_PROFILER_PRIVATE_H_ #define GJS_PROFILER_PRIVATE_H_ #include #include #include // for JSFinalizeStatus, JSGCStatus, GCReason #include #include #include #include #include "cjs/context.h" #include "cjs/profiler.h" class AutoProfilerLabel { public: explicit inline AutoProfilerLabel(JSContext* cx, const char* label, const char* dynamicString, JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::OTHER, uint32_t flags = 0) : m_stack(js::GetContextProfilingStackIfEnabled(cx)) { if (m_stack) m_stack->pushLabelFrame(label, dynamicString, this, categoryPair, flags); } inline ~AutoProfilerLabel() { if (m_stack) m_stack->pop(); } private: ProfilingStack* m_stack; }; namespace Gjs { enum GCCounters { GC_HEAP_BYTES, MALLOC_HEAP_BYTES, N_COUNTERS }; } // namespace Gjs GjsProfiler *_gjs_profiler_new(GjsContext *context); void _gjs_profiler_free(GjsProfiler *self); void _gjs_profiler_add_mark(GjsProfiler* self, int64_t time, int64_t duration, const char* group, const char* name, const char* message); [[nodiscard]] bool _gjs_profiler_sample_gc_memory_info( GjsProfiler* self, int64_t gc_counters[Gjs::GCCounters::N_COUNTERS]); [[nodiscard]] bool _gjs_profiler_is_running(GjsProfiler* self); void _gjs_profiler_setup_signals(GjsProfiler *self, GjsContext *context); void _gjs_profiler_set_finalize_status(GjsProfiler*, JSFinalizeStatus); void _gjs_profiler_set_gc_status(GjsProfiler*, JSGCStatus, JS::GCReason); #endif // GJS_PROFILER_PRIVATE_H_ cjs-128.1/cjs/profiler.cpp0000664000175000017500000007527115116312211014342 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Christian Hergert #include // for ENABLE_PROFILER, HAVE_SYS_SYSCALL_H, HAVE_UNISTD_H #ifdef HAVE_SIGNAL_H # include // for siginfo_t, sigevent, sigaction, SIGPROF, ... #endif #ifdef ENABLE_PROFILER // IWYU has a weird loop where if this is present, it asks for it to be removed, // and if absent, asks for it to be added # include // IWYU pragma: keep # include # include # include // for sscanf # include // for memcpy, strlen # include // for __NR_gettid # include // for timer_t # include // for size_t, CLOCK_MONOTONIC, itimerspec, ... # ifdef HAVE_UNISTD_H # include // for getpid, syscall # endif # include #endif #include #include #ifdef ENABLE_PROFILER # ifdef G_OS_UNIX # include # endif # include #endif #include // for JSFinalizeStatus, JSGCStatus, GCReason #include // for EnableContextProfilingStack, ... #include #include // for ProfilingStack operators #include "cjs/context.h" #include "cjs/jsapi-util.h" // for gjs_explain_gc_reason #include "cjs/mem-private.h" #include "cjs/profiler-private.h" #include "cjs/profiler.h" #define FLUSH_DELAY_SECONDS 3 /* * This is mostly non-exciting code wrapping the builtin Profiler in * mozjs. In particular, the profiler consumer is required to "bring your * own sampler". We do the very non-surprising thing of using POSIX * timers to deliver SIGPROF to the thread containing the JSContext. * * However, we do use a Linux'ism that allows us to deliver the signal * to only a single thread. Doing this in a generic fashion would * require thread-registration so that we can mask SIGPROF from all * threads except the JS thread. The gecko engine uses tgkill() to do * this with a secondary thread instead of using POSIX timers. We could * do this too, but it would still be Linux-only. * * Another option might be to use pthread_kill() and a secondary thread * to perform the notification. * * From within the signal handler, we process the current stack as * delivered to us from the JSContext. Any pointer data that comes from * the runtime has to be copied, so we keep our own dedup'd string * pointers for JavaScript file/line information. Non-JS instruction * pointers are just fine, as they can be resolved by parsing the ELF for * the file mapped on disk containing that address. * * As much of this code has to run from signal handlers, it is very * important that we don't use anything that can malloc() or lock, or * deadlocks are very likely. Most of GjsProfilerCapture is signal-safe. */ #define SAMPLES_PER_SEC G_GUINT64_CONSTANT(1000) #define NSEC_PER_SEC G_GUINT64_CONSTANT(1000000000) G_DEFINE_POINTER_TYPE(GjsProfiler, gjs_profiler) struct _GjsProfiler { #ifdef ENABLE_PROFILER /* The stack for the JSContext profiler to use for current stack * information while executing. We will look into this during our * SIGPROF handler. */ ProfilingStack stack; /* The context being profiled */ JSContext *cx; /* Buffers and writes our sampled stacks */ SysprofCaptureWriter* capture; GSource* periodic_flush; SysprofCaptureWriter* target_capture; // Cache previous values of counters so that we don't overrun the output // with counters that don't change very often uint64_t last_counter_values[GJS_N_COUNTERS]; #endif /* ENABLE_PROFILER */ /* The filename to write to */ char *filename; /* An FD to capture to */ int fd; #ifdef ENABLE_PROFILER /* Our POSIX timer to wakeup SIGPROF */ timer_t timer; /* Cached copy of our pid */ GPid pid; /* Timing information */ int64_t gc_begin_time; int64_t sweep_begin_time; int64_t group_sweep_begin_time; const char* gc_reason; // statically allocated /* GLib signal handler ID for SIGUSR2 */ unsigned sigusr2_id; unsigned counter_base; // index of first GObject memory counter unsigned gc_counter_base; // index of first GC stats counter #endif /* ENABLE_PROFILER */ /* If we are currently sampling */ unsigned running : 1; }; static GjsContext *profiling_context; #ifdef ENABLE_PROFILER /* * gjs_profiler_extract_maps: * * This function will write the mapped section information to the * capture file so that the callgraph builder can generate symbols * from the stack addresses provided. * * Returns: %TRUE if successful; otherwise %FALSE and the profile * should abort. */ [[nodiscard]] static bool gjs_profiler_extract_maps(GjsProfiler* self) { int64_t now = g_get_monotonic_time() * 1000L; g_assert(((void) "Profiler must be set up before extracting maps", self)); GjsAutoChar path = g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid)); char *content_tmp; size_t len; if (!g_file_get_contents(path, &content_tmp, &len, nullptr)) return false; GjsAutoChar content = content_tmp; GjsAutoStrv lines = g_strsplit(content, "\n", 0); for (size_t ix = 0; lines[ix]; ix++) { char file[256]; unsigned long start; unsigned long end; unsigned long offset; unsigned long inode; file[sizeof file - 1] = '\0'; int r = sscanf(lines[ix], "%lx-%lx %*15s %lx %*x:%*x %lu %255s", &start, &end, &offset, &inode, file); if (r != 5) continue; if (strcmp("[vdso]", file) == 0) { offset = 0; inode = 0; } if (!sysprof_capture_writer_add_map(self->capture, now, -1, self->pid, start, end, offset, inode, file)) return false; } return true; } static void setup_counter_helper(SysprofCaptureCounter* counter, const char* counter_name, unsigned counter_base, size_t ix) { g_snprintf(counter->category, sizeof counter->category, "GJS"); g_snprintf(counter->name, sizeof counter->name, "%s", counter_name); g_snprintf(counter->description, sizeof counter->description, "%s", GJS_COUNTER_DESCRIPTIONS[ix]); counter->id = uint32_t(counter_base + ix); counter->type = SYSPROF_CAPTURE_COUNTER_INT64; counter->value.v64 = 0; } [[nodiscard]] static bool gjs_profiler_define_counters(GjsProfiler* self) { int64_t now = g_get_monotonic_time() * 1000L; g_assert(self && "Profiler must be set up before defining counters"); std::array counters; self->counter_base = sysprof_capture_writer_request_counter(self->capture, GJS_N_COUNTERS); # define SETUP_COUNTER(counter_name, ix) \ setup_counter_helper(&counters[ix], #counter_name, self->counter_base, \ ix); GJS_FOR_EACH_COUNTER(SETUP_COUNTER); # undef SETUP_COUNTER if (!sysprof_capture_writer_define_counters( self->capture, now, -1, self->pid, counters.data(), GJS_N_COUNTERS)) return false; std::array gc_counters; self->gc_counter_base = sysprof_capture_writer_request_counter( self->capture, Gjs::GCCounters::N_COUNTERS); constexpr size_t category_size = sizeof gc_counters[0].category; constexpr size_t name_size = sizeof gc_counters[0].name; constexpr size_t description_size = sizeof gc_counters[0].description; for (size_t ix = 0; ix < Gjs::GCCounters::N_COUNTERS; ix++) { g_snprintf(gc_counters[ix].category, category_size, "GJS"); gc_counters[ix].id = uint32_t(self->gc_counter_base + ix); gc_counters[ix].type = SYSPROF_CAPTURE_COUNTER_INT64; gc_counters[ix].value.v64 = 0; } g_snprintf(gc_counters[Gjs::GCCounters::GC_HEAP_BYTES].name, name_size, "GC bytes"); g_snprintf(gc_counters[Gjs::GCCounters::GC_HEAP_BYTES].description, description_size, "Bytes used in GC heap"); g_snprintf(gc_counters[Gjs::GCCounters::MALLOC_HEAP_BYTES].name, name_size, "Malloc bytes"); g_snprintf(gc_counters[Gjs::GCCounters::MALLOC_HEAP_BYTES].description, description_size, "Malloc bytes owned by tenured GC things"); return sysprof_capture_writer_define_counters(self->capture, now, -1, self->pid, gc_counters.data(), Gjs::GCCounters::N_COUNTERS); } #endif /* ENABLE_PROFILER */ /* * _gjs_profiler_new: * @context: The #GjsContext to profile * * This creates a new profiler for the #JSContext. It is important that * this instance is freed with _gjs_profiler_free() before the context is * destroyed. * * Call gjs_profiler_start() to enable the profiler, and gjs_profiler_stop() * when you have finished. * * The profiler works by enabling the JS profiler in spidermonkey so that * sample information is available. A POSIX timer is used to signal SIGPROF * to the process on a regular interval to collect the most recent profile * sample and stash it away. It is a programming error to mask SIGPROF from * the thread controlling the JS context. * * If another #GjsContext already has a profiler, or @context already has one, * then returns %NULL instead. * * Returns: (transfer full) (nullable): A newly allocated #GjsProfiler */ GjsProfiler * _gjs_profiler_new(GjsContext *context) { g_return_val_if_fail(context, nullptr); if (profiling_context == context) { g_critical("You can only create one profiler at a time."); return nullptr; } if (profiling_context) { g_message("Not going to profile GjsContext %p; you can only profile " "one context at a time.", context); return nullptr; } GjsProfiler *self = g_new0(GjsProfiler, 1); #ifdef ENABLE_PROFILER self->cx = static_cast(gjs_context_get_native_context(context)); self->pid = getpid(); #endif self->fd = -1; profiling_context = context; return self; } /* * _gjs_profiler_free: * @self: A #GjsProfiler * * Frees a profiler instance and cleans up any allocated data. * * If the profiler is running, it will be stopped. This may result in blocking * to write the contents of the buffer to the underlying file-descriptor. */ void _gjs_profiler_free(GjsProfiler *self) { if (!self) return; if (self->running) gjs_profiler_stop(self); profiling_context = nullptr; g_clear_pointer(&self->filename, g_free); #ifdef ENABLE_PROFILER g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); g_clear_pointer(&self->target_capture, sysprof_capture_writer_unref); if (self->fd != -1) close(self->fd); self->stack.~ProfilingStack(); #endif g_free(self); } /* * _gjs_profiler_is_running: * @self: A #GjsProfiler * * Checks if the profiler is currently running. This means that the JS * profiler is enabled and POSIX signal timers are registered. * * Returns: %TRUE if the profiler is active. */ bool _gjs_profiler_is_running(GjsProfiler *self) { g_return_val_if_fail(self, false); return self->running; } #ifdef ENABLE_PROFILER static void gjs_profiler_sigprof(int signum [[maybe_unused]], siginfo_t* info, void*) { GjsProfiler *self = gjs_context_get_profiler(profiling_context); g_assert(((void) "SIGPROF handler called with invalid signal info", info)); g_assert(((void) "SIGPROF handler called with other signal", info->si_signo == SIGPROF)); /* * NOTE: * * This is the SIGPROF signal handler. Everything done in this thread * needs to be things that are safe to do in a signal handler. One thing * that is not okay to do, is *malloc*. */ if (!self || info->si_code != SI_TIMER) return; uint32_t depth = self->stack.stackSize(); if (depth == 0) return; int64_t now = g_get_monotonic_time() * 1000L; /* NOTE: cppcheck warns that alloca() is not recommended since it can * easily overflow the stack; however, dynamic allocation is not an option * here since we are in a signal handler. */ SysprofCaptureAddress* addrs = // cppcheck-suppress allocaCalled static_cast(alloca(sizeof *addrs * depth)); for (uint32_t ix = 0; ix < depth; ix++) { js::ProfilingStackFrame& entry = self->stack.frames[ix]; const char *label = entry.label(); const char *dynamic_string = entry.dynamicString(); uint32_t flipped = depth - 1 - ix; size_t label_length = strlen(label); /* * 512 is an arbitrarily large size, very likely to be enough to * hold the final string. */ char final_string[512]; char *position = final_string; size_t available_length = sizeof (final_string) - 1; if (label_length > 0) { label_length = MIN(label_length, available_length); /* Start copying the label to the final string */ memcpy(position, label, label_length); available_length -= label_length; position += label_length; /* * Add a space in between the label and the dynamic string, * if there is one. */ if (dynamic_string && available_length > 0) { *position++ = ' '; available_length--; } } /* Now append the dynamic string at the end of the final string. * The string is cut in case it doesn't fit the remaining space. */ if (dynamic_string) { size_t dynamic_string_length = strlen(dynamic_string); if (dynamic_string_length > 0) { size_t remaining_length = MIN(available_length, dynamic_string_length); memcpy(position, dynamic_string, remaining_length); position += remaining_length; } } *position = 0; /* * GeckoProfiler will put "js::RunScript" on the stack, but it has * a stack address of "this", which is not terribly useful since * everything will show up as [stack] when building callgraphs. */ if (final_string[0] != '\0') addrs[flipped] = sysprof_capture_writer_add_jitmap(self->capture, final_string); else addrs[flipped] = SysprofCaptureAddress(entry.stackAddress()); } if (!sysprof_capture_writer_add_sample(self->capture, now, -1, self->pid, -1, addrs, depth)) { gjs_profiler_stop(self); return; } unsigned ids[GJS_N_COUNTERS]; SysprofCaptureCounterValue values[GJS_N_COUNTERS]; size_t new_counts = 0; # define FETCH_COUNTERS(name, ix) \ { \ uint64_t count = GJS_GET_COUNTER(name); \ if (count != self->last_counter_values[ix]) { \ ids[new_counts] = self->counter_base + ix; \ values[new_counts].v64 = count; \ new_counts++; \ } \ self->last_counter_values[ix] = count; \ } GJS_FOR_EACH_COUNTER(FETCH_COUNTERS); # undef FETCH_COUNTERS if (new_counts > 0 && !sysprof_capture_writer_set_counters(self->capture, now, -1, self->pid, ids, values, new_counts)) gjs_profiler_stop(self); } static gboolean profiler_auto_flush_cb(void* user_data) { auto* self = static_cast(user_data); if (!self->running) return G_SOURCE_REMOVE; sysprof_capture_writer_flush(self->capture); return G_SOURCE_CONTINUE; } #endif /* ENABLE_PROFILER */ /** * gjs_profiler_start: * @self: A #GjsProfiler * * As expected, this starts the GjsProfiler. * * This will enable the underlying JS profiler and register a POSIX timer to * deliver SIGPROF on the configured sampling frequency. * * To reduce sampling overhead, #GjsProfiler stashes information about the * profile to be calculated once the profiler has been disabled. Calling * gjs_profiler_stop() will result in that delayed work to be completed. * * You should call gjs_profiler_stop() when the profiler is no longer needed. */ void gjs_profiler_start(GjsProfiler *self) { g_return_if_fail(self); if (self->running) return; #ifdef ENABLE_PROFILER g_return_if_fail(!self->capture); struct sigaction sa = {{0}}; struct sigevent sev = {{0}}; struct itimerspec its = {{0}}; struct itimerspec old_its; if (self->target_capture) { self->capture = sysprof_capture_writer_ref(self->target_capture); } else if (self->fd != -1) { self->capture = sysprof_capture_writer_new_from_fd(self->fd, 0); self->fd = -1; } else { GjsAutoChar path = g_strdup(self->filename); if (!path) path = g_strdup_printf("gjs-%jd.syscap", intmax_t(self->pid)); self->capture = sysprof_capture_writer_new(path, 0); } if (!self->capture) { g_warning("Failed to open profile capture"); return; } /* Automatically flush to be resilient against SIGINT, etc */ if (!self->periodic_flush) { self->periodic_flush = g_timeout_source_new_seconds(FLUSH_DELAY_SECONDS); g_source_set_name(self->periodic_flush, "[sysprof-capture-writer-flush]"); g_source_set_priority(self->periodic_flush, G_PRIORITY_LOW + 100); g_source_set_callback(self->periodic_flush, (GSourceFunc)profiler_auto_flush_cb, self, nullptr); g_source_attach(self->periodic_flush, g_main_context_get_thread_default()); } if (!gjs_profiler_extract_maps(self)) { g_warning("Failed to extract proc maps"); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); return; } if (!gjs_profiler_define_counters(self)) { g_warning("Failed to define sysprof counters"); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); return; } /* Setup our signal handler for SIGPROF delivery */ sa.sa_flags = SA_RESTART | SA_SIGINFO; sa.sa_sigaction = gjs_profiler_sigprof; sigemptyset(&sa.sa_mask); if (sigaction(SIGPROF, &sa, nullptr) == -1) { g_warning("Failed to register sigaction handler: %s", g_strerror(errno)); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); return; } /* * Create our SIGPROF timer * * We want to receive a SIGPROF signal on the JS thread using our * configured sampling frequency. Instead of allowing any thread to be * notified, we set the _tid value to ensure that only our thread gets * delivery of the signal. This feature is generally just for * threading implementations, but it works for us as well and ensures * that the thread is blocked while we capture the stack. */ sev.sigev_notify = SIGEV_THREAD_ID; sev.sigev_signo = SIGPROF; sev._sigev_un._tid = syscall(__NR_gettid); if (timer_create(CLOCK_MONOTONIC, &sev, &self->timer) == -1) { g_warning("Failed to create profiler timer: %s", g_strerror(errno)); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); return; } /* Calculate sampling interval */ its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC; its.it_value.tv_sec = 0; its.it_value.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC; /* Now start this timer */ if (timer_settime(self->timer, 0, &its, &old_its) != 0) { g_warning("Failed to enable profiler timer: %s", g_strerror(errno)); timer_delete(self->timer); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); return; } self->running = true; /* Notify the JS runtime of where to put stack info */ js::SetContextProfilingStack(self->cx, &self->stack); /* Start recording stack info */ js::EnableContextProfilingStack(self->cx, true); g_message("Profiler started"); #else /* !ENABLE_PROFILER */ self->running = true; g_message("Profiler is disabled. Recompile with it enabled to use."); #endif /* ENABLE_PROFILER */ } /** * gjs_profiler_stop: * @self: A #GjsProfiler * * Stops a currently running #GjsProfiler. If the profiler is not running, * this function will do nothing. * * Some work may be delayed until the end of the capture. Such delayed work * includes flushing the resulting samples and file location information to * disk. * * This may block while writing to disk. Generally, the writes are delivered * to a tmpfs device, and are therefore negligible. */ void gjs_profiler_stop(GjsProfiler *self) { /* Note: can be called from a signal handler */ g_assert(self); if (!self->running) return; #ifdef ENABLE_PROFILER struct itimerspec its = {{0}}; timer_settime(self->timer, 0, &its, nullptr); timer_delete(self->timer); js::EnableContextProfilingStack(self->cx, false); js::SetContextProfilingStack(self->cx, nullptr); sysprof_capture_writer_flush(self->capture); g_clear_pointer(&self->capture, sysprof_capture_writer_unref); g_clear_pointer(&self->periodic_flush, g_source_destroy); g_message("Profiler stopped"); #endif /* ENABLE_PROFILER */ self->running = false; } #ifdef ENABLE_PROFILER static gboolean gjs_profiler_sigusr2(void *data) { GjsContext* context = GJS_CONTEXT(data); GjsProfiler *current_profiler = gjs_context_get_profiler(context); if (current_profiler) { if (_gjs_profiler_is_running(current_profiler)) gjs_profiler_stop(current_profiler); else gjs_profiler_start(current_profiler); } return G_SOURCE_CONTINUE; } #endif /* ENABLE_PROFILER */ /* * _gjs_profiler_setup_signals: * @context: a #GjsContext with a profiler attached * * If you want to simply allow profiling of your process with minimal * fuss, simply call gjs_profiler_setup_signals(). This will allow * enabling and disabling the profiler with SIGUSR2. You must call * this from main() immediately when your program starts and must not * block SIGUSR2 from your signal mask. * * If this is not sufficient, use gjs_profiler_chain_signal() from your * own signal handler to pass the signal to a GjsProfiler. */ void _gjs_profiler_setup_signals(GjsProfiler *self, GjsContext *context) { g_return_if_fail(context == profiling_context); #ifdef ENABLE_PROFILER if (self->sigusr2_id != 0) return; self->sigusr2_id = g_unix_signal_add(SIGUSR2, gjs_profiler_sigusr2, context); #else /* !ENABLE_PROFILER */ g_message("Profiler is disabled. Not setting up signals."); (void)self; #endif /* ENABLE_PROFILER */ } /** * gjs_profiler_chain_signal: * @context: a #GjsContext with a profiler attached * @info: #siginfo_t passed in to signal handler * * Use this to pass a signal info caught by another signal handler to a * GjsProfiler. This might be needed if you have your own complex signal * handling system for which GjsProfiler cannot simply add a SIGUSR2 handler. * * This function should only be called from the JS thread. * * Returns: %TRUE if the signal was handled. */ bool gjs_profiler_chain_signal(GjsContext *context, siginfo_t *info) { #ifdef ENABLE_PROFILER if (info) { if (info->si_signo == SIGPROF) { gjs_profiler_sigprof(SIGPROF, info, nullptr); return true; } if (info->si_signo == SIGUSR2) { gjs_profiler_sigusr2(context); return true; } } #else // !ENABLE_PROFILER (void)context; (void)info; #endif /* ENABLE_PROFILER */ return false; } /** * gjs_profiler_set_capture_writer: * @self: A #GjsProfiler * @capture: (nullable): A #SysprofCaptureWriter * * Set the capture writer to which profiling data is written when the @self * is stopped. */ void gjs_profiler_set_capture_writer(GjsProfiler* self, gpointer capture) { g_return_if_fail(self); g_return_if_fail(!self->running); #ifdef ENABLE_PROFILER g_clear_pointer(&self->target_capture, sysprof_capture_writer_unref); self->target_capture = capture ? sysprof_capture_writer_ref( reinterpret_cast(capture)) : NULL; #else // Unused in the no-profiler case (void)capture; #endif } /** * gjs_profiler_set_filename: * @self: A #GjsProfiler * @filename: string containing a filename * * Set the file to which profiling data is written when the @self is stopped. * By default, this is `gjs-$PID.syscap` in the current directory. */ void gjs_profiler_set_filename(GjsProfiler *self, const char *filename) { g_return_if_fail(self); g_return_if_fail(!self->running); g_free(self->filename); self->filename = g_strdup(filename); } void _gjs_profiler_add_mark(GjsProfiler* self, int64_t time_nsec, int64_t duration_nsec, const char* group, const char* name, const char* message) { g_return_if_fail(self); g_return_if_fail(group); g_return_if_fail(name); #ifdef ENABLE_PROFILER if (self->running && self->capture != nullptr) { sysprof_capture_writer_add_mark(self->capture, time_nsec, -1, self->pid, duration_nsec, group, name, message); } #else // Unused in the no-profiler case (void)time_nsec; (void)duration_nsec; (void)message; #endif } bool _gjs_profiler_sample_gc_memory_info( GjsProfiler* self, int64_t gc_counters[Gjs::GCCounters::N_COUNTERS]) { g_return_val_if_fail(self, false); #ifdef ENABLE_PROFILER if (self->running && self->capture) { unsigned ids[Gjs::GCCounters::N_COUNTERS]; SysprofCaptureCounterValue values[Gjs::GCCounters::N_COUNTERS]; for (size_t ix = 0; ix < Gjs::GCCounters::N_COUNTERS; ix++) { ids[ix] = self->gc_counter_base + ix; values[ix].v64 = gc_counters[ix]; } int64_t now = g_get_monotonic_time() * 1000L; if (!sysprof_capture_writer_set_counters(self->capture, now, -1, self->pid, ids, values, Gjs::GCCounters::N_COUNTERS)) return false; } #else // Unused in the no-profiler case (void)gc_counters; #endif return true; } void gjs_profiler_set_fd(GjsProfiler* self, int fd) { g_return_if_fail(self); g_return_if_fail(!self->filename); g_return_if_fail(!self->running); #ifdef ENABLE_PROFILER if (self->fd != fd) { if (self->fd != -1) close(self->fd); self->fd = fd; } #else (void)fd; // Unused in the no-profiler case #endif } void _gjs_profiler_set_finalize_status(GjsProfiler* self, JSFinalizeStatus status) { #ifdef ENABLE_PROFILER // Implementation note for mozjs-128: // // Sweeping happens in three phases: // 1st phase (JSFINALIZE_GROUP_PREPARE): the collector prepares to sweep a // group of zones. 2nd phase (JSFINALIZE_GROUP_START): weak references to // unmarked things have been removed, but no GC thing has been swept. 3rd // Phase (JSFINALIZE_GROUP_END): all dead GC things for a group of zones // have been swept. The above repeats for each sweep group. // JSFINALIZE_COLLECTION_END occurs at the end of all GC. (see jsgc.cpp, // BeginSweepPhase/BeginSweepingZoneGroup and SweepPhase, all called from // IncrementalCollectSlice). // // Incremental GC muddies the waters, because BeginSweepPhase is always run // to entirety, but SweepPhase can be run incrementally and mixed with JS // code runs or even native code, when MaybeGC/IncrementalGC return. // After GROUP_START, the collector may yield to the mutator meaning JS code // can run between the callback for GROUP_START and GROUP_END. int64_t now = g_get_monotonic_time() * 1000L; switch (status) { case JSFINALIZE_GROUP_PREPARE: self->sweep_begin_time = now; break; case JSFINALIZE_GROUP_START: self->group_sweep_begin_time = now; break; case JSFINALIZE_GROUP_END: if (self->group_sweep_begin_time != 0) { _gjs_profiler_add_mark(self, self->group_sweep_begin_time, now - self->group_sweep_begin_time, "GJS", "Group sweep", nullptr); } self->group_sweep_begin_time = 0; break; case JSFINALIZE_COLLECTION_END: if (self->sweep_begin_time != 0) { _gjs_profiler_add_mark(self, self->sweep_begin_time, now - self->sweep_begin_time, "GJS", "Sweep", nullptr); } self->sweep_begin_time = 0; break; default: g_assert_not_reached(); } #else (void)self; (void)status; #endif } void _gjs_profiler_set_gc_status(GjsProfiler* self, JSGCStatus status, JS::GCReason reason) { #ifdef ENABLE_PROFILER int64_t now = g_get_monotonic_time() * 1000L; switch (status) { case JSGC_BEGIN: self->gc_begin_time = now; self->gc_reason = gjs_explain_gc_reason(reason); break; case JSGC_END: if (self->gc_begin_time != 0) { _gjs_profiler_add_mark(self, self->gc_begin_time, now - self->gc_begin_time, "GJS", "Garbage collection", self->gc_reason); } self->gc_begin_time = 0; self->gc_reason = nullptr; break; default: g_assert_not_reached(); } #else (void)self; (void)status; (void)reason; #endif } cjs-128.1/cjs/profiler.h0000664000175000017500000000167415116312211014003 0ustar fabiofabio/* profiler.h * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2016 Christian Hergert */ #ifndef GJS_PROFILER_H_ #define GJS_PROFILER_H_ #if !defined(INSIDE_GJS_H) && !defined(GJS_COMPILATION) # error "Only can be included directly." #endif #include #include #include G_BEGIN_DECLS #define GJS_TYPE_PROFILER (gjs_profiler_get_type()) typedef struct _GjsProfiler GjsProfiler; GJS_EXPORT GType gjs_profiler_get_type(void); GJS_EXPORT void gjs_profiler_set_capture_writer(GjsProfiler* self, void* capture); GJS_EXPORT void gjs_profiler_set_filename(GjsProfiler *self, const char *filename); GJS_EXPORT void gjs_profiler_set_fd(GjsProfiler* self, int fd); GJS_EXPORT void gjs_profiler_start(GjsProfiler *self); GJS_EXPORT void gjs_profiler_stop(GjsProfiler *self); G_END_DECLS #endif // GJS_PROFILER_H_ cjs-128.1/cjs/promise.cpp0000664000175000017500000002040415116312211014162 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh // SPDX-FileCopyrightText: 2021 Marco Trevisan #include #include // for size_t #include #include #include // for JS::IsCallable #include #include // for JS_DefineFunctions #include #include #include #include // for JS_NewPlainObject #include // for RunJobs #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/promise.h" #include "util/log.h" /** * promise.cpp - This file implements a custom GSource, PromiseJobQueueSource, * which handles promise dispatching within GJS. Custom GSources are able to * control under which conditions they dispatch. PromiseJobQueueSource will * always dispatch if even a single Promise is enqueued and will continue * dispatching until all Promises (also known as "Jobs" within SpiderMonkey) * are run. While this does technically mean Promises can starve the mainloop * if run recursively, this is intentional. Within JavaScript Promises are * considered "microtasks" and a microtask must run before any other task * continues. * * PromiseJobQueueSource is attached to the thread's default GMainContext with * a default priority of -1000. This is 10x the priority of G_PRIORITY_HIGH and * no application code should attempt to override this. * * See doc/Custom-GSources.md for more background information on custom * GSources and microtasks */ namespace Gjs { /** * @brief a custom GSource which handles draining our job queue. */ class PromiseJobDispatcher::Source : public GSource { // The private GJS context this source runs within. GjsContextPrivate* m_gjs; // The main context this source attaches to. GjsAutoMainContext m_main_context; // The cancellable that stops this source. GjsAutoUnref m_cancellable; GjsAutoPointer m_cancellable_source; // G_PRIORITY_HIGH is normally -100, we set 10 times that to ensure our // source always has the greatest priority. This means our prepare will // be called before other sources, and prepare will determine whether // we dispatch. static constexpr int PRIORITY = 10 * G_PRIORITY_HIGH; // GSource custom functions static GSourceFuncs source_funcs; // Called to determine whether the source should run (dispatch) in the // next event loop iteration. If the job queue is not empty we return true // to schedule a dispatch. gboolean prepare(int* timeout [[maybe_unused]]) { return !m_gjs->empty(); } gboolean dispatch() { if (g_cancellable_is_cancelled(m_cancellable)) return G_SOURCE_REMOVE; // The ready time is sometimes set to 0 to kick us out of polling, // we need to reset the value here or this source will always be the // next one to execute. (it will starve the other sources) g_source_set_ready_time(this, -1); // Drain the job queue. js::RunJobs(m_gjs->context()); return G_SOURCE_CONTINUE; } public: /** * @brief Constructs a new GjsPromiseJobQueueSource GSource and adds a * reference to the associated main context. * * @param cx the current JSContext * @param cancellable an optional cancellable */ Source(GjsContextPrivate* gjs, GMainContext* main_context) : m_gjs(gjs), m_main_context(main_context, GjsAutoTakeOwnership()), m_cancellable(g_cancellable_new()), m_cancellable_source(g_cancellable_source_new(m_cancellable)) { g_source_set_priority(this, PRIORITY); #if GLIB_CHECK_VERSION(2, 70, 0) g_source_set_static_name(this, "GjsPromiseJobQueueSource"); #else g_source_set_name(this, "GjsPromiseJobQueueSource"); #endif // Add our cancellable source to our main source, // this will trigger the main source if our cancellable // is cancelled. g_source_add_child_source(this, m_cancellable_source); } void* operator new(size_t size) { return g_source_new(&source_funcs, size); } void operator delete(void* p) { g_source_unref(static_cast(p)); } bool is_running() { return !!g_source_get_context(this); } /** * @brief Trigger the cancellable, detaching our source. */ void cancel() { g_cancellable_cancel(m_cancellable); } /** * @brief Reset the cancellable and prevent the source from stopping, * overriding a previous cancel() call. Called by start() in * PromiseJobDispatcher to ensure the custom source will start. */ void reset() { if (!g_cancellable_is_cancelled(m_cancellable)) return; gjs_debug(GJS_DEBUG_MAINLOOP, "Uncancelling promise job dispatcher"); if (is_running()) g_source_remove_child_source(this, m_cancellable_source); else g_source_destroy(m_cancellable_source); // Drop the old cancellable and create a new one, as per // https://docs.gtk.org/gio/method.Cancellable.reset.html m_cancellable = g_cancellable_new(); m_cancellable_source = g_cancellable_source_new(m_cancellable); g_source_add_child_source(this, m_cancellable_source); } }; GSourceFuncs PromiseJobDispatcher::Source::source_funcs = { [](GSource* source, int* timeout) { return static_cast(source)->prepare(timeout); }, nullptr, // check [](GSource* source, GSourceFunc, void*) { return static_cast(source)->dispatch(); }, [](GSource* source) { static_cast(source)->~Source(); }, }; PromiseJobDispatcher::PromiseJobDispatcher(GjsContextPrivate* gjs) // Acquire a guaranteed reference to this thread's default main context : m_main_context(g_main_context_ref_thread_default()), // Create and reference our custom GSource m_source(std::make_unique(gjs, m_main_context)) {} PromiseJobDispatcher::~PromiseJobDispatcher() { g_source_destroy(m_source.get()); } bool PromiseJobDispatcher::is_running() { return m_source->is_running(); } void PromiseJobDispatcher::start() { // Reset the cancellable m_source->reset(); // Don't re-attach if the task is already running if (is_running()) return; gjs_debug(GJS_DEBUG_MAINLOOP, "Starting promise job dispatcher"); g_source_attach(m_source.get(), m_main_context); } void PromiseJobDispatcher::stop() { gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping promise job dispatcher"); m_source->cancel(); } }; // namespace Gjs GJS_JSAPI_RETURN_CONVENTION bool drain_microtask_queue(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); js::RunJobs(cx); args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION bool set_main_loop_hook(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject callback(cx); if (!gjs_parse_call_args(cx, "setMainLoopHook", args, "o", "callback", &callback)) { return false; } if (!JS::IsCallable(callback)) { gjs_throw(cx, "Main loop hook must be callable"); return false; } gjs_debug(GJS_DEBUG_MAINLOOP, "Set main loop hook to %s", gjs_debug_object(callback).c_str()); GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx); if (!priv->set_main_loop_hook(callback)) { gjs_throw( cx, "A mainloop is already running. Did you already call runAsync()?"); return false; } args.rval().setUndefined(); return true; } JSFunctionSpec gjs_native_promise_module_funcs[] = { JS_FN("drainMicrotaskQueue", &drain_microtask_queue, 0, 0), JS_FN("setMainLoopHook", &set_main_loop_hook, 1, 0), JS_FS_END}; bool gjs_define_native_promise_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); if (!module) return false; return JS_DefineFunctions(cx, module, gjs_native_promise_module_funcs); } cjs-128.1/cjs/promise.h0000664000175000017500000000252515116312211013633 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh #pragma once #include #include #include #include #include "cjs/jsapi-util.h" class GjsContextPrivate; using GjsAutoMainContext = GjsAutoPointer; namespace Gjs { /** * @brief A class which wraps a custom GSource and handles associating it with a * GMainContext. While it is running, it will attach the source to the main * context so that promise jobs are run at the appropriate time. */ class PromiseJobDispatcher { class Source; // The thread-default GMainContext GjsAutoMainContext m_main_context; // The custom source. std::unique_ptr m_source; public: explicit PromiseJobDispatcher(GjsContextPrivate*); ~PromiseJobDispatcher(); /** * @brief Start (or resume) dispatching jobs from the promise job queue */ void start(); /** * @brief Stop dispatching */ void stop(); /** * @brief Whether the dispatcher is currently running */ bool is_running(); }; }; // namespace Gjs bool gjs_define_native_promise_stuff(JSContext* cx, JS::MutableHandleObject module); cjs-128.1/cjs/stack.cpp0000664000175000017500000000363415116312211013617 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2009 Red Hat, Inc. #include #include // for stderr #include #include #include #include #include #include #include // for UniqueChars #include #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" void gjs_context_print_stack_stderr(GjsContext *context) { JSContext *cx = (JSContext*) gjs_context_get_native_context(context); g_printerr("== Stack trace for context %p ==\n", context); js::DumpBacktrace(cx, stderr); } void gjs_dumpstack(void) { GjsSmartPointer contexts = gjs_context_get_all(); GList *iter; for (iter = contexts; iter; iter = iter->next) { GjsAutoUnref context(GJS_CONTEXT(iter->data)); gjs_context_print_stack_stderr(context); } } std::string gjs_dumpstack_string() { std::string out; std::ostringstream all_traces; GjsSmartPointer contexts = gjs_context_get_all(); js::Sprinter printer; GList *iter; for (iter = contexts; iter; iter = iter->next) { GjsAutoUnref context(GJS_CONTEXT(iter->data)); if (!printer.init()) { all_traces << "No stack trace for context " << context.get() << ": out of memory\n\n"; break; } auto* cx = static_cast(gjs_context_get_native_context(context)); js::DumpBacktrace(cx, printer); JS::UniqueChars trace = printer.release(); all_traces << "== Stack trace for context " << context.get() << " ==\n" << trace.get() << "\n"; } out = all_traces.str(); out.resize(MAX(out.size() - 2, 0)); return out; } cjs-128.1/cjs/text-encoding.cpp0000664000175000017500000005131315116312211015257 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC // SPDX-FileCopyrightText: 2021 Evan Welsh #include #include // for SSIZE_MAX #include // for size_t #include #include // for strcmp, memchr, strlen #include #include // for nullptr_t #include // for distance #include // for unique_ptr #include // for u16string #include // for tuple #include // for move #include #include #include #include #include #include #include // for JS_ReportOutOfMemory, JSEXN_TYPEERR #include // for JS_ClearPendingException, JS_... #include // for AutoCheckCannotGC #include #include #include #include #include #include // for UniqueChars #include #include #include // for JS_NewPlainObject, JS_InstanceOf #include // for ProtoKeyToClass #include // for JSProto_InternalError #include #include #include #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/text-encoding.h" // Callback to use with JS::NewExternalArrayBuffer() static void gfree_arraybuffer_contents(void* contents, void*) { g_free(contents); } static std::nullptr_t gjs_throw_type_error_from_gerror( JSContext* cx, GjsAutoError const& error) { g_return_val_if_fail(error, nullptr); gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "%s", error->message); return nullptr; } // UTF16_CODESET is used to encode and decode UTF-16 buffers with // iconv. To ensure the output of iconv is laid out in memory correctly // we have to use UTF-16LE on little endian systems and UTF-16BE on big // endian systems. // // This ensures we can simply reinterpret_cast iconv's output. #if G_BYTE_ORDER == G_LITTLE_ENDIAN static const char* UTF16_CODESET = "UTF-16LE"; #else static const char* UTF16_CODESET = "UTF-16BE"; #endif GJS_JSAPI_RETURN_CONVENTION static JSString* gjs_lossy_decode_from_uint8array_slow( JSContext* cx, const uint8_t* bytes, size_t bytes_len, const char* from_codeset) { GjsAutoError error; GjsAutoUnref converter( g_charset_converter_new(UTF16_CODESET, from_codeset, &error)); // This should only throw if an encoding is not available. if (error) return gjs_throw_type_error_from_gerror(cx, error); // This function converts *to* UTF-16, using a std::u16string // as its buffer. // // UTF-16 represents each character with 2 bytes or // 4 bytes, the best case scenario when converting to // UTF-16 is that every input byte encodes to two bytes, // this is typical for ASCII and non-supplementary characters. // Because we are converting from an unknown encoding // technically a single byte could be supplementary in // Unicode (4 bytes) or even represent multiple Unicode characters. // // std::u16string does not care about these implementation // details, its only concern is that is consists of byte pairs. // Given this, a single UTF-16 character could be represented // by one or two std::u16string characters. // Allocate bytes_len * 2 + 12 as our initial buffer. // bytes_len * 2 is the "best case" for LATIN1 strings // and strings which are in the basic multilingual plane. // Add 12 as a slight cushion and set the minimum allocation // at 256 to prefer running a single iteration for // small strings with supplemental plane characters. // // When converting Chinese characters, for example, // some dialectal characters are in the supplemental plane // Adding a padding of 12 prevents a few dialectal characters // from requiring a reallocation. size_t buffer_size = std::max(bytes_len * 2 + 12, static_cast(256u)); // Cast data to correct input types const char* input = reinterpret_cast(bytes); size_t input_len = bytes_len; // The base string that we'll append to. std::u16string output_str = u""; do { GjsAutoError local_error; // Create a buffer to convert into. std::unique_ptr buffer = std::make_unique(buffer_size); size_t bytes_written = 0, bytes_read = 0; g_converter_convert(G_CONVERTER(converter.get()), input, input_len, buffer.get(), buffer_size, G_CONVERTER_INPUT_AT_END, &bytes_read, &bytes_written, &local_error); // If bytes were read, adjust input. if (bytes_read > 0) { input += bytes_read; input_len -= bytes_read; } // If bytes were written append the buffer contents to our string // accumulator if (bytes_written > 0) { char16_t* utf16_buffer = reinterpret_cast(buffer.get()); // std::u16string uses exactly 2 bytes for every character. output_str.append(utf16_buffer, bytes_written / 2); } else if (local_error) { // A PARTIAL_INPUT error can only occur if the user does not provide // the full sequence for a multi-byte character, we skip over the // next character and insert a unicode fallback. // An INVALID_DATA error occurs when there is no way to decode a // given byte into UTF-16 or the given byte does not exist in the // source encoding. if (g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA) || g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT)) { // If we're already at the end of the string, don't insert a // fallback. if (input_len > 0) { // Skip the next byte and reduce length by one. input += 1; input_len -= 1; // Append the unicode fallback character to the output output_str.append(u"\ufffd", 1); } } else if (g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NO_SPACE)) { // If the buffer was full increase the buffer // size and re-try the conversion. // // This logic allocates bytes_len * 3 first, // then bytes_len * 4 (the worst case scenario // is nearly impossible) and then continues appending // arbitrary padding because we'll trust Gio and give // it additional space. if (buffer_size > bytes_len * 4) { buffer_size += 256; } else { buffer_size += bytes_len; } } else { // Stop decoding if an unknown error occurs. return gjs_throw_type_error_from_gerror(cx, local_error); } } } while (input_len > 0); // Copy the accumulator's data into a JSString of Unicode (UTF-16) chars. return JS_NewUCStringCopyN(cx, output_str.c_str(), output_str.size()); } GJS_JSAPI_RETURN_CONVENTION static JSString* gjs_decode_from_uint8array_slow(JSContext* cx, const uint8_t* input, size_t input_len, const char* encoding, bool fatal) { // If the decoding is not fatal we use the lossy decoder. if (!fatal) return gjs_lossy_decode_from_uint8array_slow(cx, input, input_len, encoding); // g_convert only handles up to SSIZE_MAX bytes, but we may have SIZE_MAX if (G_UNLIKELY(input_len > SSIZE_MAX)) { gjs_throw(cx, "Array too big to decode: %zu bytes", input_len); return nullptr; } size_t bytes_written, bytes_read; GjsAutoError error; GjsAutoChar bytes = g_convert(reinterpret_cast(input), input_len, UTF16_CODESET, encoding, &bytes_read, &bytes_written, &error); if (error) return gjs_throw_type_error_from_gerror(cx, error); // bytes_written should be bytes in a UTF-16 string so should be a // multiple of 2 g_assert((bytes_written % 2) == 0); // Cast g_convert's output to char16_t and copy the data. const char16_t* unicode_bytes = reinterpret_cast(bytes.get()); return JS_NewUCStringCopyN(cx, unicode_bytes, bytes_written / 2); } [[nodiscard]] static bool is_utf8_label(const char* encoding) { // We could be smarter about utf8 synonyms here. // For now, we handle any casing and trailing/leading // whitespace. // // is_utf8_label is only an optimization, so if a label // doesn't match we just use the slower path. if (g_ascii_strcasecmp(encoding, "utf-8") == 0 || g_ascii_strcasecmp(encoding, "utf8") == 0) return true; GjsAutoChar stripped(g_strdup(encoding)); g_strstrip(stripped); // modifies in place return g_ascii_strcasecmp(stripped, "utf-8") == 0 || g_ascii_strcasecmp(stripped, "utf8") == 0; } // Finds the length of a given data array, stopping at the first 0 byte. template [[nodiscard]] static size_t zero_terminated_length(const T* data, size_t len) { if (!data || len == 0) return 0; const T* start = data; auto* found = static_cast(std::memchr(start, '\0', len)); // If a null byte was not found, return the passed length. if (!found) return len; return std::distance(start, found); } // decode() function implementation JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array, const char* encoding, GjsStringTermination string_termination, bool fatal) { g_assert(encoding && "encoding must be non-null"); if (!JS_IsUint8Array(byte_array)) { gjs_throw(cx, "Argument to decode() must be a Uint8Array"); return nullptr; } uint8_t* data; size_t len; bool is_shared_memory; js::GetUint8ArrayLengthAndData(byte_array, &len, &is_shared_memory, &data); // If the desired behavior is zero-terminated, calculate the // zero-terminated length of the given data. if (len && string_termination == GjsStringTermination::ZERO_TERMINATED) len = zero_terminated_length(data, len); // If the calculated length is 0 we can just return an empty string. if (len == 0) return JS_GetEmptyString(cx); // Optimization, only use glib's iconv-based converters if we're dealing // with a non-UTF8 encoding. SpiderMonkey has highly optimized UTF-8 decoder // and encoders. bool encoding_is_utf8 = is_utf8_label(encoding); if (!encoding_is_utf8) return gjs_decode_from_uint8array_slow(cx, data, len, encoding, fatal); JS::RootedString decoded(cx); if (!fatal) { decoded.set(gjs_lossy_string_from_utf8_n( cx, reinterpret_cast(data), len)); } else { JS::UTF8Chars chars(reinterpret_cast(data), len); JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars)); // If an exception occurred, we need to check if the // exception was an InternalError. Unfortunately, // SpiderMonkey's decoder can throw InternalError for some // invalid UTF-8 sources, we have to convert this into a // TypeError to match the Encoding specification. if (str) { decoded.set(str); } else { JS::RootedValue exc(cx); if (!JS_GetPendingException(cx, &exc) || !exc.isObject()) return nullptr; JS::RootedObject exc_obj(cx, &exc.toObject()); const JSClass* internal_error = js::ProtoKeyToClass(JSProto_InternalError); if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) { // Clear the existing exception. JS_ClearPendingException(cx); gjs_throw_custom( cx, JSEXN_TYPEERR, nullptr, "The provided encoded data was not valid UTF-8"); } return nullptr; } } uint8_t* current_data; size_t current_len; bool ignore_val; // If a garbage collection occurs between when we call // js::GetUint8ArrayLengthAndData and return from // gjs_decode_from_uint8array, a use-after-free corruption can occur if the // garbage collector shifts the location of the Uint8Array's private data. // To mitigate this we call js::GetUint8ArrayLengthAndData again and then // compare if the length and pointer are still the same. If the pointers // differ, we use the slow path to ensure no data corruption occurred. The // shared-ness of an array cannot change between calls, so we ignore it. js::GetUint8ArrayLengthAndData(byte_array, ¤t_len, &ignore_val, ¤t_data); // Ensure the private data hasn't changed if (current_data == data) return decoded; g_assert(current_len == len && "Garbage collection should not affect data length."); // This was the UTF-8 optimized path, so we explicitly pass the encoding return gjs_decode_from_uint8array_slow(cx, current_data, current_len, "utf-8", fatal); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_decode(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject byte_array(cx); JS::UniqueChars encoding; bool fatal = false; if (!gjs_parse_call_args(cx, "decode", args, "os|b", "byteArray", &byte_array, "encoding", &encoding, "fatal", &fatal)) return false; JS::RootedString decoded( cx, gjs_decode_from_uint8array(cx, byte_array, encoding.get(), GjsStringTermination::EXPLICIT_LENGTH, fatal)); if (!decoded) return false; args.rval().setString(decoded); return true; } // encode() function implementation JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str, const char* encoding, GjsStringTermination string_termination) { JS::RootedObject array_buffer(cx); bool encoding_is_utf8 = is_utf8_label(encoding); if (encoding_is_utf8) { JS::UniqueChars utf8; size_t utf8_len; if (!gjs_string_to_utf8_n(cx, str, &utf8, &utf8_len)) return nullptr; if (string_termination == GjsStringTermination::ZERO_TERMINATED) { // strlen is safe because gjs_string_to_utf8_n returns // a null-terminated string. utf8_len = strlen(utf8.get()); } array_buffer = JS::NewArrayBufferWithContents(cx, utf8_len, std::move(utf8)); } else { GjsAutoError error; GjsAutoChar encoded = nullptr; size_t bytes_written; /* Scope for AutoCheckCannotGC, will crash if a GC is triggered * while we are using the string's chars */ { JS::AutoCheckCannotGC nogc; size_t len; if (JS::StringHasLatin1Chars(str)) { const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len); if (!chars) return nullptr; encoded = g_convert(reinterpret_cast(chars), len, encoding, // to_encoding "LATIN1", // from_encoding nullptr, // bytes_read &bytes_written, &error); } else { const char16_t* chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len); if (!chars) return nullptr; encoded = g_convert(reinterpret_cast(chars), len * 2, encoding, // to_encoding "UTF-16", // from_encoding nullptr, // bytes_read &bytes_written, &error); } } if (!encoded) return gjs_throw_type_error_from_gerror(cx, error); // frees GError if (string_termination == GjsStringTermination::ZERO_TERMINATED) { bytes_written = zero_terminated_length(encoded.get(), bytes_written); } if (bytes_written == 0) return JS_NewUint8Array(cx, 0); mozilla::UniquePtr contents{ encoded.release(), gfree_arraybuffer_contents}; array_buffer = JS::NewExternalArrayBuffer(cx, bytes_written, std::move(contents)); } if (!array_buffer) return nullptr; return JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str, JS::HandleObject uint8array, JS::MutableHandleValue rval) { if (!JS_IsUint8Array(uint8array)) { gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Argument to encodeInto() must be a Uint8Array"); return false; } uint32_t len = JS_GetTypedArrayByteLength(uint8array); bool shared = JS_GetTypedArraySharedness(uint8array); if (shared) { gjs_throw(cx, "Cannot encode data into shared memory."); return false; } mozilla::Maybe> results; { JS::AutoCheckCannotGC nogc(cx); uint8_t* data = JS_GetUint8ArrayData(uint8array, &shared, nogc); // We already checked for sharedness with JS_GetTypedArraySharedness g_assert(!shared); results = JS_EncodeStringToUTF8BufferPartial( cx, str, mozilla::AsWritableChars(mozilla::Span(data, len))); } if (!results) { JS_ReportOutOfMemory(cx); return false; } size_t read, written; std::tie(read, written) = *results; g_assert(written <= len); JS::RootedObject result(cx, JS_NewPlainObject(cx)); if (!result) return false; JS::RootedValue v_read(cx, JS::NumberValue(read)), v_written(cx, JS::NumberValue(written)); if (!JS_SetProperty(cx, result, "read", v_read) || !JS_SetProperty(cx, result, "written", v_written)) return false; rval.setObject(*result); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_encode(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedString str(cx); JS::UniqueChars encoding; if (!gjs_parse_call_args(cx, "encode", args, "Ss", "string", &str, "encoding", &encoding)) return false; JS::RootedObject uint8array( cx, gjs_encode_to_uint8array(cx, str, encoding.get(), GjsStringTermination::EXPLICIT_LENGTH)); if (!uint8array) return false; args.rval().setObject(*uint8array); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_encode_into(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedString str(cx); JS::RootedObject uint8array(cx); if (!gjs_parse_call_args(cx, "encodeInto", args, "So", "string", &str, "byteArray", &uint8array)) return false; return gjs_encode_into_uint8array(cx, str, uint8array, args.rval()); } static JSFunctionSpec gjs_text_encoding_module_funcs[] = { JS_FN("decode", gjs_decode, 3, 0), JS_FN("encodeInto", gjs_encode_into, 2, 0), JS_FN("encode", gjs_encode, 2, 0), JS_FS_END}; bool gjs_define_text_encoding_stuff(JSContext* cx, JS::MutableHandleObject module) { JSObject* new_obj = JS_NewPlainObject(cx); if (!new_obj) return false; module.set(new_obj); return JS_DefineFunctions(cx, module, gjs_text_encoding_module_funcs); } cjs-128.1/cjs/text-encoding.h0000664000175000017500000000176315116312211014730 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh #pragma once #include #include #include "cjs/macros.h" enum class GjsStringTermination { ZERO_TERMINATED, EXPLICIT_LENGTH, }; GJS_JSAPI_RETURN_CONVENTION JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject uint8array, const char* encoding, GjsStringTermination string_termination, bool fatal); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str, const char* encoding, GjsStringTermination string_termination); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_text_encoding_stuff(JSContext* cx, JS::MutableHandleObject module); cjs-128.1/debian/0000775000175000017500000000000015116312211012443 5ustar fabiofabiocjs-128.1/doc/0000775000175000017500000000000015116312211011766 5ustar fabiofabiocjs-128.1/doc/ByteArray.md0000664000175000017500000000647515116312211014226 0ustar fabiofabio# ByteArray The `ByteArray` module provides a number of utilities for converting between [`GLib.Bytes`][gbytes] object, `String` values and `Uint8Array` objects. It was originally based on an ECMAScript 4 proposal that was never adopted, but now that ES6 has typed arrays, we use the standard `Uint8Array` to represent byte arrays in GJS. The primary use for most GJS users will be to exchange bytes between various C APIs, like reading from an IO stream and then pushing the bytes into a parser. Actually manipulating bytes in GJS is likely to be pretty slow and fortunately rarely necessary. An advantage of the GJS and GObject-Introspection setup is that most of the tasks best done in C, like messing with bytes, can be. [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes #### Import > Attention: This module is not available as an ECMAScript Module The `ByteArray` module is available on the global `imports` object: ```js const ByteArray = imports.byteArray; ``` ### ByteArray.fromString(string, encoding) > Deprecated: Use [`TextEncoder.encode()`][textencoder-encode] instead Type: * Static Parameters: * string (`String`) — A string to encode * encoding (`String`) — Optional encoding of `string` Returns: * (`Uint8Array`) — A byte array Convert a String into a newly constructed `Uint8Array`; this creates a new `Uint8Array` of the same length as the String, then assigns each `Uint8Array` entry the corresponding byte value of the String encoded according to the given encoding (or UTF-8 if not given). [textencoder-encode]: https://gjs-docs.gnome.org/gjs/encoding.md#textencoder-encode ### ByteArray.toString(byteArray, encoding) > Deprecated: Use [`TextDecoder.decode()`][textdecoder-decode] instead Type: * Static Parameters: * byteArray (`Uint8Array`) — A byte array to decode * encoding (`String`) — Optional encoding of `byteArray` Returns: * (`String`) — A string Converts the `Uint8Array` into a literal string. The bytes are interpreted according to the given encoding (or UTF-8 if not given). The resulting string is guaranteed to round-trip back into an identical ByteArray by passing the result to `ByteArray.fromString()`. In other words, this check is guaranteed to pass: ```js const original = ByteArray.fromString('foobar'); const copy = ByteArray.fromString(ByteArray.toString(original)); console.assert(original.every((value, index) => value === copy[index])); ``` [textdecoder-decode]: https://gjs-docs.gnome.org/gjs/encoding.md#textdecoder-decode ### ByteArray.fromGBytes(bytes) > Deprecated: Use [`GLib.Bytes.toArray()`][gbytes-toarray] instead Type: * Static Parameters: * bytes (`GLib.Bytes`) — A [`GLib.Bytes`][gbytes] to convert Returns: * (`Uint8Array`) — A new byte array Convert a [`GLib.Bytes`][gbytes] instance into a newly constructed `Uint8Array`. The contents are copied. [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes [gbytes-toarray]: https://gjs-docs.gnome.org/gjs/overrides.md#glib-bytes-toarray ### ByteArray.toGBytes(byteArray) > Deprecated: Use [`new GLib.Bytes()`][gbytes] instead Type: * Static Parameters: * byteArray (`Uint8Array`) — A byte array to convert Returns: * (`GLib.Bytes`) — A new [`GLib.Bytes`][gbytes] Converts the `Uint8Array` into a [`GLib.Bytes`][gbytes] instance. The contents are copied. [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes cjs-128.1/doc/CPP_Style_Guide.md0000664000175000017500000010612615116312211015235 0ustar fabiofabio# C++ Coding Standards ## Introduction This guide attempts to describe a few coding standards that are being used in GJS. For formatting we follow the [Google C++ Style Guide][google]. This guide won't repeat all the rules that you can read there. Instead, it covers rules that can't be checked "mechanically" with an automated style checker. It is not meant to be exhaustive. This guide is based on the [LLVM coding standards][llvm] (source code [here][llvm-source].) No coding standards should be regarded as absolute requirements to be followed in all instances, but they are important to keep a large complicated codebase readable. Many of these rules are not uniformly followed in the code base. This is because most of GJS was written before they were put in place. Our long term goal is for the entire codebase to follow the conventions, but we explicitly *do not* want patches that do large-scale reformatting of existing code. On the other hand, it is reasonable to rename the methods of a class if you're about to change it in some other way. Just do the reformatting as a separate commit from the functionality change. The ultimate goal of these guidelines is to increase the readability and maintainability of our code base. If you have suggestions for topics to be included, please open an issue at . [google]: https://google.github.io/styleguide/cppguide.html [llvm]: https://llvm.org/docs/CodingStandards.html [llvm-source]: https://raw.githubusercontent.com/llvm-mirror/llvm/HEAD/docs/CodingStandards.rst ## Languages, Libraries, and Standards Most source code in GJS using these coding standards is C++ code. There are some places where C code is used due to environment restrictions or historical reasons. Generally, our preference is for standards conforming, modern, and portable C++ code as the implementation language of choice. ### C++ Standard Versions GJS is currently written using C++17 conforming code, although we restrict ourselves to features which are available in the major toolchains. Regardless of the supported features, code is expected to (when reasonable) be standard, portable, and modern C++17 code. We avoid unnecessary vendor-specific extensions, etc., including `g_autoptr()` and friends. ### C++ Standard Library Use the C++ standard library facilities whenever they are available for a particular task. In particular, use STL containers rather than `GList*` and `GHashTable*` and friends, for their type safety and memory management. There are some exceptions such as the standard I/O streams library which is avoided, and use in space-constrained situations. ### Supported C++17 Language and Library Features While GJS and SpiderMonkey use C++17, not all features are available in all of the toolchains which we support. A good rule of thumb is to check whether SpiderMonkey uses the feature. If so, it's okay to use in GJS. ### Other Languages Any code written in JavaScript is not subject to the formatting rules below. Instead, we adopt the formatting rules enforced by the [`eslint`][eslint] tool. [eslint]: https://eslint.org/ ## Mechanical Source Issues All source code formatting should follow the [Google C++ Style Guide][google] with a few exceptions: * We use four-space indentation, to match the previous GJS coding style so that the auto-formatter doesn't make a huge mess. * Likewise we keep short return statements on separate lines instead of allowing them on single lines. Our tools (clang-format and cpplint) have the last word on acceptable formatting. It may happen that the tools are not configured correctly, or contradict each other. In that case we accept merge requests to fix that, rather than code that the tools reject. [google]: https://google.github.io/styleguide/cppguide.html ### Source Code Formatting #### Commenting Comments are one critical part of readability and maintainability. Everyone knows they should comment their code, and so should you. When writing comments, write them as English prose, which means they should use proper capitalization, punctuation, etc. Aim to describe what the code is trying to do and why, not *how* it does it at a micro level. Here are a few critical things to document: ##### File Headers Every source file should have a header on it that describes the basic purpose of the file. If a file does not have a header, it should not be checked into the tree. The standard header looks like this: ```c++ /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: YEAR NAME #include // gi/private.cpp - private "imports._gi" module with operations that we need // to use from JS in order to create GObject classes, but should not be exposed // to client code. ``` A few things to note about this particular format: The "`-*-`" string on the first line is there to tell editors that the source file is a C++ file, not a C file (since C++ and C headers both share the `.h` extension.) This is originally an Emacs convention, but other editors use it too. The next lines in the file are machine-readable SPDX comments describing the file's copyright and the license that the file is released under. These comments should follow the [REUSE specification][reuse]. This makes it perfectly clear what terms the source code can be distributed under and should not be modified. Names can be added to the copyright when making a substantial contribution to the file, not just a function or two. After the header includes comes a paragraph or two about what code the file contains. If an algorithm is being implemented or something tricky is going on, this should be explained here, as well as any notes or *gotchas* in the code to watch out for. [reuse]: https://reuse.software/ ##### Class overviews Classes are one fundamental part of a good object oriented design. As such, a class definition should have a comment block that explains what the class is used for and how it works. Every non-trivial class is expected to have such a comment block. ##### Method information Methods defined in a class (as well as any global functions) should also be documented properly. A quick note about what it does and a description of the borderline behaviour is all that is necessary here (unless something particularly tricky or insidious is going on). The hope is that people can figure out how to use your interfaces without reading the code itself. #### Comment Formatting Either C++ style comments (`//`) or C style (`/* */`) comments are acceptable. However, when documenting a method or function, use [gtk-doc style] comments which are based on C style (`/** */`). When C style comments take more than one line, put an asterisk (`*`) at the beginning of each line: ```c++ /* a list of all GClosures installed on this object (from * signals, trampolines and explicit GClosures), used when tracing */ ``` Commenting out large blocks of code is discouraged, but if you really have to do this (for documentation purposes or as a suggestion for debug printing), use `#if 0` and `#endif`. These nest properly and are better behaved in general than C style comments. [gtk-doc style]: https://developer.gnome.org/gtk-doc-manual/unstable/documenting.html.en ### Language and Compiler Issues #### Treat Compiler Warnings Like Errors If your code has compiler warnings in it, something is wrong — you aren't casting values correctly, you have questionable constructs in your code, or you are doing something legitimately wrong. Compiler warnings can cover up legitimate errors in output and make dealing with a translation unit difficult. It is not possible to prevent all warnings from all compilers, nor is it desirable. Instead, pick a standard compiler (like GCC) that provides a good thorough set of warnings, and stick to it. Currently we use GCC and the set of warnings defined by the [`ax_compiler_flags`][ax-compiler-flags] macro. In the future, we will use Meson's highest `warning_level` setting as the arbiter. [ax-compiler-flags]: https://www.gnu.org/software/autoconf-archive/ax_compiler_flags.html#ax_compiler_flags #### Write Portable Code In almost all cases, it is possible and within reason to write completely portable code. If there are cases where it isn't possible to write portable code, isolate it behind a well defined (and well documented) interface. In practice, this means that you shouldn't assume much about the host compiler (and Visual Studio tends to be the lowest common denominator). #### Use of `class` and `struct` Keywords In C++, the `class` and `struct` keywords can be used almost interchangeably. The only difference is when they are used to declare a class: `class` makes all members private by default while `struct` makes all members public by default. Unfortunately, not all compilers follow the rules and some will generate different symbols based on whether `class` or `struct` was used to declare the symbol (e.g., MSVC). This can lead to problems at link time. * All declarations and definitions of a given `class` or `struct` must use the same keyword. For example: ```c++ class Foo; // Breaks mangling in MSVC. struct Foo { int data; }; ``` * As a rule of thumb, `struct` should be kept to structures where *all* members are declared public. ```c++ // Foo feels like a class... this is strange. struct Foo { private: int m_data; public: Foo() : m_data(0) {} int getData() const { return m_data; } void setData(int d) { m_data = d; } }; // Bar isn't POD, but it does look like a struct. struct Bar { int m_data; Bar() : m_data(0) {} }; ``` #### Use `auto` Type Deduction to Make Code More Readable Some are advocating a policy of "almost always `auto`" in C++11 and later, but GJS uses a more moderate stance. Use `auto` only if it makes the code more readable or easier to maintain. Don't "almost always" use `auto`, but do use `auto` with initializers like `cast(...)` or other places where the type is already obvious from the context. Another time when `auto` works well for these purposes is when the type would have been abstracted away anyway, often behind a container's typedef such as `std::vector::iterator`. #### Beware unnecessary copies with ``auto`` The convenience of `auto` makes it easy to forget that its default behaviour is a copy. Particularly in range-based `for` loops, careless copies are expensive. As a rule of thumb, use `auto&` unless you need to copy the result, and use `auto*` when copying pointers. ```c++ // Typically there's no reason to copy. for (const auto& val : container) observe(val); for (auto& val : container) val.change(); // Remove the reference if you really want a new copy. for (auto val : container) { val.change(); save_somewhere(val); } // Copy pointers, but make it clear that they're pointers. for (const auto* ptr : container) observe(*ptr); for (auto* ptr : container) ptr->change(); ``` #### Beware of non-determinism due to ordering of pointers In general, there is no relative ordering among pointers. As a result, when unordered containers like sets and maps are used with pointer keys the iteration order is undefined. Hence, iterating such containers may result in non-deterministic code generation. While the generated code might not necessarily be "wrong code", this non-determinism might result in unexpected runtime crashes or simply hard to reproduce bugs on the customer side making it harder to debug and fix. As a rule of thumb, in case an ordered result is expected, remember to sort an unordered container before iteration. Or use ordered containers like `std::vector` if you want to iterate pointer keys. #### Beware of non-deterministic sorting order of equal elements `std::sort` uses a non-stable sorting algorithm in which the order of equal elements is not guaranteed to be preserved. Thus using `std::sort` for a container having equal elements may result in non-determinstic behaviour. ## Style Issues ### The High-Level Issues #### Self-contained Headers Header files should be self-contained (compile on their own) and end in `.h`. Non-header files that are meant for inclusion should end in `.inc` and be used sparingly. All header files should be self-contained. Users and refactoring tools should not have to adhere to special conditions to include the header. Specifically, a header should have header guards and include all other headers it needs. There are rare cases where a file designed to be included is not self-contained. These are typically intended to be included at unusual locations, such as the middle of another file. They might not use header guards, and might not include their prerequisites. Name such files with the `.inc` extension. Use sparingly, and prefer self-contained headers when possible. #### `#include` as Little as Possible `#include` hurts compile time performance. Don't do it unless you have to, especially in header files. But wait! Sometimes you need to have the definition of a class to use it, or to inherit from it. In these cases go ahead and `#include` that header file. Be aware however that there are many cases where you don't need to have the full definition of a class. If you are using a pointer or reference to a class, you don't need the header file. If you are simply returning a class instance from a prototyped function or method, you don't need it. In fact, for most cases, you simply don't need the definition of a class. And not `#include`ing speeds up compilation. It is easy to try to go too overboard on this recommendation, however. You **must** include all of the header files that you are using — you can include them either directly or indirectly through another header file. To make sure that you don't accidentally forget to include a header file in your module header, make sure to include your module header **first** in the implementation file (as mentioned above). This way there won't be any hidden dependencies that you'll find out about later. The tool [IWYU][iwyu] can help with this, but it generates a lot of false positives, so we don't automate it. In many cases, header files with SpiderMonkey types will only need to include one SpiderMonkey header, ``, unless they have inline functions or SpiderMonkey member types. This header file contains a number of forward declarations and nothing else. [iwyu]: https://include-what-you-use.org/ #### Header inclusion order Headers should be included in the following order: - `` - C system headers - C++ system headers - GNOME library headers - SpiderMonkey library headers - GJS headers Each of these groups must be separated by blank lines. Within each group, all the headers should be alphabetized. The first five groups should use angle brackets for the includes. Note that the header `` must be included before any SpiderMonkey headers. GJS headers should use quotes, _except_ in public header files (any header file included from ``.) If you need to include headers conditionally, add the conditional after the group that it belongs to, separated by a blank line. If it is not obvious, you may add a comment after the include, explaining what this header is included for. This makes it easier to figure out whether to remove a header later if its functionality is no longer used in the file. Here is an example of all of the above rules together: ```c++ #include // for ENABLE_PROFILER #include // for strlen #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include #endif #include #include #include #include // for GCHashMap #include // for JS_New, JSAutoRealm, JS_GetProperty #include #include "gjs/atoms.h" #include "gjs/context-private.h" #include "gjs/jsapi-util.h" ``` #### Keep "Internal" Headers Private Many modules have a complex implementation that causes them to use more than one implementation (`.cpp`) file. It is often tempting to put the internal communication interface (helper classes, extra functions, etc.) in the public module header file. Don't do this! If you really need to do something like this, put a private header file in the same directory as the source files, and include it locally. This ensures that your private interface remains private and undisturbed by outsiders. It's okay to put extra implementation methods in a public class itself. Just make them private (or protected) and all is well. #### Use Early Exits and `continue` to Simplify Code When reading code, keep in mind how much state and how many previous decisions have to be remembered by the reader to understand a block of code. Aim to reduce indentation where possible when it doesn't make it more difficult to understand the code. One great way to do this is by making use of early exits and the `continue` keyword in long loops. As an example of using an early exit from a function, consider this "bad" code: ```c++ Value* do_something(Instruction* in) { if (!is_a(in) && in->has_one_use() && do_other_thing(in)) { ... some long code.... } return nullptr; } ``` This code has several problems if the body of the `if` is large. When you're looking at the top of the function, it isn't immediately clear that this *only* does interesting things with non-terminator instructions, and only applies to things with the other predicates. Second, it is relatively difficult to describe (in comments) why these predicates are important because the `if` statement makes it difficult to lay out the comments. Third, when you're deep within the body of the code, it is indented an extra level. Finally, when reading the top of the function, it isn't clear what the result is if the predicate isn't true; you have to read to the end of the function to know that it returns null. It is much preferred to format the code like this: ```c++ Value* do_something(Instruction* in) { // Terminators never need 'something' done to them because ... if (is_a(in)) return nullptr; // We conservatively avoid transforming instructions with multiple uses // because goats like cheese. if (!in->has_one_use()) return nullptr; // This is really just here for example. if (!do_other_thing(in)) return nullptr; ... some long code.... } ``` This fixes these problems. A similar problem frequently happens in `for` loops. A silly example is something like this: ```c++ for (Instruction& in : bb) { if (auto* bo = dyn_cast(&in)) { Value* lhs = bo->get_operand(0); Value* rhs = bo->get_operand(1); if (lhs != rhs) { ... } } } ``` When you have very small loops, this sort of structure is fine. But if it exceeds more than 10-15 lines, it becomes difficult for people to read and understand at a glance. The problem with this sort of code is that it gets very nested very quickly, meaning that the reader of the code has to keep a lot of context in their brain to remember what is going immediately on in the loop, because they don't know if/when the `if` conditions will have `else`s etc. It is strongly preferred to structure the loop like this: ```c++ for (Instruction& in : bb) { auto* bo = dyn_cast(&in); if (!bo) continue; Value* lhs = bo->get_operand(0); Value* rhs = bo->get_operand(1); if (lhs == rhs) continue; ... } ``` This has all the benefits of using early exits for functions: it reduces nesting of the loop, it makes it easier to describe why the conditions are true, and it makes it obvious to the reader that there is no `else` coming up that they have to push context into their brain for. If a loop is large, this can be a big understandability win. #### Don't use `else` after a `return` For similar reasons above (reduction of indentation and easier reading), please do not use `else` or `else if` after something that interrupts control flow — like `return`, `break`, `continue`, `goto`, etc. For example, this is *bad*: ```c++ case 'J': { if (is_signed) { type = cx.getsigjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_sigjmp_buf; return QualType(); } else { break; } } else { type = cx.getjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_jmp_buf; return QualType(); } else { break; } } } ``` It is better to write it like this: ```c++ case 'J': if (is_signed) { type = cx.getsigjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_sigjmp_buf; return QualType(); } } else { type = cx.getjmp_buf_type(); if (type.is_null()) { error = ASTContext::ge_missing_jmp_buf; return QualType(); } } break; ``` Or better yet (in this case) as: ```c++ case 'J': if (is_signed) type = cx.getsigjmp_buf_type(); else type = cx.getjmp_buf_type(); if (type.is_null()) { error = is_signed ? ASTContext::ge_missing_sigjmp_buf : ASTContext::ge_missing_jmp_buf; return QualType(); } break; ``` The idea is to reduce indentation and the amount of code you have to keep track of when reading the code. #### Turn Predicate Loops into Predicate Functions It is very common to write small loops that just compute a boolean value. There are a number of ways that people commonly write these, but an example of this sort of thing is: ```c++ bool found_foo = false; for (unsigned ix = 0, len = bar_list.size(); ix != len; ++ix) if (bar_list[ix]->is_foo()) { found_foo = true; break; } if (found_foo) { ... } ``` This sort of code is awkward to write, and is almost always a bad sign. Instead of this sort of loop, we strongly prefer to use a predicate function (which may be `static`) that uses early exits to compute the predicate. We prefer the code to be structured like this: ```c++ /* Helper function: returns true if the specified list has an element that is * a foo. */ static bool contains_foo(const std::vector &list) { for (unsigned ix = 0, len = list.size(); ix != len; ++ix) if (list[ix]->is_foo()) return true; return false; } ... if (contains_foo(bar_list)) { ... } ``` There are many reasons for doing this: it reduces indentation and factors out code which can often be shared by other code that checks for the same predicate. More importantly, it *forces you to pick a name* for the function, and forces you to write a comment for it. In this silly example, this doesn't add much value. However, if the condition is complex, this can make it a lot easier for the reader to understand the code that queries for this predicate. Instead of being faced with the in-line details of how we check to see if the `bar_list` contains a foo, we can trust the function name and continue reading with better locality. ### The Low-Level Issues #### Name Types, Functions, Variables, and Enumerators Properly Poorly-chosen names can mislead the reader and cause bugs. We cannot stress enough how important it is to use *descriptive* names. Pick names that match the semantics and role of the underlying entities, within reason. Avoid abbreviations unless they are well known. After picking a good name, make sure to use consistent capitalization for the name, as inconsistency requires clients to either memorize the APIs or to look it up to find the exact spelling. Different kinds of declarations have different rules: * **Type names** (including classes, structs, enums, typedefs, etc.) should be nouns and should be named in camel case, starting with an upper-case letter (e.g. `ObjectInstance`). * **Variable names** should be nouns (as they represent state). The name should be snake case (e.g. `count` or `new_param`). Private member variables should start with `m_` to distinguish them from local variables representing the same thing. * **Function names** should be verb phrases (as they represent actions), and command-like function should be imperative. The name should be snake case (e.g. `open_file()` or `is_foo()`). * **Enum declarations** (e.g. `enum Foo {...}`) are types, so they should follow the naming conventions for types. A common use for enums is as a discriminator for a union, or an indicator of a subclass. When an enum is used for something like this, it should have a `Kind` suffix (e.g. `ValueKind`). * **Enumerators** (e.g. `enum { Foo, Bar }`) and **public member variables** should start with an upper-case letter, just like types. Unless the enumerators are defined in their own small namespace or inside a class, enumerators should have a prefix corresponding to the enum declaration name. For example, `enum ValueKind { ... };` may contain enumerators like `VK_Argument`, `VK_BasicBlock`, etc. Enumerators that are just convenience constants are exempt from the requirement for a prefix. For instance: ```c++ enum { MaxSize = 42, Density = 12 }; ``` Here are some examples of good and bad names: ```c++ class VehicleMaker { ... Factory m_f; // Bad -- abbreviation and non-descriptive. Factory m_factory; // Better. Factory m_tire_factory; // Even better -- if VehicleMaker has more // than one kind of factories. }; Vehicle make_vehicle(VehicleType Type) { VehicleMaker m; // Might be OK if having a short life-span. Tire tmp1 = m.make_tire(); // Bad -- 'Tmp1' provides no information. Light headlight = m.make_light("head"); // Good -- descriptive. ... } ``` #### Assert Liberally Use the `g_assert()` macro to its fullest. Check all of your preconditions and assumptions, you never know when a bug (not necessarily even yours) might be caught early by an assertion, which reduces debugging time dramatically. To further assist with debugging, usually you should put some kind of error message in the assertion statement, which is printed if the assertion is tripped. This helps the poor debugger make sense of why an assertion is being made and enforced, and hopefully what to do about it. Here is one complete example: ```c++ inline Value* get_operand(unsigned ix) { g_assert(ix < operands.size() && "get_operand() out of range!"); return operands[ix]; } ``` To indicate a piece of code that should not be reached, use `g_assert_not_reached()`. When assertions are enabled, this will print the message if it's ever reached and then exit the program. When assertions are disabled (i.e. in release builds), `g_assert_not_reached()` becomes a hint to compilers to skip generating code for this branch. If the compiler does not support this, it will fall back to the `abort()` implementation. Neither assertions or `g_assert_not_reached()` will abort the program on a release build. If the error condition can be triggered by user input then the recoverable error mechanism of `GError*` should be used instead. In cases where this is not practical, either use `g_critical()` and continue execution as best as possible, or use `g_error()` to abort with a fatal error. For this reason, don't use `g_assert()` or `g_assert_not_reached()` in unit tests! Otherwise the tests will crash in a release build. In unit tests, use `g_assert_true()`, `g_assert_false()`, `g_assert_cmpint()`, etc. Likewise, don't use these unit test assertions in the main code! Another issue is that values used only by assertions will produce an "unused value" warning when assertions are disabled. For example, this code will warn: ```c++ unsigned size = v.size(); g_assert(size > 42 && "Vector smaller than it should be"); bool new_to_set = my_set.insert(value); g_assert(new_to_set && "The value shouldn't be in the set yet"); ``` These are two interesting different cases. In the first case, the call to `v.size()` is only useful for the assert, and we don't want it executed when assertions are disabled. Code like this should move the call into the assert itself. In the second case, the side effects of the call must happen whether the assert is enabled or not. In this case, the value should be cast to void to disable the warning. To be specific, it is preferred to write the code like this: ```c++ g_assert(v.size() > 42 && "Vector smaller than it should be"); bool new_to_set = my_set.insert(value); (void)new_to_set; g_assert(new_to_set && "The value shouldn't be in the set yet"); ``` #### Do Not Use `using namespace std` In GJS, we prefer to explicitly prefix all identifiers from the standard namespace with an `std::` prefix, rather than rely on `using namespace std;`. In header files, adding a `using namespace XXX` directive pollutes the namespace of any source file that `#include`s the header. This is clearly a bad thing. In implementation files (e.g. `.cpp` files), the rule is more of a stylistic rule, but is still important. Basically, using explicit namespace prefixes makes the code **clearer**, because it is immediately obvious what facilities are being used and where they are coming from. And **more portable**, because namespace clashes cannot occur between LLVM code and other namespaces. The portability rule is important because different standard library implementations expose different symbols (potentially ones they shouldn't), and future revisions to the C++ standard will add more symbols to the `std` namespace. As such, we never use `using namespace std;` in GJS. The exception to the general rule (i.e. it's not an exception for the `std` namespace) is for implementation files. For example, in the future we might decide to put GJS code inside a `Gjs` namespace. In that case, it is OK, and actually clearer, for the `.cpp` files to have a `using namespace Gjs;` directive at the top, after the `#include`s. This reduces indentation in the body of the file for source editors that indent based on braces, and keeps the conceptual context cleaner. The general form of this rule is that any `.cpp` file that implements code in any namespace may use that namespace (and its parents'), but should not use any others. #### Provide a Virtual Method Anchor for Classes in Headers If a class is defined in a header file and has a vtable (either it has virtual methods or it derives from classes with virtual methods), it must always have at least one out-of-line virtual method in the class. Without this, the compiler will copy the vtable and RTTI into every `.o` file that `#include`s the header, bloating `.o` file sizes and increasing link times. #### Don't use default labels in fully covered switches over enumerations `-Wswitch` warns if a switch, without a default label, over an enumeration, does not cover every enumeration value. If you write a default label on a fully covered switch over an enumeration then the `-Wswitch` warning won't fire when new elements are added to that enumeration. To help avoid adding these kinds of defaults, Clang has the warning `-Wcovered-switch-default`. A knock-on effect of this stylistic requirement is that when building GJS with GCC you may get warnings related to "control may reach end of non-void function" if you return from each case of a covered switch-over-enum because GCC assumes that the enum expression may take any representable value, not just those of individual enumerators. To suppress this warning, use `g_assert_not_reached()` after the switch. #### Use range-based `for` loops wherever possible The introduction of range-based `for` loops in C++11 means that explicit manipulation of iterators is rarely necessary. We use range-based `for` loops wherever possible for all newly added code. For example: ```c++ for (GClosure* closure : m_closures) ... use closure ...; ``` #### Don't evaluate `end()` every time through a loop In cases where range-based `for` loops can't be used and it is necessary to write an explicit iterator-based loop, pay close attention to whether `end()` is re-evaluted on each loop iteration. One common mistake is to write a loop in this style: ```c++ for (auto* closure = m_closures->begin(); closure != m_closures->end(); ++closure) ... use closure ... ``` The problem with this construct is that it evaluates `m_closures->end()` every time through the loop. Instead of writing the loop like this, we strongly prefer loops to be written so that they evaluate it once before the loop starts. A convenient way to do this is like so: ```c++ for (auto* closure = m_closures->begin(), end = m_closures->end(); closure != end; ++closure) ... use closure ... ``` The observant may quickly point out that these two loops may have different semantics: if the container is being mutated, then `m_closures->end()` may change its value every time through the loop and the second loop may not in fact be correct. If you actually do depend on this behavior, please write the loop in the first form and add a comment indicating that you did it intentionally. Why do we prefer the second form (when correct)? Writing the loop in the first form has two problems. First it may be less efficient than evaluating it at the start of the loop. In this case, the cost is probably minor — a few extra loads every time through the loop. However, if the base expression is more complex, then the cost can rise quickly. If the end expression was actually something like `some_map[x]->end()`, map lookups really aren't cheap. By writing it in the second form consistently, you eliminate the issue entirely and don't even have to think about it. The second (even bigger) issue is that writing the loop in the first form hints to the reader that the loop is mutating the container (which a comment would handily confirm!) If you write the loop in the second form, it is immediately obvious without even looking at the body of the loop that the container isn't being modified, which makes it easier to read the code and understand what it does. While the second form of the loop is a few extra keystrokes, we do strongly prefer it. #### Avoid `std::endl` The `std::endl` modifier, when used with `iostreams`, outputs a newline to the output stream specified. In addition to doing this, however, it also flushes the output stream. In other words, these are equivalent: ```c++ std::cout << std::endl; std::cout << '\n' << std::flush; ``` Most of the time, you probably have no reason to flush the output stream, so it's better to use a literal `'\n'`. #### Don't use `inline` when defining a function in a class definition A member function defined in a class definition is implicitly inline, so don't put the `inline` keyword in this case. Don't: ```c++ class Foo { public: inline void bar() { // ... } }; ``` Do: ```c++ class Foo { public: void bar() { // ... } }; ``` #### Don't use C++ standard library UTF-8/UTF-16 encoding facilities There are [bugs](https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error?forum=vcgeneral) in Visual Studio that make `wstring_convert` non-portable. Instead, use `g_utf8_to_utf16()` and friends (unfortunately not typesafe) or `mozilla::ConvertUtf8toUtf16()` and friends (when that becomes possible; it is currently not possible due to a linker bug.) cjs-128.1/doc/Console.md0000664000175000017500000001554415116312211013723 0ustar fabiofabio# Console GJS implements the [WHATWG Console][whatwg-console] specification, with some changes to accommodate GLib. In particular, log severity is mapped to [`GLib.LogLevelFlags`][gloglevelflags] and some methods are not implemented: * `console.profile()` * `console.profileEnd()` * `console.timeStamp()` #### Import The functions in this module are available globally, without import. [whatwg-console]: https://console.spec.whatwg.org/ [gloglevelflags]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags ### console.assert(condition, ...data) Type: * Static Parameters: * condition (`Boolean`) — A boolean condition which, if `false`, causes the log to print * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a critical message if the condition is not truthy. See [`console.error()`](#console-error) for additional information. ### console.clear() Type: * Static > New in GJS 1.70 (GNOME 41) Resets grouping and clears the terminal on systems supporting ANSI terminal control sequences. In file-based stdout or systems which do not support clearing, `console.clear()` has no visual effect. ### console.count(label) Type: * Static Parameters: * label (`String`) — Optional label > New in GJS 1.70 (GNOME 41) Logs how many times `console.count()` has been called with the given `label`. See [`console.countReset()`](#console-countreset) for resetting a count. ### console.countReset(label) Type: * Static Parameters: * label (`String`) — The unique label to reset the count for > New in GJS 1.70 (GNOME 41) Resets a counter used with `console.count()`. ### console.debug(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_DEBUG`][gloglevelflagsdebug]. [gloglevelflagsdebug]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_debug ### console.dir(item, options) Type: * Static Parameters: * item (`Object`) — The item to display * options (`undefined`) — Additional options for the formatter. Unused in GJS. > New in GJS 1.70 (GNOME 41) Resurively display all properties of `item`. ### console.dirxml(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Alias for [`console.log()`](#console-log) ### console.error(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_CRITICAL`][gloglevelflagscritical]. Does not use [`GLib.LogLevelFlags.LEVEL_ERROR`][gloglevelflagserror] to avoid asserting and forcibly shutting down the application. [gloglevelflagscritical]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_critical [gloglevelflagserror]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_error ### console.group(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Creates a new inline group in the console log, causing any subsequent console messages to be indented by an additional level, until `console.groupEnd()` is called. ### console.groupCollapsed(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Alias for [`console.group()`](#console-group) ### console.groupEnd() Type: * Static > New in GJS 1.70 (GNOME 41) Exits the current inline group in the console log. ### console.info(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_INFO`][gloglevelflagsinfo]. [gloglevelflagsinfo]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_info ### console.log(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_MESSAGE`][gloglevelflagsmessage]. [gloglevelflagsmessage]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_message ### console.table(tabularData, options) > Note: This is an alias for [`console.log()`](#console-log) in GJS Type: * Static Parameters: * tabularData (`Any`) — Formatting substitutions, if applicable * properties (`undefined`) — Unsupported in GJS > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_MESSAGE`][gloglevelflagsmessage]. [gloglevelflagsmessage]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_message ### console.time(label) Type: * Static Parameters: * label (`String`) — unique identifier for this action, pass to `console.timeEnd()` to complete > New in GJS 1.70 (GNOME 41) Starts a timer you can use to track how long an operation takes. ### console.timeEnd(label) Type: * Static Parameters: * label (`String`) — unique identifier for this action > New in GJS 1.70 (GNOME 41) Logs the time since the last call to `console.time(label)` and completes the action. Call `console.time(label)` again to re-measure. ### console.timeLog(label, ...data) Type: * Static Parameters: * label (`String`) — unique identifier for this action, pass to `console.timeEnd()` to complete * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs the time since the last call to `console.time(label)` where `label` is the same. ### console.trace(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Outputs a stack trace to the console. ### console.warn(...data) Type: * Static Parameters: * data (`Any`) — Formatting substitutions, if applicable > New in GJS 1.70 (GNOME 41) Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_WARNING`][gloglevelflagswarning]. [gloglevelflagswarning]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_warning ## Log Domain > New in GJS 1.70 (GNOME 41) The log domain for the default global `console` object is set to `"Gjs-Console"` by default, but can be changed if necessary. The three symbols of interest are `setConsoleLogDomain()`, `getConsoleLogDomain()` and `DEFAULT_LOG_DOMAIN`. You can import these symbols and modify the log domain like so: ```js import { setConsoleLogDomain, getConsoleLogDomain, DEFAULT_LOG_DOMAIN } from 'console'; // Setting the log domain setConsoleLogDomain('my.app.id'); // expected output: my.app.id-Message: 12:21:17.899: cool console.log('cool'); // Checking and resetting the log domain if (getConsoleLogDomain() !== DEFAULT_LOG_DOMAIN) setConsoleLogDomain(DEFAULT_LOG_DOMAIN); // expected output: Gjs-Console-Message: 12:21:17.899: cool console.log('cool'); ``` cjs-128.1/doc/Custom-GSources.md0000664000175000017500000000276715116312211015326 0ustar fabiofabio## Custom GSources GLib allows custom GSources to be added to the main loop. A custom GSource can control under what conditions it is dispatched. You can read more about GLib's main loop [here][glib-mainloop-docs]. Within GJS, we have implemented a custom GSource to handle Promise execution. It dispatches whenever a Promise is queued, occurring before any other GLib events. This mimics the behavior of a [microtask queue](mdn-microtasks) in other JavaScript environments. You can read an introduction to building custom GSources within the archived developer documentation [here][custom-gsource-tutorial] or, if unavailable, via [the original source code][custom-gsource-tutorial-source]. Another great resource is Philip Withnall's ["A detailed look at GSource"][gsource-blog-post][[permalink]][gsource-blog-post-archive]. [gsource-blog-post]: https://tecnocode.co.uk/2015/05/05/a-detailed-look-at-gsource/ [gsource-blog-post-archive]: https://web.archive.org/web/20201013000618/https://tecnocode.co.uk/2015/05/05/a-detailed-look-at-gsource/ [mdn-microtasks]: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide [glib-mainloop-docs]: https://docs.gtk.org/glib/main-loop.html#creating-new-source-types [custom-gsource-tutorial]: https://developer-old.gnome.org/gnome-devel-demos/unstable/custom-gsource.c.html.en [custom-gsource-tutorial-source]: https://gitlab.gnome.org/Archive/gnome-devel-docs/-/blob/703816cec292293fd337b6db8520b9b0afa7b3c9/platform-demos/C/custom-gsource.c.page cjs-128.1/doc/ESModules.md0000664000175000017500000001635515116312211014162 0ustar fabiofabio# ECMAScript Modules > _This documentation is inspired by [Node.js' documentation](https://github.com/nodejs/node/blob/HEAD/doc/api/esm.md) > on ECMAScript modules._ ECMAScript Modules or "ES modules" are the [official ECMAScript standard][] for importing, exporting, and reusing JavaScript code. ES modules can export `function`, `class`, `const`, `let`, and `var` statements using the `export` keyword. ```js // animalSounds.js export function bark(num) { log('bark'); } export const ANIMALS = ['dog', 'cat']; ``` Other ES modules can then import those declarations using `import` statements like the one below. ```js // main.js import { ANIMALS, bark } from './animalSounds.js'; // Logs 'bark' bark(); // Logs 'dog, cat' log(ANIMALS); ``` ## Loading ES Modules ### Command Line From the command line ES modules can be loaded with the `-m` flag: ```sh gjs -m module.js ``` ### JavaScript ES modules cannot be loaded from strings at this time. Besides the import expression syntax described above, Dynamic [`import()` statements][] can be used to load modules from any GJS script or module. ```js import('./animalSounds.js').then((module) => { // module.default is the default export // named exports are accessible as properties // module.bark }).catch(logError) ``` Because `import()` is asynchronous, you will need a mainloop running. ### C API Using the C API in `gjs.h`, ES modules can be loaded from a file or resource using `gjs_load_module_file()`. ### Shebang `example.js` ```js #!/usr/bin/env -S gjs -m import GLib from 'gi://GLib'; log(GLib); ``` ```sh chmod +x example.js ./example.js ``` ## `import` Specifiers ### Terminology The _specifier_ of an `import` statement is the string after the `from` keyword, e.g. `'path'` in `import { sep } from 'path'`. Specifiers are also used in `export from` statements, and as the argument to an `import()` expression. There are three types of specifiers: * _Relative specifiers_ like `'./window.js'`. They refer to a path relative to the location of the importing file. _The file extension is always necessary for these._ * _Bare specifiers_ like `'some-package'`. In GJS bare specifiers typically refer to built-in modules like `gi`. * _Absolute specifiers_ like `'file:///usr/share/gjs-app/file.js'`. They refer directly and explicitly to a full path or library. Bare specifier resolutions import built-in modules. All other specifier resolutions are always only resolved with the standard relative URL resolution semantics. ### Mandatory file extensions A file extension must be provided when using the `import` keyword to resolve relative or absolute specifiers. Directory files (e.g. `'./extensions/__init__.js'`) must also be fully specified. The recommended replacement for directory files (`__init__.js`) is: ```js './extensions.js' './extensions/a.js' './extensions/b.js' ``` Because file extensions are required, folders and `.js` files with the same "name" should not conflict as they did with `imports`. ### URLs ES modules are resolved and cached as URLs. This means that files containing special characters such as `#` and `?` need to be escaped. `file:`, `resource:`, and `gi:` URL schemes are supported. A specifier like `'https://example.com/app.js'` is not supported in GJS. #### `file:` URLs Modules are loaded multiple times if the `import` specifier used to resolve them has a different query or fragment. ```js import './foo.js?query=1'; // loads ./foo.js with query of "?query=1" import './foo.js?query=2'; // loads ./foo.js with query of "?query=2" ``` The root directory may be referenced via `file:///`. #### `gi:` Imports `gi:` URLs are supported as an alternative means to load GI (GObject Introspected) modules. `gi:` URLs support declaring libraries' versions. An error will be thrown when resolving imports if multiple versions of a library are present and a version has not been specified. The version is cached, so it only needs to be specified once. ```js import Gtk from 'gi://Gtk?version=4.0'; import Gdk from 'gi://Gdk?version=4.0'; import GLib from 'gi://GLib'; // GLib, GObject, and Gio are required by GJS so no version is necessary. ``` It is recommended to create a "version block" at your application's entry point. ```js import 'gi://Gtk?version=3.0' import 'gi://Gdk?version=3.0' import 'gi://Hdy?version=1.0' ``` After these declarations, you can import the libraries without version parameters. ```js import Gtk from 'gi://Gtk'; import Gdk from 'gi://Gdk'; import Hdy from 'gi://Hdy'; ``` ## Built-in modules Built-in modules provide a default export with all their exported functions and properties. Most modules provide named exports too. `cairo` does not provide named exports of its API. Modifying the values of the default export _does not_ change the values of named exports. ```js import system from 'system'; system.exit(1); ``` ```js import { ngettext as _ } from 'gettext'; _('Hello!'); ``` ## `import.meta` * {Object} The `import.meta` meta property is an `Object` that contains the following properties: ### `import.meta.url` * {string} The absolute `file:` or `resource:` URL of the module. This is identical to Node.js and browser environments. It will always provide the URI of the current module. This enables useful patterns such as relative file loading: ```js import Gio from 'gi://Gio'; const file = Gio.File.new_for_uri(import.meta.url); const data = file.get_parent().resolve_relative_path('data.json'); const [, contents] = data.load_contents(null); ``` or if you want the path for the current file or directory ```js import GLib from 'gi://GLib'; const [filename] = GLib.filename_from_uri(import.meta.url); const dirname = GLib.path_get_dirname(filename); ``` ## Interoperability with legacy `imports` modules Because `imports` is a global object, it is still available in ES modules. It is not recommended to purposely mix import styles unless absolutely necessary. ### `import` statements An `import` statement can only reference an ES module. `import` statements are permitted only in ES modules, but dynamic [`import()`][] expressions is supported in legacy `imports` modules for loading ES modules. When importing legacy `imports` modules, all `var` declarations are provided as properties on the default export. ### Differences between ES modules and legacy `imports` modules #### No `imports` and `var` exports You must use the [`export`][] syntax instead. #### No meta path properties These `imports` properties are not available in ES modules: * `__modulePath__` * `__moduleName__` * `__parentModule__` `__modulePath__`, `__moduleName__` and `__parentModule__` use cases can be replaced with [`import.meta.url`][]. [`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export [`import()`]: #esm_import_expressions [`import()` statements]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports [`import.meta.url`]: #esm_import_meta_url [`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import [`string`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String [special scheme]: https://url.spec.whatwg.org/#special-scheme [official ECMAScript standard]: https://tc39.github.io/ecma262/#sec-modules cjs-128.1/doc/Encoding.md0000664000175000017500000001063715116312211014045 0ustar fabiofabio# Encoding GJS implements the [WHATWG Encoding][whatwg-encoding] specification. The `TextDecoder` interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc. A decoder takes a list of bytes as input and emits a list of code points. The `TextEncoder` interface takes a list of code points as input and emits a list of UTF-8 bytes. #### Import The functions in this module are available globally, without import. [whatwg-encoding]: https://encoding.spec.whatwg.org/ ### TextDecoder(utfLabel, options) Type: * Static Parameters: * utfLabel (`Number`) — Optional string, defaulting to `"utf-8"`, containing the label of the encoder. * options (`Object`) — Optional dictionary with the `Boolean` property `fatal`, corresponding to the `TextDecoder.fatal` property. Returns: * (`TextDecoder`) — A newly created `TextDecoder` object > New in GJS 1.70 (GNOME 41) The `TextDecoder()` constructor returns a newly created `TextDecoder` object for the encoding specified in parameter. If the value for `utfLabel` is unknown, or is one of the two values leading to a 'replacement' decoding algorithm ("iso-2022-cn" or "iso-2022-cn-ext"), a `RangeError` is thrown. ### TextDecoder.encoding Type: * `String` > New in GJS 1.70 (GNOME 41) The `TextDecoder.encoding` read-only property returns a string containing the name of the decoding algorithm used by the specific decoder. ### TextDecoder.fatal Type: * `Boolean` > New in GJS 1.70 (GNOME 41) The fatal property of the `TextDecoder` interface is a `Boolean` indicating whether the error mode is fatal. If this value is `true`, the processed text cannot be decoded because of malformed data. If this value is `false` malformed data is replaced with placeholder characters. ### TextDecoder.ignoreBOM Type: * `Boolean` > New in GJS 1.70 (GNOME 41) The `ignoreBOM` property of the `TextDecoder` interface is a `Boolean` indicating whether the byte order mark is ignored. ### TextDecoder.decode(buffer, options) Parameters: * buffer (`Number`) — Optional `ArrayBuffer`, a `TypedArray` or a `DataView` object containing the text to decode. * options (`Object`) — Optional dictionary with the `Boolean` property `fatal`, indicating that additional data will follow in subsequent calls to `decode()`. Set to `true` if processing the data in chunks, and `false` for the final chunk or if the data is not chunked. It defaults to `false`. Returns: * (`String`) — A string result > New in GJS 1.70 (GNOME 41) The `TextDecode.decode()` method returns a string containing the text, given in parameters, decoded with the specific method for that `TextDecoder` object. ### TextEncoder() Type: * Static > New in GJS 1.70 (GNOME 41) The `TextEncoder()` constructor returns a newly created `TextEncoder` object that will generate a byte stream with UTF-8 encoding. ### TextEncoder.encoding Type: * `String` > New in GJS 1.70 (GNOME 41) The `TextEncoder.encoding` read-only property returns a string containing the name of the encoding algorithm used by the specific encoder. It can only have the following value `utf-8`. ### TextEncoder.encode(string) Parameters: * string (`String`) — A string containing the text to encode Returns: * (`Uint8Array`) — A `Uint8Array` object containing UTF-8 encoded text > New in GJS 1.70 (GNOME 41) The `TextEncoder.encode()` method takes a string as input, and returns a `Uint8Array` containing the text given in parameters encoded with the specific method for that `TextEncoder` object. ### TextEncoder.encodeInto(input, output) Parameters: * input (`String`) — A string containing the text to encode * output (`Uint8Array`) — A `Uint8Array` object instance to place the resulting UTF-8 encoded text into. Returns: * (`{String: Number}`) — An object containing the number of UTF-16 units read and bytes written > New in GJS 1.70 (GNOME 41) The `TextEncoder.encode()` method takes a string as input, and returns a `Uint8Array` containing the text given in parameters encoded with the specific method for that `TextEncoder` object. The returned object contains two members: * `read` The number of UTF-16 units of code from the source that has been converted over to UTF-8. This may be less than `string.length` if `uint8Array` did not have enough space. * `written` The number of bytes modified in the destination `Uint8Array`. The bytes written are guaranteed to form complete UTF-8 byte sequences. cjs-128.1/doc/Environment.md0000664000175000017500000000530015116312211014612 0ustar fabiofabio# Environment GJS allows runtime configuration with a number of environment variables. ## General * `GJS_PATH` Set this variable to a list of colon-separated (`:`) paths (just like `PATH`), to add them to the search path for the importer. Use of the `--include-path` command-line option is preferred over this variable. * `GJS_ABORT_ON_OOM` > NOTE: This feature is not well tested. Setting this variable to any value causes GJS to exit when an out-of-memory condition is encountered, instead of just printing a warning. ## JavaScript Engine * `JS_GC_ZEAL` Enable GC zeal, a testing and debugging feature that helps find GC-related bugs in JSAPI applications. See the [Hacking][hacking-gczeal] and the [JSAPI Documentation][mdn-gczeal] for more information about this variable. * `GJS_DISABLE_JIT` Setting this variable to any value will disable JIT compiling in the JavaScript engine. ## Debugging * `GJS_DEBUG_HEAP_OUTPUT` In addition to `System.dumpHeap()`, you can dump a heap from a running program by starting it with this environment variable set to a path and sending it the `SIGUSR1` signal. * `GJS_DEBUG_OUTPUT` Set this to "stderr" to log to `stderr` or a file path to save to. * `GJS_DEBUG_TOPICS` Set this to a semi-colon delimited (`;`) list of prefixes to allow to be logged. Prefixes include: * "JS GI USE" * "JS MEMORY" * "JS CTX" * "JS IMPORT" * "JS NATIVE" * "JS KP ALV" * "JS G REPO" * "JS G NS" * "JS G OBJ" * "JS G FUNC" * "JS G FNDMTL" * "JS G CLSR" * "JS G BXD" * "JS G ENUM" * "JS G PRM" * "JS G ERR" * "JS G IFACE" * `GJS_DEBUG_THREAD` Set this variable to print the thread number when logging. * `GJS_DEBUG_TIMESTAMP` Set this variable to print a timestamp when logging. ## Testing * `GJS_COVERAGE_OUTPUT` Set this variable to define an output path for code coverage information. Use of the `--coverage-output` command-line option is preferred over this variable. * `GJS_COVERAGE_PREFIXES` Set this variable to define a colon-separated (`:`) list of prefixes to output code coverage information for. Use of the `--coverage-prefix` command-line option is preferred over this variable. * `GJS_ENABLE_PROFILER` Set this variable to `1` to enable or `0` to disable the profiler. Use of the `--profile` command-line option is preferred over this variable. * `GJS_TRACE_FD` The GJS profiler is integrated directly into Sysprof via this variable. It not typically useful to set this manually. [hacking-gczeal]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/doc/Hacking.md#gc-zeal [mdn-gczeal]: https://developer.mozilla.org/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_SetGCZeal cjs-128.1/doc/Format.md0000664000175000017500000000615515116312211013547 0ustar fabiofabio# Format The `Format` module is a mostly deprecated module that implements `printf()` style formatting for GJS. In most cases, native [template literals][template-literals] should be preferred now, except in few situations like Gettext (See [Bug #60027][bug-60027]). ```js const foo = 'Pi'; const bar = 1; const baz = Math.PI; // expected result: "Pi to 2 decimal points: 3.14" // Native template literals const str1 = `${foo} to ${bar*2} decimal points: ${baz.toFixed(bar*2)}` // Format.vprintf() const str2 = Format.vprintf('%s to %d decimal points: %.2f', [foo, bar*2, baz]); ``` #### Import > Attention: This module is not available as an ECMAScript Module The `Format` module is available on the global `imports` object: ```js const Format = imports.format; ``` [template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals [bug-60027]: https://savannah.gnu.org/bugs/?60027 ### Format.format(...args) > Deprecated: Use [`Format.vprintf()`](#format-vprintf) instead Type: * Prototype Function Parameters: * args (`Any`) — Formatting substitutions Returns: * (`String`) — A new formatted string This function was intended to extend the `String` object and provide a `String.format` API for string formatting. Example usage: ```js const Format = imports.format; // Applying format() to the string prototype. // // This is highly discouraged, especially in GNOME Shell extensions where other // extensions might overwrite it. Use Format.vprintf() directly instead. String.prototype.format = Format.format; // Usage with String.prototype.format() // expected result: "A formatted string" const str = 'A %s %s'.format('formatted', 'string'); ``` ### Format.printf(fmt, ...args) > Deprecated: Use [template literals][template-literals] with `print()` instead Type: * Static Parameters: * fmt (`String`) — A format template * args (`Any`) — Formatting substitutions Substitute the specifiers in `fmt` with `args` and print the result to `stdout`. Example usage: ```js // expected output: A formatted string Format.printf('A %s %s', 'formatted', 'string'); ``` ### Format.vprintf(fmt, args) > Deprecated: Prefer [template literals][template-literals] when possible Type: * Static Parameters: * fmt (`String`) — A format template * args (`Array(Any)`) — Formatting substitutions Returns: * (`String`) — A new formatted string Substitute the specifiers in `fmt` with `args` and return a new string. It supports the `%s`, `%d`, `%x` and `%f` specifiers. For `%f` it also supports precisions like `vprintf('%.2f', [1.526])`. All specifiers can be prefixed with a minimum field width (e.g. `vprintf('%5s', ['foo'])`). Unless the width is prefixed with `'0'`, the formatted string will be padded with spaces. Example usage: ```js // expected result: "A formatted string" const str = Format.vprintf('A %s %s', ['formatted', 'string']); // Usage with Gettext Format.vprintf(_('%d:%d'), [11, 59]); Format.vprintf( Gettext.ngettext('I have %d apple', 'I have %d apples', num), [num]); ``` [template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals cjs-128.1/doc/Gettext.md0000664000175000017500000001542515116312211013743 0ustar fabiofabio# Gettext > See also: [`examples/gettext.js`][examples-gettext] for usage examples This module provides a convenience layer for the "gettext" family of functions, relying on GLib for the actual implementation. Example usage: ```js const Gettext = imports.gettext; Gettext.textdomain('myapp'); Gettext.bindtextdomain('myapp', '/usr/share/locale'); let translated = Gettext.gettext('Hello world!'); ``` #### Import When using ESModules: ```js import Gettext from 'gettext'; ``` When using legacy imports: ```js const Gettext = imports.gettext; ``` [examples-gettext]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/examples/gettext.js ### Gettext.LocaleCategory An enumeration of locale categories supported by GJS. * `CTYPE = 0` — Character classification * `NUMERIC = 1` — Formatting of nonmonetary numeric values * `TIME = 2` — Formatting of date and time values * `COLLATE = 3` — String collation * `MONETARY = 4` — Formatting of monetary values * `MESSAGES = 5` — Localizable natural-language messages * `ALL = 6` — All of the locale ### Gettext.setlocale(category, locale) > Note: It is rarely, if ever, necessary to call this function in GJS Parameters: * category (`Gettext.LocaleCategory`) — A locale category * locale (`String`|`null`) — A locale string, or `null` to query the locale Returns: * (`String`|`null`) — A locale string, or `null` if `locale` is not `null` Set or query the program's current locale. Example usage: ```js Gettext.setlocale(Gettext.LocaleCategory.MESSAGES, 'en_US.UTF-8'); ``` ### Gettext.textdomain(domainName) Parameters: * domainName (`String`) — A translation domain Set the default domain to `domainName`, which is used in all future gettext calls. Note that this does not affect functions that take an explicit `domainName` argument, such as `Gettext.dgettext()`. Typically this will be the project name or another unique identifier. For example, GNOME Calculator might use something like `"gnome-calculator"` while a GNOME Shell Extension might use its extension UUID. ### Gettext.bindtextdomain(domainName, dirName) Parameters: * domainName (`String`) — A translation domain * dirName (`String`) — A directory path Specify `dirName` as the directory that contains translations for `domainName`. In most cases, `dirName` will be the system locale directory, such as `/usr/share/locale`. GNOME Shell's `ExtensionUtils.initTranslations()` method, on the other hand, will check an extension's directory for a `locale` subdirectory before falling back to the system locale directory. ### Gettext.gettext(msgid) > Note: This is equivalent to calling `Gettext.dgettext(null, msgid)` Parameters: * msgid (`String`) — A string to translate Returns: * (`String`) — A translated message This function is a wrapper of `dgettext()` which does not translate the message if the default domain as set with `Gettext.textdomain()` has no translations for the current locale. ### Gettext.dgettext(domainName, msgid) > Note: This is an alias for [`GLib.dgettext()`][gdgettext] Parameters: * domainName (`String`|`null`) — A translation domain * msgid (`String`) — A string to translate Returns: * (`String`) — A translated message This function is a wrapper of `dgettext()` which does not translate the message if the default domain as set with `Gettext.textdomain()` has no translations for the current locale. [gdgettext]: https://gjs-docs.gnome.org/glib20/glib.dgettext ### Gettext.dcgettext(domainName, msgid, category) > Note: This is an alias for [`GLib.dcgettext()`][gdcgettext] Parameters: * domainName (`String`|`null`) — A translation domain * msgid (`String`) — A string to translate * category (`Gettext.LocaleCategory`) — A locale category Returns: * (`String`) — A translated message This is a variant of `Gettext.dgettext()` that allows specifying a locale category. [gdcgettext]: https://gjs-docs.gnome.org/glib20/glib.dcgettext ### Gettext.ngettext(msgid1, msgid2, n) > Note: This is equivalent to calling > `Gettext.dngettext(null, msgid1, msgid2, n)` Parameters: * msgid1 (`String`) — The singular form of the string to be translated * msgid2 (`String`) — The plural form of the string to be translated * n (`Number`) — The number determining the translation form to use Returns: * (`String`) — A translated message Translate a string that may or may not be plural, like "I have 1 apple" and "I have 2 apples". In GJS, this should be used in conjunction with [`Format.vprintf()`][vprintf], which supports the same substitutions as `printf()`: ```js const numberOfApples = Math.round(Math.random() + 1); const translated = Format.vprintf(Gettext.ngettext('I have %d apple', 'I have %d apples', numberOfApples), [numberOfApples]); ``` [vprintf]: https://gjs-docs.gnome.org/gjs/format.md#format-vprintf ### Gettext.dngettext(domainName, msgid1, msgid2, n) > Note: This is an alias for [`GLib.dngettext()`][gdngettext] Parameters: * domainName (`String`|`null`) — A translation domain * msgid1 (`String`) — A string to translate * msgid2 (`String`) — A pluralized string to translate * n (`Number`) — The number determining the translation form to use Returns: * (`String`) — A translated message This function is a wrapper of `dngettext()` which does not translate the message if the default domain as set with `textdomain()` has no translations for the current locale. [gdngettext]: https://gjs-docs.gnome.org/glib20/glib.dngettext ### Gettext.pgettext(context, msgid) > Note: This is equivalent to calling `Gettext.dpgettext(null, context, msgid)` Parameters: * context (`String`|`null`) — A context to disambiguate `msgid` * msgid (`String`) — A string to translate Returns: * (`String`) — A translated message This is a variant of `Gettext.dgettext()` which supports a disambiguating message context. This is used to disambiguate a translation where the same word may be used differently, depending on the situation. For example, in English "read" is the same for both past and present tense, but may not be in other languages. ### Gettext.dpgettext(domainName, context, msgid) > Note: This is an alias for [`GLib.dpgettext2()`][gdpgettext2] Parameters: * domainName (`String`|`null`) — A translation domain * context (`String`|`null`) — A context to disambiguate `msgid` * msgid (`String`) — A string to translate Returns: * (`String`) — A translated message This is a variant of `Gettext.dgettext()` which supports a disambiguating message context. [gdpgettext2]: https://gjs-docs.gnome.org/glib20/glib.dpgettext2 ### Gettext.domain(domainName) > Note: This method is specific to GJS Parameters: * domainName (`String`) — A domain name Returns: * (`Object`) — An object with common gettext methods Create an object with bindings for `Gettext.gettext()`, `Gettext.ngettext()`, and `Gettext.pgettext()`, bound to a `domainName`. cjs-128.1/doc/Hacking.md0000664000175000017500000002111315116312211013652 0ustar fabiofabio# Hacking on GJS ## Quick start If you are looking to get started quickly, then you can clone GJS using GNOME Builder and choose the `org.gnome.GjsConsole` build configuration. For the most part, you will be able to build GJS with the Build button and run the interpreter with the Run button. If you need to issue any of the Meson commands manually, make sure to do so in a runtime terminal (Ctrl+Alt+T) rather than a build terminal or a regular terminal. ## Setting up First of all, download the GJS source code using Git. Go to [GJS on GitLab](https://gitlab.gnome.org/GNOME/gjs), and click "Fork" near the top right of the page. Then, click the "Clone" button that's located a bit under the "Fork" button, and click the little clipboard icon next to "Clone with SSH" or "Clone with HTTPS", to copy the address to your clipboard. Go to your terminal, and type `git clone` and then paste the address into your terminal with Shift+Ctrl+V. (Don't forget Shift! It's important when pasting into a terminal.) This will download the GJS source code into a `gjs` directory. If you are contributing C++ code, install the handy git commit hook that will autoformat your code when you commit it. In your `gjs` directory, run `tools/git-pre-commit-format install`. For more information, see . (You can skip this step if it doesn't work for you, but in that case you'll need to manually format your code before it gets merged. You can also skip this step if you are not writing any C++ code.) ## Dependencies GJS requires five other libraries to be installed: GLib, libffi, gobject-introspection, SpiderMonkey (also called "mozjs128" on some systems.) and the build tool Meson. The readline library is not required, but strongly recommended. We recommend installing your system's development packages for GLib, libffi, gobject-introspection, Meson and readline.
Ubuntu sudo apt-get install libglib2.0-dev libffi-dev libreadline-dev libgirepository1.0-dev meson
Fedora sudo dnf install glib2-devel libffi readline-devel gobject-introspection-devel meson
But, if your system's versions of these packages aren't new enough, then the build process will download and build sufficient versions. SpiderMonkey cannot be auto-installed, so you will need to install it either through your system's package manager, or building it yourself. Even if your system includes a development package for SpiderMonkey, we still recommend building it if you are going to do any development on GJS's C++ code so that you can enable the debugging features. These debugging features reduce performance by quite a lot, but they will help catch mistakes in the API that could otherwise go unnoticed and cause crashes in gnome-shell later on. If you aren't writing any C++ code, and your system provides it (for example, Fedora 41 or Ubuntu 24.10 and later versions), then you don't need to build it yourself. Install SpiderMonkey using your system's package manager instead:
Fedora sudo dnf install mozjs128-devel
If you _are_ writing C++ code, then please build SpiderMonkey yourself with the debugging features enabled. This can save you time later when you submit your merge request, because the code will be checked using the debugging features. To build SpiderMonkey, follow the instructions on [this page](https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr128/docs/Building%20SpiderMonkey.md) to download the source code and build the library. If you are using `-Dprefix` to build GJS into a different path, then make sure to use the same build prefix for SpiderMonkey with `--prefix`. ## First build To build GJS, change to your `gjs` directory, and run: ```sh meson setup _build ninja -C _build ``` Add any options with `-D` arguments to the `meson setup _build` command. For a list of available options, run `meson configure`. That's it! You can now run your build of gjs for testing and hacking with ```sh meson devenv -C _build cjs-console ../script.js ``` (the path `../script.js` is relative to `_build`, not the root folder) To install GJS into the path you chose with `-Dprefix`, (or into `/usr/local` if you didn't choose a path), run `ninja -C _build install`, adding `sudo` if necessary. ## Making Sure Your Stuff Doesn't Break Anything Else Make your changes in your `gjs` directory, then run `ninja -C _build` to build a modified copy of GJS. Each changeset should ensure that the test suite still passes. In fact, each commit should ensure that the test suite still passes, though there are some exceptions to this rule. You can run the test suite with `meson test -C _build`. For some contributions, it's a good idea to test your modified version of GJS with GNOME Shell. For this, you might want to use JHBuild to build GJS instead, and run it with `jhbuild run gnome-shell --replace`. You need to be logged into an Xorg session, not Wayland, for this to work. ## Debugging Mozilla has some pretty-printers that make debugging JSAPI code easier. Unfortunately they're not included in most packaged distributions of mozjs, but you can grab them from your built copy of mozjs. After reaching a breakpoint in your program, type this to activate the pretty-printers: ```sh source /path/to/spidermonkey/js/src/_build/js/src/shell/js-gdb.py ``` (replace `/path/to/spidermonkey` with the path to your SpiderMonkey sources) ## Getting a stack trace Run your program with `gdb --args gjs myfile.js`. This will drop you into the GDB debugger interface. Enter `r` to start the program. When it segfaults, enter `bt full` to get the C++ stack trace, and enter `call gjs_dumpstack()` to get the JS stack trace. (It may need to be `call (void) gjs_dumpstack()` if you don't have debugging symbols installed.) Enter `q` to quit. ## Checking Things More Thoroughly Before A Release ### GC Zeal Run the test suite with "GC zeal" to make non-deterministic GC errors more likely to show up. To see which GC zeal options are available: ```sh JS_GC_ZEAL=-1 js128 ``` We include three test setups, `extra_gc`, `pre_verify`, and `post_verify`, for the most useful modes: `2,1`, `4`, and `11` respectively. Run them like this (replace `extra_gc` with any of the other names): ```sh meson test -C _build --setup=extra_gc ``` Failures in mode `pre_verify` usually point to a GC thing not being traced when it should have been. Failures in mode `post_verify` usually point to a weak pointer's location not being updated after GC moved it. ### Valgrind Valgrind catches memory leak errors in the C++ code. It's a good idea to run the test suite under Valgrind before each release. To run the test suite under Valgrind's memcheck tool: ```sh meson test -C _build --setup=valgrind ``` The logs from each run will be in `_build/meson-logs/testlog-valgrind.txt`. Note that LeakSanitizer, part of ASan (see below) can catch many, but not all, errors that Valgrind can catch. LSan executes faster than Valgrind, however. ### Static Code Analysis To execute cppcheck, a static code analysis tool for the C and C++, run: ```sh tools/run_cppcheck.sh ``` It is a versatile tool that can check non-standard code, including: variable checking, bounds checking, leaks, etc. It can detect the types of bugs that the compilers normally fail to detect. ### Sanitizers To build GJS with support for the ASan and UBSan sanitizers, configure meson like this: ```sh meson setup _build -Db_sanitize=address,undefined ``` and then run the tests as normal. ### Test Coverage To generate a test coverage report, run this script: ```sh tools/run_coverage.sh gio open _coverage/html/index.html ``` This will build GJS into a separate build directory with code coverage instrumentation enabled, run the test suite to collect the coverage data, and open the generated HTML report. [embedder](https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr128/docs/Building%20SpiderMonkey.md) ## Troubleshooting ### I sent a merge request from my fork but CI does not pass. Check the job log, most likely you missed the following > The container registry is not enabled in $USERNAME/gjs, enable it in the project general settings panel * Go to your fork general setting, for example https://gitlab.gnome.org/$USERNAME/gjs/edit * Expand "Visibility, project features, permissions" * Enable "Container registry" * Hit "Save changes" cjs-128.1/doc/Home.md0000664000175000017500000000013015116312211013172 0ustar fabiofabio# GJS: Javascript Bindings for GNOME This page has moved to [`README.md`](README.md). cjs-128.1/doc/Internship-Getting-Started.md0000664000175000017500000001357015116312211017444 0ustar fabiofabio# Welcome to GJS! This document is a guide to getting started with GJS, especially if you are applying to an internship program such as Outreachy or Summer of Code where you make a contribution as part of your application process. GJS is the JavaScript environment inside GNOME. It's responsible for executing the user interface code in the GNOME desktop, including the extensions that people use to modify their desktop with. It's also the environment that several GNOME apps are written in, like GNOME Sound Recorder, Polari, etc. GJS is written in both C++ and JavaScript, and is built on top of the JavaScript engine from Firefox, called SpiderMonkey. The application process is supposed to give you the opportunity to work on good newcomer bugs from GJS. > Thanks to Iain Ireland for kind permission to adapt this document from > SpiderMonkey's instructions! ## Steps to participate ### Phase 1: Getting set up There are two parts of this phase: getting your development environment set up, and getting set up to communicate with the other GNOME volunteers. For your development environment, the tasks are: 1. Make an account on [GitLab](https://gitlab.gnome.org). 1. Download the GJS source code and build GJS. > You can follow the [GNOME Newcomer > instructions](https://wiki.gnome.org/Newcomers/BuildProject) if > you want to use Builder, or follow the [instructions](Hacking.md) > for the command line. 1. Run the GJS test suite and make sure it passes. > Run `meson test -C _build`. > If you are using Builder, do this in a runtime terminal > (Ctrl+Alt+T). For communication, your tasks are: 1. Create an account on [Matrix](https://gnome.element.io). 1. Introduce yourself in [#javascript](https://matrix.to/#/#javascript:gnome.org)! Congratulations! Now you’re ready to contribute! ### Phase 2: Fixing your first bug 1. Find an unclaimed ["Newcomers" bug in the GJS bugtracker](https://gitlab.gnome.org/GNOME/gjs/-/issues?label_name%5B%5D=4.+Newcomers). 1. Post a comment on your bug to say that you're working on it. > If you're an Outreachy or Summer of Code participant, make sure to > mention that! > Please only claim bugs if you are actively working on them. > We have a limited number of newcomers bugs. 1. Work on the bug. 1. If you get stuck, ask questions in Matrix or as a comment on the bug. See below for advice on asking good questions. 1. Once your patch is complete and passes all the tests, make a merge request with GitLab. 1. If any CI results on the merge request are failing, look at the error logs and make the necessary changes to turn them green. > If this happens, it's usually due to formatting errors. 1. The project mentor, and maybe others as well, will review the code. Work with them to polish up the patch and get it ready to merge. When it's done, the mentor will merge it. > It's normal to have a few rounds of review. Congratulations! You've contributed to GNOME! ### Phase 3: Further contributions If you are applying to an internship and would like to boost your application with additional contributions, you can find another bug and start the process again. We're doing our best to make sure that we have enough newcomers bugs available for our applicants, but they tend to get fixed quickly during the internship application periods. If you've already completed an easier bug, please pick a slightly harder bug for your next contribution. ## Evaluation dimensions We **will** be evaluating applicants based on the following criteria: 1. **Communication:** When collaborating remotely, communication is critical. Does the applicant ask good questions? Does the applicant write good comments? Can the applicant clearly explain any challenges they are facing? 1. **Learning on the fly:** How quickly can the applicant ramp up on a new topic? Is the applicant willing to sit with and struggle through challenging technical problems? 1. **Programming knowledge:** You don't have to be a wizard, but you should feel reasonably comfortable with programming in the languages that will be mainly used during the project. Is the applicant able to reliably produce merge requests that pass CI with moderate feedback? Does the applicant have a basic understanding of how to debug problems? We **will not** be evaluating applicants based on the following criteria: 1. **Geographic location:** GNOME contributors come from everywhere, and we regularly collaborate across significant time zone gaps. Communication may have to be more asynchronous for applicants in some time zones, but we will not be making a decision based on location. 1. **Formal qualifications / schooling**: We will be evaluating applicants only based on their contributions during the application process. ## Asking good questions [This blog post by Julia Evans](https://jvns.ca/blog/good-questions/) is an excellent resource on asking good questions. (The "decide who to ask" section is less relevant in the context of Outreachy or Summer of Code; during the application process, you should generally be asking questions in Matrix, and whoever sees your question first and knows the answer will respond.) Good questions should respect the time of both the person answering the question, **and the person asking it** (you yourself!). You shouldn't flood the Matrix channel asking questions that you could answer yourself in a short time with a search engine. On the other hand, you should also not spend days trying to figure out the answer to something that somebody more experienced could clear up in a few minutes. If you are having problems, it is often useful to take a break and come back with a fresh head. If you're still stuck, it's amazing how often the answer will come to you as you try to write your question down. If you've managed to write out a clear statement of your problem, and you still can't figure out the answer: ask! cjs-128.1/doc/Lang.md0000664000175000017500000000611415116312211013173 0ustar fabiofabio# Lang The `Lang` module is a collection of deprecated features that have been completely superseded by standard ECMAScript. It remains a part of GJS for backwards-compatibility reasons, but should never be used in new code. #### Import > Attention: This module is not available as an ECMAScript Module The `Lang` module is available on the global `imports` object: ```js const Lang = imports.lang ``` ### Lang.bind(thisArg, function, ...args) > Deprecated: Use [`Function.prototype.bind()`][function-bind] instead Type: * Static Parameters: * thisArg (`Object`) — A JavaScript object * callback (`Function`) — A function reference * args (`Any`) — A function reference Returns: * (`Function`) — A new `Function` instance, bound to `thisArg` Binds a function to a scope. [function-bind]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind ### Lang.Class(object) > Deprecated: Use native [JavaScript Classes][js-class] instead Type: * Static Parameters: * object (`Object`) — A JavaScript object Returns: * (`Object`) — A JavaScript class expression ... Example usage: ```js const MyLegacyClass = new Lang.Class({ _init: function() { let fnorb = new FnorbLib.Fnorb(); fnorb.connect('frobate', Lang.bind(this, this._onFnorbFrobate)); }, _onFnorbFrobate: function(fnorb) { this._updateFnorb(); } }); ``` [js-class]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes ### Lang.copyProperties(source, dest) > Deprecated: Use [`Object.assign()`][object-assign] instead Type: * Static Parameters: * source (`Object`) — The source object * dest (`Object`) — The target object Copy all properties from `source` to `dest`, including those that are prefixed with an underscore (e.g. `_privateFunc()`). [object-assign]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign ### Lang.copyPublicProperties(source, dest) > Deprecated: Use [`Object.assign()`][object-assign] instead Type: * Static Parameters: * source (`Object`) — The source object * dest (`Object`) — The target object Copy all public properties from `source` to `dest`, excluding those that are prefixed with an underscore (e.g. `_privateFunc()`). [object-assign]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign ### Lang.countProperties(object) > Deprecated: Use [`Object.assign()`][object-assign] instead Type: * Static Parameters: * object (`Object`) — A JavaScript object [object-assign]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign ### Lang.getMetaClass(object) Type: * Static Parameters: * object (`Object`) — A JavaScript object Returns: * (`Object`|`null`) — A `Lang.Class` meta object ... ### Lang.Interface(object) > Deprecated: Use native [JavaScript Classes][js-class] instead Type: * Static Parameters: * object (`Object`) — A JavaScript object Returns: * (`Object`) — A JavaScript class expression ... [js-class]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes cjs-128.1/doc/Logging.md0000664000175000017500000000570715116312211013707 0ustar fabiofabio# Logging GJS includes a number of built-in functions for logging and aiding debugging, in addition to those available as a part of the GNOME APIs. In most cases, the [`console`][console] suite of functions should be preferred for logging in GJS. #### Import The functions in this module are available globally, without import. [console]: https://gjs-docs.gnome.org/gjs/console.md ### log(message) > See also: [`console.log()`][console-log] Type: * Static Parameters: * message (`Any`) — A string or any coercible value Logs a message with severity equal to [`GLib.LogLevelFlags.LEVEL_MESSAGE`][gloglevelflagsmessage]. ```js // expected output: JS LOG: Some message log('Some message'); // expected output: JS LOG: [object Object] log({key: 'value'}); ``` [console-log]: https://gjs-docs.gnome.org/gjs/console.md#console-log [gloglevelflagsmessage]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_message ### logError(error, prefix) > See also: [`console.trace()`][console-trace] Type: * Static Parameters: * error (`Error`) — An `Error` or [`GLib.Error`][gerror] object * prefix (`String`) — Optional prefix for the message Logs a stack trace for `error`, with an optional prefix, with severity equal to [`GLib.LogLevelFlags.LEVEL_WARNING`][gloglevelflagswarning]. This function is commonly used in conjunction with `try...catch` blocks to log errors while still trapping the exception: ```js try { throw new Error('Some error occured'); } catch (e) { logError(e, 'FooError'); } ``` It can also be passed directly to the `catch()` clause of a `Promise` chain: ```js Promise.reject().catch(logError); ``` [console-trace]: https://gjs-docs.gnome.org/gjs/console.md#console-trace [gerror]: https://gjs-docs.gnome.org/glib20/glib.error [gloglevelflagswarning]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags#default-level_warning ### print(...messages) > Note: this function is not useful for GNOME Shell extensions Type: * Static Parameters: * messages (`Any`) — Any number of strings or coercible values Takes any number of strings (or values that can be coerced to strings), joins them with a space and appends a newline character (`\n`). The resulting string is printed directly to `stdout` of the current process with [`g_print()`][gprint]. ```js $ gjs -c "print('foobar', 42, {});" foobar 42 [object Object] $ ``` [gprint]: https://docs.gtk.org/glib/func.print.html ### printerr(...messages) > Note: this function is not useful for GNOME Shell extensions Type: * Static Parameters: * messages (`Any`) — Any number of strings or coercible values Takes any number of strings (or values that can be coerced to strings), joins them with a space and appends a newline character (`\n`). The resulting string is printed directly to `stderr` of the current process with [`g_printerr()`][gprinterr]. ```js $ gjs -c "printerr('foobar', 42, {});" foobar 42 [object Object] $ ``` [gprinterr]: https://docs.gtk.org/glib/func.printerr.html cjs-128.1/doc/Mainloop.md0000664000175000017500000001356215116312211014075 0ustar fabiofabio# Mainloop The `Mainloop` module is a convenience layer for some common event loop methods in GLib, such as [`GLib.timeout_add()`][gtimeoutadd]. This module is not generally recommended, but is documented for the sake of existing code. Each method below contains links to the corresponding GLib method for reference. For an introduction to the GLib event loop, see the [Asynchronous Programming Tutorial][async-tutorial]. [async-tutorial]: https://gjs.guide/guides/gjs/asynchronous-programming.html [gtimeoutadd]: https://gjs-docs.gnome.org/glib20/glib.timeout_add #### Import > Attention: This module is not available as an ECMAScript Module The `Mainloop` module is available on the global `imports` object: ```js const Mainloop = imports.mainloop ``` ### Mainloop.idle_add(handler, priority) > See also: [`GLib.idle_add()`][gidleadd] Type: * Static Parameters: * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created idle source Adds a function to be called whenever there are no higher priority events pending. If the function returns `false` it is automatically removed from the list of event sources and will not be called again. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT_IDLE`. [gidleadd]: https://gjs-docs.gnome.org/glib20/glib.idle_add ### Mainloop.idle_source(handler, priority) > See also: [`GLib.idle_source_new()`][gidlesourcenew] Type: * Static Parameters: * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created idle source Creates a new idle source. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT_IDLE`. [gidlesourcenew]: https://gjs-docs.gnome.org/glib20/glib.idle_source_new ### Mainloop.quit(name) > See also: [`GLib.MainLoop.quit()`][gmainloopquit] Type: * Static Parameters: * name (`String`) — Optional name Stops a main loop from running. Any calls to `Mainloop.run(name)` for the loop will return. If `name` is given, this function will create a new [`GLib.MainLoop`][gmainloop] if necessary. [gmainloop]: https://gjs-docs.gnome.org/glib20/glib.mainloop [gmainloopquit]: https://gjs-docs.gnome.org/glib20/glib.mainloop#method-quit ### Mainloop.run(name) > See also: [`GLib.MainLoop.run()`][gmainlooprun] Type: * Static Parameters: * name (`String`) — Optional name Runs a main loop until `Mainloop.quit()` is called on the loop. If `name` is given, this function will create a new [`GLib.MainLoop`][gmainloop] if necessary. [gmainloop]: https://gjs-docs.gnome.org/glib20/glib.mainloop [gmainlooprun]: https://gjs-docs.gnome.org/glib20/glib.mainloop#method-run ### Mainloop.source_remove(id) > See also: [`GLib.Source.remove()`][gsourceremove] Type: * Static Parameters: * id (`Number`) — The ID of the source to remove Returns: * (`Boolean`) — For historical reasons, this function always returns `true` Removes the source with the given ID from the default main context. [gsourceremove]: https://gjs-docs.gnome.org/glib20/glib.source#function-remove ### Mainloop.timeout_add(timeout, handler, priority) > See also: [`GLib.timeout_add()`][gtimeoutadd] Type: * Static Parameters: * timeout (`Number`) — The timeout interval in milliseconds * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created timeout source Sets a function to be called at regular intervals, with the given priority. The function is called repeatedly until it returns `false`, at which point the timeout is automatically destroyed and the function will not be called again. The scheduling granularity/accuracy of this source will be in milliseconds. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT`. [gtimeoutadd]: https://gjs-docs.gnome.org/glib20/glib.timeout_add ### Mainloop.timeout_add_seconds(timeout, handler, priority) > See also: [`GLib.timeout_add_seconds()`][gtimeoutaddseconds] Type: * Static Parameters: * timeout (`Number`) — The timeout interval in seconds * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created timeout source Sets a function to be called at regular intervals, with the given priority. The function is called repeatedly until it returns `false`, at which point the timeout is automatically destroyed and the function will not be called again. The scheduling granularity/accuracy of this source will be in seconds. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT`. [gtimeoutaddseconds]: https://gjs-docs.gnome.org/glib20/glib.timeout_add_seconds ### Mainloop.timeout_source(timeout, handler, priority) > See also: [`GLib.timeout_source_new()`][gtimeoutsourcenew] Type: * Static Parameters: * timeout (`Number`) — The timeout interval in milliseconds * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created timeout source Creates a new timeout source. The scheduling granularity/accuracy of this source will be in milliseconds. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT`. [gtimeoutsourcenew]: https://gjs-docs.gnome.org/glib20/glib.timeout_source_new ### Mainloop.timeout_seconds_source(timeout, handler, priority) > See also: [`GLib.timeout_source_new_seconds()`][gtimeoutsourcenewseconds] Type: * Static Parameters: * timeout (`Number`) — The timeout interval in seconds * handler (`Function`) — The function to call * priority (`Number`) — Optional priority Returns: * (`GLib.Source`) — The newly-created timeout source Creates a new timeout source. The scheduling granularity/accuracy of this source will be in seconds. If not given, `priority` defaults to `GLib.PRIORITY_DEFAULT`. [gtimeoutsourcenewseconds]: https://gjs-docs.gnome.org/glib20/glib.timeout_source_new_seconds cjs-128.1/doc/Mapping.md0000664000175000017500000002524715116312211013715 0ustar fabiofabio# GObject Usage in GJS This is general overview of how to use GObject in GJS. Whenever possible GJS tries to use idiomatic JavaScript, so this document may be of more interest to C or Python developers that are new to GJS. ## GObject Construction GObjects can be constructed with the `new` operator, and usually take an `Object` map of properties: ```js const label = new Gtk.Label({ label: 'gnome.org', halign: Gtk.Align.CENTER, hexpand: true, use_markup: true, visible: true, }); ``` The object that you pass to `new` (`Gtk.Label` in the example above) is the **constructor object**, which may also contain static methods and constructor methods such as `Gio.File.new_for_path()`: ```js const file = Gio.File.new_for_path('/proc/cpuinfo'); ``` The **constructor object** is different from the **prototype object** containing instance methods. For more information on JavaScript's prototypal inheritance, this [blog post][understanding-javascript-prototypes] is a good resource. [understanding-javascript-prototypes]: https://javascriptweblog.wordpress.com/2010/06/07/understanding-javascript-prototypes/ ## GObject Subclassing > See also: [`GObject.registerClass()`](overrides.md#gobject-registerclass) GObjects have facilities for defining properties, signals and implemented interfaces. Additionally, Gtk objects support defining a CSS name and composite template. The **constructor object** is also passed to the `extends` keyword in class declarations when subclassing GObjects. ```js var MyLabel = GObject.registerClass({ // GObject GTypeName: 'Gjs_MyLabel', // GType name (see below) Implements: [ Gtk.Orientable ], // Interfaces the subclass implements Properties: {}, // More below on custom properties Signals: {}, // More below on custom signals // Gtk CssName: '', // CSS name Template: 'resource:///path/example.ui', // Builder template Children: [ 'label-child' ], // Template children InternalChildren: [ 'internal-box' ] // Template internal (private) children }, class MyLabel extends Gtk.Label { constructor(params) { // Chaining up super(params); } }); ``` Note that before GJS 1.72 (GNOME 42), you had to override `_init()` and chain-up with `super._init()`. This behaviour is still supported for backwards-compatibility, but new code should use the standard `constructor()` and chain-up with `super()`. For a more complete introduction to GObject subclassing in GJS, see the [GObject Tutorial][gobject-subclassing]. [gobject-subclassing]: https://gjs.guide/guides/gobject/subclassing.html#subclassing-gobject ## GObject Properties GObject properties may be retrieved and set using native property style access or GObject get/set methods. Note that variables in JavaScript can't contain hyphens (-) so when a property name is *unquoted* use an underscore (_). ```js let value; value = label.use_markup; value = label.get_use_markup(); value = label['use-markup']; label.use_markup = value; label.set_use_markup(value); label['use-markup'] = value; label.connect('notify::use-markup', () => {}); ``` GObject subclasses can register properties, which is necessary if you want to use `GObject.notify()` or `GObject.bind_property()`. > Warning: Never use underscores in property names in the ParamSpec, because of > the conversion between underscores and hyphens mentioned above. ```js var MyLabel = GObject.registerClass({ Properties: { 'example-prop': GObject.ParamSpec.string( 'example-prop', // property name 'ExampleProperty', // nickname 'An example read write property', // description GObject.ParamFlags.READWRITE, // READABLE/READWRITE/CONSTRUCT/etc 'A default' // default value if omitting getter/setter ) } }, class MyLabel extends Gtk.Label { get example_prop() { if (!('_example_prop' in this) return 'A default'; return this._example_prop; } set example_prop(value) { if (this._example_prop !== value) { this._example_prop = value; this.notify('example-prop'); } } }); ``` If you just want a simple property that you can get change notifications from, you can leave out the getter and setter and GJS will attempt to do the right thing. However, if you define one, you have to define both (unless the property is read-only or write-only). The 'default value' parameter passed to `GObject.ParamSpec` will be taken into account if you omit the getter and setter. If you write your own getter and setter, you have to implement the default value yourself, as in the above example. ## GObject Signals > See also: The [`Signals`][signals-module] module contains an GObject-like > signal framework for native Javascript classes Every object inherited from GObject has `connect()`, `connect_after()`, `disconnect()` and `emit()` methods. ```js // Connecting a signal handler let handlerId = label.connect('activate-link', (label, uri) => { Gtk.show_uri_on_window(label.get_toplevel(), uri, Gdk.get_current_time()); return true; }); // Emitting a signal label.emit('activate-link', 'https://www.gnome.org'); // Disconnecting a signal handler label.disconnect(handlerId); ``` GObject subclasses can also register their own signals. ```js var MyLabel = GObject.registerClass({ Signals: { 'my-signal': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [ GObject.TYPE_STRING ] } } }, class ExampleApplication extends GObject.Object { constructor() { super(); this.emit('my-signal', 'a string parameter'); } }); ``` [signals-module]: https://gjs-docs.gnome.org/gjs/signals.md ## GType Objects > See also: [`GObject.Object.$gtype`][gobject-object-gtype] and > [`GObject.registerClass()`][gobject-registerclass] This is the object that represents a type in the GObject type system. Internally a GType is an integer, but you can't access that integer in GJS. The GType object is simple wrapper with two members: * name (`String`) — A read-only string property, such as `"GObject"` * toString() (`Function`) — Returns a string representation of the GType, such as `"[object GType for 'GObject']"` Generally this object is not useful and better alternatives exist. Whenever a GType is expected as an argument, you can simply pass a **constructor object**: ```js // Passing a "constructor object" in place of a GType const listInstance = Gio.ListStore.new(Gtk.Widget); // This also works for GObject.Interface types, such as Gio.ListModel const pspec = Gio.ParamSpec.object('list', '', '', GObject.ParamFlags.READABLE, Gio.ListModel); ``` To confirm the GType of an object instance, you can just use the standard [`instanceof` operator][mdn-instanceof]: ```js // Comparing an instance to a "constructor object" const objectInstance = new GObject.Object(); // Comparing an instance to a "constructor object" if (objectInstance instanceof GObject.Object) log(true); // GtkLabel inherits from GObject.Object, so both of these are true const labelInstance = new Gtk.Label(); if (labelInstance instance of GObject.Object) log(true); if (labelInstance instance of Gtk.Label) log(true); ``` [gobject-object-gtype]: https://gjs-docs.gnome.org/gjs/overrides.md#gobject-object-gtype [gobject-registerclass]: https://gjs-docs.gnome.org/gjs/overrides.md#gobject-registerclass [mdn-instanceof]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/instanceof ## Enumerations and Flags Both enumerations and flags appear as entries under the namespace, with associated member properties. These are available in the official GJS [GNOME API documentation][gjs-docs]. Examples: ```js // enum GtkAlign, member GTK_ALIGN_CENTER Gtk.Align.CENTER; // enum GtkWindowType, member GTK_WINDOW_TOPLEVEL Gtk.WindowType.TOPLEVEL; // enum GApplicationFlags, member G_APPLICATION_FLAGS_NONE Gio.ApplicationFlags.FLAGS_NONE ``` Flags can be manipulated using native [bitwise operators][mdn-bitwise]: ```js // Setting a flags property with a combination of flags const myApp = new Gio.Application({ flags: Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_COMMAND_LINE }); // Checking if a flag is set, and removing it if so if (myApp.flags & Gio.ApplicationFlags.HANDLES_OPEN) myApp.flags &= ~Gio.ApplicationFlags.HANDLES_OPEN; ``` [gjs-docs]: https://gjs-docs.gnome.org [mdn-bitwise]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators ## Structs and Unions Structures and unions are documented in the [GNOME API documentation][gjs-docs] (e.g. [Gdk.Event][gdk-event]) and generally have either JavaScript properties or getter methods for each member. Results may vary when trying to modify structs or unions. An example from GTK3: ```js widget.connect('key-press-event', (widget, event) => { // expected output: [union instance proxy GIName:Gdk.Event jsobj@0x7f19a00b6400 native@0x5620c6a7c6e0] log(event); // expected output: true log(event.get_event_type() === Gdk.EventType.KEY_PRESS); // example output: 65507 const [, keyval] = event.get_keyval(); log(keyval); }); ``` [gdk-event]: https://gjs-docs.gnome.org/gdk40/gdk.event ## Return Values and `caller-allocates` > Note: This information is intended for C programmers. Most developers can > simply check the documentation for the function in question. In GJS functions with "out" parameters (`caller-allocates`) are returned as an array of values. For example, in C you may use function like this: ```c GtkRequisition min_size, max_size; gtk_widget_get_preferred_size (widget, &min_size, &max_size); ``` While in GJS it is returned as an array of those values instead: ```js const [minSize, maxSize] = widget.get_preferred_size(); ``` If the function has both a return value and "out" parameters, the return value will be the first element of the array: ```js try { const file = new Gio.File({ path: '/proc/cpuinfo' }); // In the C API, `ok` is the only return value of this method const [ok, contents, etag_out] = file.load_contents(null); } catch(e) { log('Failed to read file: ' + e.message); } ``` Note that because JavaScript throws exceptions, rather than setting a `GError` structure, it is common practice to elide the success boolean in GJS: ```js try { const file = new Gio.File({ path: '/proc/cpuinfo' }); // Eliding success boolean const [, contents, etag] = file.load_contents(null); } catch(e) { log('Failed to read file: ' + e.message); } ``` cjs-128.1/doc/Modules.md0000664000175000017500000000154315116312211013723 0ustar fabiofabio# Modules The documentation previously found here has been updated and reorganized. Most of the documentation can now be browsed at https://gjs-docs.gnome.org. * [Overrides](Overrides.md) * [GObject](Overrides.md#gobject) * [Gio](Overrides.md#gio) * [GLib](Overrides.md#glib) * [GObject-Introspection](Overrides.md#gobject-introspection) * Built-In Modules * [Cairo](cairo.md) * [Format](Format.md) * [Gettext](Gettext.md) * [Mainloop](Mainloop.md) * [Package Specification](Package/Specification.md) * [Signals](Signals.md) * [System](System.md) * Deprecated Modules * [ByteArray](ByteArray.md) (see [Encoding](Encoding.md)) * [Lang](Lang.md) (see [GObject](Overrides.md#gobject)) * [jsUnit](Testing.md#jsunit) (see [Jasmine](Testing.md#jasmine-gjs)) * [Tweener](http://hosted.zeh.com.br/tweener/docs/) cjs-128.1/doc/Overrides.md0000664000175000017500000007634615116312211014272 0ustar fabiofabio# Overrides Like other binding languages, GJS includes a number of overrides for various libraries, like GIO and GTK. These overrides include implementations of functions not normally available to language bindings, as well as convenience functions and support for native JavaScript features such as iteration. The library headings below are links to the JavaScript source for each override, which may clarify particular behaviour or contain extra implementation notes. ## [Gio](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/Gio.js) The `Gio` override includes a number of utilities and conveniences, in particular a number of helpers for working with D-Bus in GJS. For a longer introduction to the D-Bus utilities listed here, see the [D-Bus Tutorial][dbus-tutorial]. [dbus-tutorial]: https://gjs.guide/guides/gio/dbus.html ### Gio.DBus.session > Warning: It is a programmer error to call `close()` on this object instance Type: * [`Gio.DBusConnection`][gdbusconnection] Convenience for getting the session [`Gio.DBusConnection`][gdbusconnection]. This always returns the same object and is equivalent to calling: ```js const connection = Gio.bus_get_sync(Gio.BusType.SESSION, null); ``` [gdbusconnection]: https://gjs-docs.gnome.org/gio20/gio.dbusconnection ### Gio.DBus.system > Warning: It is a programmer error to call `close()` on this object instance Type: * [`Gio.DBusConnection`][gdbusconnection] Convenience for getting the system [`Gio.DBusConnection`][gdbusconnection]. This always returns the same object and is equivalent to calling: ```js const connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, null); ``` [gdbusconnection]: https://gjs-docs.gnome.org/gio20/gio.dbusconnection ### Gio.DBusNodeInfo.new_for_xml(xmlData) Type: * Static Parameters: * xmlData (`String`) — Valid D-Bus introspection XML Returns: * (`Gio.DBusNodeInfo`) — A [`Gio.DBusNodeInfo`][gdbusnodeinfo] structure > Note: This is an override for function normally available in GIO Parses `xmlData` and returns a [`Gio.DBusNodeInfo`][gdbusnodeinfo] representing the data. The introspection XML must contain exactly one top-level `` element. Note that this routine is using a GMarkup-based parser that only accepts a subset of valid XML documents. [gdbusnodeinfo]: https://docs.gtk.org/gio/struct.DBusNodeInfo.html ### Gio.DBusInterfaceInfo.new_for_xml(xmlData) Type: * Static Parameters: * xmlData (`String`) — Valid D-Bus introspection XML Returns: * (`Gio.DBusInterfaceInfo`) — A [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo] structure Parses `xmlData` and returns a [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo] representing the first `` element of the data. This is a convenience wrapper around `Gio.DBusNodeInfo.new_for_xml()` for the common case of a [`Gio.DBusNodeInfo`][gdbusnodeinfo] with a single interface. [gdbusinterfaceinfo]: https://gjs-docs.gnome.org/gio20/gio.dbusinterfaceinfo ### Gio.DBusProxy.makeProxyWrapper(interfaceInfo) Type: * Static Parameters: * interfaceInfo (`String`|`Gio.DBusInterfaceInfo`) — Valid D-Bus introspection XML or [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo] structure Returns: * (`Function`) — A `Function` used to create a [`Gio.DBusProxy`][gdbusproxy] Returns a `Function` that can be used to create a [`Gio.DBusProxy`][gdbusproxy] for `interfaceInfo` if it is a [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo] structure, or the first `` element if it is introspection XML. The returned `Function` has the following signature: ```js @param {Gio.DBusConnection} bus — A bus connection @param {String} name — A well-known name @param {String} object — An object path @param {Function} [asyncCallback] — Optional callback @param {Gio.Cancellable} [cancellable] — Optional cancellable @param {Gio.DBusProxyFlags} flags — Optional flags ``` The signature for `asyncCallback` is: ```js @param {Gio.DBusProxy|null} proxy — A D-Bus proxy, or null on failure @param {Error} error — An exception, or null on success ``` See the [D-Bus Tutorial][make-proxy-wrapper] for an example of how to use this function and the resulting [`Gio.DBusProxy`][gdbusproxy]. [gdbusproxy]: https://gjs-docs.gnome.org/gio20/gio.dbusproxy [make-proxy-wrapper]: https://gjs.guide/guides/gio/dbus.html#high-level-proxies ### Gio.DBusExportedObject.wrapJSObject(interfaceInfo, jsObj) Type: * Static Parameters: * interfaceInfo (`String`|`Gio.DBusInterfaceInfo`) — Valid D-Bus introspection XML or [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo] structure * jsObj (`Object`) — A `class` instance implementing `interfaceInfo` Returns: * (`Gio.DBusInterfaceSkeleton`) — A [`Gio.DBusInterfaceSkeleton`][gdbusinterfaceskeleton] Takes `jsObj`, an object instance implementing the interface described by [`Gio.DBusInterfaceInfo`][gdbusinterfaceinfo], and returns an instance of [`Gio.DBusInterfaceSkeleton`][gdbusinterfaceskeleton]. The returned object has two additional methods not normally found on a `Gio.DBusInterfaceSkeleton` instance: * `emit_property_changed(propertyName, propertyValue)` * propertyName (`String`) — A D-Bus property name * propertyValue (`GLib.Variant`) — A [`GLib.Variant`][gvariant] * `emit_signal(signalName, signalParameters)` * signalName (`String`) — A D-Bus signal name * signalParameters (`GLib.Variant`) — A [`GLib.Variant`][gvariant] See the [D-Bus Tutorial][wrap-js-object] for an example of how to use this function and the resulting [`Gio.DBusInterfaceSkeleton`][gdbusinterfaceskeleton]. [gdbusinterfaceskeleton]: https://gjs-docs.gnome.org/gio20/gio.dbusinterfaceskeleton [gvariant]: https://gjs-docs.gnome.org/glib20/glib.variant [wrap-js-object]: https://gjs.guide/guides/gio/dbus.html#exporting-interfaces ### Gio._promisify(prototype, startFunc, finishFunc) > Warning: This is a tech-preview and not guaranteed to be stable Type: * Static Parameters: * prototype (`Object`) — The prototype of a GObject class * startFunc (`Function`) — The "async" or "start" method * finishFunc (`Function`) — The "finish" method Replaces the original `startFunc` on a GObject class prototype, so that it returns a `Promise` and can be used as a JavaScript `async` function. The function may then be used like any other `Promise` without the need for a customer wrapper, simply by invoking `startFunc` without the callback argument: ```js Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async', 'read_bytes_finish'); try { const inputBytes = new GLib.Bytes('content'); const inputStream = Gio.MemoryInputStream.new_from_bytes(inputBytes); const result = await inputStream.read_bytes_async(inputBytes.get_size(), GLib.PRIORITY_DEFAULT, null); } catch (e) { logError(e, 'Failed to read bytes'); } ``` Note that for "finish" methods that normally return an array with a success boolean, a wrapped function will automatically remove it from the return value: ```js Gio._promisify(Gio.File.prototype, 'load_contents_async', 'load_contents_finish'); try { const file = Gio.File.new_for_path('file.txt'); const [contents, len, etag] = await file.load_contents_async(null); } catch (e) { logError(e, 'Failed to load file contents'); } ``` ### Gio.FileEnumerator[Symbol.asyncIterator] [Gio.FileEnumerator](gio-fileenumerator) are [async iterators](async-iterators). Each iteration returns a [Gio.FileInfo](gio-fileinfo): ```js import Gio from "gi://Gio"; const dir = Gio.File.new_for_path("/"); const enumerator = dir.enumerate_children( "standard::name", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null ); for await (const file_info of enumerator) { console.log(file_info.get_name()); } ``` [gio-fileenumerator]: https://gjs-docs.gnome.org/gio20/gio.fileenumerator [async-iterator]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols [gio-fileinfo]: https://gjs-docs.gnome.org/gio20/gio.fileinfo ### Gio.FileEnumerator[Symbol.iterator] [Gio.FileEnumerator](gio-fileenumerator) are [sync iterators](sync-iterators). Each iteration returns a [Gio.FileInfo](gio-fileinfo): ```js import Gio from "gi://Gio"; const dir = Gio.File.new_for_path("/"); const enumerator = dir.enumerate_children( "standard::name", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null ); for (const file_info of enumerator) { console.log(file_info.get_name()); } ``` [gio-fileenumerator]: https://gjs-docs.gnome.org/gio20/gio.fileenumerator [sync-iterator]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol [gio-fileinfo]: https://gjs-docs.gnome.org/gio20/gio.fileinfo ### Gio.InputStream.createAsyncIterator(count, priority) Parameters: * count (`Number`) — Number of bytes to read per iteration see [read_bytes] * priority (`Number`) — Optional priority (i.e. `GLib.PRIORITY_DEFAULT`) Returns: * (`Object`) — An [asynchronous iterator][async-iterator] Return an asynchronous iterator for a [`Gio.InputStream`][ginputstream]. Each iteration will return a [`GLib.Bytes`][gbytes] object: ```js import Gio from "gi://Gio"; const textDecoder = new TextDecoder("utf-8"); const file = Gio.File.new_for_path("/etc/os-release"); const inputStream = file.read(null); for await (const bytes of inputStream.createAsyncIterator(4)) { log(textDecoder.decode(bytes.toArray())); } ``` [read_bytes]: https://gjs-docs.gnome.org/gio20/gio.inputstream#method-read_bytes [async-iterator]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes [ginputstream]: https://gjs-docs.gnome.org/gio20/gio.inputstream ### Gio.InputStream.createSyncIterator(count, priority) Parameters: * count (`Number`) — Number of bytes to read per iteration see [read_bytes] * priority (`Number`) — Optional priority (i.e. `GLib.PRIORITY_DEFAULT`) Returns: * (`Object`) — An [synchronous iterator][sync-iterator] Return a synchronous iterator for a [`Gio.InputStream`][ginputstream]. Each iteration will return a [`GLib.Bytes`][gbytes] object: ```js import Gio from "gi://Gio"; const textDecoder = new TextDecoder("utf-8"); const file = Gio.File.new_for_path("/etc/os-release"); const inputStream = file.read(null); for (const bytes of inputStream.createSyncIterator(4)) { log(textDecoder.decode(bytes.toArray())); } ``` [read_bytes]: https://gjs-docs.gnome.org/gio20/gio.inputstream#method-read_bytes [sync-iterator]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes [ginputstream]: https://gjs-docs.gnome.org/gio20/gio.inputstream ### Gio.Application.runAsync() Returns: * (`Promise`) Similar to [`Gio.Application.run`][gio-application-run] but return a Promise which resolves when the main loop ends, instead of blocking while the main loop runs. This helps avoid the situation where Promises never resolved if you didn't run the application inside a callback. [gio-application-run]: https://gjs-docs.gnome.org/gio20~2.0/gio.application#method-run ## [GLib](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/GLib.js) The `GLib` override includes a number of utilities and conveniences for working with [`GLib.Variant`][gvariant], [`GLib.Bytes`][gbytes] and others. See the [GVariant Tutorial][make-proxy-wrapper] for examples of working with [`GLib.Variant`][gvariant] objects and the functions here. ### GLib.Bytes.toArray() Returns: * (`Uint8Array`) — A `Uint8Array` Convert a [`GLib.Bytes`][gbytes] object to a `Uint8Array` object. [gbytes]: https://gjs-docs.gnome.org/glib20/glib.bytes ### GLib.log_structured(logDomain, logLevel, stringFields) > Note: This is an override for function normally available in GLib Type: * Static Parameters: * logDomain (`String`) — A log domain, usually G_LOG_DOMAIN * logLevel (`GLib.LogLevelFlags`) — A log level, either from [`GLib.LogLevelFlags`][gloglevelflags], or a user-defined level * stringFields (`{String: Any}`) — Key–value pairs of structured data to add to the log message Log a message with structured data. For more information about this function, see the upstream documentation for [g_log_structured()][glogstructured]. [glogdomain]: https://gjs-docs.gnome.org/glib20/glib.log_domain [gloglevelflags]: https://gjs-docs.gnome.org/glib20/glib.loglevelflags [glogstructured]: https://docs.gtk.org/glib/func.log_structured.html ### GLib.Variant.unpack() Returns: * (`Any`) — A native JavaScript value, corresponding to the type of variant A convenience for unpacking a single level of a [`GLib.Variant`][gvariant]. ### GLib.Variant.deepUnpack() Returns: * (`Any`) — A native JavaScript value, corresponding to the type of variant A convenience for unpacking a [`GLib.Variant`][gvariant] and its children, but only up to one level. ### GLib.Variant.recursiveUnpack() Returns: * (`Any`) — A native JavaScript value, corresponding to the type of variant A convenience for recursively unpacking a [`GLib.Variant`][gvariant] and all its descendants. Note that this method will unpack source values (e.g. `uint32`) to native values (e.g. `Number`), so some type information may not be fully represented in the result. ### GLib.MainLoop.runAsync() Returns: * (`Promise`) Similar to [`GLib.MainLoop.run`][glib-mainloop-run] but return a Promise which resolves when the main loop ends, instead of blocking while the main loop runs. This helps avoid the situation where Promises never resolved if you didn't run the main loop inside a callback. [glib-mainloop-run]: https://gjs-docs.gnome.org/glib20/glib.mainloop#method-run ## [GObject](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/GObject.js) > See also: The [Mapping][mapping] documentation, for general GObject usage The `GObject` override mostly provides aliases for constants and types normally found in GObject, as well as [`GObject.registerClass()`](#gobject-registerclass) for registering subclasses. [mapping]: https://gjs-docs.gnome.org/gjs/mapping.md ### GObject.Object.$gtype > See also: [GType Objects][gtype-objects] Type: * `GObject.Type` The `GObject.Type` object for the given type. This is the proper way to find the GType given an object instance or a class. For a class, [`GObject.type_from_name()`][gtypefromname] can also be used. ```js // expected output: [object GType for 'GObject'] // GType for an object class log(GObject.Object.$gtype); // GType for an object instance const objectInstance = GObject.Object.new() log(objectInstance.constructor.$gtype); // GType from C type name log(GObject.type_from_name('GObject')); ``` Note that the GType name for user-defined subclasses will be prefixed with `Gjs_` (i.e. `Gjs_MyObject`), unless the `GTypeName` class property is specified when calling [`GObject.registerClass()`](#gobject-registerclass). Some applications, notably GNOME Shell, may set [`GObject.gtypeNameBasedOnJSPath`](#gobject-gtypenamebasedonjspath) to `true` which changes the prefix from `Gjs_` to `Gjs_`. For example, the GNOME Shell class `Notification` in `ui/messageTray.js` has the GType name `Gjs_ui_messageTray_Notification`. [gtypefromname]: https://gjs-docs.gnome.org/gobject20/gobject.type_from_name [gtype-objects]: https://gjs-docs.gnome.org/gjs/mapping.md#gtype-objects ### GObject.registerClass(metaInfo, klass) Type: * Static Parameters: * metaInfo (`Object`) — An optional dictionary of class properties * klass (`class`) — A JavaScript class expression Returns: * (`GObject.Class`) — A registered `GObject.Class` Registers a JavaScript class expression with the GObject type system. This function supports both a two-argument and one-argument form. In the two-argument form, the first argument is an object with meta info such as properties and signals. The second argument is the class expression for the class itself. ```js var MyObject = GObject.registerClass({ GTypeName: 'MyObject', Properties: { ... }, Signals: { ... }, }, class MyObject extends GObject.Object { constructor() { ... } }); ``` In the one-argument form, the meta info object is omitted and only the class expression is required. ```js var MyObject = GObject.registerClass( class MyObject extends GObject.Object { constructor() { ... } }); ``` See the [GObject Tutorial][gobject-subclassing] for examples of subclassing GObject and declaring class properties. [gobject-subclassing]: https://gjs.guide/guides/gobject/subclassing.html#subclassing-gobject ### GObject.ParamSpec The `GObject` override contains aliases for the various `GParamSpec` types, which are used when defining properties for a subclass. Be aware that the arguments for `flags` and default values are reversed: ```js // Original function const pspec1 = GObject.param_spec_boolean('property1', 'nick', 'blurb', true, // default value GObject.ParamFlags.READABLE); // flags // GJS alias const pspec2 = GObject.ParamSpec.boolean('property2', 'nick', 'blurb', GObject.ParamFlags.READABLE, // flags true); // default value ``` ### GObject Signal Matches This is an object passed to a number of signal matching functions. It has three properties: * signalId (`Number`) — A signal ID. Note that this is the signal ID, not a handler ID as returned from `GObject.Object.connect()`. * detail (`String`) — A signal detail, such as `prop` in `notify::prop`. * func (`Function`) — A signal callback function. For example: ```js // Note that `Function.prototype.bind()` creates a new function instance, so // you must pass the correct instance to successfully match a handler function notifyCallback(obj, pspec) { log(pspec.name); } const objectInstance = new GObject.Object(); const handlerId = objectInstance.connect('notify::property-name', notifyCallback); const result = GObject.signal_handler_find(objectInstance, { detail: 'property-name', func: notifyCallback, }); console.assert(result === handlerId); ``` ### GObject.Object.connect(name, callback) > See also: [GObject Signals Tutorial][gobject-signals-tutorial] Parameters: * name (`String`) — A detailed signal name * callback (`Function`) — A callback function Returns: * (`Number`) — A signal handler ID Connects a callback function to a signal for a particular object. The first argument of the callback will be the object emitting the signal, while the remaining arguments are the signal parameters. The handler will be called synchronously, before the default handler of the signal. `GObject.Object.emit()` will not return control until all handlers are called. For example: ```js // A signal connection (emitted when any property changes) let handler1 = obj.connect('notify', (obj, pspec) => { log(`${pspec.name} changed on ${obj.constructor.$gtype.name} object`); }); // A signal name with detail (emitted when "property-name" changes) let handler2 = obj.connect('notify::property-name', (obj, pspec) => { log(`${pspec.name} changed on ${obj.constructor.$gtype.name} object`); }); ``` [gobject-signals-tutorial]: https://gjs.guide/guides/gobject/basics.html#signals ### GObject.Object.connect_after(name, callback) > See also: [GObject Signals Tutorial][gobject-signals-tutorial] Parameters: * name (`String`) — A detailed signal name * callback (`Function`) — A callback function Returns: * (`Number`) — A signal handler ID Connects a callback function to a signal for a particular object. The first argument of the callback will be the object emitting the signal, while the remaining arguments are the signal parameters. The handler will be called synchronously, after the default handler of the signal. [gobject-signals-tutorial]: https://gjs.guide/guides/gobject/basics.html#signals ### GObject.Object.connect_object(name, callback, gobject, flags) > See also: [GObject Signals Tutorial][gobject-signals-tutorial] Parameters: * name (`String`) — A detailed signal name * callback (`Function`) — A callback function * gobject (`GObject.Object`) — A [`GObject.Object`][gobject] instance * flags (`GObject.ConnectFlags`) — Flags Returns: * (`Number`) — A signal handler ID Connects a callback function to a signal for a particular object. The `gobject` parameter is used to limit the lifetime of the connection. When the object is destroyed, the callback will be disconnected automatically. The `gobject` parameter is not otherwise used. The first argument of the callback will be the object emitting the signal, while the remaining arguments are the signal parameters. If `GObject.ConnectFlags.AFTER` is specified in `flags`, the handler will be called after the default handler of the signal. Otherwise, it will be called before. `GObject.ConnectFlags.SWAPPED` is not supported and its use will throw an exception. [gobject-signals-tutorial]: https://gjs.guide/guides/gobject/basics.html#signals ### GObject.Object.disconnect(id) > See also: [GObject Signals Tutorial][gobject-signals-tutorial] Parameters: * id (`Number`) — A signal handler ID Disconnects a handler from an instance so it will not be called during any future or currently ongoing emissions of the signal it has been connected to. The `id` has to be a valid signal handler ID, connected to a signal of the object. For example: ```js let handlerId = obj.connect('notify', (obj, pspec) => { log(`${pspec.name} changed on ${obj.constructor.$gtype.name} object`); }); if (handlerId) { obj.disconnect(handlerId); handlerId = null; } ``` [gobject-signals-tutorial]: https://gjs.guide/guides/gobject/basics.html#signals ### GObject.Object.emit(name, ...args) > See also: [GObject Signals Tutorial][gobject-signals-tutorial] Parameters: * name (`String`) — A detailed signal name * args (`Any`) — Signal parameters Returns: * (`Any`|`undefined`) — Optional return value Emits a signal. Signal emission is done synchronously. The method will only return control after all handlers are called or signal emission was stopped. In some cases, signals expect a return value (usually a `Boolean`). The effect of the return value will be described in the documentation for the signal. For example: ```js // Emitting a signal obj.emit('signal-name', arg1, arg2); // Emitting a signal that returns a boolean if (obj.emit('signal-name', arg1, arg2)) log('signal emission was handled!'); else log('signal emission was unhandled!'); ``` [gobject-signals-tutorial]: https://gjs.guide/guides/gobject/basics.html#signals ### GObject.signal_handler_find(instance, match) > Note: This function has a different signature that the original Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * match (`Object`) — A dictionary of properties to match Returns: * (`Number`|`BigInt`|`Object`|`null`) — A valid non-0 signal handler ID for a successful match. Finds the first signal handler that matches certain selection criteria. The criteria are passed as properties of a match object. The match object has to be non-empty for successful matches. If no handler was found, a falsy value is returned. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_block_matched(instance, match) > Note: This function has a different signature that the original Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * match (`Object`) — A dictionary of properties to match Returns: * (`Number`) — The number of handlers that matched. Blocks all handlers on an instance that match certain selection criteria. The criteria are passed as properties of a match object. The match object has to have at least `func` for successful matches. If no handlers were found, 0 is returned, the number of blocked handlers otherwise. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_unblock_matched(instance, match) > Note: This function has a different signature that the original Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * match (`Object`) — A dictionary of properties to match Returns: * (`Number`) — The number of handlers that matched. Unblocks all handlers on an instance that match certain selection criteria. The criteria are passed as properties of a match object. The match object has to have at least `func` for successful matches. If no handlers were found, 0 is returned, the number of blocked handlers otherwise. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_disconnect_matched(instance, match) > Note: This function has a different signature that the original Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * match (`Object`) — A dictionary of properties to match Returns: * (`Number`) — The number of handlers that matched. Disconnects all handlers on an instance that match certain selection criteria. The criteria are passed as properties of a match object. The match object has to have at least `func` for successful matches. If no handlers were found, 0 is returned, the number of blocked handlers otherwise. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_block_by_func(instance, func) Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * func (`Function`) — The callback function Returns: * (`Number`) — The number of handlers that matched. Blocks all handlers on an instance that match `func`. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_unblock_by_func(instance, func) Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * func (`Function`) — The callback function Returns: * (`Number`) — The number of handlers that matched. Unblocks all handlers on an instance that match `func`. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_disconnect_by_func(instance, func) Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * func (`Function`) — The callback function Returns: * (`Number`) — The number of handlers that matched. Disconnects all handlers on an instance that match `func`. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.signal_handlers_disconnect_by_data(instance, data) > Warning: This function does not work in GJS Type: * Static Parameters: * instance (`GObject.Object`) — A [`GObject.Object`][gobject] instance * data (`void`) — The callback data Returns: * (`Number`) — The number of handlers that matched. Disconnects all handlers on an instance that match `data`. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### GObject.gtypeNameBasedOnJSPath > Warning: This property is for advanced use cases. Never set this property in > a GNOME Shell Extension, or a loadable script in a GJS application. Type: * `Boolean` Flags: * Read / Write The property controls the default prefix for the [GType name](#gtype-objects) of a user-defined class, if not set manually. By default this property is set to `false`, and any class that does not define `GTypeName` when calling [`GObject.registerClass()`](#gobject-registerclass) will be assigned a GType name of `Gjs_`. If set to `true`, the prefix will include the import path, which can avoid conflicts if the application has multiple modules containing classes with the same name. For example, the GNOME Shell class `Notification` in `ui/messageTray.js` has the GType name `Gjs_ui_messageTray_Notification`. ## [Gtk](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/core/overrides/Gtk.js) Mostly GtkBuilder/composite template implementation. May be useful as a reference. > Reminder: You should specify a version prior to importing a library with > multiple versions. ```js // GTK3 import Gtk from 'gi://Gtk?version=3.0'; // GTK4 import Gtk from 'gi://Gtk?version=4.0'; ``` ### Gtk.Container.list_child_properties(widget) > Note: This GTK3 function requires different usage in GJS than other languages Type: * Static Parameters: * widget (`Gtk.Container`) — A [`Gtk.Container`][gtkcontainer] Returns: * (`Array(GObject.ParamSpec)`) — A list of the container's child properties as [`GObject.ParamSpec`][gparamspec] objects Returns all child properties of a container class. Note that in GJS, this is a static function on [`Gtk.Container`][gtkcontainer] that must be called with `Function.prototype.call()`, either on a widget instance or a widget class: ```js // Calling on a widget instance const box = new Gtk.Box(); const properties = Gtk.Container.list_child_properties.call(box); for (let pspec of properties) log(pspec.name); // Calling on a widget class const properties = Gtk.Container.list_child_properties.call(Gtk.Box); for (let pspec of properties) log(pspec.name); ``` For more information about this function, see the upstream documentation for [gtk_container_class_list_child_properties()][gtkcontainerclasslistchildproperties]. [gtkwidget]: https://gjs-docs.gnome.org/gtk30/gtk.widget [gtkcontainer]: https://gjs-docs.gnome.org/gtk30/gtk.container [gtkcontainerclasslistchildproperties]: https://docs.gtk.org/gtk3/class_method.Container.list_child_properties.html [gparamspec]: https://gjs-docs.gnome.org/gobject20/gobject.paramspec ## GObject Introspection > See also: [ECMAScript Modules][esmodules] The `gi` override is a wrapper for `libgirepository` for importing native GObject-Introspection libraries. [esmodules]: https://gjs-docs.gnome.org/gjs/esmodules.md #### Import ```js import gi from 'gi'; ``` ### gi.require(library, version) Type: * Static Parameters: * library (`String`) — A introspectable library * version (`String`) — A library version, if applicable > New in GJS 1.72 (GNOME 42) Loads a native gobject-introspection library. Version is required if more than one version of a library is installed. You can also import libraries through the `gi://` URL scheme. This function is only intended to be used when you want to import a library conditionally, since top-level import statements are resolved statically. ## Legacy Imports Prior to the introduction of [ES Modules](ESModules.md), GJS had its own import system. **imports** is a global object that you can use to import any js file or GObject Introspection lib as module, there are 4 special properties of **imports**: * `searchPath` An array of path that used to look for files, if you want to prepend a path you can do something like `imports.searchPath.unshift(myPath)`. * `__modulePath__` * `__moduleName__` * `__parentModule__` These 3 properties is intended to be used internally, you should not use them. Any other properties of **imports** is treated as a module, if you access these properties, an import is attempted. Gjs try to look up a js file or directory by property name from each location in `imports.searchPath`. For `imports.foo`, if a file named `foo.js` is found, this file is executed and then imported as a module object; else if a directory `foo` is found, a new importer object is returned and its `searchPath` property is replaced by the path of `foo`. Note that any variable, function and class declared at the top level, except those declared by `let` or `const`, are exported as properties of the module object, and one js file is executed only once at most even if it is imported multiple times. cjs-128.1/doc/Package/0000775000175000017500000000000015116312211013321 5ustar fabiofabiocjs-128.1/doc/Package/Specification.md0000664000175000017500000001775215116312211016437 0ustar fabiofabioThis document aims to build a set of conventions for JS applications using GJS and GObjectIntrospection. ## Rationale It is believed that the current deployment facilities for GJS apps, ie autotools, [bash wrapper scripts](https://git.gnome.org/browse/gnome-documents/tree/src/gnome-documents.in) and [sed invocations](https://git.gnome.org/browse/gnome-documents/tree/src/Makefile.am#n26) represent a huge obstacle in making the GJS application platform palatable for newcomers. Additionally, the lack of standardization on the build system hinders the diffusion of pure JS utility / convenience modules. The goal is to create a standard packaging method for GJS, similar to Python's . The choice of keeping the autotools stems from the desire of integration with GNOME submodules such as libgd and egg-list-box. While those are temporary and will enter GTK in due time, it is still worthy for free software applications to be able to share submodules easily. Moreover, so far the autotools have the best support for generating GObjectIntrospection information, and it is sometimes necessary for JS apps to use a private helper library in a compiled language. ## Requirements * Implementation details, whenever exposed to the app developers because of limitations of the underlying tools, must be copy-pastable between packages. * The application must be fully functional when run uninstalled. In particular, it must not fail because it lacks GtkBuilder files, images, CSS or GSettings. * The application must honor `--prefix` and `--libdir` (which must be a subdirectory of `--prefix`) at configure time. * The application must not require more than `--prefix` and `--libdir` to work. * The application must be installable by a regular user, provided he has write permission in `--prefix` * The format must allow the application to be comprised of one or more JS entry points, and one or more introspection based libraries ## Prior Art * [setuptools](https://pypi.python.org/pypi/setuptools) and [distutils-extra](https://launchpad.net/python-distutils-extra) (for Python) * [Ubuntu Quickly](https://wiki.ubuntu.com/Quickly|Ubuntu Quickly) (again, for Python) * [CommonJS package format](http://wiki.commonjs.org/wiki/Packages) (only describes the package layout, and does not provide runtime services) * https://live.gnome.org/BuilDj (build system only) ## Specification The following meta variable are used throughout this document: * **${package-name}**: the fully qualified ID of the package, in DBus name format. Example: org.gnome.Weather. * **${entry-point-name}**: the fully qualified ID of an entry point, in DBus name format. Example: org.gnome.Weather.Application. This must be a sub ID of **${package-name}** * **${entry-point-path}**: the entry point ID, converted to a DBus path in the same way GApplication does it (prepend /, replace . with /) * **${package-tarname}**: the short, but unambiguous, short name of the package, such as gnome-weather * **${package-version}**: the version of the package This specification is an addition to the Gjs style guide, and it inherits all requirements. ## Package layout * The application package is expected to use autotools, or a compatible build system. In particular, it must optionally support recursive configure and recursive make. * The following directories and files in the toplevel package must exist: * **src/**: contains JS modules * **src/${entry-point-name}.src.gresource.xml**: the GResource XML for JS files for the named entry point (see below) * **src/${entry-point-name}.src.gresource**: the compiled GResource for JS files * **data/**: contains misc application data (CSS, GtkBuilder definitions, images...) * **data/${entry-point-name}.desktop**: contains the primary desktop file for the application * *(OPTIONAL)* **data/${entry-point-name}.data.gresource**: contains the primary application resource * *(OPTIONAL)* **data/${entry-point-name}.gschema.xml**: contains the primary GSettings schema * *(OPTIONAL)* **data/gschemas.compiled**: compiled version of GSettings schemas in data/, for uninstalled use * *(OPTIONAL)* **lib/**: contains sources and .la files of private shared libraries * *(OPTIONAL)* **lib/.libs**: contains the compiled (.so) version of private libraries * *(OPTIONAL)* another toplevel directory such as libgd or egg-list-box: same as lib/, but for shared submodules * **po/**: contains intltool PO files and templates; the translation domain must be ${package-name} * The package must be installed as following: * **${datadir}** must be configured as **${prefix}/share** * Arch-independent private data (CSS, GtkBuilder, GResource) must be installed in **${datadir}/${package-name}**, aka **${pkgdatadir}** * Source files must be compiled in a GResource with path **${entry-point-path}/js**, in a bundle called **${entry-point-name}.src.gresource** installed in **${pkgdatadir}** * Private libraries must be **${libdir}/${package-name}**, aka ${pkglibdir} * Typelib for private libraries must be in **${pkglibdir}/girepository-1.0** * Translations must be in **${datadir}/locale/** * Other files (launches, GSettings schemas, icons, etc) must be in their specified locations, relative to **${prefix}** and **${datadir}** ## Usage Applications complying with this specification will have one application script, installed in **${prefix}/share/${package-name}** (aka **${pkgdatadir}**), and named as **${entry-point-name}**, without any extension or mangling. Optionally, one or more symlinks will be placed in ${bindir}, pointing to the appropriate script in ${pkgdatadir} and named in a fashion more suitable for command line usage (usually ${package-tarname}). Alternatively, a script that calls "gapplication launch ${package-name}" can be used. The application itself will be DBus activated from a script called **src/${entry-point-name}**, generated from configure substitution of the following **${entry-point-name}.in**: ```sh #!@GJS@ imports.package.init({ name: "${package-name}", version: "@PACKAGE_VERSION@", prefix: "@prefix@" }); imports.package.run(${main-module}) ``` Where **${main-module}** is a module containing the `main()` function that will be invoked to start the process. This function should accept a single argument, an array of command line args. The first element in the array will be the full resolved path to the entry point itself (unlike the global ARGV variable for gjs). Also unlike ARGV, it is safe to modify this array. This `main()` function should initialize a GApplication whose id is **${entry-point-name}**, and do all the work inside the GApplication `vfunc_*` handlers. > **`[!]`** Users should refer to https://github.com/gcampax/gtk-js-app for a full example of the build environment. ## Runtime support The following API will be available to applications, through the [`package.js`](https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/modules/script/package.js) module. * `globalThis.pkg` (ie `pkg` on the global object) will provide access to the package module * `pkg.name` and `pkg.version` will return the package name and version, as passed to `pkg.init()` * `pkg.prefix`, `pkg.datadir`, `pkg.libdir` will return the installed locations of those folders * `pkg.pkgdatadir`, `pkg.moduledir`, `pkg.pkglibdir`, `pkg.localedir` will return the respective directories, or the appropriate subdirectory of the current directory if running uninstalled * `pkg.initGettext()` will initialize gettext. After calling `globalThis._`, `globalThis.C_` and `globalThis.N_` will be available * `pkg.initSubmodule(name)` will initialize a submodule named @name. It must be called before accessing the typelibs installed by that submodule * `pkg.loadResource(name)` will load and register a GResource named @name. @name is optional and defaults to ${package-name} * `pkg.require(deps)` will mark a set of dependencies on GI and standard JS modules. **@deps** is a object whose keys are repository names and whose values are API versions. If the dependencies are not satisfied, `pkg.require()` will print an error message and quit. cjs-128.1/doc/Profiling.md0000664000175000017500000000106415116312211014242 0ustar fabiofabio# Profiling ## Sysprof Typical profiling of JavaScript code is performed by passing the `--gjs` and `--no-perf` options: ```sh $ sysprof-cli --gjs --no-perf -- gjs script.js ``` This will result in a `capture.syscap` file in the current directory, which can then be reviewed in the sysprof GUI: ```sh $ sysprof capture.syscap ``` Other flags can also be combined with `--gjs` when appropriate: ```sh sysprof-cli --gjs --gtk -- gjs gtk.js ``` #### See Also * Christian Hergert's [Blog Posts on Sysprof](https://blogs.gnome.org/chergert/category/sysprof/) cjs-128.1/doc/README.md0000664000175000017500000001202215116312211013242 0ustar fabiofabio# GJS GJS is JavaScript bindings for the GNOME platform APIs. Powered by Mozilla's [SpiderMonkey][spidermonkey] JavaScript engine and [GObject Introspection][gobject-introspection], it opens the entire GNOME ecosystem to JavaScript developers. The stable version of GJS is based on the latest Extended Support Release (ESR) of SpiderMonkey. To find out when a language feature was added to GJS, review [NEWS][gjs-news] in the GitLab repository. [gobject-introspection]: https://gi.readthedocs.io [spidermonkey]: https://spidermonkey.dev/ [gjs-news]: https://gitlab.gnome.org/GNOME/gjs/raw/HEAD/NEWS ## Documentation If you are reading this file in the GJS repository, you may find it more convenient to browse and search using the [API Documentation][gjs-docs] website instead. There is documentation for GLib, GTK, Adwaita, WebKit, and many more libraries. General documentation about built-in modules and APIs is under the [GJS Topic](https://gjs-docs.gnome.org/gjs). [GJS Guide][gjs-guide] has many in-depth tutorials and examples for a number of core GNOME APIs. The repository also has [code examples][gjs-examples] and thorough coverage of language features in the [test suite][gjs-tests]. [GTK4 + GJS Book][gtk4-gjs-book] is a start to finish walkthrough for creating GTK4 applications with GJS. The [GNOME developer portal][gnome-developer] contains examples of a variety of GNOME technologies written GJS, alongside other languages you may know. [Workbench] is a code sandbox for GJS, CSS and GTK. It features live preview and a library of examples and demos. [gjs-docs]: https://gjs-docs.gnome.org/ [gjs-examples]: https://gitlab.gnome.org/GNOME/gjs/tree/HEAD/examples [gjs-tests]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/installed-tests/js [gjs-guide]: https://gjs.guide [gtk4-gjs-book]: https://rmnvgr.gitlab.io/gtk4-gjs-book/ [gnome-developer]: https://developer.gnome.org/ [workbench]: https://apps.gnome.org/app/re.sonny.Workbench/ ## Applications GJS is a great option to write applications for the GNOME Desktop. The easiest way to get started is to use [GNOME Builder][gnome-builder], start a new project and select `JavaScript` language. [gnome-builder]: https://apps.gnome.org/app/org.gnome.Builder/ Here is a non-exhaustive list of applications written in GJS: GNOME Apps * [Characters](https://gitlab.gnome.org/GNOME/gnome-characters) * [Maps](https://gitlab.gnome.org/GNOME/gnome-maps) * [Weather](https://gitlab.gnome.org/GNOME/gnome-weather) * [Extensions](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/HEAD/subprojects/extensions-app) * [Polari](https://gitlab.gnome.org/GNOME/polari) * [Tangram](https://github.com/sonnyp/Tangram) * [Flatseal](https://github.com/tchx84/Flatseal) * [Commit](https://github.com/sonnyp/commit/) * [Junction](https://github.com/sonnyp/Junction) * [Oh My SVG](https://github.com/sonnyp/OhMySVG) * [Workbench](https://github.com/sonnyp/Workbench) * [GNOME Sound Recorder](https://gitlab.gnome.org/GNOME/gnome-sound-recorder) (TypeScript) * [Zap](https://apps.gnome.org/app/fr.romainvigier.zap/) Others * [Quick Lookup](https://github.com/johnfactotum/quick-lookup) * [Foliate](https://github.com/johnfactotum/foliate) * [Clapper](https://github.com/Rafostar/clapper/) * [Almond](https://github.com/stanford-oval/almond-gnome/) * [Lobjur](https://github.com/ranfdev/Lobjur) (Clojure) * [Touché](https://github.com/JoseExposito/touche) * [Annex](https://github.com/andyholmes/annex) * [Bolso](https://github.com/felipeborges/bolso) * [Design](https://github.com/dubstar-04/Design) * [Capsule](https://gitlab.gnome.org/verdre/Capsule) * [Spiel](https://gitlab.gnome.org/feaneron/spiel) * [Retro](https://github.com/sonnyp/Retro) * [libportal test](https://github.com/flatpak/libportal/tree/main/portal-test/gtk4) * [Sticky](https://github.com/vixalien/sticky) * [Playhouse](https://github.com/sonnyp/Playhouse) * [Flatpak Manifest Editor](https://gitlab.gnome.org/feaneron/flatpak-manifest-editor) * [Forge Sparks](https://github.com/rafaelmardojai/forge-sparks) * [Diccionario de la Lengua](https://codeberg.org/rafaelmardojai/diccionario-lengua) Archived * [GNOME Books](https://gitlab.gnome.org/GNOME/gnome-books) * [GNOME Documents](https://gitlab.gnome.org/GNOME/gnome-documents) ### GNOME Shell Extensions GJS is used to write [GNOME Shell Extensions](https://extensions.gnome.org), allowing anyone to make considerable modifications to the GNOME desktop. This can also be a convenient way to prototype changes you may want to contribute to the upstream GNOME Shell project. There is documentation and tutorials specifically for extension authors at [gjs.guide/extensions](https://gjs.guide/extensions). ### Embedding GJS GJS can also be embedded in other applications, such as with GNOME Shell, to provide a powerful scripting language with support for the full range of libraries with GObject-Introspection. ## Getting Help * Discourse: https://discourse.gnome.org/ * Chat: https://matrix.to/#/#javascript:gnome.org * Issue Tracker: https://gitlab.gnome.org/GNOME/gjs/issues * StackOverflow: https://stackoverflow.com/questions/tagged/gjs cjs-128.1/doc/Signals.md0000664000175000017500000000456415116312211013721 0ustar fabiofabio# Signals The `Signals` module provides a GObject-like signal framework for native JavaScript classes and objects. Example usage: ```js const Signals = imports.signals; // Apply signal methods to a class prototype var ExampleObject = class { emitExampleSignal () { this.emit('exampleSignal', 'stringArg', 42); } } Signals.addSignalMethods(ExampleObject.prototype); const obj = new ExampleObject(); // Connect to a signal const handlerId = obj.connect('exampleSignal', (obj, stringArg, intArg) => { // Note that `this` always refers `globalThis` in a Signals callback }); // Disconnect a signal handler obj.disconnect(handlerId); ``` #### Import > Attention: This module is not available as an ECMAScript Module The `Signals` module is available on the global `imports` object: ```js const Signals = imports.signals; ``` ### Signals.addSignalMethods(object) Type: * Static Parameters: * object (`Object`) — A JavaScript object Applies the `Signals` convenience methods to an `Object`. Generally, this is called on an object prototype, but may also be called on an object instance. ### connect(name, callback) > Warning: Unlike GObject signals, `this` within a signal callback will always > refer to the global object (ie. `globalThis`). Parameters: * name (`String`) — A signal name * callback (`Function`) — A callback function Returns: * (`Number`) — A handler ID Connects a callback to a signal for an object. Pass the returned ID to `disconnect()` to remove the handler. If `callback` returns `true`, emission will stop and no other handlers will be invoked. ### disconnect(id) Parameters: * id (`Number`) — The ID of the handler to be disconnected Disconnects a handler for a signal. ### disconnectAll() Disconnects all signal handlers for an object. ### emit(name, ...args) Parameters: * name (`String`) — A signal name * args (`Any`) — Any number of arguments, of any type Emits a signal for an object. Emission stops if a signal handler returns `true`. Unlike GObject signals, it is not necessary to declare signals or define their signature. Simply call `emit()` with whatever signal name you wish, with whatever arguments you wish. ### signalHandlerIsConnected(id) Parameters: * id (`Number`) — The ID of the handler to be disconnected Returns: * (`Boolean`) — `true` if connected, or `false` if not Checks if a handler ID is connected. cjs-128.1/doc/SpiderMonkey_Memory.md0000664000175000017500000001536615116312211016264 0ustar fabiofabio# Memory management in SpiderMonkey When writing JavaScript extensions in C++, we have to understand and be careful about memory management. This document only applies to C++ code using the jsapi.h API. If you simply write a GObject-style library and describe it via gobject-introspection typelib, there is no need to understand garbage collection details. ## Mark-and-sweep collector As background, SpiderMonkey uses mark-and-sweep garbage collection. (see [this page][1] for one explanation, if not familiar with this.) This is a good approach for "embeddable" interpreters, because unlike say the Boehm GC, it doesn't rely on any weird hacks like scanning the entire memory or stack of the process. The collector only has to know about stuff that the language runtime created itself. Also, mark-and-sweep is simple to understand when working with the embedding API. ## Representation of objects An object has two forms. * `JS::Value` is a type-tagged version, think of `GValue` (though it is much more efficient) * inside a `JS::Value` can be one of: a 32-bit integer, a boolean, a double, a `JSString*`, a `JS::Symbol*`, or a `JSObject*`. `JS::Value` is a 64 bits-wide union. Some of the bits are a type tag. However, don't rely on the layout of `JS::Value`, as it may change between API versions. You check the type tag with the methods `val.isObject()`, `val.isInt32()`, `val.isDouble()`, `val.isString()`, `val.isBoolean()`, `val.isSymbol()`. Use `val.isNull()` and `val.isUndefined()` rather than comparing `val == JSVAL_NULL` and `val == JSVAL_VOID` to avoid an extra memory access. null does not count as an object, so `val.isObject()` does not return true for null. This contrasts with the behavior of `JSVAL_IS_OBJECT(val)`, which was the previous API, but this was changed because the object-or-null behavior was a source of bugs. If you still want this behaviour use `val.isObjectOrNull()`. The methods `val.toObject()`, `val.toInt32()`, etc. are just accessing the appropriate members of the union. The jsapi.h header is pretty readable, if you want to learn more. Types you see in there not mentioned above, such as `JSFunction*`, would show up as an object - `val.isObject()` would return true. From a `JS::Value` perspective, everything is one of object, string, symbol, double, int, boolean, null, or undefined. ## Value types vs. allocated types; "gcthing" For integers, booleans, doubles, null, and undefined there is no pointer. The value is just part of the `JS::Value` union. So there is no way to "free" these, and no way for them to be finalized or become dangling. The importance is: these types just get ignored by the garbage collector. However, strings, symbols, and objects are all allocated pointers that get finalized eventually. These are what garbage collection applies to. The API refers to these allocated types as "GC things." The macro `val.toGCThing()` returns the value part of the union as a pointer. `val.isGCThing()` returns true for string, object, symbol, null; and false for void, boolean, double, integer. ## Tracing The general rule is that SpiderMonkey has a set of GC roots. To do the garbage collection, it finds all objects accessible from those roots, and finalizes all objects that are not. So if you have a `JS::Value` or `JSObject*`/`JSString*`/`JSFunction*`/`JS::Symbol*` somewhere that is not reachable from one of SpiderMonkey's GC roots - say, declared on the stack or in the private data of an object - that will not be found. SpiderMonkey may try to finalize this object even though you have a reference to it. If you reference JavaScript objects from your custom object, you have to use `JS::Heap` and set the `JSCLASS_MARK_IS_TRACE` flag in your JSClass, and define a trace function in the class struct. A trace function just invokes `JS::TraceEdge()` to tell SpiderMonkey about any objects you reference. See [JSTraceOp docs][2]. Tracing doesn't add a GC thing to the GC root set! It just notifies the interpreter that a thing is reachable from another thing. ## Global roots The GC roots include anything you have declared with `JS::Rooted` and the global object set on each `JSContext*`. You can also manually add roots with [`JS::PersistentRooted()`][3]. Anything reachable from any of these root objects will not be collected. `JS::PersistentRooted` pins an object in memory forever until it is destructed, so be careful of leaks. Basically `JS::PersistentRooted` changes memory management of an object to manual mode. Note that the wrapped T in `JS::PersistentRooted` is the location of your value, not the value itself. That is, a `JSObject**` or `JS::Value*`. Some implications are: * the location can't go away (don't use a stack address that will vanish before the `JS::PersistentRooted` is destructed, for example) * the root is keeping "whatever is at the location" from being collected, not "whatever was originally at the location" ## Local roots Here is the trickier part. If you create an object, say: ```c++ JSObject* obj = JS_NewPlainObject(cx); ``` `obj` is NOT now referenced by any other object. If the GC ran right away, `obj` would be collected. This is what `JS::Rooted` is for, and its specializations `JS::RootedValue`, `JS::RootedObject`, etc. `JS::Rooted` adds its wrapped `T` to the GC root set, and removes it when the `JS::Rooted` goes out of scope. Note that `JS::Rooted` can only be used on the stack. For optimization reasons, roots that are added with `JS::Rooted` must be removed in LIFO order, and the stack scoping ensures that. Any SpiderMonkey APIs that can cause a garbage collection will force you to use `JS:Rooted` by taking a `JS::Handle` instead of a bare GC thing. `JS::Handle` can only be created from `JS::Rooted. So instead of the above code, you would write ```c++ JS::RootedObject obj(cx, JS_NewPlainObject(cx)); ``` ### JSFunctionSpec and extra local roots When SpiderMonkey is calling a native function, it will pass in an argv of `JS::Value`. It already has to add all the argv values as GC roots. The "extra local roots" feature tells SpiderMonkey to stick some extra slots on the end of argv that are also GC roots. You can then assign to `argv[MAX(min_args, actual_argc)]` and whatever you put in there won't get garbage collected. This is kind of a confusing and unreadable hack IMO, though it is probably efficient and thus justified in certain cases. I don't know really. ## More tips For another attempt to explain all this, see [Rooting Guide from Mozilla.org][4]. [1]: http://www.brpreiss.com/books/opus5/html/page424.html [2]: http://developer.mozilla.org/en/docs/JSTraceOp [3]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS::PersistentRooted [4]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/GC_Rooting_Guide "GC" cjs-128.1/doc/Style_Guide.md0000664000175000017500000001007215116312211014525 0ustar fabiofabio# Coding style Our goal is to have all JavaScript code in GNOME follow a consistent style. In a dynamic language like JavaScript, it is essential to be rigorous about style (and unit tests), or you rapidly end up with a spaghetti-code mess. ## Linter GJS includes an eslint configuration file, `.eslintrc.yml`. There is an additional one that applies to test code in `installed-tests/js/.eslintrc.yml`. We recommend using this for your project, with any modifications you need that are particular to your project. In most editors you can set up eslint to run on your code as you type. Or you can set it up as a git commit hook. In any case if you contribute code to GJS, eslint will check the code in your merge request. The style guide for JS code in GJS is, by definition, the eslint config file. This file only contains conventions that the linter can't catch. ## Imports Use CamelCase when importing modules to distinguish them from ordinary variables, e.g. ```js const Big = imports.big; const {GLib} = imports.gi; ``` ## Variable declaration Always use `const` or `let` when block scope is intended. In almost all cases `const` is correct if you don't reassign the variable, and otherwise `let`. In general `var` is only needed for variables that you are exporting from a module. ```js // Iterating over an array for (let i = 0; i < 10; ++i) { let foo = bar(i); } // Iterating over an object's properties for (let prop in someobj) { ... } ``` If you don't use `let` or `const` then the variable is added to function scope, not the for loop block scope. See [What's new in JavaScript 1.7][1] A common case where this matters is when you have a closure inside a loop: ```js for (let i = 0; i < 10; ++i) { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, function () { log(`number is: ${i}`); }); } ``` If you used `var` instead of `let` it would print "10" a bunch of times. ## `this` in closures `this` will not be captured in a closure; `this` is relative to how the closure is invoked, not to the value of this where the closure is created, because `this` is a keyword with a value passed in at function invocation time, it is not a variable that can be captured in closures. To solve this, use `Function.bind()`, or arrow functions, e.g.: ```js const closure = () => { this._fnorbate(); }; // or const closure = function() { this._fnorbate() }.bind(this); ``` A more realistic example would be connecting to a signal on a method of a prototype: ```js const MyPrototype = { _init() { fnorb.connect('frobate', this._onFnorbFrobate.bind(this)); }, _onFnorbFrobate(fnorb) { this._updateFnorb(); }, }; ``` ## Object literal syntax JavaScript allows equivalently: ```js const foo = {'bar': 42}; const foo = {bar: 42}; ``` and ```js const b = foo['bar']; const b = foo.bar; ``` If your usage of an object is like an object, then you're defining "member variables." For member variables, use the no-quotes no-brackets syntax, that is, `{bar: 42}` and `foo.bar`. If your usage of an object is like a hash table (and thus conceptually the keys can have special chars in them), don't use quotes, but use brackets, `{bar: 42}`, `foo['bar']`. ## Variable naming - We use javaStyle variable names, with CamelCase for type names and lowerCamelCase for variable and method names. However, when calling a C method with underscore-based names via introspection, we just keep them looking as they do in C for simplicity. - Private variables, whether object member variables or module-scoped variables, should begin with `_`. - True global variables should be avoided whenever possible. If you do create them, the variable name should have a namespace in it, like `BigFoo` - When you assign a module to an alias to avoid typing `imports.foo.bar` all the time, the alias should be `const TitleCase` so `const Bar = imports.foo.bar;` - If you need to name a variable something weird to avoid a namespace collision, add a trailing `_` (not leading, leading `_` means private). [1]: http://developer.mozilla.org/en/docs/index.php?title=New_in_JavaScript_1.7&printable=yes#Block_scope_with_let cjs-128.1/doc/System.md0000664000175000017500000001422315116312211013576 0ustar fabiofabio# System The `System` module provides common low-level facilities such as access to process arguments and `exit()`, as well as a number of useful functions and properties for debugging. Note that the majority of the functions and properties in this module should not be used in normal operation of a GJS application. #### Import When using ESModules: ```js import System from 'system'; ``` When using legacy imports: ```js const System = imports.system; ``` ### System.addressOf(object) > See also: [`System.addressOfGObject()`](#system-addressofgobject) Type: * Static Parameters: * object (`Object`) — Any `Object` Returns: * (`String`) — A hexadecimal string (e.g. `0xb4f170f0`) Return the memory address of any object as a string. This is the address of memory being managed by the JavaScript engine, which may represent a wrapper around memory elsewhere. > Caution, don't use this as a unique identifier! > > JavaScript's garbage collector can move objects around in memory, or > deduplicate identical objects, so this value may change during the execution > of a program. ### System.addressOfGObject(gobject) > See also: [`System.addressOf()`](#system-addressof) Type: * Static Parameters: * gobject (`GObject.Object`) — Any [`GObject.Object`][gobject]-derived instance Returns: * (`String`) — A hexadecimal string (e.g. `0xb4f170f0`) > New in GJS 1.58 (GNOME 3.34) Return the memory address of any GObject as a string. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### System.breakpoint() > Warning: Using this function in code run outside of GDB will abort the process Type: * Static Inserts a breakpoint instruction into the code. With `System.breakpoint()` calls in your code, a GJS program can be debugged by running it in GDB: ``` gdb --args gjs script.js ``` Once GDB has started, you can start the program with `run`. When the debugger hits a breakpoint it will pause execution of the process and return to the prompt. You can then use the standard `backtrace` command to print a C++ stack trace, or use `call gjs_dumpstack()` to print a JavaScript stack trace: ``` (gdb) run Starting program: /usr/bin/gjs -m script.js ... Thread 1 "gjs" received signal SIGTRAP, Trace/breakpoint trap. (gdb) call gjs_dumpstack() == Stack trace for context 0x5555555b7180 == #0 555555640548 i file:///path/to/script.js:4 (394b8c3cc060 @ 12) #1 5555556404c8 i file:///path/to/script.js:7 (394b8c3cc0b0 @ 6) #2 7fffffffd3a0 b self-hosted:2408 (394b8c3a9650 @ 753) #3 5555556403e8 i self-hosted:2355 (394b8c3a9600 @ 375) (gdb) ``` To continue executing the program, you can use the `continue` (or `cont`) to resume the process and debug further. Remember that if you run the program outside of GDB, it will abort at the breakpoint, so make sure to remove any calls to `System.breakpoint()` when you're done debugging. ### System.clearDateCaches() Type: * Static Clears the timezone cache. This is a workaround for SpiderMonkey [Bug #1004706][bug-1004706]. [bug-1004706]: https://bugzilla.mozilla.org/show_bug.cgi?id=1004706 ### System.dumpHeap(path) See also: The [`heapgraph`][heapgraph] utility in the GJS repository Type: * Static Parameters: * path (`String`) — Optional file path Dump a representation of internal heap memory. If `path` is not given, GJS will write the contents to `stdout`. [heapgraph]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/tools/heapgraph.md ### System.dumpMemoryInfo(path) Type: * Static Parameters: * path (`String`) — Optional file path > New in GJS 1.70 (GNOME 41) Dump internal garbage collector statistics. If `path` is not given, GJS will write the contents to `stdout`. Example output: ```json { "gcBytes": 794624, "gcMaxBytes": 4294967295, "mallocBytes": 224459, "gcIsHighFrequencyMode": true, "gcNumber": 1, "majorGCCount": 1, "minorGCCount": 1, "sliceCount": 1, "zone": { "gcBytes": 323584, "gcTriggerBytes": 42467328, "gcAllocTrigger": 36097228.8, "mallocBytes": 120432, "mallocTriggerBytes": 59768832, "gcNumber": 1 } } ``` ### System.exit(code) Type: * Static Parameters: * code (`Number`) — An exit code This works the same as C's `exit()` function; exits the program, passing a certain error code to the shell. The shell expects the error code to be zero if there was no error, or non-zero (any value you please) to indicate an error. This value is used by other tools such as `make`; if `make` calls a program that returns a non-zero error code, then `make` aborts the build. ### System.gc() Type: * Static Run the garbage collector. ### System.programArgs Type: * `Array(String)` > New in GJS 1.68 (GNOME 40) A list of arguments passed to the current process. This is effectively an alias for the global `ARGV`, which is misleading in that it is not equivalent to the platform's `argv`. ### System.programInvocationName Type: * `String` > New in GJS 1.68 (GNOME 40) This property contains the name of the script as it was invoked from the command line. In C and other languages, this information is contained in the first element of the platform's equivalent of `argv`, but GJS's `ARGV` only contains the subsequent command-line arguments. In other words, `ARGV[0]` in GJS is the same as `argv[1]` in C. For example, passing ARGV to a `Gio.Application`/`Gtk.Application` (See also: [examples/gtk-application.js][example-application]): ```js import Gtk from 'gi://Gtk?version=3.0'; import System from 'system'; const myApp = new Gtk.Application(); myApp.connect("activate", () => log("activated")); myApp.run([System.programInvocationName, ...ARGV]); ``` [example-application]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/examples/gtk-application.js ### System.programPath Type: * `String` > New in GJS 1.68 (GNOME 40) The full path of the executed program. ### System.refcount(gobject) Type: * Static Parameters: * gobject (`GObject.Object`) — A [`GObject.Object`][gobject] Return the reference count of any GObject-derived type. When an object's reference count is zero, it is cleaned up and erased from memory. [gobject]: https://gjs-docs.gnome.org/gobject20/gobject.object ### System.version Type: * `String` This property contains version information about GJS. cjs-128.1/doc/Testing.md0000664000175000017500000000143015116312211013723 0ustar fabiofabio# Testing Testing infrastructure for GJS code is unfortunately not as complete as other languages, and help in the area would be a greatly appreciated contribution to the community. ## Jasmine GJS [Jasmine GJS][jasmine-gjs] is a fork of the Jasmine testing framework, adapted for GJS and the GLib event loop. See the [Jasmine Documentation][jasmine-doc] and the [GJS test suite][gjs-tests] for examples. [jasmine-doc]: https://jasmine.github.io/pages/docs_home.html [jasmine-gjs]: https://github.com/ptomato/jasmine-gjs [gjs-tests]: https://gitlab.gnome.org/GNOME/gjs/blob/HEAD/installed-tests/js ## jsUnit > Deprecated: Use [Jasmine GJS](#jasmine-gjs) instead The `jsUnit` module was originally used as the testing framework in GJS. It has long been deprecated in favour of Jasmine. cjs-128.1/doc/Timers.md0000664000175000017500000000362715116312211013563 0ustar fabiofabio# Timers GJS implements the [WHATWG Timers][whatwg-timers] specification, with some changes to accommodate the GLib event loop. In particular, the returned value of `setInterval()` and `setTimeout()` is not a `Number`, but a [`GLib.Source`][gsource]. #### Import The functions in this module are available globally, without import. [whatwg-timers]: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers [gsource]: https://gjs-docs.gnome.org/glib20/glib.source ### setInterval(handler, timeout, ...arguments) Type: * Static Parameters: * handler (`Function`) — The callback to invoke * timeout (`Number`) — Optional interval in milliseconds * arguments (`Array(Any)`) — Optional arguments to pass to `handler` Returns: * (`GLib.Source`) — The identifier of the repeated action > New in GJS 1.72 (GNOME 42) Schedules a timeout to run `handler` every `timeout` milliseconds. Any `arguments` are passed straight through to the `handler`. ### clearInterval(id) Type: * Static Parameters: * id (`GLib.Source`) — The identifier of the interval you want to cancel. > New in GJS 1.72 (GNOME 42) Cancels the timeout set with `setInterval()` or `setTimeout()` identified by `id`. ### setTimeout(handler, timeout, ...arguments) Type: * Static Parameters: * handler (`Function`) — The callback to invoke * timeout (`Number`) — Optional timeout in milliseconds * arguments (`Array(Any)`) — Optional arguments to pass to `handler` Returns: * (`GLib.Source`) — The identifier of the repeated action > New in GJS 1.72 (GNOME 42) Schedules a timeout to run `handler` after `timeout` milliseconds. Any `arguments` are passed straight through to the `handler`. ### clearTimeout(id) Type: * Static Parameters: * id (`GLib.Source`) — The identifier of the timeout you want to cancel. > New in GJS 1.72 (GNOME 42) Cancels the timeout set with `setTimeout()` or `setInterval()` identified by `id`. cjs-128.1/doc/Understanding-SpiderMonkey-code.md0000664000175000017500000000536215116312211020442 0ustar fabiofabio## Basics - SpiderMonkey is the Javascript engine from Mozilla Firefox. It's also known as "mozjs" in most Linux distributions, and sometimes as "JSAPI" in code. - Like most browsers' JS engines, SpiderMonkey works standalone, which is what allows GJS to work. In Mozilla terminology, this is known as "embedding", and GJS is an "embedder." - Functions that start with `JS_` or `JS::`, or types that start with `JS`, are part of the SpiderMonkey API. - Functions that start with `js_` or `js::` are part of the "JS Friend" API, which is a section of the SpiderMonkey API which is supposedly less stable. (Although SpiderMonkey isn't API stable in the first place.) - We use the SpiderMonkey from the ESR (Extended Support Release) of Firefox. These ESRs are released approximately once a year. - Since ESR 24, the official releases of standalone SpiderMonkey have fallen by the wayside. (Arguably, that was because nobody, including us, was using them.) The SpiderMonkey team may make official releases again sometime, but it's a low priority. - When reading GJS code, to quickly find out what a SpiderMonkey API function does, you can go to https://searchfox.org/ and search for it. This is literally faster than opening `jsapi.h` in your editor, and you can click through to other functions, and find everywhere a function is used. - Don't trust the wiki on MDN as documentation for SpiderMonkey, as it is mostly out of date and can be quite misleading. ## Coding conventions - Most API functions take a `JSContext *` as their first parameter. This context contains the state of the JS engine. - `cx` stands for "context." - Many API functions return a `bool`. As in many other APIs, these should return `true` for success and `false` for failure. - Specific to SpiderMonkey is the convention that if an API function returns `false`, an exception should have been thrown (a JS exception, not a C++ exception, which would terminate the program!) This is also described as "an exception should be _pending_ on `cx`". Likewise, if the function returns `true`, an exception should not be pending. - There are two ways to violate that condition: - Returning `false` with no exception pending. This is interpreted as an "uncatchable" exception, and it's used for out-of-memory and killing scripts within Firefox, for example. In GJS we use it to implement `System.exit()`. - Returning `true` while an exception is pending. This can easily happen by forgetting to check the return value of a SpiderMonkey function, and is a programmer error but not too serious. It will probably cause some warnings. - Likewise if an API function returns a pointer such as `JSObject*` (this is less common), the convention is that it should return `nullptr` on failure, in which case an exception should be pending. cjs-128.1/doc/cairo.md0000664000175000017500000001174315116312211013413 0ustar fabiofabio# Cairo The `Cairo` module is a set of custom bindings for the [cairo][cairo] 2D graphics library. Cairo is used by GTK, Clutter, Mutter and others for drawing shapes, text, compositing images and performing affine transformations. The GJS bindings for cairo follow the C API pretty closely, although some of the less common functions are not available yet. In spite of this, the bindings are complete enough that the upstream [cairo documentation][cairo-docs] may be helpful to those new to using Cairo. [cairo]: https://www.cairographics.org/ [cairo-docs]: https://www.cairographics.org/documentation/ #### Import When using ESModules: ```js import Cairo from 'cairo'; ``` When using legacy imports: ```js const Cairo = imports.cairo; ``` #### Mapping Methods are studlyCaps, similar to other JavaScript APIs. Abbreviations such as RGB, RGBA, PNG, PDF and SVG are always upper-case. For example: * `cairo_move_to()` is mapped to `Cairo.Context.moveTo()` * `cairo_surface_write_to_png()` is mapped to `Cairo.Context.writeToPNG()` Unlike the methods and structures, Cairo's enumerations are documented alongside the other GNOME APIs in the [`cairo`][cairo-devdocs] namespace. These are mapped similar to other libraries in GJS (eg. `Cairo.Format.ARGB32`). [cairo-devdocs]: https://gjs-docs.gnome.org/cairo10 ## Cairo.Context (`cairo_t`) `cairo_t` is mapped as `Cairo.Context`. You will either get a context from a third-party library such as Clutter/Gtk/Poppler or by calling the `Cairo.Context` constructor. ```js let cr = new Cairo.Context(surface); let cr = Gdk.cairo_create(...); ``` All introspection methods taking or returning a `cairo_t` will automatically create a `Cairo.Context`. ### Cairo.Context.$dispose() > Attention: This method must be called to avoid leaking memory Free a `Cairo.Context` and all associated memory. Unlike other objects and values in GJS, the `Cairo.Context` object requires an explicit free function to avoid memory leaks. However you acquire a instance, the `Cairo.Context.$dispose()` method must be called when you are done with it. For example, when using a [`Gtk.DrawingArea`][gtkdrawingarea]: ```js import Cairo from 'cairo'; import Gtk from 'gi://Gtk?version=4.0'; // Initialize GTK Gtk.init(); // Create a drawing area and set a drawing function const drawingArea = new Gtk.DrawingArea(); drawingArea.set_draw_func((area, cr, width, height) => { // Perform operations on the surface context // Freeing the context before returning from the callback cr.$dispose(); }); ``` [gtkdrawingarea]: https://gjs-docs.gnome.org/gtk40/gtk.drawingarea ## Cairo.Pattern (`cairo_pattern_t`) Prototype hierarchy * `Cairo.Pattern` * `Cairo.Gradient` * `Cairo.LinearGradient` * `Cairo.RadialGradient` * `Cairo.SurfacePattern` * `Cairo.SolidPattern` You can create a linear gradient by calling the constructor: Constructors: ```js let pattern = new Cairo.LinearGradient(0, 0, 100, 100); let pattern = new Cairo.RadialGradient(0, 0, 10, 100, 100, 10); let pattern = new Cairo.SurfacePattern(surface); let pattern = new Cairo.SolidPattern.createRGB(0, 0, 0); let pattern = new Cairo.SolidPattern.createRGBA(0, 0, 0, 0); ``` ## Cairo.Surface (`cairo_surface_t`) Prototype hierarchy * `Cairo.Surface` (abstract) * `Cairo.ImageSurface` * `Cairo.PDFSurface` * `Cairo.PSSurface` * `Cairo.SVGSurface` The native surfaces (win32, quartz, xlib) are not supported at this time. Methods manipulating a surface are present in the surface class. For example, creating a `Cairo.ImageSurface` from a PNG is done by calling a static method. ### Examples Creating an empty image surface can be done by passing a [`Cairo.Format`]: ```js /* Creating a surface from a PDF (format, width, height) */ const imageSurface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10); ``` Creating a `Cairo.ImageSurface` from a file differs somewhat depending on the file type: ```js /* Creating a surface from a PNG */ const pngSurface = Cairo.ImageSurface.createFromPNG('filename.png'); /* Creating a surface from a PDF (filename, width, height) */ const pdfSurface = new Cairo.PDFSurface('filename.pdf', 32, 32); /* Creating a surface from a PostScript file (filename, width, height) */ const psSurface = new Cairo.PSSurface('filename.ps', 32, 32); /* Creating a surface from a SVG (filename, width, height) */ const svgSurface = new Cairo.SVGSurface('filename.svg', 32, 32); ``` [`Cairo.Format`]: https://gjs-docs.gnome.org/cairo10/cairo.format ## To-do List As previously mentioned, the Cairo bindings for GJS are not entirely complete and contributions are welcome. Some of the bindings left to be implemented include: * context: wrap the remaining methods * surface methods * image surface methods * matrix * version * iterating over `cairo_path_t` Many font and glyph operations are not yet supported, and it is recommended to use [`PangoCairo`][pango-cairo] as an alternative: * glyphs * text cluster * font face * scaled font * font options [pango-cairo]: https://gjs-docs.gnome.org/pangocairo10 cjs-128.1/examples/0000775000175000017500000000000015116312211013037 5ustar fabiofabiocjs-128.1/examples/.eslintrc.yml0000664000175000017500000000054215116312211015464 0ustar fabiofabio--- parserOptions: sourceType: 'module' # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh rules: jsdoc/require-param: 'off' jsdoc/require-param-type: 'off' jsdoc/require-returns: 'off' jsdoc/require-jsdoc: 'off' jsdoc/check-tag-names: 'off' jsdoc/check-param-names: 'off' cjs-128.1/examples/README0000664000175000017500000000012015116312211013710 0ustar fabiofabioIn order to run those example scripts, do: ```sh gjs -m script-filename.js ``` cjs-128.1/examples/calc.js0000664000175000017500000000706215116312211014304 0ustar fabiofabio// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2008 Robert Carr import Gtk from 'gi://Gtk?version=3.0'; Gtk.init(null); let calcVal = ''; function updateDisplay() { label.set_markup(`${calcVal}`); if (calcVal === '') label.set_markup("0"); } function clear() { calcVal = ''; updateDisplay(); } function backspace() { calcVal = calcVal.substring(0, calcVal.length - 1); updateDisplay(); } function pressedEquals() { calcVal = calcVal.replace('sin', 'Math.sin'); calcVal = calcVal.replace('cos', 'Math.cos'); calcVal = calcVal.replace('tan', 'Math.tan'); calcVal = eval(calcVal); // Avoid ridiculous amounts of precision from toString. if (calcVal === Math.floor(calcVal)) calcVal = Math.floor(calcVal); else // bizarrely gjs loses str.toFixed() somehow?! calcVal = Math.floor(calcVal * 10000) / 10000; label.set_markup(`${calcVal}`); } function pressedOperator(button) { calcVal += button.label; updateDisplay(); } function pressedNumber(button) { calcVal = (calcVal === 0 ? '' : calcVal) + button.label; updateDisplay(); } function swapSign() { calcVal = calcVal[0] === '-' ? calcVal.substring(1) : `-${calcVal}`; updateDisplay(); } function randomNum() { calcVal = `${Math.floor(Math.random() * 1000)}`; updateDisplay(); } function packButtons(buttons, vbox) { let hbox = new Gtk.HBox(); hbox.homogeneous = true; vbox.pack_start(hbox, true, true, 2); for (let i = 0; i <= 4; i++) hbox.pack_start(buttons[i], true, true, 1); } function createButton(str, func) { let btn = new Gtk.Button({label: str}); btn.connect('clicked', func); return btn; } function createButtons() { let vbox = new Gtk.VBox({homogeneous: true}); packButtons([ createButton('(', pressedNumber), createButton('←', backspace), createButton('↻', randomNum), createButton('Clr', clear), createButton('±', swapSign), ], vbox); packButtons([ createButton(')', pressedNumber), createButton('7', pressedNumber), createButton('8', pressedNumber), createButton('9', pressedNumber), createButton('/', pressedOperator), ], vbox); packButtons([ createButton('sin(', pressedNumber), createButton('4', pressedNumber), createButton('5', pressedNumber), createButton('6', pressedNumber), createButton('*', pressedOperator), ], vbox); packButtons([ createButton('cos(', pressedNumber), createButton('1', pressedNumber), createButton('2', pressedNumber), createButton('3', pressedNumber), createButton('-', pressedOperator), ], vbox); packButtons([ createButton('tan(', pressedNumber), createButton('0', pressedNumber), createButton('.', pressedNumber), createButton('=', pressedEquals), createButton('+', pressedOperator), ], vbox); return vbox; } let win = new Gtk.Window({ title: 'Calculator', resizable: false, opacity: 0.6, }); win.resize(250, 250); win.connect('destroy', () => Gtk.main_quit()); let label = new Gtk.Label({label: ''}); label.set_alignment(1, 0); updateDisplay(); let mainvbox = new Gtk.VBox(); mainvbox.pack_start(label, false, true, 1); mainvbox.pack_start(new Gtk.HSeparator(), false, true, 5); mainvbox.pack_start(createButtons(), true, true, 2); win.add(mainvbox); win.show_all(); Gtk.main(); cjs-128.1/examples/dbus-client.js0000664000175000017500000001136615116312211015615 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Andy Holmes import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; /* * An XML DBus Interface */ const ifaceXml = ` `; // Pass the XML string to make a reusable proxy class for an interface proxies. const TestProxy = Gio.DBusProxy.makeProxyWrapper(ifaceXml); let proxy = null; let proxySignalId = 0; let proxyPropId = 0; // Watching a name on DBus. Another option is to create a proxy with the // `Gio.DBusProxyFlags.DO_NOT_AUTO_START` flag and watch the `g-name-owner` // property. function onNameAppeared(connection, name, _owner) { print(`"${name}" appeared on the session bus`); // If creating a proxy synchronously, errors will be thrown as normal try { proxy = new TestProxy( Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test' ); } catch (err) { logError(err); return; } // Proxy wrapper signals use the special functions `connectSignal()` and // `disconnectSignal()` to avoid conflicting with regular GObject signals. proxySignalId = proxy.connectSignal('TestSignal', (proxy_, name_, args) => { print(`TestSignal: ${args[0]}, ${args[1]}`); }); // To watch property changes, you can connect to the `g-properties-changed` // GObject signal with `connect()` proxyPropId = proxy.connect('g-properties-changed', (proxy_, changed, invalidated) => { for (let [prop, value] of Object.entries(changed.deepUnpack())) print(`Property '${prop}' changed to '${value.deepUnpack()}'`); for (let prop of invalidated) print(`Property '${prop}' invalidated`); }); // Reading and writing properties is straight-forward print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`); print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`); proxy.ReadWriteProperty = !proxy.ReadWriteProperty; print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`); // Both synchronous and asynchronous functions will be generated try { let value = proxy.SimpleMethodSync(); print(`SimpleMethod: ${value}`); } catch (err) { logError(`SimpleMethod: ${err.message}`); } proxy.ComplexMethodRemote('input string', (value, error, fdList) => { // If @error is not `null`, then an error occurred if (error !== null) { logError(error); return; } print(`ComplexMethod: ${value}`); // Methods that return file descriptors are fairly rare, so you should // know to expect one or not. if (fdList !== null) { // } }); } function onNameVanished(connection, name) { print(`"${name}" vanished from the session bus`); if (proxy !== null) { proxy.disconnectSignal(proxySignalId); proxy.disconnect(proxyPropId); proxy = null; } } let busWatchId = Gio.bus_watch_name( Gio.BusType.SESSION, 'org.cinnamon.cjs.Test', Gio.BusNameWatcherFlags.NONE, onNameAppeared, onNameVanished ); // Start an event loop let loop = GLib.MainLoop.new(null, false); loop.run(); // Unwatching names works just like disconnecting signal handlers. Gio.bus_unown_name(busWatchId); /* Asynchronous Usage * * Below is the alternative, asynchronous usage of proxy wrappers. If creating * a proxy asynchronously, you should not consider the proxy ready to use until * the callback is invoked without error. */ proxy = null; new TestProxy( Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', (sourceObj, error) => { // If @error is not `null` it will be an Error object indicating the // failure. @proxy will be `null` in this case. if (error !== null) { logError(error); return; } // At this point the proxy is initialized and you can start calling // functions, using properties and so on. proxy = sourceObj; print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`); }, // Optional Gio.Cancellable object. Pass `null` if you need to pass flags. null, // Optional flags passed to the Gio.DBusProxy constructor Gio.DBusProxyFlags.NONE ); cjs-128.1/examples/dbus-service.js0000664000175000017500000000666515116312211016005 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Andy Holmes import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; /* * An XML DBus Interface */ const ifaceXml = ` `; // An example of the service-side implementation of the above interface. class Service { constructor() { this.dbus = Gio.DBusExportedObject.wrapJSObject(ifaceXml, this); } // Properties get ReadOnlyProperty() { return 'a string'; } get ReadWriteProperty() { if (this._readWriteProperty === undefined) return false; return this._readWriteProperty; } set ReadWriteProperty(value) { if (this.ReadWriteProperty !== value) { this._readWriteProperty = value; // Emitting property changes over DBus this.dbus.emit_property_changed( 'ReadWriteProperty', new GLib.Variant('b', value) ); } } // Methods SimpleMethod() { print('SimpleMethod() invoked'); } ComplexMethod(input) { print(`ComplexMethod() invoked with "${input}"`); return input.length; } // Signals emitTestSignal() { this.dbus.emit_signal( 'TestSignal', new GLib.Variant('(sb)', ['string', false]) ); } } // Once you've created an instance of your service, you will want to own a name // on the bus so clients can connect to it. let serviceObj = new Service(); let serviceSignalId = 0; function onBusAcquired(connection, _name) { // At this point you have acquired a connection to the bus, and you should // export your interfaces now. serviceObj.dbus.export(connection, '/org/cinnamon/cjs/Test'); } function onNameAcquired(_connection, _name) { // Clients will typically start connecting and using your interface now. // Emit the TestSignal every few seconds serviceSignalId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => { serviceObj.emitTestSignal(); return GLib.SOURCE_CONTINUE; }); } function onNameLost(_connection, _name) { // Clients will know not to call methods on your interface now. Usually this // callback will only be invoked if you try to own a name on DBus that // already has an owner. // Stop emitting the test signal if (serviceSignalId > 0) { GLib.Source.remove(serviceSignalId); serviceSignalId = 0; } } let ownerId = Gio.bus_own_name( Gio.BusType.SESSION, 'org.cinnamon.cjs.Test', Gio.BusNameOwnerFlags.NONE, onBusAcquired, onNameAcquired, onNameLost ); // Start an event loop let loop = GLib.MainLoop.new(null, false); loop.run(); // Unowning names works just like disconnecting, but note that `onNameLost()` // will not be invoked in this case. Gio.bus_unown_name(ownerId); if (serviceSignalId > 0) { GLib.Source.remove(serviceSignalId); serviceSignalId = 0; } cjs-128.1/examples/gettext.js0000664000175000017500000000135715116312211015067 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2009 Red Hat, Inc. /* * Make sure you have a non english locale installed, for example fr_FR and run * LANGUAGE=fr_FR gjs -m gettext.js * the label should show a translation of 'Print help' */ import Gettext, {gettext as _} from 'gettext'; import Gtk from 'gi://Gtk?version=4.0'; import GLib from 'gi://GLib'; Gtk.init(); let loop = GLib.MainLoop.new(null, false); Gettext.bindtextdomain('gnome-shell', '/usr/share/locale'); Gettext.textdomain('gnome-shell'); let window = new Gtk.Window({title: 'gettext'}); window.set_child(new Gtk.Label({label: _('Print help')})); window.connect('close-request', () => { loop.quit(); }); window.present(); loop.run(); cjs-128.1/examples/gio-cat.js0000664000175000017500000000133315116312211014720 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; let loop = GLib.MainLoop.new(null, false); const decoder = new TextDecoder(); function cat(filename) { let f = Gio.file_new_for_path(filename); f.load_contents_async(null, (obj, res) => { let contents; try { contents = obj.load_contents_finish(res)[1]; } catch (err) { logError(err); loop.quit(); return; } print(decoder.decode(contents)); loop.quit(); }); loop.run(); } if (ARGV.length !== 1) printerr('Usage: gio-cat.js filename'); else cat(ARGV[0]); cjs-128.1/examples/glistmodel.js0000664000175000017500000000730715116312211015547 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Andy Holmes import GObject from 'gi://GObject'; import Gio from 'gi://Gio'; /** * An example of implementing the GListModel interface in GJS. The only real * requirement here is that the class be derived from some GObject. */ export let GjsListStore = GObject.registerClass({ GTypeName: 'GjsListStore', Implements: [Gio.ListModel], }, class MyList extends GObject.Object { constructor() { super(); /* We'll use a native Array as internal storage for the list model */ this._items = []; } /* Implementing this function amounts to returning a GType. This could be a * more specific GType, but must be a subclass of GObject. */ vfunc_get_item_type() { return GObject.Object.$gtype; } /* Implementing this function just requires returning the GObject at * @position or %null if out-of-range. This must explicitly return %null, * not `undefined`. */ vfunc_get_item(position) { return this._items[position] || null; } /* Implementing this function is as simple as return the length of the * storage object, in this case an Array. */ vfunc_get_n_items() { return this._items.length; } /** * Insert an item in the list. If @position is greater than the number of * items in the list or less than `0` it will be appended to the end of the * list. * * @param {GObject.Object} item - the item to add * @param {number} position - the position to add the item */ insertItem(item, position) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); if (position < 0 || position > this._items.length) position = this._items.length; this._items.splice(position, 0, item); this.items_changed(position, 0, 1); } /** * Append an item to the list. * * @param {GObject.Object} item - the item to add */ appendItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); let position = this._items.length; this._items.push(item); this.items_changed(position, 0, 1); } /** * Prepend an item to the list. * * @param {GObject.Object} item - the item to add */ prependItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); this._items.unshift(item); this.items_changed(0, 0, 1); } /** * Remove @item from the list. If @item is not in the list, this function * does nothing. * * @param {GObject.Object} item - the item to remove */ removeItem(item) { if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject'); let position = this._items.indexOf(item); if (position === -1) return; this._items.splice(position, 1); this.items_changed(position, 1, 0); } /** * Remove the item at @position. If @position is outside the length of the * list, this function does nothing. * * @param {number} position - the position of the item to remove */ removePosition(position) { if (position < 0 || position >= this._items.length) return; this._items.splice(position, 1); this.items_changed(position, 1, 0); } /** * Clear the list of all items. */ clear() { let length = this._items.length; if (length === 0) return; this._items = []; this.items_changed(0, length, 0); } }); cjs-128.1/examples/gtk-application.js0000664000175000017500000000722615116312211016472 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Andy Holmes // See the note about Application.run() at the bottom of the script import System from 'system'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; // Include the version in case both GTK3 and GTK4 installed // otherwise an exception will be thrown import Gtk from 'gi://Gtk?version=4.0'; // An example GtkApplication with a few bells and whistles, see also: // https://wiki.gnome.org/HowDoI/GtkApplication let ExampleApplication = GObject.registerClass({ Properties: { 'exampleprop': GObject.ParamSpec.string( 'exampleprop', // property name 'ExampleProperty', // nickname 'An example read write property', // description GObject.ParamFlags.READWRITE, // read/write/construct... 'a default value' ), }, Signals: {'examplesig': {param_types: [GObject.TYPE_INT]}}, }, class ExampleApplication extends Gtk.Application { constructor() { super({ application_id: 'org.cinnamon.cjs.ExampleApplication', flags: Gio.ApplicationFlags.FLAGS_NONE, }); } // Example signal emission emitExamplesig(number) { this.emit('examplesig', number); } vfunc_startup() { super.vfunc_startup(); // An example GAction, see: https://wiki.gnome.org/HowDoI/GAction let exampleAction = new Gio.SimpleAction({ name: 'exampleAction', parameter_type: new GLib.VariantType('s'), }); exampleAction.connect('activate', (action, param) => { param = param.deepUnpack().toString(); if (param === 'exampleParameter') log('Yes!'); }); this.add_action(exampleAction); } vfunc_activate() { super.vfunc_activate(); this.hold(); // Example ApplicationWindow let window = new Gtk.ApplicationWindow({ application: this, title: 'Example Application Window', default_width: 300, default_height: 200, }); let label = new Gtk.Label({label: this.exampleprop}); window.set_child(label); window.connect('close-request', () => { this.quit(); }); window.present(); // Example GNotification, see: https://developer.gnome.org/GNotification/ let notif = new Gio.Notification(); notif.set_title('Example Notification'); notif.set_body('Example Body'); notif.set_icon( new Gio.ThemedIcon({name: 'xsi-dialog-information-symbolic'}) ); // A default action for when the body of the notification is clicked notif.set_default_action("app.exampleAction('exampleParameter')"); // A button for the notification notif.add_button( 'Button Text', "app.exampleAction('exampleParameter')" ); // This won't actually be shown, since an application needs a .desktop // file with a base name matching the application id this.send_notification('example-notification', notif); // Withdraw this.withdraw_notification('example-notification'); } }); // The proper way to run a Gtk.Application or Gio.Application is take ARGV and // prepend the program name to it, and pass that to run() let app = new ExampleApplication(); app.run([System.programInvocationName].concat(ARGV)); // Or a one-liner... // (new ExampleApplication()).run([System.programInvocationName].concat(ARGV)); cjs-128.1/examples/gtk3-template.js0000664000175000017500000000311115116312211016052 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Andy Holmes import GObject from 'gi://GObject'; import Gio from 'gi://Gio'; import Gtk from 'gi://Gtk?version=3.0'; Gtk.init(null); /* In this example the template contents are loaded from the file as a string. * * The `Template` property of the class definition will accept: * - a `Uint8Array` or `GLib.Bytes` of XML * - an absolute file URI, such as `file:///home/user/window.ui` * - a GResource URI, such as `resource:///org/gnome/AppName/window.ui` */ const file = Gio.File.new_for_path('gtk3-template.ui'); const [, template] = file.load_contents(null); const ExampleWindow = GObject.registerClass({ GTypeName: 'ExampleWindow', Template: template, Children: [ 'box', ], InternalChildren: [ 'button', ], }, class ExampleWindow extends Gtk.Window { constructor(params = {}) { super(params); // The template has been initialized and you can access the children this.box.visible = true; // Internal children are set on the instance prefixed with a `_` this._button.visible = true; } // The signal handler bound in the UI file _onButtonClicked(button) { if (this instanceof Gtk.Window) log('Callback scope is bound to `ExampleWindow`'); button.label = 'Button was clicked!'; } }); // Create a window that stops the program when it is closed const win = new ExampleWindow(); win.connect('destroy', () => Gtk.main_quit()); win.present(); Gtk.main(); cjs-128.1/examples/gtk3-template.ui0000664000175000017500000000316215116312211016061 0ustar fabiofabio cjs-128.1/examples/gtk3.js0000664000175000017500000000501515116312211014246 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // Include the version in case both GTK3 and GTK4 installed // otherwise an exception will be thrown import Gtk from 'gi://Gtk?version=3.0'; // Initialize Gtk before you start calling anything from the import Gtk.init(null); // Construct a top-level window let win = new Gtk.Window({ type: Gtk.WindowType.TOPLEVEL, title: 'A default title', default_width: 300, default_height: 250, // A decent example of how constants are mapped: // 'Gtk' and 'WindowPosition' from the enum name GtkWindowPosition, // 'CENTER' from the enum's constant GTK_WIN_POS_CENTER window_position: Gtk.WindowPosition.CENTER, }); // Object properties can also be set or changed after construction, unless they // are marked construct-only. win.title = 'Hello World!'; // This is a callback function function onDeleteEvent() { log('delete-event emitted'); // If you return false in the "delete_event" signal handler, Gtk will emit // the "destroy" signal. // // Returning true gives you a chance to pop up 'are you sure you want to // quit?' type dialogs. return false; } // When the window is given the "delete_event" signal (this is given by the // window manager, usually by the "close" option, or on the titlebar), we ask // it to call the onDeleteEvent() function as defined above. win.connect('delete-event', onDeleteEvent); // GJS will warn when calling a C function with unexpected arguments... // // window.connect("destroy", Gtk.main_quit); // // ...so use arrow functions for inline callbacks with arguments to adjust win.connect('destroy', () => { Gtk.main_quit(); }); // Create a button to close the window let button = new Gtk.Button({ label: 'Close the Window', // Set visible to 'true' if you don't want to call button.show() later visible: true, // Another example of constant mapping: // 'Gtk' and 'Align' are taken from the GtkAlign enum, // 'CENTER' from the constant GTK_ALIGN_CENTER valign: Gtk.Align.CENTER, halign: Gtk.Align.CENTER, }); // Connect to the 'clicked' signal, using another way to call an arrow function button.connect('clicked', () => win.destroy()); // Add the button to the window win.add(button); // Show the window win.show(); // All gtk applications must have a Gtk.main(). Control will end here and wait // for an event to occur (like a key press or mouse event). The main loop will // run until Gtk.main_quit is called. Gtk.main(); cjs-128.1/examples/gtk4-frame-clock.js0000664000175000017500000000521015116312211016425 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2022 Andy Holmes import Gtk from 'gi://Gtk?version=4.0'; import GLib from 'gi://GLib'; function easeInOutQuad(p) { if ((p *= 2.0) < 1.0) return 0.5 * p * p; return -0.5 * (--p * (p - 2) - 1); } // When the button is clicked, we'll add a tick callback to run the animation function _onButtonClicked(widget) { // Prevent concurrent animations from being triggered if (widget._animationId) return; const duration = 1000; // one second in milliseconds let start = Date.now(); let fadeIn = false; // Tick callbacks are just like GSource callbacks. You will get a ID that // can be passed to Gtk.Widget.remove_tick_callback(), or you can return // GLib.SOURCE_CONTINUE and GLib.SOURCE_REMOVE as appropriate. widget._animationId = widget.add_tick_callback(() => { let now = Date.now(); // We've now passed the time duration if (now >= start + duration) { // If we just finished fading in, we're all done if (fadeIn) { widget._animationId = null; return GLib.SOURCE_REMOVE; } // If we just finished fading out, we'll start fading in fadeIn = true; start = now; } // Apply the easing function to the current progress let progress = (now - start) / duration; progress = easeInOutQuad(progress); // We are using the progress as the opacity value of the button widget.opacity = fadeIn ? progress : 1.0 - progress; return GLib.SOURCE_CONTINUE; }); } // Initialize GTK Gtk.init(); const loop = GLib.MainLoop.new(null, false); // Create a button to start the animation const button = new Gtk.Button({ label: 'Fade out, fade in', valign: Gtk.Align.CENTER, halign: Gtk.Align.CENTER, }); button.connect('clicked', _onButtonClicked); // Create a top-level window const win = new Gtk.Window({ title: 'GTK4 Frame Clock', default_width: 300, default_height: 250, child: button, }); // When a widget is destroyed any tick callbacks will be removed automatically, // so in practice our callback would be cleaned up when the window closes. win.connect('close-request', () => { // Note that removing a tick callback by ID will interrupt its progress, so // we are resetting the button opacity manually after it's removed. if (button._animationId) { button.remove_tick_callback(button._animationId); button.opacity = 1.0; } loop.quit(); }); // Show the window win.present(); loop.run(); cjs-128.1/examples/gtk4-template.js0000664000175000017500000000322415116312211016060 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Andy Holmes import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gio from 'gi://Gio'; import Gtk from 'gi://Gtk?version=4.0'; Gtk.init(); /* In this example the template contents are loaded from the file as a string. * * The `Template` property of the class definition will accept: * - a `Uint8Array` or `GLib.Bytes` of XML * - an absolute file URI, such as `file:///home/user/window.ui` * - a GResource URI, such as `resource:///org/gnome/AppName/window.ui` */ const file = Gio.File.new_for_path('gtk4-template.ui'); const [, template] = file.load_contents(null); const ExampleWindow = GObject.registerClass({ GTypeName: 'ExampleWindow', Template: template, Children: [ 'box', ], InternalChildren: [ 'button', ], }, class ExampleWindow extends Gtk.Window { constructor(params = {}) { super(params); // The template has been initialized and you can access the children this.box.visible = true; // Internal children are set on the instance prefixed with a `_` this._button.visible = true; } // The signal handler bound in the UI file _onButtonClicked(button) { if (this instanceof Gtk.Window) log('Callback scope is bound to `ExampleWindow`'); button.label = 'Button was clicked!'; } }); // Create a window that stops the program when it is closed const loop = GLib.MainLoop.new(null, false); const win = new ExampleWindow(); win.connect('close-request', () => loop.quit()); win.present(); loop.run(); cjs-128.1/examples/gtk4-template.ui0000664000175000017500000000316215116312211016062 0ustar fabiofabio cjs-128.1/examples/gtk4.js0000664000175000017500000000357315116312211014256 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // Include the version in case both GTK3 and GTK4 installed // otherwise an exception will be thrown import Gtk from 'gi://Gtk?version=4.0'; import GLib from 'gi://GLib'; // Initialize Gtk before you start calling anything from the import Gtk.init(); // If you are not using GtkApplication which has its own mainloop // you must create it yourself, see gtk-application.js example let loop = GLib.MainLoop.new(null, false); // Construct a window let win = new Gtk.Window({ title: 'A default title', default_width: 300, default_height: 250, }); // Object properties can also be set or changed after construction, unless they // are marked construct-only. win.title = 'Hello World!'; // This is a callback function function onCloseRequest() { log('close-request emitted'); loop.quit(); } // When the window is given the "close-request" signal (this is given by the // window manager, usually by the "close" option, or on the titlebar), we ask // it to call the onCloseRequest() function as defined above. win.connect('close-request', onCloseRequest); // Create a button to close the window let button = new Gtk.Button({ label: 'Close the Window', // An example of how constants are mapped: // 'Gtk' and 'Align' are taken from the GtkAlign enum, // 'CENTER' from the constant GTK_ALIGN_CENTER valign: Gtk.Align.CENTER, halign: Gtk.Align.CENTER, }); // Connect to the 'clicked' signal, using another way to call an arrow function button.connect('clicked', () => win.close()); // Add the button to the window win.set_child(button); // Show the window win.present(); // Control will end here and wait for an event to occur // (like a key press or mouse event) // The main loop will run until loop.quit is called. loop.run(); log('The main loop has completed.'); cjs-128.1/examples/http-client.js0000664000175000017500000000332515116312211015633 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Sonny Piers // This is a simple example of a HTTP client in Gjs using libsoup // https://developer.gnome.org/libsoup/stable/libsoup-client-howto.html import Soup from 'gi://Soup?version=3.0'; import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; const loop = GLib.MainLoop.new(null, false); const session = new Soup.Session(); const message = new Soup.Message({ method: 'GET', uri: GLib.Uri.parse('http://localhost:1080/hello?myname=gjs', GLib.UriFlags.NONE), }); const decoder = new TextDecoder(); session.send_async(message, null, null, send_async_callback); function splice_callback(outputStream, result) { let data; try { outputStream.splice_finish(result); data = outputStream.steal_as_bytes(); } catch (err) { logError(err); loop.quit(); return; } console.log('body:', decoder.decode(data.toArray())); loop.quit(); } function send_async_callback(self, res) { let inputStream; try { inputStream = session.send_finish(res); } catch (err) { logError(err); loop.quit(); return; } console.log('status:', message.status_code, message.reason_phrase); const response_headers = message.get_response_headers(); response_headers.foreach((name, value) => { console.log(name, ':', value); }); const contentType_ = response_headers.get_one('content-type'); const outputStream = Gio.MemoryOutputStream.new_resizable(); outputStream.splice_async(inputStream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_DEFAULT, null, splice_callback); } loop.run(); cjs-128.1/examples/http-server.js0000664000175000017500000000262315116312211015663 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC // This is a simple example of a HTTP server in GJS using libsoup // open http://localhost:1080 in your browser or use http-client.js import Soup from 'gi://Soup?version=3.0'; import GLib from 'gi://GLib'; const loop = GLib.MainLoop.new(null, false); function handler(_server, msg, _path, _query) { msg.set_status(200, null); msg.get_response_headers().set_content_type('text/html', {charset: 'UTF-8'}); msg.get_response_body().append(` Greetings, visitor from ${msg.get_remote_host()}
What is your name?
`); } function helloHandler(_server, msg, path, query) { if (!query) { msg.set_redirect(302, '/'); return; } msg.set_status(200, null); msg.get_response_headers().set_content_type('text/html', {charset: 'UTF-8'}); msg.get_response_body().append(` Hello, ${query.myname}! ☺
Go back `); } let server = new Soup.Server(); server.add_handler('/', handler); server.add_handler('/hello', helloHandler); server.listen_local(1080, Soup.ServerListenOptions.IPV4_ONLY); loop.run(); cjs-128.1/examples/test.jpg0000664000175000017500000010705715116312211014532 0ustar fabiofabio(ExifMM*iPhotoshop 3.08BIMkZ%GIhttps://flickr.com/e/15q%2Flj%2B1Yoctl4ojKlDbYKTvTnar%2BgqihS4gI1hIHfs%3DJFIF XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km!Adobed@C     C     J!1AQ"aq2#B R$Cbr3%DS45TUcs7!1AQ"a2qB#R3rS ?sdqSmCgF)K8)&+TbA⫴BP>hx"ۍ `#TexBQA>ozSBJzT01̠s[a,M9#RMW$ޮ2j4>QV)L5$e~uD@gSQlG ceQL^h4.>t "[Ϩ֗.Z4^Pn44|].rzZlbΏt*u `Z5EгAcϵ<Re ئjϥ'ifN?MEB~AL#pGjaԏ;9z ;F]Ox']5LiMe:5HX!|#/ !fTH!k92g!Jy`%PGG\n޵B#剭*82Wpkv(v9Q\4wsKp \D"A`{Vv؎ƭN (4ւbLcfr[#d!1kGvgLm#YG0Vc;x" b.:TF&ACA^_=kT0!)gH'M#Fh~=$tNZ"HO ^cXl-xظ>jes`OҶFL|Ns{!ٮ%C[n)-kPfЋ0rV!`M=#4@$ޡ h)x>VzV`N;V9r΍\;NYsWVZ%*$> hy>⧦L Mr I7qOKe4̗5! $6AX?JE*~UVBى5hqc5'rE>(4oӤp g .90붟 ^iAG.A6y4`d r`eeɫ;*W">FhQ2fRɛD"UrFh a+fRߤnA⬙1ӦCGw f62^A;CŀE<+IHS"ɦ$Sh4^ɭibk1UwSU\D`='UL8d]hR1i'Pdm, ;S4S5|A5l3C!4E@j^h6cL >;Vi 6-YuD,6sh9$ ƒ^V}(n iАAwl8+$-&6SJ|&҆pYOʛŹdPHkx;K)GSj[ ^sT U2m!-6AQٌ֤:Cw2Q{rsMBeG9((Y<i'[(d"Yp*&!hA* iH[Fqf Y b-khGYg*6*&T$֙gN{PдXi $eT*~+JԺҤ=ߊjGKtRcjzu@*/6irU$БT}S{4X0ڀjI"|oZrV24mv&mBMh6&دjA ?dŸ9G$"ޜqB<%`җM$ *A hȀSD3D(KK GQVe SGjFHeݨJ\$իQްOd-O%@979 dtjx,ۇZ`UǓZ*7䚾B#aRxApP4G$ >޴Cfkdve=d=kT^Ib2ozvЈ0jl,Jږ U$xLO@GJ#e#>5 =gku%@ZctEC#m0AnJ(?Fu*kxQW#X˲9w0j$8\0KRs@'B`C횲aOL4+k=*d *%Eb} u+(#4,֢c1>|3IÃ燷/L}Ol nlyB;EKKm ڮY^lK͢#Ҋ,5Ӌ}k0k7|7vB#{ޣ?@?)M?nec'd[,3 .ɦzKlLaE08ژR0@!lqۚmPl*'.,IX?,Vi<Ldzkb@6`y[gOP[l',e\p *\6NE"K iI4`O>TG$%#;sXy+4Ȭsִ,$*ȆrH=gUld| tyKH0Ǹst\>??uI)Z';t ɩ1a{p٬wg_H"ړO0x^褻c6Tjv--S?1]=Tg?SJ5_9La8ï=]Vk:gޣ]tYmw~#Up9$u^bksHݣKZI(垭[5%[@\GYϰPݯXIA}yЎ;Χ.ߋzE*M̳ZV%Gqn%<{lu9W_o5XtK6EYLjǰY9TMВIM/#"EЇib_NT4V-e E՛UO=sc6H>_=JS׆2ΜY _a+Peѡ;( Λ2cs׮oM?~fY~? <9Ԭe[}SJӮR֩FŘe,鎄rkV+HR[n EX9B!_%X "AךxG\g4v58ƣ* /ʳ`=?! R+Qf@~"LgmrGMX,=۞ pR*ȹ)WHVD>P < Gj 9QTsNrg$ʊED!ss9q &qIdaȁxs$*f1ZdVÐJm}"勚dykZ^ܔb v>~*dTf51 }>nɳd&[[Ou_C5溝ӺKKK'ZxםU>(鶪-%Up@ZJTsNf  nPAGeCE[ts)x/f=+Ү]>e z:zn0UܲW< Kdmz3@B L|[8OJ\6܋/m:(P[`~xY5}=q}&L? wåi_i拵%}\V>5J~T|2r a556Gg4BJc.g5բ#BJő\ nӬa?u4+fDRkΝ؜`{yHju5W*'IB`z>Tpe*Mcȵ3I*OԈ[q0o.)k+_T$[d\ہ1}u(T|]zIIDG]hެD(f}]Bd Hj2;tJeŠSF&V/Urp* R y2*[C{ҖtkBJșVv1#;bJ}hX,!CZCHi0;H;bL$5Sx2~1F xg vU!s際i/EG25ϥ?|Bmɬ|t-&LQc1C7#ެL#q]E5]'ֆ)}u}SW .n_.KQqzܖŧpXrֶUF~39',}Zc"(~gJ.L%=nzNI(۹a%ݢIQ ߎxrC ܥ()%/<MIso>CBث>8.x~q؋m}zeFɞ:mBj-f=׹-?#7,m+*W+(c#7z{Mon~dI3_< _ŮBs):NRqᗫ[kY(!:Z[[UFCdez~JK8D3|d;~sȤS\]IN)1MCyG\O9<֮6*d.* uFe晒)ud*Igk Ub1VV m&ZTH s1ԈGL$wKU8:s»fѺOS$c̱CvzzGx^{Qn+AaҺ{MG oooDAos:Fcy?'1f[򣝇dzoPOj\K RZw&p^ ʒ~WE[ON4|F!-$#+{A&ӣ JRLj\]ͤqcFq5ZzأD L28S{᪔|OzIk,:8-HȍwϹ! |cϖerrRy<%ÏW#is\SMTY%/AA}`,+]ʃI>]>4+0k +x:rCj鶼|WDln-der`N?N=;W59%|\tu4ڝM8a8<~* ZI#C ZMjT\ۤ+K?"!a9~nv8_%nԛv~>G'Ӹ]6^\Y 9M}GWbˤi}~ܗHYث c Z2ǶoZRm!#|}{kg-SdSrg 0:1$~a kk" Ō*К|;h=dԶWd% UFEJ.j)n#T>;_2f8ccxwJJosm?`QvmqՌBqu9_JWTԜ|%$Ƕ#9k Q5\Nȓ,$bv'ĝnR~GЌ!6EufIa,j=r3G^[eE9][:lmg5k:-`:6+m=jTUwGX}?V5`m&4eV*7L39{(%:IϾ_>?^M2Q{_uuh`Ğk \g;x2ʼWڒo_+ǾVW_O Rܣqg>R縸“6Y~V5,珻W!>.KǟtW,{x=zO5|翃BYE{%H]u3$F׽zW[jN&Z%2ٮf;c(zkW^cbN|#H=(]Lg]Eֱ2FɼĀڭ[C3.C>2,zZ]JW>"--0R9kD]ꚪjJ ;WQzmO\7.pVպo '4Y#&tΡfR^?zh' .s]ܦ~QDCMUH1Jli* 411ȥ]< , )ĺ Kqt$Id^'ģ )VA̘'f-f3sSރY!!k>Ӝi$ pi8 $ ( <ۗLH&[wMvQv20o'ނoz E]}1YQoK,Zwi cH}S[g.)!X gIKɧ#m}4} tK{Yᕟ#yЏJ_َ^>(Ry~JkM,&K#r5ьۄ2 }+ҥnxƹ%n8Y`ce!]*>9ؔ`G?sNjMgɧF*-ğYĝQ; fTE0$ztiM|%[xc?~ie*?FqK҆L"6ٔRw`1uFe:77 ǘ閣t.Eg>\h[x#";I`njztL#A~| OOG={a^׶>QFE'%sOcSS3K|eWP-X2͏w⹖un\Mh[e0?R)lf,}<&kh%}Z}6Rxy!݁7Tuz FcpgUtZI6:݆zZ}Lt{ϖՂ쿡=g3L;UR݃d{c05sAEn[?=mzQnƪ$mqs{`b^= &<*LX)1z⩂5 G4"Ɋ3f|Ku;3dfb8G)6M8N*q@OFLZUp9ϸ1F˲,w@W,?*=]qj-OWD}gR8GN9}x +gS]"t KX6~zϳ:ULEf5*f:G¤aQ%=\ն2i@|`&S_km]L``|LrxGyl'eR]n]Zuih'|}N-y)xk:E^ilc[G_y}BJgn{}=uӴ K,IspXH#ҵ-Wl3zڻRЬoUvov=7ַmK+3m(u9c5t4d\{kMOwz9\{7 O] َWݏ5h6 Tb 4L\}qMuuE㘼cNFm{6f YdQO$qȭ\r%g߄GӖ|FʸtMm57]mQΣh4odbVN>{ >a$QH+"Y*5RަU9y_JtFk6_~Wl$pFY@#l#pGQc3N1Z}?!]{<6&4tkY濷rFcDc1X[9I(챗ϫ:^k=׌.3fKV+]V 0PlGR=C˫hҽ>6Kϖ▁aҰi]jWL۔#tC͟LT4TO2>:b+9ϟRѼ@*l-gY?&:*=5ݞ}:Vj}ia% tDНGQ\vυ:cr=g|-ܷZm@T:ýN{ jz-;~3̻GSZ>&l>k6{WQJmvP )P `2;שZYcE\+>8~(ksֽ?mGqbڝز{7y;-q^z*3^h'rKz+/:GLέ>1eی~gסh壃|O᮷ԅJq>l5 6)lU]InFJ,܆ۈycA&]JQ&`Y7*`m=.,+E)L V6&1!.Z9"溒ʻ=sGD*^\[z}]#HvF|\6uᠪ j:#O#*|.jE(4JQƷbT(VfwjAPj*|:kZfäXA͏0*+u/S^ to8Tn<#xwUR"k#T6-vOczk2|U]PE@2}}<4:9&\{Q-u˕hr-bLyH` 'oP})J8{5nH׶ַnRP݄nO l|ӺUFGP?4;S#%JQW2r|aFH4"QK( [ ɫT$}GcܲXzwjƵ58gcSX~ ul=='SZU5DwERp;3;>zط:m_g N5+h|Q9X`c$sYK#ʠڏ z\2Q2!G|P\Kh6vxeѝEujב{% TDs"_,铔ϱzSMkF,%圡N1eO#➬O}k)n!x1Lw^ +Drx湚mtAϏsm^>SOƹ0H%{얓LnD nIR =|7Զa}>|="7&h/ ,@nUXpyR_fxgOg-vǟߔ;&HӮzf;3/A |98ֺm6z/ORk~g>:#A]s|s4ɵ`8OB>mit.TZO- x m0lZbژPXYR_.#="3@~,WFSI38a(Weo2ƽG +)mK,>=h+S?>tIt}cڿ\_b<>%~*eҡY{ƿ &#:>Tm?*,bKqђ?")nf:7w=7{?p=.!&h&_)2th0@RX{Y{@6e,ei5WZF=%.ȳi~z."`ʹkSӮwѼM$D"VL4t6~м =Zs=&CNcSڹs35uelR7N},D7 QƦQ!ݛKcq޴F|0꾴`n)F͔ZitSى!:CuPj0y0.?RkӿgGzGҝ+yv <t!ɂz#Y6V +bIp5bUUTʅMGP(=J\ߨV.+zMtf n=N"SO&xґEԍo" lʌ%OtsVGmf躯[H/'8݇G˧ٴ =BFeKk#y6=@lV RO%z57Wj:Kk6%8@'Aۓzu[R nMzG /{/7xGgYjqw ^^q,Un i>{\S4^Õ:ޖ{4ަwf:O:Vt[ ׳{~o|H[u+;3++̷ "'4^‡vyMi/~0}{YG\LNov.%xG*=תu֓O-,ʄ2(8hn=k&k+Ŝ5u~{ޤ}^(2cqs UjGrZHl"CKMNKۗh2yFNH#-2Zq9vn:|lo.0Eqwg g6;r|5WδQܟ/?]0ui^{bƖm$<@ӊ6lmѩu);6:^wo]JSʕQ9?!\ -=Z¦/bCImjd"fg^O}tNeѯ/c rY]2N@ĥ0I+*~iyiH> xKԑB{#a$RUq])OgDk|oX9*ʄ2 CEw1-2r)J؈ +)>TqƥˮZSnk$mw,`O4 ˳>nh>جfELLW[d)?i6Ƥ[9˹ٮEp%QD !XIw$BG 刧Ɩ K}>BO}-nB p9$u4[zse i <0{WOPUǹk=~tM0?!^Oө<ƣ]w {GR1+92&n>b2 C[jB.2}IBZD nU wfR_͔uXw78g [jj&KHx=^I^']2 FtGw.m%A'i{9q_e>)a3A3,՘!1Vplex+UN'wG3tFOIv\K8|DWq.1<}uq'S\gK?~0VGZIl>"E#u='+=l]1ѥj,<.Ew/4V@n 1Xs81fcFeoS{[xku_4,,q]Vģ'SE|ڏvtڕȥr+;gc λ5'!X ̾۫CdXΙ-qޒ(Թ κ8 `TSb5+P kzճ+k_WK}6!"DWQQXGd.G5BVpq\z*M%|]Ik712e@ 4p.(mEeI4KѪjc$YX|ku;Gzr{*!.!"ww.-N;эOkqhK)̾׹ך:{iIR[pڛ䲆$>1%tM FQmXNMvxāw;zV BsWrK]#6jHb23=J5u\~?@ (ɴӳ yQTrCf@Zbq*8Oy%N=d|WL`tB<$]/57MQq2e5)OS|ken+=kXt8-A1 DUcTp i@ˑ5dXmP]hd'jCUm:-xǵp(dz7՟~U#+]Q*u5ΓlYTnPe"iZNf,@>o>U㟿\9Zk $2,3VQS-lvçp 0ޝfR-5nIOҧ:&`HcWk.$y)zޝX^;wp'vbST ;m94Am9wiYj@'IŻLlmq*.9ݝB1juJ=;!+W:U[ٮnt>|k/٣kˈa`S{SvD\~N{^[\Ca鬄#<{57KxWuV_Ju*;@c ^oS]߭JTcG:;;'Y~(dāK 2+ӫnueǞJIgjӛCzzXv&A˜^Mp׃skZg}H^e9BwPyݴwgŸuI>~W$'UtMMhG)m &<(\L^P&;#1i[\qD<>ʥw{dpna՚wO}1aam 1 24x ]x\V?=Ijk_VoyӝLƽek&Wx+,pqhUS&Gntr}=Ի~/=5p|, }snS d9m*UdI.ئ?5ÌJPɕ$>jCAcqo1̘OaLRx*;'cVU#4@?hUsNInK9ci4tΏ;02^~gƋL7>FJݯӓݾ_-ӓ׳5I>/0faqo.Mn} olةWh*^n_ꬕkǦĮ(2fQ@ɧ_\c+%mﭖe {FXv_:ZK\]Jd1@9ҘKGPkPO_Oi|-LAyʫܞ}kMrk1M| vp0K38YT EmF~z4Hm^y aլk$dcag>mip;h{!4eIQQpAG qjl~S-Yz+-o.!Kdm"S{Ў]|e4aI {(YdRIBJ +n.7{gCtd6z +[x:ubV_w2ijXYtZm\V9lV՚uR jj繌qX?ӽ-cC,NW\K)lUPnŮ[y%Oke)CsS65˲ ^x{KL)k*~S=u2Y㕏#}E圻YQ 2$f:?AVN*Oot~լcՓ21^띨0_)5K}ޔW[46#۲ ⼍zZML\Z}nO(XLgL ޤ^KА+roξJ9쏜tхSj YqPޡ N"N }(Rr]̄`Q)QvKZJn! G4H8,4_J*,s¾&원\vr.O(\v /۶+\bfoM{S||WXĺj[T)MFA)}Ul$Y AЂ=UvQi8=uV2*jpFN7>G׳kQxzyE|7zz^&;[޻98kI63Jk-c+7Dį On*vT*KJjpM{1H1i9 4>X\m੡ M)$})Qb n/(F\ăڼ՝[ӎH`IN3ΔՊULTX=Y{5\=o^/u>t‚gtF?Ƕ+$Q=5th\{KDZCXx  H6 [p4wClgU88jRyx.?e%խKi\cSTSyx>kry/ÝHu%eݱ&|CdkzN /98F;`\A)n|Qsu۬c1~RPZ:y[%c| Kַ$`ϱܟb\zzpHUnXpnEQ|YFjZ?qK Gc΂[OUV<[][q-SvQrZӳ vutڕ+-ݛ@m!Ň(}@՗=w]['Ug`N>X8ʅXeu ,Ϸo zMM#;Et(H*A:ƣE-mro>4i-kDWν/M3i?SuU&kh%Tkia`-x"J!4#zT AA*8Pe,qP w*A8Цb7ܧ`]繆("z487| 8!k@]ǏqKO<1ȪGȖB rFIलlK݆~re4$c9>Iٌ}РH3 GMkyd_ochn=X$&NK9;h=?Z1XҥxR /C^IogΗ*jGfPdk50F?FU\AbD/ĄPo(p_(1>jɕuŒD*@8Uhɡu41^ߗk>C3j|$܌w/dh~!U9#nt+ye&#tTٜ  [/'Y 'LuoX.er==5ճ& ${b@Σ*qZsR[kYk"M<ɕz^mc\7d L`O#ձǿֲvIwvz^:ez|5,O:,Iq#i$>G~yE.JkQXWp'Pk)RBaiP6dVStGk| <6aѦ,lzNMvkm3[Yi$E 69l&av)k[®Yd]AǗ* |$vL/{N {_o\53Ŝ): BRS@I-ϹG;+ F=)\yfM/)bGMh6](N\Li5K-@ _ժ2>Q T33IJ ކ<3N|4C*#k2Kkw.~"/vDof)F 9#=%m5d yC, xgɭ)z'-uIo)ڈd©'<j̫ڔWD&t$J RJ%^@Z2Y=vJqM{o{A]YT1+(cAOΧרŖ~}6 ӗڏ?Ԡ+b%UQTI!-6:SnHqsh6A؟1o;D-Dm[%FG fs`ng\ HΕ-sF^>L'UO`=BXd}vS[6P͜;{c}1xd}2[2@ٞ8x؆laI&'mH;ȎM(KmZAL?4]oPYc7q~+rQڻ!Ov_rսQhcW<]=:9+`7γFpmQrlY(Mb>OVxY螻%A snƵXūZ-G:R)DnC)k1g:U!s9l!|HPCѪ` fK?x4ʱ`})S`Z _f8,G5I1M`ShEp*`5|J*9-MἽhj>0!|'>ֺ9Oy.F_FB ̫B̞c0ۨ|Ia/{L=]zolgSb뚫\7wpن721 /.(ctfmo.8x;#dPj-΍e_ҧDikir.6"% 3K-#ٹܸwKzGqqx~!Y4FG~٦~_+YlTqmLd0?h^O)&|wX; ɻ֡o sr5F;aFICؕXMެ7Sp^z@[BYPOz0Kޭڒw&5ql֖0pUGU\ M>}[qr<88ʯ{j7?eX_vIjDK<}$R4CDC iT3 mھeϏ߸a^&c-+`/ >gZ[A{ݕ8fu?klwgl~mh7OB_`{W?3 [帨Qm<֩$~y!%;^Zi+LTJʳ{Aׄsԕdm#ث+ KD>ʟ5˓j.It?.ib]\Me}OC]Z-I!T7 VYt<\t?OHt;:q1["9N?̰*Z7'eѼIxխoOc֨vf9S8E ~p0iBZ KyGL (p%?:>?Z'O M6s 9UvdisaF69 ?* __LkҚޕ,xo1[+m*}rTSaIV^ia{Җ;#4dG4֯Qo I_H9!L3Vh#g;r3>T`ByQ"kF|)4cqCsǪOKqz;(iyGMku=L㲃e=NRkaHEUpi`YTSyG>Ѯɜuk@O|߹M3ߞ*PHd,=t6Ԏ=z+}?r2ˈ95U˺wd(m&D,#rL{]:rAwk$wqVd|Wtc<3uNjyy2][.&18֪[ӷSNO}). #<{xCr?;ޥsrYtC)jrs ~lTsǹ>Q7 ѐys)1.^7 vc9׌N.^;/̟pk\|E?{qN_'϶/r~h,cݑ{+!Į |k[p|=3$MnbH8`$ȷ[kHZlD+ 3 5]وtMx'-@>59Am(KV+m SdI#n*8E9A+zh:mӐdBGӊ_tu)?YUԾjҎ}VҨ:uu΢E_RxM~v?OWMoM.뽹+z{xW~8뿓*2-3ƟAٳW+eGEHZQjfgEJd r&''3#gZ.Zk{nx9.[irΤu)է]f/MsG|%oɣ+`3/Xj6? __igkp9D7C!,|_Վ7f!5Sa@!Qi*)nu/͇fJ u.)cm^ƚKe/7Eu3"P`=-HS2tk'Zhܥv'վ=:md[ŕ26jrKQ9VY67ij!KSkh@QlN>̢E6?ZƃGTw%亦Oy yԉj+vYg>MQ)u{OG59StM7ro`ⲽu}㡪i_hYJ̖I!9o+fKKSkjmSTUmyw۵2=;w3@RR2Ū*+ώ%9 Ai/ zO7K?PKkG <_9>.Cc|WonܴnNX}M4ehk>D%w\HmI މ 6N;5Y5ۏp~{IiL#M!%@"s8_}j o?L[^Ak(6 öOpM6*e?I<#wX]Hj;'olcg>w7-,,$RO4.#1xZkN_8Ts)9sL[Ike< S T䠟qq9!7M2r\=D byylBVGsOE,&d۹:UuW%شct@$y!kXc2ѹ [r'+tzȰEg+Kj20Qдn"MNl<%rwh~ш/)nGډm>=GjUe1ÜZV-i }/xCz$GO"ߦ` j-UٖK/^ ad5%^OONnwDڏ[B>QOx$aFtݵZX?&1j?__= j1u|i-m~=FOF{l5hCY08PP+ /ZgEj[5o6|>M_im?R1tj )O?rk,*SDbAwe_Re3R!KD{}aE;/veaݔ[;0yIQk̤Uߦez7q~9(~ԲU"1^Fu.m.eckd:uk%ԧ'ST5k6>X\4=]R+WFvw0Ojeܖ[&4 ,. nbG fxf 7بEq.^VڏцI_Çjţ,$` Х.1sqq|YVϟ"$S!yS ƌF}ތ?AA) ;%ǰiS_ >Cs QsN~AZ˘*8\8<ҥ°(Kh NO 1S`mϰz4^ 3vϰl1m -0!b~ONoASoEe)AD-56(EM}]WsQr cM[TQtqXNes]fa>?|^Xe֖Ks0Y%ߙeTb8b&T;#S;A9?jM8$očO8!7YZE, Ͽ3s+j\n$Hv+pCcփ9-FUAE<@x[qo(ym_ㄔoSSb-'^.-FزGp)7z}cUKVpjrx$m:f5}'VJ1 LV:n$ԷV?tޱ;eYF}E/k-b[}*//j &:&C&W?qԲ~+SO]{\/Fipr`/`D7ZƣpƇ/qx?Ƨï`Lƛ/1xxe0kt[ۓ=n8o[=jzKa;$Əj3n}ԛxH&I1VWdSt>"T@¯ dQ+2.^a8+~V G^sة o -'ۂ.e3I<Ýҝߥ1<<$)\% ےsxg b9U89"{VE1>B6sb%}NԻ+m BrBS8jqq`6y\ybdb#kaK4B> ,@ʌ~ky\C}RJ|ȕԕڊ0@>"+ê-uPǟ,yhIe<)G<)aʐTH*.E͔LʸAX'c*8|wdM2ݐv 0XSPԼU,L.'9v>mMJ~Y9F ,J$) +V0?ݛ޻I(qkoو;T >gQ"w0}Noh pwͻV< >!@ݏOZܒɪ*-Y#2+F>7^7X8X""p#êPmEgrF*"{⮛HKHTO]JsK=-w# {Sٝ([{q։p}i*Q,#c9ۥخA:]^wf$~Lc8zMgQqjx#=I4VW8ҚMxӨm !^cx[zu 5\}KAJ|z- ҾTsݯ"fOt&esASkdb7:!䂪ါPM?;`zLN~OO[4&p|IC^&(ޝnM\|}$mL+AgVyW.-ʂؼ6? 2I3~irji3nqqU+23)_A\|@7#9Mp.{ުlA 3>Jmf,j\qn(wxB}>~zR5dePE(ڋs r 1\ekq;49&eR J9QwdN KE& ֫e/]e8oGR,v U|vVGb|"%zdjXX9EġcQ%8j@IsD+pR8m1 #)?"me8c'p5!ϹYҎ2`!O+RŏWգcZr|QIpq޻>3j~{Wֽ7oj`Zv&X}#e֩O֟t 'i0#GMӲQofgjf*[&ᰵް}+y.! z>6o]?V,KͱHTڰ댣>p ?C|RtlXU*KXKd'm)䑶 SS~[Ht(9dV`#-0#qx$=ͫ4"8T?JG#n⯈S.[cwU{<rB-eִZK 6wsܟjFb . q.}=M<6x8qsgsO I("zU/1\PMCp*.cNNm"ނ]>3v>QWdmʟMu)%9P`3%cJ/v#8wYQL]z~[h0\՟~nXw?kYmq3KO^F!Þ0=)SȟIR6+;e*iv*uzO-s.w+Q&_`{ P4_SK#VQ&g)`G|Vk.Ik{ jKwi %aثR6mj+y6CO@xhSerLZӸGm"8 jFrf(qb1BzVEOIh=gF}:eVx$jMGBOJN%G t8#-Qy]M0Ň|rW` =ڧyEKLgaj nqrM2KiYPw呾vB 緵SP,ӭ Zi>"E1,cFv|VJFթ:ƣҍo\F'C^VN^w=,X܄C WݎΛ:+tp=C"7|w.9Ȕ?0pv5q"<0tfm\ [ *j+d2+w [[`SVrGjƙ˜)ԗ"fDogA??jl㻄-Ke񣬦Kå[`g9i_NOC]7i2(aXMg~pyp.PfդMRjv$Y *7+7<חiBJQF9eI&tlcEy}|Աz$VYr9VWPΪ l C{ [ok,K(Ԯ[}r3ְf"6ʲחӪ ҰLV/nu1O(7M#ߴ ;kFZcB2sYƺR`N, C)_ЛO CpoZW7j,>X.ʄ8Z(I0^:mGKPѱcɒ"+^x&v䒊I9U/})ŗqcCIJe=Z,:|7y/52!)&XhI e[Ԋk`PUWϟlCaCKwnٕVwE[#P э>b:P{3}Ic vm|ݻ(Bd^V98T{I8QǗX$ " <}vy5mY3Yxx~t,VL>=w6?H4m s0ڸϭ2ltW\h\ú9:[QGs%4ލco31EQ5t_$ZY"nh1VWL4"#v=2v}wsg[icI|vzŮg⺯Ws>Kߧt:m7v T0|(RBpM`zUCw(Xm}"zPNsZ*w/;.ۦ]#4%Xc[Az-y0YL\N*6_rZS|v+|RcgN-+ABqZ.,L &.zy6s0)DGмsЍXr4FNTVb8$"!|7nPkOlJޣ<K(pRT>US8 Xzb)N֞ɏ~Uzl3J豰5/j.NcmD(aSsQHYRRN+$[Ҝ{VM%l44dY2>F~D8NMP&2 J\GpI4Ɂ d2nH9zXWpt cĮ[ރ ]#RET0A4,xp1xuHOY[ #[$!=Y9Cڱk@MÕ*y:a;G5>2J3̄FA-ŏ]W;GLەe=l-Y,S`)jAi]O<ݵC#ƶdsCW}1F9Wv"JL0D> v_Ul$b=+EPUٔݸUE\Nf\kCYam_VElMi@RTA`;*}Z7csZOES#$ 1NxM GSb&]˔rw%Iݭ1dY2'bA1"q#‚|"ز:5Ul+8 Ds|")O!o#qcDkF@;j,2jrMdUjzJ'Jvd]I-j3ȋ¹6қ,mY gI // This example demonstrates that Promises always execute prior // to timeouts. It should log "hello" then "world". import GLib from 'gi://GLib'; const loop = GLib.MainLoop.new(null, false); const promise = new Promise(r => { let i = 100; while (i--) ; r(); }); setTimeout(() => { promise.then(() => log('hello')); }); setTimeout(() => { log('world'); }); loop.run(); cjs-128.1/examples/webkit.js0000664000175000017500000000066615116312211014672 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC import Gtk from 'gi://Gtk?version=3.0'; import WebKit from 'gi://WebKit2?version=4.0'; Gtk.init(null); let win = new Gtk.Window(); let view = new WebKit.WebView(); view.load_uri('https://www.gnome.org'); win.add(view); win.connect('destroy', () => { Gtk.main_quit(); }); win.set_size_request(640, 480); win.show_all(); Gtk.main(); cjs-128.1/examples/websocket-client.js0000664000175000017500000000267715116312211016653 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Sonny Piers // This is an example of a WebSocket client in Gjs using libsoup // https://developer.gnome.org/libsoup/stable/libsoup-2.4-WebSockets.html import Soup from 'gi://Soup?version=3.0'; import GLib from 'gi://GLib'; const loop = GLib.MainLoop.new(null, false); const session = new Soup.Session(); const message = new Soup.Message({ method: 'GET', uri: GLib.Uri.parse('wss://ws.postman-echo.com/raw', GLib.UriFlags.NONE), }); const decoder = new TextDecoder(); session.websocket_connect_async(message, null, [], null, null, websocket_connect_async_callback); function websocket_connect_async_callback(_session, res) { let connection; try { connection = session.websocket_connect_finish(res); } catch (err) { logError(err); loop.quit(); return; } connection.connect('closed', () => { log('closed'); loop.quit(); }); connection.connect('error', (self, err) => { logError(err); loop.quit(); }); connection.connect('message', (self, type, data) => { if (type !== Soup.WebsocketDataType.TEXT) return; const str = decoder.decode(data.toArray()); log(`message: ${str}`); connection.close(Soup.WebsocketCloseCode.NORMAL, null); }); log('open'); connection.send_text('hello'); } loop.run(); cjs-128.1/gi/0000775000175000017500000000000015116312211011620 5ustar fabiofabiocjs-128.1/gi/arg-cache.cpp0000664000175000017500000027321315116312211014146 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna // SPDX-FileCopyrightText: 2020 Marco Trevisan #include #include #include // for size_t #include #include #include #include #include #include // for unordered_set::erase(), insert() #include #include #include #include #include #include #include #include // for UniqueChars #include #include #include // for InformalValueTypeName, JS_TypeOfValue #include // for JSTYPE_FUNCTION #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/closure.h" #include "gi/foreign.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" // for GjsTypecheckNoThrow #include "cjs/byteArray.h" #include "cjs/enum-utils.h" // for operator&, operator|=, operator| #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" enum ExpectedType { OBJECT, FUNCTION, STRING, LAST, }; static const char* expected_type_names[] = {"object", "function", "string"}; static_assert(G_N_ELEMENTS(expected_type_names) == ExpectedType::LAST, "Names must match the values in ExpectedType"); static constexpr void gjs_gi_argument_set_array_length(GITypeTag tag, GIArgument* arg, size_t value) { switch (tag) { case GI_TYPE_TAG_INT8: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT8: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT16: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT16: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT32: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT32: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_INT64: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_UINT64: gjs_arg_set(arg, value); break; default: g_assert_not_reached(); } } GJS_JSAPI_RETURN_CONVENTION static bool report_typeof_mismatch(JSContext* cx, const char* arg_name, JS::HandleValue value, ExpectedType expected) { gjs_throw(cx, "Expected type %s for argument '%s' but got type %s", expected_type_names[expected], arg_name, JS::InformalValueTypeName(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_gtype_mismatch(JSContext* cx, const char* arg_name, JS::Value value, GType expected) { gjs_throw( cx, "Expected an object of type %s for argument '%s' but got type %s", g_type_name(expected), arg_name, JS::InformalValueTypeName(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool report_invalid_null(JSContext* cx, const char* arg_name) { gjs_throw(cx, "Argument %s may not be null", arg_name); return false; } // Overload operator| so that Visual Studio won't complain // when converting unsigned char to GjsArgumentFlags GjsArgumentFlags operator|( GjsArgumentFlags const& v1, GjsArgumentFlags const& v2) { return static_cast(std::underlying_type::type(v1) | std::underlying_type::type(v2)); } namespace Gjs { namespace Arg { // Arguments Interfaces: // // Each of these types are meant to be used to extend each Gjs::Argument // implementation, taking advantage of the C++ multiple inheritance. struct BasicType { constexpr BasicType() : m_tag(GI_TYPE_TAG_VOID) {} constexpr explicit BasicType(GITypeTag tag) : m_tag(tag) {} constexpr operator GITypeTag() const { return m_tag; } GITypeTag m_tag : 5; }; struct HasTypeInfo { constexpr GITypeInfo* type_info() const { // Should be const GITypeInfo*, but G-I APIs won't accept that return const_cast(&m_type_info); } GITypeInfo m_type_info{}; }; struct Transferable { constexpr Transferable() : m_transfer(GI_TRANSFER_NOTHING) {} constexpr explicit Transferable(GITransfer transfer) : m_transfer(transfer) {} GITransfer m_transfer : 2; }; struct Nullable { constexpr Nullable() : m_nullable(false) {} bool m_nullable : 1; bool handle_nullable(JSContext* cx, GIArgument* arg, const char* arg_name); constexpr GjsArgumentFlags flags() const { return m_nullable ? GjsArgumentFlags::MAY_BE_NULL : GjsArgumentFlags::NONE; } }; struct Positioned { void set_arg_pos(int pos) { g_assert(pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); m_arg_pos = pos; } constexpr bool set_out_parameter(GjsFunctionCallState* state, GIArgument* arg) { // Clear all bits of the out C value. No one member is guaranteed to // span the whole union on all architectures, so use memset() instead of // gjs_arg_unset(). memset(&state->out_cvalue(m_arg_pos), 0, sizeof(GIArgument)); // The value passed to the function is actually the address of the out // C value gjs_arg_set(arg, &state->out_cvalue(m_arg_pos)); return true; } constexpr bool set_inout_parameter(GjsFunctionCallState* state, GIArgument* arg) { state->out_cvalue(m_arg_pos) = state->inout_original_cvalue(m_arg_pos) = *arg; gjs_arg_set(arg, &state->out_cvalue(m_arg_pos)); return true; } uint8_t m_arg_pos = 0; }; struct Array : BasicType { uint8_t m_length_pos = 0; GIDirection m_length_direction : 2; Array() : BasicType(), m_length_direction(GI_DIRECTION_IN) {} void set_array_length(int pos, GITypeTag tag, GIDirection direction) { g_assert(pos >= 0 && pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); m_length_pos = pos; m_length_direction = direction; m_tag = tag; } }; struct HasIntrospectionInfo { constexpr explicit HasIntrospectionInfo(GIBaseInfo* info, const GjsAutoTakeOwnership& add_ref) : m_info(info, add_ref) {} constexpr explicit HasIntrospectionInfo(GIBaseInfo* info) : m_info(info) {} GjsAutoBaseInfo m_info; }; // boxed / union / GObject struct GTypedType { explicit GTypedType(GType gtype) : m_gtype(gtype) {} constexpr GType gtype() const { return m_gtype; } protected: GType m_gtype; }; struct RegisteredType : GTypedType { RegisteredType(GType gtype, GIInfoType info_type) : GTypedType(gtype), m_info_type(info_type) {} explicit RegisteredType(GIRegisteredTypeInfo* info) : GTypedType(g_registered_type_info_get_g_type(info)), m_info_type(g_base_info_get_type(info)) { g_assert(m_gtype != G_TYPE_NONE && "Use RegisteredInterface for this type"); } GIInfoType m_info_type : 5; }; struct RegisteredInterface : HasIntrospectionInfo, GTypedType { explicit RegisteredInterface(GIRegisteredTypeInfo* info) : HasIntrospectionInfo(info, GjsAutoTakeOwnership{}), GTypedType(g_registered_type_info_get_g_type(m_info)) {} }; struct Callback : Nullable, HasIntrospectionInfo { explicit Callback(GICallbackInfo* info) : HasIntrospectionInfo(info, GjsAutoTakeOwnership{}), m_scope(GI_SCOPE_TYPE_INVALID) {} inline void set_callback_destroy_pos(int pos) { g_assert(pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); m_destroy_pos = pos < 0 ? Argument::ABSENT : pos; } [[nodiscard]] constexpr bool has_callback_destroy() { return m_destroy_pos != Argument::ABSENT; } inline void set_callback_closure_pos(int pos) { g_assert(pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); m_closure_pos = pos < 0 ? Argument::ABSENT : pos; } [[nodiscard]] constexpr bool has_callback_closure() { return m_closure_pos != Argument::ABSENT; } uint8_t m_closure_pos = Argument::ABSENT; uint8_t m_destroy_pos = Argument::ABSENT; GIScopeType m_scope : 3; }; struct Enum { explicit Enum(GIEnumInfo*); bool m_unsigned : 1; uint32_t m_min = 0; uint32_t m_max = 0; }; struct Flags { explicit Flags(GIEnumInfo*); unsigned m_mask = 0; }; struct CallerAllocates { size_t m_allocates_size = 0; }; // Gjs::Arguments: // // Each argument, irrespective of the direction, is processed in three phases: // - before calling the function [in] // - after calling it, when converting the return value and out arguments [out] // - at the end of the invocation, to release any allocated memory [release] // // Some types don't have direction (for example, caller_allocates is only out, // and callback is only in), in which case it is implied. struct SkipAll : Argument { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return skip(); } bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } protected: constexpr bool skip() { return true; } }; struct Generic : SkipAll, Transferable, HasTypeInfo {}; struct GenericIn : Generic { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return invalid(cx, G_STRFUNC); } bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct GenericInOut : GenericIn, Positioned { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct GenericOut : GenericInOut { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; GITypeTag return_tag() const override { return g_type_info_get_tag(&const_cast(this)->m_type_info); } const GITypeInfo* return_type() const override { return &m_type_info; } }; struct GenericReturn : ReturnValue { // No in! bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } }; template struct NumericOut : SkipAll, Positioned { static_assert(std::is_arithmetic_v, "Not arithmetic type"); bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), value); } }; using BooleanOut = NumericOut; template struct NumericReturn : SkipAll { static_assert(std::is_arithmetic_v, "Not arithmetic type"); bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), value); } GITypeTag return_tag() const override { return TAG; } }; using BooleanReturn = NumericReturn; struct SimpleOut : SkipAll, Positioned { bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); }; }; struct ExplicitArray : GenericOut, Array, Nullable { GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; struct ExplicitArrayIn : ExplicitArray { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); }; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct ExplicitArrayInOut : ExplicitArrayIn { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct ExplicitArrayOut : ExplicitArrayInOut { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct ReturnArray : ExplicitArrayOut { bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if (m_length_direction != GI_DIRECTION_OUT) { gjs_throw(cx, "Using different length argument direction for array %s" "is not supported for out arrays", m_arg_name); return false; } return GenericOut::in(cx, state, arg, value); }; }; using ArrayLengthOut = SimpleOut; struct FallbackIn : GenericIn, Nullable { bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; using FallbackInOut = GenericInOut; using FallbackOut = GenericOut; struct NotIntrospectable : GenericIn { explicit NotIntrospectable(NotIntrospectableReason reason) : m_reason(reason) {} NotIntrospectableReason m_reason; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct NullableIn : SkipAll, Nullable { inline bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue) override { return handle_nullable(cx, arg, m_arg_name); } GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; struct Instance : NullableIn { // Some calls accept null for the instance (thus we inherit from // NullableIn), but generally in an object oriented language it's wrong to // call a method on null. // As per this we actually default to SkipAll methods. bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return skip(); } bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } const Instance* as_instance() const override { return this; } // The instance GType can be useful only in few cases such as GObjects and // GInterfaces, so we don't store it by default, unless needed. // See Function's code to see where this is relevant. virtual GType gtype() const { return G_TYPE_NONE; } }; struct EnumIn : Instance, Enum { using Enum::Enum; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct FlagsIn : Instance, Flags { using Flags::Flags; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct RegisteredIn : Instance, RegisteredType, Transferable { using RegisteredType::RegisteredType; GType gtype() const override { return RegisteredType::gtype(); } }; struct RegisteredInterfaceIn : Instance, RegisteredInterface, Transferable { using RegisteredInterface::RegisteredInterface; GType gtype() const override { return RegisteredInterface::gtype(); } }; struct ForeignStructInstanceIn : RegisteredInterfaceIn { using RegisteredInterfaceIn::RegisteredInterfaceIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct ForeignStructIn : ForeignStructInstanceIn { using ForeignStructInstanceIn::ForeignStructInstanceIn; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct FallbackInterfaceIn : RegisteredInterfaceIn, HasTypeInfo { using RegisteredInterfaceIn::RegisteredInterfaceIn; bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_gi_argument(cx, value, &m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), arg); } }; struct BoxedInTransferNone : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; virtual GIBaseInfo* info() const { return nullptr; } }; struct BoxedIn : BoxedInTransferNone { using BoxedInTransferNone::BoxedInTransferNone; // This is a smart argument, no release needed bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } }; struct UnregisteredBoxedIn : BoxedIn, HasIntrospectionInfo { explicit UnregisteredBoxedIn(GIStructInfo* info) : BoxedIn(g_registered_type_info_get_g_type(info), g_base_info_get_type(info)), HasIntrospectionInfo(info, GjsAutoTakeOwnership{}) {} // This is a smart argument, no release needed GIBaseInfo* info() const override { return m_info; } }; struct GValueIn : BoxedIn { using BoxedIn::BoxedIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct GValueInTransferNone : GValueIn { using GValueIn::GValueIn; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct GClosureInTransferNone : BoxedInTransferNone { using BoxedInTransferNone::BoxedInTransferNone; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct GClosureIn : GClosureInTransferNone { using GClosureInTransferNone::GClosureInTransferNone; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } }; struct GBytesIn : BoxedIn { using BoxedIn::BoxedIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) override; }; struct GBytesInTransferNone : GBytesIn { using GBytesIn::GBytesIn; bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) override { return BoxedInTransferNone::release(cx, state, in_arg, out_arg); } }; struct ObjectIn : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // This is a smart argument, no release needed }; struct InterfaceIn : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // This is a smart argument, no release needed }; struct FundamentalIn : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // This is a smart argument, no release needed }; struct UnionIn : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // This is a smart argument, no release needed }; struct NullIn : NullableIn { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct BooleanIn : SkipAll { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; template struct NumericIn : SkipAll { static_assert(std::is_arithmetic_v, "Not arithmetic type"); bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; template struct NumericInOut : NumericIn, Positioned { static_assert(std::is_arithmetic_v, "Not arithmetic type"); bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if (!NumericIn::in(cx, state, arg, value)) return false; return set_inout_parameter(state, arg); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { return Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), value); } }; using BooleanInOut = NumericInOut; struct UnicharIn : SkipAll { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct GTypeIn : SkipAll { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; template struct StringInTransferNone : NullableIn { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct StringIn : StringInTransferNone { bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } }; template struct StringOutBase : SkipAll { bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { return Gjs::c_value_to_js(cx, gjs_arg_get(arg), value); } bool release(JSContext* cx, GjsFunctionCallState*, GIArgument*, GIArgument* out_arg [[maybe_unused]]) override { if constexpr (TRANSFER == GI_TRANSFER_NOTHING) { return skip(); } else if constexpr (TRANSFER == GI_TRANSFER_EVERYTHING) { g_clear_pointer(&gjs_arg_member(out_arg), g_free); return true; } else { return invalid(cx, G_STRFUNC); } } }; template struct StringReturn : StringOutBase { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return Argument::invalid(cx, G_STRFUNC); } GITypeTag return_tag() const override { return GI_TYPE_TAG_UTF8; } }; template struct StringOut : StringOutBase, Positioned { bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } }; using FilenameInTransferNone = StringInTransferNone; struct FilenameIn : FilenameInTransferNone { bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override { return skip(); } }; // .out is ignored for the instance parameter struct GTypeStructInstanceIn : Instance { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // no out bool out(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return invalid(cx, G_STRFUNC); }; }; struct ParamInstanceIn : Instance, Transferable { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; // no out bool out(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return invalid(cx, G_STRFUNC); }; }; struct CallbackIn : SkipAll, Callback { using Callback::Callback; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; private: ffi_closure *m_ffi_closure; }; using CArrayIn = ExplicitArrayIn; using CArrayInOut = ExplicitArrayInOut; using CArrayOut = ReturnArray; struct CallerAllocatesOut : GenericOut, CallerAllocates { bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; GjsArgumentFlags flags() const override { return GenericOut::flags() | GjsArgumentFlags::CALLER_ALLOCATES; } }; struct BoxedCallerAllocatesOut : CallerAllocatesOut, GTypedType { using GTypedType::GTypedType; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct ZeroTerminatedArrayInOut : GenericInOut { bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, original_out_arg)) return false; transfer = state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, out_arg); } }; struct ZeroTerminatedArrayIn : GenericIn, Nullable { bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument*) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, in_arg); } GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; struct FixedSizeArrayIn : GenericIn { bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument*) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; int size = g_type_info_get_array_fixed_size(&m_type_info); return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, size, in_arg); } }; struct FixedSizeArrayInOut : GenericInOut { bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) override { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); int size = g_type_info_get_array_fixed_size(&m_type_info); if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, size, original_out_arg)) return false; transfer = state->call_completed() ? m_transfer : GI_TRANSFER_EVERYTHING; return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, size, out_arg); } }; GJS_JSAPI_RETURN_CONVENTION bool NotIntrospectable::in(JSContext* cx, GjsFunctionCallState* state, GIArgument*, JS::HandleValue) { const char* reason_string = "invalid introspection"; switch (m_reason) { case CALLBACK_OUT: reason_string = "callback out-argument"; break; case DESTROY_NOTIFY_NO_CALLBACK: reason_string = "DestroyNotify argument with no callback"; break; case DESTROY_NOTIFY_NO_USER_DATA: reason_string = "DestroyNotify argument with no user data"; break; case INTERFACE_TRANSFER_CONTAINER: reason_string = "type not supported for (transfer container)"; break; case OUT_CALLER_ALLOCATES_NON_STRUCT: reason_string = "type not supported for (out caller-allocates)"; break; case UNREGISTERED_BOXED_WITH_TRANSFER: reason_string = "boxed type with transfer not registered as a GType"; break; case UNREGISTERED_UNION: reason_string = "union type not registered as a GType"; break; case UNSUPPORTED_TYPE: reason_string = "type not supported by introspection"; break; case LAST_REASON: g_assert_not_reached(); } gjs_throw(cx, "Function %s() cannot be called: argument '%s' with type %s is " "not introspectable because it has a %s", state->display_name().get(), m_arg_name, g_type_tag_to_string(g_type_info_get_tag(&m_type_info)), reason_string); return false; } GJS_JSAPI_RETURN_CONVENTION bool GenericIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { return gjs_value_to_gi_argument(cx, value, &m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), arg); } GJS_JSAPI_RETURN_CONVENTION bool GenericInOut::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (!GenericIn::in(cx, state, arg, value)) return false; return set_inout_parameter(state, arg); } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { void* data; size_t length; if (m_length_direction != GI_DIRECTION_INOUT && m_length_direction != GI_DIRECTION_IN) { gjs_throw(cx, "Using different length argument direction for array %s" "is not supported for in arrays", m_arg_name); return false; } if (!gjs_array_to_explicit_array(cx, value, &m_type_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), &data, &length)) return false; gjs_gi_argument_set_array_length(m_tag, &state->in_cvalue(m_length_pos), length); gjs_arg_set(arg, data); return true; } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayInOut::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (!ExplicitArrayIn::in(cx, state, arg, value)) return false; uint8_t length_pos = m_length_pos; uint8_t ix = m_arg_pos; if (!gjs_arg_get(arg)) { // Special case where we were given JS null to also pass null for // length, and not a pointer to an integer that derefs to 0. gjs_arg_unset(&state->in_cvalue(length_pos)); gjs_arg_unset(&state->out_cvalue(length_pos)); gjs_arg_unset(&state->inout_original_cvalue(length_pos)); gjs_arg_unset(&state->out_cvalue(ix)); gjs_arg_unset(&state->inout_original_cvalue(ix)); } else { if G_LIKELY (m_length_direction == GI_DIRECTION_INOUT) { state->out_cvalue(length_pos) = state->inout_original_cvalue(length_pos) = state->in_cvalue(length_pos); gjs_arg_set(&state->in_cvalue(length_pos), &state->out_cvalue(length_pos)); } state->out_cvalue(ix) = state->inout_original_cvalue(ix) = *arg; gjs_arg_set(arg, &state->out_cvalue(ix)); } return true; } GJS_JSAPI_RETURN_CONVENTION bool CallbackIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { GjsCallbackTrampoline* trampoline; void* closure; if (value.isNull() && m_nullable) { closure = nullptr; trampoline = nullptr; m_ffi_closure = nullptr; } else { if (JS_TypeOfValue(cx, value) != JSTYPE_FUNCTION) { gjs_throw(cx, "Expected function for callback argument %s, got %s", m_arg_name, JS::InformalValueTypeName(value)); return false; } JS::RootedObject callable(cx, &value.toObject()); bool is_object_method = !!state->instance_object; trampoline = GjsCallbackTrampoline::create( cx, callable, m_info, m_scope, is_object_method, false); if (!trampoline) return false; if (m_scope == GI_SCOPE_TYPE_NOTIFIED && is_object_method) { auto* priv = ObjectInstance::for_js(cx, state->instance_object); if (!priv) { gjs_throw(cx, "Signal connected to wrong type of object"); return false; } if (!priv->associate_closure(cx, trampoline)) return false; } closure = trampoline->closure(); m_ffi_closure = trampoline->get_ffi_closure(); } if (has_callback_destroy()) { GDestroyNotify destroy_notify = nullptr; if (trampoline) { /* Adding another reference and a DestroyNotify that unsets it */ g_closure_ref(trampoline); destroy_notify = [](void* data) { g_assert(data); g_closure_unref(static_cast(data)); }; } gjs_arg_set(&state->in_cvalue(m_destroy_pos), destroy_notify); } if (has_callback_closure()) gjs_arg_set(&state->in_cvalue(m_closure_pos), trampoline); if (trampoline && m_scope == GI_SCOPE_TYPE_ASYNC) { // Add an extra reference that will be cleared when garbage collecting // async calls g_closure_ref(trampoline); } bool keep_forever = !has_callback_destroy() && ( #if GI_CHECK_VERSION(1, 72, 0) m_scope == GI_SCOPE_TYPE_FOREVER || #endif m_scope == GI_SCOPE_TYPE_NOTIFIED); if (trampoline && keep_forever) { trampoline->mark_forever(); } gjs_arg_set(arg, closure); return true; } GJS_JSAPI_RETURN_CONVENTION bool GenericOut::in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) { // Default value in case a broken C function doesn't fill in the pointer return set_out_parameter(state, arg); } GJS_JSAPI_RETURN_CONVENTION bool CallerAllocatesOut::in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) { void* blob = g_malloc0(m_allocates_size); gjs_arg_set(arg, blob); gjs_arg_set(&state->out_cvalue(m_arg_pos), blob); return true; } GJS_JSAPI_RETURN_CONVENTION bool NullIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue) { return handle_nullable(cx, arg, m_arg_name); } GJS_JSAPI_RETURN_CONVENTION bool BooleanIn::in(JSContext*, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { gjs_arg_set(arg, JS::ToBoolean(value)); return true; } template GJS_JSAPI_RETURN_CONVENTION bool NumericIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { bool out_of_range = false; if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { if (out_of_range) { gjs_throw(cx, "Argument %s: value is out of range for %s", arg_name(), Gjs::static_type_name()); } return false; } gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "%s set to value %s (type %s)", GjsAutoChar{gjs_argument_display_name( arg_name(), GJS_ARGUMENT_ARGUMENT)} .get(), std::to_string(gjs_arg_get(arg)).c_str(), Gjs::static_type_name()); return true; } GJS_JSAPI_RETURN_CONVENTION bool UnicharIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (!value.isString()) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::STRING); return gjs_unichar_from_string(cx, value, &gjs_arg_member(arg)); } GJS_JSAPI_RETURN_CONVENTION bool GTypeIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return report_invalid_null(cx, m_arg_name); if (!value.isObject()) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::OBJECT); JS::RootedObject gtype_obj(cx, &value.toObject()); return gjs_gtype_get_actual_gtype( cx, gtype_obj, &gjs_arg_member(arg)); } // Common code for most types that are pointers on the C side bool Nullable::handle_nullable(JSContext* cx, GIArgument* arg, const char* arg_name) { if (!m_nullable) return report_invalid_null(cx, arg_name); gjs_arg_unset(arg); return true; } template GJS_JSAPI_RETURN_CONVENTION bool StringInTransferNone::in( JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); if (!value.isString()) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::STRING); if constexpr (TAG == GI_TYPE_TAG_FILENAME) { GjsAutoChar str; if (!gjs_string_to_filename(cx, value, &str)) return false; gjs_arg_set(arg, str.release()); return true; } else if constexpr (TAG == GI_TYPE_TAG_UTF8) { JS::UniqueChars str = gjs_string_to_utf8(cx, value); if (!str) return false; gjs_arg_set(arg, g_strdup(str.get())); return true; } else { return invalid(cx, G_STRFUNC); } } GJS_JSAPI_RETURN_CONVENTION bool EnumIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; if (!Gjs::js_value_to_c(cx, value, &number)) return false; // Unpack the values from their uint32_t bitfield. See note in // Enum::Enum(). int64_t min, max; if (m_unsigned) { min = m_min; max = m_max; } else { min = static_cast(m_min); max = static_cast(m_max); } if (number > max || number < min) { gjs_throw(cx, "%" PRId64 " is not a valid value for enum argument %s", number, m_arg_name); return false; } if (m_unsigned) gjs_arg_set(arg, number); else gjs_arg_set(arg, number); return true; } GJS_JSAPI_RETURN_CONVENTION bool FlagsIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { int64_t number; if (!Gjs::js_value_to_c(cx, value, &number)) return false; if ((uint64_t(number) & m_mask) != uint64_t(number)) { gjs_throw(cx, "0x%" PRId64 " is not a valid value for flags argument %s", number, m_arg_name); return false; } // We cast to unsigned because that's what makes sense, but then we // put it in the v_int slot because that's what we use to unmarshal // flags types at the moment. gjs_arg_set(arg, static_cast(number)); return true; } GJS_JSAPI_RETURN_CONVENTION bool ForeignStructInstanceIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { return gjs_struct_foreign_convert_to_gi_argument( cx, value, m_info, m_arg_name, GJS_ARGUMENT_ARGUMENT, m_transfer, flags(), arg); } GJS_JSAPI_RETURN_CONVENTION bool GValueIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); GType gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, >ype)) return false; if (gtype == G_TYPE_VALUE) { gjs_arg_set(arg, BoxedBase::to_c_ptr(cx, obj)); state->ignore_release.insert(arg); return true; } } Gjs::AutoGValue gvalue; if (!gjs_value_to_g_value(cx, value, &gvalue)) return false; gjs_arg_set(arg, g_boxed_copy(G_TYPE_VALUE, &gvalue)); return true; } GJS_JSAPI_RETURN_CONVENTION bool BoxedInTransferNone::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); GType gtype = RegisteredType::gtype(); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); if (gtype == G_TYPE_ERROR) { return ErrorBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, m_transfer); } return BoxedBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype, info()); } // Unions include ClutterEvent and GdkEvent, which occur fairly often in an // interactive application, so they're worth a special case in a different // virtual function. GJS_JSAPI_RETURN_CONVENTION bool UnionIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); GType gtype = RegisteredType::gtype(); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return UnionBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION bool GClosureInTransferNone::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); if (!(JS_TypeOfValue(cx, value) == JSTYPE_FUNCTION)) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::FUNCTION); JS::RootedObject callable(cx, &value.toObject()); GClosure* closure = Gjs::Closure::create_marshaled(cx, callable, "boxed"); gjs_arg_set(arg, closure); g_closure_ref(closure); g_closure_sink(closure); return true; } GJS_JSAPI_RETURN_CONVENTION bool GBytesIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, G_TYPE_BYTES); JS::RootedObject object(cx, &value.toObject()); if (JS_IsUint8Array(object)) { state->ignore_release.insert(arg); gjs_arg_set(arg, gjs_byte_array_get_bytes(object)); return true; } // The bytearray path is taking an extra ref irrespective of transfer // ownership, so we need to do the same here. return BoxedBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, GI_TRANSFER_EVERYTHING, G_TYPE_BYTES); } GJS_JSAPI_RETURN_CONVENTION bool GBytesIn::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) { if (state->ignore_release.erase(in_arg)) return BoxedIn::release(cx, state, in_arg, out_arg); return BoxedInTransferNone::release(cx, state, in_arg, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool InterfaceIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); GType gtype = RegisteredType::gtype(); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental if (ObjectBase::typecheck(cx, object, nullptr, gtype, GjsTypecheckNoThrow())) { return ObjectBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } // If this typecheck fails, then it's neither an object nor a // fundamental return FundamentalBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION bool ObjectIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); GType gtype = RegisteredType::gtype(); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return ObjectBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION bool FundamentalIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (value.isNull()) return NullableIn::in(cx, state, arg, value); GType gtype = RegisteredType::gtype(); if (!value.isObject()) return report_gtype_mismatch(cx, m_arg_name, value, gtype); JS::RootedObject object(cx, &value.toObject()); return FundamentalBase::transfer_to_gi_argument( cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } GJS_JSAPI_RETURN_CONVENTION bool GTypeStructInstanceIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { // Instance parameter is never nullable if (!value.isObject()) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::OBJECT); JS::RootedObject obj(cx, &value.toObject()); GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { gjs_throw(cx, "Invalid GType class passed for instance parameter"); return false; } // We use peek here to simplify reference counting (we just ignore transfer // annotation, as GType classes are never really freed.) We know that the // GType class is referenced at least once when the JS constructor is // initialized. if (g_type_is_a(actual_gtype, G_TYPE_INTERFACE)) gjs_arg_set(arg, g_type_default_interface_peek(actual_gtype)); else gjs_arg_set(arg, g_type_class_peek(actual_gtype)); return true; } GJS_JSAPI_RETURN_CONVENTION bool ParamInstanceIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { // Instance parameter is never nullable if (!value.isObject()) return report_typeof_mismatch(cx, m_arg_name, value, ExpectedType::OBJECT); JS::RootedObject obj(cx, &value.toObject()); if (!gjs_typecheck_param(cx, obj, G_TYPE_PARAM, true)) return false; gjs_arg_set(arg, gjs_g_param_from_param(cx, obj)); if (m_transfer == GI_TRANSFER_EVERYTHING) g_param_spec_ref(gjs_arg_get(arg)); return true; } GJS_JSAPI_RETURN_CONVENTION bool GenericInOut::out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) { return gjs_value_from_gi_argument(cx, value, &m_type_info, arg, true); } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayInOut::out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::MutableHandleValue value) { GIArgument* length_arg; if (m_length_direction != GI_DIRECTION_IN) length_arg = &(state->out_cvalue(m_length_pos)); else length_arg = &(state->in_cvalue(m_length_pos)); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); return gjs_value_from_explicit_array(cx, value, &m_type_info, m_transfer, arg, length); } GJS_JSAPI_RETURN_CONVENTION bool GenericIn::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument*) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; return gjs_gi_argument_release_in_arg(cx, transfer, &m_type_info, in_arg); } GJS_JSAPI_RETURN_CONVENTION bool GenericOut::release(JSContext* cx, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { return gjs_gi_argument_release(cx, m_transfer, &m_type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool GenericInOut::release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) { // For inout, transfer refers to what we get back from the function; for // the temporary C value we allocated, clearly we're responsible for // freeing it. GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); if (!gjs_gi_argument_release_in_arg(cx, GI_TRANSFER_NOTHING, &m_type_info, original_out_arg)) return false; return gjs_gi_argument_release(cx, m_transfer, &m_type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayOut::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); return gjs_gi_argument_release_out_array(cx, m_transfer, &m_type_info, length, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayIn::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { GIArgument* length_arg = &state->in_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; return gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, length, in_arg); } GJS_JSAPI_RETURN_CONVENTION bool ExplicitArrayInOut::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); // For inout, transfer refers to what we get back from the function; for // the temporary C value we allocated, clearly we're responsible for // freeing it. GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); // Due to https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/192 // Here we've to guess what to do, but in general is "better" to leak than // crash, so let's assume that in/out transfer is matching. if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg)) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; if (!gjs_gi_argument_release_in_array(cx, transfer, &m_type_info, length, original_out_arg)) return false; } GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; return gjs_gi_argument_release_out_array(cx, transfer, &m_type_info, length, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool CallerAllocatesOut::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { g_free(gjs_arg_steal(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION bool BoxedCallerAllocatesOut::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument*) { g_boxed_free(m_gtype, gjs_arg_steal(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION bool CallbackIn::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { ffi_closure *closure = m_ffi_closure; if (!closure) return true; g_closure_unref(static_cast(closure->user_data)); // CallbackTrampolines are refcounted because for notified/async closures // it is possible to destroy it while in call, and therefore we cannot // check its scope at this point gjs_arg_unset(in_arg); return true; } template GJS_JSAPI_RETURN_CONVENTION bool StringInTransferNone::release( JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { g_free(gjs_arg_get(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION bool ForeignStructIn::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; if (transfer == GI_TRANSFER_NOTHING) return gjs_struct_foreign_release_gi_argument(cx, m_transfer, m_info, in_arg); return true; } GJS_JSAPI_RETURN_CONVENTION bool BoxedInTransferNone::release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) { GType gtype = RegisteredType::gtype(); g_assert(g_type_is_a(gtype, G_TYPE_BOXED)); if (!gjs_arg_get(in_arg)) return true; g_boxed_free(gtype, gjs_arg_get(in_arg)); return true; } GJS_JSAPI_RETURN_CONVENTION bool GValueInTransferNone::release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) { if (state->ignore_release.erase(in_arg)) return true; return BoxedInTransferNone::release(cx, state, in_arg, out_arg); } } // namespace Arg bool Argument::invalid(JSContext* cx, const char* func) const { gjs_throw(cx, "%s not implemented", func ? func : "Function"); return false; } bool Argument::in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) { return invalid(cx, G_STRFUNC); } bool Argument::out(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) { return invalid(cx, G_STRFUNC); } bool Argument::release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) { return true; } // This is a trick to print out the sizes of the structs at compile time, in // an error message: // template struct Measure; // Measure arg_size; #ifdef GJS_DO_ARGUMENTS_SIZE_CHECK template constexpr size_t argument_maximum_size() { if constexpr (std::is_same_v>) return 24; if constexpr (std::is_same_v || std::is_same_v) return 40; else return 120; } #endif template GjsAutoCppPointer Argument::make(uint8_t index, const char* name, GITypeInfo* type_info, GITransfer transfer, GjsArgumentFlags flags, Args&&... args) { #ifdef GJS_DO_ARGUMENTS_SIZE_CHECK static_assert( sizeof(T) <= argument_maximum_size(), "Think very hard before increasing the size of Gjs::Arguments. " "One is allocated for every argument to every introspected function."); #endif auto arg = new T(args...); if constexpr (ArgKind == Arg::Kind::INSTANCE) { g_assert(index == Argument::ABSENT && "index was ignored in INSTANCE parameter"); g_assert(name == nullptr && "name was ignored in INSTANCE parameter"); arg->set_instance_parameter(); } else if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) { g_assert(index == Argument::ABSENT && "index was ignored in RETURN_VALUE parameter"); g_assert(name == nullptr && "name was ignored in RETURN_VALUE parameter"); arg->set_return_value(); } else { if constexpr (std::is_base_of_v) arg->set_arg_pos(index); arg->m_arg_name = name; } arg->m_skip_in = (flags & GjsArgumentFlags::SKIP_IN); arg->m_skip_out = (flags & GjsArgumentFlags::SKIP_OUT); if constexpr (std::is_base_of_v) arg->m_nullable = (flags & GjsArgumentFlags::MAY_BE_NULL); if constexpr (std::is_base_of_v) arg->m_transfer = transfer; if constexpr (std::is_base_of_v && ArgKind != Arg::Kind::INSTANCE) { arg->m_type_info = std::move(*type_info); } return arg; } bool ArgsCache::initialize(JSContext* cx, GICallableInfo* callable) { if (!callable) { gjs_throw(cx, "Invalid callable provided"); return false; } if (m_args) { gjs_throw(cx, "Arguments cache already initialized!"); return false; } GITypeInfo type_info; g_callable_info_load_return_type(callable, &type_info); m_has_return = g_type_info_get_tag(&type_info) != GI_TYPE_TAG_VOID || g_type_info_is_pointer(&type_info); m_is_method = !!g_callable_info_is_method(callable); int size = g_callable_info_get_n_args(callable); size += (m_is_method ? 1 : 0); size += (m_has_return ? 1 : 0); if (size > Argument::MAX_ARGS) { gjs_throw(cx, "Too many arguments, only %u are supported, while %d are " "provided!", Argument::MAX_ARGS, size); return false; } m_args = new ArgumentPtr[size]{}; return true; } template constexpr T* ArgsCache::set_argument(uint8_t index, const char* name, GITypeInfo* type_info, GITransfer transfer, GjsArgumentFlags flags, Args&&... args) { GjsAutoCppPointer arg = Argument::make( index, name, type_info, transfer, flags, args...); arg_get(index) = arg.release(); return static_cast(arg_get(index).get()); } template constexpr T* ArgsCache::set_argument(uint8_t index, const char* name, GITransfer transfer, GjsArgumentFlags flags, Args&&... args) { return set_argument(index, name, nullptr, transfer, flags, args...); } template constexpr T* ArgsCache::set_argument_auto(Args&&... args) { return set_argument(std::forward(args)...); } template constexpr T* ArgsCache::set_argument_auto(Tuple&& tuple, Args&&... args) { // TODO(3v1n0): Would be nice to have a simple way to check we're handling a // tuple return std::apply( [&](auto&&... largs) { return set_argument(largs..., std::forward(args)...); }, tuple); } template constexpr T* ArgsCache::set_return(GITypeInfo* type_info, GITransfer transfer, GjsArgumentFlags flags) { return set_argument(Argument::ABSENT, nullptr, type_info, transfer, flags); } template constexpr T* ArgsCache::set_instance(GITransfer transfer, GjsArgumentFlags flags) { return set_argument(Argument::ABSENT, nullptr, transfer, flags); } GType ArgsCache::instance_type() const { if (!m_is_method) return G_TYPE_NONE; return instance()->as_instance()->gtype(); } GITypeTag ArgsCache::return_tag() const { Argument* rval = return_value(); if (!rval) return GI_TYPE_TAG_VOID; return rval->return_tag(); } GITypeInfo* ArgsCache::return_type() const { Argument* rval = return_value(); if (!rval) return nullptr; return const_cast(rval->return_type()); } constexpr void ArgsCache::set_skip_all(uint8_t index, const char* name) { set_argument(index, name, GI_TRANSFER_NOTHING, GjsArgumentFlags::SKIP_ALL); } template void ArgsCache::set_array_argument(GICallableInfo* callable, uint8_t gi_index, GITypeInfo* type_info, GIDirection direction, GIArgInfo* arg, GjsArgumentFlags flags, int length_pos) { Arg::ExplicitArray* array; GIArgInfo length_arg; g_callable_info_load_arg(callable, length_pos, &length_arg); GITypeInfo length_type; g_arg_info_load_type(&length_arg, &length_type); if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) { GITransfer transfer = g_callable_info_get_caller_owns(callable); array = set_return(type_info, transfer, GjsArgumentFlags::NONE); } else { const char* arg_name = g_base_info_get_name(arg); GITransfer transfer = g_arg_info_get_ownership_transfer(arg); auto common_args = std::make_tuple(gi_index, arg_name, type_info, transfer, flags); if (direction == GI_DIRECTION_IN) { array = set_argument_auto(common_args); set_skip_all(length_pos, g_base_info_get_name(&length_arg)); } else if (direction == GI_DIRECTION_INOUT) { array = set_argument_auto(common_args); set_skip_all(length_pos, g_base_info_get_name(&length_arg)); } else { array = set_argument_auto(common_args); } } if (ArgKind == Arg::Kind::RETURN_VALUE || direction == GI_DIRECTION_OUT) { // Even if we skip the length argument most of time, we need to // do some basic initialization here. set_argument( length_pos, g_base_info_get_name(&length_arg), &length_type, GI_TRANSFER_NOTHING, static_cast(flags | GjsArgumentFlags::SKIP_ALL)); } array->set_array_length(length_pos, g_type_info_get_tag(&length_type), g_arg_info_get_direction(&length_arg)); } void ArgsCache::build_return(GICallableInfo* callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); if (!m_has_return) { *inc_counter_out = false; return; } GITypeInfo type_info; g_callable_info_load_return_type(callable, &type_info); GITransfer transfer = g_callable_info_get_caller_owns(callable); GITypeTag tag = g_type_info_get_tag(&type_info); *inc_counter_out = true; GjsArgumentFlags flags = GjsArgumentFlags::SKIP_IN; if (g_callable_info_may_return_null(callable)) flags |= GjsArgumentFlags::MAY_BE_NULL; switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_return(&type_info, transfer, flags); return; case GI_TYPE_TAG_INT8: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_INT16: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_INT32: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_UINT8: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_UINT16: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_UINT32: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_INT64: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_UINT64: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_FLOAT: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_DOUBLE: set_return>( &type_info, transfer, flags); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { set_return>( &type_info, transfer, flags); return; } else { set_return>( &type_info, transfer, flags); return; } case GI_TYPE_TAG_ARRAY: { int length_pos = g_type_info_get_array_length(&type_info); if (length_pos >= 0) { set_array_argument( callable, 0, &type_info, GI_DIRECTION_OUT, nullptr, flags, length_pos); return; } [[fallthrough]]; } default: break; } // in() is ignored for the return value, but skip_in is not (it is used // in the failure release path) set_return(&type_info, transfer, flags); } namespace Arg { Enum::Enum(GIEnumInfo* enum_info) { int64_t min = std::numeric_limits::max(); int64_t max = std::numeric_limits::min(); int n = g_enum_info_get_n_values(enum_info); for (int i = 0; i < n; i++) { GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); int64_t value = g_value_info_get_value(value_info); if (value > max) max = value; if (value < min) min = value; } // From the docs for g_value_info_get_value(): "This will always be // representable as a 32-bit signed or unsigned value. The use of gint64 as // the return type is to allow both." // We stuff them both into unsigned 32-bit fields, and use a flag to tell // whether we have to compare them as signed. m_min = static_cast(min); m_max = static_cast(max); m_unsigned = (min >= 0 && max > std::numeric_limits::max()); } Flags::Flags(GIEnumInfo* enum_info) { uint64_t mask = 0; int n = g_enum_info_get_n_values(enum_info); for (int i = 0; i < n; i++) { GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); int64_t value = g_value_info_get_value(value_info); // From the docs for g_value_info_get_value(): "This will always be // representable as a 32-bit signed or unsigned value. The use of // gint64 as the return type is to allow both." // We stuff both into an unsigned, int-sized field, matching the // internal representation of flags in GLib (which uses guint). mask |= static_cast(value); } m_mask = mask; } } // namespace Arg namespace arg_cache { [[nodiscard]] static inline bool is_gdk_atom(GIBaseInfo* info) { return strcmp("Atom", g_base_info_get_name(info)) == 0 && strcmp("Gdk", g_base_info_get_namespace(info)) == 0; } } // namespace arg_cache template void ArgsCache::build_interface_in_arg(uint8_t gi_index, GITypeInfo* type_info, GIBaseInfo* interface_info, GITransfer transfer, const char* name, GjsArgumentFlags flags) { GIInfoType interface_type = g_base_info_get_type(interface_info); auto base_args = std::make_tuple(gi_index, name, type_info, transfer, flags); auto common_args = std::tuple_cat(base_args, std::make_tuple(interface_info)); // We do some transfer magic later, so let's ensure we don't mess up. // Should not happen in practice. if (G_UNLIKELY(transfer == GI_TRANSFER_CONTAINER)) { set_argument_auto(base_args, INTERFACE_TRANSFER_CONTAINER); return; } switch (interface_type) { case GI_INFO_TYPE_ENUM: set_argument_auto(common_args); return; case GI_INFO_TYPE_FLAGS: set_argument_auto(common_args); return; case GI_INFO_TYPE_STRUCT: if (g_struct_info_is_foreign(interface_info)) { if constexpr (ArgKind == Arg::Kind::INSTANCE) set_argument_auto( common_args); else set_argument_auto( common_args); return; } [[fallthrough]]; case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_UNION: { GType gtype = g_registered_type_info_get_g_type(interface_info); if (interface_type == GI_INFO_TYPE_STRUCT && gtype == G_TYPE_NONE && !g_struct_info_is_gtype_struct(interface_info)) { if constexpr (ArgKind != Arg::Kind::INSTANCE) { // This covers cases such as GTypeInstance set_argument_auto( common_args); return; } } // Transfer handling is a bit complex here, because some of our in() // arguments know not to copy stuff if we don't need to. if (gtype == G_TYPE_VALUE) { if constexpr (ArgKind == Arg::Kind::INSTANCE) set_argument_auto(common_args); else if (transfer == GI_TRANSFER_NOTHING) set_argument_auto( common_args); else set_argument_auto(common_args); return; } if (arg_cache::is_gdk_atom(interface_info)) { // Fall back to the generic marshaller set_argument_auto( common_args); return; } if (gtype == G_TYPE_CLOSURE) { if (transfer == GI_TRANSFER_NOTHING && ArgKind != Arg::Kind::INSTANCE) set_argument_auto( common_args); else set_argument_auto(common_args); return; } if (gtype == G_TYPE_BYTES) { if (transfer == GI_TRANSFER_NOTHING && ArgKind != Arg::Kind::INSTANCE) set_argument_auto( common_args); else set_argument_auto(common_args); return; } if (g_type_is_a(gtype, G_TYPE_OBJECT)) { set_argument_auto(common_args); return; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { set_argument_auto( common_args); return; } if (interface_type == GI_INFO_TYPE_UNION) { if (gtype == G_TYPE_NONE) { // Can't handle unions without a GType set_argument_auto( base_args, UNREGISTERED_UNION); return; } set_argument_auto(common_args); return; } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { set_argument_auto(common_args); return; } if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { set_argument_auto(common_args); return; } // generic boxed type if (gtype == G_TYPE_NONE) { if (transfer != GI_TRANSFER_NOTHING) { // Can't transfer ownership of a structure type not // registered as a boxed set_argument_auto( base_args, UNREGISTERED_BOXED_WITH_TRANSFER); return; } set_argument_auto( common_args); return; } set_argument_auto(common_args); return; } break; case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_CONSTANT: case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: default: // Don't know how to handle this interface type (should not happen // in practice, for typelibs emitted by g-ir-compiler) set_argument_auto(base_args, UNSUPPORTED_TYPE); } } void ArgsCache::build_normal_in_arg(uint8_t gi_index, GITypeInfo* type_info, GIArgInfo* arg, GjsArgumentFlags flags) { // "Normal" in arguments are those arguments that don't require special // processing, and don't touch other arguments. // Main categories are: // - void* // - small numbers (fit in 32bit) // - big numbers (need a double) // - strings // - enums/flags (different from numbers in the way they're exposed in GI) // - objects (GObjects, boxed, unions, etc.) // - hashes // - sequences (null-terminated arrays, lists, etc.) const char* name = g_base_info_get_name(arg); GITransfer transfer = g_arg_info_get_ownership_transfer(arg); auto common_args = std::make_tuple(gi_index, name, type_info, transfer, flags); GITypeTag tag = g_type_info_get_tag(type_info); switch (tag) { case GI_TYPE_TAG_VOID: set_argument_auto(common_args); break; case GI_TYPE_TAG_BOOLEAN: set_argument_auto(common_args); break; case GI_TYPE_TAG_INT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_FLOAT: set_argument_auto>(common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UNICHAR: set_argument_auto(common_args); break; case GI_TYPE_TAG_GTYPE: set_argument_auto(common_args); break; case GI_TYPE_TAG_FILENAME: if (transfer == GI_TRANSFER_NOTHING) set_argument_auto(common_args); else set_argument_auto(common_args); break; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) set_argument_auto>( common_args); else set_argument_auto(common_args); break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); build_interface_in_arg(gi_index, type_info, interface_info, transfer, name, flags); return; } case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: default: // FIXME: Falling back to the generic marshaller set_argument_auto(common_args); } } void ArgsCache::build_normal_out_arg(uint8_t gi_index, GITypeInfo* type_info, GIArgInfo* arg, GjsArgumentFlags flags) { const char* name = g_base_info_get_name(arg); GITransfer transfer = g_arg_info_get_ownership_transfer(arg); auto common_args = std::make_tuple(gi_index, name, type_info, transfer, flags); GITypeTag tag = g_type_info_get_tag(type_info); switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_argument_auto(common_args); break; case GI_TYPE_TAG_INT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_FLOAT: set_argument_auto>(common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { set_argument_auto>( common_args); } else { set_argument_auto>( common_args); } return; default: set_argument_auto(common_args); } } void ArgsCache::build_normal_inout_arg(uint8_t gi_index, GITypeInfo* type_info, GIArgInfo* arg, GjsArgumentFlags flags) { const char* name = g_base_info_get_name(arg); GITransfer transfer = g_arg_info_get_ownership_transfer(arg); auto common_args = std::make_tuple(gi_index, name, type_info, transfer, flags); GITypeTag tag = g_type_info_get_tag(type_info); switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_argument_auto(common_args); break; case GI_TYPE_TAG_INT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT8: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT16: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT32: set_argument_auto>(common_args); return; case GI_TYPE_TAG_INT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_UINT64: set_argument_auto>(common_args); return; case GI_TYPE_TAG_FLOAT: set_argument_auto>(common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument_auto>(common_args); return; default: set_argument_auto(common_args); } } void ArgsCache::build_instance(GICallableInfo* callable) { if (!m_is_method) return; GIBaseInfo* interface_info = g_base_info_get_container(callable); // !owned GITransfer transfer = g_callable_info_get_instance_ownership_transfer(callable); // These cases could be covered by the generic marshaller, except that // there's no way to get GITypeInfo for a method's instance parameter. // Instead, special-case the arguments here that would otherwise go through // the generic marshaller. // See: https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/334 GIInfoType info_type = g_base_info_get_type(interface_info); if (info_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct(interface_info)) { set_instance(transfer); return; } if (info_type == GI_INFO_TYPE_OBJECT) { GType gtype = g_registered_type_info_get_g_type(interface_info); if (g_type_is_a(gtype, G_TYPE_PARAM)) { set_instance(transfer); return; } } build_interface_in_arg( Argument::ABSENT, nullptr, interface_info, transfer, nullptr, GjsArgumentFlags::NONE); } static constexpr bool type_tag_is_scalar(GITypeTag tag) { return GI_TYPE_TAG_IS_NUMERIC(tag) || tag == GI_TYPE_TAG_BOOLEAN || tag == GI_TYPE_TAG_GTYPE; } void ArgsCache::build_arg(uint8_t gi_index, GIDirection direction, GIArgInfo* arg, GICallableInfo* callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); GITypeInfo type_info; const char* arg_name = g_base_info_get_name(arg); g_arg_info_load_type(arg, &type_info); GITransfer transfer = g_arg_info_get_ownership_transfer(arg); GjsArgumentFlags flags = GjsArgumentFlags::NONE; if (g_arg_info_may_be_null(arg)) flags |= GjsArgumentFlags::MAY_BE_NULL; if (g_arg_info_is_caller_allocates(arg)) flags |= GjsArgumentFlags::CALLER_ALLOCATES; if (direction == GI_DIRECTION_IN) flags |= GjsArgumentFlags::SKIP_OUT; else if (direction == GI_DIRECTION_OUT) flags |= GjsArgumentFlags::SKIP_IN; *inc_counter_out = true; auto common_args = std::make_tuple(gi_index, arg_name, &type_info, transfer, flags); GITypeTag type_tag = g_type_info_get_tag(&type_info); if (direction == GI_DIRECTION_OUT && (flags & GjsArgumentFlags::CALLER_ALLOCATES)) { size_t size = 0; if (type_tag == GI_TYPE_TAG_ARRAY) { GIArrayType array_type = g_type_info_get_array_type(&type_info); switch (array_type) { case GI_ARRAY_TYPE_C: { GjsAutoTypeInfo param_info; int n_elements = g_type_info_get_array_fixed_size(&type_info); if (n_elements <= 0) break; param_info = g_type_info_get_param_type(&type_info, 0); GITypeTag param_tag = g_type_info_get_tag(param_info); size = gjs_type_get_element_size(param_tag, param_info); size *= n_elements; break; } default: break; } } else if (!type_tag_is_scalar(type_tag) && !g_type_info_is_pointer(&type_info)) { // Scalar out parameters should not be annotated with // caller-allocates, which is for structured types that need to be // allocated in order for the function to fill them in. size = gjs_type_get_element_size(type_tag, &type_info); } if (!size) { set_argument_auto( common_args, OUT_CALLER_ALLOCATES_NON_STRUCT); return; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info); GType gtype = g_registered_type_info_get_g_type(interface_info); if (g_type_is_a(gtype, G_TYPE_BOXED)) { auto* gjs_arg = set_argument_auto( common_args, gtype); gjs_arg->m_allocates_size = size; return; } } auto* gjs_arg = set_argument_auto(common_args); gjs_arg->m_allocates_size = size; return; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info); if (interface_info.type() == GI_INFO_TYPE_CALLBACK) { if (direction != GI_DIRECTION_IN) { // Can't do callbacks for out or inout set_argument_auto(common_args, CALLBACK_OUT); return; } if (strcmp(interface_info.name(), "DestroyNotify") == 0 && strcmp(interface_info.ns(), "GLib") == 0) { // We don't know (yet) what to do with GDestroyNotify appearing // before a callback. If the callback comes later in the // argument list, then the invalid argument will be // overwritten with the 'skipped' one. If no callback follows, // then this is probably an unsupported function, so the // function invocation code will check this and throw. set_argument_auto( common_args, DESTROY_NOTIFY_NO_CALLBACK); *inc_counter_out = false; } else { auto* gjs_arg = set_argument_auto( common_args, interface_info); int destroy_pos = g_arg_info_get_destroy(arg); int closure_pos = g_arg_info_get_closure(arg); if (destroy_pos >= 0) set_skip_all(destroy_pos); if (closure_pos >= 0) set_skip_all(closure_pos); if (destroy_pos >= 0 && closure_pos < 0) { set_argument_auto( common_args, DESTROY_NOTIFY_NO_USER_DATA); return; } gjs_arg->m_scope = g_arg_info_get_scope(arg); gjs_arg->set_callback_destroy_pos(destroy_pos); gjs_arg->set_callback_closure_pos(closure_pos); } return; } } if (type_tag == GI_TYPE_TAG_ARRAY && g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) { int length_pos = g_type_info_get_array_length(&type_info); if (length_pos >= 0) { Argument* cached_length = argument(length_pos); bool skip_length = cached_length && !(cached_length->skip_in() && cached_length->skip_out()); set_array_argument(callable, gi_index, &type_info, direction, arg, flags, length_pos); if (length_pos < gi_index && skip_length) { // we already collected length_pos, remove it *inc_counter_out = false; } return; } else if (g_type_info_is_zero_terminated(&type_info)) { if (direction == GI_DIRECTION_IN) { set_argument_auto(common_args); return; } else if (direction == GI_DIRECTION_INOUT) { set_argument_auto(common_args); return; } } else if (g_type_info_get_array_fixed_size(&type_info) >= 0) { if (direction == GI_DIRECTION_IN) { set_argument_auto(common_args); return; } else if (direction == GI_DIRECTION_INOUT) { set_argument_auto(common_args); return; } } } if (direction == GI_DIRECTION_IN) build_normal_in_arg(gi_index, &type_info, arg, flags); else if (direction == GI_DIRECTION_INOUT) build_normal_inout_arg(gi_index, &type_info, arg, flags); else build_normal_out_arg(gi_index, &type_info, arg, flags); return; } } // namespace Gjs cjs-128.1/gi/arg-cache.h0000664000175000017500000002024415116312211013605 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna // SPDX-FileCopyrightText: 2020 Marco Trevisan #ifndef GI_ARG_CACHE_H_ #define GI_ARG_CACHE_H_ #include #include #include #include #include #include #include "gi/arg.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" class GjsFunctionCallState; enum NotIntrospectableReason : uint8_t { CALLBACK_OUT, DESTROY_NOTIFY_NO_CALLBACK, DESTROY_NOTIFY_NO_USER_DATA, INTERFACE_TRANSFER_CONTAINER, OUT_CALLER_ALLOCATES_NON_STRUCT, UNREGISTERED_BOXED_WITH_TRANSFER, UNREGISTERED_UNION, UNSUPPORTED_TYPE, LAST_REASON }; namespace Gjs { namespace Arg { using ReturnValue = struct GenericOut; struct Instance; enum class Kind { NORMAL, INSTANCE, RETURN_VALUE, }; } // namespace Arg struct Argument { virtual ~Argument() = default; GJS_JSAPI_RETURN_CONVENTION virtual bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* in_argument, JS::HandleValue value); GJS_JSAPI_RETURN_CONVENTION virtual bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* out_argument, JS::MutableHandleValue value); GJS_JSAPI_RETURN_CONVENTION virtual bool release(JSContext* cx, GjsFunctionCallState*, GIArgument* in_argument, GIArgument* out_argument); virtual GjsArgumentFlags flags() const { GjsArgumentFlags flags = GjsArgumentFlags::NONE; if (m_skip_in) flags |= GjsArgumentFlags::SKIP_IN; else flags |= GjsArgumentFlags::ARG_IN; if (m_skip_out) flags |= GjsArgumentFlags::SKIP_OUT; else flags |= GjsArgumentFlags::ARG_OUT; return flags; } // Introspected functions can have up to 253 arguments. The callback // closure or destroy notify parameter may have a value of 255 to indicate // that it is absent. static constexpr uint8_t MAX_ARGS = std::numeric_limits::max() - 2; static constexpr uint8_t ABSENT = std::numeric_limits::max(); constexpr const char* arg_name() const { return m_arg_name; } constexpr bool skip_in() const { return m_skip_in; } constexpr bool skip_out() const { return m_skip_out; } protected: constexpr Argument() : m_skip_in(false), m_skip_out(false) {} virtual GITypeTag return_tag() const { return GI_TYPE_TAG_VOID; } virtual const GITypeInfo* return_type() const { return nullptr; } virtual const Arg::Instance* as_instance() const { return nullptr; } constexpr void set_instance_parameter() { m_arg_name = "instance parameter"; m_skip_out = true; } constexpr void set_return_value() { m_arg_name = "return value"; } bool invalid(JSContext*, const char* func = nullptr) const; const char* m_arg_name = nullptr; bool m_skip_in : 1; bool m_skip_out : 1; private: friend struct ArgsCache; template static GjsAutoCppPointer make(uint8_t index, const char* name, GITypeInfo* type_info, GITransfer transfer, GjsArgumentFlags flags, Args&&... args); }; using ArgumentPtr = GjsAutoCppPointer; // This is a trick to print out the sizes of the structs at compile time, in // an error message: // template struct Measure; // Measure arg_cache_size; #if defined(__x86_64__) && defined(__clang__) && !defined(_MSC_VER) # define GJS_DO_ARGUMENTS_SIZE_CHECK 1 // This isn't meant to be comprehensive, but should trip on at least one CI job // if sizeof(Gjs::Argument) is increased. // Note that this check is not applicable for clang-cl builds, as Windows is // an LLP64 system static_assert(sizeof(Argument) <= 24, "Think very hard before increasing the size of Gjs::Argument. " "One is allocated for every argument to every introspected " "function."); #endif // x86-64 clang struct ArgsCache { GJS_JSAPI_RETURN_CONVENTION bool initialize(JSContext* cx, GICallableInfo* callable); // COMPAT: in C++20, use default initializers for these bitfields ArgsCache() : m_is_method(false), m_has_return(false) {} constexpr bool initialized() { return m_args != nullptr; } constexpr void clear() { m_args.reset(); } void build_arg(uint8_t gi_index, GIDirection, GIArgInfo*, GICallableInfo*, bool* inc_counter_out); void build_return(GICallableInfo* callable, bool* inc_counter_out); void build_instance(GICallableInfo* callable); GType instance_type() const; GITypeTag return_tag() const; GITypeInfo* return_type() const; private: void build_normal_in_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, GjsArgumentFlags); void build_normal_out_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, GjsArgumentFlags); void build_normal_inout_arg(uint8_t gi_index, GITypeInfo*, GIArgInfo*, GjsArgumentFlags); template void build_interface_in_arg(uint8_t gi_index, GITypeInfo*, GIBaseInfo*, GITransfer, const char* name, GjsArgumentFlags); template constexpr T* set_argument(uint8_t index, const char* name, GITypeInfo*, GITransfer, GjsArgumentFlags flags, Args&&... args); template constexpr T* set_argument(uint8_t index, const char* name, GITransfer, GjsArgumentFlags flags, Args&&... args); template constexpr T* set_argument_auto(Args&&... args); template constexpr T* set_argument_auto(Tuple&& tuple, Args&&... args); template void set_array_argument(GICallableInfo* callable, uint8_t gi_index, GITypeInfo*, GIDirection, GIArgInfo*, GjsArgumentFlags flags, int length_pos); template constexpr T* set_return(GITypeInfo*, GITransfer, GjsArgumentFlags); template constexpr T* set_instance(GITransfer, GjsArgumentFlags flags = GjsArgumentFlags::NONE); constexpr void set_skip_all(uint8_t index, const char* name = nullptr); template constexpr uint8_t arg_index(uint8_t index [[maybe_unused]] = Argument::MAX_ARGS) const { if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) return 0; else if constexpr (ArgKind == Arg::Kind::INSTANCE) return (m_has_return ? 1 : 0); else if constexpr (ArgKind == Arg::Kind::NORMAL) return (m_has_return ? 1 : 0) + (m_is_method ? 1 : 0) + index; } template constexpr ArgumentPtr& arg_get(uint8_t index = Argument::MAX_ARGS) const { return m_args[arg_index(index)]; } public: constexpr Argument* argument(uint8_t index) const { return arg_get(index).get(); } constexpr Argument* instance() const { if (!m_is_method) return nullptr; return arg_get().get(); } constexpr Argument* return_value() const { if (!m_has_return) return nullptr; return arg_get().get(); } private: GjsAutoCppPointer m_args; bool m_is_method : 1; bool m_has_return : 1; }; } // namespace Gjs #endif // GI_ARG_CACHE_H_ cjs-128.1/gi/arg-inl.h0000664000175000017500000002341715116312211013331 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include #include // for nullptr_t #include #include // for to_string #include #include #include // for GType #include // for gboolean #include // for HandleValue #include "gi/js-value-inl.h" #include "gi/utils-inl.h" #include "cjs/macros.h" // GIArgument accessor templates // // These are intended to make access to the GIArgument union more type-safe and // reduce bugs that occur from assigning to one member and reading from another. // (These bugs often work fine on one processor architecture but crash on // another.) // // gjs_arg_member(GIArgument*) - returns a reference to the appropriate union // member that would hold the type T. Rarely used, unless as a pointer to a // return location. // gjs_arg_get(GIArgument*) - returns the value of type T from the // appropriate union member. // gjs_arg_set(GIArgument*, T) - sets the appropriate union member for type T. // gjs_arg_unset(GIArgument*) - sets the appropriate zero value in the // appropriate union member for type T. // gjs_arg_steal(GIArgument*) - sets the appropriate zero value in the // appropriate union member for type T and returns the replaced value. template [[nodiscard]] constexpr inline decltype(auto) gjs_arg_member(GIArgument* arg) { return (arg->*member); } /* The tag is needed to disambiguate types such as gboolean and GType * which are in fact typedef's of other generic types. * Setting a tag for a type allows to perform proper specialization. */ template [[nodiscard]] constexpr inline decltype(auto) gjs_arg_member(GIArgument* arg) { if constexpr (TAG == GI_TYPE_TAG_VOID) { if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_boolean>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_int8>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint8>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_int16>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint16>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_int32>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint32>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_int64>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint64>(arg); // gunichar is stored in v_uint32 if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint32>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_float>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_double>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_string>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_pointer>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_pointer>(arg); if constexpr (std::is_pointer()) { using NonconstPtrT = std::add_pointer_t< std::remove_const_t>>; return reinterpret_cast( gjs_arg_member<&GIArgument::v_pointer>(arg)); } } if constexpr (TAG == GI_TYPE_TAG_BOOLEAN && std::is_same_v) return gjs_arg_member<&GIArgument::v_boolean>(arg); if constexpr (TAG == GI_TYPE_TAG_GTYPE && std::is_same_v) { // GType is defined differently on 32-bit vs. 64-bit architectures. if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_size>(arg); else if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_ulong>(arg); } if constexpr (TAG == GI_TYPE_TAG_INTERFACE && std::is_integral_v) { if constexpr (std::is_signed_v) return gjs_arg_member<&GIArgument::v_int>(arg); else return gjs_arg_member<&GIArgument::v_uint>(arg); } } typedef enum { GJS_TYPE_TAG_LONG = 0, } ExtraTag; template [[nodiscard]] constexpr inline decltype(auto) gjs_arg_member(GIArgument* arg) { if constexpr (TAG == GJS_TYPE_TAG_LONG && std::is_same_v) // NOLINT(runtime/int) return gjs_arg_member<&GIArgument::v_long>(arg); else if constexpr (TAG == GJS_TYPE_TAG_LONG && std::is_same_v) // NOLINT(runtime/int) return gjs_arg_member<&GIArgument::v_ulong>(arg); } template constexpr inline void gjs_arg_set(GIArgument* arg, T v) { if constexpr (std::is_pointer_v) { using NonconstPtrT = std::add_pointer_t>>; gjs_arg_member(arg) = const_cast(v); } else { if constexpr (std::is_same_v || (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN)) v = !!v; gjs_arg_member(arg) = v; } } template constexpr inline void gjs_arg_set(GIArgument* arg, T v) { gjs_arg_member(arg) = v; } // Store function pointers as void*. It is a requirement of GLib that your // compiler can do this template constexpr inline void gjs_arg_set(GIArgument* arg, ReturnT (*v)(Args...)) { gjs_arg_member(arg) = reinterpret_cast(v); } template constexpr inline std::enable_if_t> gjs_arg_set( GIArgument* arg, void* v) { gjs_arg_set(arg, gjs_pointer_to_int(v)); } template [[nodiscard]] constexpr inline T gjs_arg_get(GIArgument* arg) { if constexpr (std::is_same_v || (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN)) return T(!!gjs_arg_member(arg)); return gjs_arg_member(arg); } template [[nodiscard]] constexpr inline T gjs_arg_get(GIArgument* arg) { return gjs_arg_member(arg); } template [[nodiscard]] constexpr inline void* gjs_arg_get_as_pointer(GIArgument* arg) { return gjs_int_to_pointer(gjs_arg_get(arg)); } template constexpr inline void gjs_arg_unset(GIArgument* arg) { if constexpr (std::is_pointer_v) gjs_arg_set(arg, nullptr); else gjs_arg_set(arg, static_cast(0)); } template [[nodiscard]] constexpr inline T gjs_arg_steal(GIArgument* arg) { auto val = gjs_arg_get(arg); gjs_arg_unset(arg); return val; } // Implementation to store rounded (u)int64_t numbers into double template [[nodiscard]] inline constexpr std::enable_if_t< std::is_integral_v && (std::numeric_limits::max() > std::numeric_limits::max()), double> gjs_arg_get_maybe_rounded(GIArgument* arg) { BigT val = gjs_arg_get(arg); if (val < Gjs::min_safe_big_number() || val > Gjs::max_safe_big_number()) { g_warning( "Value %s cannot be safely stored in a JS Number " "and may be rounded", std::to_string(val).c_str()); } return static_cast(val); } template GJS_JSAPI_RETURN_CONVENTION inline bool gjs_arg_set_from_js_value( JSContext* cx, const JS::HandleValue& value, GIArgument* arg, bool* out_of_range) { if constexpr (Gjs::type_has_js_getter()) return Gjs::js_value_to_c(cx, value, &gjs_arg_member(arg)); Gjs::JsValueHolder::Relaxed val{}; if (!Gjs::js_value_to_c_checked(cx, value, &val, out_of_range)) return false; if (*out_of_range) return false; gjs_arg_set(arg, val); return true; } // A helper function to retrieve array lengths from a GIArgument (letting the // compiler generate good instructions in case of big endian machines) [[nodiscard]] constexpr size_t gjs_gi_argument_get_array_length( GITypeTag tag, GIArgument* arg) { switch (tag) { case GI_TYPE_TAG_INT8: return gjs_arg_get(arg); case GI_TYPE_TAG_UINT8: return gjs_arg_get(arg); case GI_TYPE_TAG_INT16: return gjs_arg_get(arg); case GI_TYPE_TAG_UINT16: return gjs_arg_get(arg); case GI_TYPE_TAG_INT32: return gjs_arg_get(arg); case GI_TYPE_TAG_UINT32: return gjs_arg_get(arg); case GI_TYPE_TAG_INT64: return gjs_arg_get(arg); case GI_TYPE_TAG_UINT64: return gjs_arg_get(arg); default: g_assert_not_reached(); } } cjs-128.1/gi/arg-types-inl.h0000664000175000017500000000442315116312211014467 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include #include #include // for GType #include // for gboolean namespace Gjs { template constexpr inline const char* static_type_name() = delete; template <> constexpr inline const char* static_type_name() { return "bool"; } template <> constexpr inline const char* static_type_name() { return "int8"; } template <> constexpr inline const char* static_type_name() { return "uint8"; } template <> constexpr inline const char* static_type_name() { return "int16"; } template <> constexpr inline const char* static_type_name() { return "uint16"; } template <> constexpr inline const char* static_type_name() { return "int32"; } template <> constexpr inline const char* static_type_name() { return "uint32"; } template <> constexpr inline const char* static_type_name() { return "char32_t"; } template <> constexpr inline const char* static_type_name() { return "int64"; } template <> constexpr inline const char* static_type_name() { return "uint64"; } template <> constexpr inline const char* static_type_name() { return "float"; } template <> constexpr inline const char* static_type_name() { return "double"; } template <> constexpr inline const char* static_type_name() { return "pointer"; } template <> constexpr inline const char* static_type_name() { return "GType"; } template <> constexpr inline const char* static_type_name() { return "boolean"; } template <> constexpr inline const char* static_type_name() { return "GValue"; } template <> inline const char* static_type_name() { return "string"; } template <> inline const char* static_type_name() { return "constant string"; } template <> inline const char* static_type_name() { return "void"; } } // namespace Gjs cjs-128.1/gi/arg.cpp0000664000175000017500000036037715116312211013115 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2020 Canonical, Ltd. #include #include #include #include // for strcmp, strlen, memcpy #include #include #include #include #include #include #include #include // for JS_ReportOutOfMemory #include #include // for RootedVector, MutableWrappedPtrOp... #include #include // for JS_GetElement, JS_HasPropertyById #include // for JSPROP_ENUMERATE #include #include #include #include // for UniqueChars #include #include #include #include // for InformalValueTypeName, IdVector #include #include "gi/arg-inl.h" #include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/closure.h" #include "gi/foreign.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/interface.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/enum-utils.h" #include "cjs/macros.h" #include "cjs/jsapi-util.h" #include "util/log.h" #include "util/misc.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( JSContext*, GITransfer, GITypeInfo*, GITypeTag, GjsArgumentType, GjsArgumentFlags, GIArgument*); static void throw_invalid_argument(JSContext* cx, JS::HandleValue value, GITypeInfo* arginfo, const char* arg_name, GjsArgumentType arg_type); bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value) { /* Do proper value check for flags with GType's */ if (gtype != G_TYPE_NONE) { GjsAutoTypeClass gflags_class(gtype); uint32_t tmpval = static_cast(value); /* check all bits are valid bits for the flag and is a 32 bit flag*/ if ((tmpval &= gflags_class->mask) != value) { /* Not a guint32 with invalid mask values*/ gjs_throw(cx, "0x%" PRIx64 " is not a valid value for flags %s", value, g_type_name(gtype)); return false; } } return true; } GJS_JSAPI_RETURN_CONVENTION static bool _gjs_enum_value_is_valid(JSContext* cx, GIEnumInfo* enum_info, int64_t value) { bool found; int n_values; int i; n_values = g_enum_info_get_n_values(enum_info); found = false; for (i = 0; i < n_values; ++i) { GjsAutoValueInfo value_info = g_enum_info_get_value(enum_info, i); int64_t enum_value = g_value_info_get_value(value_info); if (enum_value == value) { found = true; break; } } if (!found) { gjs_throw(cx, "%" PRId64 " is not a valid value for enumeration %s", value, g_base_info_get_name(enum_info)); } return found; } [[nodiscard]] static bool _gjs_enum_uses_signed_type(GIEnumInfo* enum_info) { switch (g_enum_info_get_storage_type(enum_info)) { case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: return true; default: return false; } } // This is hacky - g_function_info_invoke() and g_field_info_get/set_field() // expect the enum value in gjs_arg_member(arg) and depend on all flags and // enumerations being passed on the stack in a 32-bit field. See FIXME comment // in g_field_info_get_field(). The same assumption of enums cast to 32-bit // signed integers is found in g_value_set_enum()/g_value_set_flags(). [[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value) { if (_gjs_enum_uses_signed_type (enum_info)) return int64_t(int_value); else return int64_t(uint32_t(int_value)); } /* Here for symmetry, but result is the same for the two cases */ [[nodiscard]] static int _gjs_enum_to_int(int64_t value) { return static_cast(value); } /* Check if an argument of the given needs to be released if we created it * from a JS value to pass it into a function and aren't transferring ownership. */ [[nodiscard]] static bool type_needs_release(GITypeInfo* type_info, GITypeTag type_tag) { switch (type_tag) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_UTF8: return true; case GI_TYPE_TAG_INTERFACE: { GType gtype; GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info != nullptr); switch (interface_info.type()) { case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_UNION: case GI_INFO_TYPE_BOXED: // These are subtypes of GIRegisteredTypeInfo for which the // cast is safe gtype = g_registered_type_info_get_g_type(interface_info); break; default: gtype = G_TYPE_NONE; } if (g_type_is_a(gtype, G_TYPE_CLOSURE)) return true; else if (g_type_is_a(gtype, G_TYPE_VALUE)) return true; else return false; } default: return false; } } [[nodiscard]] static inline bool is_string_type(GITypeTag tag) { return tag == GI_TYPE_TAG_FILENAME || tag == GI_TYPE_TAG_UTF8; } /* Check if an argument of the given needs to be released if we obtained it * from out argument (or the return value), and we're transferring ownership */ [[nodiscard]] static bool type_needs_out_release(GITypeInfo* type_info, GITypeTag type_tag) { switch (type_tag) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_UTF8: return true; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); switch (interface_info.type()) { case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: return false; case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_UNION: return g_type_info_is_pointer(type_info); case GI_INFO_TYPE_OBJECT: return true; default: return false; } } default: return false; } } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list( JSContext* cx, const JS::HandleValue& value, GITypeInfo* type_info, GITransfer transfer, const char* arg_name, GjsArgumentType arg_type, T** list_p) { static_assert(std::is_same_v || std::is_same_v); // While a list can be NULL in C, that means empty array in JavaScript, it // doesn't mean null in JavaScript. bool is_array; if (!JS::IsArrayObject(cx, value, &is_array)) return false; if (!is_array) { throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } JS::RootedObject array_obj(cx, &value.toObject()); uint32_t length; if (!JS::GetArrayLength(cx, array_obj, &length)) { throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release (param_info, g_type_info_get_tag(param_info))) { /* FIXME: to make this work, we'd have to keep a list of temporary * GIArguments for the function call so we could free them after * the surrounding container had been freed by the callee. */ gjs_throw(cx, "Container transfer for in parameters not supported"); return false; } transfer = GI_TRANSFER_NOTHING; } JS::RootedObject array(cx, value.toObjectOrNull()); JS::RootedValue elem(cx); T* list = nullptr; for (size_t i = 0; i < length; ++i) { elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array, i, &elem)) { gjs_throw(cx, "Missing array element %zu", i); return false; } /* FIXME we don't know if the list elements can be NULL. * gobject-introspection needs to tell us this. * Always say they can't for now. */ GIArgument elem_arg; if (!gjs_value_to_gi_argument(cx, elem, param_info, GJS_ARGUMENT_LIST_ELEMENT, transfer, &elem_arg)) { return false; } void* hash_pointer = g_type_info_hash_pointer_from_argument(param_info, &elem_arg); if constexpr (std::is_same_v) list = g_list_prepend(list, hash_pointer); else if constexpr (std::is_same_v) list = g_slist_prepend(list, hash_pointer); } if constexpr (std::is_same_v) list = g_list_reverse(list); else if constexpr (std::is_same_v) list = g_slist_reverse(list); *list_p = list; return true; } [[nodiscard]] static GHashTable* create_hash_table_for_key_type( GITypeTag key_type) { /* Don't use key/value destructor functions here, because we can't * construct correct ones in general if the value type is complex. * Rely on the type-aware gi_argument_release functions. */ if (is_string_type(key_type)) return g_hash_table_new(g_str_hash, g_str_equal); return g_hash_table_new(NULL, NULL); } template GJS_JSAPI_RETURN_CONVENTION static bool hashtable_int_key( JSContext* cx, const JS::HandleValue& value, void** pointer_out) { static_assert(std::is_integral_v, "Need an integer"); bool out_of_range = false; Gjs::JsValueHolder::Strict i; if (!Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range)) return false; if (out_of_range) { gjs_throw(cx, "value is out of range for hash table key of type %s", Gjs::static_type_name()); } *pointer_out = gjs_int_to_pointer(i); return true; } /* Converts a JS::Value to a GHashTable key, stuffing it into @pointer_out if * possible, otherwise giving the location of an allocated key in @pointer_out. */ GJS_JSAPI_RETURN_CONVENTION static bool value_to_ghashtable_key(JSContext* cx, JS::HandleValue value, GITypeTag type_tag, void** pointer_out) { bool unsupported = false; g_assert((value.isString() || value.isInt32()) && "keys from JS_Enumerate must be non-symbol property keys"); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting JS::Value to GHashTable key %s", g_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_BOOLEAN: /* This doesn't seem particularly useful, but it's easy */ *pointer_out = gjs_int_to_pointer(JS::ToBoolean(value)); break; case GI_TYPE_TAG_UNICHAR: if (value.isInt32()) { *pointer_out = gjs_int_to_pointer(value.toInt32()); } else { uint32_t ch; if (!gjs_unichar_from_string(cx, value, &ch)) return false; *pointer_out = gjs_int_to_pointer(ch); } break; case GI_TYPE_TAG_INT8: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_INT16: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_INT32: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_UINT8: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_UINT16: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_UINT32: if (!hashtable_int_key(cx, value, pointer_out)) return false; break; case GI_TYPE_TAG_FILENAME: { GjsAutoChar cstr; JS::RootedValue str_val(cx, value); if (!str_val.isString()) { JS::RootedString str(cx, JS::ToString(cx, str_val)); str_val.setString(str); } if (!gjs_string_to_filename(cx, str_val, &cstr)) return false; *pointer_out = cstr.release(); break; } case GI_TYPE_TAG_UTF8: { JS::RootedString str(cx); if (!value.isString()) str = JS::ToString(cx, value); else str = value.toString(); JS::UniqueChars cstr(JS_EncodeStringToUTF8(cx, str)); if (!cstr) return false; *pointer_out = g_strdup(cstr.get()); break; } case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: /* FIXME: The above four could be supported, but are currently not. The ones * below cannot be key types in a regular JS object; we would need to allow * marshalling Map objects into GHashTables to support those. */ case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ARRAY: unsupported = true; break; default: g_warning("Unhandled type %s for GHashTable key conversion", g_type_tag_to_string(type_tag)); unsupported = true; break; } if (G_UNLIKELY(unsupported)) { gjs_throw(cx, "Type %s not supported for hash table keys", g_type_tag_to_string(type_tag)); return false; } return true; } template [[nodiscard]] static T* heap_value_new_from_arg(GIArgument* val_arg) { T* heap_val = g_new(T, 1); *heap_val = gjs_arg_get(val_arg); return heap_val; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_object_to_g_hash(JSContext* context, JS::HandleObject props, GITypeInfo* type_info, GITransfer transfer, GHashTable** hash_p) { size_t id_ix, id_len; g_assert(props && "Property bag cannot be null"); GjsAutoTypeInfo key_param_info = g_type_info_get_param_type(type_info, 0); GjsAutoTypeInfo val_param_info = g_type_info_get_param_type(type_info, 1); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release (key_param_info, g_type_info_get_tag(key_param_info)) || type_needs_release (val_param_info, g_type_info_get_tag(val_param_info))) { /* FIXME: to make this work, we'd have to keep a list of temporary * GIArguments for the function call so we could free them after * the surrounding container had been freed by the callee. */ gjs_throw(context, "Container transfer for in parameters not supported"); return false; } transfer = GI_TRANSFER_NOTHING; } JS::Rooted ids(context, context); if (!JS_Enumerate(context, props, &ids)) return false; GITypeTag key_tag = g_type_info_get_tag(key_param_info); GjsAutoPointer result = create_hash_table_for_key_type(key_tag); JS::RootedValue key_js(context), val_js(context); JS::RootedId cur_id(context); for (id_ix = 0, id_len = ids.length(); id_ix < id_len; ++id_ix) { cur_id = ids[id_ix]; gpointer key_ptr, val_ptr; GIArgument val_arg = { 0 }; if (!JS_IdToValue(context, cur_id, &key_js) || // Type check key type. !value_to_ghashtable_key(context, key_js, key_tag, &key_ptr) || !JS_GetPropertyById(context, props, cur_id, &val_js) || // Type check and convert value to a C type !gjs_value_to_gi_argument(context, val_js, val_param_info, nullptr, GJS_ARGUMENT_HASH_ELEMENT, transfer, GjsArgumentFlags::MAY_BE_NULL, &val_arg)) return false; GITypeTag val_type = g_type_info_get_tag(val_param_info); /* Use heap-allocated values for types that don't fit in a pointer */ if (val_type == GI_TYPE_TAG_INT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_UINT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_FLOAT) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (val_type == GI_TYPE_TAG_DOUBLE) { val_ptr = heap_value_new_from_arg(&val_arg); } else { // Other types are simply stuffed inside the pointer val_ptr = g_type_info_hash_pointer_from_argument(val_param_info, &val_arg); } #if __GNUC__ >= 8 // clang-format off _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") #endif // The compiler isn't smart enough to figure out that key_ptr will // always be initialized if value_to_ghashtable_key() returns true. g_hash_table_insert(result, key_ptr, val_ptr); #if __GNUC__ >= 8 _Pragma("GCC diagnostic pop") #endif // clang-format on } *hash_p = result.release(); return true; } template [[nodiscard]] constexpr T* array_allocate(size_t length) { if constexpr (std::is_same_v) return g_new0(char*, length); T* array = g_new(T, length); array[length - 1] = {0}; return array; } template GJS_JSAPI_RETURN_CONVENTION static bool js_value_to_c_strict( JSContext* cx, const JS::HandleValue& value, T* out) { using ValueHolderT = Gjs::JsValueHolder::Strict; if constexpr (Gjs::type_has_js_getter()) return Gjs::js_value_to_c(cx, value, out); ValueHolderT v; bool ret = Gjs::js_value_to_c(cx, value, &v); *out = v; return ret; } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_auto_array( JSContext* cx, JS::Value array_value, size_t length, void** arr_p) { JS::RootedObject array(cx, array_value.toObjectOrNull()); JS::RootedValue elem(cx); // Add one so we're always zero terminated GjsSmartPointer result = array_allocate(length + 1); for (size_t i = 0; i < length; ++i) { elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array, i, &elem)) { gjs_throw(cx, "Missing array element %" G_GSIZE_FORMAT, i); return false; } if (!js_value_to_c_strict(cx, elem, &result[i])) { gjs_throw(cx, "Invalid element in %s array", Gjs::static_type_name()); return false; } } *arr_p = result.release(); return true; } bool gjs_array_from_strv(JSContext *context, JS::MutableHandleValue value_p, const char **strv) { guint i; JS::RootedValueVector elems(context); /* We treat a NULL strv as an empty array, since this function should always * set an array value when returning true. * Another alternative would be to set value_p to JS::NullValue, but clients * would need to always check for both an empty array and null if that was * the case. */ for (i = 0; strv != NULL && strv[i] != NULL; i++) { if (!elems.growBy(1)) { JS_ReportOutOfMemory(context); return false; } if (!gjs_string_from_utf8(context, strv[i], elems[i])) return false; } JSObject* obj = JS::NewArrayObject(context, elems); if (!obj) return false; value_p.setObject(*obj); return true; } bool gjs_array_to_strv(JSContext *context, JS::Value array_value, unsigned int length, void **arr_p) { return gjs_array_to_auto_array(context, array_value, length, arr_p); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_string_to_intarray(JSContext* context, JS::HandleString str, GITypeTag element_type, void** arr_p, size_t* length) { char16_t *result16; switch (element_type) { case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: { JS::UniqueChars result; if (!gjs_string_to_utf8_n(context, str, &result, length)) return false; *arr_p = _gjs_memdup2(result.get(), *length); return true; } case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: { if (!gjs_string_get_char16_data(context, str, &result16, length)) return false; *arr_p = result16; return true; } case GI_TYPE_TAG_UNICHAR: { gunichar* result_ucs4; if (!gjs_string_to_ucs4(context, str, &result_ucs4, length)) return false; *arr_p = result_ucs4; return true; } default: /* can't convert a string to this type */ gjs_throw(context, "Cannot convert string to array of '%s'", g_type_tag_to_string(element_type)); return false; } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_ptrarray(JSContext *context, JS::Value array_value, unsigned int length, GITransfer transfer, GITypeInfo *param_info, void **arr_p) { unsigned int i; JS::RootedObject array_obj(context, array_value.toObjectOrNull()); JS::RootedValue elem(context); /* Always one extra element, to cater for null terminated arrays */ GjsAutoPointer array = array_allocate(length + 1); for (i = 0; i < length; i++) { GIArgument arg; gjs_arg_unset(&arg); elem = JS::UndefinedValue(); if (!JS_GetElement(context, array_obj, i, &elem)) { gjs_throw(context, "Missing array element %u", i); return false; } if (!gjs_value_to_gi_argument(context, elem, param_info, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) { gjs_throw(context, "Invalid element in array"); return false; } array[i] = gjs_arg_get(&arg); } *arr_p = array.release(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_flat_array(JSContext* cx, JS::HandleValue array_value, unsigned length, GITypeInfo* param_info, size_t param_size, void** arr_p) { g_assert((param_size > 0) && "Only flat arrays of elements of known size are supported"); GjsAutoPointer flat_array = g_new0(uint8_t, param_size * length); JS::RootedObject array(cx, &array_value.toObject()); JS::RootedValue elem(cx); for (unsigned i = 0; i < length; i++) { elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array, i, &elem)) { gjs_throw(cx, "Missing array element %u", i); return false; } GIArgument arg; if (!gjs_value_to_gi_argument(cx, elem, param_info, GJS_ARGUMENT_ARRAY_ELEMENT, GI_TRANSFER_NOTHING, &arg)) return false; memcpy(&flat_array[param_size * i], gjs_arg_get(&arg), param_size); } *arr_p = flat_array.release(); return true; } [[nodiscard]] static bool is_gvalue(GIBaseInfo* info) { switch (g_base_info_get_type(info)) { case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_BOXED: { GType gtype = g_registered_type_info_get_g_type(info); return g_type_is_a(gtype, G_TYPE_VALUE); } default: return false; } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_array(JSContext* context, JS::HandleValue array_value, size_t length, GITransfer transfer, GITypeInfo* param_info, void** arr_p) { GITypeTag element_type = g_type_info_get_storage_type(param_info); switch (element_type) { case GI_TYPE_TAG_UTF8: return gjs_array_to_strv (context, array_value, length, arr_p); case GI_TYPE_TAG_BOOLEAN: return gjs_array_to_auto_array( context, array_value, length, arr_p); case GI_TYPE_TAG_UNICHAR: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_UINT8: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_INT8: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_UINT16: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_INT16: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_UINT32: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_INT32: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_INT64: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_UINT64: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_FLOAT: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_DOUBLE: return gjs_array_to_auto_array(context, array_value, length, arr_p); case GI_TYPE_TAG_GTYPE: return gjs_array_to_auto_array( context, array_value, length, arr_p); /* Everything else is a pointer type */ case GI_TYPE_TAG_INTERFACE: if (!g_type_info_is_pointer(param_info)) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); if (is_gvalue(interface_info)) { // Special case for GValue "flat arrays", this could also // using the generic case, but if we do so we're leaking atm. return gjs_array_to_auto_array(context, array_value, length, arr_p); } size_t element_size = gjs_type_get_element_size( g_type_info_get_tag(param_info), param_info); if (element_size) { return gjs_array_to_flat_array(context, array_value, length, param_info, element_size, arr_p); } } [[fallthrough]]; case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_FILENAME: return gjs_array_to_ptrarray(context, array_value, length, transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer, param_info, arr_p); case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unhandled array element type %d", element_type); return false; } } size_t gjs_type_get_element_size(GITypeTag element_type, GITypeInfo* type_info) { if (g_type_info_is_pointer(type_info) && element_type != GI_TYPE_TAG_ARRAY) return sizeof(void*); switch (element_type) { case GI_TYPE_TAG_BOOLEAN: return sizeof(gboolean); case GI_TYPE_TAG_INT8: return sizeof(int8_t); case GI_TYPE_TAG_UINT8: return sizeof(uint8_t); case GI_TYPE_TAG_INT16: return sizeof(int16_t); case GI_TYPE_TAG_UINT16: return sizeof(uint16_t); case GI_TYPE_TAG_INT32: return sizeof(int32_t); case GI_TYPE_TAG_UINT32: return sizeof(uint32_t); case GI_TYPE_TAG_INT64: return sizeof(int64_t); case GI_TYPE_TAG_UINT64: return sizeof(uint64_t); case GI_TYPE_TAG_FLOAT: return sizeof(float); case GI_TYPE_TAG_DOUBLE: return sizeof(double); case GI_TYPE_TAG_GTYPE: return sizeof(GType); case GI_TYPE_TAG_UNICHAR: return sizeof(char32_t); case GI_TYPE_TAG_GLIST: return sizeof(GSList); case GI_TYPE_TAG_GSLIST: return sizeof(GList); case GI_TYPE_TAG_ERROR: return sizeof(GError); case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: return sizeof(char*); case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); switch (interface_info.type()) { case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: return sizeof(unsigned int); case GI_INFO_TYPE_STRUCT: return g_struct_info_get_size(interface_info); case GI_INFO_TYPE_UNION: return g_union_info_get_size(interface_info); default: return 0; } } case GI_TYPE_TAG_GHASH: return sizeof(void*); case GI_TYPE_TAG_ARRAY: if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { int length = g_type_info_get_array_length(type_info); if (length < 0) return sizeof(void*); GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); GITypeTag param_tag = g_type_info_get_tag(param_info); return gjs_type_get_element_size(param_tag, param_info); } return sizeof(void*); case GI_TYPE_TAG_VOID: break; } g_return_val_if_reached(0); } enum class ArrayReleaseType { EXPLICIT_LENGTH, ZERO_TERMINATED, }; template static inline bool gjs_gi_argument_release_array_internal( JSContext* cx, GITransfer element_transfer, GjsArgumentFlags flags, GITypeInfo* param_type, unsigned length, GIArgument* arg) { GjsAutoPointer arg_array = gjs_arg_steal(arg); if (!arg_array) return true; if (element_transfer != GI_TRANSFER_EVERYTHING) return true; if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { if (length == 0) return true; } GITypeTag type_tag = g_type_info_get_tag(param_type); if (flags & GjsArgumentFlags::ARG_IN && !type_needs_release(param_type, type_tag)) return true; if (flags & GjsArgumentFlags::ARG_OUT && !type_needs_out_release(param_type, type_tag)) return true; size_t element_size = gjs_type_get_element_size(type_tag, param_type); if G_UNLIKELY (element_size == 0) return true; bool is_pointer = g_type_info_is_pointer(param_type); for (size_t i = 0;; i++) { GIArgument elem; auto* element_start = &arg_array[i * element_size]; auto* pointer = is_pointer ? *reinterpret_cast(element_start) : nullptr; if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { if (is_pointer) { if (!pointer) break; } else if (*element_start == 0 && memcmp(element_start, element_start + 1, element_size - 1) == 0) { break; } } gjs_arg_set(&elem, is_pointer ? pointer : element_start); JS::AutoSaveExceptionState saved_exc(cx); if (!gjs_g_arg_release_internal(cx, element_transfer, param_type, type_tag, GJS_ARGUMENT_ARRAY_ELEMENT, flags, &elem)) { return false; } if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { if (i == length - 1) break; } } return true; } static GArray* garray_new_for_storage_type(unsigned length, GITypeTag storage_type, GITypeInfo* type_info) { size_t element_size = gjs_type_get_element_size(storage_type, type_info); return g_array_sized_new(true, false, element_size, length); } char* gjs_argument_display_name(const char* arg_name, GjsArgumentType arg_type) { switch (arg_type) { case GJS_ARGUMENT_ARGUMENT: return g_strdup_printf("Argument '%s'", arg_name); case GJS_ARGUMENT_RETURN_VALUE: return g_strdup("Return value"); case GJS_ARGUMENT_FIELD: return g_strdup_printf("Field '%s'", arg_name); case GJS_ARGUMENT_LIST_ELEMENT: return g_strdup("List element"); case GJS_ARGUMENT_HASH_ELEMENT: return g_strdup("Hash element"); case GJS_ARGUMENT_ARRAY_ELEMENT: return g_strdup("Array element"); default: g_assert_not_reached (); } } [[nodiscard]] static const char* type_tag_to_human_string( GITypeInfo* type_info) { GITypeTag tag; tag = g_type_info_get_tag(type_info); if (tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); return g_info_type_to_string(interface.type()); } else { return g_type_tag_to_string(tag); } } static void throw_invalid_argument(JSContext *context, JS::HandleValue value, GITypeInfo *arginfo, const char *arg_name, GjsArgumentType arg_type) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(context, "Expected type %s for %s but got type '%s'", type_tag_to_human_string(arginfo), display_name.get(), JS::InformalValueTypeName(value)); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext* context, JS::HandleValue value, GITypeInfo* type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, GjsArgumentFlags flags, void** contents, size_t* length_p) { bool found_length; gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to C array, transfer %d", arg_name, gjs_debug_value(value).c_str(), transfer); GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); if ((value.isNull() && !(flags & GjsArgumentFlags::MAY_BE_NULL)) || (!value.isString() && !value.isObjectOrNull())) { throw_invalid_argument(context, value, param_info, arg_name, arg_type); return false; } if (value.isNull()) { *contents = NULL; *length_p = 0; } else if (value.isString()) { /* Allow strings as int8/uint8/int16/uint16 arrays */ JS::RootedString str(context, value.toString()); GITypeTag element_tag = g_type_info_get_tag(param_info); if (!gjs_string_to_intarray(context, str, element_tag, contents, length_p)) return false; } else { JS::RootedObject array_obj(context, &value.toObject()); GITypeTag element_type = g_type_info_get_tag(param_info); if (JS_IsUint8Array(array_obj) && (element_type == GI_TYPE_TAG_INT8 || element_type == GI_TYPE_TAG_UINT8)) { GBytes* bytes = gjs_byte_array_get_bytes(array_obj); *contents = g_bytes_unref_to_data(bytes, length_p); return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!JS_HasPropertyById(context, array_obj, atoms.length(), &found_length)) return false; if (found_length) { guint32 length; if (!gjs_object_require_converted_property( context, array_obj, nullptr, atoms.length(), &length)) { return false; } else { if (!gjs_array_to_array(context, value, length, transfer, param_info, contents)) return false; *length_p = length; } } else { throw_invalid_argument(context, value, param_info, arg_name, arg_type); return false; } } return true; } namespace arg { [[nodiscard]] static bool is_gdk_atom(GIBaseInfo* info) { return (strcmp("Atom", g_base_info_get_name(info)) == 0 && strcmp("Gdk", g_base_info_get_namespace(info)) == 0); } } // namespace arg static void intern_gdk_atom(const char* name, GIArgument* ret) { GjsAutoFunctionInfo atom_intern_fun = g_irepository_find_by_name(nullptr, "Gdk", "atom_intern"); GIArgument atom_intern_args[2]; /* Can only store char * in GIArgument. First argument to gdk_atom_intern * is const char *, string isn't modified. */ gjs_arg_set(&atom_intern_args[0], name); gjs_arg_set(&atom_intern_args[1], false); mozilla::Unused << g_function_info_invoke(atom_intern_fun, atom_intern_args, 2, nullptr, 0, ret, nullptr); } static bool value_to_interface_gi_argument( JSContext* cx, JS::HandleValue value, GIBaseInfo* interface_info, GIInfoType interface_type, GITransfer transfer, bool expect_object, GIArgument* arg, GjsArgumentType arg_type, GjsArgumentFlags flags, bool* report_type_mismatch) { g_assert(report_type_mismatch); GType gtype; switch (interface_type) { case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_UNION: // These are subtypes of GIRegisteredTypeInfo for which the cast is // safe gtype = g_registered_type_info_get_g_type(interface_info); break; default: gtype = G_TYPE_NONE; } if (gtype != G_TYPE_NONE) gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); if (gtype == G_TYPE_VALUE) { if (flags & GjsArgumentFlags::CALLER_ALLOCATES) { if (!gjs_value_to_g_value_no_copy(cx, value, gjs_arg_get(arg))) return false; return true; } Gjs::AutoGValue gvalue; if (!gjs_value_to_g_value(cx, value, &gvalue)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, g_boxed_copy(G_TYPE_VALUE, &gvalue)); return true; } else if (arg::is_gdk_atom(interface_info)) { if (!value.isNull() && !value.isString()) { *report_type_mismatch = true; return false; } else if (value.isNull()) { intern_gdk_atom("NONE", arg); return true; } JS::RootedString str(cx, value.toString()); JS::UniqueChars name(JS_EncodeStringToUTF8(cx, str)); if (!name) return false; intern_gdk_atom(name.get(), arg); return true; } else if (expect_object != value.isObjectOrNull()) { *report_type_mismatch = true; return false; } else if (value.isNull()) { gjs_arg_set(arg, nullptr); return true; } else if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct(interface_info)) { GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { *report_type_mismatch = true; return false; } // We use peek here to simplify reference counting (we just ignore // transfer annotation, as GType classes are never really freed) // We know that the GType class is referenced at least once when // the JS constructor is initialized. void* klass; if (g_type_is_a(actual_gtype, G_TYPE_INTERFACE)) klass = g_type_default_interface_peek(actual_gtype); else klass = g_type_class_peek(actual_gtype); gjs_arg_set(arg, klass); return true; } GType arg_gtype = gtype; if (interface_type == GI_INFO_TYPE_STRUCT && gtype == G_TYPE_NONE && !g_struct_info_is_foreign(interface_info)) { GType actual_gtype = G_TYPE_NONE; // In case we have no known type from gi we should try to be // more dynamic and try to get the type from JS, to handle the // case in which we're handling a gpointer such as GTypeInstance // FIXME(3v1n0): would be nice to know if GI would give this info if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (G_TYPE_IS_INSTANTIATABLE(actual_gtype)) gtype = actual_gtype; } if ((interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) && !g_type_is_a(gtype, G_TYPE_CLOSURE)) { // Handle Struct/Union first since we don't necessarily need a GType // for them. We special case Closures later, so skip them here. if (g_type_is_a(gtype, G_TYPE_BYTES) && JS_IsUint8Array(obj)) { gjs_arg_set(arg, gjs_byte_array_get_bytes(obj)); return true; } if (g_type_is_a(gtype, G_TYPE_ERROR)) { return ErrorBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer); } if (arg_gtype != G_TYPE_NONE || gtype == G_TYPE_NONE || g_type_is_a(gtype, G_TYPE_BOXED) || g_type_is_a(gtype, G_TYPE_VALUE) || g_type_is_a(gtype, G_TYPE_VARIANT)) { return BoxedBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); } } if (interface_type == GI_INFO_TYPE_UNION) { return UnionBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); } if (gtype != G_TYPE_NONE) { if (g_type_is_a(gtype, G_TYPE_OBJECT)) { return ObjectBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { if (!gjs_typecheck_param(cx, obj, gtype, true)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, gjs_g_param_from_param(cx, obj)); if (transfer != GI_TRANSFER_NOTHING) g_param_spec_ref(gjs_arg_get(arg)); return true; } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { if (BoxedBase::typecheck(cx, obj, interface_info, gtype, GjsTypecheckNoThrow())) { return BoxedBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype, interface_info); } GClosure* closure = Gjs::Closure::create_marshaled(cx, obj, "boxed"); // GI doesn't know about floating GClosure references. We // guess that if this is a return value going from JS::Value // to GIArgument, it's intended to be passed to a C API that // will consume the floating reference. if (arg_type != GJS_ARGUMENT_RETURN_VALUE) { g_closure_ref(closure); g_closure_sink(closure); } gjs_arg_set(arg, closure); return true; } // Should have been caught above as STRUCT/BOXED/UNION gjs_throw( cx, "Boxed type %s registered for unexpected interface_type %d", g_type_name(gtype), interface_type); return false; } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { return FundamentalBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } else if (G_TYPE_IS_INTERFACE(gtype)) { // Could be a GObject interface that's missing a prerequisite, // or could be a fundamental if (ObjectBase::typecheck(cx, obj, nullptr, gtype, GjsTypecheckNoThrow())) { return ObjectBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } // If this typecheck fails, then it's neither an object nor a // fundamental return FundamentalBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } gjs_throw(cx, "Unhandled GType %s unpacking GIArgument from Object", g_type_name(gtype)); gjs_arg_unset(arg); return false; } gjs_debug(GJS_DEBUG_GFUNCTION, "conversion of JSObject value %s to type %s failed", gjs_debug_value(value).c_str(), g_base_info_get_name(interface_info)); gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " "Object"); return false; } else if (value.isNumber()) { if (interface_type == GI_INFO_TYPE_ENUM) { int64_t value_int64; if (!JS::ToInt64(cx, value, &value_int64) || !_gjs_enum_value_is_valid(cx, interface_info, value_int64)) return false; gjs_arg_set( arg, _gjs_enum_to_int(value_int64)); return true; } else if (interface_type == GI_INFO_TYPE_FLAGS) { int64_t value_int64; if (!JS::ToInt64(cx, value, &value_int64) || !_gjs_flags_value_is_valid(cx, gtype, value_int64)) return false; gjs_arg_set( arg, _gjs_enum_to_int(value_int64)); return true; } else if (gtype == G_TYPE_NONE) { gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " "Number"); return false; } gjs_throw(cx, "Unhandled GType %s unpacking GIArgument from Number", g_type_name(gtype)); return false; } gjs_debug(GJS_DEBUG_GFUNCTION, "JSObject type '%s' is neither null nor an object", JS::InformalValueTypeName(value)); *report_type_mismatch = true; return false; } template GJS_JSAPI_RETURN_CONVENTION inline static bool gjs_arg_set_from_js_value( JSContext* cx, const JS::HandleValue& value, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type) { bool out_of_range = false; if (!gjs_arg_set_from_js_value(cx, value, arg, &out_of_range)) { if (out_of_range) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(cx, "value %s is out of range for %s (type %s)", std::to_string(gjs_arg_get(arg)).c_str(), display_name.get(), Gjs::static_type_name()); } return false; } gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "%s set to value %s (type %s)", GjsAutoChar(gjs_argument_display_name(arg_name, arg_type)).get(), std::to_string(gjs_arg_get(arg)).c_str(), Gjs::static_type_name()); return true; } static bool check_nullable_argument(JSContext* cx, const char* arg_name, GjsArgumentType arg_type, GITypeTag type_tag, GjsArgumentFlags flags, GIArgument* arg) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL) && !gjs_arg_get(arg)) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, arg_type); gjs_throw(cx, "%s (type %s) may not be null", display_name.get(), g_type_tag_to_string(type_tag)); return false; } return true; } bool gjs_value_to_gi_argument(JSContext* context, JS::HandleValue value, GITypeInfo* type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { GITypeTag type_tag = g_type_info_get_tag(type_info); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, gjs_debug_value(value).c_str(), g_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: // just so it isn't uninitialized gjs_arg_unset(arg); return check_nullable_argument(context, arg_name, arg_type, type_tag, flags, arg); case GI_TYPE_TAG_INT8: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT8: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT16: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT16: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT32: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT32: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT64: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT64: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_BOOLEAN: gjs_arg_set(arg, JS::ToBoolean(value)); return true; case GI_TYPE_TAG_FLOAT: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_DOUBLE: return gjs_arg_set_from_js_value(context, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UNICHAR: if (value.isString()) { if (!gjs_unichar_from_string(context, value, &gjs_arg_member(arg))) return false; } else { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } break; case GI_TYPE_TAG_GTYPE: if (value.isObjectOrNull()) { GType gtype; JS::RootedObject obj(context, value.toObjectOrNull()); if (!gjs_gtype_get_actual_gtype(context, obj, >ype)) return false; if (gtype == G_TYPE_INVALID) return false; gjs_arg_set(arg, gtype); } else { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } break; case GI_TYPE_TAG_FILENAME: if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isString()) { GjsAutoChar filename_str; if (!gjs_string_to_filename(context, value, &filename_str)) return false; gjs_arg_set(arg, filename_str.release()); } else { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } return check_nullable_argument(context, arg_name, arg_type, type_tag, flags, arg); case GI_TYPE_TAG_UTF8: if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isString()) { JS::RootedString str(context, value.toString()); JS::UniqueChars utf8_str(JS_EncodeStringToUTF8(context, str)); if (!utf8_str) return false; gjs_arg_set(arg, g_strdup(utf8_str.get())); } else { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } return check_nullable_argument(context, arg_name, arg_type, type_tag, flags, arg); case GI_TYPE_TAG_ERROR: if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!ErrorBase::transfer_to_gi_argument(context, obj, arg, GI_DIRECTION_IN, transfer)) return false; } else { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } return check_nullable_argument(context, arg_name, arg_type, type_tag, flags, arg); case GI_TYPE_TAG_INTERFACE: { bool expect_object = true; GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS || arg::is_gdk_atom(interface_info)) expect_object = false; if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(interface_info)) { return gjs_struct_foreign_convert_to_gi_argument( context, value, interface_info, arg_name, arg_type, transfer, flags, arg); } bool report_type_mismatch = false; if (!value_to_interface_gi_argument( context, value, interface_info, interface_type, transfer, expect_object, arg, arg_type, flags, &report_type_mismatch)) { if (report_type_mismatch) throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } if (expect_object) return check_nullable_argument(context, arg_name, arg_type, type_tag, flags, arg); } break; case GI_TYPE_TAG_GLIST: return gjs_array_to_g_list(context, value, type_info, transfer, arg_name, arg_type, &gjs_arg_member(arg)); case GI_TYPE_TAG_GSLIST: return gjs_array_to_g_list(context, value, type_info, transfer, arg_name, arg_type, &gjs_arg_member(arg)); case GI_TYPE_TAG_GHASH: if (value.isNull()) { gjs_arg_set(arg, nullptr); if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } } else if (!value.isObject()) { throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } else { GHashTable *ghash; JS::RootedObject props(context, &value.toObject()); if (!gjs_object_to_g_hash(context, props, type_info, transfer, &ghash)) { return false; } gjs_arg_set(arg, ghash); } break; case GI_TYPE_TAG_ARRAY: { GjsAutoPointer data; size_t length; GIArrayType array_type = g_type_info_get_array_type(type_info); /* First, let's handle the case where we're passed an instance * of Uint8Array and it needs to be marshalled to GByteArray. */ if (value.isObject()) { JSObject* bytearray_obj = &value.toObject(); if (JS_IsUint8Array(bytearray_obj) && array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { gjs_arg_set(arg, gjs_byte_array_get_byte_array(bytearray_obj)); break; } else { /* Fall through, !handled */ } } if (!gjs_array_to_explicit_array(context, value, type_info, arg_name, arg_type, transfer, flags, data.out(), &length)) { return false; } GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); if (array_type == GI_ARRAY_TYPE_C) { gjs_arg_set(arg, data.release()); } else if (array_type == GI_ARRAY_TYPE_ARRAY) { GITypeTag storage_type = g_type_info_get_storage_type(param_info); GArray* array = garray_new_for_storage_type(length, storage_type, param_info); if (data) g_array_append_vals(array, data, length); gjs_arg_set(arg, array); } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { GByteArray *byte_array = g_byte_array_sized_new(length); if (data) g_byte_array_append(byte_array, data.as(), length); gjs_arg_set(arg, byte_array); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GPtrArray *array = g_ptr_array_sized_new(length); g_ptr_array_set_size(array, length); if (data) memcpy(array->pdata, data, sizeof(void*) * length); gjs_arg_set(arg, array); } break; } default: g_warning("Unhandled type %s for JavaScript to GIArgument conversion", g_type_tag_to_string(type_tag)); throw_invalid_argument(context, value, type_info, arg_name, arg_type); return false; } return true; } /* If a callback function with a return value throws, we still have * to return something to C. This function defines what that something * is. It basically boils down to memset(arg, 0, sizeof(*arg)), but * gives as a bit more future flexibility and also will work if * libffi passes us a buffer that only has room for the appropriate * branch of GIArgument. (Currently it appears that the return buffer * has a fixed size large enough for the union of all types.) */ void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg) { GITypeTag type_tag = g_type_info_get_tag(type_info); switch (type_tag) { case GI_TYPE_TAG_VOID: // just so it isn't uninitialized gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT8: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT8: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT16: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT16: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT32: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT32: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UNICHAR: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INT64: gjs_arg_unset(arg); break; case GI_TYPE_TAG_UINT64: gjs_arg_unset(arg); break; case GI_TYPE_TAG_BOOLEAN: gjs_arg_unset(arg); break; case GI_TYPE_TAG_FLOAT: gjs_arg_unset(arg); break; case GI_TYPE_TAG_DOUBLE: gjs_arg_unset(arg); break; case GI_TYPE_TAG_GTYPE: gjs_arg_unset(arg); break; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_ERROR: gjs_arg_unset(arg); break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info != nullptr); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) gjs_arg_unset(arg); else gjs_arg_unset(arg); } break; case GI_TYPE_TAG_GHASH: // Possibly better to return an empty hash table? gjs_arg_unset(arg); break; case GI_TYPE_TAG_ARRAY: gjs_arg_unset(arg); break; default: g_warning("Unhandled type %s for default GIArgument initialization", g_type_tag_to_string(type_tag)); break; } } bool gjs_value_to_callback_out_arg(JSContext* context, JS::HandleValue value, GIArgInfo* arg_info, GIArgument* arg) { GIDirection direction [[maybe_unused]] = g_arg_info_get_direction(arg_info); g_assert( (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) && "gjs_value_to_callback_out_arg does not handle in arguments."); GjsArgumentFlags flags = GjsArgumentFlags::NONE; GITypeInfo type_info; g_arg_info_load_type(arg_info, &type_info); // If the argument is optional and we're passed nullptr, // ignore the GJS value. if (g_arg_info_is_optional(arg_info) && !arg) return true; // Otherwise, throw an error to prevent a segfault. if (!arg) { gjs_throw(context, "Return value %s is not optional but was passed NULL", g_base_info_get_name(arg_info)); return false; } if (g_arg_info_may_be_null(arg_info)) flags |= GjsArgumentFlags::MAY_BE_NULL; if (g_arg_info_is_caller_allocates(arg_info)) flags |= GjsArgumentFlags::CALLER_ALLOCATES; return gjs_value_to_gi_argument( context, value, &type_info, g_base_info_get_name(arg_info), (g_arg_info_is_return_value(arg_info) ? GJS_ARGUMENT_RETURN_VALUE : GJS_ARGUMENT_ARGUMENT), g_arg_info_get_ownership_transfer(arg_info), flags, arg); } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list( JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* type_info, GITransfer transfer, T* list) { static_assert(std::is_same_v || std::is_same_v); JS::RootedValueVector elems(cx); GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info); GIArgument arg; for (size_t i = 0; list; list = list->next, ++i) { g_type_info_argument_from_hash_pointer(param_info, list->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_gi_argument(cx, elems[i], param_info, GJS_ARGUMENT_LIST_ELEMENT, transfer, &arg)) return false; } JSObject* obj = JS::NewArrayObject(cx, elems); if (!obj) return false; value_p.setObject(*obj); return true; } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list( JSContext* cx, GITransfer transfer, GITypeInfo* type_info, GjsArgumentFlags flags, GIArgument* arg) { static_assert(std::is_same_v || std::is_same_v); GjsSmartPointer list = gjs_arg_steal(arg); if (transfer == GI_TRANSFER_CONTAINER) return true; GIArgument elem; GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info); GITypeTag type_tag = g_type_info_get_tag(param_info); for (T* l = list; l; l = l->next) { gjs_arg_set(&elem, l->data); if (!gjs_g_arg_release_internal(cx, transfer, param_info, type_tag, GJS_ARGUMENT_LIST_ELEMENT, flags, &elem)) { return false; } } return true; } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) GITypeInfo* param_info, GIArgument* arg, void* array, size_t length, GITransfer transfer = GI_TRANSFER_EVERYTHING) { for (size_t i = 0; i < length; i++) { gjs_arg_set(arg, *(static_cast(array) + i)); if (!gjs_value_from_gi_argument(cx, elems[i], param_info, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, arg)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_carray_internal( JSContext* context, JS::MutableHandleValue value_p, GIArrayType array_type, GITypeInfo* param_info, GITransfer transfer, guint length, void* array) { GITypeTag element_type; guint i; element_type = g_type_info_get_tag(param_info); /* Special case array(guint8) */ if (element_type == GI_TYPE_TAG_UINT8) { JSObject* obj = gjs_byte_array_from_data_copy(context, length, array); if (!obj) return false; value_p.setObject(*obj); return true; } /* Special case array(unichar) to be a string in JS */ if (element_type == GI_TYPE_TAG_UNICHAR) return gjs_string_from_ucs4(context, (gunichar *) array, length, value_p); // a null array pointer takes precedence over whatever `length` says if (!array) { JSObject* jsarray = JS::NewArrayObject(context, 0); if (!jsarray) return false; value_p.setObject(*jsarray); return true; } JS::RootedValueVector elems(context); if (!elems.resize(length)) { JS_ReportOutOfMemory(context); return false; } GIArgument arg; switch (element_type) { /* Special cases handled above */ case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UNICHAR: g_assert_not_reached(); case GI_TYPE_TAG_BOOLEAN: if (!fill_vector_from_carray( context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT8: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length)) return false; break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); GIInfoType info_type = interface_info.type(); if (array_type != GI_ARRAY_TYPE_PTR_ARRAY && (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) && !g_type_info_is_pointer(param_info)) { size_t struct_size; if (info_type == GI_INFO_TYPE_UNION) struct_size = g_union_info_get_size(interface_info); else struct_size = g_struct_info_get_size(interface_info); for (i = 0; i < length; i++) { gjs_arg_set(&arg, static_cast(array) + (struct_size * i)); if (!gjs_value_from_gi_argument( context, elems[i], param_info, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) return false; } break; } } /* fallthrough */ case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_carray(context, elems, param_info, &arg, array, length, transfer)) return false; break; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unknown Array element-type %d", element_type); return false; } JSObject* obj = JS::NewArrayObject(context, elems); if (!obj) return false; value_p.setObject(*obj); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_fixed_size_array(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* type_info, GITransfer transfer, void* array) { gint length; length = g_type_info_get_array_fixed_size(type_info); g_assert (length != -1); GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); return gjs_array_from_carray_internal(context, value_p, g_type_info_get_array_type(type_info), param_info, transfer, length, array); } bool gjs_value_from_explicit_array(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* type_info, GITransfer transfer, GIArgument* arg, int length) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); return gjs_array_from_carray_internal( context, value_p, g_type_info_get_array_type(type_info), param_info, transfer, length, gjs_arg_get(arg)); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_boxed_array(JSContext* context, JS::MutableHandleValue value_p, GIArrayType array_type, GITypeInfo* param_info, GITransfer transfer, GIArgument* arg) { GArray *array; GPtrArray *ptr_array; gpointer data = NULL; gsize length = 0; if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } switch(array_type) { case GI_ARRAY_TYPE_BYTE_ARRAY: /* GByteArray is just a typedef for GArray internally */ case GI_ARRAY_TYPE_ARRAY: array = gjs_arg_get(arg); data = array->data; length = array->len; break; case GI_ARRAY_TYPE_PTR_ARRAY: ptr_array = gjs_arg_get(arg); data = ptr_array->pdata; length = ptr_array->len; break; case GI_ARRAY_TYPE_C: // already checked in gjs_value_from_gi_argument() default: g_assert_not_reached(); } return gjs_array_from_carray_internal(context, value_p, array_type, param_info, transfer, length, data); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* param_info, GITransfer transfer, const GValue* gvalue) { void* data = nullptr; size_t length = 0; GIArrayType array_type; GType value_gtype = G_VALUE_TYPE(gvalue); // GByteArray is just a typedef for GArray internally if (g_type_is_a(value_gtype, G_TYPE_BYTE_ARRAY) || g_type_is_a(value_gtype, G_TYPE_ARRAY)) { array_type = g_type_is_a(value_gtype, G_TYPE_BYTE_ARRAY) ? GI_ARRAY_TYPE_BYTE_ARRAY : GI_ARRAY_TYPE_ARRAY; auto* array = reinterpret_cast(g_value_get_boxed(gvalue)); data = array->data; length = array->len; } else if (g_type_is_a(value_gtype, G_TYPE_PTR_ARRAY)) { array_type = GI_ARRAY_TYPE_PTR_ARRAY; auto* ptr_array = reinterpret_cast(g_value_get_boxed(gvalue)); data = ptr_array->pdata; length = ptr_array->len; } else { g_assert_not_reached(); gjs_throw(cx, "%s is not an array type", g_type_name(value_gtype)); return false; } return gjs_array_from_carray_internal(cx, value_p, array_type, param_info, transfer, length, data); } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) GITypeInfo* param_info, GIArgument* arg, void* c_array, GITransfer transfer = GI_TRANSFER_EVERYTHING) { T* array = static_cast(c_array); for (size_t i = 0;; i++) { if constexpr (std::is_scalar_v) { if (!array[i]) break; gjs_arg_set(arg, array[i]); } else { uint8_t* element_start = reinterpret_cast(&array[i]); if (*element_start == 0 && // cppcheck-suppress pointerSize memcmp(element_start, element_start + 1, sizeof(T) - 1) == 0) break; gjs_arg_set(arg, element_start); } if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_gi_argument(cx, elems[i], param_info, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, arg)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_zero_terminated_c_array( JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* param_info, GITransfer transfer, void* c_array) { GITypeTag element_type; element_type = g_type_info_get_tag(param_info); /* Special case array(guint8) */ if (element_type == GI_TYPE_TAG_UINT8) { size_t len = strlen(static_cast(c_array)); JSObject* obj = gjs_byte_array_from_data_copy(context, len, c_array); if (!obj) return false; value_p.setObject(*obj); return true; } /* Special case array(gunichar) to JS string */ if (element_type == GI_TYPE_TAG_UNICHAR) return gjs_string_from_ucs4(context, (gunichar *) c_array, -1, value_p); JS::RootedValueVector elems(context); GIArgument arg; switch (element_type) { /* Special cases handled above. */ case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UNICHAR: g_assert_not_reached(); case GI_TYPE_TAG_INT8: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); if (!g_type_info_is_pointer(param_info) && is_gvalue(interface_info)) { if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array)) return false; break; } if (!g_type_info_is_pointer(param_info)) { gjs_throw(context, "Flat C array of %s.%s not supported (see " "https://gitlab.gnome.org/GNOME/cjs/-/issues/603)", interface_info.ns(), interface_info.name()); return false; } [[fallthrough]]; } case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: if (!fill_vector_from_zero_terminated_carray( context, elems, param_info, &arg, c_array, transfer)) return false; break; /* Boolean zero-terminated array makes no sense, because FALSE is also * zero */ case GI_TYPE_TAG_BOOLEAN: gjs_throw(context, "Boolean zero-terminated array not supported"); return false; case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Unknown element-type %d", element_type); return false; } JSObject* obj = JS::NewArrayObject(context, elems); if (!obj) return false; value_p.setObject(*obj); return true; } bool gjs_object_from_g_hash(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* key_param_info, GITypeInfo* val_param_info, GITransfer transfer, GHashTable* hash) { GHashTableIter iter; // a NULL hash table becomes a null JS value if (hash==NULL) { value_p.setNull(); return true; } JS::RootedObject obj(context, JS_NewPlainObject(context)); if (!obj) return false; value_p.setObject(*obj); JS::RootedValue keyjs(context), valjs(context); JS::RootedString keystr(context); g_hash_table_iter_init(&iter, hash); void* key_pointer; void* val_pointer; GIArgument keyarg, valarg; while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) { g_type_info_argument_from_hash_pointer(key_param_info, key_pointer, &keyarg); if (!gjs_value_from_gi_argument(context, &keyjs, key_param_info, GJS_ARGUMENT_HASH_ELEMENT, transfer, &keyarg)) return false; keystr = JS::ToString(context, keyjs); if (!keystr) return false; JS::UniqueChars keyutf8(JS_EncodeStringToUTF8(context, keystr)); if (!keyutf8) return false; g_type_info_argument_from_hash_pointer(val_param_info, val_pointer, &valarg); if (!gjs_value_from_gi_argument(context, &valjs, val_param_info, GJS_ARGUMENT_HASH_ELEMENT, transfer, &valarg)) return false; if (!JS_DefineProperty(context, obj, keyutf8.get(), valjs, JSPROP_ENUMERATE)) return false; } return true; } bool gjs_value_from_gi_argument(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* type_info, GjsArgumentType argument_type, GITransfer transfer, GIArgument* arg) { GITypeTag type_tag = g_type_info_get_tag(type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument %s to JS::Value", g_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: // If the argument is a pointer, convert to null to match our // in handling. if (g_type_info_is_pointer(type_info)) value_p.setNull(); else value_p.setUndefined(); break; case GI_TYPE_TAG_BOOLEAN: value_p.setBoolean(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT32: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_UINT32: value_p.setNumber(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT64: value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); break; case GI_TYPE_TAG_UINT64: value_p.setNumber(gjs_arg_get_maybe_rounded(arg)); break; case GI_TYPE_TAG_UINT16: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT16: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_UINT8: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_INT8: value_p.setInt32(gjs_arg_get(arg)); break; case GI_TYPE_TAG_FLOAT: value_p.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); break; case GI_TYPE_TAG_DOUBLE: value_p.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); break; case GI_TYPE_TAG_GTYPE: { GType gtype = gjs_arg_get(arg); if (gtype == 0) { value_p.setNull(); return true; } JSObject* obj = gjs_gtype_create_gtype_wrapper(context, gtype); if (!obj) return false; value_p.setObject(*obj); return true; } break; case GI_TYPE_TAG_UNICHAR: { char32_t value = gjs_arg_get(arg); // Preserve the bidirectional mapping between 0 and "" if (value == 0) { value_p.set(JS_GetEmptyStringValue(context)); return true; } else if (!g_unichar_validate(value)) { gjs_throw(context, "Invalid unicode codepoint %" G_GUINT32_FORMAT, value); return false; } char utf8[7]; int bytes = g_unichar_to_utf8(value, utf8); return gjs_string_from_utf8_n(context, utf8, bytes, value_p); } case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: { const char* str = gjs_arg_get(arg); if (!str) { value_p.setNull(); return true; } if (type_tag == GI_TYPE_TAG_FILENAME) return gjs_string_from_filename(context, str, -1, value_p); return gjs_string_from_utf8(context, str, value_p); } case GI_TYPE_TAG_ERROR: { GError* ptr = gjs_arg_get(arg); if (!ptr) { value_p.setNull(); return true; } JSObject* obj = ErrorInstance::object_for_c_ptr(context, ptr); if (!obj) return false; value_p.setObject(*obj); return true; } case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_UNRESOLVED) { gjs_throw(context, "Unable to resolve arg type '%s'", g_base_info_get_name(interface_info)); return false; } /* Enum/Flags are aren't pointer types, unlike the other interface subtypes */ if (interface_type == GI_INFO_TYPE_ENUM) { int64_t value_int64 = _gjs_enum_from_int( interface_info, gjs_arg_get(arg)); if (!_gjs_enum_value_is_valid(context, interface_info, value_int64)) return false; value_p.setNumber(static_cast(value_int64)); return true; } if (interface_type == GI_INFO_TYPE_FLAGS) { int64_t value_int64 = _gjs_enum_from_int( interface_info, gjs_arg_get(arg)); GType gtype = g_registered_type_info_get_g_type( interface_info.as()); if (gtype != G_TYPE_NONE) { /* check make sure 32 bit flag */ if (static_cast(value_int64) != value_int64) { // Not a 32-bit integer gjs_throw(context, "0x%" PRIx64 " is not a valid value for flags %s", value_int64, g_type_name(gtype)); return false; } /* Pass only valid values*/ GjsAutoTypeClass gflags_class(gtype); value_int64 &= gflags_class->mask; } value_p.setNumber(static_cast(value_int64)); return true; } if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(interface_info.as())) { return gjs_struct_foreign_convert_from_gi_argument( context, value_p, interface_info, arg); } /* Everything else is a pointer type, NULL is the easy case */ if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_gtype_struct( interface_info.as())) { /* XXX: here we make the implicit assumption that GTypeClass is the same as GTypeInterface. This is true for the GType field, which is what we use, but not for the rest of the structure! */ GType gtype = G_TYPE_FROM_CLASS(gjs_arg_get(arg)); if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { return gjs_lookup_interface_constructor(context, gtype, value_p); } return gjs_lookup_object_constructor(context, gtype, value_p); } GType gtype = g_registered_type_info_get_g_type( interface_info.as()); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); /* Test GValue and GError before Struct, or it will be handled as the latter */ if (g_type_is_a(gtype, G_TYPE_VALUE)) { return gjs_value_from_g_value(context, value_p, gjs_arg_get(arg)); } if (g_type_is_a(gtype, G_TYPE_ERROR)) { JSObject* obj = ErrorInstance::object_for_c_ptr( context, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) { if (arg::is_gdk_atom(interface_info)) { GjsAutoFunctionInfo atom_name_fun = g_struct_info_find_method(interface_info, "name"); GIArgument atom_name_ret; GjsAutoError error = nullptr; if (!g_function_info_invoke(atom_name_fun, arg, 1, nullptr, 0, &atom_name_ret, error.out())) { gjs_throw(context, "Failed to call gdk_atom_name(): %s", error->message); return false; } GjsAutoChar name = gjs_arg_get(&atom_name_ret); if (g_strcmp0("NONE", name) == 0) { value_p.setNull(); return true; } return gjs_string_from_utf8(context, name, value_p); } JSObject *obj; if (gtype == G_TYPE_VARIANT) { transfer = GI_TRANSFER_EVERYTHING; } else if (transfer == GI_TRANSFER_CONTAINER) { switch (argument_type) { case GJS_ARGUMENT_ARRAY_ELEMENT: case GJS_ARGUMENT_LIST_ELEMENT: case GJS_ARGUMENT_HASH_ELEMENT: transfer = GI_TRANSFER_EVERYTHING; default: break; } } if (transfer == GI_TRANSFER_EVERYTHING) obj = BoxedInstance::new_for_c_struct( context, interface_info, gjs_arg_get(arg)); else obj = BoxedInstance::new_for_c_struct( context, interface_info, gjs_arg_get(arg), BoxedInstance::NoCopy()); if (!obj) return false; value_p.setObject(*obj); return true; } if (interface_type == GI_INFO_TYPE_UNION) { JSObject* obj = UnionInstance::new_for_c_union( context, interface_info.as(), gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } if (g_type_is_a(gtype, G_TYPE_OBJECT)) { g_assert(gjs_arg_get(arg) && "Null arg is already handled above"); return ObjectInstance::set_value_from_gobject( context, gjs_arg_get(arg), value_p); } if (g_type_is_a(gtype, G_TYPE_BOXED) || g_type_is_a(gtype, G_TYPE_ENUM) || g_type_is_a(gtype, G_TYPE_FLAGS)) { /* Should have been handled above */ gjs_throw(context, "Type %s registered for unexpected interface_type %d", g_type_name(gtype), interface_type); return false; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { JSObject* obj = gjs_param_from_g_param( context, G_PARAM_SPEC(gjs_arg_get(arg))); if (!obj) return false; value_p.setObject(*obj); return true; } if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unexpected unregistered type packing GIArgument " "into JS::Value"); return false; } if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) { JSObject* obj = FundamentalInstance::object_for_c_ptr( context, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } gjs_throw(context, "Unhandled GType %s packing GIArgument into JS::Value", g_type_name(gtype)); return false; } case GI_TYPE_TAG_ARRAY: if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { if (g_type_info_is_zero_terminated(type_info)) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != nullptr); return gjs_array_from_zero_terminated_c_array( context, value_p, param_info, transfer, gjs_arg_get(arg)); } else { /* arrays with length are handled outside of this function */ g_assert(((void) "Use gjs_value_from_explicit_array() for " "arrays with length param", g_type_info_get_array_length(type_info) == -1)); return gjs_array_from_fixed_size_array(context, value_p, type_info, transfer, gjs_arg_get(arg)); } } else if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_BYTE_ARRAY) { auto* byte_array = gjs_arg_get(arg); JSObject* array = gjs_byte_array_from_byte_array(context, byte_array); if (!array) { gjs_throw(context, "Couldn't convert GByteArray to a Uint8Array"); return false; } value_p.setObject(*array); } else { // this assumes the array type is GArray or GPtrArray GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); g_assert(param_info != nullptr); return gjs_array_from_boxed_array( context, value_p, g_type_info_get_array_type(type_info), param_info, transfer, arg); } break; case GI_TYPE_TAG_GLIST: return gjs_array_from_g_list(context, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GSLIST: return gjs_array_from_g_list(context, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GHASH: { GjsAutoTypeInfo key_param_info = g_type_info_get_param_type(type_info, 0); GjsAutoTypeInfo val_param_info = g_type_info_get_param_type(type_info, 1); g_assert(key_param_info != nullptr); g_assert(val_param_info != nullptr); return gjs_object_from_g_hash(context, value_p, key_param_info, val_param_info, transfer, gjs_arg_get(arg)); } break; default: g_warning("Unhandled type %s converting GIArgument to JavaScript", g_type_tag_to_string(type_tag)); return false; } return true; } struct GHR_closure { JSContext *context; GjsAutoTypeInfo key_param_info, val_param_info; GITransfer transfer; GjsArgumentFlags flags; bool failed; }; static gboolean gjs_ghr_helper(gpointer key, gpointer val, gpointer user_data) { GHR_closure *c = (GHR_closure *) user_data; GIArgument key_arg, val_arg; gjs_arg_set(&key_arg, key); gjs_arg_set(&val_arg, val); if (!gjs_g_arg_release_internal(c->context, c->transfer, c->key_param_info, g_type_info_get_tag(c->key_param_info), GJS_ARGUMENT_HASH_ELEMENT, c->flags, &key_arg)) c->failed = true; GITypeTag val_type = g_type_info_get_tag(c->val_param_info); switch (val_type) { case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: g_clear_pointer(&gjs_arg_member(&val_arg), g_free); break; default: if (!gjs_g_arg_release_internal( c->context, c->transfer, c->val_param_info, val_type, GJS_ARGUMENT_HASH_ELEMENT, c->flags, &val_arg)) c->failed = true; } return true; } /* We need to handle GI_TRANSFER_NOTHING differently for out parameters * (free nothing) and for in parameters (free any temporaries we've * allocated */ constexpr static bool is_transfer_in_nothing(GITransfer transfer, GjsArgumentFlags flags) { return (transfer == GI_TRANSFER_NOTHING) && (flags & GjsArgumentFlags::ARG_IN); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( JSContext* context, GITransfer transfer, GITypeInfo* type_info, GITypeTag type_tag, [[maybe_unused]] GjsArgumentType argument_type, GjsArgumentFlags flags, GIArgument* arg) { g_assert(transfer != GI_TRANSFER_NOTHING || flags != GjsArgumentFlags::NONE); switch (type_tag) { case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_GTYPE: break; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: g_clear_pointer(&gjs_arg_member(arg), g_free); break; case GI_TYPE_TAG_ERROR: if (!is_transfer_in_nothing(transfer, flags)) g_clear_error(&gjs_arg_member(arg)); break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); g_assert(interface_info); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(interface_info.as())) return gjs_struct_foreign_release_gi_argument( context, transfer, interface_info, arg); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) return true; /* Anything else is a pointer */ if (!gjs_arg_get(arg)) return true; GType gtype = g_registered_type_info_get_g_type( interface_info.as()); if (G_TYPE_IS_INSTANTIATABLE(gtype) || G_TYPE_IS_INTERFACE(gtype)) gtype = G_TYPE_FROM_INSTANCE(gjs_arg_get(arg)); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is %s", g_type_name(gtype)); // In gjs_value_from_gi_argument we handle Struct/Union types // without a registered GType, but here we are specifically handling // a GIArgument that *owns* its value, and that is nonsensical for // such types, so we don't have to worry about it. if (g_type_is_a(gtype, G_TYPE_OBJECT)) { if (!is_transfer_in_nothing(transfer, flags)) g_clear_object(&gjs_arg_member(arg)); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { if (!is_transfer_in_nothing(transfer, flags)) g_clear_pointer(&gjs_arg_member(arg), g_param_spec_unref); } else if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { g_clear_pointer(&gjs_arg_member(arg), g_closure_unref); } else if (g_type_is_a(gtype, G_TYPE_VALUE)) { /* G_TYPE_VALUE is-a G_TYPE_BOXED, but we special case it */ if (g_type_info_is_pointer (type_info)) g_boxed_free(gtype, gjs_arg_steal(arg)); else g_clear_pointer(&gjs_arg_member(arg), g_value_unset); } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (!is_transfer_in_nothing(transfer, flags)) g_boxed_free(gtype, gjs_arg_steal(arg)); } else if (g_type_is_a(gtype, G_TYPE_VARIANT)) { if (!is_transfer_in_nothing(transfer, flags)) g_clear_pointer(&gjs_arg_member(arg), g_variant_unref); } else if (gtype == G_TYPE_NONE) { if (!is_transfer_in_nothing(transfer, flags)) { gjs_throw(context, "Don't know how to release GIArgument: not an " "object or boxed type"); return false; } } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { if (!is_transfer_in_nothing(transfer, flags)) { auto* priv = FundamentalPrototype::for_gtype(context, gtype); priv->call_unref_function(gjs_arg_steal(arg)); } } else { gjs_throw(context, "Unhandled GType %s releasing GIArgument", g_type_name(gtype)); return false; } return true; } case GI_TYPE_TAG_ARRAY: { GIArrayType array_type = g_type_info_get_array_type(type_info); if (!gjs_arg_get(arg)) { /* OK */ } else if (array_type == GI_ARRAY_TYPE_C) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); GITypeTag element_type; element_type = g_type_info_get_tag(param_info); switch (element_type) { case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: if (transfer == GI_TRANSFER_CONTAINER) g_clear_pointer(&gjs_arg_member(arg), g_free); else g_clear_pointer(&gjs_arg_member(arg), g_strfreev); break; case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_GTYPE: g_clear_pointer(&gjs_arg_member(arg), g_free); break; case GI_TYPE_TAG_INTERFACE: if (!g_type_info_is_pointer(param_info)) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(param_info); GIInfoType info_type = interface_info.type(); if (info_type == GI_INFO_TYPE_STRUCT || info_type == GI_INFO_TYPE_UNION) { g_clear_pointer(&gjs_arg_member(arg), g_free); break; } } [[fallthrough]]; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: { GITransfer element_transfer = transfer; if (argument_type != GJS_ARGUMENT_ARGUMENT && transfer != GI_TRANSFER_EVERYTHING) element_transfer = GI_TRANSFER_NOTHING; if (g_type_info_is_zero_terminated(type_info)) { return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>( context, element_transfer, flags | GjsArgumentFlags::ARG_OUT, param_info, 0, arg); } else { return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>( context, element_transfer, flags | GjsArgumentFlags::ARG_OUT, param_info, g_type_info_get_array_fixed_size(type_info), arg); } } case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Releasing a C array with explicit length, that was nested" "inside another container. This is not supported (and will leak)"); return false; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { GITypeTag element_type; GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); element_type = g_type_info_get_tag(param_info); switch (element_type) { case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_GTYPE: g_clear_pointer(&gjs_arg_member(arg), g_array_unref); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_INTERFACE: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: { GjsAutoPointer array = gjs_arg_steal(arg); if (transfer != GI_TRANSFER_CONTAINER && type_needs_out_release(param_info, element_type)) { guint i; for (i = 0; i < array->len; i++) { GIArgument arg_iter; gjs_arg_set(&arg_iter, g_array_index(array, gpointer, i)); if (!gjs_g_arg_release_internal( context, transfer, param_info, element_type, GJS_ARGUMENT_ARRAY_ELEMENT, flags, &arg_iter)) return false; } } break; } case GI_TYPE_TAG_VOID: default: gjs_throw(context, "Don't know how to release GArray element-type %d", element_type); return false; } } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { g_clear_pointer(&gjs_arg_member(arg), g_byte_array_unref); } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); GjsAutoPointer array = gjs_arg_steal(arg); if (transfer != GI_TRANSFER_CONTAINER) { guint i; for (i = 0; i < array->len; i++) { GIArgument arg_iter; gjs_arg_set(&arg_iter, g_ptr_array_index(array, i)); if (!gjs_gi_argument_release(context, transfer, param_info, flags, &arg_iter)) return false; } } } else { g_assert_not_reached(); } break; } case GI_TYPE_TAG_GLIST: return gjs_g_arg_release_g_list(context, transfer, type_info, flags, arg); case GI_TYPE_TAG_GSLIST: return gjs_g_arg_release_g_list(context, transfer, type_info, flags, arg); case GI_TYPE_TAG_GHASH: if (gjs_arg_get(arg)) { GjsAutoPointer hash_table = gjs_arg_steal(arg); if (transfer == GI_TRANSFER_CONTAINER) g_hash_table_remove_all(hash_table); else { GHR_closure c = {context, nullptr, nullptr, transfer, flags, false}; c.key_param_info = g_type_info_get_param_type(type_info, 0); g_assert(c.key_param_info != nullptr); c.val_param_info = g_type_info_get_param_type(type_info, 1); g_assert(c.val_param_info != nullptr); g_hash_table_foreach_steal(hash_table, gjs_ghr_helper, &c); if (c.failed) return false; } } break; default: g_warning("Unhandled type %s releasing GIArgument", g_type_tag_to_string(type_tag)); return false; } return true; } bool gjs_gi_argument_release(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, GjsArgumentFlags flags, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING && !is_transfer_in_nothing(transfer, flags)) return true; GITypeTag type_tag = g_type_info_get_tag(type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s out param or return value", g_type_tag_to_string(type_tag)); return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, GJS_ARGUMENT_ARGUMENT, flags, arg); } bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, GjsArgumentFlags flags, GIArgument* arg) { /* GI_TRANSFER_EVERYTHING: we don't own the argument anymore. * GI_TRANSFER_CONTAINER: * - non-containers: treated as GI_TRANSFER_EVERYTHING * - containers: See FIXME in gjs_array_to_g_list(); currently * an error and we won't get here. */ if (transfer != GI_TRANSFER_NOTHING) return true; GITypeTag type_tag = g_type_info_get_tag(type_info); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s in param", g_type_tag_to_string(type_tag)); if (type_needs_release (type_info, type_tag)) return gjs_g_arg_release_internal(cx, transfer, type_info, type_tag, GJS_ARGUMENT_ARGUMENT, flags, arg); return true; } bool gjs_gi_argument_release_in_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>(context, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, param_type, length, arg); } bool gjs_gi_argument_release_in_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>(context, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, param_type, 0, arg); } bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, unsigned length, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array out param"); GITransfer element_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING; GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>(context, element_transfer, GjsArgumentFlags::ARG_OUT, param_type, length, arg); } bool gjs_gi_argument_release_out_array(JSContext* context, GITransfer transfer, GITypeInfo* type_info, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array out param"); GITransfer element_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING; GjsAutoTypeInfo param_type = g_type_info_get_param_type(type_info, 0); return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>(context, element_transfer, GjsArgumentFlags::ARG_OUT, param_type, 0, arg); } cjs-128.1/gi/arg.h0000664000175000017500000001560215116312211012546 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_ARG_H_ #define GI_ARG_H_ #include #include // for size_t #include #include #include #include // for GHashTable #include #include #include "cjs/macros.h" // Different roles for a GIArgument; currently used only in exception and debug // messages. typedef enum { GJS_ARGUMENT_ARGUMENT, GJS_ARGUMENT_RETURN_VALUE, GJS_ARGUMENT_FIELD, GJS_ARGUMENT_LIST_ELEMENT, GJS_ARGUMENT_HASH_ELEMENT, GJS_ARGUMENT_ARRAY_ELEMENT } GjsArgumentType; enum class GjsArgumentFlags : uint8_t { NONE = 0, MAY_BE_NULL = 1 << 0, CALLER_ALLOCATES = 1 << 1, SKIP_IN = 1 << 2, SKIP_OUT = 1 << 3, SKIP_ALL = SKIP_IN | SKIP_OUT, ARG_IN = 1 << 4, ARG_OUT = 1 << 5, ARG_INOUT = ARG_IN | ARG_OUT, }; // Overload operator| so that Visual Studio won't complain // when converting unsigned char to GjsArgumentFlags GjsArgumentFlags operator|(GjsArgumentFlags const& v1, GjsArgumentFlags const& v2); [[nodiscard]] char* gjs_argument_display_name(const char* arg_name, GjsArgumentType arg_type); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_callback_out_arg(JSContext* context, JS::HandleValue value, GIArgInfo* arg_info, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext* cx, JS::HandleValue value, GITypeInfo* type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, GjsArgumentFlags flags, void** contents, size_t* length_p); size_t gjs_type_get_element_size(GITypeTag element_type, GITypeInfo* type_info); void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_gi_argument(JSContext*, JS::HandleValue, GITypeInfo*, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool inline gjs_value_to_gi_argument(JSContext* cx, JS::HandleValue value, GITypeInfo* type_info, GjsArgumentType argument_type, GITransfer transfer, GIArgument* arg) { return gjs_value_to_gi_argument(cx, value, type_info, nullptr /* arg_name */, argument_type, transfer, GjsArgumentFlags::NONE, arg); } GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_gi_argument(JSContext*, JS::MutableHandleValue, GITypeInfo*, GjsArgumentType, GITransfer, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_value_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* type_info, GIArgument* arg, bool copy_structs) { return gjs_value_from_gi_argument( cx, value_p, type_info, GJS_ARGUMENT_ARGUMENT, copy_structs ? GI_TRANSFER_EVERYTHING : GI_TRANSFER_NOTHING, arg); } GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_explicit_array(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* type_info, GITransfer transfer, GIArgument* arg, int length); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_value_from_explicit_array(JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* type_info, GIArgument* arg, int length) { return gjs_value_from_explicit_array(context, value_p, type_info, GI_TRANSFER_EVERYTHING, arg, length); } GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release(JSContext*, GITransfer, GITypeInfo*, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_gi_argument_release(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, GIArgument* arg) { return gjs_gi_argument_release(cx, transfer, type_info, GjsArgumentFlags::NONE, arg); } GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, GITypeInfo*, unsigned length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, GITypeInfo*, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, GITypeInfo*, unsigned length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, GITypeInfo*, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_arg(JSContext*, GITransfer, GITypeInfo*, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, GITypeInfo* type_info, GIArgument* arg) { return gjs_gi_argument_release_in_arg(cx, transfer, type_info, GjsArgumentFlags::ARG_IN, arg); } GJS_JSAPI_RETURN_CONVENTION bool _gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value); [[nodiscard]] int64_t _gjs_enum_from_int(GIEnumInfo* enum_info, int int_value); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_strv(JSContext *context, JS::MutableHandleValue value_p, const char **strv); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_strv (JSContext *context, JS::Value array_value, unsigned int length, void **arr_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, GITypeInfo* param_info, GITransfer, const GValue* gvalue); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_from_g_hash(JSContext* cx, JS::MutableHandleValue, GITypeInfo* key_param_info, GITypeInfo* val_param_info, GITransfer transfer, GHashTable* hash); #endif // GI_ARG_H_ cjs-128.1/gi/boxed.cpp0000664000175000017500000011743015116312211013433 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include // for memcpy, size_t, strcmp #include // for move, forward #include #include #include #include #include // for JS_ReportOutOfMemory #include #include // for GCHashMap #include // for MutableWrappedPtrOperations #include // for SetReservedSlot #include // for JS_DefineFunction, JS_Enumerate #include #include #include #include // for UniqueChars #include #include #include // for IdVector #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/function.h" #include "gi/gerror.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj), m_allocated_directly(false), m_owning_ptr(false) { GJS_INC_COUNTER(boxed_instance); } [[nodiscard]] static bool struct_is_simple(GIStructInfo* info); // See GIWrapperBase::resolve(). bool BoxedPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } // Look for methods and other class properties GjsAutoFunctionInfo method_info = g_struct_info_find_method(info(), prop_name.get()); if (!method_info) { *resolved = false; return true; } #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); /* obj is the Boxed prototype */ if (!gjs_define_function(cx, obj, gtype(), method_info)) return false; *resolved = true; } else { *resolved = false; } return true; } // See GIWrapperBase::new_enumerate(). bool BoxedPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { int n_methods = g_struct_info_get_n_methods(info()); for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_struct_info_get_method(info(), i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(cx); return false; } } } return true; } /* * BoxedBase::get_copy_source(): * * Check to see if JS::Value passed in is another Boxed instance object of the * same type, and if so, retrieve the BoxedInstance private structure for it. * This function does not throw any JS exceptions. */ BoxedBase* BoxedBase::get_copy_source(JSContext* context, JS::Value value) const { if (!value.isObject()) return nullptr; JS::RootedObject object(context, &value.toObject()); BoxedBase* source_priv = BoxedBase::for_js(context, object); if (!source_priv || !g_base_info_equal(info(), source_priv->info())) return nullptr; return source_priv; } /* * BoxedInstance::allocate_directly: * * Allocate a boxed object of the correct size, set all the bytes to 0, and set * m_ptr to point to it. This is used when constructing a boxed object that can * be allocated directly (i.e., does not need to be created by a constructor * function.) */ void BoxedInstance::allocate_directly(void) { g_assert(get_prototype()->can_allocate_directly()); own_ptr(g_malloc0(g_struct_info_get_size(info()))); m_allocated_directly = true; debug_lifecycle("Boxed pointer directly allocated"); } // When initializing a boxed object from a hash of properties, we don't want to // do n O(n) lookups, so put put the fields into a hash table and store it on // proto->priv for fast lookup. std::unique_ptr BoxedPrototype::create_field_map( JSContext* cx, GIStructInfo* struct_info) { int n_fields; int i; auto result = std::make_unique(); n_fields = g_struct_info_get_n_fields(struct_info); if (!result->reserve(n_fields)) { JS_ReportOutOfMemory(cx); return nullptr; } for (i = 0; i < n_fields; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); // We get the string as a jsid later, which is interned. We intern the // string here as well, so it will be the same string pointer JSString* atom = JS_AtomizeAndPinString(cx, field_info.name()); result->putNewInfallible(atom, std::move(field_info)); } return result; } /* * BoxedPrototype::ensure_field_map: * * BoxedPrototype keeps a cache of field names to introspection info. * We only create the field cache the first time it is needed. An alternative * would be to create it when the prototype is created, in BoxedPrototype::init. */ bool BoxedPrototype::ensure_field_map(JSContext* cx) { if (!m_field_map) m_field_map = create_field_map(cx, info()); return !!m_field_map; } /* * BoxedPrototype::lookup_field: * * Look up the introspection info corresponding to the field name @prop_name, * creating the field cache if necessary. */ GIFieldInfo* BoxedPrototype::lookup_field(JSContext* cx, JSString* prop_name) { if (!ensure_field_map(cx)) return nullptr; auto entry = m_field_map->lookup(prop_name); if (!entry) { gjs_throw(cx, "No field %s on boxed type %s", gjs_debug_string(prop_name).c_str(), name()); return nullptr; } return entry->value().get(); } /* Initialize a newly created Boxed from an object that is a "hash" of * properties to set as fields of the object. We don't require that every field * of the object be set. */ bool BoxedInstance::init_from_props(JSContext* context, JS::Value props_value) { size_t ix, length; if (!props_value.isObject()) { gjs_throw(context, "argument should be a hash with fields to set"); return false; } JS::RootedObject props(context, &props_value.toObject()); JS::Rooted ids(context, context); if (!JS_Enumerate(context, props, &ids)) { gjs_throw(context, "Failed to enumerate fields hash"); return false; } JS::RootedValue value(context); for (ix = 0, length = ids.length(); ix < length; ix++) { if (!ids[ix].isString()) { gjs_throw(context, "Fields hash contained a non-string field"); return false; } GIFieldInfo* field_info = get_prototype()->lookup_field(context, ids[ix].toString()); if (!field_info) return false; /* ids[ix] is reachable because props is rooted, but require_property * doesn't know that */ if (!gjs_object_require_property(context, props, "property list", JS::HandleId::fromMarkedLocation(ids[ix].address()), &value)) return false; if (!field_setter_impl(context, field_info, value)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool boxed_invoke_constructor(JSContext* context, JS::HandleObject obj, JS::HandleId constructor_name, const JS::CallArgs& args) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); JS::RootedObject js_constructor(context); if (!gjs_object_require_property( context, obj, nullptr, gjs->atoms().constructor(), &js_constructor)) return false; JS::RootedValue js_constructor_func(context); if (!gjs_object_require_property(context, js_constructor, NULL, constructor_name, &js_constructor_func)) return false; return gjs->call_function(nullptr, js_constructor_func, args, args.rval()); } /* * BoxedInstance::copy_boxed: * * Allocate a new boxed pointer using g_boxed_copy(), either from a raw boxed * pointer or another BoxedInstance. */ void BoxedInstance::copy_boxed(void* boxed_ptr) { own_ptr(g_boxed_copy(gtype(), boxed_ptr)); debug_lifecycle("Boxed pointer created with g_boxed_copy()"); } void BoxedInstance::copy_boxed(BoxedInstance* source) { copy_boxed(source->ptr()); } /* * BoxedInstance::copy_memory: * * Allocate a new boxed pointer by copying the contents of another boxed pointer * or another BoxedInstance. */ void BoxedInstance::copy_memory(void* boxed_ptr) { allocate_directly(); memcpy(m_ptr, boxed_ptr, g_struct_info_get_size(info())); } void BoxedInstance::copy_memory(BoxedInstance* source) { copy_memory(source->ptr()); } // See GIWrapperBase::constructor(). bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj, const JS::CallArgs& args) { // Short-circuit copy-construction in the case where we can use copy_boxed() // or copy_memory() BoxedBase* source_priv; if (args.length() == 1 && (source_priv = get_copy_source(context, args[0]))) { if (!source_priv->check_is_instance(context, "construct boxed object")) return false; if (g_type_is_a(gtype(), G_TYPE_BOXED)) { copy_boxed(source_priv->to_instance()); return true; } else if (get_prototype()->can_allocate_directly()) { copy_memory(source_priv->to_instance()); return true; } } if (gtype() == G_TYPE_VARIANT) { /* Short-circuit construction for GVariants by calling into the JS packing function */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!boxed_invoke_constructor(context, obj, atoms.new_internal(), args)) return false; // The return value of GLib.Variant.new_internal() gets its own // BoxedInstance, and the one we're setting up in this constructor is // discarded. debug_lifecycle( "Boxed construction delegated to GVariant constructor, " "boxed object discarded"); return true; } BoxedPrototype* proto = get_prototype(); // If the structure is registered as a boxed, we can create a new instance // by looking for a zero-args constructor and calling it. // Constructors don't really make sense for non-boxed types, since there is // no memory management for the return value, and zero_args_constructor and // default_constructor are always -1 for them. // // For backward compatibility, we choose the zero args constructor if one // exists, otherwise we malloc the correct amount of space if possible; // finally, we fallback on the default constructor. if (proto->has_zero_args_constructor()) { GjsAutoFunctionInfo func_info = proto->zero_args_constructor_info(); GIArgument rval_arg; GjsAutoError error; if (!g_function_info_invoke(func_info, NULL, 0, NULL, 0, &rval_arg, &error)) { gjs_throw(context, "Failed to invoke boxed constructor: %s", error->message); return false; } own_ptr(gjs_arg_steal(&rval_arg)); debug_lifecycle("Boxed pointer created from zero-args constructor"); } else if (proto->can_allocate_directly_without_pointers()) { allocate_directly(); } else if (proto->has_default_constructor()) { /* for simplicity, we simply delegate all the work to the actual JS * constructor function (which we retrieve from the JS constructor, * that is, Namespace.BoxedType, or object.constructor, given that * object was created with the right prototype. */ if (!boxed_invoke_constructor(context, obj, proto->default_constructor_name(), args)) return false; // Define the expected Error properties if (gtype() == G_TYPE_ERROR) { JS::RootedObject gerror(context, &args.rval().toObject()); if (!gjs_define_error_properties(context, gerror)) return false; } // The return value of the JS constructor gets its own BoxedInstance, // and this one is discarded. debug_lifecycle( "Boxed construction delegated to JS constructor, " "boxed object discarded"); return true; } else if (proto->can_allocate_directly()) { allocate_directly(); } else { gjs_throw(context, "Unable to construct struct type %s since it has no default " "constructor and cannot be allocated directly", name()); return false; } /* If we reach this code, we need to init from a map of fields */ if (args.length() == 0) return true; if (args.length() > 1) { gjs_throw(context, "Constructor with multiple arguments not supported for %s", name()); return false; } return init_from_props(context, args[0]); } BoxedInstance::~BoxedInstance() { if (m_owning_ptr) { if (m_allocated_directly) { if (gtype() == G_TYPE_VALUE) g_value_unset(m_ptr.as()); g_free(m_ptr.release()); } else { if (g_type_is_a(gtype(), G_TYPE_BOXED)) g_boxed_free(gtype(), m_ptr.release()); else if (g_type_is_a(gtype(), G_TYPE_VARIANT)) g_variant_unref(static_cast(m_ptr.release())); else g_assert_not_reached (); } } GJS_DEC_COUNTER(boxed_instance); } BoxedPrototype::~BoxedPrototype(void) { GJS_DEC_COUNTER(boxed_prototype); } /* * BoxedBase::get_field_info: * * Does the same thing as g_struct_info_get_field(), but throws a JS exception * if there is no such field. */ GIFieldInfo* BoxedBase::get_field_info(JSContext* cx, uint32_t id) const { GIFieldInfo* field_info = g_struct_info_get_field(info(), id); if (field_info == NULL) { gjs_throw(cx, "No field %d on boxed type %s", id, name()); return NULL; } return field_info; } /* * BoxedInstance::get_nested_interface_object: * @parent_obj: the BoxedInstance JS object that owns `this` * @field_info: introspection info for the field of the parent boxed type that * is another boxed type * @interface_info: introspection info for the nested boxed type * @value: return location for a new BoxedInstance JS object * * Some boxed types have a field that consists of another boxed type. We want to * be able to expose these nested boxed types without copying them, because * changing fields of the nested boxed struct should affect the enclosing boxed * struct. * * This method creates a new BoxedInstance and JS object for a nested boxed * struct. Since both the nested JS object and the parent boxed's JS object * refer to the same memory, the parent JS object will be prevented from being * garbage collected while the nested JS object is active. */ bool BoxedInstance::get_nested_interface_object( JSContext* context, JSObject* parent_obj, GIFieldInfo* field_info, GIStructInfo* struct_info, JS::MutableHandleValue value) const { int offset; if (!struct_is_simple(struct_info)) { gjs_throw(context, "Reading field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } offset = g_field_info_get_offset (field_info); JS::RootedObject obj{ context, gjs_new_object_with_generic_prototype(context, struct_info)}; if (!obj) return false; BoxedInstance* priv = BoxedInstance::new_for_js_object(context, obj); /* A structure nested inside a parent object; doesn't have an independent allocation */ priv->share_ptr(raw_ptr() + offset); priv->debug_lifecycle( "Boxed pointer created, pointing inside memory owned by parent"); /* We never actually read the reserved slot, but we put the parent object * into it to hold onto the parent object. */ JS::SetReservedSlot(obj, BoxedInstance::PARENT_OBJECT, JS::ObjectValue(*parent_obj)); value.setObject(*obj); return true; } /* * BoxedBase::field_getter: * * JSNative property getter that is called when accessing a field defined on a * boxed type. Delegates to BoxedInstance::field_getter_impl() if the minimal * conditions have been met. */ bool BoxedBase::field_getter(JSContext* context, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(context, argc, vp, args, obj, BoxedBase, priv); if (!priv->check_is_instance(context, "get a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); GjsAutoFieldInfo field_info = priv->get_field_info(context, field_ix); if (!field_info) return false; return priv->to_instance()->field_getter_impl(context, obj, field_info, args.rval()); } // See BoxedBase::field_getter(). bool BoxedInstance::field_getter_impl(JSContext* cx, JSObject* obj, GIFieldInfo* field_info, JS::MutableHandleValue rval) const { GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (!g_type_info_is_pointer(type_info) && g_type_info_get_tag(type_info) == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); if (interface_info.type() == GI_INFO_TYPE_STRUCT || interface_info.type() == GI_INFO_TYPE_BOXED) { return get_nested_interface_object(cx, obj, field_info, interface_info, rval); } } GIArgument arg; if (!g_field_info_get_field(field_info, m_ptr, &arg)) { gjs_throw(cx, "Reading field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && g_type_info_get_array_length(type_info) != -1) { auto length_field_ix = g_type_info_get_array_length(type_info); GjsAutoFieldInfo length_field_info = get_field_info(cx, length_field_ix); if (!length_field_info) { gjs_throw(cx, "Reading field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } GIArgument length_arg; if (!g_field_info_get_field(length_field_info, m_ptr, &length_arg)) { gjs_throw(cx, "Reading field %s.%s is not supported", name(), length_field_info.name()); return false; } GjsAutoTypeInfo length_type_info = g_field_info_get_type(length_field_info); size_t length = gjs_gi_argument_get_array_length( g_type_info_get_tag(length_type_info), &length_arg); return gjs_value_from_explicit_array(cx, rval, type_info, &arg, length); } return gjs_value_from_gi_argument(cx, rval, type_info, GJS_ARGUMENT_FIELD, GI_TRANSFER_EVERYTHING, &arg); } /* * BoxedInstance::set_nested_interface_object: * @field_info: introspection info for the field of the parent boxed type that * is another boxed type * @interface_info: introspection info for the nested boxed type * @value: holds a BoxedInstance JS object of type @interface_info * * Some boxed types have a field that consists of another boxed type. This * method is called from BoxedInstance::field_setter_impl() when any such field * is being set. The contents of the BoxedInstance JS object in @value are * copied into the correct place in this BoxedInstance's memory. */ bool BoxedInstance::set_nested_interface_object(JSContext* context, GIFieldInfo* field_info, GIStructInfo* struct_info, JS::HandleValue value) { int offset; if (!struct_is_simple(struct_info)) { gjs_throw(context, "Writing field %s.%s is not supported", name(), g_base_info_get_name(field_info)); return false; } JS::RootedObject proto{context, gjs_lookup_generic_prototype(context, struct_info)}; if (!proto) return false; /* If we can't directly copy from the source object we need * to construct a new temporary object. */ BoxedBase* source_priv = get_copy_source(context, value); if (!source_priv) { JS::RootedValueArray<1> args(context); args[0].set(value); JS::RootedObject tmp_object(context, gjs_construct_object_dynamic(context, proto, args)); if (!tmp_object || !for_js_typecheck(context, tmp_object, &source_priv)) return false; } if (!source_priv->check_is_instance(context, "copy")) return false; offset = g_field_info_get_offset (field_info); memcpy(raw_ptr() + offset, source_priv->to_instance()->ptr(), g_struct_info_get_size(source_priv->info())); return true; } // See BoxedBase::field_setter(). bool BoxedInstance::field_setter_impl(JSContext* context, GIFieldInfo* field_info, JS::HandleValue value) { GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (!g_type_info_is_pointer (type_info) && g_type_info_get_tag (type_info) == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(type_info); if (interface_info.type() == GI_INFO_TYPE_STRUCT || interface_info.type() == GI_INFO_TYPE_BOXED) { return set_nested_interface_object(context, field_info, interface_info, value); } } GIArgument arg; if (!gjs_value_to_gi_argument(context, value, type_info, g_base_info_get_name(field_info), GJS_ARGUMENT_FIELD, GI_TRANSFER_NOTHING, GjsArgumentFlags::MAY_BE_NULL, &arg)) return false; bool success = true; if (!g_field_info_set_field(field_info, m_ptr, &arg)) { gjs_throw(context, "Writing field %s.%s is not supported", name(), g_base_info_get_name(field_info)); success = false; } JS::AutoSaveExceptionState saved_exc(context); if (!gjs_gi_argument_release(context, GI_TRANSFER_NOTHING, type_info, &arg)) gjs_log_exception(context); saved_exc.restore(); return success; } /* * BoxedBase::field_setter: * * JSNative property setter that is called when writing to a field defined on a * boxed type. Delegates to BoxedInstance::field_setter_impl() if the minimal * conditions have been met. */ bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, BoxedBase, priv); if (!priv->check_is_instance(cx, "set a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); GjsAutoFieldInfo field_info = priv->get_field_info(cx, field_ix); if (!field_info) return false; if (!priv->to_instance()->field_setter_impl(cx, field_info, args[0])) return false; args.rval().setUndefined(); /* No stored value */ return true; } /* * BoxedPrototype::define_boxed_class_fields: * * Defines properties on the JS prototype object, with JSNative getters and * setters, for all the fields exposed by GObject introspection. */ bool BoxedPrototype::define_boxed_class_fields(JSContext* cx, JS::HandleObject proto) { int n_fields = g_struct_info_get_n_fields(info()); int i; // We define all fields as read/write so that the user gets an error // message. If we omitted fields or defined them read-only we'd: // // - Store a new property for a non-accessible field // - Silently do nothing when writing a read-only field // // Which is pretty confusing if the only reason a field isn't writable is // language binding or memory-management restrictions. // // We just go ahead and define the fields immediately for the class; doing // it lazily in boxed_resolve() would be possible as well if doing it ahead // of time caused too much start-up memory overhead. // // At this point methods have already been defined on the prototype, so we // may get name conflicts which we need to check for. for (i = 0; i < n_fields; i++) { GjsAutoFieldInfo field = g_struct_info_get_field(info(), i); JS::RootedValue private_id(cx, JS::PrivateUint32Value(i)); JS::RootedId id{cx, gjs_intern_string_to_id(cx, field.name())}; bool already_defined; if (!JS_HasOwnPropertyById(cx, proto, id, &already_defined)) return false; if (already_defined) { gjs_debug(GJS_DEBUG_GBOXED, "Field %s.%s.%s overlaps with method of the same name; " "skipping", ns(), name(), field.name()); continue; } if (!gjs_define_property_dynamic( cx, proto, field.name(), id, "boxed_field", &BoxedBase::field_getter, &BoxedBase::field_setter, private_id, GJS_MODULE_PROP_FLAGS)) return false; } return true; } // Overrides GIWrapperPrototype::trace_impl(). void BoxedPrototype::trace_impl(JSTracer* trc) { JS::TraceEdge(trc, &m_default_constructor_name, "Boxed::default_constructor_name"); if (m_field_map) m_field_map->trace(trc); } // clang-format off const struct JSClassOps BoxedBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &BoxedBase::new_enumerate, &BoxedBase::resolve, nullptr, // mayResolve &BoxedBase::finalize, nullptr, // call nullptr, // construct &BoxedBase::trace }; /* We allocate 1 extra reserved slot; this is typically unused, but if the * boxed is for a nested structure inside a parent structure, the * reserved slot is used to hold onto the parent Javascript object and * make sure it doesn't get freed. */ const struct JSClass BoxedBase::klass = { "GObject_Boxed", JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE, &BoxedBase::class_ops }; // clang-format on [[nodiscard]] static bool type_can_be_allocated_directly( GITypeInfo* type_info) { bool is_simple = true; if (g_type_info_is_pointer(type_info)) { if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); return type_can_be_allocated_directly(param_info); } return true; } else { switch (g_type_info_get_tag(type_info)) { case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); switch (interface.type()) { case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_STRUCT: return struct_is_simple(interface.as()); case GI_INFO_TYPE_UNION: /* FIXME: Need to implement */ is_simple = false; break; case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CONSTANT: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: is_simple = false; break; case GI_INFO_TYPE_INVALID_0: g_assert_not_reached(); break; case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: default: break; } break; } case GI_TYPE_TAG_BOOLEAN: case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_INT64: case GI_TYPE_TAG_UINT64: case GI_TYPE_TAG_FLOAT: case GI_TYPE_TAG_DOUBLE: case GI_TYPE_TAG_UNICHAR: case GI_TYPE_TAG_VOID: case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: default: break; } } return is_simple; } [[nodiscard]] static bool simple_struct_has_pointers(GIStructInfo*); [[nodiscard]] static bool direct_allocation_has_pointers( GITypeInfo* type_info) { if (g_type_info_is_pointer(type_info)) { if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_ARRAY && g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_C) { GjsAutoTypeInfo param_info = g_type_info_get_param_type(type_info, 0); return direct_allocation_has_pointers(param_info); } return g_type_info_get_tag(type_info) != GI_TYPE_TAG_VOID; } if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) return false; GjsAutoBaseInfo interface = g_type_info_get_interface(type_info); if (interface.type() == GI_INFO_TYPE_BOXED || interface.type() == GI_INFO_TYPE_STRUCT) return simple_struct_has_pointers(interface.as()); return false; } /* Check if the type of the boxed is "simple" - every field is a non-pointer * type that we know how to assign to. If so, then we can allocate and free * instances without needing a constructor. */ [[nodiscard]] static bool struct_is_simple(GIStructInfo* info) { int n_fields = g_struct_info_get_n_fields(info); bool is_simple = true; int i; /* If it's opaque, it's not simple */ if (n_fields == 0) return false; for (i = 0; i < n_fields && is_simple; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(info, i); GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); is_simple = type_can_be_allocated_directly(type_info); } return is_simple; } [[nodiscard]] static bool simple_struct_has_pointers(GIStructInfo* info) { g_assert(struct_is_simple(info) && "Don't call simple_struct_has_pointers() on a non-simple struct"); int n_fields = g_struct_info_get_n_fields(info); g_assert(n_fields > 0); for (int i = 0; i < n_fields; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(info, i); GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (direct_allocation_has_pointers(type_info)) return true; } return false; } BoxedPrototype::BoxedPrototype(GIStructInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_zero_args_constructor(-1), m_default_constructor(-1), m_default_constructor_name(JS::PropertyKey::Void()), m_can_allocate_directly(struct_is_simple(info)) { if (!m_can_allocate_directly) { m_can_allocate_directly_without_pointers = false; } else { m_can_allocate_directly_without_pointers = !simple_struct_has_pointers(info); } GJS_INC_COUNTER(boxed_prototype); } // Overrides GIWrapperPrototype::init(). bool BoxedPrototype::init(JSContext* context) { int i, n_methods; int first_constructor = -1; jsid first_constructor_name = JS::PropertyKey::Void(); jsid zero_args_constructor_name = JS::PropertyKey::Void(); if (m_gtype != G_TYPE_NONE) { /* If the structure is registered as a boxed, we can create a new instance by * looking for a zero-args constructor and calling it; constructors don't * really make sense for non-boxed types, since there is no memory management * for the return value. */ n_methods = g_struct_info_get_n_methods(m_info); for (i = 0; i < n_methods; ++i) { GIFunctionInfoFlags flags; GjsAutoFunctionInfo func_info = g_struct_info_get_method(m_info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) { if (first_constructor < 0) { first_constructor = i; first_constructor_name = gjs_intern_string_to_id(context, func_info.name()); if (first_constructor_name.isVoid()) return false; } if (m_zero_args_constructor < 0 && g_callable_info_get_n_args(func_info) == 0) { m_zero_args_constructor = i; zero_args_constructor_name = gjs_intern_string_to_id(context, func_info.name()); if (zero_args_constructor_name.isVoid()) return false; } if (m_default_constructor < 0 && strcmp(func_info.name(), "new") == 0) { m_default_constructor = i; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); m_default_constructor_name = atoms.new_(); } } } if (m_default_constructor < 0) { m_default_constructor = m_zero_args_constructor; m_default_constructor_name = zero_args_constructor_name; } if (m_default_constructor < 0) { m_default_constructor = first_constructor; m_default_constructor_name = first_constructor_name; } } return true; } /* * BoxedPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the boxed class. * * Define a boxed class constructor and prototype, including all the necessary * methods and properties. */ bool BoxedPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIStructInfo* info) { JS::RootedObject prototype(context), unused_constructor(context); GType gtype = g_registered_type_info_get_g_type(info); BoxedPrototype* priv = BoxedPrototype::create_class( context, in_object, info, gtype, &unused_constructor, &prototype); if (!priv || !priv->define_boxed_class_fields(context, prototype)) return false; if (gtype == G_TYPE_ERROR && !JS_DefineFunction(context, prototype, "toString", &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS)) return false; return true; } /* Helper function to make the public API more readable. The overloads are * specified explicitly in the public API, but the implementation uses * std::forward in order to avoid duplicating code. */ template JSObject* BoxedInstance::new_for_c_struct_impl(JSContext* cx, GIStructInfo* info, void* gboxed, Args&&... args) { if (gboxed == NULL) return NULL; gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s %p with JSObject", g_base_info_get_name((GIBaseInfo *)info), gboxed); JS::RootedObject obj(cx, gjs_new_object_with_generic_prototype(cx, info)); if (!obj) return nullptr; BoxedInstance* priv = BoxedInstance::new_for_js_object(cx, obj); if (!priv) return nullptr; if (!priv->init_from_c_struct(cx, gboxed, std::forward(args)...)) return nullptr; if (priv->gtype() == G_TYPE_ERROR && !gjs_define_error_properties(cx, obj)) return nullptr; return obj; } /* * BoxedInstance::new_for_c_struct: * * Creates a new BoxedInstance JS object from a C boxed struct pointer. * * There are two overloads of this method; the NoCopy overload will simply take * the passed-in pointer but not own it, while the normal method will take a * reference, or if the boxed type can be directly allocated, copy the memory. */ JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed) { return new_for_c_struct_impl(cx, info, gboxed); } JSObject* BoxedInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed, NoCopy no_copy) { return new_for_c_struct_impl(cx, info, gboxed, no_copy); } /* * BoxedInstance::init_from_c_struct: * * Do the necessary initialization when creating a BoxedInstance JS object from * a C boxed struct pointer. * * There are two overloads of this method; the NoCopy overload will simply take * the passed-in pointer, while the normal method will take a reference, or if * the boxed type can be directly allocated, copy the memory. */ bool BoxedInstance::init_from_c_struct(JSContext*, void* gboxed, NoCopy) { // We need to create a JS Boxed which references the original C struct, not // a copy of it. Used for G_SIGNAL_TYPE_STATIC_SCOPE. share_ptr(gboxed); debug_lifecycle("Boxed pointer acquired, memory not owned"); return true; } bool BoxedInstance::init_from_c_struct(JSContext* cx, void* gboxed) { if (gtype() != G_TYPE_NONE && g_type_is_a(gtype(), G_TYPE_BOXED)) { copy_boxed(gboxed); return true; } else if (gtype() == G_TYPE_VARIANT) { own_ptr(g_variant_ref_sink(static_cast(gboxed))); debug_lifecycle("Boxed pointer created by sinking GVariant ref"); return true; } else if (get_prototype()->can_allocate_directly()) { copy_memory(gboxed); return true; } gjs_throw(cx, "Can't create a Javascript object for %s; no way to copy", name()); return false; } void* BoxedInstance::copy_ptr(JSContext* cx, GType gtype, void* ptr) { if (g_type_is_a(gtype, G_TYPE_BOXED)) return g_boxed_copy(gtype, ptr); if (g_type_is_a(gtype, G_TYPE_VARIANT)) return g_variant_ref(static_cast(ptr)); gjs_throw(cx, "Can't transfer ownership of a structure type not registered as " "boxed"); return nullptr; } cjs-128.1/gi/boxed.h0000664000175000017500000002051015116312211013070 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_BOXED_H_ #define GI_BOXED_H_ #include #include // for size_t #include #include // for unique_ptr #include #include #include #include #include // for GCHashMap #include // for DefaultHasher #include #include #include #include "gi/cwrapper.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class BoxedPrototype; class BoxedInstance; class JSTracer; namespace JS { class CallArgs; } /* To conserve memory, we have two different kinds of private data for GBoxed * JS wrappers: BoxedInstance, and BoxedPrototype. Both inherit from BoxedBase * for their common functionality. For more information, see the notes in * wrapperutils.h. */ class BoxedBase : public GIWrapperBase { friend class CWrapperPointerOps; friend class GIWrapperBase; protected: explicit BoxedBase(BoxedPrototype* proto = nullptr) : GIWrapperBase(proto) {} static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GBOXED; static constexpr const char* DEBUG_TAG = "boxed"; static const struct JSClassOps class_ops; static const struct JSClass klass; // JS property accessors GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp); // Helper methods that work on either instances or prototypes GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* get_field_info(JSContext* cx, uint32_t id) const; public: [[nodiscard]] BoxedBase* get_copy_source(JSContext* cx, JS::Value value) const; }; class BoxedPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; using FieldMap = JS::GCHashMap, GjsAutoFieldInfo, js::DefaultHasher, js::SystemAllocPolicy>; int m_zero_args_constructor; // -1 if none int m_default_constructor; // -1 if none JS::Heap m_default_constructor_name; std::unique_ptr m_field_map; bool m_can_allocate_directly_without_pointers : 1; bool m_can_allocate_directly : 1; explicit BoxedPrototype(GIStructInfo* info, GType gtype); ~BoxedPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx); static constexpr InfoType::Tag info_type_tag = InfoType::Struct; // Accessors public: [[nodiscard]] bool can_allocate_directly_without_pointers() const { return m_can_allocate_directly_without_pointers; } [[nodiscard]] bool can_allocate_directly() const { return m_can_allocate_directly; } [[nodiscard]] bool has_zero_args_constructor() const { return m_zero_args_constructor >= 0; } [[nodiscard]] bool has_default_constructor() const { return m_default_constructor >= 0; } [[nodiscard]] GIFunctionInfo* zero_args_constructor_info() const { return g_struct_info_get_method(info(), m_zero_args_constructor); } // The ID is traced from the object, so it's OK to create a handle from it. [[nodiscard]] JS::HandleId default_constructor_name() const { return JS::HandleId::fromMarkedLocation( m_default_constructor_name.address()); } // JSClass operations private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable); void trace_impl(JSTracer* trc); // Helper methods GJS_JSAPI_RETURN_CONVENTION static std::unique_ptr create_field_map( JSContext* cx, GIStructInfo* struct_info); GJS_JSAPI_RETURN_CONVENTION bool ensure_field_map(JSContext* cx); GJS_JSAPI_RETURN_CONVENTION bool define_boxed_class_fields(JSContext* cx, JS::HandleObject proto); public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIStructInfo* info); GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* lookup_field(JSContext* cx, JSString* prop_name); }; class BoxedInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; friend class BoxedBase; // for field_getter, etc. // Reserved slots static const size_t PARENT_OBJECT = 1; bool m_allocated_directly : 1; bool m_owning_ptr : 1; // if set, the JS wrapper owns the C memory referred // to by m_ptr. explicit BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj); ~BoxedInstance(void); // Don't set GIWrapperBase::m_ptr directly. Instead, use one of these // setters to express your intention to own the pointer or not. void own_ptr(void* boxed_ptr) { g_assert(!m_ptr); m_ptr = boxed_ptr; m_owning_ptr = true; } void share_ptr(void* unowned_boxed_ptr) { g_assert(!m_ptr); m_ptr = unowned_boxed_ptr; m_owning_ptr = false; } // Methods for different ways to allocate the GBoxed pointer void allocate_directly(void); void copy_boxed(void* boxed_ptr); void copy_boxed(BoxedInstance* source); void copy_memory(void* boxed_ptr); void copy_memory(BoxedInstance* source); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool init_from_props(JSContext* cx, JS::Value props_value); GJS_JSAPI_RETURN_CONVENTION bool get_nested_interface_object(JSContext* cx, JSObject* parent_obj, GIFieldInfo* field_info, GIStructInfo* interface_info, JS::MutableHandleValue value) const; GJS_JSAPI_RETURN_CONVENTION bool set_nested_interface_object(JSContext* cx, GIFieldInfo* field_info, GIStructInfo* interface_info, JS::HandleValue value); GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr); // JS property accessors GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext* cx, JSObject* obj, GIFieldInfo* info, JS::MutableHandleValue rval) const; GJS_JSAPI_RETURN_CONVENTION bool field_setter_impl(JSContext* cx, GIFieldInfo* info, JS::HandleValue value); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); // Public API for initializing BoxedInstance JS object from C struct public: struct NoCopy {}; private: GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext* cx, void* gboxed); GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext* cx, void* gboxed, NoCopy); template GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct_impl( JSContext* cx, GIStructInfo* info, void* gboxed, Args&&... args); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed); GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info, void* gboxed, NoCopy); }; #endif // GI_BOXED_H_ cjs-128.1/gi/cjs_gi_probes.d0000664000175000017500000000037015116312211014575 0ustar fabiofabio/* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2010 Red Hat, Inc. */ provider gjs { probe object__wrapper__new(void*, void*, char *, char *); probe object__wrapper__finalize(void*, void*, char *, char *); }; cjs-128.1/gi/cjs_gi_trace.h0000664000175000017500000000112215116312211014401 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 Red Hat, Inc. // SPDX-FileContributor: Author: Colin Walters #ifndef GI_GJS_GI_TRACE_H_ #define GI_GJS_GI_TRACE_H_ #include #ifdef HAVE_DTRACE /* include the generated probes header and put markers in code */ #include "cjs_gi_probes.h" #define TRACE(probe) probe #else /* Wrap the probe to allow it to be removed when no systemtap available */ #define TRACE(probe) #endif #endif // GI_GJS_GI_TRACE_H_ cjs-128.1/gi/closure.cpp0000664000175000017500000001570315116312211014006 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2021 Canonical Ltd. // SPDX-FileContributor: Marco Trevisan #include #include // for g_assert #include #include #include #include #include #include "gi/closure.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" #include "util/log.h" namespace Gjs { Closure::Closure(JSContext* cx, JSObject* callable, bool root, const char* description GJS_USED_VERBOSE_GCLOSURE) : m_cx(cx) { GJS_INC_COUNTER(closure); GClosureNotify closure_notify; if (root) { // Fully manage closure lifetime if so asked auto* gjs = GjsContextPrivate::from_cx(cx); g_assert(cx == gjs->context()); m_callable.root(cx, callable); gjs->register_notifier(global_context_notifier_cb, this); closure_notify = [](void*, GClosure* closure) { static_cast(closure)->closure_invalidated(); }; } else { // Only mark the closure as invalid if memory is managed // outside (i.e. by object.c for signals) m_callable = callable; closure_notify = [](void*, GClosure* closure) { static_cast(closure)->closure_set_invalid(); }; } g_closure_add_invalidate_notifier(this, nullptr, closure_notify); gjs_debug_closure("Create closure %p which calls callable %p '%s'", this, m_callable.debug_addr(), description); } /* * Memory management of closures is "interesting" because we're keeping around * a JSContext* and then trying to use it spontaneously from the main loop. * I don't think that's really quite kosher, and perhaps the problem is that * (in xulrunner) we just need to save a different context. * * Or maybe the right fix is to create our own context just for this? * * But for the moment, we save the context that was used to create the closure. * * Here's the problem: this context can be destroyed. AFTER the * context is destroyed, or at least potentially after, the objects in * the context's global object may be garbage collected. Remember that * JSObject* belong to a runtime, not a context. * * There is apparently no robust way to track context destruction in * SpiderMonkey, because the context can be destroyed without running * the garbage collector, and xulrunner takes over the JS_SetContextCallback() * callback. So there's no callback for us. * * So, when we go to use our context, we iterate the contexts in the runtime * and see if ours is still in the valid list, and decide to invalidate * the closure if it isn't. * * The closure can thus be destroyed in several cases: * - invalidation by unref, e.g. when a signal is disconnected, closure is unref'd * - invalidation because we were invoked while the context was dead * - invalidation through finalization (we were garbage collected) * * These don't have to happen in the same order; garbage collection can * be either before, or after, context destruction. * */ void Closure::unset_context() { if (!m_cx) return; if (m_callable && m_callable.rooted()) { auto* gjs = GjsContextPrivate::from_cx(m_cx); gjs->unregister_notifier(global_context_notifier_cb, this); } m_cx = nullptr; } void Closure::global_context_finalized() { gjs_debug_closure( "Context global object destroy notifier on closure %p which calls " "callable %p", this, m_callable.debug_addr()); if (m_callable) { // Manually unset the context as we don't need to unregister the // notifier here, or we'd end up touching a vector we're iterating m_cx = nullptr; reset(); // Notify any closure reference holders they // may want to drop references. g_closure_invalidate(this); } } /* Invalidation is like "dispose" - it is guaranteed to happen at * finalize, but may happen before finalize. Normally, g_closure_invalidate() * is called when the "target" of the closure becomes invalid, so that the * source (the signal connection, say can be removed.) The usage above * in global_context_finalized() is typical. Since the target of the closure * is under our control, it's unlikely that g_closure_invalidate() will ever * be called by anyone else, but in case it ever does, it's slightly better * to remove the "keep alive" here rather than in the finalize notifier. * * Unlike "dispose" invalidation only happens once. */ void Closure::closure_invalidated() { GJS_DEC_COUNTER(closure); gjs_debug_closure("Invalidating closure %p which calls callable %p", this, m_callable.debug_addr()); if (!m_callable) { gjs_debug_closure(" (closure %p already dead, nothing to do)", this); return; } /* The context still exists, remove our destroy notifier. Otherwise we * would call the destroy notifier on an already-freed closure. * * This happens in the normal case, when the closure is * invalidated for some reason other than destruction of the * JSContext. */ gjs_debug_closure( " (closure %p's context was alive, " "removing our destroy notifier on global object)", this); reset(); } void Closure::closure_set_invalid() { gjs_debug_closure("Invalidating signal closure %p which calls callable %p", this, m_callable.debug_addr()); m_callable.prevent_collection(); reset(); GJS_DEC_COUNTER(closure); } bool Closure::invoke(JS::HandleObject this_obj, const JS::HandleValueArray& args, JS::MutableHandleValue retval) { if (!m_callable) { /* We were destroyed; become a no-op */ reset(); return false; } JSAutoRealm ar{m_cx, m_callable.get()}; if (gjs_log_exception(m_cx)) { gjs_debug_closure( "Exception was pending before invoking callback??? " "Not expected - closure %p", this); } JS::RootedValue v_callable{m_cx, JS::ObjectValue(*m_callable.get())}; bool ok = JS::Call(m_cx, this_obj, v_callable, args, retval); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx); if (!ok) { /* Exception thrown... */ gjs_debug_closure( "Closure invocation failed (exception should have been thrown) " "closure %p callable %p", this, m_callable.debug_addr()); return false; } if (gjs_log_exception_uncaught(m_cx)) { gjs_debug_closure( "Closure invocation succeeded but an exception was set" " - closure %p", m_cx); } gjs->schedule_gc_if_needed(); return true; } } // namespace Gjs cjs-128.1/gi/closure.h0000664000175000017500000001126215116312211013447 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2021 Canonical Ltd. // SPDX-FileContributor: Marco Trevisan #ifndef GI_CLOSURE_H_ #define GI_CLOSURE_H_ #include #include #include #include #include "gi/utils-inl.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" class JSTracer; namespace JS { class HandleValueArray; } namespace Gjs { class Closure : public GClosure { protected: Closure(JSContext*, JSObject* callable, bool root, const char* description); ~Closure() { unset_context(); } // Need to call this if inheriting from Closure to call the dtor template constexpr void add_finalize_notifier() { static_assert(std::is_base_of_v); g_closure_add_finalize_notifier( this, nullptr, [](void*, GClosure* closure) { static_cast(closure)->~C(); }); } void* operator new(size_t size) { return g_closure_new_simple(size, nullptr); } void operator delete(void* p) { unref(static_cast(p)); } static Closure* ref(Closure* self) { return static_cast(g_closure_ref(self)); } static void unref(Closure* self) { g_closure_unref(self); } public: using Ptr = GjsAutoPointer; [[nodiscard]] constexpr static Closure* for_gclosure(GClosure* gclosure) { // We need to do this in order to ensure this is a constant expression return static_cast(static_cast(gclosure)); } [[nodiscard]] static Closure* create(JSContext* cx, JSObject* callable, const char* description, bool root) { auto* self = new Closure(cx, callable, root, description); self->add_finalize_notifier(); return self; } [[nodiscard]] static Closure* create_marshaled(JSContext* cx, JSObject* callable, const char* description, bool root = true) { auto* self = new Closure(cx, callable, root, description); self->add_finalize_notifier(); g_closure_set_marshal(self, marshal_cb); return self; } [[nodiscard]] static Closure* create_for_signal(JSContext* cx, JSObject* callable, const char* description, int signal_id) { auto* self = new Closure(cx, callable, false /* root */, description); self->add_finalize_notifier(); g_closure_set_meta_marshal(self, gjs_int_to_pointer(signal_id), marshal_cb); return self; } // COMPAT: constexpr in C++23 JSObject* callable() const { return m_callable.get(); } [[nodiscard]] constexpr JSContext* context() const { return m_cx; } [[nodiscard]] constexpr bool is_valid() const { return !!m_cx; } GJS_JSAPI_RETURN_CONVENTION bool invoke(JS::HandleObject, const JS::HandleValueArray&, JS::MutableHandleValue); void trace(JSTracer* tracer) { if (m_callable) m_callable.trace(tracer, "signal connection"); } private: void unset_context(); void reset() { unset_context(); m_callable.reset(); m_cx = nullptr; } static void marshal_cb(GClosure* closure, GValue* ret, unsigned n_params, const GValue* params, void* hint, void* data) { for_gclosure(closure)->marshal(ret, n_params, params, hint, data); } static void global_context_notifier_cb(JSContext*, void* data) { static_cast(data)->global_context_finalized(); } void closure_invalidated(); void closure_set_invalid(); void global_context_finalized(); void marshal(GValue* ret, unsigned n_parms, const GValue* params, void* hint, void* data); // The saved context is used for lifetime management, so that the closure // will be torn down with the context that created it. // The context could be attached to the default context of the runtime // using if we wanted the closure to survive the context that created it. JSContext* m_cx; GjsMaybeOwned m_callable; }; } // namespace Gjs #endif // GI_CLOSURE_H_ cjs-128.1/gi/cwrapper.cpp0000664000175000017500000000155015116312211014150 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #include #include #include // for JSPROP_PERMANENT #include #include #include "gi/cwrapper.h" #include "gi/gtype.h" #include "cjs/atoms.h" #include "cjs/context-private.h" bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor, GType gtype) { JS::RootedObject gtype_obj(cx, gjs_gtype_create_gtype_wrapper(cx, gtype)); if (!gtype_obj) return false; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefinePropertyById(cx, constructor, atoms.gtype(), gtype_obj, JSPROP_PERMANENT); } cjs-128.1/gi/cwrapper.h0000664000175000017500000005477715116312211013640 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento #pragma once #include #include #include // for size_t #include // for integral_constant #include // for GType #include #include #include // for JSEXN_TYPEERR #include // for MutableHandleIdVector #include // for CurrentGlobalOrNull #include #include // for GetClass #include #include #include #include #include // for JSFUN_CONSTRUCTOR, JS_NewPlainObject, JS_GetFuncti... #include // for JSProto_Object, JSProtoKey #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" struct JSFunctionSpec; struct JSPropertySpec; // gi/cwrapper.h - template implementing a JS object that wraps a C pointer. // This template is used for many of the special objects in GJS. It contains // functionality such as storing the class's prototype in a global slot, where // it can be easily retrieved in order to create new objects. /* * GJS_CHECK_WRAPPER_PRIV: * @cx: JSContext pointer passed into JSNative function * @argc: Number of arguments passed into JSNative function * @vp: Argument value array passed into JSNative function * @args: Name for JS::CallArgs variable defined by this code snippet * @thisobj: Name for JS::RootedObject variable referring to function's this * @type: Type of private data * @priv: Name for private data variable defined by this code snippet * * A convenience macro for getting the private data from GJS classes using * CWrapper or GIWrapper. * Throws an error and returns false if the 'this' object is not the right type. * Use in any JSNative function. */ #define GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, thisobj, type, priv) \ GJS_GET_THIS(cx, argc, vp, args, thisobj); \ type* priv; \ if (!type::for_js_typecheck(cx, thisobj, &priv, &args)) \ return false; GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor, GType gtype); /* * CWrapperPointerOps: * * This class contains methods that are common to both CWrapper and * GIWrapperBase, for retrieving the wrapped C pointer out of the JS object. */ template class CWrapperPointerOps { public: /* * CWrapperPointerOps::for_js: * * Gets the wrapped C pointer belonging to a particular JS object wrapper. * Checks that the wrapper object has the right JSClass (Base::klass). * A null return value means either that the object didn't have the right * class, or that no private data has been set yet on the wrapper. To * distinguish between these two cases, use for_js_typecheck(). */ [[nodiscard]] static Wrapped* for_js(JSContext* cx, JS::HandleObject wrapper) { if (!JS_InstanceOf(cx, wrapper, &Base::klass, nullptr)) return nullptr; return JS::GetMaybePtrFromReservedSlot(wrapper, POINTER); } /* * CWrapperPointerOps::typecheck: * * Checks if the given wrapper object has the right JSClass (Base::klass). */ [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject wrapper, JS::CallArgs* args = nullptr) { return JS_InstanceOf(cx, wrapper, &Base::klass, args); } /* * CWrapperPointerOps::for_js_typecheck: * * Like for_js(), only throws a JS exception if the wrapper object has the * wrong class. Use in JSNative functions, where you have access to a * JS::CallArgs. The exception message will mention args.callee. * * The second overload can be used when you don't have access to an * instance of JS::CallArgs. The exception message will be generic. */ GJS_JSAPI_RETURN_CONVENTION static bool for_js_typecheck(JSContext* cx, JS::HandleObject wrapper, Wrapped** out, JS::CallArgs* args) { if (!typecheck(cx, wrapper, args)) return false; *out = for_js_nocheck(wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool for_js_typecheck(JSContext* cx, JS::HandleObject wrapper, Wrapped** out) { if (!typecheck(cx, wrapper)) { const JSClass* obj_class = JS::GetClass(wrapper); gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object %p is not a subclass of %s, it's a %s", wrapper.get(), Base::klass.name, obj_class->name); return false; } *out = for_js_nocheck(wrapper); return true; } /* * CWrapperPointerOps::for_js_nocheck: * * Use when you don't have a JSContext* available. This method is infallible * and cannot trigger a GC, so it's safe to use from finalize() and trace(). * (It can return null if no private data has been set yet on the wrapper.) */ [[nodiscard]] static Wrapped* for_js_nocheck(JSObject* wrapper) { return JS::GetMaybePtrFromReservedSlot(wrapper, POINTER); } protected: // The first reserved slot always stores the private pointer. static const size_t POINTER = 0; /* * CWrapperPointerOps::has_private: * * Returns true if a private C pointer has already been associated with the * wrapper object. */ [[nodiscard]] static bool has_private(JSObject* wrapper) { return !!JS::GetMaybePtrFromReservedSlot(wrapper, POINTER); } /* * CWrapperPointerOps::init_private: * * Call this to initialize the wrapper object's private C pointer. The * pointer should not be null. This should not be called twice, without * calling unset_private() in between. */ static void init_private(JSObject* wrapper, Wrapped* ptr) { assert(!has_private(wrapper) && "wrapper object should be a fresh object"); assert(ptr && "private pointer should not be null, use unset_private"); JS::SetReservedSlot(wrapper, POINTER, JS::PrivateValue(ptr)); } /* * CWrapperPointerOps::unset_private: * * Call this to remove the wrapper object's private C pointer. After calling * this, it's okay to call init_private() again. */ static void unset_private(JSObject* wrapper) { JS::SetReservedSlot(wrapper, POINTER, JS::UndefinedValue()); } }; /* * CWrapper: * * This template implements a JS object that wraps a C pointer, stores its * prototype in a global slot, and includes some optional functionality. * * If you derive from this class, you must implement: * - static constexpr GjsGlobalSlot PROTOTYPE_SLOT: global slot that the * prototype will be stored in * - static constexpr GjsDebugTopic DEBUG_TOPIC: debug log domain * - static constexpr JSClass klass: see documentation in SpiderMonkey; the * class may have JSClassOps (see below under CWrapper::class_ops) but must * at least have its js::ClassSpec member set. The members of js::ClassSpec * are createConstructor, createPrototype, constructorFunctions, * constructorProperties, prototypeFunctions, prototypeProperties, * finishInit, and flags. * - static Wrapped* constructor_impl(JSContext*, const JS::CallArgs&): custom * constructor functionality. If your JS object doesn't need a constructor * (i.e. user code can't use the `new` operator on it) then you can skip this * one, and include js::ClassSpec::DontDefineConstructor in your * class_spec's flags member. * - static constexpr unsigned constructor_nargs: number of arguments that the * constructor takes. If you implement constructor_impl() then also add this. * - void finalize_impl(JS::GCContext*, Wrapped*): called when the JS object is * garbage collected, use this to free the C pointer and do any other cleanup * * Add optional functionality by setting members of class_spec: * - createConstructor: the default is to create a constructor function that * calls constructor_impl(), unless flags includes DontDefineConstructor. If * you need something else, set this member. * - createPrototype: the default is to use a plain object as the prototype. If * you need something else, set this member. * - constructorFunctions: If the class has static methods, set this member. * - constructorProperties: If the class has static properties, set this * member. * - prototypeFunctions: If the class has methods, set this member. * - prototypeProperties: If the class has properties, set this member. * - finishInit: If you need to do any other initialization on the prototype or * the constructor object, set this member. * - flags: Specify DontDefineConstructor here if you don't want a user-visible * constructor. * * You may override CWrapper::class_ops if you want to opt in to more JSClass * operations. In that case, CWrapper includes some optional functionality: * - resolve: include &resolve in your class_ops, and implement * bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool*). * - new enumerate: include &new_enumerate in your class_ops, and implement * bool new_enumerate_impl(JSContext*, JS::HandleObject, * JS::MutableHandleIdVector, bool). * * This template uses the Curiously Recurring Template Pattern (CRTP), which * requires inheriting classes to declare themselves friends of the parent * class, so that the parent class can call their private methods. * * For more information about the CRTP, the Wikipedia article is informative: * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */ template class CWrapper : public CWrapperPointerOps { GJS_JSAPI_RETURN_CONVENTION static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (!args.isConstructing()) { gjs_throw_constructor_error(cx); return false; } JS::RootedObject object( cx, JS_NewObjectForConstructor(cx, &Base::klass, args)); if (!object) return false; Wrapped* priv = Base::constructor_impl(cx, args); if (!priv) return false; CWrapperPointerOps::init_private(object, priv); args.rval().setObject(*object); return true; } GJS_JSAPI_RETURN_CONVENTION static bool abstract_constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_throw_abstract_constructor_error(cx, args); return false; } // Debug methods, no-op unless verbose logging is compiled in protected: static void debug_lifecycle( const void* wrapped_ptr GJS_USED_VERBOSE_LIFECYCLE, const void* obj GJS_USED_VERBOSE_LIFECYCLE, const char* message GJS_USED_VERBOSE_LIFECYCLE) { gjs_debug_lifecycle(Base::DEBUG_TOPIC, "[%p: JS wrapper %p] %s", wrapped_ptr, obj, message); } void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS, const char* id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) const { gjs_debug_jsprop(Base::DEBUG_TOPIC, "[%p: JS wrapper %p] %s prop %s", this, obj, message, id); } void debug_jsprop(const char* message, jsid id, const void* obj) const { if constexpr (GJS_VERBOSE_ENABLE_PROPS) debug_jsprop(message, gjs_debug_id(id).c_str(), obj); } static void finalize(JS::GCContext* gcx, JSObject* obj) { Wrapped* priv = Base::for_js_nocheck(obj); // Call only CWrapper's original method here, not any overrides; e.g., // we don't want to deal with a read barrier. CWrapper::debug_lifecycle(priv, obj, "Finalize"); Base::finalize_impl(gcx, priv); CWrapperPointerOps::unset_private(obj); } static constexpr JSClassOps class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve &CWrapper::finalize, }; /* * CWrapper::create_abstract_constructor: * * This function can be used as the createConstructor member of class_ops. * It creates a constructor that always throws if it is the new.target. Use * it if you do need a constructor object to exist (for example, if it has * static methods) but you don't want it to be able to be called. */ GJS_JSAPI_RETURN_CONVENTION static JSObject* create_abstract_constructor(JSContext* cx, JSProtoKey) { return JS_GetFunctionObject( JS_NewFunction(cx, &Base::abstract_constructor, 0, JSFUN_CONSTRUCTOR, Base::klass.name)); } /* * CWrapper::define_gtype_prop: * * This function can be used as the finishInit member of class_ops. It * defines a '$gtype' property on the constructor. If you use it, you must * implement a gtype() static method that returns the GType to define. */ GJS_JSAPI_RETURN_CONVENTION static bool define_gtype_prop(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto [[maybe_unused]]) { return gjs_wrapper_define_gtype_prop(cx, ctor, Base::gtype()); } // Used to get the prototype when it is guaranteed to have already been // created GJS_JSAPI_RETURN_CONVENTION static JSObject* prototype(JSContext* cx) { JSObject* global = JS::CurrentGlobalOrNull(cx); assert(global && "Must be in a realm to call prototype()"); JS::RootedValue v_proto( cx, gjs_get_global_slot(global, Base::PROTOTYPE_SLOT)); assert(!v_proto.isUndefined() && "create_prototype() must be called before prototype()"); assert(v_proto.isObject() && "Someone stored some weird value in a global slot"); return &v_proto.toObject(); } GJS_JSAPI_RETURN_CONVENTION static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { Wrapped* priv = CWrapperPointerOps::for_js(cx, obj); assert(priv && "resolve called on wrong object"); priv->debug_jsprop("Resolve hook", id, obj); return priv->resolve_impl(cx, obj, id, resolved); } GJS_JSAPI_RETURN_CONVENTION static bool new_enumerate(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable) { Wrapped* priv = CWrapperPointerOps::for_js(cx, obj); assert(priv && "enumerate called on wrong object"); priv->debug_jsprop("Enumerate hook", "(all)", obj); return priv->new_enumerate_impl(cx, obj, properties, only_enumerable); } public: /* * CWrapper::create_prototype: * @module: Object on which to define the constructor as a property, or * the global object if not given * * Create the class's prototype and store it in the global slot, or * retrieve it if it has already been created. * * Unless DontDefineConstructor is in class_ops.flags, also create the * class's constructor, and define it as a property on @module. */ GJS_JSAPI_RETURN_CONVENTION static JSObject* create_prototype(JSContext* cx, JS::HandleObject module = nullptr) { JSObject* global = JS::CurrentGlobalOrNull(cx); assert(global && "Must be in a realm to call create_prototype()"); // If we've been here more than once, we already have the proto JS::RootedValue v_proto( cx, gjs_get_global_slot(global, Base::PROTOTYPE_SLOT)); if (!v_proto.isUndefined()) { assert(v_proto.isObject() && "Someone stored some weird value in a global slot"); return &v_proto.toObject(); } // Workaround for bogus warning // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94554 // Note that the corresponding function pointers in the js::ClassSpec // must be initialized as nullptr, not the default initializer! (see // e.g. CairoPath::class_spec.finishInit) using NullOpType = std::integral_constant; using CreateConstructorType = std::integral_constantcreateConstructor>; using CreatePrototypeType = std::integral_constantcreatePrototype>; using NullFuncsType = std::integral_constant; using ConstructorFuncsType = std::integral_constantconstructorFunctions>; using PrototypeFuncsType = std::integral_constantprototypeFunctions>; using NullPropsType = std::integral_constant; using ConstructorPropsType = std::integral_constantconstructorProperties>; using PrototypePropsType = std::integral_constantprototypeProperties>; using NullFinishOpType = std::integral_constant; using FinishInitType = std::integral_constantfinishInit>; // Create the prototype. If no createPrototype function is provided, // then the default is to create a plain object as the prototype. JS::RootedObject proto(cx); if constexpr (!std::is_same_v) { proto = Base::klass.spec->createPrototype(cx, JSProto_Object); } else { proto = JS_NewPlainObject(cx); } if (!proto) return nullptr; if constexpr (!std::is_same_v) { if (!JS_DefineProperties(cx, proto, Base::klass.spec->prototypeProperties)) return nullptr; } if constexpr (!std::is_same_v) { if (!JS_DefineFunctions(cx, proto, Base::klass.spec->prototypeFunctions)) return nullptr; } gjs_set_global_slot(global, Base::PROTOTYPE_SLOT, JS::ObjectValue(*proto)); // Create the constructor. If no createConstructor function is provided, // then the default is to call CWrapper::constructor() which calls // Base::constructor_impl(). JS::RootedObject ctor_obj(cx); if constexpr (!(Base::klass.spec->flags & js::ClassSpec::DontDefineConstructor)) { if constexpr (!std::is_same_v) { ctor_obj = Base::klass.spec->createConstructor(cx, JSProto_Object); } else { JSFunction* ctor = JS_NewFunction( cx, &Base::constructor, Base::constructor_nargs, JSFUN_CONSTRUCTOR, Base::klass.name); ctor_obj = JS_GetFunctionObject(ctor); } if (!ctor_obj || !JS_LinkConstructorAndPrototype(cx, ctor_obj, proto)) return nullptr; if constexpr (!std::is_same_v) { if (!JS_DefineProperties( cx, ctor_obj, Base::klass.spec->constructorProperties)) return nullptr; } if constexpr (!std::is_same_v) { if (!JS_DefineFunctions(cx, ctor_obj, Base::klass.spec->constructorFunctions)) return nullptr; } } if constexpr (!std::is_same_v) { if (!Base::klass.spec->finishInit(cx, ctor_obj, proto)) return nullptr; } // Put the constructor, if one exists, as a property on the module // object. If module is not given, we are defining a global class. if (ctor_obj) { JS::RootedObject in_obj(cx, module); if (!in_obj) in_obj = global; JS::RootedId class_name( cx, gjs_intern_string_to_id(cx, Base::klass.name)); if (class_name.isVoid() || !JS_DefinePropertyById(cx, in_obj, class_name, ctor_obj, GJS_MODULE_PROP_FLAGS)) return nullptr; } gjs_debug(GJS_DEBUG_CONTEXT, "Initialized class %s prototype %p", Base::klass.name, proto.get()); return proto; } /* * CWrapper::from_c_ptr(): * * Create a new CWrapper JS object from the given C pointer. The pointer * is copied using copy_ptr(), so you must implement that if you use this * function. */ GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext* cx, Wrapped* ptr) { JS::RootedObject proto(cx, Base::prototype(cx)); if (!proto) return nullptr; JS::RootedObject wrapper( cx, JS_NewObjectWithGivenProto(cx, &Base::klass, proto)); if (!wrapper) return nullptr; CWrapperPointerOps::init_private(wrapper, Base::copy_ptr(ptr)); debug_lifecycle(ptr, wrapper, "from_c_ptr"); return wrapper; } }; cjs-128.1/gi/enumeration.cpp0000664000175000017500000001015415116312211014653 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include #include #include #include #include #include // for JS_NewPlainObject #include "gi/cwrapper.h" #include "gi/enumeration.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_define_enum_value(JSContext *context, JS::HandleObject in_object, GIValueInfo *info) { const char* value_name; gsize i; gint64 value_val; value_name = g_base_info_get_name( (GIBaseInfo*) info); value_val = g_value_info_get_value(info); /* g-i converts enum members such as GDK_GRAVITY_SOUTH_WEST to * Gdk.GravityType.south-west (where 'south-west' is value_name) * Convert back to all SOUTH_WEST. */ GjsAutoChar fixed_name = g_ascii_strup(value_name, -1); for (i = 0; fixed_name[i]; ++i) { char c = fixed_name[i]; if (!(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))) fixed_name[i] = '_'; } gjs_debug(GJS_DEBUG_GENUM, "Defining enum value %s (fixed from %s) %" PRId64, fixed_name.get(), value_name, value_val); if (!JS_DefineProperty(context, in_object, fixed_name, (double) value_val, GJS_MODULE_PROP_FLAGS)) { gjs_throw(context, "Unable to define enumeration value %s %" G_GINT64_FORMAT " (no memory most likely)", fixed_name.get(), value_val); return false; } return true; } bool gjs_define_enum_values(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info) { int i, n_values; /* Fill in enum values first, so we don't define the enum itself until we're * sure we can finish successfully. */ n_values = g_enum_info_get_n_values(info); for (i = 0; i < n_values; ++i) { GjsAutoBaseInfo value_info = g_enum_info_get_value(info, i); if (!gjs_define_enum_value(context, in_object, value_info)) return false; } return true; } bool gjs_define_enumeration(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info) { const char *enum_name; /* An enumeration is simply an object containing integer attributes for * each enum value. It does not have a special JSClass. * * We could make this more typesafe and also print enum values as strings * if we created a class for each enum and made the enum values instances * of that class. However, it would have a lot more overhead and just * be more complicated in general. I think this is fine. */ enum_name = g_base_info_get_name( (GIBaseInfo*) info); JS::RootedObject enum_obj(context, JS_NewPlainObject(context)); if (!enum_obj) { gjs_throw(context, "Could not create enumeration %s.%s", g_base_info_get_namespace(info), enum_name); return false; } GType gtype = g_registered_type_info_get_g_type(info); if (!gjs_define_enum_values(context, enum_obj, info) || !gjs_define_static_methods(context, enum_obj, gtype, info) || !gjs_wrapper_define_gtype_prop(context, enum_obj, gtype)) return false; gjs_debug(GJS_DEBUG_GENUM, "Defining %s.%s as %p", g_base_info_get_namespace( (GIBaseInfo*) info), enum_name, enum_obj.get()); if (!JS_DefineProperty(context, in_object, enum_name, enum_obj, GJS_MODULE_PROP_FLAGS)) { gjs_throw(context, "Unable to define enumeration property (no memory most likely)"); return false; } return true; } cjs-128.1/gi/enumeration.h0000664000175000017500000000132715116312211014322 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_ENUMERATION_H_ #define GI_ENUMERATION_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enum_values(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enumeration(JSContext *context, JS::HandleObject in_object, GIEnumInfo *info); #endif // GI_ENUMERATION_H_ cjs-128.1/gi/foreign.cpp0000664000175000017500000001024415116312211013756 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC #include #include // for size_t #include #include #include // for pair #include #include #include #include #include "gi/foreign.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" enum LoadedStatus { NotLoaded, Loaded }; static std::unordered_map foreign_modules{ {"cairo", NotLoaded}}; using StructID = std::pair; struct StructIDHash { [[nodiscard]] size_t operator()(StructID val) const { std::hash hasher; return hasher(val.first) ^ hasher(val.second); } }; static std::unordered_map foreign_structs_table; [[nodiscard]] static bool gjs_foreign_load_foreign_module( JSContext* cx, const char* gi_namespace) { auto entry = foreign_modules.find(gi_namespace); if (entry == foreign_modules.end()) return false; if (entry->second == Loaded) return true; // FIXME: Find a way to check if a module is imported and only execute this // statement if it isn't std::string script = "imports." + entry->first + ';'; JS::RootedValue retval{cx}; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (!gjs->eval_with_scope(nullptr, script.c_str(), script.length(), "", &retval)) { g_critical("ERROR importing foreign module %s\n", gi_namespace); return false; } entry->second = Loaded; return true; } void gjs_struct_foreign_register(const char* gi_namespace, const char* type_name, GjsForeignInfo* info) { foreign_structs_table.insert({{gi_namespace, type_name}, info}); } GJS_JSAPI_RETURN_CONVENTION static GjsForeignInfo* gjs_struct_foreign_lookup(JSContext* cx, GIStructInfo* info) { const char* ns = g_base_info_get_namespace(info); StructID key{ns, g_base_info_get_name(info)}; auto entry = foreign_structs_table.find(key); if (entry == foreign_structs_table.end()) { if (gjs_foreign_load_foreign_module(cx, ns)) entry = foreign_structs_table.find(key); } if (entry == foreign_structs_table.end()) { gjs_throw(cx, "Unable to find module implementing foreign type %s.%s", key.first.c_str(), key.second.c_str()); return nullptr; } return entry->second; } bool gjs_struct_foreign_convert_to_gi_argument( JSContext* context, JS::Value value, GIStructInfo* info, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { GjsForeignInfo *foreign; foreign = gjs_struct_foreign_lookup(context, info); if (!foreign) return false; if (!foreign->to_func(context, value, arg_name, argument_type, transfer, flags, arg)) return false; return true; } bool gjs_struct_foreign_convert_from_gi_argument(JSContext* context, JS::MutableHandleValue value_p, GIStructInfo* info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(context, info); if (!foreign) return false; if (!foreign->from_func(context, value_p, arg)) return false; return true; } bool gjs_struct_foreign_release_gi_argument(JSContext* context, GITransfer transfer, GIStructInfo* info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(context, info); if (!foreign) return false; if (!foreign->release_func) return true; if (!foreign->release_func(context, transfer, arg)) return false; return true; } cjs-128.1/gi/foreign.h0000664000175000017500000000411715116312211013425 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC #ifndef GI_FOREIGN_H_ #define GI_FOREIGN_H_ #include #include #include #include #include "gi/arg.h" #include "cjs/macros.h" typedef bool (*GjsArgOverrideToGIArgumentFunc)(JSContext*, JS::Value, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); typedef bool (*GjsArgOverrideFromGIArgumentFunc)(JSContext*, JS::MutableHandleValue, GIArgument*); typedef bool (*GjsArgOverrideReleaseGIArgumentFunc)(JSContext*, GITransfer, GIArgument*); typedef struct { GjsArgOverrideToGIArgumentFunc to_func; GjsArgOverrideFromGIArgumentFunc from_func; GjsArgOverrideReleaseGIArgumentFunc release_func; } GjsForeignInfo; void gjs_struct_foreign_register(const char* gi_namespace, const char* type_name, GjsForeignInfo* info); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_to_gi_argument(JSContext*, JS::Value, GIStructInfo*, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_from_gi_argument(JSContext*, JS::MutableHandleValue, GIStructInfo*, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_release_gi_argument(JSContext*, GITransfer, GIStructInfo*, GIArgument*); #endif // GI_FOREIGN_H_ cjs-128.1/gi/function.cpp0000664000175000017500000014744515116312211014170 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include // for NULL, size_t #include #include #include // for unique_ptr #include #include #include #include #include #include #include #include #include #include // for IsCallable #include #include #include // for JS_ReportOutOfMemory #include #include // for RuntimeHeapIsCollecting #include #include // for JSPROP_PERMANENT #include #include // for GetRealmFunctionPrototype #include #include #include #include #include #include // for HandleValueArray #include // for JSProtoKey #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/gerror.h" #include "gi/object.h" #include "gi/utils-inl.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "cjs/profiler-private.h" #include "util/log.h" /* We use guint8 for arguments; functions can't * have more than this. */ #define GJS_ARG_INDEX_INVALID G_MAXUINT8 namespace Gjs { class Function : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_function; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GFUNCTION; GjsAutoCallableInfo m_info; ArgsCache m_arguments; uint8_t m_js_in_argc; uint8_t m_js_out_argc; GIFunctionInvoker m_invoker; explicit Function(GICallableInfo* info) : m_info(info, GjsAutoTakeOwnership()), m_js_in_argc(0), m_js_out_argc(0), m_invoker({}) { GJS_INC_COUNTER(function); } ~Function(); GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx, GType gtype = G_TYPE_NONE); /** * Like CWrapperPointerOps::for_js_typecheck(), but additionally checks that * the pointer is not null, which is the case for prototype objects. */ GJS_JSAPI_RETURN_CONVENTION static bool for_js_instance(JSContext* cx, JS::HandleObject obj, Function** out, JS::CallArgs* args) { Function* priv; if (!Function::for_js_typecheck(cx, obj, &priv, args)) return false; if (!priv) { // This is the prototype gjs_throw(cx, "Impossible on prototype; only on instances"); return false; } *out = priv; return true; } GJS_JSAPI_RETURN_CONVENTION static bool call(JSContext* cx, unsigned argc, JS::Value* vp); static void finalize_impl(JS::GCContext*, Function* priv); GJS_JSAPI_RETURN_CONVENTION static bool get_length(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION bool to_string_impl(JSContext* cx, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool finish_invoke(JSContext* cx, const JS::CallArgs& args, GjsFunctionCallState* state, GIArgument* r_value = nullptr); GJS_JSAPI_RETURN_CONVENTION static JSObject* inherit_builtin_function(JSContext* cx, JSProtoKey) { JS::RootedObject builtin_function_proto( cx, JS::GetRealmFunctionPrototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, builtin_function_proto); } static const JSClassOps class_ops; static const JSPropertySpec proto_props[]; static const JSFunctionSpec proto_funcs[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor &Function::inherit_builtin_function, nullptr, // constructorFunctions nullptr, // constructorProperties Function::proto_funcs, Function::proto_props, nullptr, // finishInit js::ClassSpec::DontDefineConstructor}; static constexpr JSClass klass = { "GIRepositoryFunction", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &Function::class_ops, &Function::class_spec}; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx, GType gtype, GICallableInfo* info); [[nodiscard]] std::string format_name(); GJS_JSAPI_RETURN_CONVENTION bool invoke(JSContext* cx, const JS::CallArgs& args, JS::HandleObject this_obj = nullptr, GIArgument* r_value = nullptr); GJS_JSAPI_RETURN_CONVENTION static bool invoke_constructor_uncached(JSContext* cx, GIFunctionInfo* info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { Function function(info); if (!function.init(cx)) return false; return function.invoke(cx, args, obj, rvalue); } }; } // namespace Gjs template static inline void set_ffi_arg(void* result, GIArgument* value) { if constexpr (std::is_integral_v && std::is_signed_v) { *static_cast(result) = gjs_arg_get(value); } else if constexpr (std::is_floating_point_v || std::is_unsigned_v) { *static_cast(result) = gjs_arg_get(value); } else if constexpr (std::is_pointer_v) { *static_cast(result) = gjs_pointer_to_int(gjs_arg_get(value)); } } static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type, void* result, GIArgument* return_value) { // Be consistent with gjs_value_to_gi_argument() switch (g_type_info_get_tag(ret_type)) { case GI_TYPE_TAG_VOID: g_assert_not_reached(); case GI_TYPE_TAG_INT8: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT8: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT16: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT16: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT32: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UINT32: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_BOOLEAN: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UNICHAR: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INT64: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_INTERFACE: { GjsAutoBaseInfo interface_info(g_type_info_get_interface(ret_type)); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_ENUM || interface_type == GI_INFO_TYPE_FLAGS) set_ffi_arg(result, return_value); else set_ffi_arg(result, return_value); } break; case GI_TYPE_TAG_UINT64: // Other primitive types need to squeeze into 64-bit ffi_arg too set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_FLOAT: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_DOUBLE: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_GTYPE: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: set_ffi_arg(result, return_value); break; case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_ERROR: default: set_ffi_arg(result, return_value); break; } } void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when, const char* reason, bool dump_stack) { std::ostringstream message; message << "Attempting to run a JS callback " << when << ". " << "This is most likely caused by " << reason << ". " << "Because it would crash the application, it has been blocked."; if (m_info) { message << "\nThe offending callback was " << m_info.name() << "()" << (m_is_vfunc ? ", a vfunc." : "."); } if (dump_stack) { message << "\n" << gjs_dumpstack_string(); } g_critical("%s", message.str().c_str()); } /* This is our main entry point for ffi_closure callbacks. * ffi_prep_closure is doing pure magic and replaces the original * function call with this one which gives us the ffi arguments, * a place to store the return value and our use data. * In other words, everything we need to call the JS function and * getting the return value back. */ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { GITypeInfo ret_type; // Fill in the result with some hopefully neutral value g_callable_info_load_return_type(m_info, &ret_type); if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) { GIArgument argument = {}; gjs_gi_argument_init_default(&ret_type, &argument); set_return_ffi_arg_from_gi_argument(&ret_type, result, &argument); } if (G_UNLIKELY(!is_valid())) { warn_about_illegal_js_callback( "during shutdown", "destroying a Clutter actor or GTK widget with ::destroy signal " "connected, or using the destroy(), dispose(), or remove() vfuncs", true); return; } JSContext* context = this->context(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (JS::RuntimeHeapIsCollecting()) { warn_about_illegal_js_callback( "during garbage collection", "destroying a Clutter actor or GTK widget with ::destroy signal " "connected, or using the destroy(), dispose(), or remove() vfuncs", true); return; } if (G_UNLIKELY(!gjs->is_owner_thread())) { warn_about_illegal_js_callback("on a different thread", "an API not intended to be used in JS", false); return; } JSAutoRealm ar(context, callable()); int n_args = g_callable_info_get_n_args(m_info); g_assert(n_args >= 0); struct AutoCallbackData { AutoCallbackData(GjsCallbackTrampoline* trampoline, GjsContextPrivate* gjs) : trampoline(trampoline), gjs(gjs) {} ~AutoCallbackData() { if (trampoline->m_scope == GI_SCOPE_TYPE_ASYNC) { // We don't release the trampoline here as we've an extra ref // that has been set in gjs_marshal_callback_in() gjs_debug_closure("Saving async closure for gc cleanup %p", trampoline); gjs->async_closure_enqueue_for_gc(trampoline); } gjs->schedule_gc_if_needed(); } GjsCallbackTrampoline* trampoline; GjsContextPrivate* gjs; }; AutoCallbackData callback_data(this, gjs); JS::RootedObject this_object(context); int c_args_offset = 0; GObject* gobj = nullptr; if (m_is_vfunc) { gobj = G_OBJECT(gjs_arg_get(args[0])); if (gobj) { this_object = ObjectInstance::wrapper_from_gobject(context, gobj); if (!this_object) { if (g_object_get_qdata(gobj, ObjectBase::disposed_quark())) { warn_about_illegal_js_callback( "on disposed object", "using the destroy(), dispose(), or remove() vfuncs", false); } gjs_log_exception(context); return; } } /* "this" is not included in the GI signature, but is in the C (and * FFI) signature */ c_args_offset = 1; } JS::RootedValue rval(context); if (!callback_closure_inner(context, this_object, gobj, &rval, args, &ret_type, n_args, c_args_offset, result)) { if (!JS_IsExceptionPending(context)) { // "Uncatchable" exception thrown, we have to exit. We may be in a // main loop, or maybe not, but there's no way to tell, so we have // to exit here instead of propagating the exception back to the // original calling JS code. uint8_t code; if (gjs->should_exit(&code)) gjs->exit_immediately(code); // Some other uncatchable exception, e.g. out of memory g_error("Call to %s (%s.%s) terminated with uncatchable exception", gjs_debug_callable(callable()).c_str(), m_info.ns(), m_info.name()); } // If the callback has a GError** argument, then make a GError from the // value that was thrown. Otherwise, log it as "uncaught" (critical // instead of warning) if (!g_callable_info_can_throw_gerror(m_info)) { gjs_log_exception_uncaught(context); return; } // The GError** pointer is the last argument, and is not included in // the n_args GIArgument* error_argument = args[n_args + c_args_offset]; auto* gerror = gjs_arg_get(error_argument); GError* local_error = gjs_gerror_make_from_thrown_value(context); g_propagate_error(gerror, local_error); } } inline GIArgument* get_argument_for_arg_info(GIArgInfo* arg_info, GIArgument** args, int index) { if (!g_arg_info_is_caller_allocates(arg_info)) return *reinterpret_cast(args[index]); else return args[index]; } bool GjsCallbackTrampoline::callback_closure_inner( JSContext* context, JS::HandleObject this_object, GObject* gobject, JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type, int n_args, int c_args_offset, void* result) { int n_outargs = 0; JS::RootedValueVector jsargs(context); if (!jsargs.reserve(n_args)) g_error("Unable to reserve space for vector"); GITypeTag ret_tag = g_type_info_get_tag(ret_type); bool ret_type_is_void = ret_tag == GI_TYPE_TAG_VOID; bool in_args_to_cleanup = false; for (int i = 0, n_jsargs = 0; i < n_args; i++) { GIArgInfo arg_info; GITypeInfo type_info; GjsParamType param_type; g_callable_info_load_arg(m_info, i, &arg_info); g_arg_info_load_type(&arg_info, &type_info); /* Skip void * arguments */ if (g_type_info_get_tag(&type_info) == GI_TYPE_TAG_VOID) continue; if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT) { n_outargs++; continue; } if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT) n_outargs++; if (g_arg_info_get_ownership_transfer(&arg_info) != GI_TRANSFER_NOTHING) in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER; param_type = m_param_types[i]; switch (param_type) { case PARAM_SKIPPED: continue; case PARAM_ARRAY: { gint array_length_pos = g_type_info_get_array_length(&type_info); GIArgInfo array_length_arg; GITypeInfo arg_type_info; g_callable_info_load_arg(m_info, array_length_pos, &array_length_arg); g_arg_info_load_type(&array_length_arg, &arg_type_info); size_t length = gjs_gi_argument_get_array_length( g_type_info_get_tag(&arg_type_info), args[array_length_pos + c_args_offset]); if (!jsargs.growBy(1)) g_error("Unable to grow vector"); if (!gjs_value_from_explicit_array( context, jsargs[n_jsargs++], &type_info, g_arg_info_get_ownership_transfer(&arg_info), args[i + c_args_offset], length)) return false; break; } case PARAM_NORMAL: { if (!jsargs.growBy(1)) g_error("Unable to grow vector"); GIArgument* arg = args[i + c_args_offset]; if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT && !g_arg_info_is_caller_allocates(&arg_info)) arg = *reinterpret_cast(arg); if (!gjs_value_from_gi_argument(context, jsargs[n_jsargs++], &type_info, arg, false)) return false; break; } case PARAM_CALLBACK: /* Callbacks that accept another callback as a parameter are not * supported, see gjs_callback_trampoline_new() */ case PARAM_UNKNOWN: // PARAM_UNKNOWN is currently not ever set on a callback's args. default: g_assert_not_reached(); } } if (!invoke(this_object, jsargs, rval)) return false; if (n_outargs == 0 && ret_type_is_void) { /* void return value, no out args, nothing to do */ } else if (n_outargs == 0) { GIArgument argument; GITransfer transfer; transfer = g_callable_info_get_caller_owns(m_info); /* non-void return value, no out args. Should * be a single return value. */ if (!gjs_value_to_gi_argument(context, rval, ret_type, "callback", GJS_ARGUMENT_RETURN_VALUE, transfer, GjsArgumentFlags::MAY_BE_NULL, &argument)) return false; set_return_ffi_arg_from_gi_argument(ret_type, result, &argument); } else if (n_outargs == 1 && ret_type_is_void) { /* void return value, one out args. Should * be a single return value. */ for (int i = 0; i < n_args; i++) { GIArgInfo arg_info; g_callable_info_load_arg(m_info, i, &arg_info); if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) continue; if (!gjs_value_to_callback_out_arg( context, rval, &arg_info, get_argument_for_arg_info(&arg_info, args, i + c_args_offset))) return false; break; } } else { bool is_array = rval.isObject(); if (!JS::IsArrayObject(context, rval, &is_array)) return false; if (!is_array) { gjs_throw(context, "Call to %s (%s.%s) returned unexpected value, expecting " "an Array", gjs_debug_callable(callable()).c_str(), m_info.ns(), m_info.name()); return false; } JS::RootedValue elem(context); JS::RootedObject out_array(context, rval.toObjectOrNull()); gsize elem_idx = 0; /* more than one of a return value or an out argument. * Should be an array of output values. */ if (!ret_type_is_void) { GIArgument argument; GITransfer transfer = g_callable_info_get_caller_owns(m_info); if (!JS_GetElement(context, out_array, elem_idx, &elem)) return false; if (!gjs_value_to_gi_argument(context, elem, ret_type, "callback", GJS_ARGUMENT_RETURN_VALUE, transfer, GjsArgumentFlags::MAY_BE_NULL, &argument)) return false; if ((ret_tag == GI_TYPE_TAG_FILENAME || ret_tag == GI_TYPE_TAG_UTF8) && transfer == GI_TRANSFER_NOTHING) { // We duplicated the string so not to leak we need to both // ensure that the string is bound to the object lifetime or // created once if (gobject) { ObjectInstance::associate_string( gobject, gjs_arg_get(&argument)); } else { GjsAutoChar str = gjs_arg_steal(&argument); gjs_arg_set(&argument, g_intern_string(str)); } } set_return_ffi_arg_from_gi_argument(ret_type, result, &argument); elem_idx++; } for (int i = 0; i < n_args; i++) { GIArgInfo arg_info; g_callable_info_load_arg(m_info, i, &arg_info); if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN) continue; if (!JS_GetElement(context, out_array, elem_idx, &elem)) return false; if (!gjs_value_to_callback_out_arg( context, elem, &arg_info, get_argument_for_arg_info(&arg_info, args, i + c_args_offset))) return false; elem_idx++; } } if (!in_args_to_cleanup) return true; for (int i = 0; i < n_args; i++) { GIArgInfo arg_info; g_callable_info_load_arg(m_info, i, &arg_info); GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info); if (transfer == GI_TRANSFER_NOTHING) continue; if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN) continue; GIArgument* arg = args[i + c_args_offset]; if (m_scope == GI_SCOPE_TYPE_CALL) { GITypeInfo type_info; g_arg_info_load_type(&arg_info, &type_info); if (!gjs_gi_argument_release(context, transfer, &type_info, arg)) return false; continue; } struct InvalidateData { GIArgInfo arg_info; GIArgument arg; }; auto* data = new InvalidateData({arg_info, *arg}); g_closure_add_invalidate_notifier( this, data, [](void* invalidate_data, GClosure* c) { auto* self = static_cast(c); std::unique_ptr data( static_cast(invalidate_data)); GITransfer transfer = g_arg_info_get_ownership_transfer(&data->arg_info); GITypeInfo type_info; g_arg_info_load_type(&data->arg_info, &type_info); if (!gjs_gi_argument_release(self->context(), transfer, &type_info, &data->arg)) { gjs_throw(self->context(), "Impossible to release closure argument '%s'", g_base_info_get_name(&data->arg_info)); } }); } return true; } GjsCallbackTrampoline* GjsCallbackTrampoline::create( JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc) { g_assert(JS::IsCallable(callable) && "tried to create a callback trampoline for a non-callable object"); auto* trampoline = new GjsCallbackTrampoline( cx, callable, callable_info, scope, has_scope_object, is_vfunc); if (!trampoline->initialize()) { g_closure_unref(trampoline); return nullptr; } return trampoline; } decltype(GjsCallbackTrampoline::s_forever_closure_list) GjsCallbackTrampoline::s_forever_closure_list; GjsCallbackTrampoline::GjsCallbackTrampoline( JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc) // The rooting rule is: // - notify callbacks in GObject methods are traced from the scope object // - async and call callbacks, and other notify callbacks, are rooted // - vfuncs are traced from the GObject prototype : Closure(cx, callable, scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object, g_base_info_get_name(callable_info)), m_info(callable_info, GjsAutoTakeOwnership()), m_param_types(std::make_unique( g_callable_info_get_n_args(callable_info))), m_scope(scope), m_is_vfunc(is_vfunc) { add_finalize_notifier(); } GjsCallbackTrampoline::~GjsCallbackTrampoline() { if (m_info && m_closure) { #if GI_CHECK_VERSION(1, 71, 0) g_callable_info_destroy_closure(m_info, m_closure); #else g_callable_info_free_closure(m_info, m_closure); #endif } } void GjsCallbackTrampoline::mark_forever() { s_forever_closure_list.emplace_back(this, GjsAutoTakeOwnership{}); } void GjsCallbackTrampoline::prepare_shutdown() { s_forever_closure_list.clear(); } ffi_closure* GjsCallbackTrampoline::create_closure() { auto callback = [](ffi_cif*, void* result, void** ffi_args, void* data) { auto** args = reinterpret_cast(ffi_args); g_assert(data && "Trampoline data is not set"); Gjs::Closure::Ptr trampoline(static_cast(data), GjsAutoTakeOwnership()); trampoline.as()->callback_closure(args, result); }; #if GI_CHECK_VERSION(1, 71, 0) return g_callable_info_create_closure(m_info, &m_cif, callback, this); #else return g_callable_info_prepare_closure(m_info, &m_cif, callback, this); #endif } bool GjsCallbackTrampoline::initialize() { g_assert(is_valid()); g_assert(!m_closure); /* Analyze param types and directions, similarly to * init_cached_function_data */ int n_param_types = g_callable_info_get_n_args(m_info); for (int i = 0; i < n_param_types; i++) { GIDirection direction; GIArgInfo arg_info; GITypeInfo type_info; GITypeTag type_tag; if (m_param_types[i] == PARAM_SKIPPED) continue; g_callable_info_load_arg(m_info, i, &arg_info); g_arg_info_load_type(&arg_info, &type_info); direction = g_arg_info_get_direction(&arg_info); type_tag = g_type_info_get_tag(&type_info); if (direction != GI_DIRECTION_IN) { /* INOUT and OUT arguments are handled differently. */ continue; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info); GIInfoType interface_type = interface_info.type(); if (interface_type == GI_INFO_TYPE_CALLBACK) { gjs_throw(context(), "%s %s accepts another callback as a parameter. This " "is not supported", m_is_vfunc ? "VFunc" : "Callback", m_info.name()); return false; } } else if (type_tag == GI_TYPE_TAG_ARRAY) { if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) { int array_length_pos = g_type_info_get_array_length(&type_info); if (array_length_pos < 0) continue; if (array_length_pos < n_param_types) { GIArgInfo length_arg_info; g_callable_info_load_arg(m_info, array_length_pos, &length_arg_info); if (g_arg_info_get_direction(&length_arg_info) != direction) { gjs_throw(context(), "%s %s has an array with different-direction " "length argument. This is not supported", m_is_vfunc ? "VFunc" : "Callback", m_info.name()); return false; } m_param_types[array_length_pos] = PARAM_SKIPPED; m_param_types[i] = PARAM_ARRAY; } } } } m_closure = create_closure(); return true; } // Intended for error messages std::string Gjs::Function::format_name() { bool is_method = g_callable_info_is_method(m_info); std::string retval = is_method ? "method" : "function"; retval += ' '; retval += m_info.ns(); retval += '.'; if (is_method) { retval += g_base_info_get_name(g_base_info_get_container(m_info)); retval += '.'; } retval += m_info.name(); return retval; } namespace Gjs { static void* get_return_ffi_pointer_from_gi_argument( GITypeTag tag, GITypeInfo* return_type, GIFFIReturnValue* return_value) { if (return_type && g_type_info_is_pointer(return_type)) return &gjs_arg_member(return_value); switch (tag) { case GI_TYPE_TAG_VOID: return nullptr; case GI_TYPE_TAG_INT8: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INT16: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INT32: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UINT8: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UINT16: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UINT32: return &gjs_arg_member(return_value); case GI_TYPE_TAG_BOOLEAN: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UNICHAR: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INT64: return &gjs_arg_member(return_value); case GI_TYPE_TAG_UINT64: return &gjs_arg_member(return_value); case GI_TYPE_TAG_FLOAT: return &gjs_arg_member(return_value); case GI_TYPE_TAG_DOUBLE: return &gjs_arg_member(return_value); case GI_TYPE_TAG_INTERFACE: { if (!return_type) return nullptr; GjsAutoBaseInfo info = g_type_info_get_interface(return_type); switch (info.type()) { case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: return &gjs_arg_member( return_value); default: return &gjs_arg_member(return_value); } break; } default: return &gjs_arg_member(return_value); } } // This function can be called in two different ways. You can either use it to // create JavaScript objects by calling it without @r_value, or you can decide // to keep the return values in GIArgument format by providing a @r_value // argument. bool Function::invoke(JSContext* context, const JS::CallArgs& args, JS::HandleObject this_obj /* = nullptr */, GIArgument* r_value /* = nullptr */) { g_assert((args.isConstructing() || !this_obj) && "If not a constructor, then pass the 'this' object via CallArgs"); void* return_value_p; // will point inside the return GIArgument union GIFFIReturnValue return_value; unsigned ffi_argc = m_invoker.cif.nargs; GjsFunctionCallState state(context, m_info); if (state.gi_argc > Argument::MAX_ARGS) { gjs_throw(context, "Function %s has too many arguments", format_name().c_str()); return false; } // ffi_argc is the number of arguments that the underlying C function takes. // state.gi_argc is the number of arguments the GICallableInfo describes // (which does not include "this" or GError**). m_js_in_argc is the number // of arguments we expect the JS function to take (which does not include // PARAM_SKIPPED args). // args.length() is the number of arguments that were actually passed. if (args.length() > m_js_in_argc) { if (!JS::WarnUTF8(context, "Too many arguments to %s: expected %u, got %u", format_name().c_str(), m_js_in_argc, args.length())) return false; } else if (args.length() < m_js_in_argc) { args.reportMoreArgsNeeded(context, format_name().c_str(), m_js_in_argc, args.length()); return false; } // These arrays hold argument pointers. // - state.in_cvalue(): C values which are passed on input (in or inout) // - state.out_cvalue(): C values which are returned as arguments (out or // inout) // - state.inout_original_cvalue(): For the special case of (inout) args, // we need to keep track of the original values we passed into the // function, in case we need to free it. // - ffi_arg_pointers: For passing data to FFI, we need to create another // layer of indirection; this array is a pointer to an element in // state.in_cvalue() or state.out_cvalue(). // - return_value: The actual return value of the C function, i.e. not an // (out) param // // The 3 GIArgument arrays are indexed by the GI argument index. // ffi_arg_pointers, on the other hand, represents the actual C arguments, // in the way ffi expects them. auto ffi_arg_pointers = std::make_unique(ffi_argc); int gi_arg_pos = 0; // index into GIArgument array unsigned ffi_arg_pos = 0; // index into ffi_arg_pointers unsigned js_arg_pos = 0; // index into args JS::RootedObject obj(context, this_obj); if (!args.isConstructing() && !args.computeThis(context, &obj)) return false; std::string dynamicString("(unknown)"); if (state.is_method) { GIArgument* in_value = state.instance(); JS::RootedValue in_js_value(context, JS::ObjectValue(*obj)); if (!m_arguments.instance()->in(context, &state, in_value, in_js_value)) return false; ffi_arg_pointers[ffi_arg_pos] = in_value; ++ffi_arg_pos; // Callback lifetimes will be attached to the instance object if it is // a GObject or GInterface GType gtype = m_arguments.instance_type(); if (gtype != G_TYPE_NONE) { if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) state.instance_object = obj; if (g_type_is_a(gtype, G_TYPE_OBJECT)) { auto* o = ObjectBase::for_js(context, obj); dynamicString = o->format_name(); } } } dynamicString += '.'; dynamicString += format_name(); AutoProfilerLabel label(context, "", dynamicString.c_str()); g_assert(ffi_arg_pos + state.gi_argc < std::numeric_limits::max()); state.processed_c_args = ffi_arg_pos; for (gi_arg_pos = 0; gi_arg_pos < state.gi_argc; gi_arg_pos++, ffi_arg_pos++) { GIArgument* in_value = &state.in_cvalue(gi_arg_pos); Argument* gjs_arg = m_arguments.argument(gi_arg_pos); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' in, %d/%d GI args, %u/%u " "C args, %u/%u JS args", gjs_arg ? gjs_arg->arg_name() : "", gi_arg_pos, state.gi_argc, ffi_arg_pos, ffi_argc, js_arg_pos, args.length()); ffi_arg_pointers[ffi_arg_pos] = in_value; if (!gjs_arg) { GIArgInfo arg_info; g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info); gjs_throw(context, "Error invoking %s: impossible to determine what to pass " "to the '%s' argument. It may be that the function is " "unsupported, or there may be a bug in its annotations.", format_name().c_str(), g_base_info_get_name(&arg_info)); state.failed = true; break; } JS::RootedValue js_in_arg(context); if (js_arg_pos < args.length()) js_in_arg = args[js_arg_pos]; if (!gjs_arg->in(context, &state, in_value, js_in_arg)) { state.failed = true; break; } if (!gjs_arg->skip_in()) js_arg_pos++; state.processed_c_args++; } // This pointer needs to exist on the stack across the ffi_call() call GError** errorp = &state.local_error; /* Did argument conversion fail? In that case, skip invocation and jump to release * processing. */ if (state.failed) return finish_invoke(context, args, &state, r_value); if (state.can_throw_gerror) { g_assert(ffi_arg_pos < ffi_argc && "GError** argument number mismatch"); ffi_arg_pointers[ffi_arg_pos] = &errorp; ffi_arg_pos++; /* don't update state.processed_c_args as we deal with local_error * separately */ } g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc); g_assert_cmpuint(gi_arg_pos, ==, state.gi_argc); GITypeTag return_tag = m_arguments.return_tag(); GITypeInfo* return_type = m_arguments.return_type(); return_value_p = get_return_ffi_pointer_from_gi_argument( return_tag, return_type, &return_value); ffi_call(&m_invoker.cif, FFI_FN(m_invoker.native_address), return_value_p, ffi_arg_pointers.get()); /* Return value and out arguments are valid only if invocation doesn't * return error. In arguments need to be released always. */ if (!r_value) args.rval().setUndefined(); if (return_type) { gi_type_info_extract_ffi_return_value(return_type, &return_value, state.return_value()); } else if (return_tag != GI_TYPE_TAG_VOID) { g_assert(GI_TYPE_TAG_IS_BASIC(return_tag)); gi_type_tag_extract_ffi_return_value(return_tag, GI_INFO_TYPE_INVALID, &return_value, state.return_value()); } // Process out arguments and return values. This loop is skipped if we fail // the type conversion above, or if state.did_throw_gerror is true. js_arg_pos = 0; for (gi_arg_pos = -1; gi_arg_pos < state.gi_argc; gi_arg_pos++) { Argument* gjs_arg; GIArgument* out_value; if (gi_arg_pos == -1) { out_value = state.return_value(); gjs_arg = m_arguments.return_value(); } else { out_value = &state.out_cvalue(gi_arg_pos); gjs_arg = m_arguments.argument(gi_arg_pos); } gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' out, %d/%d GI args", gjs_arg ? gjs_arg->arg_name() : "", gi_arg_pos, state.gi_argc); JS::RootedValue js_out_arg(context); if (!r_value) { if (!gjs_arg && gi_arg_pos >= 0) { GIArgInfo arg_info; g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info); gjs_throw( context, "Error invoking %s.%s: impossible to determine what " "to pass to the out '%s' argument. It may be that the " "function is unsupported, or there may be a bug in " "its annotations.", m_info.ns(), m_info.name(), g_base_info_get_name(&arg_info)); state.failed = true; break; } if (gjs_arg && !gjs_arg->out(context, &state, out_value, &js_out_arg)) { state.failed = true; break; } } if (gjs_arg && !gjs_arg->skip_out()) { if (!r_value) { if (!state.return_values.append(js_out_arg)) { JS_ReportOutOfMemory(context); state.failed = true; break; } } js_arg_pos++; } } g_assert(state.failed || state.did_throw_gerror() || js_arg_pos == m_js_out_argc); // If we failed before calling the function, or if the function threw an // exception, then any GI_TRANSFER_EVERYTHING or GI_TRANSFER_CONTAINER // in-parameters were not transferred. Treat them as GI_TRANSFER_NOTHING so // that they are freed. return finish_invoke(context, args, &state, r_value); } bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args, GjsFunctionCallState* state, GIArgument* r_value /* = nullptr */) { // In this loop we use ffi_arg_pos just to ensure we don't release stuff // we haven't allocated yet, if we failed in type conversion above. // If we start from -1 (the return value), we need to process 1 more than // state.processed_c_args. // If we start from -2 (the instance parameter), we need to process 2 more unsigned ffi_arg_pos = state->first_arg_offset() - 1; unsigned ffi_arg_max = state->last_processed_index(); bool postinvoke_release_failed = false; for (int gi_arg_pos = -(state->first_arg_offset()); gi_arg_pos < state->gi_argc && ffi_arg_pos < ffi_arg_max; gi_arg_pos++, ffi_arg_pos++) { Argument* gjs_arg; GIArgument* in_value = nullptr; GIArgument* out_value = nullptr; if (gi_arg_pos == -2) { in_value = state->instance(); gjs_arg = m_arguments.instance(); } else if (gi_arg_pos == -1) { out_value = state->return_value(); gjs_arg = m_arguments.return_value(); } else { in_value = &state->in_cvalue(gi_arg_pos); out_value = &state->out_cvalue(gi_arg_pos); gjs_arg = m_arguments.argument(gi_arg_pos); } if (!gjs_arg) continue; gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Releasing argument '%s', %d/%d GI args, %u/%u C args", gjs_arg->arg_name(), gi_arg_pos, state->gi_argc, ffi_arg_pos, state->processed_c_args); // Only process in or inout arguments if we failed, the rest is garbage if (state->failed && gjs_arg->skip_in()) continue; // Save the return GIArgument if it was requested if (r_value && gi_arg_pos == -1) { *r_value = *out_value; continue; } if (!gjs_arg->release(cx, state, in_value, out_value)) { postinvoke_release_failed = true; // continue with the release even if we fail, to avoid leaks } } if (postinvoke_release_failed) state->failed = true; g_assert(ffi_arg_pos == state->last_processed_index()); if (!r_value && m_js_out_argc > 0 && state->call_completed()) { // If we have one return value or out arg, return that item on its // own, otherwise return a JavaScript array with [return value, // out arg 1, out arg 2, ...] if (m_js_out_argc == 1) { args.rval().set(state->return_values[0]); } else { JSObject* array = JS::NewArrayObject(cx, state->return_values); if (!array) { state->failed = true; } else { args.rval().setObject(*array); } } } if (!state->failed && state->did_throw_gerror()) { return gjs_throw_gerror(cx, state->local_error.release()); } else if (state->failed) { return false; } else { return true; } } bool Function::call(JSContext* context, unsigned js_argc, JS::Value* vp) { JS::CallArgs js_argv = JS::CallArgsFromVp(js_argc, vp); JS::RootedObject callee(context, &js_argv.callee()); Function* priv; if (!Function::for_js_typecheck(context, callee, &priv, &js_argv)) return false; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p", callee.get(), priv); g_assert(priv); return priv->invoke(context, js_argv); } Function::~Function() { g_function_invoker_destroy(&m_invoker); GJS_DEC_COUNTER(function); } void Function::finalize_impl(JS::GCContext*, Function* priv) { g_assert(priv); delete priv; } bool Function::get_length(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, this_obj); Function* priv; if (!Function::for_js_instance(cx, this_obj, &priv, &args)) return false; args.rval().setInt32(priv->m_js_in_argc); return true; } bool Function::get_name(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, rec, this_obj, Function, priv); if (priv->m_info.type() == GI_INFO_TYPE_FUNCTION) return gjs_string_from_utf8(cx, g_function_info_get_symbol(priv->m_info), rec.rval()); return gjs_string_from_utf8(cx, priv->format_name().c_str(), rec.rval()); } bool Function::to_string(JSContext* context, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(context, argc, vp, rec, this_obj, Function, priv); return priv->to_string_impl(context, rec.rval()); } bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) { int i, n_jsargs; int n_args = g_callable_info_get_n_args(m_info); n_jsargs = 0; std::string arg_names; for (i = 0; i < n_args; i++) { Argument* gjs_arg = m_arguments.argument(i); if (!gjs_arg || gjs_arg->skip_in()) continue; if (n_jsargs > 0) arg_names += ", "; n_jsargs++; arg_names += gjs_arg->arg_name(); } GjsAutoChar descr; if (m_info.type() == GI_INFO_TYPE_FUNCTION) { descr = g_strdup_printf( "%s(%s) {\n\t/* wrapper for native symbol %s() */\n}", format_name().c_str(), arg_names.c_str(), g_function_info_get_symbol(m_info)); } else { descr = g_strdup_printf("%s(%s) {\n\t/* wrapper for native symbol */\n}", format_name().c_str(), arg_names.c_str()); } return gjs_string_from_utf8(cx, descr, rval); } const JSClassOps Function::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve &Function::finalize, &Function::call, }; const JSPropertySpec Function::proto_props[] = { JS_PSG("length", &Function::get_length, JSPROP_PERMANENT), JS_PSG("name", &Function::get_name, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryFunction", JSPROP_READONLY), JS_PS_END}; /* The original Function.prototype.toString complains when given a GIRepository function as an argument */ // clang-format off const JSFunctionSpec Function::proto_funcs[] = { JS_FN("toString", &Function::to_string, 0, 0), JS_FS_END}; // clang-format on bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) { guint8 i; GjsAutoError error; if (m_info.type() == GI_INFO_TYPE_FUNCTION) { if (!g_function_info_prep_invoker(m_info, &m_invoker, &error)) return gjs_throw_gerror(context, error); } else if (m_info.type() == GI_INFO_TYPE_VFUNC) { void* addr = g_vfunc_info_get_address(m_info, gtype, &error); if (error) { if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND) return gjs_throw_gerror(context, error); gjs_throw(context, "Virtual function not implemented: %s", error->message); return false; } if (!g_function_invoker_new_for_address(addr, m_info, &m_invoker, &error)) return gjs_throw_gerror(context, error); } uint8_t n_args = g_callable_info_get_n_args(m_info); if (!m_arguments.initialize(context, m_info)) return false; m_arguments.build_instance(m_info); bool inc_counter; m_arguments.build_return(m_info, &inc_counter); if (inc_counter) m_js_out_argc++; for (i = 0; i < n_args; i++) { Argument* gjs_arg = m_arguments.argument(i); GIDirection direction; GIArgInfo arg_info; if (gjs_arg && (gjs_arg->skip_in() || gjs_arg->skip_out())) { continue; } g_callable_info_load_arg(m_info, i, &arg_info); direction = g_arg_info_get_direction(&arg_info); m_arguments.build_arg(i, direction, &arg_info, m_info, &inc_counter); if (inc_counter) { switch (direction) { case GI_DIRECTION_INOUT: m_js_out_argc++; [[fallthrough]]; case GI_DIRECTION_IN: m_js_in_argc++; break; case GI_DIRECTION_OUT: m_js_out_argc++; break; default: g_assert_not_reached(); } } } return true; } JSObject* Function::create(JSContext* context, GType gtype, GICallableInfo* info) { JS::RootedObject proto(context, Function::create_prototype(context)); if (!proto) return nullptr; JS::RootedObject function( context, JS_NewObjectWithGivenProto(context, &Function::klass, proto)); if (!function) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function"); return NULL; } auto* priv = new Function(info); Function::init_private(function, priv); debug_lifecycle(function, priv, "Constructor"); if (!priv->init(context, gtype)) return nullptr; return function; } } // namespace Gjs GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_function(JSContext *context, JS::HandleObject in_object, GType gtype, GICallableInfo *info) { GIInfoType info_type; std::string name; info_type = g_base_info_get_type((GIBaseInfo *)info); JS::RootedObject function(context, Gjs::Function::create(context, gtype, info)); if (!function) return NULL; if (info_type == GI_INFO_TYPE_FUNCTION) { name = g_base_info_get_name(info); } else if (info_type == GI_INFO_TYPE_VFUNC) { name = "vfunc_" + std::string(g_base_info_get_name(info)); } else { g_assert_not_reached (); } if (!JS_DefineProperty(context, in_object, name.c_str(), function, GJS_MODULE_PROP_FLAGS)) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function"); function = NULL; } return function; } bool gjs_invoke_constructor_from_c(JSContext* context, GIFunctionInfo* info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { return Gjs::Function::invoke_constructor_uncached(context, info, obj, args, rvalue); } cjs-128.1/gi/function.h0000664000175000017500000001347315116312211013626 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_FUNCTION_H_ #define GI_FUNCTION_H_ #include #include #include // for unique_ptr #include #include #include #include #include // for g_callable_info_get_closure_native_address #include #include #include #include #include #include #include "gi/closure.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" namespace JS { class CallArgs; } typedef enum { PARAM_NORMAL, PARAM_SKIPPED, PARAM_ARRAY, PARAM_CALLBACK, PARAM_UNKNOWN, } GjsParamType; using GjsAutoGClosure = GjsAutoPointer; struct GjsCallbackTrampoline : public Gjs::Closure { GJS_JSAPI_RETURN_CONVENTION static GjsCallbackTrampoline* create( JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc); ~GjsCallbackTrampoline(); void* closure() const { #if GI_CHECK_VERSION(1, 71, 0) return g_callable_info_get_closure_native_address(m_info, m_closure); #else return m_closure; #endif } ffi_closure* get_ffi_closure() const { return m_closure; } void mark_forever(); static void prepare_shutdown(); private: ffi_closure* create_closure(); GJS_JSAPI_RETURN_CONVENTION bool initialize(); GjsCallbackTrampoline(JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object, bool is_vfunc); void callback_closure(GIArgument** args, void* result); GJS_JSAPI_RETURN_CONVENTION bool callback_closure_inner(JSContext* cx, JS::HandleObject this_object, GObject* gobject, JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type, int n_args, int c_args_offset, void* result); void warn_about_illegal_js_callback(const char* when, const char* reason, bool dump_stack); static std::vector s_forever_closure_list; GjsAutoCallableInfo m_info; ffi_closure* m_closure = nullptr; std::unique_ptr m_param_types; ffi_cif m_cif; GIScopeType m_scope : 3; bool m_is_vfunc : 1; }; // Stack allocation only! class GjsFunctionCallState { GjsAutoCppPointer m_in_cvalues; GjsAutoCppPointer m_out_cvalues; GjsAutoCppPointer m_inout_original_cvalues; public: std::unordered_set ignore_release; JS::RootedObject instance_object; JS::RootedVector return_values; GjsAutoError local_error; GICallableInfo* info; uint8_t gi_argc = 0; uint8_t processed_c_args = 0; bool failed : 1; bool can_throw_gerror : 1; bool is_method : 1; GjsFunctionCallState(JSContext* cx, GICallableInfo* callable) : instance_object(cx), return_values(cx), info(callable), gi_argc(g_callable_info_get_n_args(callable)), failed(false), can_throw_gerror(g_callable_info_can_throw_gerror(callable)), is_method(g_callable_info_is_method(callable)) { int size = gi_argc + first_arg_offset(); m_in_cvalues = new GIArgument[size]; m_out_cvalues = new GIArgument[size]; m_inout_original_cvalues = new GIArgument[size]; } GjsFunctionCallState(const GjsFunctionCallState&) = delete; GjsFunctionCallState& operator=(const GjsFunctionCallState&) = delete; constexpr int first_arg_offset() const { return is_method ? 2 : 1; } // The list always contains the return value, and the arguments constexpr GIArgument* instance() { return is_method ? &m_in_cvalues[1] : nullptr; } constexpr GIArgument* return_value() { return &m_out_cvalues[0]; } constexpr GIArgument& in_cvalue(int index) const { return m_in_cvalues[index + first_arg_offset()]; } constexpr GIArgument& out_cvalue(int index) const { return m_out_cvalues[index + first_arg_offset()]; } constexpr GIArgument& inout_original_cvalue(int index) const { return m_inout_original_cvalues[index + first_arg_offset()]; } constexpr bool did_throw_gerror() const { return can_throw_gerror && local_error; } constexpr bool call_completed() { return !failed && !did_throw_gerror(); } constexpr unsigned last_processed_index() { return first_arg_offset() + processed_c_args; } [[nodiscard]] GjsAutoChar display_name() { GIBaseInfo* container = g_base_info_get_container(info); // !owned if (container) { return g_strdup_printf( "%s.%s.%s", g_base_info_get_namespace(container), g_base_info_get_name(container), g_base_info_get_name(info)); } return g_strdup_printf("%s.%s", g_base_info_get_namespace(info), g_base_info_get_name(info)); } }; GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_define_function(JSContext *context, JS::HandleObject in_object, GType gtype, GICallableInfo *info); GJS_JSAPI_RETURN_CONVENTION bool gjs_invoke_constructor_from_c(JSContext* cx, GIFunctionInfo* info, JS::HandleObject this_obj, const JS::CallArgs& args, GIArgument* rvalue); #endif // GI_FUNCTION_H_ cjs-128.1/gi/fundamental.cpp0000664000175000017500000004051215116312211014624 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Intel Corporation // SPDX-FileCopyrightText: 2008-2010 litl, LLC #include #include #include #include #include // for JS_ReportOutOfMemory #include // for WeakCache #include // for GetClass #include #include #include #include // for UniqueChars #include #include // for InformalValueTypeName, JS_NewObjectWithGivenP... #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/repo.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" namespace JS { class CallArgs; } FundamentalInstance::FundamentalInstance(FundamentalPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { GJS_INC_COUNTER(fundamental_instance); } /* * FundamentalInstance::associate_js_instance: * * Associates @gfundamental with @object so that @object can be retrieved in the * future if you have a pointer to @gfundamental. (Assuming @object has not been * garbage collected in the meantime.) */ bool FundamentalInstance::associate_js_instance(JSContext* cx, JSObject* object, void* gfundamental) { m_ptr = gfundamental; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (!gjs->fundamental_table().putNew(gfundamental, object)) { JS_ReportOutOfMemory(cx); return false; } debug_lifecycle(object, "associated JSObject with fundamental"); ref(); return true; } /**/ /* Find the first constructor */ [[nodiscard]] static GIFunctionInfo* find_fundamental_constructor( GIObjectInfo* info) { int i, n_methods; n_methods = g_object_info_get_n_methods(info); for (i = 0; i < n_methods; ++i) { GjsAutoFunctionInfo func_info; GIFunctionInfoFlags flags; func_info = g_object_info_get_method(info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0) return func_info.release(); } return nullptr; } /**/ bool FundamentalPrototype::resolve_interface(JSContext* cx, JS::HandleObject obj, bool* resolved, const char* name) { bool ret; GType *interfaces; guint n_interfaces; guint i; ret = true; interfaces = g_type_interfaces(gtype(), &n_interfaces); for (i = 0; i < n_interfaces; i++) { GjsAutoInterfaceInfo iface_info = g_irepository_find_by_gtype(nullptr, interfaces[i]); if (!iface_info) continue; GjsAutoFunctionInfo method_info = g_interface_info_find_method(iface_info, name); if (method_info && g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) { if (gjs_define_function(cx, obj, gtype(), method_info)) { *resolved = true; } else { ret = false; } } } g_free(interfaces); return ret; } // See GIWrapperBase::resolve(). bool FundamentalPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } /* We are the prototype, so look for methods and other class properties */ GjsAutoFunctionInfo method_info = g_object_info_find_method(info(), prop_name.get()); if (method_info) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { /* we do not define deprecated methods in the prototype */ if (g_base_info_is_deprecated(method_info)) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Ignoring definition of deprecated method %s in " "prototype %s.%s", method_info.name(), ns(), name()); *resolved = false; return true; } gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); if (!gjs_define_function(cx, obj, gtype(), method_info)) return false; *resolved = true; } } else { *resolved = false; } return resolve_interface(cx, obj, resolved, prop_name.get()); } /* * FundamentalInstance::invoke_constructor: * * Finds the type's static constructor method (the static method given by * FundamentalPrototype::constructor_info()) and invokes it with the given * arguments. */ bool FundamentalInstance::invoke_constructor(JSContext* context, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { GIFunctionInfo* constructor_info = get_prototype()->constructor_info(); if (!constructor_info) { gjs_throw(context, "Couldn't find a constructor for type %s.%s", ns(), name()); return false; } return gjs_invoke_constructor_from_c(context, constructor_info, obj, args, rvalue); } // See GIWrapperBase::constructor(). bool FundamentalInstance::constructor_impl(JSContext* cx, JS::HandleObject object, const JS::CallArgs& argv) { GIArgument ret_value; GITypeInfo return_info; if (!invoke_constructor(cx, object, argv, &ret_value) || !associate_js_instance(cx, object, gjs_arg_get(&ret_value))) return false; GICallableInfo* constructor_info = get_prototype()->constructor_info(); g_callable_info_load_return_type(constructor_info, &return_info); return gjs_gi_argument_release( cx, g_callable_info_get_caller_owns(constructor_info), &return_info, &ret_value); } FundamentalInstance::~FundamentalInstance(void) { if (m_ptr) { unref(); m_ptr = nullptr; } GJS_DEC_COUNTER(fundamental_instance); } FundamentalPrototype::FundamentalPrototype(GIObjectInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_ref_function(g_object_info_get_ref_function_pointer(info)), m_unref_function(g_object_info_get_unref_function_pointer(info)), m_get_value_function(g_object_info_get_get_value_function_pointer(info)), m_set_value_function(g_object_info_get_set_value_function_pointer(info)), m_constructor_info(find_fundamental_constructor(info)) { GJS_INC_COUNTER(fundamental_prototype); } FundamentalPrototype::~FundamentalPrototype(void) { GJS_DEC_COUNTER(fundamental_prototype); } // clang-format off const struct JSClassOps FundamentalBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &FundamentalBase::resolve, nullptr, // mayResolve &FundamentalBase::finalize, nullptr, // call nullptr, // construct &FundamentalBase::trace }; const struct JSClass FundamentalBase::klass = { "GFundamental_Object", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, &FundamentalBase::class_ops }; // clang-format on GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_lookup_fundamental_prototype(JSContext *context, GIObjectInfo *info, GType gtype) { JS::RootedObject in_object(context); const char *constructor_name; if (info) { in_object = gjs_lookup_namespace_object(context, (GIBaseInfo*) info); constructor_name = g_base_info_get_name((GIBaseInfo*) info); } else { in_object = gjs_lookup_private_namespace(context); constructor_name = g_type_name(gtype); } if (G_UNLIKELY (!in_object)) return nullptr; bool found; if (!JS_HasProperty(context, in_object, constructor_name, &found)) return nullptr; JS::RootedValue value(context); if (found && !JS_GetProperty(context, in_object, constructor_name, &value)) return nullptr; JS::RootedObject constructor(context); if (value.isUndefined()) { /* In case we're looking for a private type, and we don't find it, we need to define it first. */ if (!FundamentalPrototype::define_class(context, in_object, info, &constructor)) return nullptr; } else { if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Fundamental constructor was not an object, it was a %s", JS::InformalValueTypeName(value)); return nullptr; } constructor = &value.toObject(); } g_assert(constructor); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject prototype(context); if (!gjs_object_require_property(context, constructor, "constructor object", atoms.prototype(), &prototype)) return nullptr; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_fundamental_prototype_from_gtype(JSContext *context, GType gtype) { GjsAutoObjectInfo info; /* A given gtype might not have any definition in the introspection * data. If that's the case, try to look for a definition of any of the * parent type. */ while (gtype != G_TYPE_INVALID && !(info = g_irepository_find_by_gtype(nullptr, gtype))) gtype = g_type_parent(gtype); return gjs_lookup_fundamental_prototype(context, info, gtype); } // Overrides GIWrapperPrototype::get_parent_proto(). bool FundamentalPrototype::get_parent_proto( JSContext* cx, JS::MutableHandleObject proto) const { GType parent_gtype = g_type_parent(gtype()); if (parent_gtype != G_TYPE_INVALID) { proto.set( gjs_lookup_fundamental_prototype_from_gtype(cx, parent_gtype)); if (!proto) return false; } return true; } // Overrides GIWrapperPrototype::constructor_nargs(). unsigned FundamentalPrototype::constructor_nargs(void) const { if (m_constructor_info) return g_callable_info_get_n_args(m_constructor_info); return 0; } /* * FundamentalPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the fundamental class. * @constructor: Return location for the constructor object. * * Define a fundamental class constructor and prototype, including all the * necessary methods and properties. Provides the constructor object as an out * parameter, for convenience elsewhere. */ bool FundamentalPrototype::define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, JS::MutableHandleObject constructor) { GType gtype; gtype = g_registered_type_info_get_g_type (info); JS::RootedObject prototype(cx); FundamentalPrototype* priv = FundamentalPrototype::create_class( cx, in_object, info, gtype, constructor, &prototype); if (!priv) return false; if (g_object_info_get_n_fields(info) > 0) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Fundamental type '%s.%s' apparently has accessible fields. " "Gjs has no support for this yet, ignoring these.", priv->ns(), priv->name()); } return true; } /* * FundamentalInstance::object_for_c_ptr: * * Given a pointer to a C fundamental object, returns a JS object. This JS * object may have been cached, or it may be newly created. */ JSObject* FundamentalInstance::object_for_c_ptr(JSContext* context, void* gfundamental) { if (!gfundamental) { gjs_throw(context, "Cannot get JSObject for null fundamental pointer"); return nullptr; } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); auto p = gjs->fundamental_table().lookup(gfundamental); if (p) return p->value(); gjs_debug_marshal(GJS_DEBUG_GFUNDAMENTAL, "Wrapping fundamental %p with JSObject", gfundamental); JS::RootedObject proto(context, gjs_lookup_fundamental_prototype_from_gtype(context, G_TYPE_FROM_INSTANCE(gfundamental))); if (!proto) return nullptr; JS::RootedObject object(context, JS_NewObjectWithGivenProto( context, JS::GetClass(proto), proto)); if (!object) return nullptr; auto* priv = FundamentalInstance::new_for_js_object(context, object); if (!priv->associate_js_instance(context, object, gfundamental)) return nullptr; return object; } /* * FundamentalPrototype::for_gtype: * * Returns the FundamentalPrototype instance associated with the given GType. * Use this if you don't have the prototype object. */ FundamentalPrototype* FundamentalPrototype::for_gtype(JSContext* cx, GType gtype) { JS::RootedObject proto( cx, gjs_lookup_fundamental_prototype_from_gtype(cx, gtype)); if (!proto) return nullptr; return FundamentalPrototype::for_js(cx, proto); } bool FundamentalInstance::object_for_gvalue( JSContext* cx, const GValue* value, GType gtype, JS::MutableHandleObject object_out) { auto* proto_priv = FundamentalPrototype::for_gtype(cx, gtype); void* fobj = nullptr; if (!proto_priv->call_get_value_function(value, &fobj)) { if (!G_VALUE_HOLDS(value, gtype) || !g_value_fits_pointer(value)) { gjs_throw(cx, "Failed to convert GValue of type %s to a fundamental %s " "instance", G_VALUE_TYPE_NAME(value), g_type_name(gtype)); return false; } fobj = g_value_peek_pointer(value); } if (!fobj) { object_out.set(nullptr); return true; } object_out.set(FundamentalInstance::object_for_c_ptr(cx, fobj)); return object_out.get() != nullptr; } bool FundamentalBase::to_gvalue(JSContext* cx, JS::HandleObject obj, GValue* gvalue) { FundamentalBase* priv; if (!for_js_typecheck(cx, obj, &priv) || !priv->check_is_instance(cx, "convert to GValue")) return false; auto* instance = priv->to_instance(); if (!instance->set_value(gvalue)) { if (g_value_type_compatible(instance->gtype(), G_VALUE_TYPE(gvalue))) { g_value_set_instance(gvalue, instance->m_ptr); return true; } else if (g_value_type_transformable(instance->gtype(), G_VALUE_TYPE(gvalue))) { Gjs::AutoGValue instance_value; g_value_init(&instance_value, instance->gtype()); g_value_set_instance(&instance_value, instance->m_ptr); if (g_value_transform(&instance_value, gvalue)) return true; } gjs_throw(cx, "Fundamental object of type %s does not support conversion " "to a GValue of type %s", instance->type_name(), G_VALUE_TYPE_NAME(gvalue)); return false; } return true; } void* FundamentalInstance::copy_ptr(JSContext* cx, GType gtype, void* gfundamental) { auto* priv = FundamentalPrototype::for_gtype(cx, gtype); return priv->call_ref_function(gfundamental); } cjs-128.1/gi/fundamental.h0000664000175000017500000001401315116312211014266 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Intel Corporation // SPDX-FileCopyrightText: 2008-2010 litl, LLC #ifndef GI_FUNDAMENTAL_H_ #define GI_FUNDAMENTAL_H_ #include #include #include #include #include "gi/cwrapper.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" // for GjsAutoCallableInfo #include "cjs/macros.h" #include "util/log.h" class FundamentalPrototype; class FundamentalInstance; namespace JS { class CallArgs; } /* To conserve memory, we have two different kinds of private data for JS * wrappers for fundamental types: FundamentalInstance, and * FundamentalPrototype. Both inherit from FundamentalBase for their common * functionality. For more information, see the notes in wrapperutils.h. */ class FundamentalBase : public GIWrapperBase { friend class CWrapperPointerOps; friend class GIWrapperBase; protected: explicit FundamentalBase(FundamentalPrototype* proto = nullptr) : GIWrapperBase(proto) {} static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GFUNDAMENTAL; static constexpr const char* DEBUG_TAG = "fundamental"; static const struct JSClassOps class_ops; static const struct JSClass klass; // Public API public: GJS_JSAPI_RETURN_CONVENTION static bool to_gvalue(JSContext* cx, JS::HandleObject obj, GValue* gvalue); }; class FundamentalPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; GIObjectInfoRefFunction m_ref_function; GIObjectInfoUnrefFunction m_unref_function; GIObjectInfoGetValueFunction m_get_value_function; GIObjectInfoSetValueFunction m_set_value_function; GjsAutoCallableInfo m_constructor_info; explicit FundamentalPrototype(GIObjectInfo* info, GType gtype); ~FundamentalPrototype(void); static constexpr InfoType::Tag info_type_tag = InfoType::Object; public: GJS_JSAPI_RETURN_CONVENTION static FundamentalPrototype* for_gtype(JSContext* cx, GType gtype); // Accessors [[nodiscard]] GICallableInfo* constructor_info() const { return m_constructor_info; } void* call_ref_function(void* ptr) const { if (!m_ref_function) return ptr; return m_ref_function(ptr); } void call_unref_function(void* ptr) const { if (m_unref_function) m_unref_function(ptr); } [[nodiscard]] bool call_get_value_function(const GValue* value, void** ptr_out) const { if (!m_get_value_function) return false; *ptr_out = m_get_value_function(value); return true; } bool call_set_value_function(GValue* value, void* object) const { if (m_set_value_function) { m_set_value_function(value, object); return true; } return false; } // Helper methods private: GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; [[nodiscard]] unsigned constructor_nargs() const; GJS_JSAPI_RETURN_CONVENTION bool resolve_interface(JSContext* cx, JS::HandleObject obj, bool* resolved, const char* name); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); // Public API public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, JS::MutableHandleObject constructor); }; class FundamentalInstance : public GIWrapperInstance { friend class FundamentalBase; // for set_value() friend class GIWrapperInstance; friend class GIWrapperBase; explicit FundamentalInstance(FundamentalPrototype* prototype, JS::HandleObject obj); ~FundamentalInstance(void); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool invoke_constructor(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue); void ref(void) { get_prototype()->call_ref_function(m_ptr); } void unref(void) { get_prototype()->call_unref_function(m_ptr); } [[nodiscard]] bool set_value(GValue* gvalue) const { return get_prototype()->call_set_value_function(gvalue, m_ptr); } GJS_JSAPI_RETURN_CONVENTION bool associate_js_instance(JSContext* cx, JSObject* object, void* gfundamental); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext* cx, void* gfundamental); GJS_JSAPI_RETURN_CONVENTION static bool object_for_gvalue(JSContext* cx, const GValue* gvalue, GType gtype, JS::MutableHandleObject object_out); static void* copy_ptr(JSContext* cx, GType gtype, void* gfundamental); }; #endif // GI_FUNDAMENTAL_H_ cjs-128.1/gi/gerror.cpp0000664000175000017500000004556515116312211013643 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include #include #include #include #include #include #include #include // for JSPROP_ENUMERATE #include #include #include // for BuildStackString, CaptureCurrentStack #include #include // for UniqueChars #include #include #include // for InformalValueTypeName, JS_GetClassObject #include // for JSProtoKey, JSProto_Error, JSProt... #include "gi/arg-inl.h" #include "gi/boxed.h" #include "gi/enumeration.h" #include "gi/gerror.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/error-types.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" ErrorPrototype::ErrorPrototype(GIEnumInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_domain(g_quark_from_string(g_enum_info_get_error_domain(info))) { GJS_INC_COUNTER(gerror_prototype); } ErrorPrototype::~ErrorPrototype(void) { GJS_DEC_COUNTER(gerror_prototype); } ErrorInstance::ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { GJS_INC_COUNTER(gerror_instance); } ErrorInstance::~ErrorInstance(void) { GJS_DEC_COUNTER(gerror_instance); } /* * ErrorBase::domain: * * Fetches ErrorPrototype::domain() for instances as well as prototypes. */ GQuark ErrorBase::domain(void) const { return get_prototype()->domain(); } // See GIWrapperBase::constructor(). bool ErrorInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& argv) { if (argv.length() != 1 || !argv[0].isObject()) { gjs_throw(context, "Invalid parameters passed to GError constructor, expected one object"); return false; } JS::RootedObject params_obj(context, &argv[0].toObject()); JS::UniqueChars message; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, params_obj, "GError constructor", atoms.message(), &message)) return false; int32_t code; if (!gjs_object_require_property(context, params_obj, "GError constructor", atoms.code(), &code)) return false; m_ptr = g_error_new_literal(domain(), code, message.get()); /* We assume this error will be thrown in the same line as the constructor */ return gjs_define_error_properties(context, object); } /* * ErrorBase::get_domain: * * JSNative property getter for `domain`. This property works on prototypes as * well as instances. */ bool ErrorBase::get_domain(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); args.rval().setInt32(priv->domain()); return true; } // JSNative property getter for `message`. bool ErrorBase::get_message(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); if (!priv->check_is_instance(cx, "get a field")) return false; return gjs_string_from_utf8(cx, priv->to_instance()->message(), args.rval()); } // JSNative property getter for `code`. bool ErrorBase::get_code(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv); if (!priv->check_is_instance(cx, "get a field")) return false; args.rval().setInt32(priv->to_instance()->code()); return true; } // JSNative implementation of `toString()`. bool ErrorBase::to_string(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, self); GjsAutoChar descr; // An error created via `new GLib.Error` will have a Boxed* private pointer, // not an Error*, so we can't call regular to_string() on it. if (BoxedBase::typecheck(context, self, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) { auto* gerror = BoxedBase::to_c_ptr(context, self); if (!gerror) return false; descr = g_strdup_printf("GLib.Error %s: %s", g_quark_to_string(gerror->domain), gerror->message); return gjs_string_from_utf8(context, descr, rec.rval()); } ErrorBase* priv; if (!for_js_typecheck(context, self, &priv, &rec)) return false; /* We follow the same pattern as standard JS errors, at the expense of hiding some useful information */ if (priv->is_prototype()) { descr = g_strdup_printf("%s.%s", priv->ns(), priv->name()); } else { descr = g_strdup_printf("%s.%s: %s", priv->ns(), priv->name(), priv->to_instance()->message()); } return gjs_string_from_utf8(context, descr, rec.rval()); } // JSNative implementation of `valueOf()`. bool ErrorBase::value_of(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, self); JS::RootedObject prototype(context); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, self, "constructor", atoms.prototype(), &prototype)) { /* This error message will be more informative */ JS_ClearPendingException(context); gjs_throw(context, "GLib.Error.valueOf() called on something that is not" " a constructor"); return false; } ErrorBase* priv; if (!for_js_typecheck(context, prototype, &priv, &rec)) return false; rec.rval().setInt32(priv->domain()); return true; } // clang-format off const struct JSClassOps ErrorBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve &ErrorBase::finalize, }; const struct JSClass ErrorBase::klass = { "GLib_Error", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &ErrorBase::class_ops }; /* We need to shadow all fields of GError, to prevent calling the getter from GBoxed (which would trash memory accessing the instance private data) */ JSPropertySpec ErrorBase::proto_properties[] = { JS_PSG("domain", &ErrorBase::get_domain, GJS_MODULE_PROP_FLAGS), JS_PSG("code", &ErrorBase::get_code, GJS_MODULE_PROP_FLAGS), JS_PSG("message", &ErrorBase::get_message, GJS_MODULE_PROP_FLAGS), JS_PS_END }; JSFunctionSpec ErrorBase::static_methods[] = { JS_FN("valueOf", &ErrorBase::value_of, 0, GJS_MODULE_PROP_FLAGS), JS_FS_END }; // clang-format on // Overrides GIWrapperPrototype::get_parent_proto(). bool ErrorPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const { g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), nullptr); GjsAutoStructInfo glib_error_info = g_irepository_find_by_name(nullptr, "GLib", "Error"); proto.set(gjs_lookup_generic_prototype(cx, glib_error_info)); return !!proto; } bool ErrorPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIEnumInfo* info) { JS::RootedObject prototype(context), constructor(context); if (!ErrorPrototype::create_class(context, in_object, info, G_TYPE_ERROR, &constructor, &prototype)) return false; // Define a toString() on the prototype, as it does not exist on the // prototype of GLib.Error; and create_class() will not define it since we // supply a parent in get_parent_proto(). const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return JS_DefineFunctionById(context, prototype, atoms.to_string(), &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS) && gjs_define_enum_values(context, constructor, info); } [[nodiscard]] static GIEnumInfo* find_error_domain_info(GQuark domain) { GIEnumInfo *info; /* first an attempt without loading extra libraries */ info = g_irepository_find_by_error_domain(nullptr, domain); if (info) return info; /* load standard stuff */ g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0), nullptr); g_irepository_require(nullptr, "GObject", "2.0", GIRepositoryLoadFlags(0), nullptr); g_irepository_require(nullptr, "Gio", "2.0", GIRepositoryLoadFlags(0), nullptr); info = g_irepository_find_by_error_domain(nullptr, domain); if (info) return info; /* last attempt: load GIRepository (for invoke errors, rarely needed) */ g_irepository_require(nullptr, "GIRepository", "2.0", GIRepositoryLoadFlags(0), nullptr); info = g_irepository_find_by_error_domain(nullptr, domain); return info; } /* define properties that JS Error() expose, such as fileName, lineNumber and stack */ GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj) { JS::RootedObject frame(cx); JS::RootedString stack(cx); JS::RootedString source(cx); uint32_t line; JS::TaggedColumnNumberOneOrigin tagged_column; if (!JS::CaptureCurrentStack(cx, &frame) || !JS::BuildStackString(cx, nullptr, frame, &stack)) return false; auto ok = JS::SavedFrameResult::Ok; if (JS::GetSavedFrameSource(cx, nullptr, frame, &source) != ok || JS::GetSavedFrameLine(cx, nullptr, frame, &line) != ok || JS::GetSavedFrameColumn(cx, nullptr, frame, &tagged_column) != ok) { gjs_throw(cx, "Error getting saved frame information"); return false; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefinePropertyById(cx, obj, atoms.stack(), stack, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.file_name(), source, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.line_number(), line, JSPROP_ENUMERATE) && JS_DefinePropertyById(cx, obj, atoms.column_number(), tagged_column.oneOriginValue(), JSPROP_ENUMERATE); } [[nodiscard]] static JSProtoKey proto_key_from_error_enum(int val) { switch (val) { case GJS_JS_ERROR_EVAL_ERROR: return JSProto_EvalError; case GJS_JS_ERROR_INTERNAL_ERROR: return JSProto_InternalError; case GJS_JS_ERROR_RANGE_ERROR: return JSProto_RangeError; case GJS_JS_ERROR_REFERENCE_ERROR: return JSProto_ReferenceError; case GJS_JS_ERROR_SYNTAX_ERROR: return JSProto_SyntaxError; case GJS_JS_ERROR_TYPE_ERROR: return JSProto_TypeError; case GJS_JS_ERROR_URI_ERROR: return JSProto_URIError; case GJS_JS_ERROR_ERROR: default: return JSProto_Error; } } GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_error_from_js_gerror(JSContext *cx, GError *gerror) { JS::RootedValueArray<1> error_args(cx); if (!gjs_string_from_utf8(cx, gerror->message, error_args[0])) return nullptr; JSProtoKey error_kind = proto_key_from_error_enum(gerror->code); JS::RootedObject error_constructor(cx); if (!JS_GetClassObject(cx, error_kind, &error_constructor)) return nullptr; JS::RootedValue v_error_constructor(cx, JS::ObjectValue(*error_constructor)); JS::RootedObject error(cx); if (!JS::Construct(cx, v_error_constructor, error_args, &error)) return nullptr; return error; } JSObject* ErrorInstance::object_for_c_ptr(JSContext* context, GError* gerror) { GIEnumInfo *info; if (!gerror) return nullptr; if (gerror->domain == GJS_JS_ERROR) return gjs_error_from_js_gerror(context, gerror); info = find_error_domain_info(gerror->domain); if (!info) { /* We don't have error domain metadata */ /* Marshal the error as a plain GError */ GjsAutoBaseInfo glib_boxed = g_irepository_find_by_name(nullptr, "GLib", "Error"); return BoxedInstance::new_for_c_struct(context, glib_boxed, gerror); } gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s with JSObject", g_base_info_get_name((GIBaseInfo *)info)); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype(context, info)); if (!obj) return nullptr; ErrorInstance* priv = ErrorInstance::new_for_js_object(context, obj); priv->copy_gerror(gerror); return obj; } GError* ErrorBase::to_c_ptr(JSContext* cx, JS::HandleObject obj) { /* If this is a plain GBoxed (i.e. a GError without metadata), delegate marshalling. */ if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) return BoxedBase::to_c_ptr(cx, obj); return GIWrapperBase::to_c_ptr(cx, obj); } bool ErrorBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!ErrorBase::typecheck(cx, obj)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, ErrorBase::to_c_ptr(cx, obj)); if (!gjs_arg_get(arg)) return false; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, ErrorInstance::copy_ptr(cx, G_TYPE_ERROR, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Overrides GIWrapperBase::typecheck() bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj) { if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, GjsTypecheckNoThrow())) return true; return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR); } bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow no_throw) { if (BoxedBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw)) return true; return GIWrapperBase::typecheck(cx, obj, nullptr, G_TYPE_ERROR, no_throw); } GJS_JSAPI_RETURN_CONVENTION static GError* gerror_from_error_impl(JSContext* cx, JS::HandleObject obj) { if (ErrorBase::typecheck(cx, obj, GjsTypecheckNoThrow())) { /* This is already a GError, just copy it */ GError* inner = ErrorBase::to_c_ptr(cx, obj); if (!inner) return nullptr; return g_error_copy(inner); } /* Try to make something useful from the error name and message (in case this is a JS error) */ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v_name(cx); if (!JS_GetPropertyById(cx, obj, atoms.name(), &v_name)) return nullptr; JS::RootedValue v_message(cx); if (!JS_GetPropertyById(cx, obj, atoms.message(), &v_message)) return nullptr; if (!v_name.isString() || !v_message.isString()) { return g_error_new_literal( GJS_JS_ERROR, GJS_JS_ERROR_ERROR, "Object thrown with unexpected name or message property"); } JS::UniqueChars name = gjs_string_to_utf8(cx, v_name); if (!name) return nullptr; JS::UniqueChars message = gjs_string_to_utf8(cx, v_message); if (!message) return nullptr; GjsAutoTypeClass klass(GJS_TYPE_JS_ERROR); const GEnumValue *value = g_enum_get_value_by_name(klass, name.get()); int code; if (value) code = value->value; else code = GJS_JS_ERROR_ERROR; return g_error_new_literal(GJS_JS_ERROR, code, message.get()); } /* * gjs_gerror_make_from_thrown_value: * * Attempts to convert a JavaScript thrown value (pending on @cx) into a * #GError. This function is infallible and will always return a #GError with * some message, even if the exception value couldn't be converted. * * Clears the pending exception on @cx. * * Returns: (transfer full): a new #GError */ GError* gjs_gerror_make_from_thrown_value(JSContext* cx) { g_assert(JS_IsExceptionPending(cx) && "Should be called when an exception is pending"); JS::RootedValue exc(cx); JS_GetPendingException(cx, &exc); JS_ClearPendingException(cx); // don't log if (!exc.isObject()) { return g_error_new(GJS_JS_ERROR, GJS_JS_ERROR_ERROR, "Non-exception %s value %s thrown", JS::InformalValueTypeName(exc), gjs_debug_value(exc).c_str()); } JS::RootedObject obj(cx, &exc.toObject()); GError* retval = gerror_from_error_impl(cx, obj); if (retval) return retval; // Make a GError with an InternalError even if it wasn't possible to convert // the exception into one gjs_log_exception(cx); // log the inner exception return g_error_new_literal(GJS_JS_ERROR, GJS_JS_ERROR_INTERNAL_ERROR, "Failed to convert JS thrown value into GError"); } /* * gjs_throw_gerror: * * Converts a GError into a JavaScript exception, and frees the GError. * Differently from gjs_throw(), it will overwrite an existing exception, as it * is used to report errors from C functions. * * Returns: false, for convenience in returning from the calling function. */ bool gjs_throw_gerror(JSContext* cx, GjsAutoError const& error) { // return false even if the GError is null, as presumably something failed // in the calling code, and the caller expects to throw. g_return_val_if_fail(error, false); JS::RootedObject err_obj(cx, ErrorInstance::object_for_c_ptr(cx, error)); if (!err_obj || !gjs_define_error_properties(cx, err_obj)) return false; JS::RootedValue err(cx, JS::ObjectValue(*err_obj)); JS_SetPendingException(cx, err); return false; } cjs-128.1/gi/gerror.h0000664000175000017500000001250215116312211013271 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_GERROR_H_ #define GI_GERROR_H_ #include #include #include #include #include #include #include "gi/cwrapper.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" // for GjsAutoPointer operators #include "cjs/macros.h" #include "util/log.h" class ErrorPrototype; class ErrorInstance; namespace JS { class CallArgs; } /* To conserve memory, we have two different kinds of private data for GError * JS wrappers: ErrorInstance, and ErrorPrototype. Both inherit from ErrorBase * for their common functionality. For more information, see the notes in * wrapperutils.h. * * ErrorPrototype, unlike the other GIWrapperPrototype subclasses, represents a * single error domain instead of a single GType. All Errors have a GType of * G_TYPE_ERROR. * * Note that in some situations GError structs can show up as BoxedInstance * instead of ErrorInstance. We have some special cases in this code to deal * with that. */ class ErrorBase : public GIWrapperBase { friend class CWrapperPointerOps; friend class GIWrapperBase; protected: explicit ErrorBase(ErrorPrototype* proto = nullptr) : GIWrapperBase(proto) {} static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GERROR; static constexpr const char* DEBUG_TAG = "gerror"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSPropertySpec proto_properties[]; static JSFunctionSpec static_methods[]; // Accessors public: [[nodiscard]] GQuark domain(void) const; // Property getters protected: GJS_JSAPI_RETURN_CONVENTION static bool get_domain(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool get_message(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool get_code(JSContext* cx, unsigned argc, JS::Value* vp); // JS methods GJS_JSAPI_RETURN_CONVENTION static bool value_of(JSContext* cx, unsigned argc, JS::Value* vp); public: GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp); // Helper methods GJS_JSAPI_RETURN_CONVENTION static GError* to_c_ptr(JSContext* cx, JS::HandleObject obj); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership); GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject obj); [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow); }; class ErrorPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; GQuark m_domain; static constexpr InfoType::Tag info_type_tag = InfoType::Enum; explicit ErrorPrototype(GIEnumInfo* info, GType gtype); ~ErrorPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; public: [[nodiscard]] GQuark domain(void) const { return m_domain; } GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIEnumInfo* info); }; class ErrorInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; explicit ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj); ~ErrorInstance(void); public: void copy_gerror(GError* other) { m_ptr = g_error_copy(other); } GJS_JSAPI_RETURN_CONVENTION static GError* copy_ptr(JSContext*, GType, void* ptr) { return g_error_copy(static_cast(ptr)); } // Accessors [[nodiscard]] const char* message(void) const { return m_ptr->message; } [[nodiscard]] int code(void) const { return m_ptr->code; } // JS constructor private: GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); // Public API public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext* cx, GError* gerror); }; GJS_JSAPI_RETURN_CONVENTION GError* gjs_gerror_make_from_thrown_value(JSContext* cx); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj); bool gjs_throw_gerror(JSContext* cx, GjsAutoError const&); #endif // GI_GERROR_H_ cjs-128.1/gi/gobject.cpp0000664000175000017500000002655415116312211013755 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include // for move, pair #include #include #include #include #include // for JSPROP_READONLY #include #include #include #include #include #include // for JS_NewPlainObject #include #include "gi/gobject.h" #include "gi/object.h" #include "gi/value.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" static std::unordered_map class_init_properties; [[nodiscard]] static JSContext* current_js_context() { GjsContext* gjs = gjs_context_get_current(); return static_cast(gjs_context_get_native_context(gjs)); } void push_class_init_properties(GType gtype, AutoParamArray* params) { class_init_properties[gtype] = std::move(*params); } bool pop_class_init_properties(GType gtype, AutoParamArray* params_out) { auto found = class_init_properties.find(gtype); if (found == class_init_properties.end()) return false; *params_out = std::move(found->second); class_init_properties.erase(found); return true; } GJS_JSAPI_RETURN_CONVENTION static bool jsobj_set_gproperty(JSContext* cx, JS::HandleObject object, const GValue* value, GParamSpec* pspec) { JS::RootedValue jsvalue(cx); if (!gjs_value_from_g_value(cx, &jsvalue, value)) return false; GjsAutoChar underscore_name = gjs_hyphen_to_underscore(pspec->name); if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) { unsigned flags = GJS_MODULE_PROP_FLAGS | JSPROP_READONLY; GjsAutoChar camel_name = gjs_hyphen_to_camel(pspec->name); if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) { JS::Rooted> jsprop(cx); JS::RootedObject holder(cx); JS::RootedObject getter(cx); // Ensure to call any associated setter method if (!g_str_equal(underscore_name.get(), pspec->name)) { if (!JS_GetPropertyDescriptor(cx, object, underscore_name, &jsprop, &holder)) { return false; } if (jsprop.isSome() && jsprop->setter() && !JS_SetProperty(cx, object, underscore_name, jsvalue)) { return false; } if (jsprop.isSome() && jsprop->getter()) getter.set(jsprop->getter()); } if (!g_str_equal(camel_name.get(), pspec->name)) { if (!JS_GetPropertyDescriptor(cx, object, camel_name, &jsprop, &holder)) { return false; } if (jsprop.isSome() && jsprop.value().setter() && !JS_SetProperty(cx, object, camel_name, jsvalue)) { return false; } if (!getter && jsprop.isSome() && jsprop->getter()) getter.set(jsprop->getter()); } if (!JS_GetPropertyDescriptor(cx, object, pspec->name, &jsprop, &holder)) return false; if (jsprop.isSome() && jsprop.value().setter() && !JS_SetProperty(cx, object, pspec->name, jsvalue)) return false; if (!getter && jsprop.isSome() && jsprop->getter()) getter.set(jsprop->getter()); // If a getter is found, redefine the property with that getter // and no setter. if (getter) return JS_DefineProperty(cx, object, underscore_name, getter, nullptr, GJS_MODULE_PROP_FLAGS) && JS_DefineProperty(cx, object, camel_name, getter, nullptr, GJS_MODULE_PROP_FLAGS) && JS_DefineProperty(cx, object, pspec->name, getter, nullptr, GJS_MODULE_PROP_FLAGS); } return JS_DefineProperty(cx, object, underscore_name, jsvalue, flags) && JS_DefineProperty(cx, object, camel_name, jsvalue, flags) && JS_DefineProperty(cx, object, pspec->name, jsvalue, flags); } return JS_SetProperty(cx, object, underscore_name, jsvalue); } static void gjs_object_base_init(void* klass) { auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass)); if (priv) priv->ref_vfuncs(); } static void gjs_object_base_finalize(void* klass) { auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass)); if (priv) priv->unref_vfuncs(); } static GObject* gjs_object_constructor( GType type, unsigned n_construct_properties, GObjectConstructParam* construct_properties) { JSContext* cx = current_js_context(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (!gjs->object_init_list().empty()) { GType parent_type = g_type_parent(type); /* The object is being constructed from JS: * Simply chain up to the first non-gjs constructor */ while (G_OBJECT_CLASS(g_type_class_peek(parent_type))->constructor == gjs_object_constructor) parent_type = g_type_parent(parent_type); return G_OBJECT_CLASS(g_type_class_peek(parent_type)) ->constructor(type, n_construct_properties, construct_properties); } /* The object is being constructed from native code (e.g. GtkBuilder): * Construct the JS object from the constructor, then use the GObject * that was associated in gjs_object_custom_init() */ Gjs::AutoMainRealm ar{gjs}; JS::RootedValue constructor{cx}; if (!gjs_lookup_object_constructor(cx, type, &constructor)) return nullptr; JS::RootedObject object(cx); if (n_construct_properties) { JS::RootedObject props_hash(cx, JS_NewPlainObject(cx)); for (unsigned i = 0; i < n_construct_properties; i++) if (!jsobj_set_gproperty(cx, props_hash, construct_properties[i].value, construct_properties[i].pspec)) return nullptr; JS::RootedValueArray<1> args(cx); args[0].set(JS::ObjectValue(*props_hash)); if (!JS::Construct(cx, constructor, args, &object)) return nullptr; } else if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(), &object)) { return nullptr; } auto* priv = ObjectBase::for_js_nocheck(object); /* Should have been set in init_impl() and pushed into object_init_list, * then popped from object_init_list in gjs_object_custom_init() */ g_assert(priv); /* We only hold a toggle ref at this point, add back a ref that the * native code can own. */ return G_OBJECT(g_object_ref(priv->to_instance()->ptr())); } static void gjs_object_set_gproperty(GObject* object, unsigned property_id [[maybe_unused]], const GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); if (!priv) { g_warning("Wrapper for GObject %p was disposed, cannot set property %s", object, g_param_spec_get_name(pspec)); return; } JSContext* cx = current_js_context(); JS::RootedObject js_obj(cx, priv->wrapper()); JSAutoRealm ar(cx, js_obj); if (!jsobj_set_gproperty(cx, js_obj, value, pspec)) gjs_log_exception_uncaught(cx); } static void gjs_object_get_gproperty(GObject* object, unsigned property_id [[maybe_unused]], GValue* value, GParamSpec* pspec) { auto* priv = ObjectInstance::for_gobject(object); if (!priv) { g_warning("Wrapper for GObject %p was disposed, cannot get property %s", object, g_param_spec_get_name(pspec)); return; } JSContext* cx = current_js_context(); JS::RootedObject js_obj(cx, priv->wrapper()); JS::RootedValue jsvalue(cx); JSAutoRealm ar(cx, js_obj); GjsAutoChar underscore_name = gjs_hyphen_to_underscore(pspec->name); if (!JS_GetProperty(cx, js_obj, underscore_name, &jsvalue)) { gjs_log_exception_uncaught(cx); return; } if (!gjs_value_to_g_value(cx, jsvalue, value)) gjs_log_exception(cx); } static void gjs_object_class_init(void* class_pointer, void*) { GObjectClass* klass = G_OBJECT_CLASS(class_pointer); GType gtype = G_OBJECT_CLASS_TYPE(klass); klass->constructor = gjs_object_constructor; klass->set_property = gjs_object_set_gproperty; klass->get_property = gjs_object_get_gproperty; AutoParamArray properties; if (!pop_class_init_properties(gtype, &properties)) return; unsigned i = 0; for (GjsAutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_class_install_property(klass, ++i, pspec); } } static void gjs_object_custom_init(GTypeInstance* instance, void* g_class [[maybe_unused]]) { JSContext* cx = current_js_context(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); if (gjs->object_init_list().empty()) return; JS::RootedObject object(cx, gjs->object_init_list().back()); auto* priv_base = ObjectBase::for_js_nocheck(object); g_assert(priv_base); // Should have been set in init_impl() ObjectInstance* priv = priv_base->to_instance(); if (priv_base->gtype() != G_TYPE_FROM_INSTANCE(instance)) { /* This is not the most derived instance_init function, do nothing. */ return; } gjs->object_init_list().popBack(); if (!priv->init_custom_class_from_gobject(cx, object, G_OBJECT(instance))) gjs_log_exception_uncaught(cx); } static void gjs_interface_init(void* g_iface, void*) { GType gtype = G_TYPE_FROM_INTERFACE(g_iface); AutoParamArray properties; if (!pop_class_init_properties(gtype, &properties)) return; for (GjsAutoParam& pspec : properties) { g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); g_object_interface_install_property(g_iface, pspec); } } constexpr GTypeInfo gjs_gobject_class_info = { 0, // class_size gjs_object_base_init, gjs_object_base_finalize, gjs_object_class_init, GClassFinalizeFunc(nullptr), nullptr, // class_data 0, // instance_size 0, // n_preallocs gjs_object_custom_init, }; constexpr GTypeInfo gjs_gobject_interface_info = { sizeof(GTypeInterface), // class_size GBaseInitFunc(nullptr), GBaseFinalizeFunc(nullptr), gjs_interface_init, GClassFinalizeFunc(nullptr), nullptr, // class_data 0, // instance_size 0, // n_preallocs nullptr, // instance_init }; cjs-128.1/gi/gobject.h0000664000175000017500000000122015116312211013401 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento #ifndef GI_GOBJECT_H_ #define GI_GOBJECT_H_ #include #include #include #include "cjs/jsapi-util.h" using AutoParamArray = std::vector; extern const GTypeInfo gjs_gobject_class_info; extern const GTypeInfo gjs_gobject_interface_info; void push_class_init_properties(GType gtype, AutoParamArray* params); bool pop_class_init_properties(GType gtype, AutoParamArray* params_out); #endif // GI_GOBJECT_H_ cjs-128.1/gi/gtype.cpp0000664000175000017500000001560515116312211013463 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #include #include #include #include #include #include // for WeakCache #include #include // for JSPROP_PERMANENT #include #include #include #include #include // for JS_NewObjectWithGivenProto #include #include "gi/cwrapper.h" #include "gi/gtype.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" /* * GTypeObj: * * Wrapper object used to represent a GType in JavaScript. * In C, GTypes are just a pointer-sized integer, but in JS they have a 'name' * property and a toString() method. * The integer is stuffed into CWrapper's pointer slot. */ class GTypeObj : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_gtype; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GTYPE; // JSClass operations // No private data is allocated, it's stuffed directly in the private field // of JSObject, so nothing to free static void finalize_impl(JS::GCContext*, void*) {} // Properties GJS_JSAPI_RETURN_CONVENTION static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); GType gtype = value(cx, obj, &args); if (gtype == 0) return false; return gjs_string_from_utf8(cx, g_type_name(gtype), args.rval()); } // Methods GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); GType gtype = value(cx, obj, &rec); if (gtype == 0) return false; GjsAutoChar strval = g_strdup_printf("[object GType for '%s']", g_type_name(gtype)); return gjs_string_from_utf8(cx, strval, rec.rval()); } // clang-format off static constexpr JSPropertySpec proto_props[] = { JS_PSG("name", >ypeObj::get_name, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryGType", JSPROP_READONLY), JS_PS_END}; static constexpr JSFunctionSpec proto_funcs[] = { JS_FN("toString", >ypeObj::to_string, 0, 0), JS_FS_END}; // clang-format on static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties GTypeObj::proto_funcs, GTypeObj::proto_props, nullptr, // finishInit js::ClassSpec::DontDefineConstructor}; static constexpr JSClass klass = { "GIRepositoryGType", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, >ypeObj::class_ops, >ypeObj::class_spec}; GJS_JSAPI_RETURN_CONVENTION static GType value(JSContext* cx, JS::HandleObject obj, JS::CallArgs* args) { void* data; if (!for_js_typecheck(cx, obj, &data, args)) return G_TYPE_NONE; return GPOINTER_TO_SIZE(data); } GJS_JSAPI_RETURN_CONVENTION static GType value(JSContext* cx, JS::HandleObject obj) { return GPOINTER_TO_SIZE(for_js(cx, obj)); } GJS_JSAPI_RETURN_CONVENTION static bool actual_gtype_recurse(JSContext* cx, const GjsAtoms& atoms, JS::HandleObject object, GType* gtype_out, int recurse) { GType gtype = value(cx, object); if (gtype > 0) { *gtype_out = gtype; return true; } JS::RootedValue v_gtype(cx); // OK, we don't have a GType wrapper object -- grab the "$gtype" // property on that and hope it's a GType wrapper object if (!JS_GetPropertyById(cx, object, atoms.gtype(), &v_gtype)) return false; if (!v_gtype.isObject()) { // OK, so we're not a class. But maybe we're an instance. Check for // "constructor" and recurse on that. if (!JS_GetPropertyById(cx, object, atoms.constructor(), &v_gtype)) return false; } if (recurse > 0 && v_gtype.isObject()) { JS::RootedObject gtype_obj(cx, &v_gtype.toObject()); return actual_gtype_recurse(cx, atoms, gtype_obj, gtype_out, recurse - 1); } *gtype_out = G_TYPE_INVALID; return true; } public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx, GType gtype) { g_assert(gtype != 0 && "Attempted to create wrapper object for invalid GType"); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); // We cannot use gtype_table().lookupForAdd() here, because in between // the lookup and the add, GCs may take place and mutate the hash table. // A GC may only remove an element, not add one, so it's still safe to // do this without locking. auto p = gjs->gtype_table().lookup(gtype); if (p.found()) return p->value(); JS::RootedObject proto(cx, GTypeObj::create_prototype(cx)); if (!proto) return nullptr; JS::RootedObject gtype_wrapper( cx, JS_NewObjectWithGivenProto(cx, >ypeObj::klass, proto)); if (!gtype_wrapper) return nullptr; GTypeObj::init_private(gtype_wrapper, GSIZE_TO_POINTER(gtype)); gjs->gtype_table().put(gtype, gtype_wrapper); return gtype_wrapper; } GJS_JSAPI_RETURN_CONVENTION static bool actual_gtype(JSContext* cx, JS::HandleObject object, GType* gtype_out) { g_assert(gtype_out && "Missing return location"); // 2 means: recurse at most three times (including this call). // The levels are calculated considering that, in the worst case we need // to go from instance to class, from class to GType object and from // GType object to GType value. const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return actual_gtype_recurse(cx, atoms, object, gtype_out, 2); } }; JSObject* gjs_gtype_create_gtype_wrapper(JSContext* context, GType gtype) { return GTypeObj::create(context, gtype); } bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object, GType* gtype_out) { return GTypeObj::actual_gtype(context, object, gtype_out); } cjs-128.1/gi/gtype.h0000664000175000017500000000125315116312211013122 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #ifndef GI_GTYPE_H_ #define GI_GTYPE_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_gtype_create_gtype_wrapper (JSContext *context, GType gtype); GJS_JSAPI_RETURN_CONVENTION bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object, GType* gtype_out); #endif // GI_GTYPE_H_ cjs-128.1/gi/interface.cpp0000664000175000017500000001345415116312211014273 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #include #include #include #include // for JS_ReportOutOfMemory #include // for MutableHandleIdVector #include // for PropertyKey, jsid #include #include // for UniqueChars #include "gi/function.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" InterfacePrototype::InterfacePrototype(GIInterfaceInfo* info, GType gtype) : GIWrapperPrototype(info, gtype), m_vtable( static_cast(g_type_default_interface_ref(gtype))) { GJS_INC_COUNTER(interface); } InterfacePrototype::~InterfacePrototype(void) { g_clear_pointer(&m_vtable, g_type_default_interface_unref); GJS_DEC_COUNTER(interface); } bool InterfacePrototype::new_enumerate_impl( JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { if (!info()) return true; int n_methods = g_interface_info_get_n_methods(info()); if (!properties.reserve(properties.length() + n_methods)) { JS_ReportOutOfMemory(cx); return false; } for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_interface_info_get_method(info(), i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) return false; properties.infallibleAppend(id); } } return true; } // See GIWrapperBase::resolve(). bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { /* If we have no GIRepository information then this interface was defined * from within GJS. In that case, it has no properties that need to be * resolved from within C code, as interfaces cannot inherit. */ if (!info()) { *resolved = false; return true; } JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } GjsAutoFunctionInfo method_info = g_interface_info_find_method(m_info, prop_name.get()); if (method_info) { if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { if (!gjs_define_function(context, obj, m_gtype, method_info)) return false; *resolved = true; } else { *resolved = false; } } else { *resolved = false; } return true; } /* * InterfaceBase::has_instance: * * JSNative implementation of `[Symbol.hasInstance]()`. This method is never * called directly, but instead is called indirectly by the JS engine as part of * an `instanceof` expression. */ bool InterfaceBase::has_instance(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, interface_constructor); JS::RootedObject interface_proto(cx); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, interface_constructor, "interface constructor", atoms.prototype(), &interface_proto)) return false; InterfaceBase* priv; if (!for_js_typecheck(cx, interface_proto, &priv)) return false; return priv->to_prototype()->has_instance_impl(cx, args); } // See InterfaceBase::has_instance(). bool InterfacePrototype::has_instance_impl(JSContext* cx, const JS::CallArgs& args) { // This method is never called directly, so no need for error messages. g_assert(args.length() == 1); if (!args[0].isObject()) { args.rval().setBoolean(false); return true; } JS::RootedObject instance(cx, &args[0].toObject()); bool isinstance = ObjectBase::typecheck(cx, instance, nullptr, m_gtype, GjsTypecheckNoThrow()); args.rval().setBoolean(isinstance); return true; } // clang-format off const struct JSClassOps InterfaceBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &InterfaceBase::new_enumerate, &InterfaceBase::resolve, nullptr, // mayResolve &InterfaceBase::finalize, }; const struct JSClass InterfaceBase::klass = { "GObject_Interface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &InterfaceBase::class_ops }; JSFunctionSpec InterfaceBase::static_methods[] = { JS_SYM_FN(hasInstance, &InterfaceBase::has_instance, 1, 0), JS_FS_END }; // clang-format on bool gjs_lookup_interface_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p) { JSObject *constructor; GjsAutoInterfaceInfo interface_info = gjs_lookup_gtype(nullptr, gtype); if (!interface_info) { gjs_throw(context, "Cannot expose non introspectable interface %s", g_type_name(gtype)); return false; } constructor = gjs_lookup_generic_constructor(context, interface_info); if (G_UNLIKELY(!constructor)) return false; value_p.setObject(*constructor); return true; } cjs-128.1/gi/interface.h0000664000175000017500000001050515116312211013732 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #ifndef GI_INTERFACE_H_ #define GI_INTERFACE_H_ #include #include #include #include #include #include #include #include #include "gi/cwrapper.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class InterfacePrototype; class InterfaceInstance; /* For more information on this Base/Prototype/Interface scheme, see the notes * in wrapperutils.h. * * What's unusual about this subclass is that InterfaceInstance should never * actually be instantiated. Interfaces can't be constructed, and * GIWrapperBase::constructor() is overridden to just throw an exception and not * create any JS wrapper object. * * We use the template classes from wrapperutils.h anyway, because there is * still a lot of common code. */ class InterfaceBase : public GIWrapperBase { friend class CWrapperPointerOps; friend class GIWrapperBase; protected: explicit InterfaceBase(InterfacePrototype* proto = nullptr) : GIWrapperBase(proto) {} static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GINTERFACE; static constexpr const char* DEBUG_TAG = "interface"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSFunctionSpec static_methods[]; // JSNative methods // Overrides GIWrapperBase::constructor(). GJS_JSAPI_RETURN_CONVENTION static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); gjs_throw_abstract_constructor_error(cx, args); return false; } GJS_JSAPI_RETURN_CONVENTION static bool has_instance(JSContext* cx, unsigned argc, JS::Value* vp); }; class InterfacePrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; friend class InterfaceBase; // for has_instance_impl // the GTypeInterface vtable wrapped by this JS object GTypeInterface* m_vtable; static constexpr InfoType::Tag info_type_tag = InfoType::Interface; explicit InterfacePrototype(GIInterfaceInfo* info, GType gtype); ~InterfacePrototype(void); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable); // JS methods GJS_JSAPI_RETURN_CONVENTION bool has_instance_impl(JSContext* cx, const JS::CallArgs& args); }; class InterfaceInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; [[noreturn]] InterfaceInstance(InterfacePrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { g_assert_not_reached(); } [[noreturn]] ~InterfaceInstance(void) { g_assert_not_reached(); } }; GJS_JSAPI_RETURN_CONVENTION bool gjs_lookup_interface_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p); #endif // GI_INTERFACE_H_ cjs-128.1/gi/js-value-inl.h0000664000175000017500000002770315116312211014310 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include #include // for isnan #include #include #include #include #include #include #include #include #include #include // for UniqueChars #include // for CanonicalizeNaN #include "gi/gtype.h" #include "gi/value.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" namespace Gjs { template struct TypeWrapper { constexpr TypeWrapper() : m_value(0) {} explicit constexpr TypeWrapper(T v) : m_value(v) {} constexpr operator T() const { return m_value; } constexpr operator T() { return m_value; } private: T m_value; }; namespace JsValueHolder { template constexpr bool comparable_types() { return std::is_arithmetic_v == std::is_arithmetic_v && std::is_signed_v == std::is_signed_v; } template constexpr bool type_fits() { if constexpr (comparable_types()) { return (std::is_integral_v == std::is_integral_v && std::numeric_limits::max() <= std::numeric_limits::max() && std::numeric_limits::lowest() >= std::numeric_limits::lowest()); } return false; } /* The tag is needed to disambiguate types such as gboolean and GType * which are in fact typedef's of other generic types. * Setting a tag for a type allows to perform proper specialization. */ template constexpr auto get_strict() { if constexpr (TAG != GI_TYPE_TAG_VOID) { if constexpr (std::is_same_v && TAG == GI_TYPE_TAG_GTYPE) return GType{}; else if constexpr (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN) return gboolean{}; else return; } else { if constexpr (std::is_same_v) return char32_t{}; else if constexpr (type_fits()) return int32_t{}; else if constexpr (type_fits()) return uint32_t{}; else if constexpr (type_fits()) return int64_t{}; else if constexpr (type_fits()) return uint64_t{}; else if constexpr (type_fits()) return double{}; else return T{}; } } template constexpr auto get_relaxed() { if constexpr (std::is_same_v || std::is_same_v) return TypeWrapper{}; else if constexpr (type_fits()) return int32_t{}; else if constexpr (type_fits()) return uint32_t{}; else if constexpr (std::is_arithmetic_v) return double{}; else return T{}; } template using Strict = decltype(JsValueHolder::get_strict()); template using Relaxed = decltype(JsValueHolder::get_relaxed()); } // namespace JsValueHolder template > constexpr bool type_has_js_getter() { return std::is_same_v; } /* Avoid implicit conversions */ template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*, const JS::HandleValue&, T*) = delete; template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, int32_t* out) { return JS::ToInt32(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, uint32_t* out) { return JS::ToUint32(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, char32_t* out) { uint32_t tmp; bool retval = JS::ToUint32(cx, value, &tmp); *out = tmp; return retval; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, int64_t* out) { if (value.isBigInt()) { *out = JS::ToBigInt64(value.toBigInt()); return true; } return JS::ToInt64(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, uint64_t* out) { if (value.isBigInt()) { *out = JS::ToBigUint64(value.toBigInt()); return true; } return JS::ToUint64(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, double* out) { return JS::ToNumber(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext*, const JS::HandleValue& value, gboolean* out) { *out = !!JS::ToBoolean(value); return true; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, GType* out) { if (!value.isObject()) return false; JS::RootedObject elem_obj(cx); elem_obj = &value.toObject(); if (!gjs_gtype_get_actual_gtype(cx, elem_obj, out)) return false; if (*out == G_TYPE_INVALID) return false; return true; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, GValue* out) { *out = G_VALUE_INIT; return gjs_value_to_g_value(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, char** out) { JS::UniqueChars tmp_result = gjs_string_to_utf8(cx, value); if (!tmp_result) return false; *out = g_strdup(tmp_result.get()); return true; } template [[nodiscard]] inline constexpr BigT max_safe_big_number() { return (BigT(1) << std::numeric_limits::digits) - 1; } template [[nodiscard]] inline constexpr BigT min_safe_big_number() { if constexpr (std::is_signed_v) return -(max_safe_big_number()); return std::numeric_limits::lowest(); } template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) { static_assert(std::numeric_limits::max() >= std::numeric_limits::max() && std::numeric_limits::lowest() <= std::numeric_limits::lowest(), "Container can't contain wanted type"); if constexpr (std::is_same_v || std::is_same_v) { if (out_of_range) { JS::BigInt* bi = nullptr; *out_of_range = false; if (value.isBigInt()) { bi = value.toBigInt(); } else if (value.isNumber()) { double number = value.toNumber(); if (!std::isfinite(number)) { *out = 0; return true; } number = std::trunc(number); bi = JS::NumberToBigInt(cx, number); if (!bi) return false; } if (bi) { *out_of_range = Gjs::bigint_is_out_of_range(bi, out); return true; } } } if constexpr (std::is_same_v) return js_value_to_c(cx, value, out); // JS::ToIntNN() converts undefined, NaN, infinity to 0 if constexpr (std::is_integral_v) { if (value.isUndefined() || (value.isDouble() && !std::isfinite(value.toDouble()))) { *out = 0; return true; } } if constexpr (std::is_arithmetic_v) { bool ret = js_value_to_c(cx, value, out); if (out_of_range) { // Infinity and NaN preserved between floating point types if constexpr (std::is_floating_point_v && std::is_floating_point_v) { if (!std::isfinite(*out)) { *out_of_range = false; return ret; } } *out_of_range = (*out > static_cast(std::numeric_limits::max()) || *out < static_cast(std::numeric_limits::lowest())); if constexpr (std::is_integral_v && std::is_floating_point_v) *out_of_range |= std::isnan(*out); } return ret; // https://trac.cppcheck.net/ticket/10731 // cppcheck-suppress missingReturn } } template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( JSContext* cx, const JS::HandleValue& value, TypeWrapper* out, bool* out_of_range) { static_assert(std::is_integral_v); WantedType wanted_out; if (!js_value_to_c_checked(cx, value, &wanted_out, out_of_range)) return false; *out = TypeWrapper{wanted_out}; return true; } template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js( JSContext* cx [[maybe_unused]], T value, JS::MutableHandleValue js_value_p) { if constexpr (std::is_same_v) { js_value_p.setBoolean(value); return true; } else if constexpr (std::is_same_v< // NOLINT(readability/braces) T, gboolean> && TAG == GI_TYPE_TAG_BOOLEAN) { js_value_p.setBoolean(value); return true; } else if constexpr (std::is_arithmetic_v) { if constexpr (std::is_same_v || std::is_same_v) { if (value < Gjs::min_safe_big_number() || value > Gjs::max_safe_big_number()) { js_value_p.setDouble(value); return true; } } if constexpr (std::is_floating_point_v) { js_value_p.setDouble(JS::CanonicalizeNaN(double{value})); return true; } js_value_p.setNumber(value); return true; } else if constexpr (std::is_same_v || std::is_same_v) { if (!value) { js_value_p.setNull(); return true; } return gjs_string_from_utf8(cx, value, js_value_p); } else { static_assert(std::is_arithmetic_v, "Unsupported type"); } } template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked( JSContext* cx [[maybe_unused]], T value, JS::MutableHandleValue js_value_p) { if constexpr (std::is_same_v || std::is_same_v) { if (value < Gjs::min_safe_big_number() || value > Gjs::max_safe_big_number()) { g_warning( "Value %s cannot be safely stored in a JS Number " "and may be rounded", std::to_string(value).c_str()); } } return c_value_to_js(cx, value, js_value_p); } } // namespace Gjs cjs-128.1/gi/ns.cpp0000664000175000017500000002307415116312211012752 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include #include #include #include #include #include // for JS_ReportOutOfMemory #include // for MutableHandleIdVector #include #include // for JSPROP_READONLY #include #include #include #include // for UniqueChars #include // for JS_NewObjectWithGivenProto #include "gi/cwrapper.h" #include "gi/ns.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" #if GLIB_CHECK_VERSION(2, 79, 2) # include "cjs/deprecation.h" #endif // GLib >= 2.79.2 [[nodiscard]] static bool type_is_enumerable(GIInfoType info_type) { switch (info_type) { case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_UNION: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CONSTANT: return true; // Don't enumerate types which GJS doesn't define on namespaces. // See gjs_define_info case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: default: return false; } } class Ns : private GjsAutoChar, public CWrapper { friend CWrapperPointerOps; friend CWrapper; #if GLIB_CHECK_VERSION(2, 79, 2) bool m_is_gio_or_glib : 1; #endif // GLib >= 2.79.2 static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_ns; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GNAMESPACE; explicit Ns(const char* ns_name) : GjsAutoChar(const_cast(ns_name), GjsAutoTakeOwnership()) { GJS_INC_COUNTER(ns); #if GLIB_CHECK_VERSION(2, 79, 2) m_is_gio_or_glib = strcmp(ns_name, "Gio") == 0 || strcmp(ns_name, "GLib") == 0; #endif // GLib >= 2.79.2 } ~Ns() { GJS_DEC_COUNTER(ns); } #if GLIB_CHECK_VERSION(2, 79, 2) // helper function void platform_specific_warning(JSContext* cx, const char* prefix, const char* platform, const char* resolved_name, const char** exceptions = nullptr) { if (!g_str_has_prefix(resolved_name, prefix)) return; const char* base_name = resolved_name + strlen(prefix); GjsAutoChar old_name = g_strdup_printf("%s.%s", this->get(), resolved_name); if (exceptions) { for (const char** exception = exceptions; *exception; exception++) { if (strcmp(old_name, *exception) == 0) return; } } GjsAutoChar new_name = g_strdup_printf("%s%s.%s", this->get(), platform, base_name); _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId::PlatformSpecificTypelib, {old_name.get(), new_name.get()}); } #endif // GLib >= 2.79.2 // JSClass operations // The *resolved out parameter, on success, should be false to indicate that // id was not resolved; and true if id was resolved. GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { if (!id.isString()) { *resolved = false; return true; // not resolved, but no error } // let Object.prototype resolve these const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (id == atoms.to_string() || id == atoms.value_of()) { *resolved = false; return true; } JS::UniqueChars name; if (!gjs_get_string_id(cx, id, &name)) return false; if (!name) { *resolved = false; return true; // not resolved, but no error } GjsAutoBaseInfo info = g_irepository_find_by_name(nullptr, get(), name.get()); if (!info) { *resolved = false; // No property defined, but no error either return true; } gjs_debug(GJS_DEBUG_GNAMESPACE, "Found info type %s for '%s' in namespace '%s'", gjs_info_type_name(info.type()), info.name(), info.ns()); #if GLIB_CHECK_VERSION(2, 79, 2) static const char* unix_types_exceptions[] = { "Gio.UnixConnection", "Gio.UnixCredentialsMessage", "Gio.UnixFDList", "Gio.UnixSocketAddress", "Gio.UnixSocketAddressType", nullptr}; if (m_is_gio_or_glib) { platform_specific_warning(cx, "Unix", "Unix", name.get(), unix_types_exceptions); platform_specific_warning(cx, "unix_", "Unix", name.get()); platform_specific_warning(cx, "Win32", "Win32", name.get()); platform_specific_warning(cx, "win32_", "Win32", name.get()); } #endif // GLib >= 2.79.2 bool defined; if (!gjs_define_info(cx, obj, info, &defined)) { gjs_debug(GJS_DEBUG_GNAMESPACE, "Failed to define info '%s'", info.name()); return false; } // we defined the property in this object? *resolved = defined; return true; } GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj [[maybe_unused]], JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { int n = g_irepository_get_n_infos(nullptr, get()); if (!properties.reserve(properties.length() + n)) { JS_ReportOutOfMemory(cx); return false; } for (int k = 0; k < n; k++) { GjsAutoBaseInfo info = g_irepository_get_info(nullptr, get(), k); GIInfoType info_type = g_base_info_get_type(info); if (!type_is_enumerable(info_type)) continue; const char* name = info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) return false; properties.infallibleAppend(id); } return true; } static void finalize_impl(JS::GCContext*, Ns* priv) { g_assert(priv && "Finalize called on wrong object"); delete priv; } // Properties and methods GJS_JSAPI_RETURN_CONVENTION static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, Ns, priv); return gjs_string_from_utf8(cx, priv->get(), args.rval()); } GJS_JSAPI_RETURN_CONVENTION static bool get_version(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, Ns, priv); const char *version = g_irepository_get_version(nullptr, priv->get()); return gjs_string_from_utf8(cx, version, args.rval()); } static constexpr JSClassOps class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &Ns::new_enumerate, &Ns::resolve, nullptr, // mayResolve &Ns::finalize, }; // clang-format off static constexpr JSPropertySpec proto_props[] = { JS_STRING_SYM_PS(toStringTag, "GIRepositoryNamespace", JSPROP_READONLY), JS_PSG("__name__", &Ns::get_name, GJS_MODULE_PROP_FLAGS), JS_PSG("__version__", &Ns::get_version, GJS_MODULE_PROP_FLAGS & ~JSPROP_ENUMERATE), JS_PS_END}; // clang-format on static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties nullptr, // prototypeFunctions Ns::proto_props, nullptr, // finishInit js::ClassSpec::DontDefineConstructor}; static constexpr JSClass klass = { "GIRepositoryNamespace", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, &Ns::class_ops, &Ns::class_spec}; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx, const char* ns_name) { JS::RootedObject proto(cx, Ns::create_prototype(cx)); if (!proto) return nullptr; JS::RootedObject ns(cx, JS_NewObjectWithGivenProto(cx, &Ns::klass, proto)); if (!ns) return nullptr; auto* priv = new Ns(ns_name); Ns::init_private(ns, priv); gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, "ns constructor, obj %p priv %p", ns.get(), priv); return ns; } }; JSObject* gjs_create_ns(JSContext *context, const char *ns_name) { return Ns::create(context, ns_name); } cjs-128.1/gi/ns.h0000664000175000017500000000064715116312211012420 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_NS_H_ #define GI_NS_H_ #include #include "cjs/macros.h" class JSObject; struct JSContext; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_ns(JSContext *context, const char *ns_name); #endif // GI_NS_H_ cjs-128.1/gi/object.cpp0000664000175000017500000033615515116312211013607 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2018 Philip Chimento #include #include #include // for memset, strcmp #include // for find #include // for mem_fn #include #include #include // for tie #include #include // for move #include #include #include #include #include // for IsCallable, JS_CallFunctionValue #include #include #include #include #include // for JS_ReportOutOfMemory #include // for JS_AddWeakPointerCompartmentCallback #include // for MutableWrappedPtrOperations #include #include // for AddAssociatedMemory, RemoveAssoci... #include #include // for JSPROP_PERMANENT, JSPROP_READONLY #include #include #include #include // for UniqueChars #include #include #include #include // for JS_GetFunctionObject, IdVector #include // for JS_GetObjectFunction, GetFunctionNativeReserved #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/cjs_gi_trace.h" #include "gi/object.h" #include "gi/repo.h" #include "gi/toggle.h" #include "gi/utils-inl.h" // for gjs_int_to_pointer #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util-root.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "cjs/profiler-private.h" #include "util/log.h" class JSTracer; /* This is a trick to print out the sizes of the structs at compile time, in * an error message. */ // template struct Measure; // Measure instance_size; // Measure prototype_size; #if defined(__x86_64__) && defined(__clang__) /* This isn't meant to be comprehensive, but should trip on at least one CI job * if sizeof(ObjectInstance) is increased. */ static_assert(sizeof(ObjectInstance) <= 64, "Think very hard before increasing the size of ObjectInstance. " "There can be tens of thousands of them alive in a typical " "gnome-shell run."); #endif // x86-64 clang bool ObjectInstance::s_weak_pointer_callback = false; decltype(ObjectInstance::s_wrapped_gobject_list) ObjectInstance::s_wrapped_gobject_list; static const auto DISPOSED_OBJECT = std::numeric_limits::max(); GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_object_prototype_from_info(JSContext*, GIBaseInfo*, GType); // clang-format off G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type) G_DEFINE_QUARK(gjs::custom-property, ObjectBase::custom_property) G_DEFINE_QUARK(gjs::instance-strings, ObjectBase::instance_strings) G_DEFINE_QUARK(gjs::disposed, ObjectBase::disposed) // clang-format on [[nodiscard]] static GQuark gjs_object_priv_quark() { static GQuark val = 0; if (G_UNLIKELY (!val)) val = g_quark_from_static_string ("gjs::private"); return val; } bool ObjectBase::is_custom_js_class() { return !!g_type_get_qdata(gtype(), ObjectBase::custom_type_quark()); } void ObjectInstance::link() { g_assert(std::find(s_wrapped_gobject_list.begin(), s_wrapped_gobject_list.end(), this) == s_wrapped_gobject_list.end()); s_wrapped_gobject_list.push_back(this); } void ObjectInstance::unlink() { Gjs::remove_one_from_unsorted_vector(&s_wrapped_gobject_list, this); } const void* ObjectBase::jsobj_addr(void) const { if (is_prototype()) return nullptr; return to_instance()->m_wrapper.debug_addr(); } // Overrides GIWrapperBase::typecheck(). We only override the overload that // throws, so that we can throw our own more informative error. bool ObjectBase::typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype) { if (GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype)) return true; gjs_throw(cx, "This JS object wrapper isn't wrapping a GObject." " If this is a custom subclass, are you sure you chained" " up to the parent _init properly?"); return false; } bool ObjectInstance::check_gobject_disposed_or_finalized( const char* for_what) const { if (!m_gobj_disposed) return true; g_critical( "Object %s.%s (%p), has been already %s — impossible to %s " "it. This might be caused by the object having been destroyed from C " "code using something such as destroy(), dispose(), or remove() " "vfuncs.\n%s", ns(), name(), m_ptr.get(), m_gobj_finalized ? "finalized" : "disposed", for_what, gjs_dumpstack_string().c_str()); return false; } bool ObjectInstance::check_gobject_finalized(const char* for_what) const { if (check_gobject_disposed_or_finalized(for_what)) return true; return !m_gobj_finalized; } ObjectInstance * ObjectInstance::for_gobject(GObject *gobj) { auto priv = static_cast(g_object_get_qdata(gobj, gjs_object_priv_quark())); if (priv) priv->check_js_object_finalized(); return priv; } void ObjectInstance::check_js_object_finalized(void) { if (!m_uses_toggle_ref) return; if (G_UNLIKELY(m_wrapper_finalized)) { g_critical( "Object %p (a %s) resurfaced after the JS wrapper was finalized. " "This is some library doing dubious memory management inside " "dispose()", m_ptr.get(), type_name()); m_wrapper_finalized = false; g_assert(!m_wrapper); /* should associate again with a new wrapper */ } } ObjectPrototype* ObjectPrototype::for_gtype(GType gtype) { return static_cast( g_type_get_qdata(gtype, gjs_object_priv_quark())); } void ObjectPrototype::set_type_qdata(void) { g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this); } void ObjectInstance::set_object_qdata(void) { g_object_set_qdata_full( m_ptr, gjs_object_priv_quark(), this, [](void* object) { auto* self = static_cast(object); if (G_UNLIKELY(!self->m_gobj_disposed)) { g_warning( "Object %p (a %s) was finalized but we didn't track " "its disposal", self->m_ptr.get(), g_type_name(self->gtype())); self->m_gobj_disposed = true; } self->m_gobj_finalized = true; gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p finalized", self->m_ptr.get()); }); } void ObjectInstance::unset_object_qdata(void) { auto priv_quark = gjs_object_priv_quark(); if (g_object_get_qdata(m_ptr, priv_quark) == this) g_object_steal_qdata(m_ptr, priv_quark); } GParamSpec* ObjectPrototype::find_param_spec_from_id( JSContext* cx, GjsAutoTypeClass const& object_class, JS::HandleString key) { /* First check for the ID in the cache */ JS::UniqueChars js_prop_name(JS_EncodeStringToUTF8(cx, key)); if (!js_prop_name) return nullptr; GjsAutoChar gname = gjs_hyphen_from_camel(js_prop_name.get()); GParamSpec* pspec = g_object_class_find_property(object_class, gname); if (!pspec) { gjs_wrapper_throw_nonexistent_field(cx, m_gtype, js_prop_name.get()); return nullptr; } return pspec; } /* A hook on adding a property to an object. This is called during a set * property operation after all the resolve hooks on the prototype chain have * failed to resolve. We use this to mark an object as needing toggle refs when * custom state is set on it, because we need to keep the JS GObject wrapper * alive in order not to lose custom "expando" properties. */ bool ObjectBase::add_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value) { auto* priv = ObjectBase::for_js(cx, obj); /* priv is null during init: property is not being added from JS */ if (!priv) { debug_jsprop_static("Add property hook", id, obj); return true; } if (priv->is_prototype()) return true; return priv->to_instance()->add_property_impl(cx, obj, id, value); } bool ObjectInstance::add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue) { debug_jsprop("Add property hook", id, obj); if (is_custom_js_class()) return true; ensure_uses_toggle_ref(cx); return true; } bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); auto* pspec = static_cast( gjs_dynamic_property_private_slot(&args.callee()).toPrivate()); std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"}; AutoProfilerLabel label(cx, "property getter", fullName.c_str()); priv->debug_jsprop("Property getter", pspec->name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ return priv->to_instance()->prop_getter_impl(cx, pspec, args.rval()); } bool ObjectInstance::prop_getter_impl(JSContext* cx, GParamSpec* param, JS::MutableHandleValue rval) { if (!check_gobject_finalized("get any property from")) { rval.setUndefined(); return true; } if (param->flags & G_PARAM_DEPRECATED) { const std::string& class_name = format_name(); _gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {class_name.c_str(), param->name}); } if ((param->flags & G_PARAM_READABLE) == 0) { rval.setUndefined(); return true; } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", param->name); Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param)); g_object_get_property(m_ptr, param->name, &gvalue); return gjs_value_from_g_value(cx, rval, &gvalue); } [[nodiscard]] static GjsAutoFieldInfo lookup_field_info(GIObjectInfo* info, const char* name) { int n_fields = g_object_info_get_n_fields(info); int ix; GjsAutoFieldInfo retval; for (ix = 0; ix < n_fields; ix++) { retval = g_object_info_get_field(info, ix); if (strcmp(name, retval.name()) == 0) break; retval.reset(); } if (!retval || !(g_field_info_get_flags(retval) & GI_FIELD_IS_READABLE)) return nullptr; return retval; } bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + "]"}; AutoProfilerLabel label(cx, "field getter", fullName.c_str()); priv->debug_jsprop("Field getter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ return priv->to_instance()->field_getter_impl(cx, name, args.rval()); } bool ObjectInstance::field_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval) { if (!check_gobject_finalized("get any property from")) return true; ObjectPrototype* proto_priv = get_prototype(); GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); GITypeTag tag; GIArgument arg = { 0 }; gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field", gjs_debug_string(name).c_str()); GjsAutoTypeInfo type = g_field_info_get_type(field); tag = g_type_info_get_tag(type); switch (tag) { case GI_TYPE_TAG_ARRAY: case GI_TYPE_TAG_ERROR: case GI_TYPE_TAG_GHASH: case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: case GI_TYPE_TAG_INTERFACE: gjs_throw(cx, "Can't get field %s; GObject introspection supports only " "fields with simple types, not %s", gjs_debug_string(name).c_str(), g_type_tag_to_string(tag)); return false; default: break; } if (!g_field_info_get_field(field, m_ptr, &arg)) { gjs_throw(cx, "Error getting field %s from object", gjs_debug_string(name).c_str()); return false; } return gjs_value_from_gi_argument(cx, rval, type, GJS_ARGUMENT_FIELD, GI_TRANSFER_EVERYTHING, &arg); /* transfer is irrelevant because g_field_info_get_field() doesn't * handle boxed types */ } /* Dynamic setter for GObject properties. Returns false on OOM/exception. * args.rval() becomes the "stored value" for the property. */ bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); auto* pspec = static_cast( gjs_dynamic_property_private_slot(&args.callee()).toPrivate()); std::string fullName{priv->format_name() + "[\"" + pspec->name + "\"]"}; AutoProfilerLabel label(cx, "property setter", fullName.c_str()); priv->debug_jsprop("Property setter", pspec->name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ /* Clear the JS stored value, to avoid keeping additional references */ args.rval().setUndefined(); return priv->to_instance()->prop_setter_impl(cx, pspec, args[0]); } bool ObjectInstance::prop_setter_impl(JSContext* cx, GParamSpec* param_spec, JS::HandleValue value) { if (!check_gobject_finalized("set any property on")) return true; if (!(param_spec->flags & G_PARAM_WRITABLE)) /* prevent setting the prop even in JS */ return gjs_wrapper_throw_readonly_field(cx, gtype(), param_spec->name); if (param_spec->flags & G_PARAM_DEPRECATED) { const std::string& class_name = format_name(); _gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {class_name.c_str(), param_spec->name}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s", param_spec->name); Gjs::AutoGValue gvalue(G_PARAM_SPEC_VALUE_TYPE(param_spec)); if (!gjs_value_to_g_value(cx, value, &gvalue)) return false; g_object_set_property(m_ptr, param_spec->name, &gvalue); return true; } bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedString name(cx, gjs_dynamic_property_private_slot(&args.callee()).toString()); std::string fullName{priv->format_name() + "[" + gjs_debug_string(name) + "]"}; AutoProfilerLabel label(cx, "field setter", fullName.c_str()); priv->debug_jsprop("Field setter", name, obj); if (priv->is_prototype()) return true; /* Ignore silently; note that this is different from what we do for * boxed types, for historical reasons */ /* We have to update args.rval(), because JS caches it as the property's "stored * value" (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/Stored_value) * and so subsequent gets would get the stored value instead of accessing * the field */ args.rval().setUndefined(); return priv->to_instance()->field_setter_not_impl(cx, name); } bool ObjectInstance::field_setter_not_impl(JSContext* cx, JS::HandleString name) { if (!check_gobject_finalized("set GObject field on")) return true; ObjectPrototype* proto_priv = get_prototype(); GIFieldInfo* field = proto_priv->lookup_cached_field_info(cx, name); /* As far as I know, GI never exposes GObject instance struct fields as * writable, so no need to implement this for the time being */ if (g_field_info_get_flags(field) & GI_FIELD_IS_WRITABLE) { g_message("Field %s of a GObject is writable, but setting it is not " "implemented", gjs_debug_string(name).c_str()); return true; } return gjs_wrapper_throw_readonly_field(cx, gtype(), g_base_info_get_name(field)); } bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) { GjsAutoError error; GType ptype = g_type_parent(m_gtype); gpointer addr1, addr2; addr1 = g_vfunc_info_get_address(info, m_gtype, &error); if (error) return false; addr2 = g_vfunc_info_get_address(info, ptype, &error); if (error) return false; return addr1 == addr2; } [[nodiscard]] static GjsAutoVFuncInfo find_vfunc_on_parents( GIObjectInfo* info, const char* name, bool* out_defined_by_parent) { bool defined_by_parent = false; /* ref the first info so that we don't destroy * it when unrefing parents later */ GjsAutoObjectInfo parent(info, GjsAutoTakeOwnership()); /* Since it isn't possible to override a vfunc on * an interface without reimplementing it, we don't need * to search the parent types when looking for a vfunc. */ GjsAutoVFuncInfo vfunc = g_object_info_find_vfunc_using_interfaces(parent, name, nullptr); while (!vfunc && parent) { parent = g_object_info_get_parent(parent); if (parent) vfunc = g_object_info_find_vfunc(parent, name); defined_by_parent = true; } if (out_defined_by_parent) *out_defined_by_parent = defined_by_parent; return vfunc; } /* Taken from GLib */ static void canonicalize_key(const GjsAutoChar& key) { for (char* p = key; *p != 0; p++) { char c = *p; if (c != '-' && (c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) *p = '-'; } } /* @name must already be canonicalized */ [[nodiscard]] static bool is_ginterface_property_name(GIInterfaceInfo* info, const char* name) { int n_props = g_interface_info_get_n_properties(info); GjsAutoPropertyInfo prop_info; for (int ix = 0; ix < n_props; ix++) { prop_info = g_interface_info_get_property(info, ix); if (strcmp(name, prop_info.name()) == 0) break; prop_info.reset(); } return !!prop_info; } bool ObjectPrototype::lazy_define_gobject_property( JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec* pspec, bool* resolved, const char* name) { bool found = false; if (!JS_AlreadyHasOwnPropertyById(cx, obj, id, &found)) return false; if (found) { /* Already defined, so *resolved = false because we didn't just * define it */ *resolved = false; return true; } debug_jsprop("Defining lazy GObject property", id, obj); // Do not fetch JS overridden properties from GObject, to avoid // infinite recursion. if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) { *resolved = false; return true; } JS::RootedValue private_value{cx, JS::PrivateValue(pspec)}; if (!gjs_define_property_dynamic( cx, obj, name, id, "gobject_prop", &ObjectBase::prop_getter, &ObjectBase::prop_setter, private_value, // Make property configurable so that interface properties can be // overridden by GObject.ParamSpec.override in the class that // implements them GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT)) return false; *resolved = true; return true; } // An object shared by the getter and setter to store the interface' prototype // and overrides. static constexpr size_t ACCESSOR_SLOT = 0; static bool interface_getter(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedValue v_accessor( cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT)); g_assert(v_accessor.isObject() && "accessor must be an object"); JS::RootedObject accessor(cx, &v_accessor.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); // Check if an override value has been set bool has_override_symbol = false; if (!JS_HasPropertyById(cx, accessor, atoms.override(), &has_override_symbol)) return false; if (has_override_symbol) { JS::RootedValue v_override_symbol(cx); if (!JS_GetPropertyById(cx, accessor, atoms.override(), &v_override_symbol)) return false; g_assert(v_override_symbol.isSymbol() && "override symbol must be a symbol"); JS::RootedSymbol override_symbol(cx, v_override_symbol.toSymbol()); JS::RootedId override_id(cx, JS::PropertyKey::Symbol(override_symbol)); JS::RootedObject this_obj(cx); if (!args.computeThis(cx, &this_obj)) return false; bool has_override = false; if (!JS_HasPropertyById(cx, this_obj, override_id, &has_override)) return false; if (has_override) return JS_GetPropertyById(cx, this_obj, override_id, args.rval()); } JS::RootedValue v_prototype(cx); if (!JS_GetPropertyById(cx, accessor, atoms.prototype(), &v_prototype)) return false; g_assert(v_prototype.isObject() && "prototype must be an object"); JS::RootedObject prototype(cx, &v_prototype.toObject()); JS::RootedFunction fn_obj{cx, JS_GetObjectFunction(&args.callee())}; JS::RootedString fn_name{cx}; if (!JS_GetFunctionId(cx, fn_obj, &fn_name)) return false; JS::RootedId id{cx, JS::PropertyKey::NonIntAtom(fn_name)}; return JS_GetPropertyById(cx, prototype, id, args.rval()); } static bool interface_setter(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedValue v_accessor( cx, js::GetFunctionNativeReserved(&args.callee(), ACCESSOR_SLOT)); JS::RootedObject accessor(cx, &v_accessor.toObject()); JS::RootedString description( cx, JS_AtomizeAndPinString(cx, "Private interface function setter")); JS::RootedSymbol symbol(cx, JS::NewSymbol(cx, description)); JS::RootedValue v_symbol(cx, JS::SymbolValue(symbol)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_SetPropertyById(cx, accessor, atoms.override(), v_symbol)) return false; args.rval().setUndefined(); JS::RootedObject this_obj(cx); if (!args.computeThis(cx, &this_obj)) return false; JS::RootedId override_id(cx, JS::PropertyKey::Symbol(symbol)); return JS_SetPropertyById(cx, this_obj, override_id, args[0]); } static bool resolve_on_interface_prototype(JSContext* cx, GIInterfaceInfo* iface_info, JS::HandleId identifier, JS::HandleObject class_prototype, bool* found) { GType gtype = g_base_info_get_type(iface_info); JS::RootedObject interface_prototype( cx, gjs_lookup_object_prototype_from_info(cx, iface_info, gtype)); if (!interface_prototype) return false; bool exists = false; if (!JS_HasPropertyById(cx, interface_prototype, identifier, &exists)) return false; // If the property doesn't exist on the interface prototype, we don't need // to perform this trick. if (!exists) { *found = false; return true; } // Lazily define a property on the class prototype if a property // of that name is present on an interface prototype that the class // implements. // // Define a property of the same name on the class prototype, with a // getter and setter. This is so that e.g. file.dup() calls the _current_ // value of Gio.File.prototype.dup(), not the original, so that it can be // overridden (or monkeypatched). // // The setter (interface_setter() above) marks the property as overridden if // it is set from user code. The getter (interface_getter() above) proxies // the interface prototype's property, unless it was marked as overridden. // // Store the identifier in the getter and setter function's ID slots for // to enable looking up the original value on the interface prototype. JS::RootedObject getter( cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved( cx, interface_getter, 0, 0, identifier))); if (!getter) return false; JS::RootedObject setter( cx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved( cx, interface_setter, 1, 0, identifier))); if (!setter) return false; JS::RootedObject accessor(cx, JS_NewPlainObject(cx)); if (!accessor) return false; js::SetFunctionNativeReserved(setter, ACCESSOR_SLOT, JS::ObjectValue(*accessor)); js::SetFunctionNativeReserved(getter, ACCESSOR_SLOT, JS::ObjectValue(*accessor)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v_prototype(cx, JS::ObjectValue(*interface_prototype)); if (!JS_SetPropertyById(cx, accessor, atoms.prototype(), v_prototype)) return false; // Create a new descriptor with our getter and setter, that is configurable // and enumerable, because GObject may need to redefine it later. JS::PropertyAttributes attrs{JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable}; JS::Rooted desc( cx, JS::PropertyDescriptor::Accessor(getter, setter, attrs)); if (!JS_DefinePropertyById(cx, class_prototype, identifier, desc)) return false; *found = true; return true; } bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name, ResolveWhat resolve_props) { guint n_interfaces; guint i; GjsAutoChar canonical_name; if (resolve_props == ConsiderMethodsAndProperties) { // Optimization: GObject property names must start with a letter if (g_ascii_isalpha(name[0])) { canonical_name = gjs_hyphen_from_camel(name); canonicalize_key(canonical_name); } } GIInterfaceInfo** interfaces; g_irepository_get_object_gtype_interfaces(nullptr, m_gtype, &n_interfaces, &interfaces); /* Fallback to GType system for non custom GObjects with no GI information */ if (canonical_name && G_TYPE_IS_CLASSED(m_gtype) && !is_custom_js_class()) { GjsAutoTypeClass oclass(m_gtype); if (GParamSpec* pspec = g_object_class_find_property(oclass, canonical_name)) return lazy_define_gobject_property(cx, obj, id, pspec, resolved, name); for (i = 0; i < n_interfaces; i++) { GType iface_gtype = g_registered_type_info_get_g_type(interfaces[i]); if (!G_TYPE_IS_CLASSED(iface_gtype)) continue; GjsAutoTypeClass iclass(iface_gtype); if (GParamSpec* pspec = g_object_class_find_property(iclass, canonical_name)) return lazy_define_gobject_property(cx, obj, id, pspec, resolved, name); } } for (i = 0; i < n_interfaces; i++) { GIInterfaceInfo* iface_info = interfaces[i]; GjsAutoFunctionInfo method_info = g_interface_info_find_method(iface_info, name); if (method_info) { if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { bool found = false; if (!resolve_on_interface_prototype(cx, iface_info, id, obj, &found)) return false; // Fallback to defining the function from type info... if (!found && !gjs_define_function(cx, obj, m_gtype, method_info)) return false; *resolved = true; return true; } } /* If the name refers to a GObject property, lazily define the property * in JS as we do below in the real resolve hook. We ignore fields here * because I don't think interfaces can have fields */ if (canonical_name && is_ginterface_property_name(iface_info, canonical_name)) { GjsAutoTypeClass oclass(m_gtype); // unowned GParamSpec* pspec = g_object_class_find_property( oclass, canonical_name); // unowned if (pspec && pspec->owner_type == m_gtype) { return lazy_define_gobject_property(cx, obj, id, pspec, resolved, name); } } return resolve_on_interface_prototype(cx, iface_info, id, obj, resolved); } *resolved = false; return true; } [[nodiscard]] static GjsAutoChar get_gobject_property_name(GIObjectInfo* info, const char* name) { // Optimization: GObject property names must start with a letter if (!g_ascii_isalpha(name[0])) return nullptr; int n_props = g_object_info_get_n_properties(info); int n_ifaces = g_object_info_get_n_interfaces(info); int ix; GjsAutoChar canonical_name = gjs_hyphen_from_camel(name); canonicalize_key(canonical_name); for (ix = 0; ix < n_props; ix++) { GjsAutoPropertyInfo prop_info = g_object_info_get_property(info, ix); if (strcmp(canonical_name, prop_info.name()) == 0) return canonical_name; } for (ix = 0; ix < n_ifaces; ix++) { GjsAutoInterfaceInfo iface_info = g_object_info_get_interface(info, ix); if (is_ginterface_property_name(iface_info, canonical_name)) return canonical_name; } return nullptr; } // Override of GIWrapperBase::id_is_never_lazy() bool ObjectBase::id_is_never_lazy(jsid name, const GjsAtoms& atoms) { // Keep this list in sync with ObjectBase::proto_properties and // ObjectBase::proto_methods. However, explicitly do not include // connect() in it, because there are a few cases where the lazy property // should override the predefined one, such as Gio.Cancellable.connect(). return name == atoms.init() || name == atoms.connect_after() || name == atoms.emit(); } bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { if (m_unresolvable_cache.has(id)) { *resolved = false; return true; } JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } if (!uncached_resolve(context, obj, id, prop_name.get(), resolved)) return false; if (!*resolved && !m_unresolvable_cache.putNew(id)) { JS_ReportOutOfMemory(context); return false; } return true; } bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved) { // If we have no GIRepository information (we're a JS GObject subclass or an // internal non-introspected class such as GLocalFile), we need to look at // exposing interfaces. Look up our interfaces through GType data, and then // hope that *those* are introspectable. if (!info()) return resolve_no_info(context, obj, id, resolved, name, ConsiderMethodsAndProperties); if (g_str_has_prefix(name, "vfunc_")) { /* The only time we find a vfunc info is when we're the base * class that defined the vfunc. If we let regular prototype * chaining resolve this, we'd have the implementation for the base's * vfunc on the base class, without any other "real" implementations * in the way. If we want to expose a "real" vfunc implementation, * we need to go down to the parent infos and look at their VFuncInfos. * * This is good, but it's memory-hungry -- we would define every * possible vfunc on every possible object, even if it's the same * "real" vfunc underneath. Instead, only expose vfuncs that are * different from their parent, and let prototype chaining do the * rest. */ const char *name_without_vfunc_ = &(name[6]); /* lifetime tied to name */ bool defined_by_parent; GjsAutoVFuncInfo vfunc = find_vfunc_on_parents( m_info, name_without_vfunc_, &defined_by_parent); if (vfunc) { /* In the event that the vfunc is unchanged, let regular * prototypal inheritance take over. */ if (defined_by_parent && is_vfunc_unchanged(vfunc)) { *resolved = false; return true; } if (!gjs_define_function(context, obj, m_gtype, vfunc)) return false; *resolved = true; return true; } /* If the vfunc wasn't found, fall through, back to normal * method resolution. */ } if (auto const& canonical_name = get_gobject_property_name(m_info, name)) { GjsAutoTypeClass gobj_class{m_gtype}; if (GParamSpec* pspec = g_object_class_find_property(gobj_class, canonical_name)) return lazy_define_gobject_property(context, obj, id, pspec, resolved, name); } GjsAutoFieldInfo field_info = lookup_field_info(m_info, name); if (field_info) { bool found = false; if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found)) return false; if (found) { *resolved = false; return true; } debug_jsprop("Defining lazy GObject field", id, obj); unsigned flags = GJS_MODULE_PROP_FLAGS; if (!(g_field_info_get_flags(field_info) & GI_FIELD_IS_WRITABLE)) flags |= JSPROP_READONLY; JS::RootedString key(context, id.toString()); if (!m_field_cache.putNew(key, field_info.release())) { JS_ReportOutOfMemory(context); return false; } JS::RootedValue private_id(context, JS::StringValue(key)); if (!gjs_define_property_dynamic( context, obj, name, id, "gobject_field", &ObjectBase::field_getter, &ObjectBase::field_setter, private_id, flags)) return false; *resolved = true; return true; } /* find_method does not look at methods on parent classes, * we rely on javascript to walk up the __proto__ chain * and find those and define them in the right prototype. * * Note that if it isn't a method on the object, since JS * lacks multiple inheritance, we're sticking the iface * methods in the object prototype, which means there are many * copies of the iface methods (one per object class node that * introduces the iface) */ GjsAutoBaseInfo implementor_info; GjsAutoFunctionInfo method_info = g_object_info_find_method_using_interfaces(m_info, name, implementor_info.out()); /** * Search through any interfaces implemented by the GType; * See https://bugzilla.gnome.org/show_bug.cgi?id=632922 * for background on why we need to do this. */ if (!method_info) return resolve_no_info(context, obj, id, resolved, name, ConsiderOnlyMethods); #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for %s (%s.%s)", method_info.name(), type_name(), ns(), this->name()); if (GI_IS_INTERFACE_INFO(implementor_info)) { bool found = false; if (!resolve_on_interface_prototype(context, implementor_info, id, obj, &found)) return false; // If the method was not found fallback to defining the function // from type info... if (!found && !gjs_define_function(context, obj, m_gtype, method_info)) { return false; } } else if (!gjs_define_function(context, obj, m_gtype, method_info)) { return false; } *resolved = true; /* we defined the prop in obj */ } return true; } bool ObjectPrototype::new_enumerate_impl(JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { unsigned n_interfaces; GType* interfaces = g_type_interfaces(gtype(), &n_interfaces); for (unsigned k = 0; k < n_interfaces; k++) { GjsAutoInterfaceInfo iface_info = g_irepository_find_by_gtype(nullptr, interfaces[k]); if (!iface_info) { continue; } int n_methods = g_interface_info_get_n_methods(iface_info); int n_properties = g_interface_info_get_n_properties(iface_info); if (!properties.reserve(properties.length() + n_methods + n_properties)) { JS_ReportOutOfMemory(cx); return false; } // Methods for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_interface_info_get_method(iface_info, i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) return false; properties.infallibleAppend(id); } } // Properties for (int i = 0; i < n_properties; i++) { GjsAutoPropertyInfo prop_info = g_interface_info_get_property(iface_info, i); GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); jsid id = gjs_intern_string_to_id(cx, js_name); if (id.isVoid()) return false; properties.infallibleAppend(id); } } g_free(interfaces); if (info()) { int n_methods = g_object_info_get_n_methods(info()); int n_properties = g_object_info_get_n_properties(info()); if (!properties.reserve(properties.length() + n_methods + n_properties)) { JS_ReportOutOfMemory(cx); return false; } // Methods for (int i = 0; i < n_methods; i++) { GjsAutoFunctionInfo meth_info = g_object_info_get_method(info(), i); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); if (flags & GI_FUNCTION_IS_METHOD) { const char* name = meth_info.name(); jsid id = gjs_intern_string_to_id(cx, name); if (id.isVoid()) return false; properties.infallibleAppend(id); } } // Properties for (int i = 0; i < n_properties; i++) { GjsAutoPropertyInfo prop_info = g_object_info_get_property(info(), i); GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name()); jsid id = gjs_intern_string_to_id(cx, js_name); if (id.isVoid()) return false; properties.infallibleAppend(id); } } return true; } /* Set properties from args to constructor (args[0] is supposed to be * a hash) */ bool ObjectPrototype::props_to_g_parameters( JSContext* context, GjsAutoTypeClass const& object_class, JS::HandleObject props, std::vector* names, AutoGValueVector* values) { size_t ix, length; JS::RootedId prop_id(context); JS::RootedValue value(context); JS::Rooted ids(context, context); std::unordered_set visited_params; if (!JS_Enumerate(context, props, &ids)) { gjs_throw(context, "Failed to create property iterator for object props hash"); return false; } values->reserve(ids.length()); for (ix = 0, length = ids.length(); ix < length; ix++) { /* ids[ix] is reachable because props is rooted, but require_property * doesn't know that */ prop_id = ids[ix]; if (!prop_id.isString()) return gjs_wrapper_throw_nonexistent_field( context, m_gtype, gjs_debug_id(prop_id).c_str()); JS::RootedString js_prop_name(context, prop_id.toString()); GParamSpec* param_spec = find_param_spec_from_id(context, object_class, js_prop_name); if (!param_spec) return false; if (visited_params.find(param_spec) != visited_params.end()) continue; visited_params.insert(param_spec); if (!JS_GetPropertyById(context, props, prop_id, &value)) return false; if (value.isUndefined()) { gjs_throw(context, "Invalid value 'undefined' for property %s in " "object initializer.", param_spec->name); return false; } if (!(param_spec->flags & G_PARAM_WRITABLE)) return gjs_wrapper_throw_readonly_field(context, m_gtype, param_spec->name); /* prevent setting the prop even in JS */ Gjs::AutoGValue& gvalue = values->emplace_back(G_PARAM_SPEC_VALUE_TYPE(param_spec)); if (!gjs_value_to_g_value(context, value, &gvalue)) return false; names->push_back(param_spec->name); // owned by GParamSpec } return true; } void ObjectInstance::wrapped_gobj_dispose_notify( void* data, GObject* where_the_object_was GJS_USED_VERBOSE_LIFECYCLE) { auto *priv = static_cast(data); priv->gobj_dispose_notify(); gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p disposed", where_the_object_was); } void ObjectInstance::track_gobject_finalization() { auto quark = ObjectBase::disposed_quark(); g_object_steal_qdata(m_ptr, quark); g_object_set_qdata_full(m_ptr, quark, this, [](void* data) { auto* self = static_cast(data); self->m_gobj_finalized = true; gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Wrapped GObject %p finalized", self->m_ptr.get()); }); } void ObjectInstance::ignore_gobject_finalization() { auto quark = ObjectBase::disposed_quark(); if (g_object_get_qdata(m_ptr, quark) == this) { g_object_steal_qdata(m_ptr, quark); g_object_set_qdata(m_ptr, quark, gjs_int_to_pointer(DISPOSED_OBJECT)); } } void ObjectInstance::gobj_dispose_notify(void) { m_gobj_disposed = true; unset_object_qdata(); track_gobject_finalization(); if (m_uses_toggle_ref) { g_object_ref(m_ptr.get()); g_object_remove_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this); ToggleQueue::get_default()->cancel(this); wrapped_gobj_toggle_notify(this, m_ptr, TRUE); m_uses_toggle_ref = false; } if (GjsContextPrivate::from_current_context()->is_owner_thread()) discard_wrapper(); } void ObjectInstance::remove_wrapped_gobjects_if( const ObjectInstance::Predicate& predicate, const ObjectInstance::Action& action) { // Note: remove_if() does not actually remove elements, just reorders them // and returns a start iterator of elements to remove s_wrapped_gobject_list.erase( std::remove_if(s_wrapped_gobject_list.begin(), s_wrapped_gobject_list.end(), ([predicate, action](ObjectInstance* link) { if (predicate(link)) { action(link); return true; } return false; })), s_wrapped_gobject_list.end()); } /* * ObjectInstance::context_dispose_notify: * * Callback called when the #GjsContext is disposed. It just calls * handle_context_dispose() on every ObjectInstance. */ void ObjectInstance::context_dispose_notify(void*, GObject* where_the_object_was [[maybe_unused]]) { std::for_each(s_wrapped_gobject_list.begin(), s_wrapped_gobject_list.end(), std::mem_fn(&ObjectInstance::handle_context_dispose)); } /* * ObjectInstance::handle_context_dispose: * * Called on each existing ObjectInstance when the #GjsContext is disposed. */ void ObjectInstance::handle_context_dispose(void) { if (wrapper_is_rooted()) { debug_lifecycle("Was rooted, but unrooting due to GjsContext dispose"); discard_wrapper(); } } void ObjectInstance::toggle_down(void) { debug_lifecycle("Toggle notify DOWN"); /* Change to weak ref so the wrapper-wrappee pair can be * collected by the GC */ if (wrapper_is_rooted()) { debug_lifecycle("Unrooting wrapper"); GjsContextPrivate* gjs = GjsContextPrivate::from_current_context(); switch_to_unrooted(gjs->context()); /* During a GC, the collector asks each object which other * objects that it wants to hold on to so if there's an entire * section of the heap graph that's not connected to anything * else, and not reachable from the root set, then it can be * trashed all at once. * * GObjects, however, don't work like that, there's only a * reference count but no notion of who owns the reference so, * a JS object that's wrapping a GObject is unconditionally held * alive as long as the GObject has >1 references. * * Since we cannot know how many more wrapped GObjects are going * be marked for garbage collection after the owner is destroyed, * always queue a garbage collection when a toggle reference goes * down. */ if (!gjs->destroying()) gjs->schedule_gc(); } } void ObjectInstance::toggle_up(void) { if (G_UNLIKELY(!m_ptr || m_gobj_disposed || m_gobj_finalized)) { if (m_ptr) { gjs_debug_lifecycle( GJS_DEBUG_GOBJECT, "Avoid to toggle up a wrapper for a %s object: %p (%s)", m_gobj_finalized ? "finalized" : "disposed", m_ptr.get(), g_type_name(gtype())); } else { gjs_debug_lifecycle( GJS_DEBUG_GOBJECT, "Avoid to toggle up a wrapper for a released %s object (%p)", g_type_name(gtype()), this); } return; } /* We need to root the JSObject associated with the passed in GObject so it * doesn't get garbage collected (and lose any associated javascript state * such as custom properties). */ if (!has_wrapper()) /* Object already GC'd */ return; debug_lifecycle("Toggle notify UP"); /* Change to strong ref so the wrappee keeps the wrapper alive * in case the wrapper has data in it that the app cares about */ if (!wrapper_is_rooted()) { // FIXME: thread the context through somehow. Maybe by looking up the // realm that obj belongs to. debug_lifecycle("Rooting wrapper"); auto* cx = GjsContextPrivate::from_current_context()->context(); switch_to_rooted(cx); } } static void toggle_handler(ObjectInstance* self, ToggleQueue::Direction direction) { switch (direction) { case ToggleQueue::UP: self->toggle_up(); break; case ToggleQueue::DOWN: self->toggle_down(); break; default: g_assert_not_reached(); } } void ObjectInstance::wrapped_gobj_toggle_notify(void* instance, GObject*, gboolean is_last_ref) { bool is_main_thread; bool toggle_up_queued, toggle_down_queued; auto* self = static_cast(instance); GjsContextPrivate* gjs = GjsContextPrivate::from_current_context(); if (gjs->destroying()) { /* Do nothing here - we're in the process of disassociating * the objects. */ return; } /* We only want to touch javascript from one thread. * If we're not in that thread, then we need to defer processing * to it. * In case we're toggling up (and thus rooting the JS object) we * also need to take care if GC is running. The marking side * of it is taken care by JS::Heap, which we use in GjsMaybeOwned, * so we're safe. As for sweeping, it is too late: the JS object * is dead, and attempting to keep it alive would soon crash * the process. Plus, if we touch the JSAPI from another thread, libmozjs * aborts in most cases when in debug mode. * Thus, we drain the toggle queue when GC starts, in order to * prevent this from happening. * In practice, a toggle up during JS finalize can only happen * for temporary refs/unrefs of objects that are garbage anyway, * because JS code is never invoked while the finalizers run * and C code needs to clean after itself before it returns * from dispose()/finalize(). * On the other hand, toggling down is a lot simpler, because * we're creating more garbage. So we just unroot the object, make it a * weak pointer, and wait for the next GC cycle. * * Note that one would think that toggling up only happens * in the main thread (because toggling up is the result of * the JS object, previously visible only to JS code, becoming * visible to the refcounted C world), but because of weird * weak singletons like g_bus_get_sync() objects can see toggle-ups * from different threads too. */ is_main_thread = gjs->is_owner_thread(); auto toggle_queue = ToggleQueue::get_default(); std::tie(toggle_down_queued, toggle_up_queued) = toggle_queue->is_queued(self); bool anything_queued = toggle_up_queued || toggle_down_queued; if (is_last_ref) { /* We've transitions from 2 -> 1 references, * The JSObject is rooted and we need to unroot it so it * can be garbage collected */ if (is_main_thread && !anything_queued) { self->toggle_down(); } else { toggle_queue->enqueue(self, ToggleQueue::DOWN, toggle_handler); } } else { /* We've transitioned from 1 -> 2 references. * * The JSObject associated with the gobject is not rooted, * but it needs to be. We'll root it. */ if (is_main_thread && !anything_queued && !JS::RuntimeHeapIsCollecting()) { self->toggle_up(); } else { toggle_queue->enqueue(self, ToggleQueue::UP, toggle_handler); } } } void ObjectInstance::release_native_object(void) { static GType gdksurface_type = 0; discard_wrapper(); if (m_gobj_finalized) { g_critical( "Object %p of type %s has been finalized while it was still " "owned by gjs, this is due to invalid memory management.", m_ptr.get(), g_type_name(gtype())); m_ptr.release(); return; } if (m_ptr) gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Releasing native object %s %p", g_type_name(gtype()), m_ptr.get()); if (m_gobj_disposed) ignore_gobject_finalization(); if (m_uses_toggle_ref && !m_gobj_disposed) { g_object_remove_toggle_ref(m_ptr.release(), wrapped_gobj_toggle_notify, this); return; } // Unref the object. Handle any special cases for destruction here if (m_ptr->ref_count == 1) { // Quickest way to check for GdkSurface if Gdk has been loaded? // surface_type may be 0 if Gdk not loaded. The type may be a private // type and not have introspection info. if (!gdksurface_type) gdksurface_type = g_type_from_name("GdkSurface"); if (gdksurface_type && g_type_is_a(gtype(), gdksurface_type)) { GObject* ptr = m_ptr.release(); // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6289 GjsAutoObjectInfo surface_info = g_irepository_find_by_gtype(nullptr, gdksurface_type); g_assert(surface_info && "Could not find introspected GdkSurface info"); GjsAutoFunctionInfo destroy_func = g_object_info_find_method(surface_info, "destroy"); GIArgument destroy_args; gjs_arg_set(&destroy_args, ptr); GIArgument unused_return; GjsAutoError err; if (!g_function_info_invoke(destroy_func, &destroy_args, 1, nullptr, 0, &unused_return, err.out())) g_critical("Error destroying GdkSurface %p: %s", ptr, err->message); } } m_ptr = nullptr; } /* At shutdown, we need to ensure we've cleared the context of any * pending toggle references. */ void gjs_object_clear_toggles(void) { ToggleQueue::get_default()->handle_all_toggles(toggle_handler); } void gjs_object_shutdown_toggle_queue(void) { ToggleQueue::get_default()->shutdown(); } /* * ObjectInstance::prepare_shutdown: * * Called when the #GjsContext is disposed, in order to release all GC roots of * JSObjects that are held by GObjects. */ void ObjectInstance::prepare_shutdown(void) { /* We iterate over all of the objects, breaking the JS <-> C * association. We avoid the potential recursion implied in: * toggle ref removal -> gobj dispose -> toggle ref notify * by emptying the toggle queue earlier in the shutdown sequence. */ ObjectInstance::remove_wrapped_gobjects_if( std::mem_fn(&ObjectInstance::wrapper_is_rooted), std::mem_fn(&ObjectInstance::release_native_object)); } ObjectInstance::ObjectInstance(ObjectPrototype* prototype, JS::HandleObject object) : GIWrapperInstance(prototype, object), m_wrapper_finalized(false), m_gobj_disposed(false), m_gobj_finalized(false), m_uses_toggle_ref(false) { GTypeQuery query; g_type_query(gtype(), &query); if (G_LIKELY(query.type)) JS::AddAssociatedMemory(object, query.instance_size, MemoryUse::GObjectInstanceStruct); GJS_INC_COUNTER(object_instance); } ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype) : GIWrapperPrototype(info, gtype) { g_type_class_ref(gtype); GJS_INC_COUNTER(object_prototype); } /* * ObjectInstance::update_heap_wrapper_weak_pointers: * * Private callback, called after the JS engine finishes garbage collection, and * notifies when weak pointers need to be either moved or swept. */ void ObjectInstance::update_heap_wrapper_weak_pointers(JSTracer* trc, JS::Compartment*, void*) { gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Weak pointer update callback, " "%zu wrapped GObject(s) to examine", ObjectInstance::num_wrapped_gobjects()); // Take a lock on the queue till we're done with it, so that we don't // risk that another thread will queue something else while sweeping auto locked_queue = ToggleQueue::get_default(); ObjectInstance::remove_wrapped_gobjects_if( [&trc](ObjectInstance* instance) -> bool { return instance->weak_pointer_was_finalized(trc); }, std::mem_fn(&ObjectInstance::disassociate_js_gobject)); s_wrapped_gobject_list.shrink_to_fit(); } bool ObjectInstance::weak_pointer_was_finalized(JSTracer* trc) { if (has_wrapper() && !wrapper_is_rooted()) { bool toggle_down_queued, toggle_up_queued; auto toggle_queue = ToggleQueue::get_default(); std::tie(toggle_down_queued, toggle_up_queued) = toggle_queue->is_queued(this); if (!toggle_down_queued && toggle_up_queued) return false; if (!update_after_gc(trc)) return false; if (toggle_down_queued) toggle_queue->cancel(this); /* Ouch, the JS object is dead already. Disassociate the * GObject and hope the GObject dies too. (Remove it from * the weak pointer list first, since the disassociation * may also cause it to be erased.) */ debug_lifecycle("Found GObject weak pointer whose JS wrapper is about " "to be finalized"); return true; } return false; } /* * ObjectInstance::ensure_weak_pointer_callback: * * Private method called when adding a weak pointer for the first time. */ void ObjectInstance::ensure_weak_pointer_callback(JSContext* cx) { if (!s_weak_pointer_callback) { JS_AddWeakPointerCompartmentCallback( cx, &ObjectInstance::update_heap_wrapper_weak_pointers, nullptr); s_weak_pointer_callback = true; } } void ObjectInstance::associate_js_gobject(JSContext *context, JS::HandleObject object, GObject *gobj) { g_assert(!wrapper_is_rooted()); m_uses_toggle_ref = false; m_ptr = gobj; set_object_qdata(); m_wrapper = object; m_gobj_disposed = !!g_object_get_qdata(gobj, ObjectBase::disposed_quark()); ensure_weak_pointer_callback(context); link(); if (!G_UNLIKELY(m_gobj_disposed)) g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this); } void ObjectInstance::ensure_uses_toggle_ref(JSContext* cx) { if (m_uses_toggle_ref) return; if (!check_gobject_disposed_or_finalized("add toggle reference on")) return; debug_lifecycle("Switching object instance to toggle ref"); g_assert(!wrapper_is_rooted()); /* OK, here is where things get complicated. We want the * wrapped gobj to keep the JSObject* wrapper alive, because * people might set properties on the JSObject* that they care * about. Therefore, whenever the refcount on the wrapped gobj * is >1, i.e. whenever something other than the wrapper is * referencing the wrapped gobj, the wrapped gobj has a strong * ref (gc-roots the wrapper). When the refcount on the * wrapped gobj is 1, then we change to a weak ref to allow * the wrapper to be garbage collected (and thus unref the * wrappee). */ m_uses_toggle_ref = true; switch_to_rooted(cx); g_object_add_toggle_ref(m_ptr, wrapped_gobj_toggle_notify, this); /* We now have both a ref and a toggle ref, we only want the toggle ref. * This may immediately remove the GC root we just added, since refcount * may drop to 1. */ g_object_unref(m_ptr); } static void invalidate_closure_vector(std::vector* closures, void* data, GClosureNotify notify_func) { g_assert(closures); g_assert(notify_func); for (auto it = closures->begin(); it != closures->end();) { // This will also free the closure data, through the closure // invalidation mechanism, but adding a temporary reference to // ensure that the closure is still valid when calling invalidation // notify callbacks GjsAutoGClosure closure(*it, GjsAutoTakeOwnership()); it = closures->erase(it); // Only call the invalidate notifiers that won't touch this vector g_closure_remove_invalidate_notifier(closure, data, notify_func); g_closure_invalidate(closure); } g_assert(closures->empty()); } // Note: m_wrapper (the JS object) may already be null when this is called, if // it was finalized while the GObject was toggled down. void ObjectInstance::disassociate_js_gobject(void) { bool had_toggle_down, had_toggle_up; std::tie(had_toggle_down, had_toggle_up) = ToggleQueue::get_default()->cancel(this); if (had_toggle_up && !had_toggle_down) { g_error( "JS object wrapper for GObject %p (%s) is being released while " "toggle references are still pending.", m_ptr.get(), type_name()); } if (!m_gobj_disposed) g_object_weak_unref(m_ptr.get(), wrapped_gobj_dispose_notify, this); if (!m_gobj_finalized) { /* Fist, remove the wrapper pointer from the wrapped GObject */ unset_object_qdata(); } /* Now release all the resources the current wrapper has */ invalidate_closures(); release_native_object(); /* Mark that a JS object once existed, but it doesn't any more */ m_wrapper_finalized = true; } bool ObjectInstance::init_impl(JSContext* context, const JS::CallArgs& args, JS::HandleObject object) { g_assert(gtype() != G_TYPE_NONE); if (args.length() > 1 && !JS::WarnUTF8(context, "Too many arguments to the constructor of %s: expected " "1, got %u", name(), args.length())) return false; GjsAutoTypeClass object_class(gtype()); std::vector names; AutoGValueVector values; if (args.length() > 0 && !args[0].isUndefined()) { if (!args[0].isObject()) { gjs_throw(context, "Argument to the constructor of %s should be a plain JS " "object with properties to set", name()); return false; } JS::RootedObject props(context, &args[0].toObject()); if (ObjectInstance::typecheck(context, props, nullptr, G_TYPE_NONE, GjsTypecheckNoThrow{})) { gjs_throw(context, "Argument to the constructor of %s should be a plain JS " "object with properties to set", name()); return false; } if (!m_proto->props_to_g_parameters(context, object_class, props, &names, &values)) return false; } if (G_TYPE_IS_ABSTRACT(gtype())) { gjs_throw(context, "Cannot instantiate abstract type %s", g_type_name(gtype())); return false; } // Mark this object in the construction stack, it will be popped in // gjs_object_custom_init() in gi/gobject.cpp. if (is_custom_js_class()) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (!gjs->object_init_list().append(object)) { JS_ReportOutOfMemory(context); return false; } } g_assert(names.size() == values.size()); GObject* gobj = g_object_new_with_properties(gtype(), values.size(), names.data(), values.data()); ObjectInstance *other_priv = ObjectInstance::for_gobject(gobj); if (other_priv && other_priv->m_wrapper != object.get()) { /* g_object_new_with_properties() returned an object that's already * tracked by a JS object. * * This typically occurs in one of two cases: * - This object is a singleton like IBus.IBus * - This object passed itself to JS before g_object_new_* returned * * In these cases, return the existing JS wrapper object instead * of creating a new one. * * 'object' has a value that was originally created by * JS_NewObjectForConstructor in GJS_NATIVE_CONSTRUCTOR_PRELUDE, but * we're not actually using it, so just let it get collected. Avoiding * this would require a non-trivial amount of work. * */ bool toggle_ref_added = false; if (!m_uses_toggle_ref) { other_priv->ensure_uses_toggle_ref(context); toggle_ref_added = m_uses_toggle_ref; } args.rval().setObject(*other_priv->m_wrapper.get()); if (toggle_ref_added) g_clear_object(&gobj); /* We already own a reference */ return true; } if (G_IS_INITIALLY_UNOWNED(gobj) && !g_object_is_floating(gobj)) { /* GtkWindow does not return a ref to caller of g_object_new. * Need a flag in gobject-introspection to tell us this. */ gjs_debug(GJS_DEBUG_GOBJECT, "Newly-created object is initially unowned but we did not get the " "floating ref, probably GtkWindow, using hacky workaround"); g_object_ref(gobj); } else if (g_object_is_floating(gobj)) { g_object_ref_sink(gobj); } else { /* we should already have a ref */ } if (!m_ptr) associate_js_gobject(context, object, gobj); TRACE(GJS_OBJECT_WRAPPER_NEW(this, m_ptr, ns(), name())); args.rval().setObject(*object); return true; } // See GIWrapperBase::constructor() bool ObjectInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& argv) { JS::RootedValue initer(context); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); const auto& new_target = argv.newTarget(); bool has_gtype; g_assert(new_target.isObject() && "new.target needs to be an object"); JS::RootedObject rooted_target(context, &new_target.toObject()); if (!JS_HasOwnPropertyById(context, rooted_target, gjs->atoms().gtype(), &has_gtype)) return false; if (!has_gtype) { gjs_throw(context, "Tried to construct an object without a GType; are " "you using GObject.registerClass() when inheriting " "from a GObject type?"); return false; } return gjs_object_require_property(context, object, "GObject instance", gjs->atoms().init(), &initer) && gjs->call_function(object, initer, argv, argv.rval()); } void ObjectInstance::trace_impl(JSTracer* tracer) { for (GClosure *closure : m_closures) Gjs::Closure::for_gclosure(closure)->trace(tracer); } void ObjectPrototype::trace_impl(JSTracer* tracer) { m_field_cache.trace(tracer); m_unresolvable_cache.trace(tracer); for (GClosure* closure : m_vfuncs) Gjs::Closure::for_gclosure(closure)->trace(tracer); } void ObjectInstance::finalize_impl(JS::GCContext* gcx, JSObject* obj) { GTypeQuery query; g_type_query(gtype(), &query); if (G_LIKELY(query.type)) JS::RemoveAssociatedMemory(obj, query.instance_size, MemoryUse::GObjectInstanceStruct); GIWrapperInstance::finalize_impl(gcx, obj); } ObjectInstance::~ObjectInstance() { TRACE(GJS_OBJECT_WRAPPER_FINALIZE(this, m_ptr, ns(), name())); invalidate_closures(); // Do not keep the queue locked here, as we may want to leave the other // threads to queue toggle events till we're owning the GObject so that // eventually (once the toggle reference is finally removed) we can be // sure that no other toggle event will target this (soon dead) wrapper. bool had_toggle_up; bool had_toggle_down; std::tie(had_toggle_down, had_toggle_up) = ToggleQueue::get_default()->cancel(this); /* GObject is not already freed */ if (m_ptr) { if (!had_toggle_up && had_toggle_down) { g_error( "Finalizing wrapper for an object that's scheduled to be " "unrooted: %s.%s\n", ns(), name()); } if (!m_gobj_disposed) g_object_weak_unref(m_ptr, wrapped_gobj_dispose_notify, this); if (!m_gobj_finalized) unset_object_qdata(); bool was_using_toggle_refs = m_uses_toggle_ref; release_native_object(); if (was_using_toggle_refs) { // We need to cancel again, to be sure that no other thread added // another toggle reference before we were removing the last one. ToggleQueue::get_default()->cancel(this); } } if (wrapper_is_rooted()) { /* This happens when the refcount on the object is still >1, * for example with global objects GDK never frees like GdkDisplay, * when we close down the JS runtime. */ gjs_debug(GJS_DEBUG_GOBJECT, "Wrapper was finalized despite being kept alive, has refcount >1"); debug_lifecycle("Unrooting object"); discard_wrapper(); } unlink(); GJS_DEC_COUNTER(object_instance); } ObjectPrototype::~ObjectPrototype() { invalidate_closure_vector(&m_vfuncs, this, &vfunc_invalidated_notify); g_type_class_unref(g_type_class_peek(m_gtype)); GJS_DEC_COUNTER(object_prototype); } static JSObject* gjs_lookup_object_constructor_from_info(JSContext* context, GIBaseInfo* info, GType gtype) { g_return_val_if_fail( !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL); JS::RootedObject in_object(context); const char *constructor_name; if (info) { in_object = gjs_lookup_namespace_object(context, info); constructor_name = g_base_info_get_name(info); } else { in_object = gjs_lookup_private_namespace(context); constructor_name = g_type_name(gtype); } if (G_UNLIKELY (!in_object)) return NULL; bool found; if (!JS_HasProperty(context, in_object, constructor_name, &found)) return NULL; JS::RootedValue value(context); if (found && !JS_GetProperty(context, in_object, constructor_name, &value)) return NULL; JS::RootedObject constructor(context); if (value.isUndefined()) { /* In case we're looking for a private type, and we don't find it, we need to define it first. */ JS::RootedObject ignored(context); if (!ObjectPrototype::define_class(context, in_object, nullptr, gtype, nullptr, 0, &constructor, &ignored)) return nullptr; } else { if (G_UNLIKELY (!value.isObject())) return NULL; constructor = &value.toObject(); } g_assert(constructor); return constructor; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_object_prototype_from_info(JSContext* context, GIBaseInfo* info, GType gtype) { g_return_val_if_fail( !info || GI_IS_OBJECT_INFO(info) || GI_IS_INTERFACE_INFO(info), NULL); JS::RootedObject constructor(context, gjs_lookup_object_constructor_from_info(context, info, gtype)); if (G_UNLIKELY(!constructor)) return NULL; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject prototype(context); if (!gjs_object_require_property(context, constructor, "constructor object", atoms.prototype(), &prototype)) return NULL; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject * gjs_lookup_object_prototype(JSContext *context, GType gtype) { GjsAutoObjectInfo info = gjs_lookup_gtype(nullptr, gtype); return gjs_lookup_object_prototype_from_info(context, info, gtype); } // Retrieves a GIFieldInfo for a field named @key. This is for use in // field_getter_impl() and field_setter_not_impl(), where the field info *must* // have been cached previously in resolve_impl() on this ObjectPrototype or one // of its parent ObjectPrototypes. This will fail an assertion if there is no // cached field info. // // The caller does not own the return value, and it can never be null. GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx, JS::HandleString key) { if (!info()) { // Custom JS classes can't have fields, and fields on internal classes // are not available. We must be looking up a field on a // GObject-introspected parent. GType parent_gtype = g_type_parent(m_gtype); g_assert(parent_gtype != G_TYPE_INVALID && "Custom JS class must have parent"); ObjectPrototype* parent_proto = ObjectPrototype::for_gtype(parent_gtype); if (!parent_proto) { JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, parent_gtype)); parent_proto = ObjectPrototype::for_js(cx, proto); } g_assert(parent_proto && "Custom JS class's parent must have been accessed in JS"); return parent_proto->lookup_cached_field_info(cx, key); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Looking up cached field info for %s in '%s' prototype", gjs_debug_string(key).c_str(), g_type_name(m_gtype)); auto entry = m_field_cache.lookupForAdd(key); if (entry) return entry->value().get(); // We must be looking up a field defined on a parent. Look up the prototype // object via its GIObjectInfo. GjsAutoObjectInfo parent_info = g_object_info_get_parent(m_info); JS::RootedObject parent_proto(cx, gjs_lookup_object_prototype_from_info( cx, parent_info, G_TYPE_INVALID)); ObjectPrototype* parent = ObjectPrototype::for_js(cx, parent_proto); return parent->lookup_cached_field_info(cx, key); } bool ObjectInstance::associate_closure(JSContext* cx, GClosure* closure) { if (!is_prototype()) to_instance()->ensure_uses_toggle_ref(cx); g_assert(std::find(m_closures.begin(), m_closures.end(), closure) == m_closures.end() && "This closure was already associated with this object"); /* This is a weak reference, and will be cleared when the closure is * invalidated */ m_closures.push_back(closure); g_closure_add_invalidate_notifier( closure, this, &ObjectInstance::closure_invalidated_notify); return true; } void ObjectInstance::closure_invalidated_notify(void* data, GClosure* closure) { // This callback should *only* touch m_closures auto* priv = static_cast(data); Gjs::remove_one_from_unsorted_vector(&priv->m_closures, closure); } void ObjectInstance::invalidate_closures() { invalidate_closure_vector(&m_closures, this, &closure_invalidated_notify); m_closures.shrink_to_fit(); } bool ObjectBase::connect(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "connect to signals")) return false; return priv->to_instance()->connect_impl(cx, args, false); } bool ObjectBase::connect_after(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "connect to signals")) return false; return priv->to_instance()->connect_impl(cx, args, true); } bool ObjectBase::connect_object(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "connect to signals")) return false; return priv->to_instance()->connect_impl(cx, args, false, true); } bool ObjectInstance::connect_impl(JSContext* context, const JS::CallArgs& args, bool after, bool object) { gulong id; guint signal_id; GQuark signal_detail; const char* func_name = object ? "connect_object" : after ? "connect_after" : "connect"; gjs_debug_gsignal("connect obj %p priv %p", m_wrapper.get(), this); if (!check_gobject_disposed_or_finalized("connect to any signal on")) { args.rval().setInt32(0); return true; } JS::UniqueChars signal_name; JS::RootedObject callback(context); JS::RootedObject associate_obj(context); GConnectFlags flags; if (object) { if (!gjs_parse_call_args(context, func_name, args, "sooi", "signal name", &signal_name, "callback", &callback, "gobject", &associate_obj, "connect_flags", &flags)) return false; if (flags & G_CONNECT_SWAPPED) { gjs_throw(context, "Unsupported connect flag G_CONNECT_SWAPPED"); return false; } after = flags & G_CONNECT_AFTER; } else { if (!gjs_parse_call_args(context, func_name, args, "so", "signal name", &signal_name, "callback", &callback)) return false; } std::string dynamicString = format_name() + '.' + func_name + "('" + signal_name.get() + "')"; AutoProfilerLabel label(context, "", dynamicString.c_str()); if (!JS::IsCallable(callback)) { gjs_throw(context, "second arg must be a callback"); return false; } if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, true)) { gjs_throw(context, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } GClosure* closure = Gjs::Closure::create_for_signal( context, callback, "signal callback", signal_id); if (closure == NULL) return false; if (associate_obj.get() != nullptr) { ObjectInstance* obj = ObjectInstance::for_js(context, associate_obj); if (!obj) return false; if (!obj->associate_closure(context, closure)) return false; } else if (!associate_closure(context, closure)) { return false; } id = g_signal_connect_closure_by_id(m_ptr, signal_id, signal_detail, closure, after); args.rval().setDouble(id); return true; } bool ObjectBase::emit(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "emit signal")) return false; return priv->to_instance()->emit_impl(cx, args); } bool ObjectInstance::emit_impl(JSContext *context, const JS::CallArgs& argv) { guint signal_id; GQuark signal_detail; GSignalQuery signal_query; unsigned int i; gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this, argv.length()); if (!check_gobject_finalized("emit any signal on")) { argv.rval().setUndefined(); return true; } JS::UniqueChars signal_name; if (!gjs_parse_call_args(context, "emit", argv, "!s", "signal name", &signal_name)) return false; std::string dynamicString = format_name() + "emit('" + signal_name.get() + "')"; AutoProfilerLabel label(context, "", dynamicString.c_str()); if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, false)) { gjs_throw(context, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } g_signal_query(signal_id, &signal_query); if ((argv.length() - 1) != signal_query.n_params) { gjs_throw(context, "Signal '%s' on %s requires %d args got %d", signal_name.get(), type_name(), signal_query.n_params, argv.length() - 1); return false; } AutoGValueVector instance_and_args; instance_and_args.reserve(signal_query.n_params + 1); std::vector args_to_steal; Gjs::AutoGValue& instance = instance_and_args.emplace_back(gtype()); g_value_set_instance(&instance, m_ptr); for (i = 0; i < signal_query.n_params; ++i) { GType gtype = signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE; Gjs::AutoGValue& value = instance_and_args.emplace_back(gtype); if ((signal_query.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0) { if (!gjs_value_to_g_value_no_copy(context, argv[i + 1], &value)) return false; } else { if (!gjs_value_to_g_value(context, argv[i + 1], &value)) return false; } if (!ObjectBase::info()) continue; GjsAutoSignalInfo signal_info = g_object_info_find_signal( ObjectBase::info(), signal_query.signal_name); if (!signal_info) continue; GjsAutoArgInfo arg_info = g_callable_info_get_arg(signal_info, i); if (g_arg_info_get_ownership_transfer(arg_info) != GI_TRANSFER_NOTHING) { // FIXME(3v1n0): As it happens in many places in gjs, we can't track // (yet) containers content, so in case of transfer container we // can only leak. args_to_steal.push_back(&value); } } if (signal_query.return_type == G_TYPE_NONE) { g_signal_emitv(instance_and_args.data(), signal_id, signal_detail, nullptr); argv.rval().setUndefined(); std::for_each(args_to_steal.begin(), args_to_steal.end(), [](Gjs::AutoGValue* value) { value->steal(); }); return true; } GType gtype = signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE; Gjs::AutoGValue rvalue(gtype); g_signal_emitv(instance_and_args.data(), signal_id, signal_detail, &rvalue); std::for_each(args_to_steal.begin(), args_to_steal.end(), [](Gjs::AutoGValue* value) { value->steal(); }); return gjs_value_from_g_value(context, argv.rval(), &rvalue); } bool ObjectInstance::signal_match_arguments_from_object( JSContext* cx, JS::HandleObject match_obj, GSignalMatchType* mask_out, unsigned* signal_id_out, GQuark* detail_out, JS::MutableHandleObject callable_out) { g_assert(mask_out && signal_id_out && detail_out && "forgot out parameter"); int mask = 0; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); bool has_id; unsigned signal_id = 0; if (!JS_HasOwnPropertyById(cx, match_obj, atoms.signal_id(), &has_id)) return false; if (has_id) { mask |= G_SIGNAL_MATCH_ID; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.signal_id(), &value)) return false; JS::UniqueChars signal_name = gjs_string_to_utf8(cx, value); if (!signal_name) return false; signal_id = g_signal_lookup(signal_name.get(), gtype()); } bool has_detail; GQuark detail = 0; if (!JS_HasOwnPropertyById(cx, match_obj, atoms.detail(), &has_detail)) return false; if (has_detail) { mask |= G_SIGNAL_MATCH_DETAIL; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.detail(), &value)) return false; JS::UniqueChars detail_string = gjs_string_to_utf8(cx, value); if (!detail_string) return false; detail = g_quark_from_string(detail_string.get()); } bool has_func; JS::RootedObject callable(cx); if (!JS_HasOwnPropertyById(cx, match_obj, atoms.func(), &has_func)) return false; if (has_func) { mask |= G_SIGNAL_MATCH_CLOSURE; JS::RootedValue value(cx); if (!JS_GetPropertyById(cx, match_obj, atoms.func(), &value)) return false; if (!value.isObject() || !JS::IsCallable(&value.toObject())) { gjs_throw(cx, "'func' property must be a function"); return false; } callable = &value.toObject(); } if (!has_id && !has_detail && !has_func) { gjs_throw(cx, "Must specify at least one of signalId, detail, or func"); return false; } *mask_out = GSignalMatchType(mask); if (has_id) *signal_id_out = signal_id; if (has_detail) *detail_out = detail; if (has_func) callable_out.set(callable); return true; } bool ObjectBase::signal_find(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "find signal")) return false; return priv->to_instance()->signal_find_impl(cx, args); } bool ObjectInstance::signal_find_impl(JSContext* cx, const JS::CallArgs& args) { gjs_debug_gsignal("[Gi.signal_find_symbol]() obj %p priv %p argc %d", m_wrapper.get(), this, args.length()); if (!check_gobject_finalized("find any signal on")) { args.rval().setInt32(0); return true; } JS::RootedObject match(cx); if (!gjs_parse_call_args(cx, "[Gi.signal_find_symbol]", args, "o", "match", &match)) return false; GSignalMatchType mask; unsigned signal_id; GQuark detail; JS::RootedObject callable(cx); if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id, &detail, &callable)) return false; uint64_t handler = 0; if (!callable) { handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, nullptr, nullptr, nullptr); } else { for (GClosure* candidate : m_closures) { if (Gjs::Closure::for_gclosure(candidate)->callable() == callable) { handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, candidate, nullptr, nullptr); if (handler != 0) break; } } } args.rval().setNumber(static_cast(handler)); return true; } template static inline const char* signal_match_to_action_name(); template <> inline const char* signal_match_to_action_name<&g_signal_handlers_block_matched>() { return "block"; } template <> inline const char* signal_match_to_action_name<&g_signal_handlers_unblock_matched>() { return "unblock"; } template <> inline const char* signal_match_to_action_name<&g_signal_handlers_disconnect_matched>() { return "disconnect"; } template bool ObjectBase::signals_action(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); const std::string action_name = signal_match_to_action_name(); if (!priv->check_is_instance(cx, (action_name + " signal").c_str())) return false; return priv->to_instance()->signals_action_impl(cx, args); } template bool ObjectInstance::signals_action_impl(JSContext* cx, const JS::CallArgs& args) { const std::string action_name = signal_match_to_action_name(); const std::string action_tag = "[Gi.signals_" + action_name + "_symbol]"; gjs_debug_gsignal("[%s]() obj %p priv %p argc %d", action_tag.c_str(), m_wrapper.get(), this, args.length()); if (!check_gobject_finalized((action_name + " any signal on").c_str())) { args.rval().setInt32(0); return true; } JS::RootedObject match(cx); if (!gjs_parse_call_args(cx, action_tag.c_str(), args, "o", "match", &match)) { return false; } GSignalMatchType mask; unsigned signal_id; GQuark detail; JS::RootedObject callable(cx); if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id, &detail, &callable)) { return false; } unsigned n_matched = 0; if (!callable) { n_matched = MatchFunc(m_ptr, mask, signal_id, detail, nullptr, nullptr, nullptr); } else { std::vector candidates; for (GClosure* candidate : m_closures) { if (Gjs::Closure::for_gclosure(candidate)->callable() == callable) candidates.push_back(candidate); } for (GClosure* candidate : candidates) { n_matched += MatchFunc(m_ptr, mask, signal_id, detail, candidate, nullptr, nullptr); } } args.rval().setNumber(n_matched); return true; } bool ObjectBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); const char* kind = ObjectBase::DEBUG_TAG; if (!priv->is_prototype()) kind = priv->to_instance()->to_string_kind(); return gjs_wrapper_to_string_func( cx, obj, kind, priv->info(), priv->gtype(), priv->is_prototype() ? nullptr : priv->to_instance()->ptr(), args.rval()); } /* * ObjectInstance::to_string_kind: * * ObjectInstance shows a "disposed" marker in its toString() method if the * wrapped GObject has already been disposed. */ const char* ObjectInstance::to_string_kind(void) const { if (m_gobj_finalized) return "object (FINALIZED)"; return m_gobj_disposed ? "object (DISPOSED)" : "object"; } /* * ObjectBase::init_gobject: * * This is named "init_gobject()" but corresponds to "_init()" in JS. The reason * for the name is that an "init()" method is used within SpiderMonkey to * indicate fallible initialization that must be done before an object can be * used, which is not the case here. */ bool ObjectBase::init_gobject(JSContext* context, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(context, argc, vp, argv, obj, ObjectBase, priv); if (!priv->check_is_instance(context, "initialize")) return false; std::string dynamicString = priv->format_name() + "._init"; AutoProfilerLabel label(context, "", dynamicString.c_str()); return priv->to_instance()->init_impl(context, argv, obj); } // clang-format off const struct JSClassOps ObjectBase::class_ops = { &ObjectBase::add_property, nullptr, // deleteProperty nullptr, // enumerate &ObjectBase::new_enumerate, &ObjectBase::resolve, nullptr, // mayResolve &ObjectBase::finalize, NULL, NULL, &ObjectBase::trace, }; const struct JSClass ObjectBase::klass = { "GObject_Object", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, &ObjectBase::class_ops }; JSFunctionSpec ObjectBase::proto_methods[] = { JS_FN("_init", &ObjectBase::init_gobject, 0, 0), JS_FN("connect", &ObjectBase::connect, 0, 0), JS_FN("connect_after", &ObjectBase::connect_after, 0, 0), JS_FN("connect_object", &ObjectBase::connect_object, 0, 0), JS_FN("emit", &ObjectBase::emit, 0, 0), JS_FS_END }; JSPropertySpec ObjectBase::proto_properties[] = { JS_STRING_SYM_PS(toStringTag, "GObject_Object", JSPROP_READONLY), JS_PS_END}; // clang-format on // Override of GIWrapperPrototype::get_parent_proto() bool ObjectPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const { GType parent_type = g_type_parent(gtype()); if (parent_type == G_TYPE_INVALID) { proto.set(nullptr); return true; } JSObject* prototype = gjs_lookup_object_prototype(cx, parent_type); if (!prototype) return false; proto.set(prototype); return true; } bool ObjectPrototype::get_parent_constructor( JSContext* cx, JS::MutableHandleObject constructor) const { GType parent_type = g_type_parent(gtype()); if (parent_type == G_TYPE_INVALID) { constructor.set(nullptr); return true; } JS::RootedValue v_constructor(cx); if (!gjs_lookup_object_constructor(cx, parent_type, &v_constructor)) return false; g_assert(v_constructor.isObject() && "gjs_lookup_object_constructor() should always produce an object"); constructor.set(&v_constructor.toObject()); return true; } void ObjectPrototype::set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes) { if (interface_gtypes) { for (uint32_t n = 0; n < n_interface_gtypes; n++) { m_interface_gtypes.push_back(interface_gtypes[n]); } } } /* * ObjectPrototype::define_class: * @in_object: Object where the constructor is stored, typically a repo object. * @info: Introspection info for the GObject class. * @gtype: #GType for the GObject class. * @constructor: Return location for the constructor object. * @prototype: Return location for the prototype object. * * Define a GObject class constructor and prototype, including all the * necessary methods and properties that are not introspected. Provides the * constructor and prototype objects as out parameters, for convenience * elsewhere. */ bool ObjectPrototype::define_class( JSContext* context, JS::HandleObject in_object, GIObjectInfo* info, GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { ObjectPrototype* priv = ObjectPrototype::create_class( context, in_object, info, gtype, constructor, prototype); if (!priv) return false; priv->set_interfaces(interface_gtypes, n_interface_gtypes); JS::RootedObject parent_constructor(context); if (!priv->get_parent_constructor(context, &parent_constructor)) return false; // If this is a fundamental constructor (e.g. GObject.Object) the // parent constructor may be null. if (parent_constructor) { if (!JS_SetPrototype(context, constructor, parent_constructor)) return false; } // hook_up_vfunc and the signal handler matcher functions can't be included // in gjs_object_instance_proto_funcs because they are custom symbols. const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return JS_DefineFunctionById(context, prototype, atoms.hook_up_vfunc(), &ObjectBase::hook_up_vfunc, 3, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(context, prototype, atoms.signal_find(), &ObjectBase::signal_find, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( context, prototype, atoms.signals_block(), &ObjectBase::signals_action<&g_signal_handlers_block_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( context, prototype, atoms.signals_unblock(), &ObjectBase::signals_action<&g_signal_handlers_unblock_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(context, prototype, atoms.signals_disconnect(), &ObjectBase::signals_action< &g_signal_handlers_disconnect_matched>, 1, GJS_MODULE_PROP_FLAGS); } /* * ObjectInstance::init_custom_class_from_gobject: * * Does all the necessary initialization for an ObjectInstance and JSObject * wrapper, given a newly-created GObject pointer, of a GObject class that was * created in JS with GObject.registerClass(). This is called from the GObject's * instance init function in gobject.cpp, and that's the only reason it's a * public method. */ bool ObjectInstance::init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper, GObject* gobj) { associate_js_gobject(cx, wrapper, gobj); // Custom JS objects will most likely have visible state, so just do this // from the start. ensure_uses_toggle_ref(cx); if (!m_uses_toggle_ref) { gjs_throw(cx, "Impossible to set toggle references on %sobject %p", m_gobj_disposed ? "disposed " : "", gobj); return false; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue v(cx); if (!JS_GetPropertyById(cx, wrapper, atoms.instance_init(), &v)) return false; if (v.isUndefined()) return true; if (!v.isObject() || !JS::IsCallable(&v.toObject())) { gjs_throw(cx, "_instance_init property was not a function"); return false; } JS::RootedValue ignored_rval(cx); return JS_CallFunctionValue(cx, wrapper, v, JS::HandleValueArray::empty(), &ignored_rval); } /* * ObjectInstance::new_for_gobject: * * Creates a new JSObject wrapper for the GObject pointer @gobj, and an * ObjectInstance private structure to go along with it. */ ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) { g_assert(gobj && "Cannot create JSObject for null GObject pointer"); GType gtype = G_TYPE_FROM_INSTANCE(gobj); gjs_debug_marshal(GJS_DEBUG_GOBJECT, "Wrapping %s %p with JSObject", g_type_name(gtype), gobj); JS::RootedObject proto(cx, gjs_lookup_object_prototype(cx, gtype)); if (!proto) return nullptr; JS::RootedObject obj( cx, JS_NewObjectWithGivenProto(cx, &ObjectBase::klass, proto)); if (!obj) return nullptr; ObjectPrototype* prototype = resolve_prototype(cx, proto); if (!prototype) return nullptr; ObjectInstance* priv = new ObjectInstance(prototype, obj); ObjectBase::init_private(obj, priv); g_object_ref_sink(gobj); priv->associate_js_gobject(cx, obj, gobj); g_assert(priv->wrapper() == obj.get()); return priv; } /* * ObjectInstance::wrapper_from_gobject: * * Gets a JSObject wrapper for the GObject pointer @gobj. If one already exists, * then it is returned. Otherwise a new one is created with * ObjectInstance::new_for_gobject(). */ JSObject* ObjectInstance::wrapper_from_gobject(JSContext* cx, GObject* gobj) { g_assert(gobj && "Cannot get JSObject for null GObject pointer"); ObjectInstance* priv = ObjectInstance::for_gobject(gobj); if (!priv) { /* We have to create a wrapper */ priv = new_for_gobject(cx, gobj); if (!priv) return nullptr; } return priv->wrapper(); } bool ObjectInstance::set_value_from_gobject(JSContext* cx, GObject* gobj, JS::MutableHandleValue value_p) { if (!gobj) { value_p.setNull(); return true; } auto* wrapper = ObjectInstance::wrapper_from_gobject(cx, gobj); if (wrapper) { value_p.setObject(*wrapper); return true; } gjs_throw(cx, "Failed to find JS object for GObject %p of type %s", gobj, g_type_name(G_TYPE_FROM_INSTANCE(gobj))); return false; } // Replaces GIWrapperBase::to_c_ptr(). The GIWrapperBase version is deleted. bool ObjectBase::to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr) { g_assert(ptr); auto* priv = ObjectBase::for_js(cx, obj); if (!priv || priv->is_prototype()) return false; ObjectInstance* instance = priv->to_instance(); if (!instance->check_gobject_finalized("access")) { *ptr = nullptr; return true; } *ptr = instance->ptr(); return true; } // Overrides GIWrapperBase::transfer_to_gi_argument(). bool ObjectBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!ObjectBase::typecheck(cx, obj, expected_info, expected_gtype)) { gjs_arg_unset(arg); return false; } GObject* ptr; if (!ObjectBase::to_c_ptr(cx, obj, &ptr)) return false; gjs_arg_set(arg, ptr); // Pointer can be null if object was already disposed by C code if (!ptr) return true; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, ObjectInstance::copy_ptr(cx, expected_gtype, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Overrides GIWrapperInstance::typecheck_impl() bool ObjectInstance::typecheck_impl(JSContext* cx, GIBaseInfo* expected_info, GType expected_type) const { g_assert(m_gobj_disposed || !m_ptr || gtype() == G_OBJECT_TYPE(m_ptr.as())); return GIWrapperInstance::typecheck_impl(cx, expected_info, expected_type); } GJS_JSAPI_RETURN_CONVENTION static bool find_vfunc_info(JSContext* context, GType implementor_gtype, GIBaseInfo* vfunc_info, const char* vfunc_name, void** implementor_vtable_ret, GjsAutoFieldInfo* field_info_ret) { GType ancestor_gtype; int length, i; GIBaseInfo *ancestor_info; GjsAutoStructInfo struct_info; bool is_interface; field_info_ret->reset(); *implementor_vtable_ret = NULL; ancestor_info = g_base_info_get_container(vfunc_info); ancestor_gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)ancestor_info); is_interface = g_base_info_get_type(ancestor_info) == GI_INFO_TYPE_INTERFACE; GjsAutoTypeClass implementor_class(implementor_gtype); if (is_interface) { GTypeInstance *implementor_iface_class; implementor_iface_class = (GTypeInstance*) g_type_interface_peek(implementor_class, ancestor_gtype); if (implementor_iface_class == NULL) { gjs_throw (context, "Couldn't find GType of implementor of interface %s.", g_type_name(ancestor_gtype)); return false; } *implementor_vtable_ret = implementor_iface_class; struct_info = g_interface_info_get_iface_struct((GIInterfaceInfo*)ancestor_info); } else { struct_info = g_object_info_get_class_struct((GIObjectInfo*)ancestor_info); *implementor_vtable_ret = implementor_class; } length = g_struct_info_get_n_fields(struct_info); for (i = 0; i < length; i++) { GjsAutoFieldInfo field_info = g_struct_info_get_field(struct_info, i); if (strcmp(field_info.name(), vfunc_name) != 0) continue; GjsAutoTypeInfo type_info = g_field_info_get_type(field_info); if (g_type_info_get_tag(type_info) != GI_TYPE_TAG_INTERFACE) { /* We have a field with the same name, but it's not a callback. * There's no hope of being another field with a correct name, * so just abort early. */ return true; } else { *field_info_ret = std::move(field_info); return true; } } return true; } bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, prototype, ObjectBase, priv); /* Normally we wouldn't assert is_prototype(), but this method can only be * called internally so it's OK to crash if done wrongly */ return priv->to_prototype()->hook_up_vfunc_impl(cx, args); } bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args) { JS::UniqueChars name; JS::RootedObject callable(cx); if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so", "name", &name, "callable", &callable)) return false; args.rval().setUndefined(); /* find the first class that actually has repository information */ GIObjectInfo *info = m_info; GType info_gtype = m_gtype; while (!info && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); info = g_irepository_find_by_gtype(nullptr, info_gtype); } /* If we don't have 'info', we don't have the base class (GObject). * This is awful, so abort now. */ g_assert(info != NULL); GjsAutoVFuncInfo vfunc = g_object_info_find_vfunc(info, name.get()); // Search the parent type chain while (!vfunc && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); info = g_irepository_find_by_gtype(nullptr, info_gtype); if (info) vfunc = g_object_info_find_vfunc(info, name.get()); } // If the vfunc doesn't exist in the parent // type chain, loop through the explicitly // defined interfaces... if (!vfunc) { for (GType interface_gtype : m_interface_gtypes) { GjsAutoInterfaceInfo interface = g_irepository_find_by_gtype(nullptr, interface_gtype); // Private and dynamic interfaces (defined in JS) do not have type // info. if (interface) { vfunc = g_interface_info_find_vfunc(interface, name.get()); if (vfunc) break; } } } // If the vfunc is still not found, it could exist on an interface // implemented by a parent. This is an error, as hooking up the vfunc // would create an implementation on the interface itself. In this // case, print a more helpful error than... // "Could not find definition of virtual function" // // See https://gitlab.gnome.org/GNOME/cjs/-/issues/89 if (!vfunc) { unsigned n_interfaces; GjsAutoPointer interface_list = g_type_interfaces(m_gtype, &n_interfaces); for (unsigned i = 0; i < n_interfaces; i++) { GjsAutoInterfaceInfo interface = g_irepository_find_by_gtype(nullptr, interface_list[i]); if (interface) { GjsAutoVFuncInfo parent_vfunc = g_interface_info_find_vfunc(interface, name.get()); if (parent_vfunc) { GjsAutoChar identifier = g_strdup_printf( "%s.%s", interface.ns(), interface.name()); gjs_throw(cx, "%s does not implement %s, add %s to your " "implements array", g_type_name(m_gtype), identifier.get(), identifier.get()); return false; } } } // Fall back to less helpful error message gjs_throw(cx, "Could not find definition of virtual function %s", name.get()); return false; } void *implementor_vtable; GjsAutoFieldInfo field_info; if (!find_vfunc_info(cx, m_gtype, vfunc, name.get(), &implementor_vtable, &field_info)) return false; if (field_info) { gint offset; void* method_ptr; GjsCallbackTrampoline *trampoline; offset = g_field_info_get_offset(field_info); method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset); if (!JS::IsCallable(callable)) { gjs_throw(cx, "Tried to deal with a vfunc that wasn't callable"); return false; } trampoline = GjsCallbackTrampoline::create( cx, callable, vfunc, GI_SCOPE_TYPE_NOTIFIED, true, true); if (!trampoline) return false; // This is traced, and will be cleared from the list when the closure is // invalidated g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(), trampoline) == m_vfuncs.end() && "This vfunc was already associated with this class"); m_vfuncs.push_back(trampoline); g_closure_add_invalidate_notifier( trampoline, this, &ObjectPrototype::vfunc_invalidated_notify); g_closure_add_invalidate_notifier( trampoline, nullptr, [](void*, GClosure* closure) { g_closure_unref(closure); }); *reinterpret_cast(method_ptr) = trampoline->closure(); } return true; } void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) { // This callback should *only* touch m_vfuncs auto* priv = static_cast(data); Gjs::remove_one_from_unsorted_vector(&priv->m_vfuncs, closure); } bool gjs_lookup_object_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p) { JSObject *constructor; GjsAutoObjectInfo object_info = gjs_lookup_gtype(nullptr, gtype); constructor = gjs_lookup_object_constructor_from_info(context, object_info, gtype); if (G_UNLIKELY (constructor == NULL)) return false; value_p.setObject(*constructor); return true; } void ObjectInstance::associate_string(GObject* obj, char* str) { auto* instance_strings = static_cast( g_object_get_qdata(obj, ObjectBase::instance_strings_quark())); if (!instance_strings) { instance_strings = g_ptr_array_new_with_free_func(g_free); g_object_set_qdata_full( obj, ObjectBase::instance_strings_quark(), instance_strings, reinterpret_cast(g_ptr_array_unref)); } g_ptr_array_add(instance_strings, str); } cjs-128.1/gi/object.h0000664000175000017500000004356215116312211013251 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_OBJECT_H_ #define GI_OBJECT_H_ #include #include // for size_t #include // for uint32_t #include #include #include #include #include #include #include // for GCHashMap #include // for DefaultHasher #include #include #include #include #include // for HashGeneric, HashNumber #include // for MOZ_LIKELY #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class GjsAtoms; class JSTracer; namespace JS { class CallArgs; } namespace Gjs { namespace Test { struct ObjectInstance; } } class ObjectInstance; class ObjectPrototype; /* * ObjectBase: * * Specialization of GIWrapperBase for GObject instances. See the documentation * in wrapperutils.h. * * It's important that ObjectBase and ObjectInstance not grow in size without a * very good reason. There can be tens, maybe hundreds of thousands of these * objects alive in a typical gnome-shell run, so even 8 more bytes will add up. * It's less critical that ObjectPrototype stay small, since only one of these * is allocated per GType. */ class ObjectBase : public GIWrapperBase { friend class GIWrapperBase; protected: explicit ObjectBase(ObjectPrototype* proto = nullptr) : GIWrapperBase(proto) {} public: using SignalMatchFunc = guint(gpointer, GSignalMatchType, guint, GQuark, GClosure*, gpointer, gpointer); static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GOBJECT; static constexpr const char* DEBUG_TAG = "GObject"; static const struct JSClassOps class_ops; static const struct JSClass klass; static JSFunctionSpec proto_methods[]; static JSPropertySpec proto_properties[]; static GObject* to_c_ptr(JSContext* cx, JS::HandleObject obj) = delete; GJS_JSAPI_RETURN_CONVENTION static bool to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info = nullptr); private: // This is used in debug methods only. [[nodiscard]] const void* jsobj_addr() const; /* Helper methods */ protected: void debug_lifecycle(const char* message) const { GIWrapperBase::debug_lifecycle(jsobj_addr(), message); } [[nodiscard]] bool id_is_never_lazy(jsid name, const GjsAtoms& atoms); [[nodiscard]] bool is_custom_js_class(); public: GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype); [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, GIObjectInfo* expected_info, GType expected_gtype, GjsTypecheckNoThrow no_throw) { return GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype, no_throw); } /* JSClass operations */ static bool add_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value); /* JS property getters/setters */ public: GJS_JSAPI_RETURN_CONVENTION static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp); /* JS methods */ GJS_JSAPI_RETURN_CONVENTION static bool connect(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool connect_after(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool connect_object(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool emit(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool signal_find(JSContext* cx, unsigned argc, JS::Value* vp); template GJS_JSAPI_RETURN_CONVENTION static bool signals_action(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool init_gobject(JSContext* cx, unsigned argc, JS::Value* vp); GJS_JSAPI_RETURN_CONVENTION static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp); /* Quarks */ protected: [[nodiscard]] static GQuark instance_strings_quark(); public: [[nodiscard]] static GQuark custom_type_quark(); [[nodiscard]] static GQuark custom_property_quark(); [[nodiscard]] static GQuark disposed_quark(); }; // See https://bugzilla.mozilla.org/show_bug.cgi?id=1614220 struct IdHasher { typedef jsid Lookup; static mozilla::HashNumber hash(jsid id) { if (MOZ_LIKELY(id.isString())) return js::DefaultHasher::hash(id.toString()); if (id.isSymbol()) return js::DefaultHasher::hash(id.toSymbol()); return mozilla::HashGeneric(id.asRawBits()); } static bool match(jsid id1, jsid id2) { return id1 == id2; } }; class ObjectPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; using FieldCache = JS::GCHashMap, GjsAutoFieldInfo, js::DefaultHasher, js::SystemAllocPolicy>; using NegativeLookupCache = JS::GCHashSet, IdHasher, js::SystemAllocPolicy>; FieldCache m_field_cache; NegativeLookupCache m_unresolvable_cache; // a list of vfunc GClosures installed on this prototype, used when tracing std::vector m_vfuncs; // a list of interface types explicitly associated with this prototype, // by gjs_add_interface std::vector m_interface_gtypes; ObjectPrototype(GIObjectInfo* info, GType gtype); ~ObjectPrototype(); static constexpr InfoType::Tag info_type_tag = InfoType::Object; public: [[nodiscard]] static ObjectPrototype* for_gtype(GType gtype); /* Helper methods */ private: GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const; GJS_JSAPI_RETURN_CONVENTION bool get_parent_constructor(JSContext* cx, JS::MutableHandleObject constructor) const; [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info); static void vfunc_invalidated_notify(void* data, GClosure* closure); GJS_JSAPI_RETURN_CONVENTION bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec*, bool* resolved, const char* name); enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties }; GJS_JSAPI_RETURN_CONVENTION bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved, const char* name, ResolveWhat resolve_props); GJS_JSAPI_RETURN_CONVENTION bool uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved); public: void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes); void set_type_qdata(void); GJS_JSAPI_RETURN_CONVENTION GParamSpec* find_param_spec_from_id(JSContext*, GjsAutoTypeClass const&, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION GIFieldInfo* lookup_cached_field_info(JSContext* cx, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION bool props_to_g_parameters(JSContext*, GjsAutoTypeClass const&, JS::HandleObject props, std::vector* names, AutoGValueVector* values); GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIObjectInfo* info, GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype); void ref_vfuncs(void) { for (GClosure* closure : m_vfuncs) g_closure_ref(closure); } void unref_vfuncs(void) { for (GClosure* closure : m_vfuncs) g_closure_unref(closure); } /* JSClass operations */ private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable); void trace_impl(JSTracer* tracer); /* JS methods */ public: GJS_JSAPI_RETURN_CONVENTION bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args); }; class ObjectInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; friend class ObjectBase; // for add_property, prop_getter, etc. friend struct Gjs::Test::ObjectInstance; // GIWrapperInstance::m_ptr may be null in ObjectInstance. GjsMaybeOwned m_wrapper; // a list of all GClosures installed on this object (from signal connections // and scope-notify callbacks passed to methods), used when tracing std::vector m_closures; bool m_wrapper_finalized : 1; bool m_gobj_disposed : 1; bool m_gobj_finalized : 1; /* True if this object has visible JS state, and thus its lifecycle is * managed using toggle references. False if this object just keeps a * hard ref on the underlying GObject, and may be finalized at will. */ bool m_uses_toggle_ref : 1; static bool s_weak_pointer_callback; /* Constructors */ private: ObjectInstance(ObjectPrototype* prototype, JS::HandleObject obj); ~ObjectInstance(); GJS_JSAPI_RETURN_CONVENTION static ObjectInstance* new_for_gobject(JSContext* cx, GObject* gobj); // Extra method to get an existing ObjectInstance from qdata public: [[nodiscard]] static ObjectInstance* for_gobject(GObject* gobj); /* Accessors */ private: [[nodiscard]] bool has_wrapper() const { return !!m_wrapper; } public: [[nodiscard]] JSObject* wrapper() const { return m_wrapper.get(); } /* Methods to manipulate the JS object wrapper */ private: void discard_wrapper(void) { m_wrapper.reset(); } void switch_to_rooted(JSContext* cx) { m_wrapper.switch_to_rooted(cx); } void switch_to_unrooted(JSContext* cx) { m_wrapper.switch_to_unrooted(cx); } [[nodiscard]] bool update_after_gc(JSTracer* trc) { return m_wrapper.update_after_gc(trc); } [[nodiscard]] bool wrapper_is_rooted() const { return m_wrapper.rooted(); } void release_native_object(void); void associate_js_gobject(JSContext* cx, JS::HandleObject obj, GObject* gobj); void disassociate_js_gobject(void); void handle_context_dispose(void); [[nodiscard]] bool weak_pointer_was_finalized(JSTracer* trc); static void ensure_weak_pointer_callback(JSContext* cx); static void update_heap_wrapper_weak_pointers(JSTracer* trc, JS::Compartment*, void* data); public: void toggle_down(void); void toggle_up(void); GJS_JSAPI_RETURN_CONVENTION static JSObject* wrapper_from_gobject(JSContext* cx, GObject* ptr); GJS_JSAPI_RETURN_CONVENTION static bool set_value_from_gobject(JSContext* cx, GObject*, JS::MutableHandleValue); /* Methods to manipulate the list of closures */ private: void invalidate_closures(); static void closure_invalidated_notify(void* data, GClosure* closure); public: GJS_JSAPI_RETURN_CONVENTION bool associate_closure(JSContext*, GClosure*); /* Helper methods */ private: void set_object_qdata(void); void unset_object_qdata(void); void track_gobject_finalization(); void ignore_gobject_finalization(); void check_js_object_finalized(void); void ensure_uses_toggle_ref(JSContext*); [[nodiscard]] bool check_gobject_disposed_or_finalized( const char* for_what) const; [[nodiscard]] bool check_gobject_finalized(const char* for_what) const; GJS_JSAPI_RETURN_CONVENTION bool signal_match_arguments_from_object( JSContext* cx, JS::HandleObject props_obj, GSignalMatchType* mask_out, unsigned* signal_id_out, GQuark* detail_out, JS::MutableHandleObject callable_out); public: static GObject* copy_ptr(JSContext*, GType, void* ptr) { return G_OBJECT(g_object_ref(G_OBJECT(ptr))); } GJS_JSAPI_RETURN_CONVENTION bool init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper, GObject* gobj); static void associate_string(GObject* obj, char* str); /* Methods to manipulate the linked list of instances */ private: static std::vector s_wrapped_gobject_list; void link(void); void unlink(void); [[nodiscard]] static size_t num_wrapped_gobjects() { return s_wrapped_gobject_list.size(); } using Action = std::function; using Predicate = std::function; static void remove_wrapped_gobjects_if(const Predicate& predicate, const Action& action); public: static void prepare_shutdown(void); /* JSClass operations */ private: GJS_JSAPI_RETURN_CONVENTION bool add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value); void finalize_impl(JS::GCContext*, JSObject* obj); void trace_impl(JSTracer* trc); /* JS property getters/setters */ private: GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl(JSContext* cx, GParamSpec*, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext* cx, JS::HandleString name, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext* cx, GParamSpec*, JS::HandleValue value); GJS_JSAPI_RETURN_CONVENTION bool field_setter_not_impl(JSContext* cx, JS::HandleString name); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); /* JS methods */ private: GJS_JSAPI_RETURN_CONVENTION bool connect_impl(JSContext* cx, const JS::CallArgs& args, bool after, bool object = false); GJS_JSAPI_RETURN_CONVENTION bool emit_impl(JSContext* cx, const JS::CallArgs& args); GJS_JSAPI_RETURN_CONVENTION bool signal_find_impl(JSContext* cx, const JS::CallArgs& args); template GJS_JSAPI_RETURN_CONVENTION bool signals_action_impl( JSContext* cx, const JS::CallArgs& args); GJS_JSAPI_RETURN_CONVENTION bool init_impl(JSContext* cx, const JS::CallArgs& args, JS::HandleObject obj); [[nodiscard]] const char* to_string_kind() const; GJS_JSAPI_RETURN_CONVENTION bool typecheck_impl(JSContext* cx, GIBaseInfo* expected_info, GType expected_type) const; /* Notification callbacks */ void gobj_dispose_notify(void); static void wrapped_gobj_dispose_notify(void* data, GObject*); static void wrapped_gobj_toggle_notify(void* instance, GObject* gobj, gboolean is_last_ref); public: static void context_dispose_notify(void* data, GObject* where_the_object_was); }; GJS_JSAPI_RETURN_CONVENTION bool gjs_lookup_object_constructor(JSContext *context, GType gtype, JS::MutableHandleValue value_p); void gjs_object_clear_toggles(void); void gjs_object_shutdown_toggle_queue(void); #endif // GI_OBJECT_H_ cjs-128.1/gi/param.cpp0000664000175000017500000002167415116312211013436 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include // for size_t #include #include #include #include #include // for JSEXN_TYPEERR #include // for GetClass #include #include // for JSPROP_READONLY #include // for JSPropertySpec, JS_PS_END, JS_STR... #include #include #include // for UniqueChars #include #include // for JS_NewObjectForConstructor, JS_NewObjectWithG... #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" extern struct JSClass gjs_param_class; // Reserved slots static const size_t POINTER = 0; struct Param : GjsAutoParam { explicit Param(GParamSpec* param) : GjsAutoParam(param, GjsAutoTakeOwnership()) {} }; [[nodiscard]] static GParamSpec* param_value(JSContext* cx, JS::HandleObject obj) { if (!JS_InstanceOf(cx, obj, &gjs_param_class, nullptr)) return nullptr; auto* priv = JS::GetMaybePtrFromReservedSlot(obj, POINTER); return priv ? priv->get() : nullptr; } /* * The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool param_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { if (!param_value(context, obj)) { /* instance, not prototype */ *resolved = false; return true; } JS::UniqueChars name; if (!gjs_get_string_id(context, id, &name)) return false; if (!name) { *resolved = false; return true; /* not resolved, but no error */ } GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); GjsAutoFunctionInfo method_info = g_object_info_find_method(info, name.get()); if (!method_info) { *resolved = false; return true; } #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for GObject.ParamSpec", method_info.name()); if (!gjs_define_function(context, obj, G_TYPE_PARAM, method_info)) return false; *resolved = true; /* we defined the prop in obj */ } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_param_constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (!args.isConstructing()) { gjs_throw_constructor_error(cx); return false; } JS::RootedObject new_object( cx, JS_NewObjectForConstructor(cx, &gjs_param_class, args)); if (!new_object) return false; GJS_INC_COUNTER(param); args.rval().setObject(*new_object); return true; } static void param_finalize(JS::GCContext*, JSObject* obj) { Param* priv = JS::GetMaybePtrFromReservedSlot(obj, POINTER); gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "finalize, obj %p priv %p", obj, priv); if (!priv) return; /* wrong class? */ GJS_DEC_COUNTER(param); JS::SetReservedSlot(obj, POINTER, JS::UndefinedValue()); delete priv; } /* The bizarre thing about this vtable is that it applies to both * instances of the object, and to the prototype that instances of the * class have. */ static const struct JSClassOps gjs_param_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate param_resolve, nullptr, // mayResolve param_finalize}; static JSPropertySpec proto_props[] = { JS_STRING_SYM_PS(toStringTag, "GObject_ParamSpec", JSPROP_READONLY), JS_PS_END}; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties nullptr, // prototypeFunctions proto_props, // prototypeProperties nullptr // finishInit }; struct JSClass gjs_param_class = { "GObject_ParamSpec", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &gjs_param_class_ops, &class_spec}; GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_param_prototype(JSContext *context) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject in_object( context, gjs_lookup_namespace_object_by_name(context, atoms.gobject())); if (G_UNLIKELY (!in_object)) return nullptr; JS::RootedValue value(context); if (!JS_GetPropertyById(context, in_object, atoms.param_spec(), &value) || G_UNLIKELY(!value.isObject())) return nullptr; JS::RootedObject constructor(context, &value.toObject()); g_assert(constructor); if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value) || G_UNLIKELY(!value.isObjectOrNull())) return nullptr; return value.toObjectOrNull(); } bool gjs_define_param_class(JSContext *context, JS::HandleObject in_object) { JS::RootedObject prototype(context), constructor(context); if (!gjs_init_class_dynamic( context, in_object, nullptr, "GObject", "ParamSpec", &gjs_param_class, gjs_param_constructor, 0, proto_props, // props of prototype nullptr, // funcs of prototype nullptr, // props of constructor, MyConstructor.myprop nullptr, // funcs of constructor &prototype, &constructor)) return false; if (!gjs_wrapper_define_gtype_prop(context, constructor, G_TYPE_PARAM)) return false; GjsAutoObjectInfo info = g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM); if (!gjs_define_static_methods(context, constructor, G_TYPE_PARAM, info)) return false; gjs_debug(GJS_DEBUG_GPARAM, "Defined class ParamSpec prototype is %p class %p in object %p", prototype.get(), &gjs_param_class, in_object.get()); return true; } JSObject* gjs_param_from_g_param(JSContext *context, GParamSpec *gparam) { JSObject *obj; if (!gparam) return nullptr; gjs_debug(GJS_DEBUG_GPARAM, "Wrapping %s '%s' on %s with JSObject", g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam)), gparam->name, g_type_name(gparam->owner_type)); JS::RootedObject proto(context, gjs_lookup_param_prototype(context)); obj = JS_NewObjectWithGivenProto(context, JS::GetClass(proto), proto); GJS_INC_COUNTER(param); auto* priv = new Param(gparam); JS::SetReservedSlot(obj, POINTER, JS::PrivateValue(priv)); gjs_debug(GJS_DEBUG_GPARAM, "JSObject created with param instance %p type %s", gparam, g_type_name(G_TYPE_FROM_INSTANCE(gparam))); return obj; } GParamSpec* gjs_g_param_from_param(JSContext *context, JS::HandleObject obj) { if (!obj) return nullptr; return param_value(context, obj); } bool gjs_typecheck_param(JSContext *context, JS::HandleObject object, GType expected_type, bool throw_error) { bool result; if (!gjs_typecheck_instance(context, object, &gjs_param_class, throw_error)) return false; GParamSpec* param = param_value(context, object); if (!param) { if (throw_error) { gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, "Object is GObject.ParamSpec.prototype, not an " "object instance - cannot convert to a GObject." "ParamSpec instance"); } return false; } if (expected_type != G_TYPE_NONE) result = g_type_is_a(G_TYPE_FROM_INSTANCE(param), expected_type); else result = true; if (!result && throw_error) { gjs_throw_custom(context, JSEXN_TYPEERR, nullptr, "Object is of type %s - cannot convert to %s", g_type_name(G_TYPE_FROM_INSTANCE(param)), g_type_name(expected_type)); } return result; } cjs-128.1/gi/param.h0000664000175000017500000000163215116312211013073 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_PARAM_H_ #define GI_PARAM_H_ #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_param_class(JSContext *context, JS::HandleObject in_object); GJS_JSAPI_RETURN_CONVENTION GParamSpec *gjs_g_param_from_param (JSContext *context, JS::HandleObject obj); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_param_from_g_param (JSContext *context, GParamSpec *param); [[nodiscard]] bool gjs_typecheck_param(JSContext* cx, JS::HandleObject obj, GType expected_type, bool throw_error); #endif // GI_PARAM_H_ cjs-128.1/gi/private.cpp0000664000175000017500000005423215116312211014004 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2018 Philip Chimento #include #include #include #include #include // for JS::GetArrayLength #include // for IsCallable #include #include #include #include #include #include // for UniqueChars #include #include #include // for JS_NewPlainObject #include "gi/closure.h" #include "gi/gobject.h" #include "gi/gtype.h" #include "gi/interface.h" #include "gi/object.h" #include "gi/param.h" #include "gi/private.h" #include "gi/repo.h" #include "gi/value.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" /* gi/private.cpp - private "imports._gi" module with operations that we need * to use from JS in order to create GObject classes, but should not be exposed * to client code. */ GJS_JSAPI_RETURN_CONVENTION static bool gjs_override_property(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; JS::RootedObject type(cx); if (!gjs_parse_call_args(cx, "override_property", args, "so", "name", &name, "type", &type)) return false; GType gtype; if (!gjs_gtype_get_actual_gtype(cx, type, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(cx, "Invalid parameter type was not a GType"); return false; } GParamSpec* pspec; if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { auto* interface_type = static_cast(g_type_default_interface_ref(gtype)); pspec = g_object_interface_find_property(interface_type, name.get()); g_type_default_interface_unref(interface_type); } else { GjsAutoTypeClass class_type(gtype); pspec = g_object_class_find_property(class_type, name.get()); } if (!pspec) { gjs_throw(cx, "No such property '%s' to override on type '%s'", name.get(), g_type_name(gtype)); return false; } GjsAutoParam new_pspec = g_param_spec_override(name.get(), pspec); g_param_spec_set_qdata(new_pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); args.rval().setObject(*gjs_param_from_g_param(cx, new_pspec)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool validate_interfaces_and_properties_args(JSContext* cx, JS::HandleObject interfaces, JS::HandleObject properties, uint32_t* n_interfaces, uint32_t* n_properties) { bool is_array; if (!JS::IsArrayObject(cx, interfaces, &is_array)) return false; if (!is_array) { gjs_throw(cx, "Invalid parameter interfaces (expected Array)"); return false; } uint32_t n_int; if (!JS::GetArrayLength(cx, interfaces, &n_int)) return false; if (!JS::IsArrayObject(cx, properties, &is_array)) return false; if (!is_array) { gjs_throw(cx, "Invalid parameter properties (expected Array)"); return false; } uint32_t n_prop; if (!JS::GetArrayLength(cx, properties, &n_prop)) return false; if (n_interfaces) *n_interfaces = n_int; if (n_properties) *n_properties = n_prop; return true; } GJS_JSAPI_RETURN_CONVENTION static bool save_properties_for_class_init(JSContext* cx, JS::HandleObject properties, uint32_t n_properties, GType gtype) { AutoParamArray properties_native; JS::RootedValue prop_val(cx); JS::RootedObject prop_obj(cx); for (uint32_t i = 0; i < n_properties; i++) { if (!JS_GetElement(cx, properties, i, &prop_val)) return false; if (!prop_val.isObject()) { gjs_throw(cx, "Invalid parameter, expected object"); return false; } prop_obj = &prop_val.toObject(); if (!gjs_typecheck_param(cx, prop_obj, G_TYPE_NONE, true)) return false; properties_native.emplace_back( g_param_spec_ref(gjs_g_param_from_param(cx, prop_obj))); } push_class_init_properties(gtype, &properties_native); return true; } GJS_JSAPI_RETURN_CONVENTION static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces, uint32_t n_interfaces, GType* iface_types) { for (uint32_t ix = 0; ix < n_interfaces; ix++) { JS::RootedValue iface_val(cx); if (!JS_GetElement(cx, interfaces, ix, &iface_val)) return false; if (!iface_val.isObject()) { gjs_throw( cx, "Invalid parameter interfaces (element %d was not a GType)", ix); return false; } JS::RootedObject iface(cx, &iface_val.toObject()); GType iface_type; if (!gjs_gtype_get_actual_gtype(cx, iface, &iface_type)) return false; if (iface_type == G_TYPE_INVALID) { gjs_throw( cx, "Invalid parameter interfaces (element %d was not a GType)", ix); return false; } iface_types[ix] = iface_type; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool create_wrapper_array(JSContext* cx, JS::HandleObject prototype, GType type, JS::MutableHandleValue rval) { JS::RootedObject gtype_wrapper(cx, gjs_gtype_create_gtype_wrapper(cx, type)); if (!gtype_wrapper) return false; JS::RootedValueArray<2> tuple(cx); tuple[0].setObject(*prototype); tuple[1].setObject(*gtype_wrapper); JS::RootedObject array(cx, JS::NewArrayObject(cx, tuple)); if (!array) return false; rval.setObject(*array); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_interface_impl(JSContext* cx, const char* name, JS::HandleObject interfaces, JS::HandleObject properties, GType* gtype) { uint32_t n_interfaces, n_properties; if (!validate_interfaces_and_properties_args(cx, interfaces, properties, &n_interfaces, &n_properties)) return false; GjsAutoPointer iface_types = g_new(GType, n_interfaces); /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types)) return false; if (g_type_from_name(name) != G_TYPE_INVALID) { gjs_throw(cx, "Type name %s is already registered", name); return false; } GTypeInfo type_info = gjs_gobject_interface_info; GType interface_type = g_type_register_static(G_TYPE_INTERFACE, name, &type_info, GTypeFlags(0)); g_type_set_qdata(interface_type, ObjectBase::custom_type_quark(), GINT_TO_POINTER(1)); if (!save_properties_for_class_init(cx, properties, n_properties, interface_type)) return false; for (uint32_t ix = 0; ix < n_interfaces; ix++) g_type_interface_add_prerequisite(interface_type, iface_types[ix]); *gtype = interface_type; return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_interface(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; JS::RootedObject interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_interface", args, "soo", "name", &name, "interfaces", &interfaces, "properties", &properties)) return false; GType interface_type; if (!gjs_register_interface_impl(cx, name.get(), interfaces, properties, &interface_type)) return false; /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); if (!module) return false; // error will have been thrown already JS::RootedObject constructor(cx), ignored_prototype(cx); if (!InterfacePrototype::create_class(cx, module, nullptr, interface_type, &constructor, &ignored_prototype)) return false; args.rval().setObject(*constructor); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_interface_with_class(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; JS::RootedObject klass(cx), interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_interface_with_class", args, "osoo", "class", &klass, "name", &name, "interfaces", &interfaces, "properties", &properties)) return false; GType interface_type; if (!gjs_register_interface_impl(cx, name.get(), interfaces, properties, &interface_type)) return false; /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); if (!module) return false; // error will have been thrown already JS::RootedObject prototype(cx); if (!InterfacePrototype::wrap_class(cx, module, nullptr, interface_type, klass, &prototype)) return false; return create_wrapper_array(cx, prototype, interface_type, args.rval()); } static inline void gjs_add_interface(GType instance_type, GType interface_type) { static GInterfaceInfo interface_vtable{nullptr, nullptr, nullptr}; g_type_add_interface_static(instance_type, interface_type, &interface_vtable); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_type_impl(JSContext* cx, const char* name, GTypeFlags type_flags, JS::HandleObject parent, JS::HandleObject interfaces, JS::HandleObject properties, GType** iface_types_out, uint32_t* n_interfaces_out, GType* gtype) { if (!parent) return false; /* Don't pass the argv to it, as otherwise we will log about the callee * while we only care about the parent object type. */ ObjectBase* parent_priv; if (!ObjectBase::for_js_typecheck(cx, parent, &parent_priv)) return false; uint32_t n_interfaces, n_properties; if (!validate_interfaces_and_properties_args(cx, interfaces, properties, &n_interfaces, &n_properties)) return false; GjsAutoPointer iface_types = g_new(GType, n_interfaces); /* We do interface addition in two passes so that any failure is caught early, before registering the GType (which we can't undo) */ if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types)) return false; if (g_type_from_name(name) != G_TYPE_INVALID) { gjs_throw(cx, "Type name %s is already registered", name); return false; } /* We checked parent above, in ObjectBase::for_js_typecheck() */ g_assert(parent_priv); GTypeQuery query; g_type_query(parent_priv->gtype(), &query); if (G_UNLIKELY( g_type_test_flags(parent_priv->gtype(), G_TYPE_FLAG_FINAL))) { gjs_throw(cx, "Cannot inherit from a final type"); return false; } GTypeInfo type_info = gjs_gobject_class_info; type_info.class_size = query.class_size; type_info.instance_size = query.instance_size; GType instance_type = g_type_register_static(parent_priv->gtype(), name, &type_info, type_flags); g_type_set_qdata(instance_type, ObjectBase::custom_type_quark(), GINT_TO_POINTER(1)); if (!save_properties_for_class_init(cx, properties, n_properties, instance_type)) return false; for (uint32_t ix = 0; ix < n_interfaces; ix++) gjs_add_interface(instance_type, iface_types[ix]); *gtype = instance_type; *n_interfaces_out = n_interfaces; *iface_types_out = iface_types.release(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; GTypeFlags type_flags; JS::RootedObject parent(cx), interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_type", argv, "osioo", "parent", &parent, "name", &name, "flags", &type_flags, "interfaces", &interfaces, "properties", &properties)) return false; GType instance_type; GjsAutoPointer iface_types; uint32_t n_interfaces; if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces, properties, iface_types.out(), &n_interfaces, &instance_type)) return false; /* create a custom JSClass */ JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); JS::RootedObject constructor(cx), prototype(cx); if (!ObjectPrototype::define_class(cx, module, nullptr, instance_type, iface_types, n_interfaces, &constructor, &prototype)) return false; auto* priv = ObjectPrototype::for_js(cx, prototype); priv->set_type_qdata(); argv.rval().setObject(*constructor); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_register_type_with_class(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); JS::UniqueChars name; GTypeFlags type_flags; JS::RootedObject klass(cx), parent(cx), interfaces(cx), properties(cx); if (!gjs_parse_call_args(cx, "register_type_with_class", argv, "oosioo", "class", &klass, "parent", &parent, "name", &name, "flags", &type_flags, "interfaces", &interfaces, "properties", &properties)) return false; GType instance_type; uint32_t n_interfaces; GjsAutoPointer iface_types; if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces, properties, iface_types.out(), &n_interfaces, &instance_type)) return false; // create a custom JSClass JS::RootedObject module(cx, gjs_lookup_private_namespace(cx)); if (!module) return false; JS::RootedObject prototype(cx); ObjectPrototype* priv = ObjectPrototype::wrap_class( cx, module, nullptr, instance_type, klass, &prototype); if (!priv) return false; priv->set_interfaces(iface_types, n_interfaces); priv->set_type_qdata(); return create_wrapper_array(cx, prototype, instance_type, argv.rval()); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_signal_new(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars signal_name; int32_t flags, accumulator_enum; JS::RootedObject gtype_obj(cx), return_gtype_obj(cx), params_obj(cx); if (!gjs_parse_call_args(cx, "signal_new", args, "osiioo", "gtype", >ype_obj, "signal name", &signal_name, "flags", &flags, "accumulator", &accumulator_enum, "return gtype", &return_gtype_obj, "params", ¶ms_obj)) return false; /* we only support standard accumulators for now */ GSignalAccumulator accumulator; switch (accumulator_enum) { case 1: accumulator = g_signal_accumulator_first_wins; break; case 2: accumulator = g_signal_accumulator_true_handled; break; case 0: default: accumulator = nullptr; } GType return_type; if (!gjs_gtype_get_actual_gtype(cx, return_gtype_obj, &return_type)) return false; if (accumulator == g_signal_accumulator_true_handled && return_type != G_TYPE_BOOLEAN) { gjs_throw(cx, "GObject.SignalAccumulator.TRUE_HANDLED can only be used " "with boolean signals"); return false; } uint32_t n_parameters; if (!JS::GetArrayLength(cx, params_obj, &n_parameters)) return false; GjsAutoPointer params = g_new(GType, n_parameters); JS::RootedValue gtype_val(cx); for (uint32_t ix = 0; ix < n_parameters; ix++) { if (!JS_GetElement(cx, params_obj, ix, >ype_val) || !gtype_val.isObject()) { gjs_throw(cx, "Invalid signal parameter number %d", ix); return false; } JS::RootedObject gjs_gtype(cx, >ype_val.toObject()); if (!gjs_gtype_get_actual_gtype(cx, gjs_gtype, ¶ms[ix])) return false; } GType gtype; if (!gjs_gtype_get_actual_gtype(cx, gtype_obj, >ype)) return false; unsigned signal_id = g_signal_newv( signal_name.get(), gtype, GSignalFlags(flags), /* class closure */ nullptr, accumulator, /* accu_data */ nullptr, /* c_marshaller */ nullptr, return_type, n_parameters, params); // FIXME: what if ID is greater than int32 max? args.rval().setInt32(signal_id); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_lookup_constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject gtype_obj(cx); if (!gjs_parse_call_args(cx, "lookupConstructor", args, "o", "gtype", >ype_obj)) return false; GType gtype; if (!gjs_gtype_get_actual_gtype(cx, gtype_obj, >ype)) return false; if (gtype == G_TYPE_NONE) { gjs_throw(cx, "Invalid GType for constructor lookup"); return false; } return gjs_lookup_object_constructor(cx, gtype, args.rval()); } template GJS_JSAPI_RETURN_CONVENTION static bool symbol_getter(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); args.rval().setSymbol((atoms.*member)().toSymbol()); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_associate_closure(JSContext* context, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); JS::RootedObject func_obj(context); JS::RootedObject target_obj(context); Gjs::Closure::Ptr closure; Gjs::AutoGValue value(G_TYPE_CLOSURE); ObjectInstance* obj; if (!gjs_parse_call_args(context, "associateClosure", argv, "oo", "object", &target_obj, "func", &func_obj)) return false; g_assert(JS::IsCallable(func_obj) && "associateClosure's function must be callable"); obj = ObjectInstance::for_js(context, target_obj); if (!obj) return false; closure = Gjs::Closure::create_marshaled(context, func_obj, "wrapped", false); if (!obj->associate_closure(context, closure)) return false; g_value_set_boxed(&value, closure); return gjs_value_from_g_value(context, argv.rval(), &value); } static JSFunctionSpec private_module_funcs[] = { JS_FN("override_property", gjs_override_property, 2, GJS_MODULE_PROP_FLAGS), JS_FN("register_interface", gjs_register_interface, 3, GJS_MODULE_PROP_FLAGS), JS_FN("register_interface_with_class", gjs_register_interface_with_class, 4, GJS_MODULE_PROP_FLAGS), JS_FN("register_type", gjs_register_type, 4, GJS_MODULE_PROP_FLAGS), JS_FN("register_type_with_class", gjs_register_type_with_class, 5, GJS_MODULE_PROP_FLAGS), JS_FN("signal_new", gjs_signal_new, 6, GJS_MODULE_PROP_FLAGS), JS_FN("lookupConstructor", gjs_lookup_constructor, 1, 0), JS_FN("associateClosure", gjs_associate_closure, 2, GJS_MODULE_PROP_FLAGS), JS_FS_END, }; static JSPropertySpec private_module_props[] = { JS_PSG("gobject_prototype_symbol", symbol_getter<&GjsAtoms::gobject_prototype>, GJS_MODULE_PROP_FLAGS), JS_PSG("hook_up_vfunc_symbol", symbol_getter<&GjsAtoms::hook_up_vfunc>, GJS_MODULE_PROP_FLAGS), JS_PSG("signal_find_symbol", symbol_getter<&GjsAtoms::signal_find>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_block_symbol", symbol_getter<&GjsAtoms::signals_block>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_unblock_symbol", symbol_getter<&GjsAtoms::signals_unblock>, GJS_MODULE_PROP_FLAGS), JS_PSG("signals_disconnect_symbol", symbol_getter<&GjsAtoms::signals_disconnect>, GJS_MODULE_PROP_FLAGS), JS_PS_END}; bool gjs_define_private_gi_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); return JS_DefineFunctions(cx, module, private_module_funcs) && JS_DefineProperties(cx, module, private_module_props); } cjs-128.1/gi/private.h0000664000175000017500000000064015116312211013443 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_PRIVATE_H_ #define GI_PRIVATE_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_private_gi_stuff(JSContext* cx, JS::MutableHandleObject module); #endif // GI_PRIVATE_H_ cjs-128.1/gi/repo.cpp0000664000175000017500000006347015116312211013303 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include // for strlen #if GJS_VERBOSE_ENABLE_GI_USAGE # include #endif #include #include #include #include // for JS_CallFunctionValue #include #include #include #include // for CurrentGlobalOrNull #include // for PropertyKey #include // for GetClass #include #include // for JSPROP_PERMANENT, JSPROP_RESOLVING #include #include #include #include // for UniqueChars #include #include #include #include // for JS_NewPlainObject, JS_NewObject #include "gi/arg.h" #include "gi/boxed.h" #include "gi/enumeration.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/interface.h" #include "gi/ns.h" #include "gi/object.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/union.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/module.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION static bool lookup_override_function(JSContext *, JS::HandleId, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION static bool get_version_for_ns(JSContext* context, JS::HandleObject repo_obj, JS::HandleId ns_id, JS::UniqueChars* version) { JS::RootedObject versions(context); bool found; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (!gjs_object_require_property(context, repo_obj, "GI repository object", atoms.versions(), &versions)) return false; if (!JS_AlreadyHasOwnPropertyById(context, versions, ns_id, &found)) return false; if (!found) return true; return gjs_object_require_property(context, versions, NULL, ns_id, version); } static void strlist_free(GList* l) { g_list_free_full(l, g_free); } GJS_JSAPI_RETURN_CONVENTION static bool resolve_namespace_object(JSContext* context, JS::HandleObject repo_obj, JS::HandleId ns_id) { JS::UniqueChars version; if (!get_version_for_ns(context, repo_obj, ns_id, &version)) return false; JS::UniqueChars ns_name; if (!gjs_get_string_id(context, ns_id, &ns_name)) return false; if (!ns_name) { gjs_throw(context, "Requiring invalid namespace on imports.gi"); return false; } GjsAutoPointer versions = g_irepository_enumerate_versions(nullptr, ns_name.get()); unsigned nversions = g_list_length(versions); if (nversions > 1 && !version && !g_irepository_is_registered(nullptr, ns_name.get(), nullptr) && !JS::WarnUTF8(context, "Requiring %s but it has %u versions available; use " "imports.gi.versions to pick one", ns_name.get(), nversions)) return false; GjsAutoError error; // If resolving Gio, load the platform-specific typelib first, so that // GioUnix/GioWin32 GTypes get looked up in there with higher priority, // instead of in Gio. #if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32)) if (strcmp(ns_name.get(), "Gio") == 0) { # ifdef G_OS_UNIX const char* platform = "Unix"; # else // G_OS_WIN32 const char* platform = "Win32"; # endif // G_OS_UNIX/G_OS_WIN32 GjsAutoChar platform_specific = g_strconcat(ns_name.get(), platform, nullptr); if (!g_irepository_require(nullptr, platform_specific, version.get(), GIRepositoryLoadFlags(0), &error)) { gjs_throw(context, "Failed to require %s %s: %s", platform_specific.get(), version.get(), error->message); return false; } } #endif // GLib >= 2.79.2 g_irepository_require(nullptr, ns_name.get(), version.get(), GIRepositoryLoadFlags(0), &error); if (error) { gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(), version ? version.get() : "none", error->message); return false; } /* Defines a property on "obj" (the javascript repo object) * with the given namespace name, pointing to that namespace * in the repo. */ JS::RootedObject gi_namespace(context, gjs_create_ns(context, ns_name.get())); JS::RootedValue override(context); if (!lookup_override_function(context, ns_id, &override) || // Define the property early, to avoid reentrancy issues if the override // module looks for namespaces that import this !JS_DefinePropertyById(context, repo_obj, ns_id, gi_namespace, GJS_MODULE_PROP_FLAGS)) return false; JS::RootedValue result(context); if (!override.isUndefined() && !JS_CallFunctionValue (context, gi_namespace, /* thisp */ override, /* callee */ JS::HandleValueArray::empty(), &result)) return false; gjs_debug(GJS_DEBUG_GNAMESPACE, "Defined namespace '%s' %p in GIRepository %p", ns_name.get(), gi_namespace.get(), repo_obj.get()); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); gjs->schedule_gc_if_needed(); return true; } /* * The *resolved out parameter, on success, should be false to indicate that id * was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool repo_resolve(JSContext *context, JS::HandleObject obj, JS::HandleId id, bool *resolved) { if (!id.isString()) { *resolved = false; return true; /* not resolved, but no error */ } /* let Object.prototype resolve these */ const GjsAtoms& atoms = GjsContextPrivate::atoms(context); if (id == atoms.to_string() || id == atoms.value_of()) { *resolved = false; return true; } gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook, obj %s", gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str()); if (!resolve_namespace_object(context, obj, id)) return false; *resolved = true; return true; } static const struct JSClassOps gjs_repo_class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate repo_resolve, }; struct JSClass gjs_repo_class = { "GIRepository", 0, &gjs_repo_class_ops, }; GJS_JSAPI_RETURN_CONVENTION static JSObject* repo_new(JSContext *context) { JS::RootedObject repo(context, JS_NewObject(context, &gjs_repo_class)); if (repo == nullptr) return nullptr; gjs_debug_lifecycle(GJS_DEBUG_GREPO, "repo constructor, obj %p", repo.get()); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject versions(context, JS_NewPlainObject(context)); if (!JS_DefinePropertyById(context, repo, atoms.versions(), versions, JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; /* GLib/GObject/Gio are fixed at 2.0, since we depend on them * internally. */ JS::RootedString two_point_oh(context, JS_NewStringCopyZ(context, "2.0")); if (!JS_DefinePropertyById(context, versions, atoms.glib(), two_point_oh, JSPROP_PERMANENT) || !JS_DefinePropertyById(context, versions, atoms.gobject(), two_point_oh, JSPROP_PERMANENT) || !JS_DefinePropertyById(context, versions, atoms.gio(), two_point_oh, JSPROP_PERMANENT)) return nullptr; #if GLIB_CHECK_VERSION(2, 79, 2) # if defined(G_OS_UNIX) if (!JS_DefineProperty(context, versions, "GLibUnix", two_point_oh, JSPROP_PERMANENT) || !JS_DefineProperty(context, versions, "GioUnix", two_point_oh, JSPROP_PERMANENT)) return nullptr; # elif defined(G_OS_WIN32) if (!JS_DefineProperty(context, versions, "GLibWin32", two_point_oh, JSPROP_PERMANENT) || !JS_DefineProperty(context, versions, "GioWin32", two_point_oh, JSPROP_PERMANENT)) return nullptr; # endif // G_OS_UNIX/G_OS_WIN32 #endif // GLib >= 2.79.2 JS::RootedObject private_ns(context, JS_NewPlainObject(context)); if (!JS_DefinePropertyById(context, repo, atoms.private_ns_marker(), private_ns, JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; return repo; } bool gjs_define_repo(JSContext *cx, JS::MutableHandleObject repo) { repo.set(repo_new(cx)); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_constant_info(JSContext* cx, GIConstantInfo* info, JS::MutableHandleValue value) { GIArgument garg; g_constant_info_get_value(info, &garg); GjsAutoTypeInfo type_info = g_constant_info_get_type(info); bool ok = gjs_value_from_gi_argument(cx, value, type_info, &garg, true); g_constant_info_free_value(info, &garg); return ok; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_define_constant(JSContext *context, JS::HandleObject in_object, GIConstantInfo *info) { JS::RootedValue value(context); const char *name; if (!gjs_value_from_constant_info(context, info, &value)) return false; name = g_base_info_get_name((GIBaseInfo*) info); return JS_DefineProperty(context, in_object, name, value, GJS_MODULE_PROP_FLAGS); } #if GJS_VERBOSE_ENABLE_GI_USAGE void _gjs_log_info_usage(GIBaseInfo *info) { #define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" ) #define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" ) { char *details; GIInfoType info_type; GIBaseInfo *container; info_type = g_base_info_get_type(info); if (info_type == GI_INFO_TYPE_FUNCTION) { std::string args("{ "); int n_args; int i; GITransfer retval_transfer; n_args = g_callable_info_get_n_args((GICallableInfo*) info); for (i = 0; i < n_args; ++i) { GIArgInfo *arg; GIDirection direction; GITransfer transfer; arg = g_callable_info_get_arg((GICallableInfo*)info, i); direction = g_arg_info_get_direction(arg); transfer = g_arg_info_get_ownership_transfer(arg); if (i > 0) args += ", "; args += std::string("{ GI_DIRECTION_") + DIRECTION_STRING(direction) + ", GI_TRANSFER_" + TRANSFER_STRING(transfer) + " }"; g_base_info_unref((GIBaseInfo*) arg); } args += " }"; retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info); details = g_strdup_printf( ".details = { .func = { .retval_transfer = GI_TRANSFER_%s, " ".n_args = %d, .args = %s } }", TRANSFER_STRING(retval_transfer), n_args, args.c_str()); } else { details = g_strdup_printf(".details = { .nothing = {} }"); } container = g_base_info_get_container(info); gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },", gjs_info_type_name(info_type), g_base_info_get_namespace(info), container ? g_base_info_get_name(container) : "", g_base_info_get_name(info), details); g_free(details); } } #endif /* GJS_VERBOSE_ENABLE_GI_USAGE */ bool gjs_define_info(JSContext *context, JS::HandleObject in_object, GIBaseInfo *info, bool *defined) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(info); #endif *defined = true; switch (g_base_info_get_type(info)) { case GI_INFO_TYPE_FUNCTION: { JSObject *f; f = gjs_define_function(context, in_object, 0, (GICallableInfo*) info); if (f == NULL) return false; } break; case GI_INFO_TYPE_OBJECT: { GType gtype; gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info); if (g_type_is_a (gtype, G_TYPE_PARAM)) { if (!gjs_define_param_class(context, in_object)) return false; } else if (g_type_is_a (gtype, G_TYPE_OBJECT)) { JS::RootedObject ignored1(context), ignored2(context); if (!ObjectPrototype::define_class(context, in_object, info, gtype, nullptr, 0, &ignored1, &ignored2)) return false; } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { JS::RootedObject ignored(context); if (!FundamentalPrototype::define_class(context, in_object, info, &ignored)) return false; } else { gjs_throw (context, "Unsupported type %s, deriving from fundamental %s", g_type_name(gtype), g_type_name(g_type_fundamental(gtype))); return false; } } break; case GI_INFO_TYPE_STRUCT: /* We don't want GType structures in the namespace, we expose their fields as vfuncs and their methods as static methods */ if (g_struct_info_is_gtype_struct((GIStructInfo*) info)) { *defined = false; break; } /* Fall through */ case GI_INFO_TYPE_BOXED: if (!BoxedPrototype::define_class(context, in_object, info)) return false; break; case GI_INFO_TYPE_UNION: if (!UnionPrototype::define_class(context, in_object, (GIUnionInfo*)info)) return false; break; case GI_INFO_TYPE_ENUM: if (g_enum_info_get_error_domain((GIEnumInfo*) info)) { /* define as GError subclass */ if (!ErrorPrototype::define_class(context, in_object, info)) return false; break; } [[fallthrough]]; case GI_INFO_TYPE_FLAGS: if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info)) return false; break; case GI_INFO_TYPE_CONSTANT: if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info)) return false; break; case GI_INFO_TYPE_INTERFACE: { JS::RootedObject ignored1(context), ignored2(context); if (!InterfacePrototype::create_class( context, in_object, info, g_registered_type_info_get_g_type(info), &ignored1, &ignored2)) return false; } break; case GI_INFO_TYPE_INVALID: case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: case GI_INFO_TYPE_UNRESOLVED: default: gjs_throw(context, "API of type %s not implemented, cannot define %s.%s", gjs_info_type_name(g_base_info_get_type(info)), g_base_info_get_namespace(info), g_base_info_get_name(info)); return false; } return true; } /* Get the "unknown namespace", which should be used for unnamespaced types */ JSObject* gjs_lookup_private_namespace(JSContext *context) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return gjs_lookup_namespace_object_by_name(context, atoms.private_ns_marker()); } /* Get the namespace object that the GIBaseInfo should be inside */ JSObject* gjs_lookup_namespace_object(JSContext *context, GIBaseInfo *info) { const char *ns; ns = g_base_info_get_namespace(info); if (ns == NULL) { gjs_throw(context, "%s '%s' does not have a namespace", gjs_info_type_name(g_base_info_get_type(info)), g_base_info_get_name(info)); return NULL; } JS::RootedId ns_name(context, gjs_intern_string_to_id(context, ns)); if (ns_name.isVoid()) return nullptr; return gjs_lookup_namespace_object_by_name(context, ns_name); } /* Check if an exception's 'name' property is equal to ImportError. Ignores * all errors that might arise. */ [[nodiscard]] static bool is_import_error(JSContext* cx, JS::HandleValue thrown_value) { if (!thrown_value.isObject()) return false; JS::AutoSaveExceptionState saved_exc(cx); JS::RootedObject exc(cx, &thrown_value.toObject()); JS::RootedValue exc_name(cx); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); bool eq; bool retval = JS_GetPropertyById(cx, exc, atoms.name(), &exc_name) && JS_StringEqualsLiteral(cx, exc_name.toString(), "ImportError", &eq) && eq; saved_exc.restore(); return retval; } GJS_JSAPI_RETURN_CONVENTION static bool lookup_override_function(JSContext *cx, JS::HandleId ns_name, JS::MutableHandleValue function) { JS::AutoSaveExceptionState saved_exc(cx); JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)}; JS::RootedValue importer( cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS)); g_assert(importer.isObject()); JS::RootedObject overridespkg(cx), module(cx); JS::RootedObject importer_obj(cx, &importer.toObject()); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, importer_obj, "importer", atoms.overrides(), &overridespkg)) return false; if (!gjs_object_require_property(cx, overridespkg, "GI repository object", ns_name, &module)) { JS::RootedValue exc(cx); JS_GetPendingException(cx, &exc); /* If the exception was an ImportError (i.e., module not found) then * we simply didn't have an override, don't throw an exception */ if (is_import_error(cx, exc)) { saved_exc.restore(); return true; } return false; } // If the override module is present, it must have a callable _init(). An // override module without _init() is probably unintentional. (function // being undefined means there was no override module.) if (!gjs_object_require_property(cx, module, "override module", atoms.init(), function) || !function.isObject() || !JS::IsCallable(&function.toObject())) { gjs_throw(cx, "Unexpected value for _init in overrides module"); return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static JSObject* lookup_namespace(JSContext* cx, JSObject* global, JS::HandleId ns_name) { JS::RootedObject native_registry(cx, gjs_get_native_registry(global)); auto priv = GjsContextPrivate::from_cx(cx); const GjsAtoms& atoms = priv->atoms(); JS::RootedObject gi(cx); if (!gjs_global_registry_get(cx, native_registry, atoms.gi(), &gi)) return nullptr; if (!gi) { gjs_throw(cx, "No gi property in native registry"); return nullptr; } JS::RootedObject retval(cx); if (!gjs_object_require_property(cx, gi, "GI repository object", ns_name, &retval)) return NULL; return retval; } JSObject* gjs_lookup_namespace_object_by_name(JSContext* cx, JS::HandleId ns_name) { JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); g_assert(gjs_global_get_type(global) == GjsGlobalType::DEFAULT); return lookup_namespace(cx, global, ns_name); } const char* gjs_info_type_name(GIInfoType type) { switch (type) { case GI_INFO_TYPE_INVALID: return "INVALID"; case GI_INFO_TYPE_FUNCTION: return "FUNCTION"; case GI_INFO_TYPE_CALLBACK: return "CALLBACK"; case GI_INFO_TYPE_STRUCT: return "STRUCT"; case GI_INFO_TYPE_BOXED: return "BOXED"; case GI_INFO_TYPE_ENUM: return "ENUM"; case GI_INFO_TYPE_FLAGS: return "FLAGS"; case GI_INFO_TYPE_OBJECT: return "OBJECT"; case GI_INFO_TYPE_INTERFACE: return "INTERFACE"; case GI_INFO_TYPE_CONSTANT: return "CONSTANT"; case GI_INFO_TYPE_UNION: return "UNION"; case GI_INFO_TYPE_VALUE: return "VALUE"; case GI_INFO_TYPE_SIGNAL: return "SIGNAL"; case GI_INFO_TYPE_VFUNC: return "VFUNC"; case GI_INFO_TYPE_PROPERTY: return "PROPERTY"; case GI_INFO_TYPE_FIELD: return "FIELD"; case GI_INFO_TYPE_ARG: return "ARG"; case GI_INFO_TYPE_TYPE: return "TYPE"; case GI_INFO_TYPE_UNRESOLVED: return "UNRESOLVED"; case GI_INFO_TYPE_INVALID_0: g_assert_not_reached(); return "----INVALID0----"; default: return "???"; } } char* gjs_hyphen_from_camel(const char *camel_name) { GString *s; const char *p; /* four hyphens should be reasonable guess */ s = g_string_sized_new(strlen(camel_name) + 4 + 1); for (p = camel_name; *p; ++p) { if (g_ascii_isupper(*p)) { g_string_append_c(s, '-'); g_string_append_c(s, g_ascii_tolower(*p)); } else { g_string_append_c(s, *p); } } return g_string_free(s, false); } JSObject * gjs_lookup_generic_constructor(JSContext *context, GIBaseInfo *info) { JS::RootedObject in_object{context, gjs_lookup_namespace_object(context, info)}; const char* constructor_name = g_base_info_get_name(info); if (G_UNLIKELY (!in_object)) return NULL; JS::RootedValue value(context); if (!JS_GetProperty(context, in_object, constructor_name, &value)) return NULL; if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Constructor of %s.%s was the wrong type, expected an object", g_base_info_get_namespace(info), constructor_name); return NULL; } return &value.toObject(); } JSObject * gjs_lookup_generic_prototype(JSContext *context, GIBaseInfo *info) { JS::RootedObject constructor(context, gjs_lookup_generic_constructor(context, info)); if (G_UNLIKELY(!constructor)) return NULL; const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedValue value(context); if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value)) return NULL; if (G_UNLIKELY(!value.isObject())) { gjs_throw(context, "Prototype of %s.%s was the wrong type, expected an object", g_base_info_get_namespace(info), g_base_info_get_name(info)); return NULL; } return &value.toObject(); } JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, GIBaseInfo* info) { JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info)); if (!proto) return nullptr; return JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto); } // Handle the case where g_irepository_find_by_gtype() returns a type in Gio // that should be in GioUnix or GioWin32. This may be an interface, class, or // boxed. This function only needs to be called if you are going to do something // with the GIBaseInfo that involves handing a JS object to the user. Otherwise, // use g_irepository_find_by_gtype() directly. GIBaseInfo* gjs_lookup_gtype(GIRepository* repo, GType gtype) { GjsAutoBaseInfo retval = g_irepository_find_by_gtype(repo, gtype); if (!retval) return nullptr; #if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32)) # ifdef G_OS_UNIX static const char* c_prefix = "GUnix"; static const char* new_ns = "GioUnix"; # else // G_OS_WIN32 static const char* c_prefix = "GWin32"; static const char* new_ns = "GioWin32"; # endif const char* ns = g_base_info_get_namespace(retval); if (strcmp(ns, "Gio") != 0) return retval.release(); const char* gtype_name = g_type_name(gtype); if (!g_str_has_prefix(gtype_name, c_prefix)) return retval.release(); const char* new_name = gtype_name + strlen(c_prefix); GIBaseInfo* new_info = g_irepository_find_by_name(repo, new_ns, new_name); if (new_info) return new_info; #endif // GLib >= 2.79.2 return retval.release(); } cjs-128.1/gi/repo.h0000664000175000017500000000363415116312211012744 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_REPO_H_ #define GI_REPO_H_ #include #include #include #include #include "cjs/macros.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_repo(JSContext *cx, JS::MutableHandleObject repo); [[nodiscard]] const char* gjs_info_type_name(GIInfoType type); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_private_namespace (JSContext *context); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_namespace_object (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject *gjs_lookup_namespace_object_by_name(JSContext *context, JS::HandleId name); GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_lookup_generic_constructor (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject * gjs_lookup_generic_prototype (JSContext *context, GIBaseInfo *info); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, GIBaseInfo* info); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_info(JSContext *context, JS::HandleObject in_object, GIBaseInfo *info, bool *defined); [[nodiscard]] char* gjs_hyphen_from_camel(const char* camel_name); [[nodiscard]] GIBaseInfo* gjs_lookup_gtype(GIRepository*, GType); #if GJS_VERBOSE_ENABLE_GI_USAGE void _gjs_log_info_usage(GIBaseInfo *info); #endif #endif // GI_REPO_H_ cjs-128.1/gi/toggle.cpp0000664000175000017500000001530415116312211013610 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. // SPDX-FileCopyrightText: 2021 Canonical Ltd. // SPDX-FileContributor: Authored by: Philip Chimento // SPDX-FileContributor: Philip Chimento // SPDX-FileContributor: Marco Trevisan #include #include // for find_if #include #include #include // for pair #include "gi/object.h" #include "gi/toggle.h" #include "util/log.h" /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */ inline void debug(const char* did GJS_USED_VERBOSE_LIFECYCLE, const ObjectInstance* object GJS_USED_VERBOSE_LIFECYCLE) { gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue %s %p (%s @ %p)", did, object, object ? g_type_name(object->gtype()) : "", object ? object->ptr() : nullptr); } void ToggleQueue::lock() { auto holding_thread = std::thread::id(); auto current_thread = std::this_thread::get_id(); while (!m_holder.compare_exchange_weak(holding_thread, current_thread, std::memory_order_acquire)) { // In case the current thread is holding the lock, we can just try // again, checking if this is still true and in case continue if (holding_thread != current_thread) holding_thread = std::thread::id(); } m_holder_ref_count++; } void ToggleQueue::maybe_unlock() { g_assert(owns_lock() && "Nothing to unlock here"); if (!(--m_holder_ref_count)) m_holder.store(std::thread::id(), std::memory_order_release); } std::deque::iterator ToggleQueue::find_operation_locked( const ObjectInstance* obj, ToggleQueue::Direction direction) { return std::find_if( q.begin(), q.end(), [obj, direction](const Item& item) -> bool { return item.object == obj && item.direction == direction; }); } std::deque::const_iterator ToggleQueue::find_operation_locked(const ObjectInstance* obj, ToggleQueue::Direction direction) const { return std::find_if( q.begin(), q.end(), [obj, direction](const Item& item) -> bool { return item.object == obj && item.direction == direction; }); } void ToggleQueue::handle_all_toggles(Handler handler) { g_assert(owns_lock() && "Unsafe access to queue"); while (handle_toggle(handler)) ; } gboolean ToggleQueue::idle_handle_toggle(void *data) { auto self = Locked(static_cast(data)); self->handle_all_toggles(self->m_toggle_handler); return G_SOURCE_REMOVE; } void ToggleQueue::idle_destroy_notify(void *data) { auto self = Locked(static_cast(data)); self->m_idle_id = 0; self->m_toggle_handler = nullptr; } std::pair ToggleQueue::is_queued(ObjectInstance* obj) const { g_assert(owns_lock() && "Unsafe access to queue"); bool has_toggle_down = find_operation_locked(obj, DOWN) != q.end(); bool has_toggle_up = find_operation_locked(obj, UP) != q.end(); return {has_toggle_down, has_toggle_up}; } std::pair ToggleQueue::cancel(ObjectInstance* obj) { debug("cancel", obj); g_assert(owns_lock() && "Unsafe access to queue"); bool had_toggle_down = false; bool had_toggle_up = false; for (auto it = q.begin(); it != q.end();) { if (it->object == obj) { had_toggle_down |= (it->direction == Direction::DOWN); had_toggle_up |= (it->direction == Direction::UP); it = q.erase(it); continue; } it++; } gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue: %p (%p) was %s", obj, obj ? obj->ptr() : nullptr, had_toggle_down && had_toggle_up ? "queued to toggle BOTH" : had_toggle_down ? "queued to toggle DOWN" : had_toggle_up ? "queued to toggle UP" : "not queued"); return {had_toggle_down, had_toggle_up}; } bool ToggleQueue::handle_toggle(Handler handler) { g_assert(owns_lock() && "Unsafe access to queue"); if (q.empty()) return false; auto const& item = q.front(); if (item.direction == UP) debug("handle UP", item.object); else debug("handle DOWN", item.object); handler(item.object, item.direction); q.pop_front(); return true; } void ToggleQueue::shutdown(void) { debug("shutdown", nullptr); g_assert(((void)"Queue should have been emptied before shutting down", q.empty())); m_shutdown = true; } void ToggleQueue::enqueue(ObjectInstance* obj, ToggleQueue::Direction direction, // https://trac.cppcheck.net/ticket/10733 // cppcheck-suppress passedByValue ToggleQueue::Handler handler) { g_assert(owns_lock() && "Unsafe access to queue"); if (G_UNLIKELY (m_shutdown)) { gjs_debug(GJS_DEBUG_GOBJECT, "Enqueuing GObject %p to toggle %s after " "shutdown, probably from another thread (%p).", obj->ptr(), direction == UP ? "UP" : "DOWN", g_thread_self()); return; } auto other_item = find_operation_locked(obj, direction == UP ? DOWN : UP); if (other_item != q.end()) { if (direction == UP) { debug("enqueue UP, dequeuing already DOWN object", obj); } else { debug("enqueue DOWN, dequeuing already UP object", obj); } q.erase(other_item); return; } /* Only keep an unowned reference on the object here, as if we're here, the * JSObject wrapper has already a reference and we don't want to cause * any weak notify in case it has lost one already in the main thread. * So let's just save the pointer to keep track of the object till we * don't handle this toggle. * We rely on object's cancelling the queue in case an object gets * finalized earlier than we've processed it. */ q.emplace_back(obj, direction); if (direction == UP) { debug("enqueue UP", obj); } else { debug("enqueue DOWN", obj); } if (m_idle_id) { g_assert(((void) "Should always enqueue with the same handler", m_toggle_handler == handler)); return; } m_toggle_handler = handler; m_idle_id = g_idle_add_full(G_PRIORITY_HIGH, idle_handle_toggle, this, idle_destroy_notify); } cjs-128.1/gi/toggle.h0000664000175000017500000000703515116312211013257 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. // SPDX-FileCopyrightText: 2021 Canonical Ltd. // SPDX-FileContributor: Authored by: Philip Chimento // SPDX-FileContributor: Philip Chimento // SPDX-FileContributor: Marco Trevisan #ifndef GI_TOGGLE_H_ #define GI_TOGGLE_H_ #include #include #include #include #include // for pair #include // for gboolean class ObjectInstance; namespace Gjs { namespace Test { struct ToggleQueue; } } /* Thread-safe queue for enqueueing toggle-up or toggle-down events on GObjects * from any thread. For more information, see object.cpp, comments near * wrapped_gobj_toggle_notify(). */ class ToggleQueue { public: enum Direction { DOWN, UP }; using Handler = void (*)(ObjectInstance*, Direction); private: friend Gjs::Test::ToggleQueue; struct Item { Item() {} Item(ObjectInstance* o, Direction d) : object(o), direction(d) {} ObjectInstance* object; ToggleQueue::Direction direction; }; struct Locked { explicit Locked(ToggleQueue* queue) { queue->lock(); } ~Locked() { get_default_unlocked().maybe_unlock(); } ToggleQueue* operator->() { return &get_default_unlocked(); } }; std::deque q; std::atomic_bool m_shutdown = ATOMIC_VAR_INIT(false); unsigned m_idle_id = 0; Handler m_toggle_handler = nullptr; std::atomic m_holder = std::thread::id(); unsigned m_holder_ref_count = 0; void lock(); void maybe_unlock(); [[nodiscard]] bool is_locked() const { return m_holder != std::thread::id(); } [[nodiscard]] bool owns_lock() const { return m_holder == std::this_thread::get_id(); } [[nodiscard]] std::deque::iterator find_operation_locked( const ObjectInstance* obj, Direction direction); [[nodiscard]] std::deque::const_iterator find_operation_locked( const ObjectInstance* obj, Direction direction) const; static gboolean idle_handle_toggle(void *data); static void idle_destroy_notify(void *data); [[nodiscard]] static ToggleQueue& get_default_unlocked() { static ToggleQueue the_singleton; return the_singleton; } public: /* These two functions return a pair DOWN, UP signifying whether toggles * are / were queued. is_queued() just checks and does not modify. */ [[nodiscard]] std::pair is_queued(ObjectInstance* obj) const; /* Cancels pending toggles and returns whether any were queued. */ std::pair cancel(ObjectInstance* obj); /* Pops a toggle from the queue and processes it. Call this if you don't * want to wait for it to be processed in idle time. Returns false if queue * is empty. */ bool handle_toggle(Handler handler); void handle_all_toggles(Handler handler); /* After calling this, the toggle queue won't accept any more toggles. Only * intended for use when destroying the JSContext and breaking the * associations between C and JS objects. */ void shutdown(void); /* Queues a toggle to be processed in idle time. */ void enqueue(ObjectInstance* obj, Direction direction, Handler handler); [[nodiscard]] static Locked get_default() { return Locked(&get_default_unlocked()); } }; #endif // GI_TOGGLE_H_ cjs-128.1/gi/union.cpp0000664000175000017500000001517415116312211013464 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include #include #include #include #include #include // for UniqueChars #include #include "gi/arg-inl.h" #include "gi/function.h" #include "gi/repo.h" #include "gi/union.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" UnionPrototype::UnionPrototype(GIUnionInfo* info, GType gtype) : GIWrapperPrototype(info, gtype) { GJS_INC_COUNTER(union_prototype); } UnionPrototype::~UnionPrototype(void) { GJS_DEC_COUNTER(union_prototype); } UnionInstance::UnionInstance(UnionPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { GJS_INC_COUNTER(union_instance); } UnionInstance::~UnionInstance(void) { if (m_ptr) { g_boxed_free(gtype(), m_ptr); m_ptr = nullptr; } GJS_DEC_COUNTER(union_instance); } // See GIWrapperBase::resolve(). bool UnionPrototype::resolve_impl(JSContext* context, JS::HandleObject obj, JS::HandleId id, bool* resolved) { JS::UniqueChars prop_name; if (!gjs_get_string_id(context, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } // Look for methods and other class properties GjsAutoFunctionInfo method_info = g_union_info_find_method(info(), prop_name.get()); if (method_info) { #if GJS_VERBOSE_ENABLE_GI_USAGE _gjs_log_info_usage(method_info); #endif if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) { gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s.%s", method_info.name(), ns(), name()); /* obj is union proto */ if (!gjs_define_function(context, obj, gtype(), method_info)) return false; *resolved = true; /* we defined the prop in object_proto */ } else { *resolved = false; } } else { *resolved = false; } return true; } GJS_JSAPI_RETURN_CONVENTION static void* union_new(JSContext* context, JS::HandleObject this_obj, const JS::CallArgs& args, GIUnionInfo* info) { int n_methods; int i; /* Find a zero-args constructor and call it */ n_methods = g_union_info_get_n_methods(info); for (i = 0; i < n_methods; ++i) { GIFunctionInfoFlags flags; GjsAutoFunctionInfo func_info = g_union_info_get_method(info, i); flags = g_function_info_get_flags(func_info); if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0 && g_callable_info_get_n_args((GICallableInfo*) func_info) == 0) { GIArgument rval; if (!gjs_invoke_constructor_from_c(context, func_info, this_obj, args, &rval)) return nullptr; if (!gjs_arg_get(&rval)) { gjs_throw(context, "Unable to construct union type %s as its" "constructor function returned null", g_base_info_get_name(info)); return nullptr; } return gjs_arg_get(&rval); } } gjs_throw(context, "Unable to construct union type %s since it has no zero-args , can only wrap an existing one", g_base_info_get_name((GIBaseInfo*) info)); return nullptr; } // See GIWrapperBase::constructor(). bool UnionInstance::constructor_impl(JSContext* context, JS::HandleObject object, const JS::CallArgs& args) { if (args.length() > 0 && !JS::WarnUTF8(context, "Arguments to constructor of %s ignored", name())) return false; m_ptr = union_new(context, object, args, info()); return !!m_ptr; } // clang-format off const struct JSClassOps UnionBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate nullptr, // newEnumerate &UnionBase::resolve, nullptr, // mayResolve &UnionBase::finalize, }; const struct JSClass UnionBase::klass = { "GObject_Union", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, &UnionBase::class_ops }; // clang-format on bool UnionPrototype::define_class(JSContext* context, JS::HandleObject in_object, GIUnionInfo* info) { GType gtype; JS::RootedObject prototype(context), constructor(context); /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return false; } return !!UnionPrototype::create_class(context, in_object, info, gtype, &constructor, &prototype); } JSObject* UnionInstance::new_for_c_union(JSContext* context, GIUnionInfo* info, void* gboxed) { GType gtype; if (!gboxed) return nullptr; /* For certain unions, we may be able to relax this in the future by * directly allocating union memory, as we do for structures in boxed.c */ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) info); if (gtype == G_TYPE_NONE) { gjs_throw(context, "Unions must currently be registered as boxed types"); return nullptr; } gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping union %s %p with JSObject", g_base_info_get_name((GIBaseInfo *)info), gboxed); JS::RootedObject obj(context, gjs_new_object_with_generic_prototype(context, info)); if (!obj) return nullptr; UnionInstance* priv = UnionInstance::new_for_js_object(context, obj); priv->copy_union(gboxed); return obj; } void* UnionInstance::copy_ptr(JSContext* cx, GType gtype, void* ptr) { if (g_type_is_a(gtype, G_TYPE_BOXED)) return g_boxed_copy(gtype, ptr); gjs_throw(cx, "Can't transfer ownership of a union type not registered as " "boxed"); return nullptr; } cjs-128.1/gi/union.h0000664000175000017500000000566515116312211013135 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_UNION_H_ #define GI_UNION_H_ #include #include #include #include #include "gi/cwrapper.h" #include "gi/wrapperutils.h" #include "cjs/macros.h" #include "util/log.h" namespace JS { class CallArgs; } struct JSClass; struct JSClassOps; class UnionPrototype; class UnionInstance; class UnionBase : public GIWrapperBase { friend class CWrapperPointerOps; friend class GIWrapperBase; protected: explicit UnionBase(UnionPrototype* proto = nullptr) : GIWrapperBase(proto) {} static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GBOXED; static constexpr const char* DEBUG_TAG = "union"; static const JSClassOps class_ops; static const JSClass klass; }; class UnionPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; static constexpr InfoType::Tag info_type_tag = InfoType::Union; explicit UnionPrototype(GIUnionInfo* info, GType gtype); ~UnionPrototype(void); GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved); // Overrides GIWrapperPrototype::constructor_nargs(). [[nodiscard]] unsigned constructor_nargs(void) const { return 0; } public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, GIUnionInfo* info); }; class UnionInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; explicit UnionInstance(UnionPrototype* prototype, JS::HandleObject obj); ~UnionInstance(void); GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_union(JSContext* cx, GIUnionInfo* info, void* gboxed); /* * UnionInstance::copy_union: * * Allocate a new union pointer using g_boxed_copy(), from a raw union * pointer. */ void copy_union(void* ptr) { m_ptr = g_boxed_copy(gtype(), ptr); } GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr); }; #endif // GI_UNION_H_ cjs-128.1/gi/utils-inl.h0000664000175000017500000000343015116312211013711 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include #include // IWYU pragma: keep (for find) #include // IWYU pragma: keep (for swap) #include template constexpr void* gjs_int_to_pointer(T v) { static_assert(std::is_integral_v, "Need integer value"); if constexpr (std::is_signed_v) return reinterpret_cast(static_cast(v)); else return reinterpret_cast(static_cast(v)); } template constexpr T gjs_pointer_to_int(void* p) { static_assert(std::is_integral_v, "Need integer value"); if constexpr (std::is_signed_v) return static_cast(reinterpret_cast(p)); else return static_cast(reinterpret_cast(p)); } template <> inline void* gjs_int_to_pointer(bool v) { return gjs_int_to_pointer(!!v); } template <> inline bool gjs_pointer_to_int(void* p) { return !!gjs_pointer_to_int(p); } namespace Gjs { template inline bool remove_one_from_unsorted_vector(std::vector* v, const T& value) { // This assumes that there's only a copy of the same value in the vector // so this needs to be ensured when populating it. // We use the swap and pop idiom to avoid moving all the values. auto it = std::find(v->begin(), v->end(), value); if (it != v->end()) { std::swap(*it, v->back()); v->pop_back(); g_assert(std::find(v->begin(), v->end(), value) == v->end()); return true; } return false; } } // namespace Gjs cjs-128.1/gi/value.cpp0000664000175000017500000013056615116312211013453 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #include #include // for NULL #include #include #include #include #include #include #include #include #include #include #include #include // for RootedVector #include // for RuntimeHeapIsCollecting #include #include #include #include // for UniqueChars #include #include #include #include // for InformalValueTypeName, JS_Get... #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/closure.h" #include "gi/foreign.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/gtype.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/objectbox.h" #include "util/log.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal(JSContext*, JS::MutableHandleValue, const GValue*, bool no_copy = false, bool is_introspected_signal = false, GIArgInfo* = nullptr, GITypeInfo* = nullptr); GJS_JSAPI_RETURN_CONVENTION static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg, const GValue* value) { switch (G_VALUE_TYPE(value)) { case G_TYPE_CHAR: gjs_arg_set(arg, g_value_get_schar(value)); return true; case G_TYPE_UCHAR: gjs_arg_set(arg, g_value_get_uchar(value)); return true; case G_TYPE_BOOLEAN: gjs_arg_set(arg, g_value_get_boolean(value)); return true; case G_TYPE_INT: gjs_arg_set(arg, g_value_get_int(value)); return true; case G_TYPE_UINT: gjs_arg_set(arg, g_value_get_uint(value)); return true; case G_TYPE_LONG: gjs_arg_set( // NOLINT(runtime/int) arg, g_value_get_long(value)); return true; case G_TYPE_ULONG: gjs_arg_set(arg, g_value_get_ulong(value)); return true; case G_TYPE_INT64: gjs_arg_set(arg, int64_t{g_value_get_int64(value)}); return true; case G_TYPE_UINT64: gjs_arg_set(arg, uint64_t{g_value_get_uint64(value)}); return true; case G_TYPE_FLOAT: gjs_arg_set(arg, g_value_get_float(value)); return true; case G_TYPE_DOUBLE: gjs_arg_set(arg, g_value_get_double(value)); return true; case G_TYPE_STRING: gjs_arg_set(arg, g_value_get_string(value)); return true; case G_TYPE_POINTER: gjs_arg_set(arg, g_value_get_pointer(value)); return true; case G_TYPE_VARIANT: gjs_arg_set(arg, g_value_get_variant(value)); return true; default: { if (g_value_fits_pointer(value)) { gjs_arg_set(arg, g_value_peek_pointer(value)); return true; } GType gtype = G_VALUE_TYPE(value); if (g_type_is_a(gtype, G_TYPE_FLAGS)) { gjs_arg_set(arg, g_value_get_flags(value)); return true; } if (g_type_is_a(gtype, G_TYPE_ENUM)) { gjs_arg_set(arg, g_value_get_enum(value)); return true; } if (g_type_is_a(gtype, G_TYPE_GTYPE)) { gjs_arg_set(arg, g_value_get_gtype(value)); return true; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { gjs_arg_set(arg, g_value_get_param(value)); return true; } } } gjs_throw(cx, "No know GArgument conversion for %s", G_VALUE_TYPE_NAME(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool maybe_release_signal_value(JSContext* cx, GjsAutoArgInfo const& arg_info, GITypeInfo* type_info, const GValue* gvalue, GITransfer transfer) { if (transfer == GI_TRANSFER_NOTHING) return true; GIArgument arg; if (!gjs_arg_set_from_gvalue(cx, &arg, gvalue)) return false; if (!gjs_gi_argument_release(cx, transfer, type_info, GjsArgumentFlags::ARG_OUT, &arg)) { gjs_throw(cx, "Cannot release argument %s value, we're gonna leak!", arg_info.name()); return false; } return true; } /* * Gets signal introspection info about closure, or NULL if not found. Currently * only works for signals on introspected GObjects, not signals on GJS-defined * GObjects nor standalone closures. The return value must be unreffed. */ [[nodiscard]] static GjsAutoSignalInfo get_signal_info_if_available( GSignalQuery* signal_query) { if (!signal_query->itype) return nullptr; GjsAutoBaseInfo obj = g_irepository_find_by_gtype(nullptr, signal_query->itype); if (!obj) return nullptr; GIInfoType info_type = obj.type(); if (info_type == GI_INFO_TYPE_OBJECT) return g_object_info_find_signal(obj, signal_query->signal_name); else if (info_type == GI_INFO_TYPE_INTERFACE) return g_interface_info_find_signal(obj, signal_query->signal_name); return nullptr; } /* * Fill in value_p with a JS array, converted from a C array stored as a pointer * in array_value, with its length stored in array_length_value. */ GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_array_and_length_values( JSContext* context, JS::MutableHandleValue value_p, GITypeInfo* array_type_info, const GValue* array_value, GIArgInfo* array_length_arg_info, GITypeInfo* array_length_type_info, const GValue* array_length_value, bool no_copy, bool is_introspected_signal) { JS::RootedValue array_length(context); g_assert(G_VALUE_HOLDS_POINTER(array_value)); g_assert(G_VALUE_HOLDS_INT(array_length_value)); if (!gjs_value_from_g_value_internal( context, &array_length, array_length_value, no_copy, is_introspected_signal, array_length_arg_info, array_length_type_info)) return false; GIArgument array_arg; gjs_arg_set(&array_arg, g_value_get_pointer(array_value)); return gjs_value_from_explicit_array( context, value_p, array_type_info, no_copy ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING, &array_arg, array_length.toInt32()); } // FIXME(3v1n0): Move into closure.cpp one day... void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values, const GValue* param_values, void* invocation_hint, void* marshal_data) { JSContext *context; unsigned i; GSignalQuery signal_query = { 0, }; gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", this); if (!is_valid()) { /* We were destroyed; become a no-op */ return; } context = m_cx; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); if (JS::RuntimeHeapIsCollecting()) { GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint; std::ostringstream message; message << "Attempting to call back into JSAPI during the sweeping " "phase of GC. This is most likely caused by not destroying " "a Clutter actor or Gtk+ widget with ::destroy signals " "connected, but can also be caused by using the destroy(), " "dispose(), or remove() vfuncs. Because it would crash the " "application, it has been blocked and the JS callback not " "invoked."; if (hint) { gpointer instance; g_signal_query(hint->signal_id, &signal_query); instance = g_value_peek_pointer(¶m_values[0]); message << "\nThe offending signal was " << signal_query.signal_name << " on " << g_type_name(G_TYPE_FROM_INSTANCE(instance)) << " " << instance << "."; } message << "\n" << gjs_dumpstack_string(); g_critical("%s", message.str().c_str()); return; } JSAutoRealm ar(context, callable()); if (marshal_data) { /* we are used for a signal handler */ guint signal_id; signal_id = GPOINTER_TO_UINT(marshal_data); g_signal_query(signal_id, &signal_query); if (!signal_query.signal_id) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called on invalid signal"); return; } if (signal_query.n_params + 1 != n_param_values) { gjs_debug(GJS_DEBUG_GCLOSURE, "Signal handler being called with wrong number of parameters"); return; } } /* Check if any parameters, such as array lengths, need to be eliminated * before we invoke the closure. */ struct ArgumentDetails { int array_len_index_for = -1; bool skip = false; GITypeInfo type_info; GjsAutoArgInfo arg_info; }; std::vector args_details(n_param_values); bool needs_cleanup = false; GjsAutoSignalInfo signal_info = get_signal_info_if_available(&signal_query); if (signal_info) { /* Start at argument 1, skip the instance parameter */ for (i = 1; i < n_param_values; ++i) { int array_len_pos; ArgumentDetails& arg_details = args_details[i]; arg_details.arg_info = g_callable_info_get_arg(signal_info, i - 1); g_arg_info_load_type(arg_details.arg_info, &arg_details.type_info); array_len_pos = g_type_info_get_array_length(&arg_details.type_info); if (array_len_pos != -1) { args_details[array_len_pos + 1].skip = true; arg_details.array_len_index_for = array_len_pos + 1; } if (!needs_cleanup && g_arg_info_get_ownership_transfer(arg_details.arg_info) != GI_TRANSFER_NOTHING) needs_cleanup = true; } } JS::RootedValueVector argv(context); /* May end up being less */ if (!argv.reserve(n_param_values)) g_error("Unable to reserve space"); JS::RootedValue argv_to_append(context); bool is_introspected_signal = !!signal_info; for (i = 0; i < n_param_values; ++i) { const GValue* gval = ¶m_values[i]; ArgumentDetails& arg_details = args_details[i]; bool no_copy; bool res; if (arg_details.skip) continue; no_copy = false; if (i >= 1 && signal_query.signal_id) { no_copy = (signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0; } if (arg_details.array_len_index_for != -1) { const GValue* array_len_gval = ¶m_values[arg_details.array_len_index_for]; ArgumentDetails& array_len_details = args_details[arg_details.array_len_index_for]; res = gjs_value_from_array_and_length_values( context, &argv_to_append, &arg_details.type_info, gval, array_len_details.arg_info, &array_len_details.type_info, array_len_gval, no_copy, is_introspected_signal); } else { res = gjs_value_from_g_value_internal( context, &argv_to_append, gval, no_copy, is_introspected_signal, arg_details.arg_info, &arg_details.type_info); } if (!res) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert arg %d in order to invoke closure", i); gjs_log_exception(context); return; } argv.infallibleAppend(argv_to_append); } JS::RootedValue rval(context); if (!invoke(nullptr, argv, &rval)) { if (JS_IsExceptionPending(context)) { gjs_log_exception_uncaught(context); } else { // "Uncatchable" exception thrown, we have to exit. This // matches the closure exit handling in function.cpp uint8_t code; if (gjs->should_exit(&code)) gjs->exit_immediately(code); // Some other uncatchable exception, e.g. out of memory g_error("Call to %s terminated with uncatchable exception", gjs_debug_callable(callable()).c_str()); } } if (needs_cleanup) { for (i = 0; i < n_param_values; ++i) { ArgumentDetails& arg_details = args_details[i]; if (!arg_details.arg_info) continue; GITransfer transfer = g_arg_info_get_ownership_transfer(arg_details.arg_info); if (transfer == GI_TRANSFER_NOTHING) continue; if (!maybe_release_signal_value(context, arg_details.arg_info, &arg_details.type_info, ¶m_values[i], transfer)) { gjs_log_exception(context); return; } } } // null return_value means the closure wasn't expected to return a value. // Discard the JS function's return value in that case. if (return_value != NULL) { if (rval.isUndefined()) { // Either an exception was thrown and logged, or the JS function // returned undefined. Leave the GValue uninitialized. // FIXME: not sure what happens on the other side with an // uninitialized GValue! return; } if (!gjs_value_to_g_value(context, rval, return_value)) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert return value when invoking closure"); gjs_log_exception(context); return; } } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_guess_g_type(JSContext* context, JS::Value value, GType* gtype_out) { g_assert(gtype_out && "Invalid return location"); if (value.isNull()) { *gtype_out = G_TYPE_POINTER; return true; } if (value.isString()) { *gtype_out = G_TYPE_STRING; return true; } if (value.isInt32()) { *gtype_out = G_TYPE_INT; return true; } if (value.isDouble()) { *gtype_out = G_TYPE_DOUBLE; return true; } if (value.isBoolean()) { *gtype_out = G_TYPE_BOOLEAN; return true; } if (value.isBigInt()) { // Assume that if the value is negative or within the int64_t limit, // then we're handling a signed integer, otherwise unsigned. int64_t ignored; if (JS::BigIntIsNegative(value.toBigInt()) || JS::BigIntFits(value.toBigInt(), &ignored)) *gtype_out = G_TYPE_INT64; else *gtype_out = G_TYPE_UINT64; return true; } if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); return gjs_gtype_get_actual_gtype(context, obj, gtype_out); } *gtype_out = G_TYPE_INVALID; return true; } static bool throw_expect_type(JSContext* cx, JS::HandleValue value, const char* expected_type, GType gtype = 0, bool out_of_range = false) { JS::UniqueChars val_str; out_of_range = (out_of_range && value.isNumeric()); if (out_of_range) { JS::RootedString str(cx, JS::ToString(cx, value)); if (str) val_str = JS_EncodeStringToUTF8(cx, str); } gjs_throw(cx, "Wrong type %s; %s%s%s expected%s%s", JS::InformalValueTypeName(value), expected_type, gtype ? " " : "", gtype ? g_type_name(gtype) : "", out_of_range ? ". But it's out of range: " : "", out_of_range ? val_str.get() : ""); return false; /* for convenience */ } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_to_g_value_internal(JSContext *context, JS::HandleValue value, GValue *gvalue, bool no_copy) { GType gtype; bool out_of_range = false; gtype = G_VALUE_TYPE(gvalue); if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); GType boxed_gtype; if (!gjs_gtype_get_actual_gtype(context, obj, &boxed_gtype)) return false; // Don't unbox GValue if the GValue's gtype is GObject.Value if (g_type_is_a(boxed_gtype, G_TYPE_VALUE) && gtype != G_TYPE_VALUE) { if (no_copy) { gjs_throw( context, "Cannot convert GObject.Value object without copying."); return false; } GValue* source = BoxedBase::to_c_ptr(context, obj); // Only initialize the value if it doesn't have a type // and our source GValue has been initialized GType source_gtype = G_VALUE_TYPE(source); if (gtype == 0) { if (source_gtype == 0) { gjs_throw(context, "GObject.Value is not initialized with a type"); return false; } g_value_init(gvalue, source_gtype); } GType dest_gtype = G_VALUE_TYPE(gvalue); if (!g_value_type_compatible(source_gtype, dest_gtype)) { gjs_throw(context, "GObject.Value expected GType %s, found %s", g_type_name(dest_gtype), g_type_name(source_gtype)); return false; } g_value_copy(source, gvalue); return true; } } if (gtype == 0) { if (!gjs_value_guess_g_type(context, value, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(context, "Could not guess unspecified GValue type"); return false; } gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Guessed GValue type %s from JS Value", g_type_name(gtype)); g_value_init(gvalue, gtype); } gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting JS::Value to gtype %s", g_type_name(gtype)); if (gtype == G_TYPE_STRING) { /* Don't use ValueToString since we don't want to just toString() * everything automatically */ if (value.isNull()) { g_value_set_string(gvalue, NULL); } else if (value.isString()) { JS::RootedString str(context, value.toString()); JS::UniqueChars utf8_string(JS_EncodeStringToUTF8(context, str)); if (!utf8_string) return false; g_value_set_string(gvalue, utf8_string.get()); } else { return throw_expect_type(context, value, "string"); } } else if (gtype == G_TYPE_CHAR) { int32_t i; if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { g_value_set_schar(gvalue, static_cast(i)); } else { return throw_expect_type(context, value, "char", 0, out_of_range); } } else if (gtype == G_TYPE_UCHAR) { uint32_t i; if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { g_value_set_uchar(gvalue, (unsigned char)i); } else { return throw_expect_type(context, value, "unsigned char", 0, out_of_range); } } else if (gtype == G_TYPE_INT) { gint32 i; if (Gjs::js_value_to_c(context, value, &i)) { g_value_set_int(gvalue, i); } else { return throw_expect_type(context, value, "integer"); } } else if (gtype == G_TYPE_INT64) { int64_t i; if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { g_value_set_int64(gvalue, i); } else { return throw_expect_type(context, value, "64-bit integer", 0, out_of_range); } } else if (gtype == G_TYPE_DOUBLE) { gdouble d; if (Gjs::js_value_to_c(context, value, &d)) { g_value_set_double(gvalue, d); } else { return throw_expect_type(context, value, "double"); } } else if (gtype == G_TYPE_FLOAT) { gdouble d; if (Gjs::js_value_to_c_checked(context, value, &d, &out_of_range) && !out_of_range) { g_value_set_float(gvalue, d); } else { return throw_expect_type(context, value, "float", 0, out_of_range); } } else if (gtype == G_TYPE_UINT) { guint32 i; if (Gjs::js_value_to_c(context, value, &i)) { g_value_set_uint(gvalue, i); } else { return throw_expect_type(context, value, "unsigned integer"); } } else if (gtype == G_TYPE_UINT64) { uint64_t i; if (Gjs::js_value_to_c_checked(context, value, &i, &out_of_range) && !out_of_range) { g_value_set_uint64(gvalue, i); } else { return throw_expect_type(context, value, "unsigned 64-bit integer", 0, out_of_range); } } else if (gtype == G_TYPE_BOOLEAN) { /* JS::ToBoolean() can't fail */ g_value_set_boolean(gvalue, JS::ToBoolean(value)); } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { GObject *gobj; gobj = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!ObjectBase::typecheck(context, obj, nullptr, gtype) || !ObjectBase::to_c_ptr(context, obj, &gobj)) return false; if (!gobj) return true; // treat disposed object as if value.isNull() } else { return throw_expect_type(context, value, "object", gtype); } g_value_set_object(gvalue, gobj); } else if (gtype == G_TYPE_STRV) { if (value.isNull()) return true; bool is_array; if (!JS::IsArrayObject(context, value, &is_array)) return false; if (!is_array) return throw_expect_type(context, value, "strv"); JS::RootedObject array_obj(context, &value.toObject()); uint32_t length; if (!JS::GetArrayLength(context, array_obj, &length)) return throw_expect_type(context, value, "strv"); void* result; if (!gjs_array_to_strv(context, value, length, &result)) return false; g_value_take_boxed(gvalue, static_cast(result)); } else if (g_type_is_a(gtype, G_TYPE_BOXED)) { void *gboxed; gboxed = NULL; if (value.isNull()) return true; /* special case GValue */ if (gtype == G_TYPE_VALUE) { /* explicitly handle values that are already GValues to avoid infinite recursion */ if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); GType guessed_gtype; if (!gjs_value_guess_g_type(context, value, &guessed_gtype)) return false; if (guessed_gtype == G_TYPE_VALUE) { gboxed = BoxedBase::to_c_ptr(context, obj); g_value_set_boxed(gvalue, gboxed); return true; } } Gjs::AutoGValue nested_gvalue; if (!gjs_value_to_g_value(context, value, &nested_gvalue)) return false; g_value_set_boxed(gvalue, &nested_gvalue); return true; } if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (gtype == ObjectBox::gtype()) { g_value_set_boxed(gvalue, ObjectBox::boxed(context, obj).get()); return true; } else if (gtype == G_TYPE_ERROR) { /* special case GError */ gboxed = ErrorBase::to_c_ptr(context, obj); if (!gboxed) return false; } else if (gtype == G_TYPE_BYTE_ARRAY) { /* special case GByteArray */ JS::RootedObject obj(context, &value.toObject()); if (JS_IsUint8Array(obj)) { g_value_take_boxed(gvalue, gjs_byte_array_get_byte_array(obj)); return true; } } else if (gtype == G_TYPE_ARRAY) { gjs_throw(context, "Converting %s to GArray is not supported", JS::InformalValueTypeName(value)); return false; } else if (gtype == G_TYPE_PTR_ARRAY) { gjs_throw(context, "Converting %s to GArray is not supported", JS::InformalValueTypeName(value)); return false; } else if (gtype == G_TYPE_HASH_TABLE) { gjs_throw(context, "Converting %s to GHashTable is not supported", JS::InformalValueTypeName(value)); return false; } else { GjsAutoBaseInfo registered = g_irepository_find_by_gtype(nullptr, gtype); /* We don't necessarily have the typelib loaded when we first see the structure... */ if (registered) { GIInfoType info_type = registered.type(); if (info_type == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign( registered.as())) { GIArgument arg; if (!gjs_struct_foreign_convert_to_gi_argument( context, value, registered, nullptr, GJS_ARGUMENT_ARGUMENT, GI_TRANSFER_NOTHING, GjsArgumentFlags::MAY_BE_NULL, &arg)) return false; gboxed = gjs_arg_get(&arg); } } /* First try a union, if that fails, assume a boxed struct. Distinguishing which one is expected would require checking the associated GIBaseInfo, which is not necessary possible, if e.g. we see the GType without loading the typelib. */ if (!gboxed) { if (UnionBase::typecheck(context, obj, nullptr, gtype, GjsTypecheckNoThrow())) { gboxed = UnionBase::to_c_ptr(context, obj); } else { if (!BoxedBase::typecheck(context, obj, nullptr, gtype)) return false; gboxed = BoxedBase::to_c_ptr(context, obj); } if (!gboxed) return false; } } } else { return throw_expect_type(context, value, "boxed type", gtype); } if (no_copy) g_value_set_static_boxed(gvalue, gboxed); else g_value_set_boxed(gvalue, gboxed); } else if (gtype == G_TYPE_VARIANT) { GVariant *variant = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!BoxedBase::typecheck(context, obj, nullptr, G_TYPE_VARIANT)) return false; variant = BoxedBase::to_c_ptr(context, obj); if (!variant) return false; } else { return throw_expect_type(context, value, "boxed type", gtype); } g_value_set_variant (gvalue, variant); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { int64_t value_int64; if (Gjs::js_value_to_c(context, value, &value_int64)) { GEnumValue *v; GjsAutoTypeClass enum_class(gtype); /* See arg.c:_gjs_enum_to_int() */ v = g_enum_get_value(enum_class, (int)value_int64); if (v == NULL) { gjs_throw(context, "%d is not a valid value for enumeration %s", value.toInt32(), g_type_name(gtype)); return false; } g_value_set_enum(gvalue, v->value); } else { return throw_expect_type(context, value, "enum", gtype); } } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) { int64_t value_int64; if (Gjs::js_value_to_c(context, value, &value_int64)) { if (!_gjs_flags_value_is_valid(context, gtype, value_int64)) return false; /* See arg.c:_gjs_enum_to_int() */ g_value_set_flags(gvalue, (int)value_int64); } else { return throw_expect_type(context, value, "flags", gtype); } } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { void *gparam; gparam = NULL; if (value.isNull()) { /* nothing to do */ } else if (value.isObject()) { JS::RootedObject obj(context, &value.toObject()); if (!gjs_typecheck_param(context, obj, gtype, true)) return false; gparam = gjs_g_param_from_param(context, obj); } else { return throw_expect_type(context, value, "param type", gtype); } g_value_set_param(gvalue, (GParamSpec*) gparam); } else if (gtype == G_TYPE_GTYPE) { GType type; if (!value.isObject()) return throw_expect_type(context, value, "GType object"); JS::RootedObject obj(context, &value.toObject()); if (!gjs_gtype_get_actual_gtype(context, obj, &type)) return false; g_value_set_gtype(gvalue, type); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (value.isNull()) { /* Nothing to do */ } else { gjs_throw(context, "Cannot convert non-null JS value to G_POINTER"); return false; } } else if (value.isNumber() && g_value_type_transformable(G_TYPE_INT, gtype)) { /* Only do this crazy gvalue transform stuff after we've * exhausted everything else. Adding this for * e.g. ClutterUnit. */ gint32 i; if (Gjs::js_value_to_c(context, value, &i)) { GValue int_value = { 0, }; g_value_init(&int_value, G_TYPE_INT); g_value_set_int(&int_value, i); g_value_transform(&int_value, gvalue); } else { return throw_expect_type(context, value, "integer"); } } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { // The gtype is none of the above, it should be derived from a custom // fundamental type. if (!value.isObject()) return throw_expect_type(context, value, "object", gtype); JS::RootedObject fundamental_object(context, &value.toObject()); if (!FundamentalBase::to_gvalue(context, fundamental_object, gvalue)) return false; } else { gjs_debug(GJS_DEBUG_GCLOSURE, "JS::Value is number %d gtype fundamental %d transformable to int %d from int %d", value.isNumber(), G_TYPE_IS_FUNDAMENTAL(gtype), g_value_type_transformable(gtype, G_TYPE_INT), g_value_type_transformable(G_TYPE_INT, gtype)); gjs_throw(context, "Don't know how to convert JavaScript object to GType %s", g_type_name(gtype)); return false; } return true; } bool gjs_value_to_g_value(JSContext *context, JS::HandleValue value, GValue *gvalue) { return gjs_value_to_g_value_internal(context, value, gvalue, false); } bool gjs_value_to_g_value_no_copy(JSContext *context, JS::HandleValue value, GValue *gvalue) { return gjs_value_to_g_value_internal(context, value, gvalue, true); } [[nodiscard]] static JS::Value convert_int_to_enum(GType gtype, int v) { double v_double; if (v > 0 && v < G_MAXINT) { /* Optimize the unambiguous case */ v_double = v; } else { /* Need to distinguish between negative integers and unsigned integers */ GjsAutoEnumInfo info = g_irepository_find_by_gtype(nullptr, gtype); // Native enums don't have type info, assume // they are signed to avoid crashing when // they are exposed to JS. if (!info) { v_double = int64_t(v); } else { v_double = _gjs_enum_from_int(info, v); } } return JS::NumberValue(v_double); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal(JSContext* context, JS::MutableHandleValue value_p, const GValue* gvalue, bool no_copy, bool is_introspected_signal, GIArgInfo* arg_info, GITypeInfo* type_info) { GType gtype; gtype = G_VALUE_TYPE(gvalue); gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting gtype %s to JS::Value", g_type_name(gtype)); if (gtype != G_TYPE_STRV && g_value_fits_pointer(gvalue) && g_value_peek_pointer(gvalue) == nullptr) { // In theory here we should throw if !g_arg_info_may_be_null(arg_info) // however most signals don't explicitly mark themselves as nullable, // so better to avoid this. gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Converting NULL %s to JS::NullValue()", g_type_name(gtype)); value_p.setNull(); return true; } if (gtype == G_TYPE_STRING) { return gjs_string_from_utf8(context, g_value_get_string(gvalue), value_p); } else if (gtype == G_TYPE_CHAR) { signed char v; v = g_value_get_schar(gvalue); value_p.setInt32(v); } else if (gtype == G_TYPE_UCHAR) { unsigned char v; v = g_value_get_uchar(gvalue); value_p.setInt32(v); } else if (gtype == G_TYPE_INT) { int v; v = g_value_get_int(gvalue); value_p.set(JS::NumberValue(v)); } else if (gtype == G_TYPE_UINT) { guint v; v = g_value_get_uint(gvalue); value_p.setNumber(v); } else if (gtype == G_TYPE_DOUBLE) { double d; d = g_value_get_double(gvalue); value_p.setNumber(JS::CanonicalizeNaN(d)); } else if (gtype == G_TYPE_FLOAT) { double d; d = g_value_get_float(gvalue); value_p.setNumber(JS::CanonicalizeNaN(d)); } else if (gtype == G_TYPE_BOOLEAN) { bool v; v = g_value_get_boolean(gvalue); value_p.setBoolean(!!v); } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { return ObjectInstance::set_value_from_gobject( context, static_cast(g_value_get_object(gvalue)), value_p); } else if (gtype == G_TYPE_STRV) { if (!gjs_array_from_strv (context, value_p, (const char**) g_value_get_boxed (gvalue))) { gjs_throw(context, "Failed to convert strv to array"); return false; } } else if (gtype == G_TYPE_ARRAY || gtype == G_TYPE_BYTE_ARRAY || gtype == G_TYPE_PTR_ARRAY) { if (gtype == G_TYPE_BYTE_ARRAY) { auto* byte_array = static_cast(g_value_get_boxed(gvalue)); JSObject* array = gjs_byte_array_from_byte_array(context, byte_array); if (!array) { gjs_throw(context, "Couldn't convert GByteArray to a Uint8Array"); return false; } value_p.setObject(*array); return true; } if (!is_introspected_signal || !arg_info) { gjs_throw(context, "Unknown signal"); return false; } GITransfer transfer = g_arg_info_get_ownership_transfer(arg_info); GjsAutoTypeInfo element_info = g_type_info_get_param_type(type_info, 0); if (!gjs_array_from_g_value_array(context, value_p, element_info, transfer, gvalue)) { gjs_throw(context, "Failed to convert array"); return false; } } else if (gtype == G_TYPE_HASH_TABLE) { if (!arg_info) { gjs_throw(context, "Failed to get GValue from Hash Table without" "signal information"); return false; } GjsAutoTypeInfo key_info = g_type_info_get_param_type(type_info, 0); GjsAutoTypeInfo value_info = g_type_info_get_param_type(type_info, 1); GITransfer transfer = g_arg_info_get_ownership_transfer(arg_info); if (!gjs_object_from_g_hash( context, value_p, key_info, value_info, transfer, static_cast(g_value_get_boxed(gvalue)))) { gjs_throw(context, "Failed to convert Hash Table"); return false; } } else if (g_type_is_a(gtype, G_TYPE_BOXED) || gtype == G_TYPE_VARIANT) { void *gboxed; JSObject *obj; if (g_type_is_a(gtype, G_TYPE_BOXED)) gboxed = g_value_get_boxed(gvalue); else gboxed = g_value_get_variant(gvalue); if (gtype == ObjectBox::gtype()) { obj = ObjectBox::object_for_c_ptr(context, static_cast(gboxed)); if (!obj) return false; value_p.setObject(*obj); return true; } /* special case GError */ if (gtype == G_TYPE_ERROR) { obj = ErrorInstance::object_for_c_ptr(context, static_cast(gboxed)); if (!obj) return false; value_p.setObject(*obj); return true; } /* special case GValue */ if (gtype == G_TYPE_VALUE) { return gjs_value_from_g_value(context, value_p, static_cast(gboxed)); } /* The only way to differentiate unions and structs is from * their g-i info as both GBoxed */ GjsAutoBaseInfo info = gjs_lookup_gtype(nullptr, gtype); if (!info) { gjs_throw(context, "No introspection information found for %s", g_type_name(gtype)); return false; } if (info.type() == GI_INFO_TYPE_STRUCT && g_struct_info_is_foreign(info)) { GIArgument arg; gjs_arg_set(&arg, gboxed); return gjs_struct_foreign_convert_from_gi_argument(context, value_p, info, &arg); } GIInfoType type = info.type(); if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) { if (no_copy) obj = BoxedInstance::new_for_c_struct(context, info, gboxed, BoxedInstance::NoCopy()); else obj = BoxedInstance::new_for_c_struct(context, info, gboxed); } else if (type == GI_INFO_TYPE_UNION) { obj = UnionInstance::new_for_c_union(context, info, gboxed); } else { gjs_throw(context, "Unexpected introspection type %d for %s", info.type(), g_type_name(gtype)); return false; } value_p.setObjectOrNull(obj); } else if (g_type_is_a(gtype, G_TYPE_ENUM)) { value_p.set(convert_int_to_enum(gtype, g_value_get_enum(gvalue))); } else if (g_type_is_a(gtype, G_TYPE_PARAM)) { GParamSpec *gparam; JSObject *obj; gparam = g_value_get_param(gvalue); obj = gjs_param_from_g_param(context, gparam); value_p.setObjectOrNull(obj); } else if (is_introspected_signal && g_type_is_a(gtype, G_TYPE_POINTER)) { if (!arg_info) { gjs_throw(context, "Unknown signal."); return false; } g_assert(((void)"Check gjs_value_from_array_and_length_values() before" " calling gjs_value_from_g_value_internal()", g_type_info_get_array_length(type_info) == -1)); GIArgument arg; gjs_arg_set(&arg, g_value_get_pointer(gvalue)); return gjs_value_from_gi_argument(context, value_p, type_info, &arg, true); } else if (gtype == G_TYPE_GTYPE) { GType gvalue_gtype = g_value_get_gtype(gvalue); if (gvalue_gtype == 0) { value_p.setNull(); return true; } JS::RootedObject obj( context, gjs_gtype_create_gtype_wrapper(context, gvalue_gtype)); if (!obj) return false; value_p.setObject(*obj); } else if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (g_value_get_pointer(gvalue) != nullptr) { gjs_throw(context, "Can't convert non-null pointer to JS value"); return false; } } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) { GValue double_value = { 0, }; double v; g_value_init(&double_value, G_TYPE_DOUBLE); g_value_transform(gvalue, &double_value); v = g_value_get_double(&double_value); value_p.setNumber(v); } else if (g_value_type_transformable(gtype, G_TYPE_INT)) { GValue int_value = { 0, }; int v; g_value_init(&int_value, G_TYPE_INT); g_value_transform(gvalue, &int_value); v = g_value_get_int(&int_value); value_p.set(JS::NumberValue(v)); } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) { /* The gtype is none of the above, it should be a custom fundamental type. */ JS::RootedObject obj(context); if (!FundamentalInstance::object_for_gvalue(context, gvalue, gtype, &obj)) return false; value_p.setObjectOrNull(obj); } else { gjs_throw(context, "Don't know how to convert GType %s to JavaScript object", g_type_name(gtype)); return false; } return true; } bool gjs_value_from_g_value(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue) { return gjs_value_from_g_value_internal(context, value_p, gvalue, false); } cjs-128.1/gi/value.h0000664000175000017500000000510015116312211013101 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef GI_VALUE_H_ #define GI_VALUE_H_ #include #include // for move, swap #include // for vector #include #include #include "cjs/macros.h" namespace Gjs { struct AutoGValue : GValue { AutoGValue() : GValue(G_VALUE_INIT) { static_assert(sizeof(AutoGValue) == sizeof(GValue)); } explicit AutoGValue(GType gtype) : AutoGValue() { g_value_init(this, gtype); } AutoGValue(AutoGValue const& src) : AutoGValue(G_VALUE_TYPE(&src)) { g_value_copy(&src, this); } AutoGValue& operator=(AutoGValue other) { // We need to cast to GValue here not to make swap to recurse here std::swap(*static_cast(this), *static_cast(&other)); return *this; } AutoGValue(AutoGValue&& src) { switch (G_VALUE_TYPE(&src)) { case G_TYPE_NONE: case G_TYPE_CHAR: case G_TYPE_UCHAR: case G_TYPE_BOOLEAN: case G_TYPE_INT: case G_TYPE_UINT: case G_TYPE_LONG: case G_TYPE_ULONG: case G_TYPE_INT64: case G_TYPE_UINT64: case G_TYPE_FLOAT: case G_TYPE_DOUBLE: *static_cast(this) = std::move(static_cast(src)); break; default: // We can't safely move in complex cases, so let's just copy this->steal(); *this = src; g_value_unset(&src); } } void steal() { *static_cast(this) = G_VALUE_INIT; } ~AutoGValue() { g_value_unset(this); } }; } // namespace Gjs using AutoGValueVector = std::vector; GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value (JSContext *context, JS::HandleValue value, GValue *gvalue); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value_no_copy (JSContext *context, JS::HandleValue value, GValue *gvalue); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_g_value(JSContext *context, JS::MutableHandleValue value_p, const GValue *gvalue); #endif // GI_VALUE_H_ cjs-128.1/gi/wrapperutils.cpp0000664000175000017500000001423015116312211015065 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #include #include #include #include #include #include "gi/function.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" /* Default spidermonkey toString is worthless. Replace it * with something that gives us both the introspection name * and a memory address. */ bool gjs_wrapper_to_string_func(JSContext* context, JSObject* this_obj, const char* objtype, GIBaseInfo* info, GType gtype, const void* native_address, JS::MutableHandleValue rval) { std::ostringstream out; out << '[' << objtype; if (!native_address) out << " prototype of"; else out << " instance wrapper"; if (info) { out << " GIName:" << g_base_info_get_namespace(info) << "." << g_base_info_get_name(info); } else { out << " GType:" << g_type_name(gtype); } out << " jsobj@" << this_obj; if (native_address) out << " native@" << native_address; out << ']'; return gjs_string_from_utf8(context, out.str().c_str(), rval); } bool gjs_wrapper_throw_nonexistent_field(JSContext* cx, GType gtype, const char* field_name) { gjs_throw(cx, "No property %s on %s", field_name, g_type_name(gtype)); return false; } bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, const char* field_name) { gjs_throw(cx, "Property %s.%s is not writable", g_type_name(gtype), field_name); return false; } // These policies work around having separate g_foo_info_get_n_methods() and // g_foo_info_get_method() functions for different GIInfoTypes. It's not // possible to use GIFooInfo* as the template parameter, because the GIFooInfo // structs are all typedefs of GIBaseInfo. It's also not possible to use the // GIInfoType enum value as the template parameter, because GI_INFO_TYPE_BOXED // could be either a GIStructInfo or GIUnionInfo. template static inline GIStructInfo* no_type_struct(InfoT*) { return nullptr; } template > struct InfoMethodsPolicy { static constexpr decltype(NMethods) n_methods = NMethods; static constexpr decltype(Method) method = Method; static constexpr decltype(TypeStruct) type_struct = TypeStruct; }; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy< InfoType::Interface, GIInterfaceInfo, &g_interface_info_get_n_methods, &g_interface_info_get_method, &g_interface_info_get_iface_struct> {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy {}; template <> struct InfoMethodsPolicy : InfoMethodsPolicy { }; template bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info) { int n_methods = InfoMethodsPolicy::n_methods(info); for (int ix = 0; ix < n_methods; ix++) { GjsAutoFunctionInfo meth_info = InfoMethodsPolicy::method(info, ix); GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info); // Anything that isn't a method we put on the constructor. This // includes introspection methods, as well as static // methods. We may want to change this to use // GI_FUNCTION_IS_CONSTRUCTOR and GI_FUNCTION_IS_STATIC or the like // in the future. if (!(flags & GI_FUNCTION_IS_METHOD)) { if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } } // Also define class/interface methods if there is a gtype struct GjsAutoStructInfo type_struct = InfoMethodsPolicy::type_struct(info); // Not an error for it to be null even in the case of Object and Interface; // documentation says g_object_info_get_class_struct() and // g_interface_info_get_iface_struct() can validly return a null pointer. if (!type_struct) return true; n_methods = g_struct_info_get_n_methods(type_struct); for (int ix = 0; ix < n_methods; ix++) { GjsAutoFunctionInfo meth_info = g_struct_info_get_method(type_struct, ix); if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } return true; } // All possible instantiations are needed template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); template bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); cjs-128.1/gi/wrapperutils.h0000664000175000017500000012532015116312211014535 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2018 Philip Chimento #ifndef GI_WRAPPERUTILS_H_ #define GI_WRAPPERUTILS_H_ #include #include #include #include #include #include #include #include #include #include // for JSEXN_TYPEERR #include // for MutableHandleIdVector #include #include #include #include // for JS_DefineFunctionById #include #include #include #include // for JS_GetPrototype #include "gi/arg-inl.h" #include "gi/cwrapper.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/profiler-private.h" #include "util/log.h" struct JSFunctionSpec; struct JSPropertySpec; class JSTracer; GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj, const char* objtype, GIBaseInfo* info, GType gtype, const void* native_address, JS::MutableHandleValue ret); bool gjs_wrapper_throw_nonexistent_field(JSContext* cx, GType gtype, const char* field_name); bool gjs_wrapper_throw_readonly_field(JSContext* cx, GType gtype, const char* field_name); namespace InfoType { enum Tag { Enum, Interface, Object, Struct, Union }; } namespace MemoryUse { constexpr JS::MemoryUse GObjectInstanceStruct = JS::MemoryUse::Embedding1; } struct GjsTypecheckNoThrow {}; /* * gjs_define_static_methods: * * Defines all static methods from @info on @constructor. Also includes class * methods for GIObjectInfo, and interface methods for GIInterfaceInfo. */ template GJS_JSAPI_RETURN_CONVENTION bool gjs_define_static_methods( JSContext* cx, JS::HandleObject constructor, GType gtype, GIBaseInfo* info); /* * GIWrapperBase: * * In most different kinds of C pointer that we expose to JS through GObject * Introspection (boxed, fundamental, gerror, interface, object, union), we want * to have different private structures for the prototype JS object and the JS * objects representing instances. Both should inherit from a base structure for * their common functionality. * * This is mainly for memory reasons. We need to keep track of the GIBaseInfo* * and GType for each dynamically created class, but we don't need to duplicate * that information (16 bytes on x64 systems) for every instance. In some cases * there can also be other information that's only used on the prototype. * * So, to conserve memory, we split the private structures in FooInstance and * FooPrototype, which both inherit from FooBase. All the repeated code in these * structures lives in GIWrapperBase, GIWrapperPrototype, and GIWrapperInstance. * * The m_proto member needs a bit of explanation, as this is used to implement * an unusual form of polymorphism. Sadly, we cannot have virtual methods in * FooBase, because SpiderMonkey can be compiled with or without RTTI, so we * cannot count on being able to cast FooBase to FooInstance or FooPrototype * with dynamic_cast<>, and the vtable would take up just as much space anyway. * Instead, we use the CRTP technique, and distinguish between FooInstance and * FooPrototype using the m_proto member, which will be null for FooPrototype. * Instead of casting, we have the to_prototype() and to_instance() methods * which will give you a pointer if the FooBase is of the correct type (and * assert if not.) * * The CRTP requires inheriting classes to declare themselves friends of the * parent class, so that the parent class can call their private methods. * * For more information about the CRTP, the Wikipedia article is informative: * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */ template class GIWrapperBase : public CWrapperPointerOps { protected: // nullptr if this Base is a Prototype; points to the corresponding // Prototype if this Base is an Instance. Prototype* m_proto; explicit GIWrapperBase(Prototype* proto = nullptr) : m_proto(proto) {} // These three can be overridden in subclasses. See define_jsclass(). static constexpr JSPropertySpec* proto_properties = nullptr; static constexpr JSPropertySpec* static_properties = nullptr; static constexpr JSFunctionSpec* proto_methods = nullptr; static constexpr JSFunctionSpec* static_methods = nullptr; public: // Methods implementing our CRTP polymorphism scheme follow below. We don't // use standard C++ polymorphism because that would occupy another 8 bytes // for a vtable. /* * GIWrapperBase::is_prototype: * * Returns whether this Base is actually a Prototype (true) or an Instance * (false). */ [[nodiscard]] bool is_prototype() const { return !m_proto; } /* * GIWrapperBase::to_prototype: * GIWrapperBase::to_instance: * * These methods assert that this Base is of the correct subclass. If you * don't want to assert, then either check beforehand with is_prototype(), * or use get_prototype(). */ [[nodiscard]] Prototype* to_prototype() { g_assert(is_prototype()); return reinterpret_cast(this); } [[nodiscard]] const Prototype* to_prototype() const { g_assert(is_prototype()); return reinterpret_cast(this); } [[nodiscard]] Instance* to_instance() { g_assert(!is_prototype()); return reinterpret_cast(this); } [[nodiscard]] const Instance* to_instance() const { g_assert(!is_prototype()); return reinterpret_cast(this); } /* * GIWrapperBase::get_prototype: * * get_prototype() doesn't assert. If you call it on a Prototype, it returns * you the same object cast to the correct type; if you call it on an * Instance, it returns you the Prototype belonging to the corresponding JS * prototype. */ [[nodiscard]] [[gnu::const]] Prototype* get_prototype() { return is_prototype() ? to_prototype() : m_proto; } [[nodiscard]] const Prototype* get_prototype() const { return is_prototype() ? to_prototype() : m_proto; } // Accessors for Prototype members follow below. Both Instance and Prototype // should be able to access the GIFooInfo and the GType, but for space // reasons we store them only on Prototype. [[nodiscard]] GIBaseInfo* info() const { return get_prototype()->info(); } [[nodiscard]] GType gtype() const { return get_prototype()->gtype(); } // The next three methods are operations derived from the GIFooInfo. [[nodiscard]] const char* type_name() const { return g_type_name(gtype()); } [[nodiscard]] const char* ns() const { return info() ? g_base_info_get_namespace(info()) : ""; } [[nodiscard]] const char* name() const { return info() ? g_base_info_get_name(info()) : type_name(); } [[nodiscard]] std::string format_name() const { std::string retval = ns(); if (!retval.empty()) retval += '.'; retval += name(); return retval; } private: // Accessor for Instance member. Used only in debug methods and toString(). [[nodiscard]] const void* ptr_addr() const { return is_prototype() ? nullptr : to_instance()->ptr(); } // Debug methods protected: void debug_lifecycle(const char* message GJS_USED_VERBOSE_LIFECYCLE) const { gjs_debug_lifecycle( Base::DEBUG_TOPIC, "[%p: %s pointer %p - %s.%s (%s)] %s", this, Base::DEBUG_TAG, ptr_addr(), ns(), name(), type_name(), message); } void debug_lifecycle(const void* obj GJS_USED_VERBOSE_LIFECYCLE, const char* message GJS_USED_VERBOSE_LIFECYCLE) const { gjs_debug_lifecycle( Base::DEBUG_TOPIC, "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s", this, Base::DEBUG_TAG, ptr_addr(), obj, ns(), name(), type_name(), message); } void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS, const char* id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) const { gjs_debug_jsprop( Base::DEBUG_TOPIC, "[%p: %s pointer %p - JS wrapper %p - %s.%s (%s)] %s '%s'", this, Base::DEBUG_TAG, ptr_addr(), obj, ns(), name(), type_name(), message, id); } void debug_jsprop(const char* message, jsid id, const void* obj) const { if constexpr (GJS_VERBOSE_ENABLE_PROPS) debug_jsprop(message, gjs_debug_id(id).c_str(), obj); } void debug_jsprop(const char* message, JSString* id, const void* obj) const { if constexpr (GJS_VERBOSE_ENABLE_PROPS) debug_jsprop(message, gjs_debug_string(id).c_str(), obj); } static void debug_jsprop_static(const char* message GJS_USED_VERBOSE_PROPS, jsid id GJS_USED_VERBOSE_PROPS, const void* obj GJS_USED_VERBOSE_PROPS) { gjs_debug_jsprop(Base::DEBUG_TOPIC, "[%s JS wrapper %p] %s '%s', no instance associated", Base::DEBUG_TAG, obj, message, gjs_debug_id(id).c_str()); } // JS class operations, used only in the JSClassOps struct /* * GIWrapperBase::new_enumerate: * * Include this in the Base::klass vtable if the class should support * lazy enumeration (listing all of the lazy properties that can be defined * in resolve().) If it is included, then there must be a corresponding * Prototype::new_enumerate_impl() method. */ GJS_JSAPI_RETURN_CONVENTION static bool new_enumerate(JSContext* cx, JS::HandleObject obj, JS::MutableHandleIdVector properties, bool only_enumerable) { Base* priv = Base::for_js(cx, obj); priv->debug_jsprop("Enumerate hook", "(all)", obj); if (!priv->is_prototype()) { // Instances don't have any methods or properties. // Spidermonkey will call new_enumerate on the prototype next. return true; } return priv->to_prototype()->new_enumerate_impl(cx, obj, properties, only_enumerable); } private: /* * GIWrapperBase::id_is_never_lazy: * * Returns true if @id should never be treated as a lazy property. The * JSResolveOp for an instance is called for every property not defined, * even if it's one of the functions or properties we're adding to the * prototype manually, such as toString(). * * Override this and chain up if you have Base::resolve in your JSClassOps * vtable, and have overridden Base::proto_properties or * Base::proto_methods. You should add any identifiers in the override that * you have added to the prototype object. */ [[nodiscard]] static bool id_is_never_lazy(jsid id, const GjsAtoms& atoms) { // toString() is always defined somewhere on the prototype chain, so it // is never a lazy property. return id == atoms.to_string(); } protected: /** * GIWrapperBase::resolve_prototype: */ [[nodiscard]] static Prototype* resolve_prototype(JSContext* cx, JS::HandleObject proto) { if (JS::GetClass(proto) == &Base::klass) return Prototype::for_js(cx, proto); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); bool has_property = false; if (!JS_HasOwnPropertyById(cx, proto, atoms.gobject_prototype(), &has_property)) return nullptr; if (!has_property) { gjs_throw(cx, "Tried to construct an object without a GType"); return nullptr; } JS::RootedValue gobject_proto(cx); if (!JS_GetPropertyById(cx, proto, atoms.gobject_prototype(), &gobject_proto)) return nullptr; if (!gobject_proto.isObject()) { gjs_throw(cx, "Tried to construct an object without a GType"); return nullptr; } JS::RootedObject obj(cx, &gobject_proto.toObject()); // gobject_prototype is an internal symbol so we can assert that it is // only assigned to objects with &Base::klass definitions g_assert(JS::GetClass(obj) == &Base::klass); return Prototype::for_js(cx, obj); } /* * GIWrapperBase::resolve: * * Include this in the Base::klass vtable if the class should support lazy * properties. If it is included, then there must be a corresponding * Prototype::resolve_impl() method. * * The *resolved out parameter, on success, should be false to indicate that * id was not resolved; and true if id was resolved. */ GJS_JSAPI_RETURN_CONVENTION static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { Base* priv = Base::for_js(cx, obj); if (!priv) { // This catches a case in Object where the private struct isn't set // until the initializer is called, so just defer to prototype // chains in this case. // // This isn't too bad: either you get undefined if the field doesn't // exist on any of the prototype chains, or whatever code will run // afterwards will fail because of the "!priv" check there. debug_jsprop_static("Resolve hook", id, obj); *resolved = false; return true; } priv->debug_jsprop("Resolve hook", id, obj); if (!priv->is_prototype()) { // We are an instance, not a prototype, so look for per-instance // props that we want to define on the JSObject. Generally we do not // want to cache these in JS, we want to always pull them from the C // object, or JS would not see any changes made from C. So we use // the property accessors, not this resolve hook. *resolved = false; return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (id_is_never_lazy(id, atoms)) { *resolved = false; return true; } return priv->to_prototype()->resolve_impl(cx, obj, id, resolved); } /* * GIWrapperBase::finalize: * * This should always be included in the Base::klass vtable. The destructors * of Prototype and Instance will be called in the finalize hook. It is not * necessary to include a finalize_impl() function in Prototype or Instance. * Any needed finalization should be done in ~Prototype() and ~Instance(). */ static void finalize(JS::GCContext* gcx, JSObject* obj) { Base* priv = Base::for_js_nocheck(obj); if (!priv) return; // construction didn't finish // Call only GIWrapperBase's original method here, not any overrides; // e.g., we don't want to deal with a read barrier in ObjectInstance. static_cast(priv)->debug_lifecycle(obj, "Finalize"); if (priv->is_prototype()) priv->to_prototype()->finalize_impl(gcx, obj); else priv->to_instance()->finalize_impl(gcx, obj); Base::unset_private(obj); } /* * GIWrapperBase::trace: * * This should be included in the Base::klass vtable if any of the Base, * Prototype or Instance structures contain any members that the JS garbage * collector must trace. Each struct containing such members must override * GIWrapperBase::trace_impl(), GIWrapperPrototype::trace_impl(), and/or * GIWrapperInstance::trace_impl() in order to perform the trace. */ static void trace(JSTracer* trc, JSObject* obj) { Base* priv = Base::for_js_nocheck(obj); if (!priv) return; // Don't log in trace(). That would overrun even the most verbose logs. if (priv->is_prototype()) priv->to_prototype()->trace_impl(trc); else priv->to_instance()->trace_impl(trc); priv->trace_impl(trc); } /* * GIWrapperBase::trace_impl: * Override if necessary. See trace(). */ void trace_impl(JSTracer*) {} // JSNative methods /* * GIWrapperBase::constructor: * * C++ implementation of the JS constructor passed to JS_InitClass(). Only * called on instances, never on prototypes. This method contains the * functionality common to all GI wrapper classes. There must be a * corresponding Instance::constructor_impl method containing the rest of * the functionality. */ GJS_JSAPI_RETURN_CONVENTION static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (!args.isConstructing()) { gjs_throw_constructor_error(cx); return false; } JS::RootedObject obj( cx, JS_NewObjectForConstructor(cx, &Base::klass, args)); if (!obj) return false; JS::RootedObject proto(cx); if (!JS_GetPrototype(cx, obj, &proto)) return false; Prototype* prototype = resolve_prototype(cx, proto); if (!prototype) return false; args.rval().setUndefined(); Instance* priv = Instance::new_for_js_object(prototype, obj); { std::string fullName = priv->format_name(); AutoProfilerLabel label(cx, "constructor", fullName.c_str()); if (!priv->constructor_impl(cx, obj, args)) return false; } static_cast(priv)->debug_lifecycle(obj, "JSObject created"); gjs_debug_lifecycle(Base::DEBUG_TOPIC, "m_proto is %p", priv->get_prototype()); // We may need to return a value different from obj (for example because // we delegate to another constructor) if (args.rval().isUndefined()) args.rval().setObject(*obj); return true; } /* * GIWrapperBase::to_string: * * JSNative method connected to the toString() method in JS. */ GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv); return gjs_wrapper_to_string_func(cx, obj, Base::DEBUG_TAG, priv->info(), priv->gtype(), priv->ptr_addr(), args.rval()); } // Helper methods public: /* * GIWrapperBase::check_is_instance: * @for_what: string used in the exception message if an exception is thrown * * Used in JSNative methods to ensure the passed-in JS object is an instance * and not the prototype. Throws a JS exception if the prototype is passed * in. */ GJS_JSAPI_RETURN_CONVENTION bool check_is_instance(JSContext* cx, const char* for_what) const { if (!is_prototype()) return true; gjs_throw(cx, "Can't %s on %s.%s.prototype; only on instances", for_what, ns(), name()); return false; } /* * GIWrapperBase::to_c_ptr: * * Returns the underlying C pointer of the wrapped object, or throws a JS * exception if that is not possible (for example, the passed-in JS object * is the prototype.) * * Includes a JS typecheck (but without any extra typecheck of the GType or * introspection info that you would get from GIWrapperBase::typecheck(), so * if you want that you still have to do the typecheck before calling this * method.) */ template GJS_JSAPI_RETURN_CONVENTION static T* to_c_ptr(JSContext* cx, JS::HandleObject obj) { Base* priv; if (!Base::for_js_typecheck(cx, obj, &priv) || !priv->check_is_instance(cx, "get a C pointer")) return nullptr; return static_cast(priv->to_instance()->ptr()); } /* * GIWrapperBase::transfer_to_gi_argument: * @arg: #GIArgument to fill with the value from @obj * @transfer_direction: Either %GI_DIRECTION_IN or %GI_DIRECTION_OUT * @transfer_ownership: #GITransfer value specifying whether @arg should * copy or acquire a reference to the value or not * @expected_gtype: #GType to perform a typecheck with * @expected_info: Introspection info to perform a typecheck with * * Prepares @arg for passing the value from @obj into C code. It will get a * C pointer from @obj and assign it to @arg's pointer field, taking a * reference with GIWrapperInstance::copy_ptr() if @transfer_direction and * @transfer_ownership indicate that it should. * * Includes a typecheck using GIWrapperBase::typecheck(), to which * @expected_gtype and @expected_info are passed. * * If returning false, then @arg's pointer field is null. */ GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj, GIArgument* arg, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype, GIBaseInfo* expected_info = nullptr) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!Base::typecheck(cx, obj, expected_info, expected_gtype)) { gjs_arg_unset(arg); return false; } gjs_arg_set(arg, Base::to_c_ptr(cx, obj)); if (!gjs_arg_get(arg)) return false; if ((transfer_direction == GI_DIRECTION_IN && transfer_ownership != GI_TRANSFER_NOTHING) || (transfer_direction == GI_DIRECTION_OUT && transfer_ownership == GI_TRANSFER_EVERYTHING)) { gjs_arg_set(arg, Instance::copy_ptr(cx, expected_gtype, gjs_arg_get(arg))); if (!gjs_arg_get(arg)) return false; } return true; } // Public typecheck API /* * GIWrapperBase::typecheck: * @expected_info: (nullable): GI info to check * @expected_type: (nullable): GType to check * * Checks not only that the JS object is of the correct JSClass (like * CWrapperPointerOps::typecheck() does); but also that the object is an * instance, not the prototype; and that the instance's wrapped pointer is * of the correct GType or GI info. * * The overload with a GjsTypecheckNoThrow parameter will not throw a JS * exception if the prototype is passed in or the typecheck fails. */ GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject object, GIBaseInfo* expected_info, GType expected_gtype) { Base* priv; if (!Base::for_js_typecheck(cx, object, &priv) || !priv->check_is_instance(cx, "convert to pointer")) return false; if (priv->to_instance()->typecheck_impl(cx, expected_info, expected_gtype)) return true; if (expected_info) { gjs_throw_custom( cx, JSEXN_TYPEERR, nullptr, "Object is of type %s.%s - cannot convert to %s.%s", priv->ns(), priv->name(), g_base_info_get_namespace(expected_info), g_base_info_get_name(expected_info)); } else { gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object is of type %s.%s - cannot convert to %s", priv->ns(), priv->name(), g_type_name(expected_gtype)); } return false; } [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject object, GIBaseInfo* expected_info, GType expected_gtype, GjsTypecheckNoThrow) { Base* priv = Base::for_js(cx, object); if (!priv || priv->is_prototype()) return false; return priv->to_instance()->typecheck_impl(cx, expected_info, expected_gtype); } // Deleting these constructors and assignment operators will also delete // them from derived classes. GIWrapperBase(const GIWrapperBase& other) = delete; GIWrapperBase(GIWrapperBase&& other) = delete; GIWrapperBase& operator=(const GIWrapperBase& other) = delete; GIWrapperBase& operator=(GIWrapperBase&& other) = delete; }; /* * GIWrapperPrototype: * * The specialization of GIWrapperBase which becomes the private data of JS * prototype objects. For example, it is the parent class of BoxedPrototype. * * Classes inheriting from GIWrapperPrototype must declare "friend class * GIWrapperBase" as well as the normal CRTP requirement of "friend class * GIWrapperPrototype", because of the unusual polymorphism scheme, in order for * Base to call methods such as trace_impl(). */ template class GIWrapperPrototype : public Base { using GjsAutoPrototype = GjsAutoPointer; protected: // m_info may be null in the case of JS-defined types, or internal types // not exposed through introspection, such as GLocalFile. Not all subclasses // of GIWrapperPrototype support this. Object and Interface support it in // any case. GjsAutoBaseInfo m_info; GType m_gtype; explicit GIWrapperPrototype(Info* info, GType gtype) : Base(), m_info(info, GjsAutoTakeOwnership()), m_gtype(gtype) { Base::debug_lifecycle("Prototype constructor"); } /* * GIWrapperPrototype::init: * * Performs any initialization that cannot be done in the constructor of * GIWrapperPrototype, either because it can fail, or because it can cause a * garbage collection. * * This default implementation does nothing. Override in a subclass if * necessary. */ GJS_JSAPI_RETURN_CONVENTION bool init(JSContext*) { return true; } // The following four methods are private because they are used only in // create_class(). private: /* * GIWrapperPrototype::parent_proto: * * Returns in @proto the parent class's prototype object, or nullptr if * there is none. * * This default implementation is for GObject introspection types that can't * inherit in JS, like Boxed and Union. Override this if the type can * inherit in JS. */ GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) const { proto.set(nullptr); return true; } /* * GIWrapperPrototype::constructor_nargs: * * Override this if the type's constructor takes other than 1 argument. */ [[nodiscard]] unsigned constructor_nargs() const { return 1; } /* * GIWrapperPrototype::define_jsclass: * @in_object: JSObject on which to define the class constructor as a * property * @parent_proto: (nullable): prototype of the prototype * @constructor: return location for the constructor function object * @prototype: return location for the prototype object * * Defines a JS class with constructor and prototype, and optionally defines * properties and methods on the prototype object, and methods on the * constructor object. * * By default no properties or methods are defined, but derived classes can * override the GIWrapperBase::proto_properties, * GIWrapperBase::proto_methods, and GIWrapperBase::static_methods members. * Static properties would also be possible but are not used anywhere in GJS * so are not implemented yet. * * Note: no prototype methods are defined if @parent_proto is null. * * Here is a refresher comment on the difference between __proto__ and * prototype that has been in the GJS codebase since forever: * * https://web.archive.org/web/20100716231157/http://egachine.berlios.de/embedding-sm-best-practice/apa.html * https://www.sitepoint.com/javascript-inheritance/ * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/ * * What we want is: repoobj.Gtk.Window is constructor for a GtkWindow * wrapper JSObject (gjs_define_object_class() is supposed to define Window * in Gtk.) * * Window.prototype contains the methods on Window, e.g. set_default_size() * mywindow.__proto__ is Window.prototype * mywindow.__proto__.__proto__ is Bin.prototype * mywindow.__proto__.__proto__.__proto__ is Container.prototype * * Because Window.prototype is an instance of Window in a sense, * Window.prototype.__proto__ is Window.prototype, just as * mywindow.__proto__ is Window.prototype * * If we do "mywindow = new Window()" then we should get: * mywindow.__proto__ == Window.prototype * which means "mywindow instanceof Window" is true. * * Remember "Window.prototype" is "the __proto__ of stuff constructed with * new Window()" * * __proto__ is used to search for properties if you do "this.foo", while * .prototype is only relevant for constructors and is used to set __proto__ * on new'd objects. So .prototype only makes sense on constructors. * * JS_SetPrototype() and JS_GetPrototype() are for __proto__. To set/get * .prototype, just use the normal property accessors, or JS_InitClass() * sets it up automatically. */ GJS_JSAPI_RETURN_CONVENTION bool define_jsclass(JSContext* cx, JS::HandleObject in_object, JS::HandleObject parent_proto, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { // The GI namespace is only used to set the JSClass->name field (exposed // by Object.prototype.toString, for example). We can safely set // "unknown" if this is a custom or internal JS class with no GI // namespace, as in that case the name is already globally unique (it's // a GType name). const char* gi_namespace = Base::info() ? Base::ns() : "unknown"; unsigned nargs = static_cast(this)->constructor_nargs(); if (!gjs_init_class_dynamic( cx, in_object, parent_proto, gi_namespace, Base::name(), &Base::klass, &Base::constructor, nargs, Base::proto_properties, parent_proto ? nullptr : Base::proto_methods, Base::static_properties, Base::static_methods, prototype, constructor)) return false; gjs_debug(Base::DEBUG_TOPIC, "Defined class for %s (%s), prototype %p, " "JSClass %p, in object %p", Base::name(), Base::type_name(), prototype.get(), JS::GetClass(prototype), in_object.get()); return true; } /* * GIWrapperPrototype::define_static_methods: * * Defines all introspectable static methods on @constructor, including * class methods for objects, and interface methods for interfaces. See * gjs_define_static_methods() for details. * * It requires Prototype to have an info_type_tag member to indicate * the correct template specialization of gjs_define_static_methods(). */ GJS_JSAPI_RETURN_CONVENTION bool define_static_methods(JSContext* cx, JS::HandleObject constructor) { if (!info()) return true; // no introspection means no methods to define return gjs_define_static_methods( cx, constructor, m_gtype, m_info); } GJS_JSAPI_RETURN_CONVENTION static Prototype* create_prototype(Info* info, GType gtype) { g_assert(gtype != G_TYPE_INVALID); // We have to keep the Prototype in an arcbox because some of its // members are needed in some Instance destructors, e.g. m_gtype to // figure out how to free the Instance's m_ptr, and m_info to figure out // how many bytes to free if it is allocated directly. Storing a // refcount on the prototype is cheaper than storing pointers to m_info // and m_gtype on each instance. Prototype* priv = g_atomic_rc_box_new0(Prototype); new (priv) Prototype(info, gtype); return priv; } public: /** * GIWrapperPrototype::create_class: * @in_object: JSObject on which to define the class constructor as a * property * @info: (nullable): Introspection info for the class, or null if the class * has been defined in JS * @gtype: GType for the class * @constructor: return location for the constructor function object * @prototype: return location for the prototype object * * Creates a JS class that wraps a GI pointer, by defining its constructor * function and prototype object. The prototype object is given an instance * of GIWrapperPrototype as its private data, which is also returned. * Basically treat this method as the public constructor. * * Also defines all the requested methods and properties on the prototype * and constructor objects (see define_jsclass()), as well as a `$gtype` * property and a toString() method. * * This method can be overridden and chained up to if the derived class * needs to define more properties on the constructor or prototype objects, * e.g. eager GI properties. */ GJS_JSAPI_RETURN_CONVENTION static Prototype* create_class(JSContext* cx, JS::HandleObject in_object, Info* info, GType gtype, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { g_assert(in_object); GjsAutoPrototype priv = create_prototype(info, gtype); if (!priv->init(cx)) return nullptr; JS::RootedObject parent_proto(cx); if (!priv->get_parent_proto(cx, &parent_proto) || !priv->define_jsclass(cx, in_object, parent_proto, constructor, prototype)) return nullptr; // Init the private variable of @private before we do anything else. If // a garbage collection or error happens subsequently, then this object // might be traced and we would end up dereferencing a null pointer. Prototype* proto = priv.release(); Prototype::init_private(prototype, proto); if (!gjs_wrapper_define_gtype_prop(cx, constructor, gtype)) return nullptr; // Every class has a toString() with C++ implementation, so define that // without requiring it to be listed in Base::proto_methods if (!parent_proto) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!JS_DefineFunctionById(cx, prototype, atoms.to_string(), &Base::to_string, 0, GJS_MODULE_PROP_FLAGS)) return nullptr; } if (!proto->define_static_methods(cx, constructor)) return nullptr; return proto; } GJS_JSAPI_RETURN_CONVENTION static Prototype* wrap_class(JSContext* cx, JS::HandleObject in_object, Info* info, GType gtype, JS::HandleObject constructor, JS::MutableHandleObject prototype) { g_assert(in_object); GjsAutoPrototype priv = create_prototype(info, gtype); if (!priv->init(cx)) return nullptr; JS::RootedObject parent_proto(cx); if (!priv->get_parent_proto(cx, &parent_proto)) return nullptr; if (parent_proto) { prototype.set( JS_NewObjectWithGivenProto(cx, &Base::klass, parent_proto)); } else { prototype.set(JS_NewObject(cx, &Base::klass)); } if (!prototype) return nullptr; Prototype* proto = priv.release(); Prototype::init_private(prototype, proto); if (!proto->define_static_methods(cx, constructor)) return nullptr; GjsAutoChar class_name = g_strdup_printf("%s", proto->name()); if (!JS_DefineProperty(cx, in_object, class_name, constructor, GJS_MODULE_PROP_FLAGS)) return nullptr; return proto; } // Methods to get an existing Prototype /* * GIWrapperPrototype::for_js: * * Like Base::for_js(), but asserts that the returned private struct is a * Prototype and not an Instance. */ [[nodiscard]] static Prototype* for_js(JSContext* cx, JS::HandleObject wrapper) { return Base::for_js(cx, wrapper)->to_prototype(); } /* * GIWrapperPrototype::for_js_prototype: * * Gets the Prototype private data from to @wrapper.prototype. Cannot return * null, and asserts so. */ [[nodiscard]] static Prototype* for_js_prototype(JSContext* cx, JS::HandleObject wrapper) { JS::RootedObject proto(cx); JS_GetPrototype(cx, wrapper, &proto); Base* retval = Base::for_js(cx, proto); g_assert(retval); return retval->to_prototype(); } // Accessors [[nodiscard]] Info* info() const { return m_info; } [[nodiscard]] GType gtype() const { return m_gtype; } // Helper methods private: static void destroy_notify(void* ptr) { static_cast(ptr)->~Prototype(); } public: Prototype* acquire(void) { g_atomic_rc_box_acquire(this); return static_cast(this); } void release(void) { g_atomic_rc_box_release_full(this, &destroy_notify); } // JSClass operations protected: void finalize_impl(JS::GCContext*, JSObject*) { release(); } // Override if necessary void trace_impl(JSTracer*) {} }; using GIWrappedUnowned = void; template <> struct GjsSmartPointer : GjsAutoPointer { using GjsAutoPointer::GjsAutoPointer; }; /* * GIWrapperInstance: * * The specialization of GIWrapperBase which becomes the private data of JS * instance objects. For example, it is the parent class of BoxedInstance. * * Classes inheriting from GIWrapperInstance must declare "friend class * GIWrapperBase" as well as the normal CRTP requirement of "friend class * GIWrapperInstance", because of the unusual polymorphism scheme, in order for * Base to call methods such as trace_impl(). */ template class GIWrapperInstance : public Base { protected: GjsSmartPointer m_ptr; explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj) : Base(prototype), m_ptr(nullptr) { Base::m_proto->acquire(); Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor"); } ~GIWrapperInstance(void) { Base::m_proto->release(); } public: /* * GIWrapperInstance::new_for_js_object: * * Creates a GIWrapperInstance and associates it with @obj as its private * data. This is called by the JS constructor. */ [[nodiscard]] static Instance* new_for_js_object(JSContext* cx, JS::HandleObject obj) { Prototype* prototype = Prototype::for_js_prototype(cx, obj); auto* priv = new Instance(prototype, obj); // Init the private variable before we do anything else. If a garbage // collection happens when calling the constructor, then this object // might be traced and we would end up dereferencing a null pointer. Instance::init_private(obj, priv); return priv; } [[nodiscard]] static Instance* new_for_js_object(Prototype* prototype, JS::HandleObject obj) { auto* priv = new Instance(prototype, obj); Instance::init_private(obj, priv); return priv; } // Method to get an existing Instance /* * GIWrapperInstance::for_js: * * Like Base::for_js(), but asserts that the returned private struct is an * Instance and not a Prototype. */ [[nodiscard]] static Instance* for_js(JSContext* cx, JS::HandleObject wrapper) { return Base::for_js(cx, wrapper)->to_instance(); } // Accessors [[nodiscard]] Wrapped* ptr() const { return m_ptr; } /* * GIWrapperInstance::raw_ptr: * * Like ptr(), but returns a byte pointer for use in byte arithmetic. */ [[nodiscard]] uint8_t* raw_ptr() const { return reinterpret_cast(ptr()); } // JSClass operations protected: void finalize_impl(JS::GCContext*, JSObject*) { delete static_cast(this); } // Override if necessary void trace_impl(JSTracer*) {} // Helper methods /* * GIWrapperInstance::typecheck_impl: * * See GIWrapperBase::typecheck(). Checks that the instance's wrapped * pointer is of the correct GType or GI info. Does not throw a JS * exception. * * It's possible to override typecheck_impl() if you need an extra step in * the check. */ [[nodiscard]] bool typecheck_impl(JSContext*, GIBaseInfo* expected_info, GType expected_gtype) const { if (expected_gtype != G_TYPE_NONE) return g_type_is_a(Base::gtype(), expected_gtype); else if (expected_info) return g_base_info_equal(Base::info(), expected_info); return true; } }; #endif // GI_WRAPPERUTILS_H_ cjs-128.1/gjs.doap0000664000175000017500000000266615116312211012663 0ustar fabiofabio gjs gjs GNOME JavaScript bindings GNOME JavaScript bindings C++ Philip Chimento ptomato pchimento cjs-128.1/installed-tests/0000775000175000017500000000000015116312211014340 5ustar fabiofabiocjs-128.1/installed-tests/.eslintrc.yml0000664000175000017500000000023615116312211016765 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh rules: jsdoc/require-jsdoc: 'off' cjs-128.1/installed-tests/debugger-test.sh0000664000175000017500000000137515116312211017443 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs=cjs-console fi echo 1..1 DEBUGGER_SCRIPT="$1" JS_SCRIPT="$1.js" EXPECTED_OUTPUT="$1.output" THE_DIFF=$("$gjs" -d "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ -e "s#$1#$(basename $1)#g" \ -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ | diff -u "$EXPECTED_OUTPUT" -) EXITCODE=$? if test -n "$THE_DIFF"; then echo "not ok 1 - $1" echo "$THE_DIFF" | while read line; do echo "#$line"; done else if test $EXITCODE -ne 0; then echo "not ok 1 - $1 # command failed" exit 1 fi echo "ok 1 - $1" fi cjs-128.1/installed-tests/debugger.test.in0000664000175000017500000000037115116312211017433 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento [Test] Type=session Exec=@installed_tests_execdir@/debugger-test.sh @installed_tests_execdir@/debugger/@name@ Output=TAP cjs-128.1/installed-tests/debugger/0000775000175000017500000000000015116312211016124 5ustar fabiofabiocjs-128.1/installed-tests/debugger/.eslintrc.yml0000664000175000017500000000023715116312211020552 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento rules: no-debugger: 'off' cjs-128.1/installed-tests/debugger/backtrace.debugger0000664000175000017500000000041415116312211021550 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento backtrace c bt c backtrace full bt full where c # test printing locals when exception is thrown before initialization of a value c bt full q cjs-128.1/installed-tests/debugger/backtrace.debugger.js0000664000175000017500000000062415116312211022166 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento debugger; [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]].every(array => { debugger; array.every(num => { debugger; print(num); return false; }); return false; }); function mistake(array) { let {uninitialized_} = array.shift(); } mistake([]); cjs-128.1/installed-tests/debugger/backtrace.debugger.output0000664000175000017500000000307215116312211023112 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> backtrace #0 toplevel at backtrace.debugger.js:3:1 db> c Debugger statement, toplevel at backtrace.debugger.js:3:1 db> bt #0 toplevel at backtrace.debugger.js:3:1 db> c Debugger statement, ([object Array], 0, [object Array]) at backtrace.debugger.js:5:5 db> backtrace full #0 ([object Array], 0, [object Array]) at backtrace.debugger.js:5:5 arguments = [object Arguments] array = [object Array] #1 toplevel at backtrace.debugger.js:4:37 db> bt full #0 ([object Array], 0, [object Array]) at backtrace.debugger.js:5:5 arguments = [object Arguments] array = [object Array] #1 toplevel at backtrace.debugger.js:4:37 db> where #0 ([object Array], 0, [object Array]) at backtrace.debugger.js:5:5 #1 toplevel at backtrace.debugger.js:4:37 db> c Debugger statement, (1, 0, [object Array]) at backtrace.debugger.js:7:9 db> # test printing locals when exception is thrown before initialization of a value db> c 1 Unwinding due to exception. (Type 'c' to continue unwinding.) #0 mistake([object Array]) at backtrace.debugger.js:14:34 14 let {uninitialized_} = array.shift(); Exception value is: $1 = [object TypeError] TypeError: array.shift() is undefined db> bt full #0 mistake([object Array]) at backtrace.debugger.js:14:34 uninitialized_ = #1 toplevel at backtrace.debugger.js:16:8 db> q Program exited with code 0 cjs-128.1/installed-tests/debugger/breakpoint.debugger0000664000175000017500000000024015116312211021764 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento breakpoint 4 break 6 b 8 c c c c cjs-128.1/installed-tests/debugger/breakpoint.debugger.js0000664000175000017500000000033215116312211022401 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('1'); print('2'); function foo() { print('Function foo'); } print('3'); foo(); cjs-128.1/installed-tests/debugger/breakpoint.debugger.output0000664000175000017500000000106215116312211023326 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> breakpoint 4 Breakpoint 1 at breakpoint.debugger.js:4:1 db> break 6 Breakpoint 2 at breakpoint.debugger.js:6:5 db> b 8 Breakpoint 3 at breakpoint.debugger.js:8:1 db> c 1 Breakpoint 1, toplevel at breakpoint.debugger.js:4:1 db> c 2 Breakpoint 3, toplevel at breakpoint.debugger.js:8:1 db> c 3 Breakpoint 2, foo() at breakpoint.debugger.js:6:5 db> c Function foo Program exited with code 0 cjs-128.1/installed-tests/debugger/continue.debugger0000664000175000017500000000021715116312211021456 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento continue cont c cjs-128.1/installed-tests/debugger/continue.debugger.js0000664000175000017500000000022515116312211022070 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento debugger; debugger; cjs-128.1/installed-tests/debugger/continue.debugger.output0000664000175000017500000000052315116312211023015 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> continue Debugger statement, toplevel at continue.debugger.js:3:1 db> cont Debugger statement, toplevel at continue.debugger.js:4:1 db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/delete.debugger0000664000175000017500000000034115116312211021072 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento b 4 b 5 b 6 b 7 # Check that breakpoint 4 still remains after deleting 1-3 delete 1 del 2 d 3 c c cjs-128.1/installed-tests/debugger/delete.debugger.js0000664000175000017500000000027515116312211021513 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('1'); print('2'); print('3'); print('4'); print('5'); cjs-128.1/installed-tests/debugger/delete.debugger.output0000664000175000017500000000126315116312211022435 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> b 4 Breakpoint 1 at delete.debugger.js:4:1 db> b 5 Breakpoint 2 at delete.debugger.js:5:1 db> b 6 Breakpoint 3 at delete.debugger.js:6:1 db> b 7 Breakpoint 4 at delete.debugger.js:7:1 db> # Check that breakpoint 4 still remains after deleting 1-3 db> delete 1 Breakpoint 1 at delete.debugger.js:4:1 deleted db> del 2 Breakpoint 2 at delete.debugger.js:5:1 deleted db> d 3 Breakpoint 3 at delete.debugger.js:6:1 deleted db> c 1 2 3 4 Breakpoint 4, toplevel at delete.debugger.js:7:1 db> c 5 Program exited with code 0 cjs-128.1/installed-tests/debugger/detach.debugger0000664000175000017500000000020615116312211021060 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento detach cjs-128.1/installed-tests/debugger/detach.debugger.js0000664000175000017500000000021615116312211021474 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('hi'); cjs-128.1/installed-tests/debugger/detach.debugger.output0000664000175000017500000000032315116312211022417 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> detach hi Program exited with code 0 cjs-128.1/installed-tests/debugger/down-up.debugger0000664000175000017500000000024515116312211021224 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c down up up up up up down dn dn dn c cjs-128.1/installed-tests/debugger/down-up.debugger.js0000664000175000017500000000036715116312211021644 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function a() { b(); } function b() { c(); } function c() { d(); } function d() { debugger; } a(); cjs-128.1/installed-tests/debugger/down-up.debugger.output0000664000175000017500000000151415116312211022563 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Debugger statement, d() at down-up.debugger.js:16:5 db> down Youngest frame selected; you cannot go down. db> up #1 c() at down-up.debugger.js:12:5 12 d(); db> up #2 b() at down-up.debugger.js:8:5 8 c(); db> up #3 a() at down-up.debugger.js:4:5 4 b(); db> up #4 toplevel at down-up.debugger.js:19:1 19 a(); db> up Initial frame selected; you cannot go up. db> down #3 a() at down-up.debugger.js:4:5 4 b(); db> dn #2 b() at down-up.debugger.js:8:5 8 c(); db> dn #1 c() at down-up.debugger.js:12:5 12 d(); db> dn #0 d() at down-up.debugger.js:16:5 16 debugger; db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/finish.debugger0000664000175000017500000000022015116312211021104 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c finish c fin c cjs-128.1/installed-tests/debugger/finish.debugger.js0000664000175000017500000000054515116312211021531 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function foo() { print('Print me'); debugger; print('Print me also'); } function bar() { print('Print me'); debugger; print('Print me also'); return 5; } foo(); bar(); print('Print me at the end'); cjs-128.1/installed-tests/debugger/finish.debugger.output0000664000175000017500000000116115116312211022450 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Print me Debugger statement, foo() at finish.debugger.js:5:5 db> finish Run till exit from foo() at finish.debugger.js:5:5 Print me also No value returned. toplevel at finish.debugger.js:16:1 db> c Print me Debugger statement, bar() at finish.debugger.js:11:5 db> fin Run till exit from bar() at finish.debugger.js:11:5 Print me also Value returned is: $1 = 5 toplevel at finish.debugger.js:17:1 db> c Print me at the end Program exited with code 0 cjs-128.1/installed-tests/debugger/frame.debugger0000664000175000017500000000021715116312211020724 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c frame 2 f 1 c cjs-128.1/installed-tests/debugger/frame.debugger.js0000664000175000017500000000030115116312211021331 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function a() { b(); } function b() { debugger; } a(); cjs-128.1/installed-tests/debugger/frame.debugger.output0000664000175000017500000000057315116312211022270 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Debugger statement, b() at frame.debugger.js:8:5 db> frame 2 #2 toplevel at frame.debugger.js:11:1 11 a(); db> f 1 #1 a() at frame.debugger.js:4:5 4 b(); db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/keys.debugger0000664000175000017500000000030415116312211020602 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c keys a k a keys a.foo keys {} keys bar keys ['a', 'b', 'c'] keys c cjs-128.1/installed-tests/debugger/keys.debugger.js0000664000175000017500000000035715116312211021225 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento const a = { foo: 1, bar: null, tres: undefined, [Symbol('s')]: 'string', }; debugger; void a; cjs-128.1/installed-tests/debugger/keys.debugger.output0000664000175000017500000000111415116312211022141 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Debugger statement, toplevel at keys.debugger.js:9:1 db> keys a "foo", "bar", "tres", Symbol("s") db> k a "foo", "bar", "tres", Symbol("s") db> keys a.foo a.foo is 1, not an object db> keys {} No own properties db> keys bar Exception caught while evaluating bar: [object ReferenceError] db> keys ['a', 'b', 'c'] "0", "1", "2", "length" db> keys Missing argument. See 'help keys' db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/lastvalues.debugger0000664000175000017500000000026115116312211022014 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Philip Chimento c p a p b p c p $1 p $2 p $3 p $$ p $6*3 p $$*3 c cjs-128.1/installed-tests/debugger/lastvalues.debugger.js0000664000175000017500000000031615116312211022430 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento const a = undefined; const b = null; const c = 42; debugger; void (a, b, c); cjs-128.1/installed-tests/debugger/lastvalues.debugger.output0000664000175000017500000000067215116312211023361 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2020 Philip Chimento db> c Debugger statement, toplevel at lastvalues.debugger.js:6:1 db> p a $1 = undefined db> p b $2 = null db> p c $3 = 42 db> p $1 $4 = undefined db> p $2 $5 = null db> p $3 $6 = 42 db> p $$ $7 = 42 db> p $6*3 $8 = 126 db> p $$*3 $9 = 378 db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/list.debugger0000664000175000017500000000031615116312211020605 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma set colors false list list 4 list 11 list 12 list 0 list divide break 4 c list q cjs-128.1/installed-tests/debugger/list.debugger.js0000664000175000017500000000047015116312211021221 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma function divide(a, b) { if (b === 0) return undefined; else if (a === undefined || b === undefined) return undefined; else return a / b; } divide(); cjs-128.1/installed-tests/debugger/list.debugger.output0000664000175000017500000000327415116312211022152 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma db> set colors false db> list 6 else if (a === undefined || b === undefined) 7 return undefined; 8 else 9 return a / b; 10 } *11 divide(); db> list 4 1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 2 // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma 3 function divide(a, b) { *4 if (b === 0) 5 return undefined; 6 else if (a === undefined || b === undefined) 7 return undefined; 8 else 9 return a / b; db> list 11 6 else if (a === undefined || b === undefined) 7 return undefined; 8 else 9 return a / b; 10 } *11 divide(); db> list 12 7 return undefined; 8 else 9 return a / b; 10 } 11 divide(); db> list 0 1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 2 // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma 3 function divide(a, b) { 4 if (b === 0) 5 return undefined; db> list divide Unknown option db> break 4 Breakpoint 1 at list.debugger.js:4:9 db> c Breakpoint 1, divide() at list.debugger.js:4:9 db> list 1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 2 // SPDX-FileCopyrightText: 2021 Mantoh Nasah Kuma 3 function divide(a, b) { *4 if (b === 0) 5 return undefined; 6 else if (a === undefined || b === undefined) 7 return undefined; 8 else 9 return a / b; db> q Program exited with code 0 cjs-128.1/installed-tests/debugger/next.debugger0000664000175000017500000000022415116312211020606 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c next n n n n n n n cjs-128.1/installed-tests/debugger/next.debugger.js0000664000175000017500000000036515116312211021227 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function a() { debugger; b(); print('A line in a'); } function b() { print('A line in b'); } a(); cjs-128.1/installed-tests/debugger/next.debugger.output0000664000175000017500000000120015116312211022140 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Debugger statement, a() at next.debugger.js:4:5 db> next a() at next.debugger.js:4:5 db> n a() at next.debugger.js:5:5 A line in b db> n a() at next.debugger.js:6:5 A line in a db> n a() at next.debugger.js:7:1 No value returned. db> n a() at next.debugger.js:7:1 toplevel at next.debugger.js:13:1 db> n toplevel at next.debugger.js:13:1 db> n toplevel at next.debugger.js:14:1 No value returned. db> n toplevel at next.debugger.js:14:1 Program exited with code 0 cjs-128.1/installed-tests/debugger/print.debugger0000664000175000017500000000040015116312211020760 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c # Simple types print a p b p c p d p e p f p g # Objects print h print/b h print/p h p i p/b i p j p k p/b k p l p m p n p o c cjs-128.1/installed-tests/debugger/print.debugger.js0000664000175000017500000000125715116312211021406 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento const {GObject} = imports.gi; const a = undefined; const b = null; const c = 42; const d = 'some string'; const e = false; const f = true; const g = Symbol('foobar'); const h = [1, 'money', 2, 'show', {three: 'to', 'get ready': 'go cat go'}]; const i = {some: 'plain object', that: 'has keys'}; const j = new Set([5, 6, 7]); const k = class J {}; const l = new GObject.Object(); const m = new Error('message'); const n = {a: 1}; const o = {some: 'plain object', [Symbol('that')]: 'has symbols'}; debugger; void (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); cjs-128.1/installed-tests/debugger/print.debugger.output0000664000175000017500000000232115116312211022323 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> c Debugger statement, toplevel at print.debugger.js:19:1 db> # Simple types db> print a $1 = undefined db> p b $2 = null db> p c $3 = 42 db> p d $4 = "some string" db> p e $5 = false db> p f $6 = true db> p g $7 = Symbol("foobar") db> # Objects db> print h $8 = [object Array] [1, "money", 2, "show", { three: "to", get ready: "go cat go" }] db> print/b h $9 = [object Array] [object Array] db> print/p h $10 = [object Array] [1, "money", 2, "show", { three: "to", get ready: "go cat go" }] db> p i $11 = [object Object] { some: "plain object", that: "has keys" } db> p/b i $12 = [object Object] [object Object] db> p j $13 = [object Set] {} db> p k $14 = [object Function] [ Function: J ] db> p/b k $15 = [object Function] [object Function] db> p l $16 = [object GObject_Object] [object instance wrapper GIName:GObject.Object jsobj@0xADDR native@0xADDR] db> p m $17 = [object Error] Error: message db> p n $18 = [object Object] { a: 1 } db> p o $19 = [object Object] { some: "plain object", [Symbol("that")]: "has symbols" } db> c Program exited with code 0 cjs-128.1/installed-tests/debugger/quit.debugger0000664000175000017500000000020115116312211020605 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento q cjs-128.1/installed-tests/debugger/quit.debugger.js0000664000175000017500000000021615116312211021226 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('hi'); cjs-128.1/installed-tests/debugger/quit.debugger.output0000664000175000017500000000031315116312211022150 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> q Program exited with code 0 cjs-128.1/installed-tests/debugger/return.debugger0000664000175000017500000000033315116312211021150 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento b 4 b 8 b 12 c f 1 return f 0 return ret 5 ret foo p 2 ret `${4 * 10 + $1} is the answer` c cjs-128.1/installed-tests/debugger/return.debugger.js0000664000175000017500000000043515116312211021566 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function func1() { return 1; } function func2() { return 2; } function func3() { return 3; } print(func1()); print(func2()); print(func3()); cjs-128.1/installed-tests/debugger/return.debugger.output0000664000175000017500000000156015116312211022512 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> b 4 Breakpoint 1 at return.debugger.js:4:5 db> b 8 Breakpoint 2 at return.debugger.js:8:5 db> b 12 Breakpoint 3 at return.debugger.js:12:5 db> c Breakpoint 1, func1() at return.debugger.js:4:5 db> f 1 #1 toplevel at return.debugger.js:15:7 15 print(func1()); db> return To return, you must select the newest frame (use 'frame 0') db> f 0 #0 func1() at return.debugger.js:4:5 4 return 1; db> return undefined Breakpoint 2, func2() at return.debugger.js:8:5 db> ret 5 5 Breakpoint 3, func3() at return.debugger.js:12:5 db> ret foo Exception caught while evaluating foo: [object ReferenceError] db> p 2 $1 = 2 db> ret `${4 * 10 + $1} is the answer` 42 is the answer Program exited with code 0 cjs-128.1/installed-tests/debugger/set.debugger0000664000175000017500000000062315116312211020426 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento # Currently the only option is "pretty" for pretty-printing. Set doesn't yet # allow setting variables in the program. c p a set pretty 0 p a set pretty 1 p a set pretty off p a set pretty on p a set pretty false p a set pretty true p a set pretty no p a set pretty yes p a q cjs-128.1/installed-tests/debugger/set.debugger.js0000664000175000017500000000024115116312211021035 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento const a = {}; debugger; void a; cjs-128.1/installed-tests/debugger/set.debugger.output0000664000175000017500000000145515116312211021771 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> # Currently the only option is "pretty" for pretty-printing. Set doesn't yet db> # allow setting variables in the program. db> c Debugger statement, toplevel at set.debugger.js:4:1 db> p a $1 = [object Object] {} db> set pretty 0 db> p a $2 = [object Object] db> set pretty 1 db> p a $3 = [object Object] {} db> set pretty off db> p a $4 = [object Object] db> set pretty on db> p a $5 = [object Object] {} db> set pretty false db> p a $6 = [object Object] db> set pretty true db> p a $7 = [object Object] {} db> set pretty no db> p a $8 = [object Object] db> set pretty yes db> p a $9 = [object Object] {} db> q Program exited with code 0 cjs-128.1/installed-tests/debugger/step.debugger0000664000175000017500000000022715116312211020606 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento s s s s s s s s s s s s cjs-128.1/installed-tests/debugger/step.debugger.js0000664000175000017500000000034715116312211021224 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function a() { b(); print('A line in a'); } function b() { print('A line in b'); } a(); cjs-128.1/installed-tests/debugger/step.debugger.output0000664000175000017500000000153415116312211022147 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> s toplevel at step.debugger.js:12:1 entered frame: a() at step.debugger.js:4:5 db> s a() at step.debugger.js:4:5 entered frame: b() at step.debugger.js:9:5 db> s b() at step.debugger.js:9:5 A line in b db> s b() at step.debugger.js:10:1 No value returned. db> s b() at step.debugger.js:10:1 a() at step.debugger.js:4:5 db> s a() at step.debugger.js:4:5 db> s a() at step.debugger.js:5:5 A line in a db> s a() at step.debugger.js:6:1 No value returned. db> s a() at step.debugger.js:6:1 toplevel at step.debugger.js:12:1 db> s toplevel at step.debugger.js:12:1 db> s toplevel at step.debugger.js:13:1 No value returned. db> s toplevel at step.debugger.js:13:1 Program exited with code 0 cjs-128.1/installed-tests/debugger/throw-ignored.debugger0000664000175000017500000000017615116312211022426 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Florian Müllner c q cjs-128.1/installed-tests/debugger/throw-ignored.debugger.js0000664000175000017500000000043215116312211023034 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Florian Müllner let count = 0; function a() { throw new Error(`Exception nº ${++count}`); } try { a(); } catch (e) { print(`Caught exception: ${e}`); } a(); cjs-128.1/installed-tests/debugger/throw-ignored.debugger.output0000664000175000017500000000072415116312211023764 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2021 Florian Müllner db> c Caught exception: Error: Exception nº 1 Unwinding due to exception. (Type 'c' to continue unwinding.) #0 a() at throw-ignored.debugger.js:7:11 7 throw new Error(`Exception nº ${++count}`); Exception value is: $1 = [object Error] Error: Exception nº 2 db> q Program exited with code 0 cjs-128.1/installed-tests/debugger/throw.debugger0000664000175000017500000000034215116312211020774 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento set ignoreCaughtExceptions false c f 1 throw {} f 0 p 3.14 throw 'foobar' + $1 fin throw foo throw cjs-128.1/installed-tests/debugger/throw.debugger.js0000664000175000017500000000035415116312211021412 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento function a() { debugger; return 5; } try { a(); } catch (e) { print(`Exception: ${e}`); } cjs-128.1/installed-tests/debugger/throw.debugger.output0000664000175000017500000000207315116312211022336 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> set ignoreCaughtExceptions false db> c Debugger statement, a() at throw.debugger.js:4:5 db> f 1 #1 toplevel at throw.debugger.js:9:5 9 a(); db> throw {} To throw, you must select the newest frame (use 'frame 0') db> f 0 #0 a() at throw.debugger.js:4:5 4 debugger; db> p 3.14 $1 = 3.14 db> throw 'foobar' + $1 Unwinding due to exception. (Type 'c' to continue unwinding.) #0 a() at throw.debugger.js:4:5 4 debugger; Exception value is: $2 = "foobar3.14" db> fin Run till exit from a() at throw.debugger.js:4:5 Frame terminated by exception: $3 = "foobar3.14" (To rethrow it, type 'throw'.) Unwinding due to exception. (Type 'c' to continue unwinding.) #0 toplevel at throw.debugger.js:9:5 9 a(); Exception value is: $4 = "foobar3.14" db> throw foo Exception caught while evaluating foo: [object ReferenceError] db> throw Exception: foobar3.14 Program exited with code 0 cjs-128.1/installed-tests/debugger/until.debugger0000664000175000017500000000022415116312211020763 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento until 5 upto 7 u 9 c cjs-128.1/installed-tests/debugger/until.debugger.js0000664000175000017500000000032615116312211021401 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('1'); print('2'); print('3'); (function () { print('4'); })(); print('5'); cjs-128.1/installed-tests/debugger/until.debugger.output0000664000175000017500000000071215116312211022324 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2018 Philip Chimento db> until 5 toplevel at until.debugger.js:3:1 1 2 db> upto 7 toplevel at until.debugger.js:5:1 3 entered frame: () at until.debugger.js:7:5 db> u 9 () at until.debugger.js:7:5 4 No value returned. toplevel at until.debugger.js:9:1 db> c 5 Program exited with code 0 cjs-128.1/installed-tests/extra/0000775000175000017500000000000015116312211015463 5ustar fabiofabiocjs-128.1/installed-tests/extra/gjs.supp0000664000175000017500000000501015116312211017153 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2008 litl, LLC # Valgrind suppressions file for GJS # This is intended to be used in addition to GLib's glib.supp file. # SpiderMonkey leaks { mozjs-thread-stack-init Memcheck:Leak match-leak-kinds: possible fun:calloc ... fun:pthread_create@@GLIBC_2.2.5 fun:_ZN7mozilla9TimeStamp20ComputeProcessUptimeEv fun:_ZN7mozilla9TimeStamp15ProcessCreationEPb fun:_ZN2JS6detail25InitWithFailureDiagnosticEb fun:_Z7JS_Initv } # Various things that I don't believe are related to GJS { gtk-style-context Memcheck:Leak match-leak-kinds: possible fun:malloc fun:g_malloc ... fun:gtk_css_node_declaration_make_writable* ... fun:gtk_style_constructed } # https://bugs.freedesktop.org/show_bug.cgi?id=105466 { freedesktop-bug-105466 Memcheck:Leak match-leak-kinds: definite fun:malloc ... fun:FcConfigSubstituteWithPat fun:_cairo_ft_resolve_pattern fun:_cairo_ft_font_face_get_implementation fun:cairo_scaled_font_create fun:_cairo_gstate_ensure_scaled_font ... fun:_cairo_default_context_get_scaled_font fun:cairo_show_text } # Data that Cairo keeps around for the process lifetime # This could be freed by calling cairo_debug_reset_static_data(), but it's # not a good idea to call that function in production, because certain versions # of Cairo have bugs that cause it to fail assertions and crash. { cairo-static-data Memcheck:Leak match-leak-kinds: definite fun:malloc ... fun:FcPatternDuplicate fun:_cairo_ft_font_face_create_for_pattern ... fun:_cairo_gstate_ensure_scaled_font ... fun:_cairo_default_context_get_scaled_font ... fun:cairo_show_text } # https://gitlab.gnome.org/GNOME/gobject-introspection/issues/265 { gobject-introspection-default-repository Memcheck:Leak match-leak-kinds: definite fun:realloc ... fun:build_typelib_key fun:register_internal } # Workaround for https://github.com/mesonbuild/meson/issues/4427 # When fixed, valgrind should already not trace bash { bash-workaround Memcheck:Leak match-leak-kinds: definite fun:malloc fun:xmalloc fun:set_default_locale fun:main } # https://gitlab.gnome.org/GNOME/glib/-/issues/1911 { g-type-register-static Memcheck:Leak match-leak-kinds:possible fun:malloc ... fun:g_type_register_static } { g-type-register-static-calloc Memcheck:Leak match-leak-kinds:possible fun:calloc ... fun:g_type_register_static } cjs-128.1/installed-tests/extra/lsan.supp0000664000175000017500000000121415116312211017327 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2017 Endless Mobile, Inc. # SpiderMonkey leaks a mutex for each GC helper thread. leak:js::HelperThread::threadLoop # https://bugs.freedesktop.org/show_bug.cgi?id=105466 leak:libfontconfig.so.1 # https://bugzilla.mozilla.org/show_bug.cgi?id=1478679 leak:js::coverage::LCovSource::writeScript leak:js/src/util/Text.cpp # GIO Module instances are created once and they're expected to be "leaked" leak:g_io_module_new # Gtk test may leak because of a Gdk/X11 issue: # https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6037 leak:gdk_x11_selection_input_stream_new_async cjs-128.1/installed-tests/extra/tsan.supp0000664000175000017500000000072015116312211017340 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Canonical Ltd. # When calling a closure we expect the async function to initialize # memory that is later managed by the worker thread, if something is not # synchronized, this is not a gjs issue so we can ignore it. # Also those are mostly false positive as it can be tested by removing # this line and testing with glib compiled with -Db_sanitize=thread race:gjs_closure_invoke cjs-128.1/installed-tests/js/0000775000175000017500000000000015116312211014754 5ustar fabiofabiocjs-128.1/installed-tests/js/.eslintrc.yml0000664000175000017500000000327315116312211017405 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento env: jasmine: true rules: no-restricted-globals: - error - name: fdescribe message: Do not commit fdescribe(). Use describe() instead. - name: fit message: Do not commit fit(). Use it() instead. no-restricted-syntax: - error - selector: CallExpression[callee.name="it"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="beforeEach"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="afterEach"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="beforeAll"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead - selector: CallExpression[callee.name="afterAll"] > ArrowFunctionExpression message: Arrow functions can mess up some Jasmine APIs. Use function () instead overrides: - files: - matchers.js - minijasmine.js - minijasmine-executor.js - testAsync.js - testAsyncMainloop.js - testCairoModule.js - testConsole.js - testESModules.js - testEncoding.js - testGLibLogWriter.js - testTimers.js - testWeakRef.js - modules/importmeta.js - modules/exports.js - modules/greet.js - modules/say.js - modules/sideEffect4.js parserOptions: sourceType: module cjs-128.1/installed-tests/js/complex3.ui0000664000175000017500000000242215116312211017045 0ustar fabiofabio cjs-128.1/installed-tests/js/complex4.ui0000664000175000017500000000213215116312211017044 0ustar fabiofabio cjs-128.1/installed-tests/js/jasmine.js0000664000175000017500000076243215116312211016756 0ustar fabiofabio// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2008-2020 Pivotal Labs // eslint-disable-next-line no-unused-vars var getJasmineRequireObj = (function(jasmineGlobal) { var jasmineRequire; if ( typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined' ) { if (typeof global !== 'undefined') { jasmineGlobal = global; } else { jasmineGlobal = {}; } jasmineRequire = exports; } else { if ( typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]' ) { jasmineGlobal = window; } jasmineRequire = jasmineGlobal.jasmineRequire = {}; } function getJasmineRequire() { return jasmineRequire; } getJasmineRequire().core = function(jRequire) { var j$ = {}; jRequire.base(j$, jasmineGlobal); j$.util = jRequire.util(j$); j$.errors = jRequire.errors(); j$.formatErrorMsg = jRequire.formatErrorMsg(); j$.Any = jRequire.Any(j$); j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); j$.MockDate = jRequire.MockDate(); j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); j$.Env = jRequire.Env(j$); j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); j$.Expector = jRequire.Expector(j$); j$.Expectation = jRequire.Expectation(j$); j$.buildExpectationResult = jRequire.buildExpectationResult(j$); j$.JsApiReporter = jRequire.JsApiReporter(j$); j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( j$ ); j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$); j$.pp = j$.makePrettyPrinter(); j$.MatchersUtil = jRequire.MatchersUtil(j$); j$.matchersUtil = new j$.MatchersUtil({ customTesters: [], pp: j$.pp }); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); j$.MapContaining = jRequire.MapContaining(j$); j$.SetContaining = jRequire.SetContaining(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); j$.UserContext = jRequire.UserContext(j$); j$.Suite = jRequire.Suite(j$); j$.Timer = jRequire.Timer(); j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.Order = jRequire.Order(); j$.DiffBuilder = jRequire.DiffBuilder(j$); j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$); j$.ObjectPath = jRequire.ObjectPath(j$); j$.MismatchTree = jRequire.MismatchTree(j$); j$.GlobalErrors = jRequire.GlobalErrors(j$); j$.Truthy = jRequire.Truthy(j$); j$.Falsy = jRequire.Falsy(j$); j$.Empty = jRequire.Empty(j$); j$.NotEmpty = jRequire.NotEmpty(j$); j$.matchers = jRequire.requireMatchers(jRequire, j$); j$.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$); return j$; }; return getJasmineRequire; })(this); getJasmineRequireObj().requireMatchers = function(jRequire, j$) { var availableMatchers = [ 'nothing', 'toBe', 'toBeCloseTo', 'toBeDefined', 'toBeInstanceOf', 'toBeFalse', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeLessThan', 'toBeLessThanOrEqual', 'toBeNaN', 'toBeNegativeInfinity', 'toBeNull', 'toBePositiveInfinity', 'toBeTrue', 'toBeTruthy', 'toBeUndefined', 'toContain', 'toEqual', 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', 'toHaveBeenCalledOnceWith', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', 'toMatch', 'toThrow', 'toThrowError', 'toThrowMatching' ], matchers = {}; for (var i = 0; i < availableMatchers.length; i++) { var name = availableMatchers[i]; matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().base = function(j$, jasmineGlobal) { j$.unimplementedMethod_ = function() { throw new Error('unimplemented method'); }; /** * Maximum object depth the pretty printer will print to. * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH * @since 1.3.0 */ j$.MAX_PRETTY_PRINT_DEPTH = 8; /** * Maximum number of array elements to display when pretty printing objects. * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH * @since 2.7.0 */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; /** * Maximum number of characters to display when pretty printing objects. * Characters past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_CHARS * @since 2.9.0 */ j$.MAX_PRETTY_PRINT_CHARS = 1000; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL * @since 1.3.0 */ j$.DEFAULT_TIMEOUT_INTERVAL = 5000; j$.getGlobal = function() { return jasmineGlobal; }; /** * Get the currently booted Jasmine Environment. * * @name jasmine.getEnv * @since 1.3.0 * @function * @return {Env} */ j$.getEnv = function(options) { var env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); //jasmine. singletons in here (setTimeout blah blah). return env; }; j$.isArray_ = function(value) { return j$.isA_('Array', value); }; j$.isObject_ = function(value) { return ( !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value) ); }; j$.isString_ = function(value) { return j$.isA_('String', value); }; j$.isNumber_ = function(value) { return j$.isA_('Number', value); }; j$.isFunction_ = function(value) { return j$.isA_('Function', value); }; j$.isAsyncFunction_ = function(value) { return j$.isA_('AsyncFunction', value); }; j$.isTypedArray_ = function(value) { return ( j$.isA_('Float32Array', value) || j$.isA_('Float64Array', value) || j$.isA_('Int16Array', value) || j$.isA_('Int32Array', value) || j$.isA_('Int8Array', value) || j$.isA_('Uint16Array', value) || j$.isA_('Uint32Array', value) || j$.isA_('Uint8Array', value) || j$.isA_('Uint8ClampedArray', value) ); }; j$.isA_ = function(typeName, value) { return j$.getType_(value) === '[object ' + typeName + ']'; }; j$.isError_ = function(value) { if (value instanceof Error) { return true; } if (value && value.constructor && value.constructor.constructor) { var valueGlobal = value.constructor.constructor('return this'); if (j$.isFunction_(valueGlobal)) { valueGlobal = valueGlobal(); } if (valueGlobal.Error && value instanceof valueGlobal.Error) { return true; } } return false; }; j$.isAsymmetricEqualityTester_ = function(obj) { return obj ? j$.isA_('Function', obj.asymmetricMatch) : false; }; j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; j$.isDomNode = function(obj) { // Node is a function, because constructors return typeof jasmineGlobal.Node !== 'undefined' ? obj instanceof jasmineGlobal.Node : obj !== null && typeof obj === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string'; // return obj.nodeType > 0; }; j$.isMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.Map !== 'undefined' && obj.constructor === jasmineGlobal.Map ); }; j$.isSet = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.Set !== 'undefined' && obj.constructor === jasmineGlobal.Set ); }; j$.isWeakMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.WeakMap !== 'undefined' && obj.constructor === jasmineGlobal.WeakMap ); }; j$.isDataView = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && typeof jasmineGlobal.DataView !== 'undefined' && obj.constructor === jasmineGlobal.DataView ); }; j$.isPromise = function(obj) { return ( typeof jasmineGlobal.Promise !== 'undefined' && !!obj && obj.constructor === jasmineGlobal.Promise ); }; j$.isPromiseLike = function(obj) { return !!obj && j$.isFunction_(obj.then); }; j$.fnNameFor = function(func) { if (func.name) { return func.name; } var matches = func.toString().match(/^\s*function\s*(\w+)\s*\(/) || func.toString().match(/^\s*\[object\s*(\w+)Constructor\]/); return matches ? matches[1] : ''; }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is an instance of the specified class/constructor. * @name jasmine.any * @since 1.3.0 * @function * @param {Constructor} clazz - The constructor to check against. */ j$.any = function(clazz) { return new j$.Any(clazz); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is not `null` and not `undefined`. * @name jasmine.anything * @since 2.2.0 * @function */ j$.anything = function() { return new j$.Anything(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is `true` or anything truthy. * @name jasmine.truthy * @since 3.1.0 * @function */ j$.truthy = function() { return new j$.Truthy(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey. * @name jasmine.falsy * @since 3.1.0 * @function */ j$.falsy = function() { return new j$.Falsy(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is empty. * @name jasmine.empty * @since 3.1.0 * @function */ j$.empty = function() { return new j$.Empty(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared is not empty. * @name jasmine.notEmpty * @since 3.1.0 * @function */ j$.notEmpty = function() { return new j$.NotEmpty(); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value being compared contains at least the keys and values. * @name jasmine.objectContaining * @since 1.3.0 * @function * @param {Object} sample - The subset of properties that _must_ be in the actual. */ j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. * @name jasmine.stringMatching * @since 2.2.0 * @function * @param {RegExp|String} expected */ j$.stringMatching = function(expected) { return new j$.StringMatching(expected); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is an `Array` that contains at least the elements in the sample. * @name jasmine.arrayContaining * @since 2.2.0 * @function * @param {Array} sample */ j$.arrayContaining = function(sample) { return new j$.ArrayContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order. * @name jasmine.arrayWithExactContents * @since 2.8.0 * @function * @param {Array} sample */ j$.arrayWithExactContents = function(sample) { return new j$.ArrayWithExactContents(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if every key/value pair in the sample passes the deep equality comparison * with at least one key/value pair in the actual value being compared * @name jasmine.mapContaining * @since 3.5.0 * @function * @param {Map} sample - The subset of items that _must_ be in the actual. */ j$.mapContaining = function(sample) { return new j$.MapContaining(sample); }; /** * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * that will succeed if every item in the sample passes the deep equality comparison * with at least one item in the actual value being compared * @name jasmine.setContaining * @since 3.5.0 * @function * @param {Set} sample - The subset of items that _must_ be in the actual. */ j$.setContaining = function(sample) { return new j$.SetContaining(sample); }; j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; } return ( putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker ); }; }; getJasmineRequireObj().util = function(j$) { var util = {}; util.inherit = function(childClass, parentClass) { var Subclass = function() {}; Subclass.prototype = parentClass.prototype; childClass.prototype = new Subclass(); }; util.htmlEscape = function(str) { if (!str) { return str; } return str .replace(/&/g, '&') .replace(//g, '>'); }; util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) { arrayOfArgs.push(args[i]); } return arrayOfArgs; }; util.isUndefined = function(obj) { return obj === void 0; }; util.arrayContains = function(array, search) { var i = array.length; while (i--) { if (array[i] === search) { return true; } } return false; }; util.clone = function(obj) { if (Object.prototype.toString.apply(obj) === '[object Array]') { return obj.slice(); } var cloned = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; }; util.cloneArgs = function(args) { var clonedArgs = []; var argsAsArray = j$.util.argsToArray(args); for (var i = 0; i < argsAsArray.length; i++) { var str = Object.prototype.toString.apply(argsAsArray[i]), primitives = /^\[object (Boolean|String|RegExp|Number)/; // All falsey values are either primitives, `null`, or `undefined. if (!argsAsArray[i] || str.match(primitives)) { clonedArgs.push(argsAsArray[i]); } else { clonedArgs.push(j$.util.clone(argsAsArray[i])); } } return clonedArgs; }; util.getPropertyDescriptor = function(obj, methodName) { var descriptor, proto = obj; do { descriptor = Object.getOwnPropertyDescriptor(proto, methodName); proto = Object.getPrototypeOf(proto); } while (!descriptor && proto); return descriptor; }; util.objectDifference = function(obj, toRemove) { var diff = {}; for (var key in obj) { if (util.has(obj, key) && !util.has(toRemove, key)) { diff[key] = obj[key]; } } return diff; }; util.has = function(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); }; util.errorWithStack = function errorWithStack() { // Don't throw and catch if we don't have to, because it makes it harder // for users to debug their code with exception breakpoints. var error = new Error(); if (error.stack) { return error; } // But some browsers (e.g. Phantom) only provide a stack trace if we throw. try { throw new Error(); } catch (e) { return e; } }; function callerFile() { var trace = new j$.StackTrace(util.errorWithStack()); return trace.frames[2].file; } util.jasmineFile = (function() { var result; return function() { if (!result) { result = callerFile(); } return result; }; })(); function StopIteration() {} StopIteration.prototype = Object.create(Error.prototype); StopIteration.prototype.constructor = StopIteration; // useful for maps and sets since `forEach` is the only IE11-compatible way to iterate them util.forEachBreakable = function(iterable, iteratee) { function breakLoop() { throw new StopIteration(); } try { iterable.forEach(function(value, key) { iteratee(breakLoop, value, key, iterable); }); } catch (error) { if (!(error instanceof StopIteration)) throw error; } }; return util; }; getJasmineRequireObj().Spec = function(j$) { function Spec(attrs) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return { befores: [], afters: [] }; }; this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() {}; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.timer = attrs.timer || new j$.Timer(); if (!this.queueableFn.fn) { this.pend(); } /** * @typedef SpecResult * @property {Int} id - The unique id of this spec. * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [], passedExpectations: [], deprecationWarnings: [], pendingReason: '', duration: null, properties: null }; } Spec.prototype.addExpectationResult = function(passed, data, isError) { var expectationResult = this.expectationResultFactory(data); if (passed) { this.result.passedExpectations.push(expectationResult); } else { this.result.failedExpectations.push(expectationResult); if (this.throwOnExpectationFailure && !isError) { throw new j$.errors.ExpectationFailed(); } } }; Spec.prototype.setSpecProperty = function(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; }; Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Spec.prototype.expectAsync = function(actual) { return this.asyncExpectationFactory(actual, this); }; Spec.prototype.execute = function(onComplete, excluded, failSpecWithNoExp) { var self = this; var onStart = { fn: function(done) { self.timer.start(); self.onStart(self, done); } }; var complete = { fn: function(done) { self.queueableFn.fn = null; self.result.status = self.status(excluded, failSpecWithNoExp); self.result.duration = self.timer.elapsed(); self.resultCallback(self.result, done); } }; var fns = this.beforeAndAfterFns(); var regularFns = fns.befores.concat(this.queueableFn); var runnerConfig = { isLeaf: true, queueableFns: regularFns, cleanupFns: fns.afters, onException: function() { self.onException.apply(self, arguments); }, onComplete: function() { onComplete( self.result.status === 'failed' && new j$.StopExecutionError('spec failed') ); }, userContext: this.userContext() }; if (this.markedPending || excluded === true) { runnerConfig.queueableFns = []; runnerConfig.cleanupFns = []; } runnerConfig.queueableFns.unshift(onStart); runnerConfig.cleanupFns.push(complete); this.queueRunnerFactory(runnerConfig); }; Spec.prototype.onException = function onException(e) { if (Spec.isPendingSpecException(e)) { this.pend(extractCustomPendingMessage(e)); return; } if (e instanceof j$.errors.ExpectationFailed) { return; } this.addExpectationResult( false, { matcherName: '', passed: false, expected: '', actual: '', error: e }, true ); }; Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { this.result.pendingReason = message; } }; Spec.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Spec.prototype.status = function(excluded, failSpecWithNoExpectations) { if (excluded === true) { return 'excluded'; } if (this.markedPending) { return 'pending'; } if ( this.result.failedExpectations.length > 0 || (failSpecWithNoExpectations && this.result.failedExpectations.length + this.result.passedExpectations.length === 0) ) { return 'failed'; } return 'passed'; }; Spec.prototype.getFullName = function() { return this.getSpecName(this); }; Spec.prototype.addDeprecationWarning = function(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( this.expectationResultFactory(deprecation) ); }; var extractCustomPendingMessage = function(e) { var fullMessage = e.toString(), boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; return fullMessage.substr(boilerplateEnd); }; Spec.pendingSpecExceptionMessage = '=> marked Pending'; Spec.isPendingSpecException = function(e) { return !!( e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1 ); }; return Spec; }; if (typeof window == void 0 && typeof exports == 'object') { /* globals exports */ exports.Spec = jasmineRequire.Spec; } /*jshint bitwise: false*/ getJasmineRequireObj().Order = function() { function Order(options) { this.random = 'random' in options ? options.random : true; var seed = (this.seed = options.seed || generateSeed()); this.sort = this.random ? randomOrder : naturalOrder; function naturalOrder(items) { return items; } function randomOrder(items) { var copy = items.slice(); copy.sort(function(a, b) { return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); }); return copy; } function generateSeed() { return String(Math.random()).slice(-5); } // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function // used to get a different output when the key changes slightly. // We use your return to sort the children randomly in a consistent way when // used in conjunction with a seed function jenkinsHash(key) { var hash, i; for (hash = i = 0; i < key.length; ++i) { hash += key.charCodeAt(i); hash += hash << 10; hash ^= hash >> 6; } hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; return hash; } } return Order; }; getJasmineRequireObj().Env = function(j$) { /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. * @name Env * @since 2.0.0 * @classdesc The Jasmine environment * @constructor */ function Env(options) { options = options || {}; var self = this; var global = options.global || j$.getGlobal(); var customPromise; var totalSpecsDefined = 0; var realSetTimeout = global.setTimeout; var realClearTimeout = global.clearTimeout; var clearStack = j$.getClearStack(global); this.clock = new j$.Clock( global, function() { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global) ); var runnableResources = {}; var currentSpec = null; var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var hasFailures = false; /** * This represents the available options to configure Jasmine. * Options that are not provided will use their default values * @interface Configuration * @since 3.3.0 */ var config = { /** * Whether to randomize spec execution order * @name Configuration#random * @since 3.3.0 * @type Boolean * @default true */ random: true, /** * Seed to use as the basis of randomization. * Null causes the seed to be determined randomly at the start of execution. * @name Configuration#seed * @since 3.3.0 * @type function * @default null */ seed: null, /** * Whether to stop execution of the suite after the first spec failure * @name Configuration#failFast * @since 3.3.0 * @type Boolean * @default false */ failFast: false, /** * Whether to fail the spec if it ran no expectations. By default * a spec that ran no expectations is reported as passed. Setting this * to true will report such spec as a failure. * @name Configuration#failSpecWithNoExpectations * @since 3.5.0 * @type Boolean * @default false */ failSpecWithNoExpectations: false, /** * Whether to cause specs to only have one expectation failure. * @name Configuration#oneFailurePerSpec * @since 3.3.0 * @type Boolean * @default false */ oneFailurePerSpec: false, /** * Function to use to filter specs * @name Configuration#specFilter * @since 3.3.0 * @type function * @default true */ specFilter: function() { return true; }, /** * Whether or not reporters should hide disabled specs from their output. * Currently only supported by Jasmine's HTMLReporter * @name Configuration#hideDisabled * @since 3.3.0 * @type Boolean * @default false */ hideDisabled: false, /** * Set to provide a custom promise library that Jasmine will use if it needs * to create a promise. If not set, it will default to whatever global Promise * library is available (if any). * @name Configuration#Promise * @since 3.5.0 * @type function * @default undefined */ Promise: undefined }; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; }; var currentRunnable = function() { return currentSpec || currentSuite(); }; var globalErrors = null; var installGlobalErrors = function() { if (globalErrors) { return; } globalErrors = new j$.GlobalErrors(); globalErrors.install(); }; if (!options.suppressLoadErrors) { installGlobalErrors(); globalErrors.pushListener(function( message, filename, lineno, colNo, err ) { topSuite.result.failedExpectations.push({ passed: false, globalErrorType: 'load', message: message, stack: err && err.stack, filename: filename, lineno: lineno }); }); } /** * Configure your jasmine environment * @name Env#configure * @since 3.3.0 * @argument {Configuration} configuration * @function */ this.configure = function(configuration) { if (configuration.specFilter) { config.specFilter = configuration.specFilter; } if (configuration.hasOwnProperty('random')) { config.random = !!configuration.random; } if (configuration.hasOwnProperty('seed')) { config.seed = configuration.seed; } if (configuration.hasOwnProperty('failFast')) { config.failFast = configuration.failFast; } if (configuration.hasOwnProperty('failSpecWithNoExpectations')) { config.failSpecWithNoExpectations = configuration.failSpecWithNoExpectations; } if (configuration.hasOwnProperty('oneFailurePerSpec')) { config.oneFailurePerSpec = configuration.oneFailurePerSpec; } if (configuration.hasOwnProperty('hideDisabled')) { config.hideDisabled = configuration.hideDisabled; } // Don't use hasOwnProperty to check for Promise existence because Promise // can be initialized to undefined, either explicitly or by using the // object returned from Env#configuration. In particular, Karma does this. if (configuration.Promise) { if ( typeof configuration.Promise.resolve === 'function' && typeof configuration.Promise.reject === 'function' ) { customPromise = configuration.Promise; } else { throw new Error( 'Custom promise library missing `resolve`/`reject` functions' ); } } }; /** * Get the current configuration for your jasmine environment * @name Env#configuration * @since 3.3.0 * @function * @returns {Configuration} */ this.configuration = function() { var result = {}; for (var property in config) { result[property] = config[property]; } return result; }; Object.defineProperty(this, 'specFilter', { get: function() { self.deprecated( 'Getting specFilter directly from Env is deprecated and will be removed in a future version of Jasmine, please check the specFilter option from `configuration`' ); return config.specFilter; }, set: function(val) { self.deprecated( 'Setting specFilter directly on Env is deprecated and will be removed in a future version of Jasmine, please use the specFilter option in `configure`' ); config.specFilter = val; } }); this.setDefaultSpyStrategy = function(defaultStrategyFn) { if (!currentRunnable()) { throw new Error( 'Default spy strategy must be set in a before function or a spec' ); } runnableResources[ currentRunnable().id ].defaultStrategyFn = defaultStrategyFn; }; this.addSpyStrategy = function(name, fn) { if (!currentRunnable()) { throw new Error( 'Custom spy strategies must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; }; this.addCustomEqualityTester = function(tester) { if (!currentRunnable()) { throw new Error( 'Custom Equalities must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customEqualityTesters.push( tester ); }; this.addMatchers = function(matchersToAdd) { if (!currentRunnable()) { throw new Error( 'Matchers must be added in a before function or a spec' ); } var customMatchers = runnableResources[currentRunnable().id].customMatchers; for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } }; this.addAsyncMatchers = function(matchersToAdd) { if (!currentRunnable()) { throw new Error( 'Async Matchers must be added in a before function or a spec' ); } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } }; this.addCustomObjectFormatter = function(formatter) { if (!currentRunnable()) { throw new Error( 'Custom object formatters must be added in a before function or a spec' ); } runnableResources[currentRunnable().id].customObjectFormatters.push( formatter ); }; j$.Expectation.addCoreMatchers(j$.matchers); j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); var nextSpecId = 0; var getNextSpecId = function() { return 'spec' + nextSpecId++; }; var nextSuiteId = 0; var getNextSuiteId = function() { return 'suite' + nextSuiteId++; }; var makePrettyPrinter = function() { var customObjectFormatters = runnableResources[currentRunnable().id].customObjectFormatters; return j$.makePrettyPrinter(customObjectFormatters); }; var makeMatchersUtil = function() { var customEqualityTesters = runnableResources[currentRunnable().id].customEqualityTesters; return new j$.MatchersUtil({ customTesters: customEqualityTesters, pp: makePrettyPrinter() }); }; var expectationFactory = function(actual, spec) { var customEqualityTesters = runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ matchersUtil: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { return spec.addExpectationResult(passed, result); } }; function recordLateExpectation(runable, runableType, result) { var delayedExpectationResult = {}; Object.keys(result).forEach(function(k) { delayedExpectationResult[k] = result[k]; }); delayedExpectationResult.passed = false; delayedExpectationResult.globalErrorType = 'lateExpectation'; delayedExpectationResult.message = runableType + ' "' + runable.getFullName() + '" ran a "' + result.matcherName + '" expectation after it finished.\n'; if (result.message) { delayedExpectationResult.message += 'Message: "' + result.message + '"\n'; } delayedExpectationResult.message += 'Did you forget to return or await the result of expectAsync?'; topSuite.result.failedExpectations.push(delayedExpectationResult); } var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { if (currentRunnable() !== spec) { recordLateExpectation(spec, runableType, result); } return spec.addExpectationResult(passed, result); } }; var suiteAsyncExpectationFactory = function(actual, suite) { return asyncExpectationFactory(actual, suite, 'Suite'); }; var specAsyncExpectationFactory = function(actual, suite) { return asyncExpectationFactory(actual, suite, 'Spec'); }; var defaultResourcesForRunnable = function(id, parentRunnableId) { var resources = { spies: [], customEqualityTesters: [], customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, defaultStrategyFn: undefined, customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { resources.customEqualityTesters = j$.util.clone( runnableResources[parentRunnableId].customEqualityTesters ); resources.customMatchers = j$.util.clone( runnableResources[parentRunnableId].customMatchers ); resources.customAsyncMatchers = j$.util.clone( runnableResources[parentRunnableId].customAsyncMatchers ); resources.defaultStrategyFn = runnableResources[parentRunnableId].defaultStrategyFn; } runnableResources[id] = resources; }; var clearResourcesForRunnable = function(id) { spyRegistry.clearSpies(); delete runnableResources[id]; }; var beforeAndAfterFns = function(suite) { return function() { var befores = [], afters = []; while (suite) { befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); suite = suite.parentSuite; } return { befores: befores.reverse(), afters: afters }; }; }; var getSpecName = function(spec, suite) { var fullName = [spec.description], suiteFullName = suite.getFullName(); if (suiteFullName !== '') { fullName.unshift(suiteFullName); } return fullName.join(' '); }; // TODO: we may just be able to pass in the fn instead of wrapping here var buildExpectationResult = j$.buildExpectationResult, exceptionFormatter = new j$.ExceptionFormatter(), expectationResultFactory = function(attrs) { attrs.messageFormatter = exceptionFormatter.message; attrs.stackFormatter = exceptionFormatter.stack; return buildExpectationResult(attrs); }; /** * Sets whether Jasmine should throw an Error when an expectation fails. * This causes a spec to only have one expectation failure. * @name Env#throwOnExpectationFailure * @since 2.3.0 * @function * @param {Boolean} value Whether to throw when a expectation fails * @deprecated Use the `oneFailurePerSpec` option with {@link Env#configure} */ this.throwOnExpectationFailure = function(value) { this.deprecated( 'Setting throwOnExpectationFailure directly on Env is deprecated and will be removed in a future version of Jasmine, please use the oneFailurePerSpec option in `configure`' ); this.configure({ oneFailurePerSpec: !!value }); }; this.throwingExpectationFailures = function() { this.deprecated( 'Getting throwingExpectationFailures directly from Env is deprecated and will be removed in a future version of Jasmine, please check the oneFailurePerSpec option from `configuration`' ); return config.oneFailurePerSpec; }; /** * Set whether to stop suite execution when a spec fails * @name Env#stopOnSpecFailure * @since 2.7.0 * @function * @param {Boolean} value Whether to stop suite execution when a spec fails * @deprecated Use the `failFast` option with {@link Env#configure} */ this.stopOnSpecFailure = function(value) { this.deprecated( 'Setting stopOnSpecFailure directly is deprecated and will be removed in a future version of Jasmine, please use the failFast option in `configure`' ); this.configure({ failFast: !!value }); }; this.stoppingOnSpecFailure = function() { this.deprecated( 'Getting stoppingOnSpecFailure directly from Env is deprecated and will be removed in a future version of Jasmine, please check the failFast option from `configuration`' ); return config.failFast; }; /** * Set whether to randomize test execution order * @name Env#randomizeTests * @since 2.4.0 * @function * @param {Boolean} value Whether to randomize execution order * @deprecated Use the `random` option with {@link Env#configure} */ this.randomizeTests = function(value) { this.deprecated( 'Setting randomizeTests directly is deprecated and will be removed in a future version of Jasmine, please use the random option in `configure`' ); config.random = !!value; }; this.randomTests = function() { this.deprecated( 'Getting randomTests directly from Env is deprecated and will be removed in a future version of Jasmine, please check the random option from `configuration`' ); return config.random; }; /** * Set the random number seed for spec randomization * @name Env#seed * @since 2.4.0 * @function * @param {Number} value The seed value * @deprecated Use the `seed` option with {@link Env#configure} */ this.seed = function(value) { this.deprecated( 'Setting seed directly is deprecated and will be removed in a future version of Jasmine, please use the seed option in `configure`' ); if (value) { config.seed = value; } return config.seed; }; this.hidingDisabled = function(value) { this.deprecated( 'Getting hidingDisabled directly from Env is deprecated and will be removed in a future version of Jasmine, please check the hideDisabled option from `configuration`' ); return config.hideDisabled; }; /** * @name Env#hideDisabled * @since 3.2.0 * @function */ this.hideDisabled = function(value) { this.deprecated( 'Setting hideDisabled directly is deprecated and will be removed in a future version of Jasmine, please use the hideDisabled option in `configure`' ); config.hideDisabled = !!value; }; this.deprecated = function(deprecation) { var runnable = currentRunnable() || topSuite; runnable.addDeprecationWarning(deprecation); if ( typeof console !== 'undefined' && typeof console.error === 'function' ) { console.error('DEPRECATION:', deprecation); } }; var queueRunnerFactory = function(options, args) { var failFast = false; if (options.isLeaf) { failFast = config.oneFailurePerSpec; } else if (!options.isReporter) { failFast = config.failFast; } options.clearStack = options.clearStack || clearStack; options.timeout = { setTimeout: realSetTimeout, clearTimeout: realClearTimeout }; options.fail = self.fail; options.globalErrors = globalErrors; options.completeOnFirstError = failFast; options.onException = options.onException || function(e) { (currentRunnable() || topSuite).onException(e); }; options.deprecated = self.deprecated; new j$.QueueRunner(options).execute(args); }; var topSuite = new j$.Suite({ env: this, id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', expectationFactory: expectationFactory, asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory }); defaultResourcesForRunnable(topSuite.id); currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; }; /** * This represents the available reporter callback for an object passed to {@link Env#addReporter}. * @interface Reporter * @see custom_reporter */ var reporter = new j$.ReportDispatcher( [ /** * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. * @function * @name Reporter#jasmineStarted * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'jasmineStarted', /** * When the entire suite has finished execution `jasmineDone` is called * @function * @name Reporter#jasmineDone * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'jasmineDone', /** * `suiteStarted` is invoked when a `describe` starts to run * @function * @name Reporter#suiteStarted * @param {SuiteResult} result Information about the individual {@link describe} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'suiteStarted', /** * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run * * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. * @function * @name Reporter#suiteDone * @param {SuiteResult} result * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'suiteDone', /** * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) * @function * @name Reporter#specStarted * @param {SpecResult} result Information about the individual {@link it} being run * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'specStarted', /** * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. * * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. * @function * @name Reporter#specDone * @param {SpecResult} result * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ 'specDone' ], queueRunnerFactory ); this.execute = function(runnablesToRun) { installGlobalErrors(); if (!runnablesToRun) { if (focusedRunnables.length) { runnablesToRun = focusedRunnables; } else { runnablesToRun = [topSuite.id]; } } var order = new j$.Order({ random: config.random, seed: config.seed }); var processor = new j$.TreeProcessor({ tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, failSpecWithNoExpectations: config.failSpecWithNoExpectations, nodeStart: function(suite, next) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); reporter.suiteStarted(suite.result, next); suite.startTimer(); }, nodeComplete: function(suite, result, next) { if (suite !== currentSuite()) { throw new Error('Tried to complete the wrong suite'); } clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); if (result.status === 'failed') { hasFailures = true; } suite.endTimer(); reporter.suiteDone(result, next); }, orderChildren: function(node) { return order.sort(node.children); }, excludeNode: function(spec) { return !config.specFilter(spec); } }); if (!processor.processTree().valid) { throw new Error( 'Invalid order: would cause a beforeAll or afterAll to be run multiple times' ); } var jasmineTimer = new j$.Timer(); jasmineTimer.start(); /** * Information passed to the {@link Reporter#jasmineStarted} event. * @typedef JasmineStartedInfo * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. */ reporter.jasmineStarted( { totalSpecsDefined: totalSpecsDefined, order: order }, function() { currentlyExecutingSuites.push(topSuite); processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); var overallStatus, incompleteReason; if (hasFailures || topSuite.result.failedExpectations.length > 0) { overallStatus = 'failed'; } else if (focusedRunnables.length > 0) { overallStatus = 'incomplete'; incompleteReason = 'fit() or fdescribe() was found'; } else if (totalSpecsDefined === 0) { overallStatus = 'incomplete'; incompleteReason = 'No specs found'; } else { overallStatus = 'passed'; } /** * Information passed to the {@link Reporter#jasmineDone} event. * @typedef JasmineDoneInfo * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. * @property {Int} totalTime - The total time (in ms) that it took to execute the suite * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. */ reporter.jasmineDone( { overallStatus: overallStatus, totalTime: jasmineTimer.elapsed(), incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations, deprecationWarnings: topSuite.result.deprecationWarnings }, function() {} ); }); } ); }; /** * Add a custom reporter to the Jasmine environment. * @name Env#addReporter * @since 2.0.0 * @function * @param {Reporter} reporterToAdd The reporter to be added. * @see custom_reporter */ this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; /** * Provide a fallback reporter if no other reporters have been specified. * @name Env#provideFallbackReporter * @since 2.5.0 * @function * @param {Reporter} reporterToAdd The reporter * @see custom_reporter */ this.provideFallbackReporter = function(reporterToAdd) { reporter.provideFallbackReporter(reporterToAdd); }; /** * Clear all registered reporters * @name Env#clearReporters * @since 2.5.2 * @function */ this.clearReporters = function() { reporter.clearReporters(); }; var spyFactory = new j$.SpyFactory( function getCustomStrategies() { var runnable = currentRunnable(); if (runnable) { return runnableResources[runnable.id].customSpyStrategies; } return {}; }, function getDefaultStrategyFn() { var runnable = currentRunnable(); if (runnable) { return runnableResources[runnable.id].defaultStrategyFn; } return undefined; }, function getPromise() { return customPromise || global.Promise; } ); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { if (!currentRunnable()) { throw new Error( 'Spies must be created in a before function or a spec' ); } return runnableResources[currentRunnable().id].spies; }, createSpy: function(name, originalFn) { return self.createSpy(name, originalFn); } }); this.allowRespy = function(allow) { spyRegistry.allowRespy(allow); }; this.spyOn = function() { return spyRegistry.spyOn.apply(spyRegistry, arguments); }; this.spyOnProperty = function() { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; this.spyOnAllFunctions = function() { return spyRegistry.spyOnAllFunctions.apply(spyRegistry, arguments); }; this.createSpy = function(name, originalFn) { if (arguments.length === 1 && j$.isFunction_(name)) { originalFn = name; name = originalFn.name; } return spyFactory.createSpy(name, originalFn); }; this.createSpyObj = function(baseName, methodNames, propertyNames) { return spyFactory.createSpyObj(baseName, methodNames, propertyNames); }; var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error( caller + ' expects a function argument; received ' + j$.getType_(fn) ); } }; var ensureIsFunctionOrAsync = function(fn, caller) { if (!j$.isFunction_(fn) && !j$.isAsyncFunction_(fn)) { throw new Error( caller + ' expects a function argument; received ' + j$.getType_(fn) ); } }; function ensureIsNotNested(method) { var runnable = currentRunnable(); if (runnable !== null && runnable !== undefined) { throw new Error( "'" + method + "' should only be used in 'describe' function" ); } } var suiteFactory = function(description) { var suite = new j$.Suite({ env: self, id: getNextSuiteId(), description: description, parentSuite: currentDeclarationSuite, timer: new j$.Timer(), expectationFactory: expectationFactory, asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory, throwOnExpectationFailure: config.oneFailurePerSpec }); return suite; }; this.describe = function(description, specDefinitions) { ensureIsNotNested('describe'); ensureIsFunction(specDefinitions, 'describe'); var suite = suiteFactory(description); if (specDefinitions.length > 0) { throw new Error('describe does not expect any arguments'); } if (currentDeclarationSuite.markedPending) { suite.pend(); } addSpecsToSuite(suite, specDefinitions); return suite; }; this.xdescribe = function(description, specDefinitions) { ensureIsNotNested('xdescribe'); ensureIsFunction(specDefinitions, 'xdescribe'); var suite = suiteFactory(description); suite.pend(); addSpecsToSuite(suite, specDefinitions); return suite; }; var focusedRunnables = []; this.fdescribe = function(description, specDefinitions) { ensureIsNotNested('fdescribe'); ensureIsFunction(specDefinitions, 'fdescribe'); var suite = suiteFactory(description); suite.isFocused = true; focusedRunnables.push(suite.id); unfocusAncestor(); addSpecsToSuite(suite, specDefinitions); return suite; }; function addSpecsToSuite(suite, specDefinitions) { var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); currentDeclarationSuite = suite; var declarationError = null; try { specDefinitions.call(suite); } catch (e) { declarationError = e; } if (declarationError) { suite.onException(declarationError); } currentDeclarationSuite = parentSuite; } function findFocusedAncestor(suite) { while (suite) { if (suite.isFocused) { return suite.id; } suite = suite.parentSuite; } return null; } function unfocusAncestor() { var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); if (focusedAncestor) { for (var i = 0; i < focusedRunnables.length; i++) { if (focusedRunnables[i] === focusedAncestor) { focusedRunnables.splice(i, 1); break; } } } } var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; var spec = new j$.Spec({ id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, asyncExpectationFactory: specAsyncExpectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); }, onStart: specStarted, description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, userContext: function() { return suite.clonedSharedUserContext(); }, queueableFn: { fn: fn, timeout: timeout || 0 }, throwOnExpectationFailure: config.oneFailurePerSpec, timer: new j$.Timer() }); return spec; function specResultCallback(result, next) { clearResourcesForRunnable(spec.id); currentSpec = null; if (result.status === 'failed') { hasFailures = true; } reporter.specDone(result, next); } function specStarted(spec, next) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); reporter.specStarted(spec.result, next); } }; this.it = function(description, fn, timeout) { ensureIsNotNested('it'); // it() sometimes doesn't have a fn argument, so only check the type if // it's given. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'it'); } var spec = specFactory(description, fn, currentDeclarationSuite, timeout); if (currentDeclarationSuite.markedPending) { spec.pend(); } currentDeclarationSuite.addChild(spec); return spec; }; this.xit = function(description, fn, timeout) { ensureIsNotNested('xit'); // xit(), like it(), doesn't always have a fn argument, so only check the // type when needed. if (arguments.length > 1 && typeof fn !== 'undefined') { ensureIsFunctionOrAsync(fn, 'xit'); } var spec = this.it.apply(this, arguments); spec.pend('Temporarily disabled with xit'); return spec; }; this.fit = function(description, fn, timeout) { ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); var spec = specFactory(description, fn, currentDeclarationSuite, timeout); currentDeclarationSuite.addChild(spec); focusedRunnables.push(spec.id); unfocusAncestor(); return spec; }; /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult} * @name Env#setSpecProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ this.setSpecProperty = function(key, value) { if (!currentRunnable() || currentRunnable() == currentSuite()) { throw new Error( "'setSpecProperty' was used when there was no current spec" ); } currentRunnable().setSpecProperty(key, value); }; /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SuiteResult} * @name Env#setSuiteProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ this.setSuiteProperty = function(key, value) { if (!currentSuite()) { throw new Error( "'setSuiteProperty' was used when there was no current suite" ); } currentSuite().setSuiteProperty(key, value); }; this.expect = function(actual) { if (!currentRunnable()) { throw new Error( "'expect' was used when there was no current spec, this could be because an asynchronous test timed out" ); } return currentRunnable().expect(actual); }; this.expectAsync = function(actual) { if (!currentRunnable()) { throw new Error( "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" ); } return currentRunnable().expectAsync(actual); }; this.beforeEach = function(beforeEachFunction, timeout) { ensureIsNotNested('beforeEach'); ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach'); currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: timeout || 0 }); }; this.beforeAll = function(beforeAllFunction, timeout) { ensureIsNotNested('beforeAll'); ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll'); currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: timeout || 0 }); }; this.afterEach = function(afterEachFunction, timeout) { ensureIsNotNested('afterEach'); ensureIsFunctionOrAsync(afterEachFunction, 'afterEach'); afterEachFunction.isCleanup = true; currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: timeout || 0 }); }; this.afterAll = function(afterAllFunction, timeout) { ensureIsNotNested('afterAll'); ensureIsFunctionOrAsync(afterAllFunction, 'afterAll'); currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: timeout || 0 }); }; this.pending = function(message) { var fullMessage = j$.Spec.pendingSpecExceptionMessage; if (message) { fullMessage += message; } throw fullMessage; }; this.fail = function(error) { if (!currentRunnable()) { throw new Error( "'fail' was used when there was no current spec, this could be because an asynchronous test timed out" ); } var message = 'Failed'; if (error) { message += ': '; if (error.message) { message += error.message; } else if (j$.isString_(error)) { message += error; } else { // pretty print all kind of objects. This includes arrays. message += makePrettyPrinter()(error); } } currentRunnable().addExpectationResult(false, { matcherName: '', passed: false, expected: '', actual: '', message: message, error: error && error.message ? error : null }); if (config.oneFailurePerSpec) { throw new Error(message); } }; this.cleanup_ = function() { if (globalErrors) { globalErrors.uninstall(); } }; } return Env; }; getJasmineRequireObj().JsApiReporter = function(j$) { /** * @name jsApiReporter * @classdesc {@link Reporter} added by default in `boot.js` to record results for retrieval in javascript code. An instance is made available as `jsApiReporter` on the global object. * @class * @hideconstructor */ function JsApiReporter(options) { var timer = options.timer || new j$.Timer(), status = 'loaded'; this.started = false; this.finished = false; this.runDetails = {}; this.jasmineStarted = function() { this.started = true; status = 'started'; timer.start(); }; var executionTime; this.jasmineDone = function(runDetails) { this.finished = true; this.runDetails = runDetails; executionTime = timer.elapsed(); status = 'done'; }; /** * Get the current status for the Jasmine environment. * @name jsApiReporter#status * @since 2.0.0 * @function * @return {String} - One of `loaded`, `started`, or `done` */ this.status = function() { return status; }; var suites = [], suites_hash = {}; this.suiteStarted = function(result) { suites_hash[result.id] = result; }; this.suiteDone = function(result) { storeSuite(result); }; /** * Get the results for a set of suites. * * Retrievable in slices for easier serialization. * @name jsApiReporter#suiteResults * @since 2.1.0 * @function * @param {Number} index - The position in the suites list to start from. * @param {Number} length - Maximum number of suite results to return. * @return {SuiteResult[]} */ this.suiteResults = function(index, length) { return suites.slice(index, index + length); }; function storeSuite(result) { suites.push(result); suites_hash[result.id] = result; } /** * Get all of the suites in a single object, with their `id` as the key. * @name jsApiReporter#suites * @since 2.0.0 * @function * @return {Object} - Map of suite id to {@link SuiteResult} */ this.suites = function() { return suites_hash; }; var specs = []; this.specDone = function(result) { specs.push(result); }; /** * Get the results for a set of specs. * * Retrievable in slices for easier serialization. * @name jsApiReporter#specResults * @since 2.0.0 * @function * @param {Number} index - The position in the specs list to start from. * @param {Number} length - Maximum number of specs results to return. * @return {SpecResult[]} */ this.specResults = function(index, length) { return specs.slice(index, index + length); }; /** * Get all spec results. * @name jsApiReporter#specs * @since 2.0.0 * @function * @return {SpecResult[]} */ this.specs = function() { return specs; }; /** * Get the number of milliseconds it took for the full Jasmine suite to run. * @name jsApiReporter#executionTime * @since 2.0.0 * @function * @return {Number} */ this.executionTime = function() { return executionTime; }; } return JsApiReporter; }; getJasmineRequireObj().Any = function(j$) { function Any(expectedObject) { if (typeof expectedObject === 'undefined') { throw new TypeError( 'jasmine.any() expects to be passed a constructor function. ' + 'Please pass one or use jasmine.anything() to match any object.' ); } this.expectedObject = expectedObject; } Any.prototype.asymmetricMatch = function(other) { if (this.expectedObject == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedObject == Number) { return typeof other == 'number' || other instanceof Number; } if (this.expectedObject == Function) { return typeof other == 'function' || other instanceof Function; } if (this.expectedObject == Object) { return other !== null && typeof other == 'object'; } if (this.expectedObject == Boolean) { return typeof other == 'boolean'; } /* jshint -W122 */ /* global Symbol */ if (typeof Symbol != 'undefined' && this.expectedObject == Symbol) { return typeof other == 'symbol'; } /* jshint +W122 */ return other instanceof this.expectedObject; }; Any.prototype.jasmineToString = function() { return ''; }; return Any; }; getJasmineRequireObj().Anything = function(j$) { function Anything() {} Anything.prototype.asymmetricMatch = function(other) { return !j$.util.isUndefined(other) && other !== null; }; Anything.prototype.jasmineToString = function() { return ''; }; return Anything; }; getJasmineRequireObj().ArrayContaining = function(j$) { function ArrayContaining(sample) { this.sample = sample; } ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } // If the actual parameter is not an array, we can fail immediately, since it couldn't // possibly be an "array containing" anything. However, we also want an empty sample // array to match anything, so we need to double-check we aren't in that case if (!j$.isArray_(other) && this.sample.length > 0) { return false; } for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!matchersUtil.contains(other, item)) { return false; } } return true; }; ArrayContaining.prototype.jasmineToString = function (pp) { return ''; }; return ArrayContaining; }; getJasmineRequireObj().ArrayWithExactContents = function(j$) { function ArrayWithExactContents(sample) { this.sample = sample; } ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } if (this.sample.length !== other.length) { return false; } for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!matchersUtil.contains(other, item)) { return false; } } return true; }; ArrayWithExactContents.prototype.jasmineToString = function(pp) { return ''; }; return ArrayWithExactContents; }; getJasmineRequireObj().Empty = function (j$) { function Empty() {} Empty.prototype.asymmetricMatch = function (other) { if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) { return other.length === 0; } if (j$.isMap(other) || j$.isSet(other)) { return other.size === 0; } if (j$.isObject_(other)) { return Object.keys(other).length === 0; } return false; }; Empty.prototype.jasmineToString = function () { return ''; }; return Empty; }; getJasmineRequireObj().Falsy = function(j$) { function Falsy() {} Falsy.prototype.asymmetricMatch = function(other) { return !other; }; Falsy.prototype.jasmineToString = function() { return ''; }; return Falsy; }; getJasmineRequireObj().MapContaining = function(j$) { function MapContaining(sample) { if (!j$.isMap(sample)) { throw new Error('You must provide a map to `mapContaining`, not ' + j$.pp(sample)); } this.sample = sample; } MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, value, key) { // for each key/value pair in `sample` // there should be at least one pair in `other` whose key and value both match var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( matchersUtil.equals(oKey, key) && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); } }); if (!hasMatch) { hasAllMatches = false; breakLoop(); } }); return hasAllMatches; }; MapContaining.prototype.jasmineToString = function(pp) { return ''; }; return MapContaining; }; getJasmineRequireObj().NotEmpty = function (j$) { function NotEmpty() {} NotEmpty.prototype.asymmetricMatch = function (other) { if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) { return other.length !== 0; } if (j$.isMap(other) || j$.isSet(other)) { return other.size !== 0; } if (j$.isObject_(other)) { return Object.keys(other).length !== 0; } return false; }; NotEmpty.prototype.jasmineToString = function () { return ''; }; return NotEmpty; }; getJasmineRequireObj().ObjectContaining = function(j$) { function ObjectContaining(sample) { this.sample = sample; } function getPrototype(obj) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(obj); } if (obj.constructor.prototype == obj) { return null; } return obj.constructor.prototype; } function hasProperty(obj, property) { if (!obj || typeof(obj) !== 'object') { return false; } if (Object.prototype.hasOwnProperty.call(obj, property)) { return true; } return hasProperty(getPrototype(obj), property); } ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } if (typeof(other) !== 'object') { return false; } for (var property in this.sample) { if (!hasProperty(other, property) || !matchersUtil.equals(this.sample[property], other[property])) { return false; } } return true; }; ObjectContaining.prototype.valuesForDiff_ = function(other, pp) { if (!j$.isObject_(other)) { return { self: this.jasmineToString(pp), other: other }; } var filteredOther = {}; Object.keys(this.sample).forEach(function (k) { // eq short-circuits comparison of objects that have different key sets, // so include all keys even if undefined. filteredOther[k] = other[k]; }); return { self: this.sample, other: filteredOther }; }; ObjectContaining.prototype.jasmineToString = function(pp) { return ''; }; return ObjectContaining; }; getJasmineRequireObj().SetContaining = function(j$) { function SetContaining(sample) { if (!j$.isSet(sample)) { throw new Error('You must provide a set to `setContaining`, not ' + j$.pp(sample)); } this.sample = sample; } SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } }); if (!hasMatch) { hasAllMatches = false; breakLoop(); } }); return hasAllMatches; }; SetContaining.prototype.jasmineToString = function(pp) { return ''; }; return SetContaining; }; getJasmineRequireObj().StringMatching = function(j$) { function StringMatching(expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error('Expected is not a String or a RegExp'); } this.regexp = new RegExp(expected); } StringMatching.prototype.asymmetricMatch = function(other) { return this.regexp.test(other); }; StringMatching.prototype.jasmineToString = function() { return ''; }; return StringMatching; }; getJasmineRequireObj().Truthy = function(j$) { function Truthy() {} Truthy.prototype.asymmetricMatch = function(other) { return !!other; }; Truthy.prototype.jasmineToString = function() { return ''; }; return Truthy; }; getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { /* Older versions of Jasmine passed an array of custom equality testers as the second argument to each asymmetric equality tester's `asymmetricMatch` method. Newer versions will pass a `MatchersUtil` instance. The asymmetricEqualityTesterArgCompatShim allows for a graceful migration from the old interface to the new by "being" both an array of custom equality testers and a `MatchersUtil` at the same time. This code should be removed in the next major release. */ var likelyArrayProps = [ 'concat', 'constructor', 'copyWithin', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toSource', 'toString', 'unshift', 'values' ]; function asymmetricEqualityTesterArgCompatShim( matchersUtil, customEqualityTesters ) { var self = Object.create(matchersUtil), props, i, k; copy(self, customEqualityTesters, 'length'); for (i = 0; i < customEqualityTesters.length; i++) { copy(self, customEqualityTesters, i); } var props = arrayProps(); for (i = 0; i < props.length; i++) { k = props[i]; if (k !== 'length') { copy(self, Array.prototype, k); } } return self; } function copy(dest, src, propName) { Object.defineProperty(dest, propName, { get: function() { return src[propName]; } }); } function arrayProps() { var props, a, k; if (!Object.getOwnPropertyDescriptors) { return likelyArrayProps.filter(function(k) { return Array.prototype.hasOwnProperty(k); }); } props = Object.getOwnPropertyDescriptors(Array.prototype); // eslint-disable-line compat/compat a = []; for (k in props) { a.push(k); } return a; } return asymmetricEqualityTesterArgCompatShim; }; getJasmineRequireObj().CallTracker = function(j$) { /** * @namespace Spy#calls * @since 2.0.0 */ function CallTracker() { var calls = []; var opts = {}; this.track = function(context) { if (opts.cloneArgs) { context.args = j$.util.cloneArgs(context.args); } calls.push(context); }; /** * Check whether this spy has been invoked. * @name Spy#calls#any * @since 2.0.0 * @function * @return {Boolean} */ this.any = function() { return !!calls.length; }; /** * Get the number of invocations of this spy. * @name Spy#calls#count * @since 2.0.0 * @function * @return {Integer} */ this.count = function() { return calls.length; }; /** * Get the arguments that were passed to a specific invocation of this spy. * @name Spy#calls#argsFor * @since 2.0.0 * @function * @param {Integer} index The 0-based invocation index. * @return {Array} */ this.argsFor = function(index) { var call = calls[index]; return call ? call.args : []; }; /** * Get the raw calls array for this spy. * @name Spy#calls#all * @since 2.0.0 * @function * @return {Spy.callData[]} */ this.all = function() { return calls; }; /** * Get all of the arguments for each invocation of this spy in the order they were received. * @name Spy#calls#allArgs * @since 2.0.0 * @function * @return {Array} */ this.allArgs = function() { var callArgs = []; for (var i = 0; i < calls.length; i++) { callArgs.push(calls[i].args); } return callArgs; }; /** * Get the first invocation of this spy. * @name Spy#calls#first * @since 2.0.0 * @function * @return {ObjecSpy.callData} */ this.first = function() { return calls[0]; }; /** * Get the most recent invocation of this spy. * @name Spy#calls#mostRecent * @since 2.0.0 * @function * @return {ObjecSpy.callData} */ this.mostRecent = function() { return calls[calls.length - 1]; }; /** * Reset this spy as if it has never been called. * @name Spy#calls#reset * @since 2.0.0 * @function */ this.reset = function() { calls = []; }; /** * Set this spy to do a shallow clone of arguments passed to each invocation. * @name Spy#calls#saveArgumentsByValue * @since 2.5.0 * @function */ this.saveArgumentsByValue = function() { opts.cloneArgs = true; }; } return CallTracker; }; getJasmineRequireObj().clearStack = function(j$) { var maxInlineCallCount = 10; function messageChannelImpl(global, setTimeout) { var channel = new global.MessageChannel(), head = {}, tail = head; var taskRunning = false; channel.port1.onmessage = function() { head = head.next; var task = head.task; delete head.task; if (taskRunning) { global.setTimeout(task, 0); } else { try { taskRunning = true; task(); } finally { taskRunning = false; } } }; var currentCallCount = 0; return function clearStack(fn) { currentCallCount++; if (currentCallCount < maxInlineCallCount) { tail = tail.next = { task: fn }; channel.port2.postMessage(0); } else { currentCallCount = 0; setTimeout(fn); } }; } function getClearStack(global) { var currentCallCount = 0; var realSetTimeout = global.setTimeout; var setTimeoutImpl = function clearStack(fn) { Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); }; if (j$.isFunction_(global.setImmediate)) { var realSetImmediate = global.setImmediate; return function(fn) { currentCallCount++; if (currentCallCount < maxInlineCallCount) { realSetImmediate(fn); } else { currentCallCount = 0; setTimeoutImpl(fn); } }; } else if (!j$.util.isUndefined(global.MessageChannel)) { return messageChannelImpl(global, setTimeoutImpl); } else { return setTimeoutImpl; } } return getClearStack; }; getJasmineRequireObj().Clock = function() { /* global process */ var NODE_JS = typeof process !== 'undefined' && process.versions && typeof process.versions.node === 'string'; /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}. * @class Clock * @classdesc Jasmine's mock clock is used when testing time dependent code. */ function Clock(global, delayedFunctionSchedulerFactory, mockDate) { var self = this, realTimingFunctions = { setTimeout: global.setTimeout, clearTimeout: global.clearTimeout, setInterval: global.setInterval, clearInterval: global.clearInterval }, fakeTimingFunctions = { setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval }, installed = false, delayedFunctionScheduler, timer; self.FakeTimeout = FakeTimeout; /** * Install the mock clock over the built-in methods. * @name Clock#install * @since 2.0.0 * @function * @return {Clock} */ self.install = function() { if (!originalTimingFunctionsIntact()) { throw new Error( 'Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?' ); } replace(global, fakeTimingFunctions); timer = fakeTimingFunctions; delayedFunctionScheduler = delayedFunctionSchedulerFactory(); installed = true; return self; }; /** * Uninstall the mock clock, returning the built-in methods to their places. * @name Clock#uninstall * @since 2.0.0 * @function */ self.uninstall = function() { delayedFunctionScheduler = null; mockDate.uninstall(); replace(global, realTimingFunctions); timer = realTimingFunctions; installed = false; }; /** * Execute a function with a mocked Clock * * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes. * @name Clock#withMock * @since 2.3.0 * @function * @param {Function} closure The function to be called. */ self.withMock = function(closure) { this.install(); try { closure(); } finally { this.uninstall(); } }; /** * Instruct the installed Clock to also mock the date returned by `new Date()` * @name Clock#mockDate * @since 2.1.0 * @function * @param {Date} [initialDate=now] The `Date` to provide. */ self.mockDate = function(initialDate) { mockDate.install(initialDate); }; self.setTimeout = function(fn, delay, params) { return Function.prototype.apply.apply(timer.setTimeout, [ global, arguments ]); }; self.setInterval = function(fn, delay, params) { return Function.prototype.apply.apply(timer.setInterval, [ global, arguments ]); }; self.clearTimeout = function(id) { return Function.prototype.call.apply(timer.clearTimeout, [global, id]); }; self.clearInterval = function(id) { return Function.prototype.call.apply(timer.clearInterval, [global, id]); }; /** * Tick the Clock forward, running any enqueued timeouts along the way * @name Clock#tick * @since 1.3.0 * @function * @param {int} millis The number of milliseconds to tick. */ self.tick = function(millis) { if (installed) { delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); }); } else { throw new Error( 'Mock clock is not installed, use jasmine.clock().install()' ); } }; return self; function originalTimingFunctionsIntact() { return ( global.setTimeout === realTimingFunctions.setTimeout && global.clearTimeout === realTimingFunctions.clearTimeout && global.setInterval === realTimingFunctions.setInterval && global.clearInterval === realTimingFunctions.clearInterval ); } function replace(dest, source) { for (var prop in source) { dest[prop] = source[prop]; } } function setTimeout(fn, delay) { if (!NODE_JS) { return delayedFunctionScheduler.scheduleFunction( fn, delay, argSlice(arguments, 2) ); } var timeout = new FakeTimeout(); delayedFunctionScheduler.scheduleFunction( fn, delay, argSlice(arguments, 2), false, timeout ); return timeout; } function clearTimeout(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function setInterval(fn, interval) { if (!NODE_JS) { return delayedFunctionScheduler.scheduleFunction( fn, interval, argSlice(arguments, 2), true ); } var timeout = new FakeTimeout(); delayedFunctionScheduler.scheduleFunction( fn, interval, argSlice(arguments, 2), true, timeout ); return timeout; } function clearInterval(id) { return delayedFunctionScheduler.removeFunctionWithId(id); } function argSlice(argsObj, n) { return Array.prototype.slice.call(argsObj, n); } } /** * Mocks Node.js Timeout class */ function FakeTimeout() {} FakeTimeout.prototype.ref = function() { return this; }; FakeTimeout.prototype.unref = function() { return this; }; return Clock; }; getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { function DelayedFunctionScheduler() { var self = this; var scheduledLookup = []; var scheduledFunctions = {}; var currentTime = 0; var delayedFnCount = 0; var deletedKeys = []; self.tick = function(millis, tickDate) { millis = millis || 0; var endTime = currentTime + millis; runScheduledFunctions(endTime, tickDate); currentTime = endTime; }; self.scheduleFunction = function( funcToCall, millis, params, recurring, timeoutKey, runAtMillis ) { var f; if (typeof funcToCall === 'string') { /* jshint evil: true */ f = function() { return eval(funcToCall); }; /* jshint evil: false */ } else { f = funcToCall; } millis = millis || 0; timeoutKey = timeoutKey || ++delayedFnCount; runAtMillis = runAtMillis || currentTime + millis; var funcToSchedule = { runAtMillis: runAtMillis, funcToCall: f, recurring: recurring, params: params, timeoutKey: timeoutKey, millis: millis }; if (runAtMillis in scheduledFunctions) { scheduledFunctions[runAtMillis].push(funcToSchedule); } else { scheduledFunctions[runAtMillis] = [funcToSchedule]; scheduledLookup.push(runAtMillis); scheduledLookup.sort(function(a, b) { return a - b; }); } return timeoutKey; }; self.removeFunctionWithId = function(timeoutKey) { deletedKeys.push(timeoutKey); for (var runAtMillis in scheduledFunctions) { var funcs = scheduledFunctions[runAtMillis]; var i = indexOfFirstToPass(funcs, function(func) { return func.timeoutKey === timeoutKey; }); if (i > -1) { if (funcs.length === 1) { delete scheduledFunctions[runAtMillis]; deleteFromLookup(runAtMillis); } else { funcs.splice(i, 1); } // intervals get rescheduled when executed, so there's never more // than a single scheduled function with a given timeoutKey break; } } }; return self; function indexOfFirstToPass(array, testFn) { var index = -1; for (var i = 0; i < array.length; ++i) { if (testFn(array[i])) { index = i; break; } } return index; } function deleteFromLookup(key) { var value = Number(key); var i = indexOfFirstToPass(scheduledLookup, function(millis) { return millis === value; }); if (i > -1) { scheduledLookup.splice(i, 1); } } function reschedule(scheduledFn) { self.scheduleFunction( scheduledFn.funcToCall, scheduledFn.millis, scheduledFn.params, true, scheduledFn.timeoutKey, scheduledFn.runAtMillis + scheduledFn.millis ); } function forEachFunction(funcsToRun, callback) { for (var i = 0; i < funcsToRun.length; ++i) { callback(funcsToRun[i]); } } function runScheduledFunctions(endTime, tickDate) { tickDate = tickDate || function() {}; if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { tickDate(endTime - currentTime); return; } do { deletedKeys = []; var newCurrentTime = scheduledLookup.shift(); tickDate(newCurrentTime - currentTime); currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; delete scheduledFunctions[currentTime]; forEachFunction(funcsToRun, function(funcToRun) { if (funcToRun.recurring) { reschedule(funcToRun); } }); forEachFunction(funcsToRun, function(funcToRun) { if (j$.util.arrayContains(deletedKeys, funcToRun.timeoutKey)) { // skip a timeoutKey deleted whilst we were running return; } funcToRun.funcToCall.apply(null, funcToRun.params || []); }); deletedKeys = []; } while ( scheduledLookup.length > 0 && // checking first if we're out of time prevents setTimeout(0) // scheduled in a funcToRun from forcing an extra iteration currentTime !== endTime && scheduledLookup[0] <= endTime ); // ran out of functions to call, but still time left on the clock if (currentTime !== endTime) { tickDate(endTime - currentTime); } } } return DelayedFunctionScheduler; }; getJasmineRequireObj().errors = function() { function ExpectationFailed() {} ExpectationFailed.prototype = new Error(); ExpectationFailed.prototype.constructor = ExpectationFailed; return { ExpectationFailed: ExpectationFailed }; }; getJasmineRequireObj().ExceptionFormatter = function(j$) { var ignoredProperties = [ 'name', 'message', 'stack', 'fileName', 'sourceURL', 'line', 'lineNumber', 'column', 'description', 'jasmineMessage' ]; function ExceptionFormatter(options) { var jasmineFile = (options && options.jasmineFile) || j$.util.jasmineFile(); this.message = function(error) { var message = ''; if (error.jasmineMessage) { message += error.jasmineMessage; } else if (error.name && error.message) { message += error.name + ': ' + error.message; } else if (error.message) { message += error.message; } else { message += error.toString() + ' thrown'; } if (error.fileName || error.sourceURL) { message += ' in ' + (error.fileName || error.sourceURL); } if (error.line || error.lineNumber) { message += ' (line ' + (error.line || error.lineNumber) + ')'; } return message; }; this.stack = function(error) { if (!error || !error.stack) { return null; } var stackTrace = new j$.StackTrace(error); var lines = filterJasmine(stackTrace); var result = ''; if (stackTrace.message) { lines.unshift(stackTrace.message); } result += formatProperties(error); result += lines.join('\n'); return result; }; function filterJasmine(stackTrace) { var result = [], jasmineMarker = stackTrace.style === 'webkit' ? '' : ' at '; stackTrace.frames.forEach(function(frame) { if (frame.file && frame.file !== jasmineFile) { result.push(frame.raw); } else if (result[result.length - 1] !== jasmineMarker) { result.push(jasmineMarker); } }); return result; } function formatProperties(error) { if (!(error instanceof Object)) { return; } var result = {}; var empty = true; for (var prop in error) { if (j$.util.arrayContains(ignoredProperties, prop)) { continue; } result[prop] = error[prop]; empty = false; } if (!empty) { return 'error properties: ' + j$.pp(result) + '\n'; } return ''; } } return ExceptionFormatter; }; getJasmineRequireObj().Expectation = function(j$) { /** * Matchers that come with Jasmine out of the box. * @namespace matchers */ function Expectation(options) { this.expector = new j$.Expector(options); var customMatchers = options.customMatchers || {}; for (var matcherName in customMatchers) { this[matcherName] = wrapSyncCompare( matcherName, customMatchers[matcherName] ); } } /** * Add some context for an {@link expect} * @function * @name matchers#withContext * @since 3.3.0 * @param {String} message - Additional context to show when the matcher fails * @return {matchers} */ Expectation.prototype.withContext = function withContext(message) { return addFilter(this, new ContextAddingFilter(message)); }; /** * Invert the matcher following this {@link expect} * @member * @name matchers#not * @since 1.3.0 * @type {matchers} * @example * expect(something).not.toBe(true); */ Object.defineProperty(Expectation.prototype, 'not', { get: function() { return addFilter(this, syncNegatingFilter); } }); /** * Asynchronous matchers. * @namespace async-matchers */ function AsyncExpectation(options) { var global = options.global || j$.getGlobal(); this.expector = new j$.Expector(options); if (!global.Promise) { throw new Error( 'expectAsync is unavailable because the environment does not support promises.' ); } var customAsyncMatchers = options.customAsyncMatchers || {}; for (var matcherName in customAsyncMatchers) { this[matcherName] = wrapAsyncCompare( matcherName, customAsyncMatchers[matcherName] ); } } /** * Add some context for an {@link expectAsync} * @function * @name async-matchers#withContext * @since 3.3.0 * @param {String} message - Additional context to show when the async matcher fails * @return {async-matchers} */ AsyncExpectation.prototype.withContext = function withContext(message) { return addFilter(this, new ContextAddingFilter(message)); }; /** * Invert the matcher following this {@link expectAsync} * @member * @name async-matchers#not * @type {async-matchers} * @example * await expectAsync(myPromise).not.toBeResolved(); * @example * return expectAsync(myPromise).not.toBeResolved(); */ Object.defineProperty(AsyncExpectation.prototype, 'not', { get: function() { return addFilter(this, asyncNegatingFilter); } }); function wrapSyncCompare(name, matcherFactory) { return function() { var result = this.expector.compare(name, matcherFactory, arguments); this.expector.processResult(result); }; } function wrapAsyncCompare(name, matcherFactory) { return function() { var self = this; // Capture the call stack here, before we go async, so that it will contain // frames that are relevant to the user instead of just parts of Jasmine. var errorForStack = j$.util.errorWithStack(); return this.expector .compare(name, matcherFactory, arguments) .then(function(result) { self.expector.processResult(result, errorForStack); }); }; } function addCoreMatchers(prototype, matchers, wrapper) { for (var matcherName in matchers) { var matcher = matchers[matcherName]; prototype[matcherName] = wrapper(matcherName, matcher); } } function addFilter(source, filter) { var result = Object.create(source); result.expector = source.expector.addFilter(filter); return result; } function negatedFailureMessage(result, matcherName, args, matchersUtil) { if (result.message) { if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } args = args.slice(); args.unshift(true); args.unshift(matcherName); return matchersUtil.buildFailureMessage.apply(matchersUtil, args); } function negate(result) { result.pass = !result.pass; return result; } var syncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { return negate(matcher.compare.apply(null, arguments)); } return matcher.negativeCompare || defaultNegativeCompare; }, buildFailureMessage: negatedFailureMessage }; var asyncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { return matcher.compare.apply(this, arguments).then(negate); } return matcher.negativeCompare || defaultNegativeCompare; }, buildFailureMessage: negatedFailureMessage }; function ContextAddingFilter(message) { this.message = message; } ContextAddingFilter.prototype.modifyFailureMessage = function(msg) { var nl = msg.indexOf('\n'); if (nl === -1) { return this.message + ': ' + msg; } else { return this.message + ':\n' + indent(msg); } }; function indent(s) { return s.replace(/^/gm, ' '); } return { factory: function(options) { return new Expectation(options || {}); }, addCoreMatchers: function(matchers) { addCoreMatchers(Expectation.prototype, matchers, wrapSyncCompare); }, asyncFactory: function(options) { return new AsyncExpectation(options || {}); }, addAsyncCoreMatchers: function(matchers) { addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare); } }; }; getJasmineRequireObj().ExpectationFilterChain = function() { function ExpectationFilterChain(maybeFilter, prev) { this.filter_ = maybeFilter; this.prev_ = prev; } ExpectationFilterChain.prototype.addFilter = function(filter) { return new ExpectationFilterChain(filter, this); }; ExpectationFilterChain.prototype.selectComparisonFunc = function(matcher) { return this.callFirst_('selectComparisonFunc', arguments).result; }; ExpectationFilterChain.prototype.buildFailureMessage = function( result, matcherName, args, matchersUtil ) { return this.callFirst_('buildFailureMessage', arguments).result; }; ExpectationFilterChain.prototype.modifyFailureMessage = function(msg) { var result = this.callFirst_('modifyFailureMessage', arguments).result; return result || msg; }; ExpectationFilterChain.prototype.callFirst_ = function(fname, args) { var prevResult; if (this.prev_) { prevResult = this.prev_.callFirst_(fname, args); if (prevResult.found) { return prevResult; } } if (this.filter_ && this.filter_[fname]) { return { found: true, result: this.filter_[fname].apply(this.filter_, args) }; } return { found: false }; }; return ExpectationFilterChain; }; //TODO: expectation result may make more sense as a presentation of an expectation. getJasmineRequireObj().buildExpectationResult = function(j$) { function buildExpectationResult(options) { var messageFormatter = options.messageFormatter || function() {}, stackFormatter = options.stackFormatter || function() {}; /** * @typedef Expectation * @property {String} matcherName - The name of the matcher that was executed for this expectation. * @property {String} message - The failure message for the expectation. * @property {String} stack - The stack trace for the failure if available. * @property {Boolean} passed - Whether the expectation passed or failed. * @property {Object} expected - If the expectation failed, what was the expected value. * @property {Object} actual - If the expectation failed, what actual value was produced. */ var result = { matcherName: options.matcherName, message: message(), stack: stack(), passed: options.passed }; if (!result.passed) { result.expected = options.expected; result.actual = options.actual; if (options.error && !j$.isString_(options.error)) { if ('code' in options.error) { result.code = options.error.code; } if ( options.error.code === 'ERR_ASSERTION' && options.expected === '' && options.actual === '' ) { result.expected = options.error.expected; result.actual = options.error.actual; result.matcherName = 'assert ' + options.error.operator; } } } return result; function message() { if (options.passed) { return 'Passed.'; } else if (options.message) { return options.message; } else if (options.error) { return messageFormatter(options.error); } return ''; } function stack() { if (options.passed) { return ''; } var error = options.error; if (!error) { if (options.errorForStack) { error = options.errorForStack; } else if (options.stack) { error = options; } else { try { throw new Error(message()); } catch (e) { error = e; } } } return stackFormatter(error); } } return buildExpectationResult; }; getJasmineRequireObj().Expector = function(j$) { function Expector(options) { this.matchersUtil = options.matchersUtil || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; this.actual = options.actual; this.addExpectationResult = options.addExpectationResult || function() {}; this.filters = new j$.ExpectationFilterChain(); } Expector.prototype.instantiateMatcher = function( matcherName, matcherFactory, args ) { this.matcherName = matcherName; this.args = Array.prototype.slice.call(args, 0); this.expected = this.args.slice(0); this.args.unshift(this.actual); var matcher = matcherFactory(this.matchersUtil, this.customEqualityTesters); var comparisonFunc = this.filters.selectComparisonFunc(matcher); return comparisonFunc || matcher.compare; }; Expector.prototype.buildMessage = function(result) { var self = this; if (result.pass) { return ''; } var msg = this.filters.buildFailureMessage( result, this.matcherName, this.args, this.matchersUtil, defaultMessage ); return this.filters.modifyFailureMessage(msg || defaultMessage()); function defaultMessage() { if (!result.message) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); return self.matchersUtil.buildFailureMessage.apply( self.matchersUtil, args ); } else if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } }; Expector.prototype.compare = function(matcherName, matcherFactory, args) { var matcherCompare = this.instantiateMatcher( matcherName, matcherFactory, args ); return matcherCompare.apply(null, this.args); }; Expector.prototype.addFilter = function(filter) { var result = Object.create(this); result.filters = this.filters.addFilter(filter); return result; }; Expector.prototype.processResult = function(result, errorForStack) { var message = this.buildMessage(result); if (this.expected.length === 1) { this.expected = this.expected[0]; } this.addExpectationResult(result.pass, { matcherName: this.matcherName, passed: result.pass, message: message, error: errorForStack ? undefined : result.error, errorForStack: errorForStack || undefined, actual: this.actual, expected: this.expected // TODO: this may need to be arrayified/sliced }); }; return Expector; }; getJasmineRequireObj().formatErrorMsg = function() { function generateErrorMsg(domain, usage) { var usageDefinition = usage ? '\nUsage: ' + usage : ''; return function errorMsg(msg) { return domain + ' : ' + msg + usageDefinition; }; } return generateErrorMsg; }; getJasmineRequireObj().GlobalErrors = function(j$) { function GlobalErrors(global) { var handlers = []; global = global || j$.getGlobal(); var onerror = function onerror() { var handler = handlers[handlers.length - 1]; if (handler) { handler.apply(null, Array.prototype.slice.call(arguments, 0)); } else { throw arguments[0]; } }; this.originalHandlers = {}; this.jasmineHandlers = {}; this.installOne_ = function installOne_(errorType, jasmineMessage) { function taggedOnError(error) { error.jasmineMessage = jasmineMessage + ': ' + error; var handler = handlers[handlers.length - 1]; if (handler) { handler(error); } else { throw error; } } this.originalHandlers[errorType] = global.process.listeners(errorType); this.jasmineHandlers[errorType] = taggedOnError; global.process.removeAllListeners(errorType); global.process.on(errorType, taggedOnError); this.uninstall = function uninstall() { var errorTypes = Object.keys(this.originalHandlers); for (var iType = 0; iType < errorTypes.length; iType++) { var errorType = errorTypes[iType]; global.process.removeListener( errorType, this.jasmineHandlers[errorType] ); for (var i = 0; i < this.originalHandlers[errorType].length; i++) { global.process.on(errorType, this.originalHandlers[errorType][i]); } delete this.originalHandlers[errorType]; delete this.jasmineHandlers[errorType]; } }; }; this.install = function install() { if ( global.process && global.process.listeners && j$.isFunction_(global.process.on) ) { this.installOne_('uncaughtException', 'Uncaught exception'); this.installOne_('unhandledRejection', 'Unhandled promise rejection'); } else { var originalHandler = global.onerror; global.onerror = onerror; var browserRejectionHandler = function browserRejectionHandler(event) { if (j$.isError_(event.reason)) { event.reason.jasmineMessage = 'Unhandled promise rejection: ' + event.reason; onerror(event.reason); } else { onerror('Unhandled promise rejection: ' + event.reason); } }; if (global.addEventListener) { global.addEventListener( 'unhandledrejection', browserRejectionHandler ); } this.uninstall = function uninstall() { global.onerror = originalHandler; if (global.removeEventListener) { global.removeEventListener( 'unhandledrejection', browserRejectionHandler ); } }; } }; this.pushListener = function pushListener(listener) { handlers.push(listener); }; this.popListener = function popListener() { handlers.pop(); }; } return GlobalErrors; }; /* eslint-disable compat/compat */ getJasmineRequireObj().toBePending = function(j$) { /** * Expect a promise to be pending, ie. the promise is neither resolved nor rejected. * @function * @async * @name async-matchers#toBePending * @since 3.6 * @example * await expectAsync(aPromise).toBePending(); */ return function toBePending() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBePending to be called on a promise.'); } var want = {}; return Promise.race([actual, Promise.resolve(want)]).then( function(got) { return {pass: want === got}; }, function() { return {pass: false}; } ); } }; }; }; getJasmineRequireObj().toBeRejected = function(j$) { /** * Expect a promise to be rejected. * @function * @async * @name async-matchers#toBeRejected * @since 3.1.0 * @example * await expectAsync(aPromise).toBeRejected(); * @example * return expectAsync(aPromise).toBeRejected(); */ return function toBeRejected() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBeRejected to be called on a promise.'); } return actual.then( function() { return {pass: false}; }, function() { return {pass: true}; } ); } }; }; }; getJasmineRequireObj().toBeRejectedWith = function(j$) { /** * Expect a promise to be rejected with a value equal to the expected, using deep equality comparison. * @function * @async * @name async-matchers#toBeRejectedWith * @since 3.3.0 * @param {Object} expected - Value that the promise is expected to be rejected with * @example * await expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ return function toBeRejectedWith(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWith to be called on a promise.'); } function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + 'to be rejected with ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function() { return { pass: false, message: prefix(false) + ' but it was resolved.' }; }, function(actualValue) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' }; } else { return { pass: false, message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' }; } } ); } }; }; }; getJasmineRequireObj().toBeRejectedWithError = function(j$) { /** * Expect a promise to be rejected with a value matched to the expected * @function * @async * @name async-matchers#toBeRejectedWithError * @since 3.5.0 * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` * @example * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, 'Error message'); * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, /Error message/); * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError); * await expectAsync(aPromise).toBeRejectedWithError('Error message'); * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); */ return function toBeRejectedWithError(matchersUtil) { return { compare: function(actualPromise, arg1, arg2) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWithError to be called on a promise.'); } var expected = getExpectedFromArgs(arg1, arg2, matchersUtil); return actualPromise.then( function() { return { pass: false, message: 'Expected a promise to be rejected but it was resolved.' }; }, function(actualValue) { return matchError(actualValue, expected, matchersUtil); } ); } }; }; function matchError(actual, expected, matchersUtil) { if (!j$.isError_(actual)) { return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } if (!(actual instanceof expected.error)) { return fail(expected, 'rejected with type ' + j$.fnNameFor(actual.constructor)); } var actualMessage = actual.message; if (actualMessage === expected.message || typeof expected.message === 'undefined') { return pass(expected); } if (expected.message instanceof RegExp && expected.message.test(actualMessage)) { return pass(expected); } return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } function pass(expected) { return { pass: true, message: 'Expected a promise not to be rejected with ' + expected.printValue + ', but it was.' }; } function fail(expected, message) { return { pass: false, message: 'Expected a promise to be rejected with ' + expected.printValue + ' but it was ' + message + '.' }; } function getExpectedFromArgs(arg1, arg2, matchersUtil) { var error, message; if (isErrorConstructor(arg1)) { error = arg1; message = arg2; } else { error = Error; message = arg1; } return { error: error, message: message, printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + matchersUtil.pp(message)) }; } function isErrorConstructor(value) { return typeof value === 'function' && (value === Error || j$.isError_(value.prototype)); } }; getJasmineRequireObj().toBeResolved = function(j$) { /** * Expect a promise to be resolved. * @function * @async * @name async-matchers#toBeResolved * @since 3.1.0 * @example * await expectAsync(aPromise).toBeResolved(); * @example * return expectAsync(aPromise).toBeResolved(); */ return function toBeResolved() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { throw new Error('Expected toBeResolved to be called on a promise.'); } return actual.then( function() { return {pass: true}; }, function() { return {pass: false}; } ); } }; }; }; getJasmineRequireObj().toBeResolvedTo = function(j$) { /** * Expect a promise to be resolved to a value equal to the expected, using deep equality comparison. * @function * @async * @name async-matchers#toBeResolvedTo * @since 3.1.0 * @param {Object} expected - Value that the promise is expected to resolve to * @example * await expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ return function toBeResolvedTo(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeResolvedTo to be called on a promise.'); } function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + 'to be resolved to ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function(actualValue) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' }; } else { return { pass: false, message: prefix(false) + ' but it was resolved to ' + matchersUtil.pp(actualValue) + '.' }; } }, function() { return { pass: false, message: prefix(false) + ' but it was rejected.' }; } ); } }; }; }; getJasmineRequireObj().DiffBuilder = function (j$) { return function DiffBuilder(config) { var prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(), mismatches = new j$.MismatchTree(), path = new j$.ObjectPath(), actualRoot = undefined, expectedRoot = undefined; return { setRoots: function (actual, expected) { actualRoot = actual; expectedRoot = expected; }, recordMismatch: function (formatter) { mismatches.add(path, formatter); }, getMessage: function () { var messages = []; mismatches.traverse(function (path, isLeaf, formatter) { var actualCustom, expectedCustom, useCustom, derefResult = dereferencePath(path, actualRoot, expectedRoot, prettyPrinter), actual = derefResult.actual, expected = derefResult.expected; if (formatter) { messages.push(formatter(actual, expected, path, prettyPrinter)); return true; } actualCustom = prettyPrinter.customFormat_(actual); expectedCustom = prettyPrinter.customFormat_(expected); useCustom = !(j$.util.isUndefined(actualCustom) && j$.util.isUndefined(expectedCustom)); if (useCustom) { messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path)); return false; // don't recurse further } if (isLeaf) { messages.push(defaultFormatter(actual, expected, path, prettyPrinter)); } return true; }); return messages.join('\n'); }, withPath: function (pathComponent, block) { var oldPath = path; path = path.add(pathComponent); block(); path = oldPath; } }; function defaultFormatter(actual, expected, path, prettyPrinter) { return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path); } function wrapPrettyPrinted(actual, expected, path) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + actual + ' to equal ' + expected + '.'; } }; function dereferencePath(objectPath, actual, expected, pp) { function handleAsymmetricExpected() { if (j$.isAsymmetricEqualityTester_(expected) && j$.isFunction_(expected.valuesForDiff_)) { var asymmetricResult = expected.valuesForDiff_(actual, pp); expected = asymmetricResult.self; actual = asymmetricResult.other; } } var i; handleAsymmetricExpected(); for (i = 0; i < objectPath.components.length; i++) { actual = actual[objectPath.components[i]]; expected = expected[objectPath.components[i]]; handleAsymmetricExpected(); } return {actual: actual, expected: expected}; } }; getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: convert all uses of j$.pp to use the injected pp /** * _Note:_ Do not construct this directly. Jasmine will construct one and * pass it to matchers and asymmetric equality testers. * @name MatchersUtil * @classdesc Utilities for use in implementing matchers * @constructor */ function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; /** * Formats a value for use in matcher failure messages and similar contexts, * taking into account the current set of custom value formatters. * @function * @name MatchersUtil#pp * @since 3.6.0 * @param {*} value The value to pretty-print * @return {string} The pretty-printed value */ this.pp = options.pp || function() {}; }; /** * Determines whether `haystack` contains `needle`, using the same comparison * logic as {@link MatchersUtil#equals}. * @function * @name MatchersUtil#contains * @since 2.0.0 * @param {*} haystack The collection to search * @param {*} needle The value to search for * @param [customTesters] An array of custom equality testers * @returns {boolean} True if `needle` was found in `haystack` */ MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { return haystack.has(needle); } if ((Object.prototype.toString.apply(haystack) === '[object Array]') || (!!haystack && !haystack.indexOf)) { for (var i = 0; i < haystack.length; i++) { if (this.equals(haystack[i], needle, customTesters)) { return true; } } return false; } return !!haystack && haystack.indexOf(needle) >= 0; }; MatchersUtil.prototype.buildFailureMessage = function() { var self = this; var args = Array.prototype.slice.call(arguments, 0), matcherName = args[0], isNot = args[1], actual = args[2], expected = args.slice(3), englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); var message = 'Expected ' + self.pp(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; if (expected.length > 0) { for (var i = 0; i < expected.length; i++) { if (i > 0) { message += ','; } message += ' ' + self.pp(expected[i]); } } return message + '.'; }; MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { var values = b.valuesForDiff_(a, this.pp); this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { diffBuilder.recordMismatch(); } }; MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = j$.isAsymmetricEqualityTester_(a), asymmetricB = j$.isAsymmetricEqualityTester_(b), shim, result; if (asymmetricA === asymmetricB) { return undefined; } shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters); if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.recordMismatch(); } return result; } if (asymmetricB) { result = b.asymmetricMatch(a, shim); if (!result) { this.asymmetricDiff_(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } }; /** * Determines whether two values are deeply equal to each other. * @function * @name MatchersUtil#equals * @since 2.0.0 * @param {*} a The first value to compare * @param {*} b The second value to compare * @param [customTesters] An array of custom equality testers * @returns {boolean} True if the values are equal */ MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { var customTesters, diffBuilder; if (isDiffBuilder(customTestersOrDiffBuilder)) { diffBuilder = customTestersOrDiffBuilder; } else { customTesters = customTestersOrDiffBuilder; diffBuilder = diffBuilderOrNothing; } customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); diffBuilder.setRoots(a, b); return this.eq_(a, b, [], [], customTesters, diffBuilder); }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var result = true, self = this, i; var asymmetricResult = this.asymmetricMatch_(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } for (i = 0; i < customTesters.length; i++) { var customTesterResult = customTesters[i](a, b); if (!j$.util.isUndefined(customTesterResult)) { if (!customTesterResult) { diffBuilder.recordMismatch(); } return customTesterResult; } } if (a instanceof Error && b instanceof Error) { result = a.message == b.message; if (!result) { diffBuilder.recordMismatch(); } return result; } // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) { result = a !== 0 || 1 / a == 1 / b; if (!result) { diffBuilder.recordMismatch(); } return result; } // A strict comparison is necessary because `null == undefined`. if (a === null || b === null) { result = a === b; if (!result) { diffBuilder.recordMismatch(); } return result; } var className = Object.prototype.toString.call(a); if (className != Object.prototype.toString.call(b)) { diffBuilder.recordMismatch(); return false; } switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. result = a == String(b); if (!result) { diffBuilder.recordMismatch(); } return result; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. result = a != +a ? b != +b : (a === 0 && b === 0 ? 1 / a == 1 / b : a == +b); if (!result) { diffBuilder.recordMismatch(); } return result; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. result = +a == +b; if (!result) { diffBuilder.recordMismatch(); } return result; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { diffBuilder.recordMismatch(); return false; } var aIsDomNode = j$.isDomNode(a); var bIsDomNode = j$.isDomNode(b); if (aIsDomNode && bIsDomNode) { // At first try to use DOM3 method isEqualNode result = a.isEqualNode(b); if (!result) { diffBuilder.recordMismatch(); } return result; } if (aIsDomNode || bIsDomNode) { diffBuilder.recordMismatch(); return false; } var aIsPromise = j$.isPromise(a); var bIsPromise = j$.isPromise(b); if (aIsPromise && bIsPromise) { return a === b; } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] == a) { return bStack[length] == b; } } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); var size = 0; // Recursively compare objects and arrays. // Compare array lengths to determine if a deep comparison is necessary. if (className == '[object Array]') { var aLength = a.length; var bLength = b.length; diffBuilder.withPath('length', function() { if (aLength !== bLength) { diffBuilder.recordMismatch(); result = false; } }); for (i = 0; i < aLength || i < bLength; i++) { diffBuilder.withPath(i, function() { if (i >= bLength) { diffBuilder.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp)); result = false; } else { result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } if (!result) { return false; } } else if (j$.isMap(a) && j$.isMap(b)) { if (a.size != b.size) { diffBuilder.recordMismatch(); return false; } var keysA = []; var keysB = []; a.forEach( function( valueA, keyA ) { keysA.push( keyA ); }); b.forEach( function( valueB, keyB ) { keysB.push( keyB ); }); // For both sets of keys, check they map to equal values in both maps. // Keep track of corresponding keys (in insertion order) in order to handle asymmetric obj keys. var mapKeys = [keysA, keysB]; var cmpKeys = [keysB, keysA]; var mapIter, mapKey, mapValueA, mapValueB; var cmpIter, cmpKey; for (i = 0; result && i < mapKeys.length; i++) { mapIter = mapKeys[i]; cmpIter = cmpKeys[i]; for (var j = 0; result && j < mapIter.length; j++) { mapKey = mapIter[j]; cmpKey = cmpIter[j]; mapValueA = a.get(mapKey); // Only use the cmpKey when one of the keys is asymmetric and the corresponding key matches, // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) && this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } if (!result) { diffBuilder.recordMismatch(); return false; } } else if (j$.isSet(a) && j$.isSet(b)) { if (a.size != b.size) { diffBuilder.recordMismatch(); return false; } var valuesA = []; a.forEach( function( valueA ) { valuesA.push( valueA ); }); var valuesB = []; b.forEach( function( valueB ) { valuesB.push( valueB ); }); // For both sets, check they are all contained in the other set var setPairs = [[valuesA, valuesB], [valuesB, valuesA]]; var stackPairs = [[aStack, bStack], [bStack, aStack]]; var baseValues, baseValue, baseStack; var otherValues, otherValue, otherStack; var found; var prevStackSize; for (i = 0; result && i < setPairs.length; i++) { baseValues = setPairs[i][0]; otherValues = setPairs[i][1]; baseStack = stackPairs[i][0]; otherStack = stackPairs[i][1]; // For each value in the base set... for (var k = 0; result && k < baseValues.length; k++) { baseValue = baseValues[k]; found = false; // ... test that it is present in the other set for (var l = 0; !found && l < otherValues.length; l++) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); } } result = result && found; } } if (!result) { diffBuilder.recordMismatch(); return false; } } else { // Objects with different constructors are not equivalent, but `Object`s // or `Array`s from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && isFunction(aCtor) && isFunction(bCtor) && a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } // Deep compare objects. var aKeys = keys(a, className == '[object Array]'), key; size = aKeys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (keys(b, className == '[object Array]').length !== size) { diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } for (i = 0; i < size; i++) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } diffBuilder.withPath(key, function() { if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); } if (!result) { return false; } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { var keys = []; for (var key in o) { if (j$.util.has(o, key)) { keys.push(key); } } return keys; })(obj); if (!isArray) { return allKeys; } if (allKeys.length === 0) { return allKeys; } var extraKeys = []; for (var i = 0; i < allKeys.length; i++) { if (!/^[0-9]+$/.test(allKeys[i])) { extraKeys.push(allKeys[i]); } } return extraKeys; } function isFunction(obj) { return typeof obj === 'function'; } function objectKeysAreDifferentFormatter(pp, actual, expected, path) { var missingProperties = j$.util.objectDifference(expected, actual), extraProperties = j$.util.objectDifference(actual, expected), missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), messages = []; if (!path.depth()) { path = 'object'; } if (missingPropertiesMessage.length) { messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage); } if (extraPropertiesMessage.length) { messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage); } return messages.join('\n'); } function constructorsAreDifferentFormatter(pp, actual, expected, path) { if (!path.depth()) { path = 'object'; } return 'Expected ' + path + ' to be a kind of ' + j$.fnNameFor(expected.constructor) + ', but was ' + pp(actual) + '.'; } function actualArrayIsLongerFormatter(pp, actual, expected, path) { return 'Unexpected ' + path + (path.depth() ? ' = ' : '') + pp(actual) + ' in array.'; } function formatKeyValuePairs(pp, obj) { var formatted = ''; for (var key in obj) { formatted += '\n ' + key + ': ' + pp(obj[key]); } return formatted; } function isDiffBuilder(obj) { return obj && typeof obj.recordMismatch === 'function'; } return MatchersUtil; }; getJasmineRequireObj().MismatchTree = function (j$) { /* To be able to apply custom object formatters at all possible levels of an object graph, DiffBuilder needs to be able to know not just where the mismatch occurred but also all ancestors of the mismatched value in both the expected and actual object graphs. MismatchTree maintains that context and provides it via the traverse method. */ function MismatchTree(path) { this.path = path || new j$.ObjectPath([]); this.formatter = undefined; this.children = []; this.isMismatch = false; } MismatchTree.prototype.add = function (path, formatter) { var key, child; if (path.depth() === 0) { this.formatter = formatter; this.isMismatch = true; } else { key = path.components[0]; path = path.shift(); child = this.child(key); if (!child) { child = new MismatchTree(this.path.add(key)); this.children.push(child); } child.add(path, formatter); } }; MismatchTree.prototype.traverse = function (visit) { var i, hasChildren = this.children.length > 0; if (this.isMismatch || hasChildren) { if (visit(this.path, !hasChildren, this.formatter)) { for (i = 0; i < this.children.length; i++) { this.children[i].traverse(visit); } } } }; MismatchTree.prototype.child = function(key) { var i, pathEls; for (i = 0; i < this.children.length; i++) { pathEls = this.children[i].path.components; if (pathEls[pathEls.length - 1] === key) { return this.children[i]; } } }; return MismatchTree; }; getJasmineRequireObj().nothing = function() { /** * {@link expect} nothing explicitly. * @function * @name matchers#nothing * @since 2.8.0 * @example * expect().nothing(); */ function nothing() { return { compare: function() { return { pass: true }; } }; } return nothing; }; getJasmineRequireObj().NullDiffBuilder = function(j$) { return function() { return { withPath: function(_, block) { block(); }, setRoots: function() {}, recordMismatch: function() {} }; }; }; getJasmineRequireObj().ObjectPath = function(j$) { function ObjectPath(components) { this.components = components || []; } ObjectPath.prototype.toString = function() { if (this.components.length) { return '$' + map(this.components, formatPropertyAccess).join(''); } else { return ''; } }; ObjectPath.prototype.add = function(component) { return new ObjectPath(this.components.concat([component])); }; ObjectPath.prototype.shift = function() { return new ObjectPath(this.components.slice(1)); }; ObjectPath.prototype.depth = function() { return this.components.length; }; function formatPropertyAccess(prop) { if (typeof prop === 'number') { return '[' + prop + ']'; } if (isValidIdentifier(prop)) { return '.' + prop; } return '[\'' + prop + '\']'; } function map(array, fn) { var results = []; for (var i = 0; i < array.length; i++) { results.push(fn(array[i])); } return results; } function isValidIdentifier(string) { return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string); } return ObjectPath; }; getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { var availableMatchers = [ 'toBePending', 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', 'toBeRejectedWith', 'toBeRejectedWithError' ], matchers = {}; for (var i = 0; i < availableMatchers.length; i++) { var name = availableMatchers[i]; matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().toBe = function(j$) { /** * {@link expect} the actual value to be `===` to the expected value. * @function * @name matchers#toBe * @since 1.3.0 * @param {Object} expected - The expected value to compare against. * @example * expect(thing).toBe(realThing); */ function toBe(matchersUtil) { var tip = ' Tip: To check for deep equality, use .toEqual() instead of .toBe().'; return { compare: function(actual, expected) { var result = { pass: actual === expected }; if (typeof expected === 'object') { result.message = matchersUtil.buildFailureMessage('toBe', result.pass, actual, expected) + tip; } return result; } }; } return toBe; }; getJasmineRequireObj().toBeCloseTo = function() { /** * {@link expect} the actual value to be within a specified precision of the expected value. * @function * @name matchers#toBeCloseTo * @since 1.3.0 * @param {Object} expected - The expected value to compare against. * @param {Number} [precision=2] - The number of decimal points to check. * @example * expect(number).toBeCloseTo(42.2, 3); */ function toBeCloseTo() { return { compare: function(actual, expected, precision) { if (precision !== 0) { precision = precision || 2; } if (expected === null || actual === null) { throw new Error('Cannot use toBeCloseTo with null. Arguments evaluated to: ' + 'expect(' + actual + ').toBeCloseTo(' + expected + ').' ); } var pow = Math.pow(10, precision + 1); var delta = Math.abs(expected - actual); var maxDelta = Math.pow(10, -precision) / 2; return { pass: Math.round(delta * pow) <= maxDelta * pow }; } }; } return toBeCloseTo; }; getJasmineRequireObj().toBeDefined = function() { /** * {@link expect} the actual value to be defined. (Not `undefined`) * @function * @name matchers#toBeDefined * @since 1.3.0 * @example * expect(result).toBeDefined(); */ function toBeDefined() { return { compare: function(actual) { return { pass: (void 0 !== actual) }; } }; } return toBeDefined; }; getJasmineRequireObj().toBeFalse = function() { /** * {@link expect} the actual value to be `false`. * @function * @name matchers#toBeFalse * @since 3.5.0 * @example * expect(result).toBeFalse(); */ function toBeFalse() { return { compare: function(actual) { return { pass: actual === false }; } }; } return toBeFalse; }; getJasmineRequireObj().toBeFalsy = function() { /** * {@link expect} the actual value to be falsy * @function * @name matchers#toBeFalsy * @since 2.0.0 * @example * expect(result).toBeFalsy(); */ function toBeFalsy() { return { compare: function(actual) { return { pass: !actual }; } }; } return toBeFalsy; }; getJasmineRequireObj().toBeGreaterThan = function() { /** * {@link expect} the actual value to be greater than the expected value. * @function * @name matchers#toBeGreaterThan * @since 2.0.0 * @param {Number} expected - The value to compare against. * @example * expect(result).toBeGreaterThan(3); */ function toBeGreaterThan() { return { compare: function(actual, expected) { return { pass: actual > expected }; } }; } return toBeGreaterThan; }; getJasmineRequireObj().toBeGreaterThanOrEqual = function() { /** * {@link expect} the actual value to be greater than or equal to the expected value. * @function * @name matchers#toBeGreaterThanOrEqual * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeGreaterThanOrEqual(25); */ function toBeGreaterThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual >= expected }; } }; } return toBeGreaterThanOrEqual; }; getJasmineRequireObj().toBeInstanceOf = function(j$) { var usageError = j$.formatErrorMsg('', 'expect(value).toBeInstanceOf()'); /** * {@link expect} the actual to be an instance of the expected class * @function * @name matchers#toBeInstanceOf * @since 3.5.0 * @param {Object} expected - The class or constructor function to check for * @example * expect('foo').toBeInstanceOf(String); * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ function toBeInstanceOf(matchersUtil) { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : matchersUtil.pp(actual), expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), expectedMatcher, pass; try { expectedMatcher = new j$.Any(expected); pass = expectedMatcher.asymmetricMatch(actual); } catch (error) { throw new Error(usageError('Expected value is not a constructor function')); } if (pass) { return { pass: true, message: 'Expected instance of ' + actualType + ' not to be an instance of ' + expectedType }; } else { return { pass: false, message: 'Expected instance of ' + actualType + ' to be an instance of ' + expectedType }; } } }; } return toBeInstanceOf; }; getJasmineRequireObj().toBeLessThan = function() { /** * {@link expect} the actual value to be less than the expected value. * @function * @name matchers#toBeLessThan * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeLessThan(0); */ function toBeLessThan() { return { compare: function(actual, expected) { return { pass: actual < expected }; } }; } return toBeLessThan; }; getJasmineRequireObj().toBeLessThanOrEqual = function() { /** * {@link expect} the actual value to be less than or equal to the expected value. * @function * @name matchers#toBeLessThanOrEqual * @since 2.0.0 * @param {Number} expected - The expected value to compare against. * @example * expect(result).toBeLessThanOrEqual(123); */ function toBeLessThanOrEqual() { return { compare: function(actual, expected) { return { pass: actual <= expected }; } }; } return toBeLessThanOrEqual; }; getJasmineRequireObj().toBeNaN = function(j$) { /** * {@link expect} the actual value to be `NaN` (Not a Number). * @function * @name matchers#toBeNaN * @since 1.3.0 * @example * expect(thing).toBeNaN(); */ function toBeNaN(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual !== actual) }; if (result.pass) { result.message = 'Expected actual not to be NaN.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be NaN.'; }; } return result; } }; } return toBeNaN; }; getJasmineRequireObj().toBeNegativeInfinity = function(j$) { /** * {@link expect} the actual value to be `-Infinity` (-infinity). * @function * @name matchers#toBeNegativeInfinity * @since 2.6.0 * @example * expect(thing).toBeNegativeInfinity(); */ function toBeNegativeInfinity(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual === Number.NEGATIVE_INFINITY) }; if (result.pass) { result.message = 'Expected actual not to be -Infinity.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be -Infinity.'; }; } return result; } }; } return toBeNegativeInfinity; }; getJasmineRequireObj().toBeNull = function() { /** * {@link expect} the actual value to be `null`. * @function * @name matchers#toBeNull * @since 1.3.0 * @example * expect(result).toBeNull(); */ function toBeNull() { return { compare: function(actual) { return { pass: actual === null }; } }; } return toBeNull; }; getJasmineRequireObj().toBePositiveInfinity = function(j$) { /** * {@link expect} the actual value to be `Infinity` (infinity). * @function * @name matchers#toBePositiveInfinity * @since 2.6.0 * @example * expect(thing).toBePositiveInfinity(); */ function toBePositiveInfinity(matchersUtil) { return { compare: function(actual) { var result = { pass: (actual === Number.POSITIVE_INFINITY) }; if (result.pass) { result.message = 'Expected actual not to be Infinity.'; } else { result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be Infinity.'; }; } return result; } }; } return toBePositiveInfinity; }; getJasmineRequireObj().toBeTrue = function() { /** * {@link expect} the actual value to be `true`. * @function * @name matchers#toBeTrue * @since 3.5.0 * @example * expect(result).toBeTrue(); */ function toBeTrue() { return { compare: function(actual) { return { pass: actual === true }; } }; } return toBeTrue; }; getJasmineRequireObj().toBeTruthy = function() { /** * {@link expect} the actual value to be truthy. * @function * @name matchers#toBeTruthy * @since 2.0.0 * @example * expect(thing).toBeTruthy(); */ function toBeTruthy() { return { compare: function(actual) { return { pass: !!actual }; } }; } return toBeTruthy; }; getJasmineRequireObj().toBeUndefined = function() { /** * {@link expect} the actual value to be `undefined`. * @function * @name matchers#toBeUndefined * @since 1.3.0 * @example * expect(result).toBeUndefined(): */ function toBeUndefined() { return { compare: function(actual) { return { pass: void 0 === actual }; } }; } return toBeUndefined; }; getJasmineRequireObj().toContain = function() { /** * {@link expect} the actual value to contain a specific value. * @function * @name matchers#toContain * @since 2.0.0 * @param {Object} expected - The value to look for. * @example * expect(array).toContain(anElement); * expect(string).toContain(substring); */ function toContain(matchersUtil) { return { compare: function(actual, expected) { return { pass: matchersUtil.contains(actual, expected) }; } }; } return toContain; }; getJasmineRequireObj().toEqual = function(j$) { /** * {@link expect} the actual value to be equal to the expected, using deep equality comparison. * @function * @name matchers#toEqual * @since 1.3.0 * @param {Object} expected - Expected value * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); return result; } }; } return toEqual; }; getJasmineRequireObj().toHaveBeenCalled = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalled()'); /** * {@link expect} the actual (a {@link Spy}) to have been called. * @function * @name matchers#toHaveBeenCalled * @since 1.3.0 * @example * expect(mySpy).toHaveBeenCalled(); * expect(mySpy).not.toHaveBeenCalled(); */ function toHaveBeenCalled(matchersUtil) { return { compare: function(actual) { var result = {}; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (arguments.length > 1) { throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith')); } result.pass = actual.calls.any(); result.message = result.pass ? 'Expected spy ' + actual.and.identity + ' not to have been called.' : 'Expected spy ' + actual.and.identity + ' to have been called.'; return result; } }; } return toHaveBeenCalled; }; getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledBefore()'); /** * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}. * @function * @name matchers#toHaveBeenCalledBefore * @since 2.6.0 * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}. * @example * expect(mySpy).toHaveBeenCalledBefore(otherSpy); */ function toHaveBeenCalledBefore(matchersUtil) { return { compare: function(firstSpy, latterSpy) { if (!j$.isSpy(firstSpy)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(firstSpy) + '.')); } if (!j$.isSpy(latterSpy)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(latterSpy) + '.')); } var result = { pass: false }; if (!firstSpy.calls.count()) { result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.'; return result; } if (!latterSpy.calls.count()) { result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.'; return result; } var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder; var first2ndSpyCall = latterSpy.calls.first().invocationOrder; result.pass = latest1stSpyCall < first2ndSpyCall; if (result.pass) { result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy ' + latterSpy.and.identity + ', but it was'; } else { var first1stSpyCall = firstSpy.calls.first().invocationOrder; var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; if(first1stSpyCall < first2ndSpyCall) { result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)'; } else if (latest2ndSpyCall > latest1stSpyCall) { result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)'; } else { result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' + latterSpy.and.identity; } } return result; } }; } return toHaveBeenCalledBefore; }; getJasmineRequireObj().toHaveBeenCalledOnceWith = function (j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledOnceWith(...arguments)'); /** * {@link expect} the actual (a {@link Spy}) to have been called exactly once, and exactly with the particular arguments. * @function * @name matchers#toHaveBeenCalledOnceWith * @since 3.6.0 * @param {...Object} - The arguments to look for * @example * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); */ function toHaveBeenCalledOnceWith(util) { return { compare: function () { var args = Array.prototype.slice.call(arguments, 0), actual = args[0], expectedArgs = args.slice(1); if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); } var prettyPrintedCalls = actual.calls.allArgs().map(function (argsForCall) { return ' ' + j$.pp(argsForCall); }); if (actual.calls.count() === 1 && util.contains(actual.calls.allArgs(), expectedArgs)) { return { pass: true, message: 'Expected spy ' + actual.and.identity + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + ' ' + j$.pp(expectedArgs) + '\n' + 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' }; } function getDiffs() { return actual.calls.allArgs().map(function (argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); util.equals(argsForCall, expectedArgs, diffBuilder); return diffBuilder.getMessage(); }); } function butString() { switch (actual.calls.count()) { case 0: return 'But it was never called.\n\n'; case 1: return 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n' + getDiffs().join('\n') + '\n\n'; default: return 'But the actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n'; } } return { pass: false, message: 'Expected spy ' + actual.and.identity + ' to have been called only once, and with given args:\n' + ' ' + j$.pp(expectedArgs) + '\n' + butString() }; } }; } return toHaveBeenCalledOnceWith; }; getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); /** * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times. * @function * @name matchers#toHaveBeenCalledTimes * @since 2.4.0 * @param {Number} expected - The number of invocations to look for. * @example * expect(mySpy).toHaveBeenCalledTimes(3); */ function toHaveBeenCalledTimes(matchersUtil) { return { compare: function(actual, expected) { if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } var args = Array.prototype.slice.call(arguments, 0), result = { pass: false }; if (!j$.isNumber_(expected)) { throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.')); } actual = args[0]; var calls = actual.calls.count(); var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? 'Expected spy ' + actual.and.identity + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : 'Expected spy ' + actual.and.identity + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; } return toHaveBeenCalledTimes; }; getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledWith(...arguments)'); /** * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once. * @function * @name matchers#toHaveBeenCalledWith * @since 1.3.0 * @param {...Object} - The arguments to look for * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ function toHaveBeenCalledWith(matchersUtil) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), actual = args[0], expectedArgs = args.slice(1), result = { pass: false }; if (!j$.isSpy(actual)) { throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (!actual.calls.any()) { result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was never called.'; }; return result; } if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was.'; }; } else { result.message = function() { var prettyPrintedCalls = actual.calls.allArgs().map(function(argsForCall) { return ' ' + matchersUtil.pp(argsForCall); }); var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); matchersUtil.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + ' ' + matchersUtil.pp(expectedArgs) + '\n' + '' + 'but actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' + diffs.join('\n'); }; } return result; } }; } return toHaveBeenCalledWith; }; getJasmineRequireObj().toHaveClass = function(j$) { /** * {@link expect} the actual value to be a DOM element that has the expected class * @function * @name matchers#toHaveClass * @since 3.0.0 * @param {Object} expected - The class name to test for * @example * var el = document.createElement('div'); * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ function toHaveClass(matchersUtil) { return { compare: function(actual, expected) { if (!isElement(actual)) { throw new Error(matchersUtil.pp(actual) + ' is not a DOM element'); } return { pass: actual.classList.contains(expected) }; } }; } function isElement(maybeEl) { return maybeEl && maybeEl.classList && j$.isFunction_(maybeEl.classList.contains); } return toHaveClass; }; getJasmineRequireObj().toHaveSize = function(j$) { /** * {@link expect} the actual size to be equal to the expected, using array-like length or object keys size. * @function * @name matchers#toHaveSize * @since 3.6.0 * @param {Object} expected - Expected size * @example * array = [1,2]; * expect(array).toHaveSize(2); */ function toHaveSize() { return { compare: function(actual, expected) { var result = { pass: false }; if (j$.isA_('WeakSet', actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) { throw new Error('Cannot get size of ' + actual + '.'); } if (j$.isSet(actual) || j$.isMap(actual)) { result.pass = actual.size === expected; } else if (isLength(actual.length)) { result.pass = actual.length === expected; } else { result.pass = Object.keys(actual).length === expected; } return result; } }; } var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line compat/compat function isLength(value) { return (typeof value == 'number') && value > -1 && value % 1 === 0 && value <= MAX_SAFE_INTEGER; } return toHaveSize; }; getJasmineRequireObj().toMatch = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toMatch( || )'); /** * {@link expect} the actual value to match a regular expression * @function * @name matchers#toMatch * @since 1.3.0 * @param {RegExp|String} expected - Value to look for in the string. * @example * expect("my string").toMatch(/string$/); * expect("other string").toMatch("her"); */ function toMatch() { return { compare: function(actual, expected) { if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { throw new Error(getErrorMsg('Expected is not a String or a RegExp')); } var regexp = new RegExp(expected); return { pass: regexp.test(actual) }; } }; } return toMatch; }; getJasmineRequireObj().toThrow = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrow()'); /** * {@link expect} a function to `throw` something. * @function * @name matchers#toThrow * @since 2.0.0 * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that something was thrown will be checked. * @example * expect(function() { return 'things'; }).toThrow('foo'); * expect(function() { return 'stuff'; }).toThrow(); */ function toThrow(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, threw = false, thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } try { actual(); } catch (e) { threw = true; thrown = e; } if (!threw) { result.message = 'Expected function to throw an exception.'; return result; } if (arguments.length == 1) { result.pass = true; result.message = function() { return 'Expected function not to throw, but it threw ' + matchersUtil.pp(thrown) + '.'; }; return result; } if (matchersUtil.equals(thrown, expected)) { result.pass = true; result.message = function() { return 'Expected function not to throw ' + matchersUtil.pp(expected) + '.'; }; } else { result.message = function() { return 'Expected function to throw ' + matchersUtil.pp(expected) + ', but it threw ' + matchersUtil.pp(thrown) + '.'; }; } return result; } }; } return toThrow; }; getJasmineRequireObj().toThrowError = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrowError(, )'); /** * {@link expect} a function to `throw` an `Error`. * @function * @name matchers#toThrowError * @since 2.0.0 * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` * @example * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message'); * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/); * expect(function() { return 'stuff'; }).toThrowError(MyCustomError); * expect(function() { return 'other'; }).toThrowError(/foo/); * expect(function() { return 'other'; }).toThrowError(); */ function toThrowError(matchersUtil) { return { compare: function(actual) { var errorMatcher = getMatcher.apply(null, arguments), thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } try { actual(); return fail('Expected function to throw an Error.'); } catch (e) { thrown = e; } if (!j$.isError_(thrown)) { return fail(function() { return 'Expected function to throw an Error, but it threw ' + matchersUtil.pp(thrown) + '.'; }); } return errorMatcher.match(thrown); } }; function getMatcher() { var expected, errorType; if (arguments[2]) { errorType = arguments[1]; expected = arguments[2]; if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); } else { return exactMatcher(arguments[1], null); } } else { return anyMatcher(); } } function anyMatcher() { return { match: function(error) { return pass('Expected function not to throw an Error, but it threw ' + j$.fnNameFor(error) + '.'); } }; } function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } function messageMatch(message) { if (typeof expected == 'string') { return expected == message; } else { return expected.test(message); } } var errorTypeDescription = errorType ? j$.fnNameFor(errorType) : 'an exception'; function thrownDescription(thrown) { var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', thrownMessage = ''; if (expected) { thrownMessage = ' with message ' + matchersUtil.pp(thrown.message); } return thrownName + thrownMessage; } function messageDescription() { if (expected === null) { return ''; } else if (expected instanceof RegExp) { return ' with a message matching ' + matchersUtil.pp(expected); } else { return ' with message ' + matchersUtil.pp(expected); } } function matches(error) { return (errorType === null || error instanceof errorType) && (expected === null || messageMatch(error.message)); } return { match: function(thrown) { if (matches(thrown)) { return pass(function() { return 'Expected function not to throw ' + errorTypeDescription + messageDescription() + '.'; }); } else { return fail(function() { return 'Expected function to throw ' + errorTypeDescription + messageDescription() + ', but it threw ' + thrownDescription(thrown) + '.'; }); } } }; } function isStringOrRegExp(potential) { return potential instanceof RegExp || (typeof potential == 'string'); } function isAnErrorType(type) { if (typeof type !== 'function') { return false; } var Surrogate = function() {}; Surrogate.prototype = type.prototype; return j$.isError_(new Surrogate()); } } function pass(message) { return { pass: true, message: message }; } function fail(message) { return { pass: false, message: message }; } return toThrowError; }; getJasmineRequireObj().toThrowMatching = function(j$) { var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); /** * {@link expect} a function to `throw` something matching a predicate. * @function * @name matchers#toThrowMatching * @since 3.0.0 * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. * @example * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); */ function toThrowMatching(matchersUtil) { return { compare: function(actual, predicate) { var thrown; if (typeof actual !== 'function') { throw new Error(usageError('Actual is not a Function')); } if (typeof predicate !== 'function') { throw new Error(usageError('Predicate is not a Function')); } try { actual(); return fail('Expected function to throw an exception.'); } catch (e) { thrown = e; } if (predicate(thrown)) { return pass('Expected function not to throw an exception matching a predicate.'); } else { return fail(function() { return 'Expected function to throw an exception matching a predicate, ' + 'but it threw ' + thrownDescription(thrown) + '.'; }); } } }; function thrownDescription(thrown) { if (thrown && thrown.constructor) { return j$.fnNameFor(thrown.constructor) + ' with message ' + matchersUtil.pp(thrown.message); } else { return matchersUtil.pp(thrown); } } } function pass(message) { return { pass: true, message: message }; } function fail(message) { return { pass: false, message: message }; } return toThrowMatching; }; getJasmineRequireObj().MockDate = function() { function MockDate(global) { var self = this; var currentTime = 0; if (!global || !global.Date) { self.install = function() {}; self.tick = function() {}; self.uninstall = function() {}; return self; } var GlobalDate = global.Date; self.install = function(mockDate) { if (mockDate instanceof GlobalDate) { currentTime = mockDate.getTime(); } else { currentTime = new GlobalDate().getTime(); } global.Date = FakeDate; }; self.tick = function(millis) { millis = millis || 0; currentTime = currentTime + millis; }; self.uninstall = function() { currentTime = 0; global.Date = GlobalDate; }; createDateProperties(); return self; function FakeDate() { switch (arguments.length) { case 0: return new GlobalDate(currentTime); case 1: return new GlobalDate(arguments[0]); case 2: return new GlobalDate(arguments[0], arguments[1]); case 3: return new GlobalDate(arguments[0], arguments[1], arguments[2]); case 4: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3] ); case 5: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4] ); case 6: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5] ); default: return new GlobalDate( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6] ); } } function createDateProperties() { FakeDate.prototype = GlobalDate.prototype; FakeDate.now = function() { if (GlobalDate.now) { return currentTime; } else { throw new Error('Browser does not support Date.now()'); } }; FakeDate.toSource = GlobalDate.toSource; FakeDate.toString = GlobalDate.toString; FakeDate.parse = GlobalDate.parse; FakeDate.UTC = GlobalDate.UTC; } } return MockDate; }; getJasmineRequireObj().makePrettyPrinter = function(j$) { function SinglePrettyPrintRun(customObjectFormatters, pp) { this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; this.pp_ = pp; } function hasCustomToString(value) { // value.toString !== Object.prototype.toString if value has no custom toString but is from another context (e.g. // iframe, web worker) try { return ( j$.isFunction_(value.toString) && value.toString !== Object.prototype.toString && value.toString() !== Object.prototype.toString.call(value) ); } catch (e) { // The custom toString() threw. return true; } } SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { var customFormatResult = this.applyCustomFormatters_(value); if (customFormatResult) { this.emitScalar(customFormatResult); } else if (j$.util.isUndefined(value)) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === 0 && 1 / value === -Infinity) { this.emitScalar('-0'); } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { this.emitScalar(value.jasmineToString(this.pp_)); } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { this.emitScalar('spy on ' + value.and.identity); } else if (j$.isSpy(value.toString)) { this.emitScalar('spy on ' + value.toString.and.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (j$.isDomNode(value)) { if (value.tagName) { this.emitDomElement(value); } else { this.emitScalar('HTMLNode'); } } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (j$.isSet(value)) { this.emitSet(value); } else if (j$.isMap(value)) { this.emitMap(value); } else if (j$.isTypedArray_(value)) { this.emitTypedArray(value); } else if ( value.toString && typeof value === 'object' && !j$.isArray_(value) && hasCustomToString(value) ) { try { this.emitScalar(value.toString()); } catch (e) { this.emitScalar('has-invalid-toString-method'); } } else if (j$.util.arrayContains(this.seen, value)) { this.emitScalar( '' ); } else if (j$.isArray_(value) || j$.isA_('Object', value)) { this.seen.push(value); if (j$.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } this.seen.pop(); } else { this.emitScalar(value.toString()); } } catch (e) { if (this.ppNestLevel_ > 1 || !(e instanceof MaxCharsReachedError)) { throw e; } } finally { this.ppNestLevel_--; } }; SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) { return customFormat(value, this.customObjectFormatters_); }; SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; if (obj.__lookupGetter__) { isGetter = function isGetter(prop) { var getter = obj.__lookupGetter__(prop); return !j$.util.isUndefined(getter) && getter !== null; }; } var length = Math.min(objKeys.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); for (var i = 0; i < length; i++) { var property = objKeys[i]; fn(property, isGetter(property)); } return objKeys.length > length; }; SinglePrettyPrintRun.prototype.emitScalar = function(value) { this.append(value); }; SinglePrettyPrintRun.prototype.emitString = function(value) { this.append("'" + value + "'"); }; SinglePrettyPrintRun.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Array'); return; } var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); this.append('[ '); for (var i = 0; i < length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } if (array.length > length) { this.append(', ...'); } var self = this; var first = array.length === 0; var truncated = this.iterateObject(array, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.formatProperty(array, property, isGetter); }); if (truncated) { this.append(', ...'); } this.append(' ]'); }; SinglePrettyPrintRun.prototype.emitSet = function(set) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Set'); return; } this.append('Set( '); var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); var i = 0; set.forEach(function(value, key) { if (i >= size) { return; } if (i > 0) { this.append(', '); } this.format(value); i++; }, this); if (set.size > size) { this.append(', ...'); } this.append(' )'); }; SinglePrettyPrintRun.prototype.emitMap = function(map) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Map'); return; } this.append('Map( '); var size = Math.min(map.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); var i = 0; map.forEach(function(value, key) { if (i >= size) { return; } if (i > 0) { this.append(', '); } this.format([key, value]); i++; }, this); if (map.size > size) { this.append(', ...'); } this.append(' )'); }; SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; constructorName = typeof ctor === 'function' && obj instanceof ctor ? j$.fnNameFor(obj.constructor) : 'null'; this.append(constructorName); if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { return; } var self = this; this.append('({ '); var first = true; var truncated = this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.formatProperty(obj, property, isGetter); }); if (truncated) { this.append(', ...'); } this.append(' })'); }; SinglePrettyPrintRun.prototype.emitTypedArray = function(arr) { var constructorName = j$.fnNameFor(arr.constructor), limitedArray = Array.prototype.slice.call( arr, 0, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH ), itemsString = Array.prototype.join.call(limitedArray, ', '); if (limitedArray.length !== arr.length) { itemsString += ', ...'; } this.append(constructorName + ' [ ' + itemsString + ' ]'); }; SinglePrettyPrintRun.prototype.emitDomElement = function(el) { var tagName = el.tagName.toLowerCase(), attrs = el.attributes, i, len = attrs.length, out = '<' + tagName, attr; for (i = 0; i < len; i++) { attr = attrs[i]; out += ' ' + attr.name; if (attr.value !== '') { out += '="' + attr.value + '"'; } } out += '>'; if (el.childElementCount !== 0 || el.textContent !== '') { out += '...'; } this.append(out); }; SinglePrettyPrintRun.prototype.formatProperty = function( obj, property, isGetter ) { this.append(property); this.append(': '); if (isGetter) { this.append(''); } else { this.format(obj[property]); } }; SinglePrettyPrintRun.prototype.append = function(value) { // This check protects us from the rare case where an object has overriden // `toString()` with an invalid implementation (returning a non-string). if (typeof value !== 'string') { value = Object.prototype.toString.call(value); } var result = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); this.length += result.value.length; this.stringParts.push(result.value); if (result.truncated) { throw new MaxCharsReachedError(); } }; function truncate(s, maxlen) { if (s.length <= maxlen) { return { value: s, truncated: false }; } s = s.substring(0, maxlen - 4) + ' ...'; return { value: s, truncated: true }; } function MaxCharsReachedError() { this.message = 'Exceeded ' + j$.MAX_PRETTY_PRINT_CHARS + ' characters while pretty-printing a value'; } MaxCharsReachedError.prototype = new Error(); function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { var keys = []; for (var key in o) { if (j$.util.has(o, key)) { keys.push(key); } } return keys; })(obj); if (!isArray) { return allKeys; } if (allKeys.length === 0) { return allKeys; } var extraKeys = []; for (var i = 0; i < allKeys.length; i++) { if (!/^[0-9]+$/.test(allKeys[i])) { extraKeys.push(allKeys[i]); } } return extraKeys; } function customFormat(value, customObjectFormatters) { var i, result; for (i = 0; i < customObjectFormatters.length; i++) { result = customObjectFormatters[i](value); if (result !== undefined) { return result; } } } return function(customObjectFormatters) { customObjectFormatters = customObjectFormatters || []; var pp = function(value) { var prettyPrinter = new SinglePrettyPrintRun(customObjectFormatters, pp); prettyPrinter.format(value); return prettyPrinter.stringParts.join(''); }; pp.customFormat_ = function(value) { return customFormat(value, customObjectFormatters); }; return pp; }; }; getJasmineRequireObj().QueueRunner = function(j$) { function StopExecutionError() {} StopExecutionError.prototype = new Error(); j$.StopExecutionError = StopExecutionError; function once(fn) { var called = false; return function(arg) { if (!called) { called = true; // Direct call using single parameter, because cleanup/next does not need more fn(arg); } return null; }; } function emptyFn() {} function QueueRunner(attrs) { var queueableFns = attrs.queueableFns || []; this.queueableFns = queueableFns.concat(attrs.cleanupFns || []); this.firstCleanupIx = queueableFns.length; this.onComplete = attrs.onComplete || emptyFn; this.clearStack = attrs.clearStack || function(fn) { fn(); }; this.onException = attrs.onException || emptyFn; this.userContext = attrs.userContext || new j$.UserContext(); this.timeout = attrs.timeout || { setTimeout: setTimeout, clearTimeout: clearTimeout }; this.fail = attrs.fail || emptyFn; this.globalErrors = attrs.globalErrors || { pushListener: emptyFn, popListener: emptyFn }; this.completeOnFirstError = !!attrs.completeOnFirstError; this.errored = false; if (typeof this.onComplete !== 'function') { throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); } this.deprecated = attrs.deprecated; } QueueRunner.prototype.execute = function() { var self = this; this.handleFinalError = function(message, source, lineno, colno, error) { // Older browsers would send the error as the first parameter. HTML5 // specifies the the five parameters above. The error instance should // be preffered, otherwise the call stack would get lost. self.onException(error || message); }; this.globalErrors.pushListener(this.handleFinalError); this.run(0); }; QueueRunner.prototype.skipToCleanup = function(lastRanIndex) { if (lastRanIndex < this.firstCleanupIx) { this.run(this.firstCleanupIx); } else { this.run(lastRanIndex + 1); } }; QueueRunner.prototype.clearTimeout = function(timeoutId) { Function.prototype.apply.apply(this.timeout.clearTimeout, [ j$.getGlobal(), [timeoutId] ]); }; QueueRunner.prototype.setTimeout = function(fn, timeout) { return Function.prototype.apply.apply(this.timeout.setTimeout, [ j$.getGlobal(), [fn, timeout] ]); }; QueueRunner.prototype.attempt = function attempt(iterativeIndex) { var self = this, completedSynchronously = true, handleError = function handleError(error) { onException(error); next(error); }, cleanup = once(function cleanup() { if (timeoutId !== void 0) { self.clearTimeout(timeoutId); } self.globalErrors.popListener(handleError); }), next = once(function next(err) { cleanup(); if (j$.isError_(err)) { if (!(err instanceof StopExecutionError) && !err.jasmineMessage) { self.fail(err); } self.errored = errored = true; } function runNext() { if (self.completeOnFirstError && errored) { self.skipToCleanup(iterativeIndex); } else { self.run(iterativeIndex + 1); } } if (completedSynchronously) { self.setTimeout(runNext); } else { runNext(); } }), errored = false, queueableFn = self.queueableFns[iterativeIndex], timeoutId; next.fail = function nextFail() { self.fail.apply(null, arguments); self.errored = errored = true; next(); }; self.globalErrors.pushListener(handleError); if (queueableFn.timeout !== undefined) { var timeoutInterval = queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL; timeoutId = self.setTimeout(function() { var error = new Error( 'Timeout - Async function did not complete within ' + timeoutInterval + 'ms ' + (queueableFn.timeout ? '(custom timeout)' : '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)') ); onException(error); next(); }, timeoutInterval); } try { if (queueableFn.fn.length === 0) { var maybeThenable = queueableFn.fn.call(self.userContext); if (maybeThenable && j$.isFunction_(maybeThenable.then)) { maybeThenable.then(next, onPromiseRejection); completedSynchronously = false; return { completedSynchronously: false }; } } else { queueableFn.fn.call(self.userContext, next); completedSynchronously = false; return { completedSynchronously: false }; } } catch (e) { onException(e); self.errored = errored = true; } cleanup(); return { completedSynchronously: true, errored: errored }; function onException(e) { self.onException(e); self.errored = errored = true; } function onPromiseRejection(e) { onException(e); next(); } }; QueueRunner.prototype.run = function(recursiveIndex) { var length = this.queueableFns.length, self = this, iterativeIndex; for ( iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++ ) { var result = this.attempt(iterativeIndex); if (!result.completedSynchronously) { return; } self.errored = self.errored || result.errored; if (this.completeOnFirstError && result.errored) { this.skipToCleanup(iterativeIndex); return; } } this.clearStack(function() { self.globalErrors.popListener(self.handleFinalError); self.onComplete(self.errored && new StopExecutionError()); }); }; return QueueRunner; }; getJasmineRequireObj().ReportDispatcher = function(j$) { function ReportDispatcher(methods, queueRunnerFactory) { var dispatchedMethods = methods || []; for (var i = 0; i < dispatchedMethods.length; i++) { var method = dispatchedMethods[i]; this[method] = (function(m) { return function() { dispatch(m, arguments); }; })(method); } var reporters = []; var fallbackReporter = null; this.addReporter = function(reporter) { reporters.push(reporter); }; this.provideFallbackReporter = function(reporter) { fallbackReporter = reporter; }; this.clearReporters = function() { reporters = []; }; return this; function dispatch(method, args) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } var onComplete = args[args.length - 1]; args = j$.util.argsToArray(args).splice(0, args.length - 1); var fns = []; for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; addFn(fns, reporter, method, args); } queueRunnerFactory({ queueableFns: fns, onComplete: onComplete, isReporter: true }); } function addFn(fns, reporter, method, args) { var fn = reporter[method]; if (!fn) { return; } var thisArgs = j$.util.cloneArgs(args); if (fn.length <= 1) { fns.push({ fn: function() { return fn.apply(reporter, thisArgs); } }); } else { fns.push({ fn: function(done) { return fn.apply(reporter, thisArgs.concat([done])); } }); } } } return ReportDispatcher; }; getJasmineRequireObj().interface = function(jasmine, env) { var jasmineInterface = { /** * Callback passed to parts of the Jasmine base interface. * * By default Jasmine assumes this function completes synchronously. * If you have code that you need to test asynchronously, you can declare that you receive a `done` callback, return a Promise, or use the `async` keyword if it is supported in your environment. * @callback implementationCallback * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on. * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. */ /** * Create a group of specs (often called a suite). * * Calls to `describe` can be nested within other calls to compose your suite as a tree. * @name describe * @since 1.3.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ describe: function(description, specDefinitions) { return env.describe(description, specDefinitions); }, /** * A temporarily disabled [`describe`]{@link describe} * * Specs within an `xdescribe` will be marked pending and not executed * @name xdescribe * @since 1.3.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ xdescribe: function(description, specDefinitions) { return env.xdescribe(description, specDefinitions); }, /** * A focused [`describe`]{@link describe} * * If suites or specs are focused, only those that are focused will be executed * @see fit * @name fdescribe * @since 2.1.0 * @function * @global * @param {String} description Textual description of the group * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and specs */ fdescribe: function(description, specDefinitions) { return env.fdescribe(description, specDefinitions); }, /** * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the state of the code. * * A spec whose expectations all succeed will be passing and a spec with any failures will fail. * The name `it` is a pronoun for the test target, not an abbreviation of anything. It makes the * spec more readable by connecting the function name `it` and the argument `description` as a * complete sentence. * @name it * @since 1.3.0 * @function * @global * @param {String} description Textual description of what this spec is checking * @param {implementationCallback} [testFunction] Function that contains the code of your test. If not provided the test will be `pending`. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. * @see async */ it: function() { return env.it.apply(env, arguments); }, /** * A temporarily disabled [`it`]{@link it} * * The spec will report as `pending` and will not be executed. * @name xit * @since 1.3.0 * @function * @global * @param {String} description Textual description of what this spec is checking. * @param {implementationCallback} [testFunction] Function that contains the code of your test. Will not be executed. */ xit: function() { return env.xit.apply(env, arguments); }, /** * A focused [`it`]{@link it} * * If suites or specs are focused, only those that are focused will be executed. * @name fit * @since 2.1.0 * @function * @global * @param {String} description Textual description of what this spec is checking. * @param {implementationCallback} testFunction Function that contains the code of your test. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. * @see async */ fit: function() { return env.fit.apply(env, arguments); }, /** * Run some shared setup before each of the specs in the {@link describe} in which it is called. * @name beforeEach * @since 1.3.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to setup your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeEach. * @see async */ beforeEach: function() { return env.beforeEach.apply(env, arguments); }, /** * Run some shared teardown after each of the specs in the {@link describe} in which it is called. * @name afterEach * @since 1.3.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to teardown your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach. * @see async */ afterEach: function() { return env.afterEach.apply(env, arguments); }, /** * Run some shared setup once before all of the specs in the {@link describe} are run. * * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. * @name beforeAll * @since 2.1.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to setup your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll. * @see async */ beforeAll: function() { return env.beforeAll.apply(env, arguments); }, /** * Run some shared teardown once after all of the specs in the {@link describe} are run. * * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. * @name afterAll * @since 2.1.0 * @function * @global * @param {implementationCallback} [function] Function that contains the code to teardown your specs. * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll. * @see async */ afterAll: function() { return env.afterAll.apply(env, arguments); }, /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult} * @name setSpecProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ setSpecProperty: function(key, value) { return env.setSpecProperty(key, value); }, /** * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SuiteResult} * @name setSuiteProperty * @since 3.6.0 * @function * @param {String} key The name of the property * @param {*} value The value of the property */ setSuiteProperty: function(key, value) { return env.setSuiteProperty(key, value); }, /** * Create an expectation for a spec. * @name expect * @since 1.3.0 * @function * @global * @param {Object} actual - Actual computed value to test expectations against. * @return {matchers} */ expect: function(actual) { return env.expect(actual); }, /** * Create an asynchronous expectation for a spec. Note that the matchers * that are provided by an asynchronous expectation all return promises * which must be either returned from the spec or waited for using `await` * in order for Jasmine to associate them with the correct spec. * @name expectAsync * @since 3.3.0 * @function * @global * @param {Object} actual - Actual computed value to test expectations against. * @return {async-matchers} * @example * await expectAsync(somePromise).toBeResolved(); * @example * return expectAsync(somePromise).toBeResolved(); */ expectAsync: function(actual) { return env.expectAsync(actual); }, /** * Mark a spec as pending, expectation results will be ignored. * @name pending * @since 2.0.0 * @function * @global * @param {String} [message] - Reason the spec is pending. */ pending: function() { return env.pending.apply(env, arguments); }, /** * Explicitly mark a spec as failed. * @name fail * @since 2.1.0 * @function * @global * @param {String|Error} [error] - Reason for the failure. */ fail: function() { return env.fail.apply(env, arguments); }, /** * Install a spy onto an existing object. * @name spyOn * @since 1.3.0 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy}. * @param {String} methodName - The name of the method to replace with a {@link Spy}. * @returns {Spy} */ spyOn: function(obj, methodName) { return env.spyOn(obj, methodName); }, /** * Install a spy on a property installed with `Object.defineProperty` onto an existing object. * @name spyOnProperty * @since 2.6.0 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy} * @param {String} propertyName - The name of the property to replace with a {@link Spy}. * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on. * @returns {Spy} */ spyOnProperty: function(obj, methodName, accessType) { return env.spyOnProperty(obj, methodName, accessType); }, /** * Installs spies on all writable and configurable properties of an object. * @name spyOnAllFunctions * @since 3.2.1 * @function * @global * @param {Object} obj - The object upon which to install the {@link Spy}s * @returns {Object} the spied object */ spyOnAllFunctions: function(obj) { return env.spyOnAllFunctions(obj); }, jsApiReporter: new jasmine.JsApiReporter({ timer: new jasmine.Timer() }), /** * @namespace jasmine */ jasmine: jasmine }; /** * Add a custom equality tester for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addCustomEqualityTester * @since 2.0.0 * @function * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or `false` comparison result if it knows how to compare them, and `undefined` otherwise. * @see custom_equality */ jasmine.addCustomEqualityTester = function(tester) { env.addCustomEqualityTester(tester); }; /** * Add custom matchers for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addMatchers * @since 2.0.0 * @function * @param {Object} matchers - Keys from this object will be the new matcher names. * @see custom_matcher */ jasmine.addMatchers = function(matchers) { return env.addMatchers(matchers); }; /** * Add custom async matchers for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addAsyncMatchers * @since 3.5.0 * @function * @param {Object} matchers - Keys from this object will be the new async matcher names. * @see custom_matcher */ jasmine.addAsyncMatchers = function(matchers) { return env.addAsyncMatchers(matchers); }; /** * Add a custom object formatter for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addCustomObjectFormatter * @since 3.6.0 * @function * @param {Function} formatter - A function which takes a value to format and returns a string if it knows how to format it, and `undefined` otherwise. * @see custom_object_formatters */ jasmine.addCustomObjectFormatter = function(formatter) { return env.addCustomObjectFormatter(formatter); }; /** * Get the currently booted mock {Clock} for this Jasmine environment. * @name jasmine.clock * @since 2.0.0 * @function * @returns {Clock} */ jasmine.clock = function() { return env.clock; }; /** * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. * @name jasmine.createSpy * @since 1.3.0 * @function * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. * @param {Function} [originalFn] - Function to act as the real implementation. * @return {Spy} */ jasmine.createSpy = function(name, originalFn) { return env.createSpy(name, originalFn); }; /** * Create an object with multiple {@link Spy}s as its members. * @name jasmine.createSpyObj * @since 1.3.0 * @function * @param {String} [baseName] - Base name for the spies in the object. * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. * @param {String[]|Object} [propertyNames] - Array of property names to create spies for, or Object whose keys will be propertynames and values the {@link Spy#and#returnValue|returnValue}. * @return {Object} */ jasmine.createSpyObj = function(baseName, methodNames, propertyNames) { return env.createSpyObj(baseName, methodNames, propertyNames); }; /** * Add a custom spy strategy for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.addSpyStrategy * @since 3.5.0 * @function * @param {String} name - The name of the strategy (i.e. what you call from `and`) * @param {Function} factory - Factory function that returns the plan to be executed. */ jasmine.addSpyStrategy = function(name, factory) { return env.addSpyStrategy(name, factory); }; /** * Set the default spy strategy for the current scope of specs. * * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. * @name jasmine.setDefaultSpyStrategy * @function * @param {Function} defaultStrategyFn - a function that assigns a strategy * @example * beforeEach(function() { * jasmine.setDefaultSpyStrategy(and => and.returnValue(true)); * }); */ jasmine.setDefaultSpyStrategy = function(defaultStrategyFn) { return env.setDefaultSpyStrategy(defaultStrategyFn); }; return jasmineInterface; }; getJasmineRequireObj().Spy = function(j$) { var nextOrder = (function() { var order = 0; return function() { return order++; }; })(); var matchersUtil = new j$.MatchersUtil({ customTesters: [], pp: j$.makePrettyPrinter() }); /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor * @name Spy */ function Spy( name, originalFn, customStrategies, defaultStrategyFn, getPromise ) { var numArgs = typeof originalFn === 'function' ? originalFn.length : 0, wrapper = makeFunc(numArgs, function(context, args, invokeNew) { return spy(context, args, invokeNew); }), strategyDispatcher = new SpyStrategyDispatcher({ name: name, fn: originalFn, getSpy: function() { return wrapper; }, customStrategies: customStrategies, getPromise: getPromise }), callTracker = new j$.CallTracker(), spy = function(context, args, invokeNew) { /** * @name Spy.callData * @property {object} object - `this` context for the invocation. * @property {number} invocationOrder - Order of the invocation. * @property {Array} args - The arguments passed for this invocation. */ var callData = { object: context, invocationOrder: nextOrder(), args: Array.prototype.slice.apply(args) }; callTracker.track(callData); var returnValue = strategyDispatcher.exec(context, args, invokeNew); callData.returnValue = returnValue; return returnValue; }; function makeFunc(length, fn) { switch (length) { case 1: return function wrap1(a) { return fn(this, arguments, this instanceof wrap1); }; case 2: return function wrap2(a, b) { return fn(this, arguments, this instanceof wrap2); }; case 3: return function wrap3(a, b, c) { return fn(this, arguments, this instanceof wrap3); }; case 4: return function wrap4(a, b, c, d) { return fn(this, arguments, this instanceof wrap4); }; case 5: return function wrap5(a, b, c, d, e) { return fn(this, arguments, this instanceof wrap5); }; case 6: return function wrap6(a, b, c, d, e, f) { return fn(this, arguments, this instanceof wrap6); }; case 7: return function wrap7(a, b, c, d, e, f, g) { return fn(this, arguments, this instanceof wrap7); }; case 8: return function wrap8(a, b, c, d, e, f, g, h) { return fn(this, arguments, this instanceof wrap8); }; case 9: return function wrap9(a, b, c, d, e, f, g, h, i) { return fn(this, arguments, this instanceof wrap9); }; default: return function wrap() { return fn(this, arguments, this instanceof wrap); }; } } for (var prop in originalFn) { if (prop === 'and' || prop === 'calls') { throw new Error( "Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon" ); } wrapper[prop] = originalFn[prop]; } /** * @member {SpyStrategy} - Accesses the default strategy for the spy. This strategy will be used * whenever the spy is called with arguments that don't match any strategy * created with {@link Spy#withArgs}. * @name Spy#and * @since 2.0.0 * @example * spyOn(someObj, 'func').and.returnValue(42); */ wrapper.and = strategyDispatcher.and; /** * Specifies a strategy to be used for calls to the spy that have the * specified arguments. * @name Spy#withArgs * @since 3.0.0 * @function * @param {...*} args - The arguments to match * @type {SpyStrategy} * @example * spyOn(someObj, 'func').withArgs(1, 2, 3).and.returnValue(42); * someObj.func(1, 2, 3); // returns 42 */ wrapper.withArgs = function() { return strategyDispatcher.withArgs.apply(strategyDispatcher, arguments); }; wrapper.calls = callTracker; if (defaultStrategyFn) { defaultStrategyFn(wrapper.and); } return wrapper; } function SpyStrategyDispatcher(strategyArgs) { var baseStrategy = new j$.SpyStrategy(strategyArgs); var argsStrategies = new StrategyDict(function() { return new j$.SpyStrategy(strategyArgs); }); this.and = baseStrategy; this.exec = function(spy, args, invokeNew) { var strategy = argsStrategies.get(args); if (!strategy) { if (argsStrategies.any() && !baseStrategy.isConfigured()) { throw new Error( "Spy '" + strategyArgs.name + "' received a call with arguments " + j$.pp(Array.prototype.slice.call(args)) + ' but all configured strategies specify other arguments.' ); } else { strategy = baseStrategy; } } return strategy.exec(spy, args, invokeNew); }; this.withArgs = function() { return { and: argsStrategies.getOrCreate(arguments) }; }; } function StrategyDict(strategyFactory) { this.strategies = []; this.strategyFactory = strategyFactory; } StrategyDict.prototype.any = function() { return this.strategies.length > 0; }; StrategyDict.prototype.getOrCreate = function(args) { var strategy = this.get(args); if (!strategy) { strategy = this.strategyFactory(); this.strategies.push({ args: args, strategy: strategy }); } return strategy; }; StrategyDict.prototype.get = function(args) { var i; for (i = 0; i < this.strategies.length; i++) { if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } }; return Spy; }; getJasmineRequireObj().SpyFactory = function(j$) { function SpyFactory(getCustomStrategies, getDefaultStrategyFn, getPromise) { var self = this; this.createSpy = function(name, originalFn) { return j$.Spy( name, originalFn, getCustomStrategies(), getDefaultStrategyFn(), getPromise ); }; this.createSpyObj = function(baseName, methodNames, propertyNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); if (baseNameIsCollection) { propertyNames = methodNames; methodNames = baseName; baseName = 'unknown'; } var obj = {}; var spy, descriptor; var methods = normalizeKeyValues(methodNames); for (var i = 0; i < methods.length; i++) { spy = obj[methods[i][0]] = self.createSpy( baseName + '.' + methods[i][0] ); if (methods[i].length > 1) { spy.and.returnValue(methods[i][1]); } } var properties = normalizeKeyValues(propertyNames); for (var i = 0; i < properties.length; i++) { descriptor = { get: self.createSpy(baseName + '.' + properties[i][0] + '.get'), set: self.createSpy(baseName + '.' + properties[i][0] + '.set') }; if (properties[i].length > 1) { descriptor.get.and.returnValue(properties[i][1]); descriptor.set.and.returnValue(properties[i][1]); } Object.defineProperty(obj, properties[i][0], descriptor); } if (methods.length === 0 && properties.length === 0) { throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; } return obj; }; } function normalizeKeyValues(object) { var result = []; if (j$.isArray_(object)) { for (var i = 0; i < object.length; i++) { result.push([object[i]]); } } else if (j$.isObject_(object)) { for (var key in object) { if (object.hasOwnProperty(key)) { result.push([key, object[key]]); } } } return result; } return SpyFactory; }; getJasmineRequireObj().SpyRegistry = function(j$) { var spyOnMsg = j$.formatErrorMsg('', 'spyOn(, )'); var spyOnPropertyMsg = j$.formatErrorMsg( '', 'spyOnProperty(, , [accessType])' ); function SpyRegistry(options) { options = options || {}; var global = options.global || j$.getGlobal(); var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow) { this.respy = allow; }; this.spyOn = function(obj, methodName) { var getErrorMsg = spyOnMsg; if (j$.util.isUndefined(obj) || obj === null) { throw new Error( getErrorMsg( 'could not find an object to spy upon for ' + methodName + '()' ) ); } if (j$.util.isUndefined(methodName) || methodName === null) { throw new Error(getErrorMsg('No method name supplied')); } if (j$.util.isUndefined(obj[methodName])) { throw new Error(getErrorMsg(methodName + '() method does not exist')); } if (obj[methodName] && j$.isSpy(obj[methodName])) { if (this.respy) { return obj[methodName]; } else { throw new Error( getErrorMsg(methodName + ' has already been spied upon') ); } } var descriptor = Object.getOwnPropertyDescriptor(obj, methodName); if (descriptor && !(descriptor.writable || descriptor.set)) { throw new Error( getErrorMsg(methodName + ' is not declared writable or has no setter') ); } var originalMethod = obj[methodName], spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if ( Object.prototype.hasOwnProperty.call(obj, methodName) || (obj === global && methodName === 'onerror') ) { restoreStrategy = function() { obj[methodName] = originalMethod; }; } else { restoreStrategy = function() { if (!delete obj[methodName]) { obj[methodName] = originalMethod; } }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); obj[methodName] = spiedMethod; return spiedMethod; }; this.spyOnProperty = function(obj, propertyName, accessType) { var getErrorMsg = spyOnPropertyMsg; accessType = accessType || 'get'; if (j$.util.isUndefined(obj)) { throw new Error( getErrorMsg( 'spyOn could not find an object to spy upon for ' + propertyName + '' ) ); } if (j$.util.isUndefined(propertyName)) { throw new Error(getErrorMsg('No property name supplied')); } var descriptor = j$.util.getPropertyDescriptor(obj, propertyName); if (!descriptor) { throw new Error(getErrorMsg(propertyName + ' property does not exist')); } if (!descriptor.configurable) { throw new Error( getErrorMsg(propertyName + ' is not declared configurable') ); } if (!descriptor[accessType]) { throw new Error( getErrorMsg( 'Property ' + propertyName + ' does not have access type ' + accessType ) ); } if (j$.isSpy(descriptor[accessType])) { if (this.respy) { return descriptor[accessType]; } else { throw new Error( getErrorMsg( propertyName + '#' + accessType + ' has already been spied upon' ) ); } } var originalDescriptor = j$.util.clone(descriptor), spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { restoreStrategy = function() { Object.defineProperty(obj, propertyName, originalDescriptor); }; } else { restoreStrategy = function() { delete obj[propertyName]; }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); descriptor[accessType] = spy; Object.defineProperty(obj, propertyName, descriptor); return spy; }; this.spyOnAllFunctions = function(obj) { if (j$.util.isUndefined(obj)) { throw new Error( 'spyOnAllFunctions could not find an object to spy upon' ); } var pointer = obj, props = [], prop, descriptor; while (pointer) { for (prop in pointer) { if ( Object.prototype.hasOwnProperty.call(pointer, prop) && pointer[prop] instanceof Function ) { descriptor = Object.getOwnPropertyDescriptor(pointer, prop); if ( (descriptor.writable || descriptor.set) && descriptor.configurable ) { props.push(prop); } } } pointer = Object.getPrototypeOf(pointer); } for (var i = 0; i < props.length; i++) { this.spyOn(obj, props[i]); } return obj; }; this.clearSpies = function() { var spies = currentSpies(); for (var i = spies.length - 1; i >= 0; i--) { var spyEntry = spies[i]; spyEntry.restoreObjectToOriginalState(); } }; } return SpyRegistry; }; getJasmineRequireObj().SpyStrategy = function(j$) { /** * @interface SpyStrategy */ function SpyStrategy(options) { options = options || {}; var self = this; /** * Get the identifying information for the spy. * @name SpyStrategy#identity * @since 3.0.0 * @member * @type {String} */ this.identity = options.name || 'unknown'; this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; var k, cs = options.customStrategies || {}; for (k in cs) { if (j$.util.has(cs, k) && !this[k]) { this[k] = createCustomPlan(cs[k]); } } var getPromise = typeof options.getPromise === 'function' ? options.getPromise : function() {}; var requirePromise = function(name) { var Promise = getPromise(); if (!Promise) { throw new Error( name + ' requires global Promise, or `Promise` configured with `jasmine.getEnv().configure()`' ); } return Promise; }; /** * Tell the spy to return a promise resolving to the specified value when invoked. * @name SpyStrategy#resolveTo * @since 3.5.0 * @function * @param {*} value The value to return. */ this.resolveTo = function(value) { var Promise = requirePromise('resolveTo'); self.plan = function() { return Promise.resolve(value); }; return self.getSpy(); }; /** * Tell the spy to return a promise rejecting with the specified value when invoked. * @name SpyStrategy#rejectWith * @since 3.5.0 * @function * @param {*} value The value to return. */ this.rejectWith = function(value) { var Promise = requirePromise('rejectWith'); self.plan = function() { return Promise.reject(value); }; return self.getSpy(); }; } function createCustomPlan(factory) { return function() { var plan = factory.apply(null, arguments); if (!j$.isFunction_(plan)) { throw new Error('Spy strategy must return a function'); } this.plan = plan; return this.getSpy(); }; } /** * Execute the current spy strategy. * @name SpyStrategy#exec * @since 2.0.0 * @function */ SpyStrategy.prototype.exec = function(context, args, invokeNew) { var contextArgs = [context].concat( args ? Array.prototype.slice.call(args) : [] ); var target = this.plan.bind.apply(this.plan, contextArgs); return invokeNew ? new target() : target(); }; /** * Tell the spy to call through to the real implementation when invoked. * @name SpyStrategy#callThrough * @since 2.0.0 * @function */ SpyStrategy.prototype.callThrough = function() { this.plan = this.originalFn; return this.getSpy(); }; /** * Tell the spy to return the value when invoked. * @name SpyStrategy#returnValue * @since 2.0.0 * @function * @param {*} value The value to return. */ SpyStrategy.prototype.returnValue = function(value) { this.plan = function() { return value; }; return this.getSpy(); }; /** * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. * @name SpyStrategy#returnValues * @since 2.1.0 * @function * @param {...*} values - Values to be returned on subsequent calls to the spy. */ SpyStrategy.prototype.returnValues = function() { var values = Array.prototype.slice.call(arguments); this.plan = function() { return values.shift(); }; return this.getSpy(); }; /** * Tell the spy to throw an error when invoked. * @name SpyStrategy#throwError * @since 2.0.0 * @function * @param {Error|Object|String} something Thing to throw */ SpyStrategy.prototype.throwError = function(something) { var error = j$.isString_(something) ? new Error(something) : something; this.plan = function() { throw error; }; return this.getSpy(); }; /** * Tell the spy to call a fake implementation when invoked. * @name SpyStrategy#callFake * @since 2.0.0 * @function * @param {Function} fn The function to invoke with the passed parameters. */ SpyStrategy.prototype.callFake = function(fn) { if (!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { throw new Error( 'Argument passed to callFake should be a function, got ' + fn ); } this.plan = fn; return this.getSpy(); }; /** * Tell the spy to do nothing when invoked. This is the default. * @name SpyStrategy#stub * @since 2.0.0 * @function */ SpyStrategy.prototype.stub = function(fn) { this.plan = function() {}; return this.getSpy(); }; SpyStrategy.prototype.isConfigured = function() { return this.plan !== this._defaultPlan; }; return SpyStrategy; }; getJasmineRequireObj().StackTrace = function(j$) { function StackTrace(error) { var lines = error.stack.split('\n').filter(function(line) { return line !== ''; }); var extractResult = extractMessage(error.message, lines); if (extractResult) { this.message = extractResult.message; lines = extractResult.remainder; } var parseResult = tryParseFrames(lines); this.frames = parseResult.frames; this.style = parseResult.style; } var framePatterns = [ // PhantomJS on Linux, Node, Chrome, IE, Edge // e.g. " at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)" // Note that the "function name" can include a surprisingly large set of // characters, including angle brackets and square brackets. { re: /^\s*at ([^\)]+) \(([^\)]+)\)$/, fnIx: 1, fileLineColIx: 2, style: 'v8' }, // NodeJS alternate form, often mixed in with the Chrome style // e.g. " at /some/path:4320:20 { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, // PhantomJS on OS X, Safari, Firefox // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" { re: /^(([^@\s]+)@)?([^\s]+)$/, fnIx: 2, fileLineColIx: 3, style: 'webkit' } ]; // regexes should capture the function name (if any) as group 1 // and the file, line, and column as group 2. function tryParseFrames(lines) { var style = null; var frames = lines.map(function(line) { var convertedLine = first(framePatterns, function(pattern) { var overallMatch = line.match(pattern.re), fileLineColMatch; if (!overallMatch) { return null; } fileLineColMatch = overallMatch[pattern.fileLineColIx].match( /^(.*):(\d+):\d+$/ ); if (!fileLineColMatch) { return null; } style = style || pattern.style; return { raw: line, file: fileLineColMatch[1], line: parseInt(fileLineColMatch[2], 10), func: overallMatch[pattern.fnIx] }; }); return convertedLine || { raw: line }; }); return { style: style, frames: frames }; } function first(items, fn) { var i, result; for (i = 0; i < items.length; i++) { result = fn(items[i]); if (result) { return result; } } } function extractMessage(message, stackLines) { var len = messagePrefixLength(message, stackLines); if (len > 0) { return { message: stackLines.slice(0, len).join('\n'), remainder: stackLines.slice(len) }; } } function messagePrefixLength(message, stackLines) { if (!stackLines[0].match(/^\w*Error/)) { return 0; } var messageLines = message.split('\n'); var i; for (i = 1; i < messageLines.length; i++) { if (messageLines[i] !== stackLines[i]) { return 0; } } return messageLines.length; } return StackTrace; }; getJasmineRequireObj().Suite = function(j$) { function Suite(attrs) { this.env = attrs.env; this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.expectationResultFactory = attrs.expectationResultFactory; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.beforeFns = []; this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; this.timer = attrs.timer || new j$.Timer(); this.children = []; /** * @typedef SuiteResult * @property {Int} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. * @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach. * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty} */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), failedExpectations: [], deprecationWarnings: [], duration: null, properties: null }; } Suite.prototype.setSuiteProperty = function(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; }; Suite.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; Suite.prototype.expectAsync = function(actual) { return this.asyncExpectationFactory(actual, this); }; Suite.prototype.getFullName = function() { var fullName = []; for ( var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite ) { if (parentSuite.parentSuite) { fullName.unshift(parentSuite.description); } } return fullName.join(' '); }; Suite.prototype.pend = function() { this.markedPending = true; }; Suite.prototype.beforeEach = function(fn) { this.beforeFns.unshift(fn); }; Suite.prototype.beforeAll = function(fn) { this.beforeAllFns.push(fn); }; Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; Suite.prototype.afterAll = function(fn) { this.afterAllFns.unshift(fn); }; Suite.prototype.startTimer = function() { this.timer.start(); }; Suite.prototype.endTimer = function() { this.result.duration = this.timer.elapsed(); }; function removeFns(queueableFns) { for (var i = 0; i < queueableFns.length; i++) { queueableFns[i].fn = null; } } Suite.prototype.cleanupBeforeAfter = function() { removeFns(this.beforeAllFns); removeFns(this.afterAllFns); removeFns(this.beforeFns); removeFns(this.afterFns); }; Suite.prototype.addChild = function(child) { this.children.push(child); }; Suite.prototype.status = function() { if (this.markedPending) { return 'pending'; } if (this.result.failedExpectations.length > 0) { return 'failed'; } else { return 'passed'; } }; Suite.prototype.canBeReentered = function() { return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; }; Suite.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Suite.prototype.sharedUserContext = function() { if (!this.sharedContext) { this.sharedContext = this.parentSuite ? this.parentSuite.clonedSharedUserContext() : new j$.UserContext(); } return this.sharedContext; }; Suite.prototype.clonedSharedUserContext = function() { return j$.UserContext.fromExisting(this.sharedUserContext()); }; Suite.prototype.onException = function() { if (arguments[0] instanceof j$.errors.ExpectationFailed) { return; } var data = { matcherName: '', passed: false, expected: '', actual: '', error: arguments[0] }; var failedExpectation = this.expectationResultFactory(data); if (!this.parentSuite) { failedExpectation.globalErrorType = 'afterAll'; } this.result.failedExpectations.push(failedExpectation); }; Suite.prototype.addExpectationResult = function() { if (isFailure(arguments)) { var data = arguments[1]; this.result.failedExpectations.push(this.expectationResultFactory(data)); if (this.throwOnExpectationFailure) { throw new j$.errors.ExpectationFailed(); } } }; Suite.prototype.addDeprecationWarning = function(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( this.expectationResultFactory(deprecation) ); }; function isFailure(args) { return !args[0]; } return Suite; }; if (typeof window == void 0 && typeof exports == 'object') { /* globals exports */ exports.Suite = jasmineRequire.Suite; } getJasmineRequireObj().Timer = function() { var defaultNow = (function(Date) { return function() { return new Date().getTime(); }; })(Date); function Timer(options) { options = options || {}; var now = options.now || defaultNow, startTime; this.start = function() { startTime = now(); }; this.elapsed = function() { return now() - startTime; }; } return Timer; }; getJasmineRequireObj().TreeProcessor = function() { function TreeProcessor(attrs) { var tree = attrs.tree, runnableIds = attrs.runnableIds, queueRunnerFactory = attrs.queueRunnerFactory, nodeStart = attrs.nodeStart || function() {}, nodeComplete = attrs.nodeComplete || function() {}, failSpecWithNoExpectations = !!attrs.failSpecWithNoExpectations, orderChildren = attrs.orderChildren || function(node) { return node.children; }, excludeNode = attrs.excludeNode || function(node) { return false; }, stats = { valid: true }, processed = false, defaultMin = Infinity, defaultMax = 1 - Infinity; this.processTree = function() { processNode(tree, true); processed = true; return stats; }; this.execute = function(done) { if (!processed) { this.processTree(); } if (!stats.valid) { throw 'invalid order'; } var childFns = wrapChildren(tree, 0); queueRunnerFactory({ queueableFns: childFns, userContext: tree.sharedUserContext(), onException: function() { tree.onException.apply(tree, arguments); }, onComplete: done }); }; function runnableIndex(id) { for (var i = 0; i < runnableIds.length; i++) { if (runnableIds[i] === id) { return i; } } } function processNode(node, parentExcluded) { var executableIndex = runnableIndex(node.id); if (executableIndex !== undefined) { parentExcluded = false; } if (!node.children) { var excluded = parentExcluded || excludeNode(node); stats[node.id] = { excluded: excluded, willExecute: !excluded && !node.markedPending, segments: [ { index: 0, owner: node, nodes: [node], min: startingMin(executableIndex), max: startingMax(executableIndex) } ] }; } else { var hasExecutableChild = false; var orderedChildren = orderChildren(node); for (var i = 0; i < orderedChildren.length; i++) { var child = orderedChildren[i]; processNode(child, parentExcluded); if (!stats.valid) { return; } var childStats = stats[child.id]; hasExecutableChild = hasExecutableChild || childStats.willExecute; } stats[node.id] = { excluded: parentExcluded, willExecute: hasExecutableChild }; segmentChildren(node, orderedChildren, stats[node.id], executableIndex); if (!node.canBeReentered() && stats[node.id].segments.length > 1) { stats = { valid: false }; } } } function startingMin(executableIndex) { return executableIndex === undefined ? defaultMin : executableIndex; } function startingMax(executableIndex) { return executableIndex === undefined ? defaultMax : executableIndex; } function segmentChildren( node, orderedChildren, nodeStats, executableIndex ) { var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, result = [currentSegment], lastMax = defaultMax, orderedChildSegments = orderChildSegments(orderedChildren); function isSegmentBoundary(minIndex) { return ( lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1 ); } for (var i = 0; i < orderedChildSegments.length; i++) { var childSegment = orderedChildSegments[i], maxIndex = childSegment.max, minIndex = childSegment.min; if (isSegmentBoundary(minIndex)) { currentSegment = { index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax }; result.push(currentSegment); } currentSegment.nodes.push(childSegment); currentSegment.min = Math.min(currentSegment.min, minIndex); currentSegment.max = Math.max(currentSegment.max, maxIndex); lastMax = maxIndex; } nodeStats.segments = result; } function orderChildSegments(children) { var specifiedOrder = [], unspecifiedOrder = []; for (var i = 0; i < children.length; i++) { var child = children[i], segments = stats[child.id].segments; for (var j = 0; j < segments.length; j++) { var seg = segments[j]; if (seg.min === defaultMin) { unspecifiedOrder.push(seg); } else { specifiedOrder.push(seg); } } } specifiedOrder.sort(function(a, b) { return a.min - b.min; }); return specifiedOrder.concat(unspecifiedOrder); } function executeNode(node, segmentNumber) { if (node.children) { return { fn: function(done) { var onStart = { fn: function(next) { nodeStart(node, next); } }; queueRunnerFactory({ onComplete: function() { var args = Array.prototype.slice.call(arguments, [0]); node.cleanupBeforeAfter(); nodeComplete(node, node.getResult(), function() { done.apply(undefined, args); }); }, queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)), userContext: node.sharedUserContext(), onException: function() { node.onException.apply(node, arguments); } }); } }; } else { return { fn: function(done) { node.execute( done, stats[node.id].excluded, failSpecWithNoExpectations ); } }; } } function wrapChildren(node, segmentNumber) { var result = [], segmentChildren = stats[node.id].segments[segmentNumber].nodes; for (var i = 0; i < segmentChildren.length; i++) { result.push( executeNode(segmentChildren[i].owner, segmentChildren[i].index) ); } if (!stats[node.id].willExecute) { return result; } return node.beforeAllFns.concat(result).concat(node.afterAllFns); } } return TreeProcessor; }; getJasmineRequireObj().UserContext = function(j$) { function UserContext() {} UserContext.fromExisting = function(oldContext) { var context = new UserContext(); for (var prop in oldContext) { if (oldContext.hasOwnProperty(prop)) { context[prop] = oldContext[prop]; } } return context; }; return UserContext; }; getJasmineRequireObj().version = function() { return '3.6.0'; };cjs-128.1/installed-tests/js/jsunit.gresources.xml0000664000175000017500000000344415116312211021177 0ustar fabiofabio complex3.ui complex4.ui jasmine.js minijasmine-executor.js minijasmine.js modules/alwaysThrows.js modules/badOverrides/GIMarshallingTests.js modules/badOverrides/Gio.js modules/badOverrides/Regress.js modules/badOverrides/WarnLib.js modules/badOverrides2/GIMarshallingTests.js modules/badOverrides2/Gio.js modules/badOverrides2/Regress.js modules/badOverrides2/WarnLib.js modules/data.txt modules/dynamic.js modules/encodings.json modules/exports.js modules/foobar.js modules/greet.js modules/importmeta.js modules/lexicalScope.js modules/modunicode.js modules/mutualImport/a.js modules/mutualImport/b.js modules/overrides/GIMarshallingTests.js modules/say.js modules/sideEffect.js modules/sideEffect2.js modules/sideEffect3.js modules/sideEffect4.js modules/subA/subB/__init__.js modules/subA/subB/baz.js modules/subA/subB/foobar.js modules/subBadInit/__init__.js modules/subErrorInit/__init__.js cjs-128.1/installed-tests/js/libgjstesttools/0000775000175000017500000000000015116312211020207 5ustar fabiofabiocjs-128.1/installed-tests/js/libgjstesttools/gjs-test-tools.cpp0000664000175000017500000002512615116312211023617 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Marco Trevisan #include "installed-tests/js/libgjstesttools/gjs-test-tools.h" #include #include #include "cjs/jsapi-util.h" #ifdef G_OS_UNIX # include # include /* for FD_CLOEXEC */ # include # include /* for close, write */ # include /* for g_unix_open_pipe */ #endif static std::atomic s_tmp_object = nullptr; static GWeakRef s_tmp_weak; static std::unordered_set s_finalized_objects; static std::mutex s_finalized_objects_lock; struct FinalizedObjectsLocked { FinalizedObjectsLocked() : hold(s_finalized_objects_lock) {} std::unordered_set* operator->() { return &s_finalized_objects; } std::lock_guard hold; }; void gjs_test_tools_init() {} void gjs_test_tools_reset() { gjs_test_tools_clear_saved(); g_weak_ref_set(&s_tmp_weak, nullptr); FinalizedObjectsLocked()->clear(); } void gjs_test_tools_ref(GObject* object) { g_object_ref(object); } void gjs_test_tools_unref(GObject* object) { g_object_unref(object); } // clang-format off static G_DEFINE_QUARK(gjs-test-utils::finalize, finalize); // clang-format on static void monitor_object_finalization(GObject* object) { g_object_steal_qdata(object, finalize_quark()); g_object_set_qdata_full(object, finalize_quark(), object, [](void* data) { FinalizedObjectsLocked()->insert(static_cast(data)); }); } void gjs_test_tools_delayed_ref(GObject* object, int interval) { g_timeout_add( interval, [](void *data) { g_object_ref(G_OBJECT(data)); return G_SOURCE_REMOVE; }, object); } void gjs_test_tools_delayed_unref(GObject* object, int interval) { g_timeout_add( interval, [](void *data) { g_object_unref(G_OBJECT(data)); return G_SOURCE_REMOVE; }, object); } void gjs_test_tools_delayed_dispose(GObject* object, int interval) { g_timeout_add( interval, [](void *data) { g_object_run_dispose(G_OBJECT(data)); return G_SOURCE_REMOVE; }, object); } void gjs_test_tools_save_object(GObject* object) { g_object_ref(object); gjs_test_tools_save_object_unreffed(object); } void gjs_test_tools_save_object_unreffed(GObject* object) { GObject* expected = nullptr; g_assert_true(s_tmp_object.compare_exchange_strong(expected, object)); } void gjs_test_tools_clear_saved() { if (!FinalizedObjectsLocked()->count(s_tmp_object)) { auto* object = s_tmp_object.exchange(nullptr); g_clear_object(&object); } else { s_tmp_object = nullptr; } } void gjs_test_tools_ref_other_thread(GObject* object, GError** error) { auto* thread = g_thread_try_new("ref_object", g_object_ref, object, error); if (thread) g_thread_join(thread); // cppcheck-suppress memleak } typedef enum { REF = 1 << 0, UNREF = 1 << 1, } RefType; typedef struct { GObject* object; RefType ref_type; int delay; } RefThreadData; static RefThreadData* ref_thread_data_new(GObject* object, int interval, RefType ref_type) { auto* ref_data = g_new(RefThreadData, 1); ref_data->object = object; ref_data->delay = interval; ref_data->ref_type = ref_type; monitor_object_finalization(object); return ref_data; } static void* ref_thread_func(void* data) { GjsAutoPointer ref_data = static_cast(data); if (FinalizedObjectsLocked()->count(ref_data->object)) return nullptr; if (ref_data->delay > 0) g_usleep(ref_data->delay); if (FinalizedObjectsLocked()->count(ref_data->object)) return nullptr; if (ref_data->ref_type & REF) g_object_ref(ref_data->object); if (!(ref_data->ref_type & UNREF)) { return ref_data->object; } else if (ref_data->ref_type & REF) { g_usleep(ref_data->delay); if (FinalizedObjectsLocked()->count(ref_data->object)) return nullptr; } if (ref_data->object != s_tmp_object) g_object_steal_qdata(ref_data->object, finalize_quark()); g_object_unref(ref_data->object); return nullptr; } void gjs_test_tools_unref_other_thread(GObject* object, GError** error) { auto* thread = g_thread_try_new("unref_object", ref_thread_func, ref_thread_data_new(object, -1, UNREF), error); if (thread) g_thread_join(thread); // cppcheck-suppress memleak } /** * gjs_test_tools_delayed_ref_other_thread: * Returns: (transfer full) */ GThread* gjs_test_tools_delayed_ref_other_thread(GObject* object, int interval, GError** error) { return g_thread_try_new("ref_object", ref_thread_func, ref_thread_data_new(object, interval, REF), error); } /** * gjs_test_tools_delayed_unref_other_thread: * Returns: (transfer full) */ GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object, int interval, GError** error) { return g_thread_try_new("unref_object", ref_thread_func, ref_thread_data_new(object, interval, UNREF), error); } /** * gjs_test_tools_delayed_ref_unref_other_thread: * Returns: (transfer full) */ GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object, int interval, GError** error) { return g_thread_try_new( "ref_unref_object", ref_thread_func, ref_thread_data_new(object, interval, static_cast(REF | UNREF)), error); } void gjs_test_tools_run_dispose_other_thread(GObject* object, GError** error) { auto* thread = g_thread_try_new( "run_dispose", [](void* object) -> void* { g_object_run_dispose(G_OBJECT(object)); return nullptr; }, object, error); // cppcheck-suppress leakNoVarFunctionCall g_thread_join(thread); // cppcheck-suppress memleak } /** * gjs_test_tools_get_saved: * Returns: (transfer full) */ GObject* gjs_test_tools_get_saved() { if (FinalizedObjectsLocked()->count(s_tmp_object)) s_tmp_object = nullptr; return s_tmp_object.exchange(nullptr); } /** * gjs_test_tools_steal_saved: * Returns: (transfer none) */ GObject* gjs_test_tools_steal_saved() { return gjs_test_tools_get_saved(); } void gjs_test_tools_save_weak(GObject* object) { g_weak_ref_set(&s_tmp_weak, object); } /** * gjs_test_tools_peek_saved: * Returns: (transfer none) */ GObject* gjs_test_tools_peek_saved() { if (FinalizedObjectsLocked()->count(s_tmp_object)) return nullptr; return s_tmp_object; } int gjs_test_tools_get_saved_ref_count() { GObject* saved = gjs_test_tools_peek_saved(); return saved ? saved->ref_count : 0; } /** * gjs_test_tools_get_weak: * Returns: (transfer full) */ GObject* gjs_test_tools_get_weak() { return static_cast(g_weak_ref_get(&s_tmp_weak)); } /** * gjs_test_tools_get_weak_other_thread: * Returns: (transfer full) */ GObject* gjs_test_tools_get_weak_other_thread(GError** error) { auto* thread = g_thread_try_new( "weak_get", [](void*) -> void* { return gjs_test_tools_get_weak(); }, NULL, error); if (!thread) return nullptr; return static_cast( // cppcheck-suppress leakNoVarFunctionCall g_thread_join(thread)); } /** * gjs_test_tools_get_disposed: * Returns: (transfer none) */ GObject* gjs_test_tools_get_disposed(GObject* object) { g_object_run_dispose(G_OBJECT(object)); return object; } #ifdef G_OS_UNIX // Adapted from glnx_throw_errno_prefix() static gboolean throw_errno_prefix(GError** error, const char* prefix) { int errsv = errno; g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errsv), g_strerror(errsv)); g_prefix_error(error, "%s: ", prefix); errno = errsv; return FALSE; } #endif /* G_OS_UNIX */ /** * gjs_open_bytes: * @bytes: bytes to send to the pipe * @error: Return location for a #GError, or %NULL * * Creates a pipe and sends @bytes to it, such that it is suitable for passing * to g_subprocess_launcher_take_fd(). * * Returns: file descriptor, or -1 on error */ int gjs_test_tools_open_bytes(GBytes* bytes, GError** error) { int pipefd[2], result; size_t count; const void* buf; ssize_t bytes_written; g_return_val_if_fail(bytes, -1); g_return_val_if_fail(error == NULL || *error == NULL, -1); #ifdef G_OS_UNIX if (!g_unix_open_pipe(pipefd, FD_CLOEXEC, error)) return -1; buf = g_bytes_get_data(bytes, &count); bytes_written = write(pipefd[1], buf, count); if (bytes_written < 0) { throw_errno_prefix(error, "write"); return -1; } if ((size_t)bytes_written != count) g_warning("%s: %zu bytes sent, only %zd bytes written", __func__, count, bytes_written); result = close(pipefd[1]); if (result == -1) { throw_errno_prefix(error, "close"); return -1; } return pipefd[0]; #else g_error("%s is currently supported on UNIX only", __func__); #endif } /** * gjs_test_tools_new_unaligned_bytes: * @len: Length of buffer to allocate * * Creates a data buffer at a location 1 byte away from an 8-byte alignment * boundary, to make sure that tests fail when SpiderMonkey enforces an * alignment restriction on embedder data. * * The buffer is filled with repeated 0x00-0x07 bytes containing the least * significant 3 bits of that byte's address. * * Returns: (transfer full): a #GBytes */ GBytes* gjs_test_tools_new_unaligned_bytes(size_t len) { auto* buffer = static_cast(g_aligned_alloc0(1, len + 1, 8)); for (size_t ix = 0; ix < len + 1; ix++) { buffer[ix] = reinterpret_cast(buffer + ix) & 0x07; } return g_bytes_new_with_free_func(buffer + 1, len, g_aligned_free, buffer); } alignas(8) static const char static_bytes[] = "hello"; /** * gjs_test_tools_new_static_bytes: * * Returns a buffer that lives in static storage. * * Returns: (transfer full): a #GBytes */ GBytes* gjs_test_tools_new_static_bytes() { return g_bytes_new_static(static_bytes, 6); } cjs-128.1/installed-tests/js/libgjstesttools/gjs-test-tools.h0000664000175000017500000000531215116312211023257 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Marco Trevisan #pragma once #ifndef GJS_TEST_TOOL_EXTERN # define GJS_TEST_TOOL_EXTERN #endif #include #include #include G_BEGIN_DECLS GJS_TEST_TOOL_EXTERN void gjs_test_tools_init(void); GJS_TEST_TOOL_EXTERN void gjs_test_tools_reset(void); GJS_TEST_TOOL_EXTERN void gjs_test_tools_ref(GObject* object); GJS_TEST_TOOL_EXTERN void gjs_test_tools_unref(GObject* object); GJS_TEST_TOOL_EXTERN void gjs_test_tools_delayed_ref(GObject* object, int interval); GJS_TEST_TOOL_EXTERN void gjs_test_tools_delayed_unref(GObject* object, int interval); GJS_TEST_TOOL_EXTERN void gjs_test_tools_delayed_dispose(GObject* object, int interval); GJS_TEST_TOOL_EXTERN void gjs_test_tools_ref_other_thread(GObject* object, GError** error); GJS_TEST_TOOL_EXTERN GThread* gjs_test_tools_delayed_ref_other_thread(GObject* object, int interval, GError** error); GJS_TEST_TOOL_EXTERN void gjs_test_tools_unref_other_thread(GObject* object, GError** error); GJS_TEST_TOOL_EXTERN GThread* gjs_test_tools_delayed_unref_other_thread(GObject* object, int interval, GError** error); GJS_TEST_TOOL_EXTERN GThread* gjs_test_tools_delayed_ref_unref_other_thread(GObject* object, int interval, GError** error); GJS_TEST_TOOL_EXTERN void gjs_test_tools_run_dispose_other_thread(GObject* object, GError** error); GJS_TEST_TOOL_EXTERN void gjs_test_tools_save_object(GObject* object); GJS_TEST_TOOL_EXTERN void gjs_test_tools_save_object_unreffed(GObject* object); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_get_saved(); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_steal_saved(); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_peek_saved(); GJS_TEST_TOOL_EXTERN int gjs_test_tools_get_saved_ref_count(); GJS_TEST_TOOL_EXTERN void gjs_test_tools_clear_saved(); GJS_TEST_TOOL_EXTERN void gjs_test_tools_save_weak(GObject* object); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_get_weak(); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_get_weak_other_thread(GError** error); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_get_disposed(GObject* object); GJS_TEST_TOOL_EXTERN int gjs_test_tools_open_bytes(GBytes* bytes, GError** error); GJS_TEST_TOOL_EXTERN GBytes* gjs_test_tools_new_unaligned_bytes(size_t len); GJS_TEST_TOOL_EXTERN GBytes* gjs_test_tools_new_static_bytes(); G_END_DECLS cjs-128.1/installed-tests/js/libgjstesttools/meson.build0000664000175000017500000000240415116312211022351 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Marco Trevisan libgjstesttools_extra_cpp_args = [] if cc.get_argument_syntax() == 'msvc' # We need to ensure the symbols in the test DLLs export in clang-cl builds libgjstesttools_extra_cpp_args += ['-DGJS_TEST_TOOL_EXTERN=__declspec(dllexport)extern'] endif gjstest_tools_sources = [ 'gjs-test-tools.cpp', 'gjs-test-tools.h', ] libgjstesttools = library('gjstesttools', sources: gjstest_tools_sources, include_directories: top_include, dependencies: libcjs_dep, cpp_args: libcjs_cpp_args + libgjstesttools_extra_cpp_args, install: get_option('installed_tests'), install_dir: installed_tests_execdir) gjstest_tools_gir = gnome.generate_gir(libgjstesttools, includes: ['GObject-2.0', 'Gio-2.0'], sources: gjstest_tools_sources, namespace: 'CjsTestTools', nsversion: '1.0', symbol_prefix: 'gjs_test_tools_', fatal_warnings: get_option('werror'), install: get_option('installed_tests'), install_gir: false, install_dir_typelib: installed_tests_execdir) gjstest_tools_typelib = gjstest_tools_gir[1] libgjstesttools_dep = declare_dependency( link_with: libgjstesttools, include_directories: include_directories('.')) cjs-128.1/installed-tests/js/matchers.js0000664000175000017500000000406515116312211017125 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh /** * A jasmine asymmetric matcher which expects an array-like object * to contain the given element array in the same order with the * same length. Useful for testing typed arrays. * * @template T * @param {T[]} elements an array of elements to compare with * @returns */ export function arrayLikeWithExactContents(elements) { return { /** * @param {ArrayLike} compareTo an array-like object to compare to * @returns {boolean} */ asymmetricMatch(compareTo) { return ( compareTo.length === elements.length && elements.every((e, i) => e === compareTo[i]) ); }, /** * @returns {string} */ jasmineToString() { return `)`; }, }; } /** * A jasmine asymmetric matcher which compares a given string to an * array-like object of bytes. The compared bytes are decoded using * TextDecoder and then compared using jasmine.stringMatching. * * @param {string | RegExp} text the text or regular expression to compare decoded bytes to * @param {string} [encoding] the encoding of elements * @returns */ export function decodedStringMatching(text, encoding = 'utf-8') { const matcher = jasmine.stringMatching(text); return { /** * @param {ArrayLike} compareTo an array of bytes to decode and compare to * @returns {boolean} */ asymmetricMatch(compareTo) { const decoder = new TextDecoder(encoding); const decoded = decoder.decode(new Uint8Array(Array.from(compareTo))); return matcher.asymmetricMatch(decoded, []); }, /** * @returns {string} */ jasmineToString() { return ``; }, }; } cjs-128.1/installed-tests/js/meson.build0000664000175000017500000001201215116312211017112 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan ### Jasmine tests ############################################################## jsunit_resources_files = gnome.compile_resources('jsunit-resources', 'jsunit.gresources.xml', c_name: 'jsunit_resources') minijasmine = executable('minijasmine', '../minijasmine.cpp', jsunit_resources_files, dependencies: libcjs_dep, cpp_args: [ '-DINSTTESTDIR="@0@"'.format(prefix / installed_tests_execdir), ], include_directories: top_include, install: get_option('installed_tests'), install_dir: installed_tests_execdir) subdir('libgjstesttools') jasmine_tests = [ 'self', 'Cairo', 'Exceptions', 'Format', 'Fundamental', 'Gettext', 'GIMarshalling', 'Gio', 'GLib', 'GObject', 'GObjectClass', 'GObjectInterface', 'GObjectValue', 'GTypeClass', 'Importer', 'Importer2', 'Introspection', 'Lang', 'LegacyByteArray', 'LegacyClass', 'LegacyGObject', 'Mainloop', 'Namespace', 'Package', 'ParamSpec', 'Print', 'Promise', 'Regress', 'Signals', 'System', 'Tweener', 'WarnLib', ] if not get_option('skip_gtk_tests') jasmine_tests += [ 'Gtk3', 'GObjectDestructionAccess', 'LegacyGtk', ] endif installed_js_tests_dir = installed_tests_execdir / 'js' gschemas_compiled = gnome.compile_schemas( depend_files: 'org.cinnamon.CjsTest.gschema.xml') tests_dependencies = [ gschemas_compiled, cjs_private_typelib, gjstest_tools_typelib, gi_tests.get_variable('gimarshallingtests_typelib'), gi_tests.get_variable('regress_typelib'), gi_tests.get_variable('warnlib_typelib'), ] foreach test : jasmine_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: test_file, depends: tests_dependencies, env: tests_environment, protocol: 'tap', suite: 'JS') test_description_subst = { 'name': 'test@0@.js'.format(test), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file(configuration: test_description_subst, input: '../minijasmine.test.in', output: 'test@0@.test'.format(test), install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_js_tests_dir) endif endforeach if get_option('installed_tests') install_subdir('modules', install_dir: installed_js_tests_dir) endif # testGDBus.js and testGtk4.js are separate, because they can be skipped, and # during build should be run using dbus-run-session dbus_tests = ['GDBus'] if not get_option('skip_gtk_tests') have_gtk4 = dependency('gtk4', required: false).found() if have_gtk4 # FIXME: find out why GTK4 tries to acquire a message bus dbus_tests += 'Gtk4' endif endif bus_config = files('../../test/test-bus.conf') foreach test : dbus_tests test_file = files('test@0@.js'.format(test)) if not get_option('skip_dbus_tests') test(test, dbus_run_session, args: ['--config-file', bus_config, '--', minijasmine, test_file], env: tests_environment, protocol: 'tap', suite: 'dbus', depends: tests_dependencies) endif dbus_test_description_subst = { 'name': 'test@0@.js'.format(test), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file( configuration: dbus_test_description_subst, input: '../minijasmine.test.in', output: 'test@0@.test'.format(test), install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_js_tests_dir) endif endforeach # tests using ES modules are also separate because they need an extra # minijasmine flag modules_tests = [ 'Async', 'CairoModule', 'Console', 'ESModules', 'AsyncMainloop', 'Encoding', 'GLibLogWriter', 'Global', 'Timers', 'WeakRef', ] foreach test : modules_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file, '-m'], env: tests_environment, protocol: 'tap', suite: 'JS') esm_test_description_subst = { 'name': 'test@0@.js'.format(test), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file(configuration: esm_test_description_subst, input: '../minijasmine-module.test.in', output: 'test@0@.test'.format(test), install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_js_tests_dir) endif endforeach if get_option('installed_tests') install_data('matchers.js', install_dir: installed_js_tests_dir) endif cjs-128.1/installed-tests/js/minijasmine-executor.js0000664000175000017500000000322415116312211021452 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento // SPDX-FileCopyrightText: 2022 Evan Welsh import * as system from 'system'; import GLib from 'gi://GLib'; import { environment, retval, errorsOutput, mainloop, mainloopLock, } from './minijasmine.js'; // environment.execute() queues up all the tests and runs them // asynchronously. This should start after the main loop starts, otherwise // we will hit the main loop only after several tests have already run. For // consistency we should guarantee that there is a main loop running during // all tests. GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { try { environment.execute(); } catch (e) { print('Bail out! Exception occurred inside Jasmine:', e); mainloop.quit(); system.exit(1); } return GLib.SOURCE_REMOVE; }); // Keep running the main loop while mainloopLock is not null and resolves true. // This happens when testing the main loop itself, in testAsyncMainloop.js. We // don't want to exit minijasmine when the inner loop exits. do { // Run the mainloop // This rule is to encourage parallelizing async // operations, in this case we don't want that. // eslint-disable-next-line no-await-in-loop await mainloop.runAsync(); // eslint-disable-next-line no-await-in-loop } while (await mainloopLock); // On exit, check the return value and print any errors which occurred if (retval !== 0) { printerr(errorsOutput.join('\n')); print('# Test script failed; see test log for assertions'); system.exit(retval); } cjs-128.1/installed-tests/js/minijasmine.js0000664000175000017500000001074715116312211017626 0ustar fabiofabio#!/usr/bin/env -S gjs -m // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento import GLib from 'gi://GLib'; function _filterStack(stack) { if (!stack) return 'No stack'; return stack.split('\n') .filter(stackLine => stackLine.indexOf('resource:///org/cjs/jsunit') === -1) .join('\n'); } let jasmineRequire = imports.jasmine.getJasmineRequireObj(); let jasmineCore = jasmineRequire.core(jasmineRequire); export let environment = jasmineCore.getEnv(); environment.configure({ random: false, }); export const mainloop = GLib.MainLoop.new(null, false); export let retval = 0; export let errorsOutput = []; // Install Jasmine API on the global object let jasmineInterface = jasmineRequire.interface(jasmineCore, environment); Object.assign(globalThis, jasmineInterface); // Reporter that outputs according to the Test Anything Protocol // See http://testanything.org/tap-specification.html class TapReporter { constructor() { this._failedSuites = []; this._specCount = 0; } jasmineStarted(info) { print(`1..${info.totalSpecsDefined}`); } jasmineDone() { this._failedSuites.forEach(failure => { failure.failedExpectations.forEach(result => { print('not ok - An error was thrown outside a test'); print(`# ${result.message}`); }); }); mainloop.quit(); } suiteDone(result) { if (result.failedExpectations && result.failedExpectations.length > 0) { globalThis._jasmineRetval = 1; this._failedSuites.push(result); } if (result.status === 'disabled') print('# Suite was disabled:', result.fullName); } specStarted() { this._specCount++; } specDone(result) { let tapReport; if (result.status === 'failed') { globalThis._jasmineRetval = 1; tapReport = 'not ok'; } else { tapReport = 'ok'; } tapReport += ` ${this._specCount} ${result.fullName}`; if (result.status === 'pending' || result.status === 'disabled' || result.status === 'excluded') { let reason = result.pendingReason || result.status; tapReport += ` # SKIP ${reason}`; } print(tapReport); // Print additional diagnostic info on failure if (result.status === 'failed' && result.failedExpectations) { result.failedExpectations.forEach(failedExpectation => { const output = []; const messageLines = failedExpectation.message.split('\n'); output.push(`Message: ${messageLines.shift()}`); output.push(...messageLines.map(str => ` ${str}`)); output.push('Stack:'); let stackTrace = _filterStack(failedExpectation.stack).trim(); output.push(...stackTrace.split('\n').map(str => ` ${str}`)); if (errorsOutput.length) { errorsOutput.push( Array(GLib.getenv('COLUMNS') || 80).fill('―').join('')); } errorsOutput.push(`Test: ${result.fullName}`); errorsOutput.push(...output); print(output.map(l => `# ${l}`).join('\n')); }); } } } environment.addReporter(new TapReporter()); // If we're running the tests in certain JS_GC_ZEAL modes or Valgrind, then some // will time out if the CI machine is under a certain load. In that case // increase the default timeout. const gcZeal = GLib.getenv('JS_GC_ZEAL'); const valgrind = GLib.getenv('VALGRIND'); if (valgrind || (gcZeal && (gcZeal === '2' || gcZeal.startsWith('2,') || gcZeal === '4'))) jasmine.DEFAULT_TIMEOUT_INTERVAL *= 5; /** * The Promise (or null) that minijasmine-executor locks on * to avoid exiting prematurely */ export let mainloopLock = null; /** * Stops the main loop but prevents the minijasmine-executor from * exiting. This is used for testing the main loop itself. * * @returns a callback which returns control of the main loop to * minijasmine-executor */ export function acquireMainloop() { let resolve; mainloopLock = new Promise(_resolve => (resolve = _resolve)); if (!mainloop.is_running()) throw new Error("Main loop was stopped already, can't acquire"); mainloop.quit(); return () => { mainloopLock = null; resolve(true); }; } cjs-128.1/installed-tests/js/modules/0000775000175000017500000000000015116312211016424 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/alwaysThrows.js0000664000175000017500000000025415116312211021472 0ustar fabiofabio// line 0 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC throw new Error('This is an error that always happens on line 3'); cjs-128.1/installed-tests/js/modules/badOverrides/0000775000175000017500000000000015116312211021035 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/badOverrides/.eslintrc.yml0000664000175000017500000000041015116312211023454 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento rules: no-throw-literal: 'off' # these are intended to be bad code no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-128.1/installed-tests/js/modules/badOverrides/GIMarshallingTests.js0000664000175000017500000000031115116312211025072 0ustar fabiofabio// Sabotage the import of imports.gi.GIMarshallingTests! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento throw '💩'; cjs-128.1/installed-tests/js/modules/badOverrides/Gio.js0000664000175000017500000000030015116312211022102 0ustar fabiofabio// Sabotage the import of imports.gi.Gio! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento var _init = '💩'; cjs-128.1/installed-tests/js/modules/badOverrides/Regress.js0000664000175000017500000000032715116312211023007 0ustar fabiofabio// Sabotage the import of imports.gi.Regress! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento function _init() { throw '💩'; } cjs-128.1/installed-tests/js/modules/badOverrides/WarnLib.js0000664000175000017500000000031115116312211022724 0ustar fabiofabio// Sabotage the import of imports.gi.WarnLib! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento k$^s^%$#^*($%jdghdsfjkgd cjs-128.1/installed-tests/js/modules/badOverrides2/0000775000175000017500000000000015116312211021117 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/badOverrides2/.eslintrc.yml0000664000175000017500000000041015116312211023536 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento rules: no-throw-literal: 'off' # these are intended to be bad code no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-128.1/installed-tests/js/modules/badOverrides2/GIMarshallingTests.js0000664000175000017500000000036215116312211025162 0ustar fabiofabio// Sabotage the import of imports.gi.GIMarshallingTests! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento var _init = {thingsThatThisObjectIsNot: ['callable']}; cjs-128.1/installed-tests/js/modules/badOverrides2/Gio.js0000664000175000017500000000027615116312211022200 0ustar fabiofabio// Sabotage the import of imports.gi.Gio! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento var _init = null; cjs-128.1/installed-tests/js/modules/badOverrides2/Regress.js0000664000175000017500000000027315116312211023071 0ustar fabiofabio// Sabotage the import of imports.gi.Regress! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento var _init; cjs-128.1/installed-tests/js/modules/badOverrides2/WarnLib.js0000664000175000017500000000025715116312211023017 0ustar fabiofabio// Sabotage the import of imports.gi.WarnLib! // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento cjs-128.1/installed-tests/js/modules/data.txt0000664000175000017500000000001215116312211020067 0ustar fabiofabiotest data cjs-128.1/installed-tests/js/modules/dynamic.js0000664000175000017500000000033215116312211020404 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh /* exported test */ async function test() { const {say} = await import('./say.js'); return say('I did it!'); } cjs-128.1/installed-tests/js/modules/encodings.json0000664000175000017500000002316315116312211021275 0ustar fabiofabio[ { "encodings": [ { "labels": [ "unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8", "utf-8", "utf8", "x-unicode20utf8" ], "name": "UTF-8" } ], "heading": "The Encoding" }, { "encodings": [ { "labels": [ "866", "cp866", "csibm866", "ibm866" ], "name": "IBM866" }, { "labels": [ "csisolatin2", "iso-8859-2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", "iso_8859-2:1987", "l2", "latin2" ], "name": "ISO-8859-2" }, { "labels": [ "csisolatin3", "iso-8859-3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", "iso_8859-3:1988", "l3", "latin3" ], "name": "ISO-8859-3" }, { "labels": [ "csisolatin4", "iso-8859-4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", "iso_8859-4:1988", "l4", "latin4" ], "name": "ISO-8859-4" }, { "labels": [ "csisolatincyrillic", "cyrillic", "iso-8859-5", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", "iso_8859-5:1988" ], "name": "ISO-8859-5" }, { "labels": [ "arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", "iso_8859-6:1987" ], "name": "ISO-8859-6" }, { "labels": [ "csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-8859-7", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", "iso_8859-7:1987", "sun_eu_greek" ], "name": "ISO-8859-7" }, { "labels": [ "csiso88598e", "csisolatinhebrew", "hebrew", "iso-8859-8", "iso-8859-8-e", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", "iso_8859-8:1988", "visual" ], "name": "ISO-8859-8" }, { "labels": [ "csiso88598i", "iso-8859-8-i", "logical" ], "name": "ISO-8859-8-I" }, { "labels": [ "csisolatin6", "iso-8859-10", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6" ], "name": "ISO-8859-10" }, { "labels": [ "iso-8859-13", "iso8859-13", "iso885913" ], "name": "ISO-8859-13" }, { "labels": [ "iso-8859-14", "iso8859-14", "iso885914" ], "name": "ISO-8859-14" }, { "labels": [ "csisolatin9", "iso-8859-15", "iso8859-15", "iso885915", "iso_8859-15", "l9" ], "name": "ISO-8859-15" }, { "labels": [ "iso-8859-16" ], "name": "ISO-8859-16" }, { "labels": [ "cskoi8r", "koi", "koi8", "koi8-r", "koi8_r" ], "name": "KOI8-R" }, { "labels": [ "koi8-ru", "koi8-u" ], "name": "KOI8-U" }, { "labels": [ "csmacintosh", "mac", "macintosh", "x-mac-roman" ], "name": "macintosh" }, { "labels": [ "dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620", "windows-874" ], "name": "windows-874" }, { "labels": [ "cp1250", "windows-1250", "x-cp1250" ], "name": "windows-1250" }, { "labels": [ "cp1251", "windows-1251", "x-cp1251" ], "name": "windows-1251" }, { "labels": [ "ansi_x3.4-1968", "ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1", "latin1", "us-ascii", "windows-1252", "x-cp1252" ], "name": "windows-1252" }, { "labels": [ "cp1253", "windows-1253", "x-cp1253" ], "name": "windows-1253" }, { "labels": [ "cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso8859-9", "iso88599", "iso_8859-9", "iso_8859-9:1989", "l5", "latin5", "windows-1254", "x-cp1254" ], "name": "windows-1254" }, { "labels": [ "cp1255", "windows-1255", "x-cp1255" ], "name": "windows-1255" }, { "labels": [ "cp1256", "windows-1256", "x-cp1256" ], "name": "windows-1256" }, { "labels": [ "cp1257", "windows-1257", "x-cp1257" ], "name": "windows-1257" }, { "labels": [ "cp1258", "windows-1258", "x-cp1258" ], "name": "windows-1258" }, { "labels": [ "x-mac-cyrillic", "x-mac-ukrainian" ], "name": "x-mac-cyrillic" } ], "heading": "Legacy single-byte encodings" }, { "encodings": [ { "labels": [ "chinese", "csgb2312", "csiso58gb231280", "gb2312", "gb_2312", "gb_2312-80", "gbk", "iso-ir-58", "x-gbk" ], "name": "GBK" }, { "labels": [ "gb18030" ], "name": "gb18030" } ], "heading": "Legacy multi-byte Chinese (simplified) encodings" }, { "encodings": [ { "labels": [ "big5", "big5-hkscs", "cn-big5", "csbig5", "x-x-big5" ], "name": "Big5" } ], "heading": "Legacy multi-byte Chinese (traditional) encodings" }, { "encodings": [ { "labels": [ "cseucpkdfmtjapanese", "euc-jp", "x-euc-jp" ], "name": "EUC-JP" }, { "labels": [ "csiso2022jp", "iso-2022-jp" ], "name": "ISO-2022-JP" }, { "labels": [ "csshiftjis", "ms932", "ms_kanji", "shift-jis", "shift_jis", "sjis", "windows-31j", "x-sjis" ], "name": "Shift_JIS" } ], "heading": "Legacy multi-byte Japanese encodings" }, { "encodings": [ { "labels": [ "cseuckr", "csksc56011987", "euc-kr", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949" ], "name": "EUC-KR" } ], "heading": "Legacy multi-byte Korean encodings" }, { "encodings": [ { "labels": [ "csiso2022kr", "hz-gb-2312", "iso-2022-cn", "iso-2022-cn-ext", "iso-2022-kr", "replacement" ], "name": "replacement" }, { "labels": [ "unicodefffe", "utf-16be" ], "name": "UTF-16BE" }, { "labels": [ "csunicode", "iso-10646-ucs-2", "ucs-2", "unicode", "unicodefeff", "utf-16", "utf-16le" ], "name": "UTF-16LE" }, { "labels": [ "x-user-defined" ], "name": "x-user-defined" } ], "heading": "Legacy miscellaneous encodings" } ] cjs-128.1/installed-tests/js/modules/exports.js0000664000175000017500000000061515116312211020470 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh import Gio from 'gi://Gio'; export default 5; export const NamedExport = 'Hello, World'; const thisFile = Gio.File.new_for_uri(import.meta.url); const dataFile = thisFile.get_parent().resolve_relative_path('data.txt'); export const [, data] = dataFile.load_contents(null); cjs-128.1/installed-tests/js/modules/foobar.js0000664000175000017500000000062415116312211020234 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // simple test module (used by testImporter.js) /* eslint no-redeclare: ["error", { "builtinGlobals": false }] */ // for toString /* exported bar, foo, testToString, toString */ var foo = 'This is foo'; var bar = 'This is bar'; var toString = x => x; function testToString(x) { return toString(x); } cjs-128.1/installed-tests/js/modules/greet.js0000664000175000017500000000055615116312211020076 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Philip Chimento import GLib from 'gi://GLib'; const uri = GLib.Uri.parse(import.meta.url, GLib.UriFlags.NONE); const {greeting, name} = GLib.Uri.parse_params(uri.get_query(), -1, '&', GLib.UriParamsFlags.NONE); export default `${greeting}, ${name}`; cjs-128.1/installed-tests/js/modules/importmeta.js0000664000175000017500000000046715116312211021152 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Philip Chimento if (typeof import.meta.importSync !== 'undefined') throw new Error('internal import meta property should not be visible in userland'); export default Object.keys(import.meta); cjs-128.1/installed-tests/js/modules/lexicalScope.js0000664000175000017500000000172415116312211021401 0ustar fabiofabio/* exported a, b, c */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento // Tests bindings in the global scope (var) and lexical environment (let, const) // This should be exported as a property when importing this module: var a = 1; // These should not be exported, but for compatibility we will pretend they are // for the time being: let b = 2; const c = 3; // It's not clear whether this should be exported in ES6, but for compatibility // it should be: this.d = 4; // Modules should have access to standard properties on the global object. if (typeof imports === 'undefined') throw new Error('fail the import'); // This should probably not be supported in the future, but I'm not sure how // we can phase it out compatibly. The module should also have access to // properties that the importing code defines. if (typeof expectMe === 'undefined') throw new Error('fail the import'); cjs-128.1/installed-tests/js/modules/modunicode.js0000664000175000017500000000027015116312211021107 0ustar fabiofabio/* exported uval */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. // This file is written in UTF-8. var uval = 'const ♥ utf8'; cjs-128.1/installed-tests/js/modules/mutualImport/0000775000175000017500000000000015116312211021126 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/mutualImport/a.js0000664000175000017500000000053215116312211021704 0ustar fabiofabio/* exported getCount, getCountViaB, incrementCount */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 Red Hat, Inc. const B = imports.mutualImport.b; let count = 0; function incrementCount() { count++; } function getCount() { return count; } function getCountViaB() { return B.getCount(); } cjs-128.1/installed-tests/js/modules/mutualImport/b.js0000664000175000017500000000032115116312211021701 0ustar fabiofabio/* exported getCount */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 Red Hat, Inc. const A = imports.mutualImport.a; function getCount() { return A.getCount(); } cjs-128.1/installed-tests/js/modules/overrides/0000775000175000017500000000000015116312211020426 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/overrides/.eslintrc.yml0000664000175000017500000000031115116312211023045 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento rules: no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-128.1/installed-tests/js/modules/overrides/GIMarshallingTests.js0000664000175000017500000000174115116312211024473 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Philip Chimento function _init() { const GIMarshallingTests = this; GIMarshallingTests.OVERRIDES_CONSTANT = 7; GIMarshallingTests.OverridesStruct.prototype._real_method = GIMarshallingTests.OverridesStruct.prototype.method; GIMarshallingTests.OverridesStruct.prototype.method = function () { return this._real_method() / 7; }; GIMarshallingTests.OverridesObject.prototype._realInit = GIMarshallingTests.OverridesObject.prototype._init; GIMarshallingTests.OverridesObject.prototype._init = function (num, ...args) { this._realInit(...args); this.num = num; }; GIMarshallingTests.OverridesObject.prototype._realMethod = GIMarshallingTests.OverridesObject.prototype.method; GIMarshallingTests.OverridesObject.prototype.method = function () { return this._realMethod() / 7; }; } cjs-128.1/installed-tests/js/modules/say.js0000664000175000017500000000036615116312211017563 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento export function say(str) { return `<( ${str} )`; } export default function () { return 'default export'; } cjs-128.1/installed-tests/js/modules/sideEffect.js0000664000175000017500000000027015116312211021022 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento globalThis.leakyState ??= 0; globalThis.leakyState++; cjs-128.1/installed-tests/js/modules/sideEffect2.js0000664000175000017500000000027015116312211021104 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento globalThis.leakyState ??= 0; globalThis.leakyState++; cjs-128.1/installed-tests/js/modules/sideEffect3.js0000664000175000017500000000026615116312211021112 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Gary Li globalThis.queryLeakyState ??= 0; globalThis.queryLeakyState++; cjs-128.1/installed-tests/js/modules/sideEffect4.js0000664000175000017500000000033015116312211021103 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Gary Li const doImport = async () => { await import('./sideEffect3.js?bar=baz'); }; await doImport(); cjs-128.1/installed-tests/js/modules/subA/0000775000175000017500000000000015116312211017316 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/subA/subB/0000775000175000017500000000000015116312211020211 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/subA/subB/__init__.js0000664000175000017500000000064115116312211022307 0ustar fabiofabio/* exported ImporterClass, testImporterFunction */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2009 litl, LLC function testImporterFunction() { return '__init__ function tested'; } function ImporterClass() { this._init(); } ImporterClass.prototype = { _init() { this._a = '__init__ class tested'; }, testMethod() { return this._a; }, }; cjs-128.1/installed-tests/js/modules/subA/subB/baz.js0000664000175000017500000000000015116312211021311 0ustar fabiofabiocjs-128.1/installed-tests/js/modules/subA/subB/foobar.js0000664000175000017500000000033415116312211022017 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // simple test module (used by testImporter.js) /* exported bar, foo */ var foo = 'This is foo'; var bar = 'This is bar'; cjs-128.1/installed-tests/js/modules/subBadInit/0000775000175000017500000000000015116312211020450 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/subBadInit/__init__.js0000664000175000017500000000021615116312211022544 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh this is gobbledygook cjs-128.1/installed-tests/js/modules/subErrorInit/0000775000175000017500000000000015116312211021053 5ustar fabiofabiocjs-128.1/installed-tests/js/modules/subErrorInit/__init__.js0000664000175000017500000000022515116312211023147 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh throw Error('a bad init!'); cjs-128.1/installed-tests/js/org.cinnamon.CjsTest.gschema.xml0000664000175000017500000000135015116312211023051 0ustar fabiofabio (-1, -1) false false 10 cjs-128.1/installed-tests/js/testAsync.js0000664000175000017500000000415315116312211017272 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; const PRIORITIES = [ 'PRIORITY_LOW', 'PRIORITY_HIGH', 'PRIORITY_DEFAULT', 'PRIORITY_HIGH_IDLE', 'PRIORITY_DEFAULT_IDLE', ]; describe('Async microtasks resolves before', function () { // Generate test suites with different types of Sources const tests = [ { description: 'idle task with', createSource: () => GLib.idle_source_new(), }, { description: '0-second timeout task with', // A timeout of 0 tests if microtasks (promises) run before // non-idle tasks which would normally execute "next" in the loop createSource: () => GLib.timeout_source_new(0), }, ]; for (const {description, createSource} of tests) { describe(description, function () { const CORRECT_TASKS = [ 'async 1', 'async 2', 'source task', ]; for (const priority of PRIORITIES) { it(`priority set to GLib.${priority}`, function (done) { const tasks = []; const source = createSource(); source.set_priority(GLib[priority]); GObject.source_set_closure(source, () => { tasks.push('source task'); expect(tasks).toEqual(jasmine.arrayWithExactContents(CORRECT_TASKS)); done(); source.destroy(); return GLib.SOURCE_REMOVE; }); source.attach(null); (async () => { // Without an await async functions execute // synchronously tasks.push(await 'async 1'); })().then(() => { tasks.push('async 2'); }); }); } }); } }); cjs-128.1/installed-tests/js/testAsyncMainloop.js0000664000175000017500000000141515116312211020767 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2022 Evan Welsh import GLib from 'gi://GLib'; import {acquireMainloop} from 'resource:///org/cjs/jsunit/minijasmine.js'; describe('Async mainloop', function () { let loop, quit; beforeEach(function () { loop = new GLib.MainLoop(null, false); quit = jasmine.createSpy('quit').and.callFake(() => { loop.quit(); return GLib.SOURCE_REMOVE; }); }); it('resolves when main loop exits', async function () { const release = acquireMainloop(); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, quit); await loop.runAsync(); expect(quit).toHaveBeenCalled(); release(); }); }); cjs-128.1/installed-tests/js/testCairo.js0000664000175000017500000003072115116312211017252 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gtk = '3.0'; const Cairo = imports.cairo; const {Gdk, GIMarshallingTests, GLib, Gtk, Regress} = imports.gi; function _ts(obj) { return obj.toString().slice(8, -1); } describe('Cairo', function () { let cr, surface; beforeEach(function () { surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10); cr = new Cairo.Context(surface); }); describe('context', function () { it('has the right type', function () { expect(cr instanceof Cairo.Context).toBeTruthy(); }); it('reports its target surface', function () { expect(_ts(cr.getTarget())).toEqual('ImageSurface'); }); it('can set its source to a pattern', function () { let pattern = Cairo.SolidPattern.createRGB(1, 2, 3); cr.setSource(pattern); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); it('can set its antialias', function () { cr.setAntialias(Cairo.Antialias.NONE); expect(cr.getAntialias()).toEqual(Cairo.Antialias.NONE); }); it('can set its fill rule', function () { cr.setFillRule(Cairo.FillRule.EVEN_ODD); expect(cr.getFillRule()).toEqual(Cairo.FillRule.EVEN_ODD); }); it('can set its line cap', function () { cr.setLineCap(Cairo.LineCap.ROUND); expect(cr.getLineCap()).toEqual(Cairo.LineCap.ROUND); }); it('can set its line join', function () { cr.setLineJoin(Cairo.LineJoin.ROUND); expect(cr.getLineJoin()).toEqual(Cairo.LineJoin.ROUND); }); it('can set its line width', function () { cr.setLineWidth(1138); expect(cr.getLineWidth()).toEqual(1138); }); it('can set its miter limit', function () { cr.setMiterLimit(42); expect(cr.getMiterLimit()).toEqual(42); }); it('can set its operator', function () { cr.setOperator(Cairo.Operator.IN); expect(cr.getOperator()).toEqual(Cairo.Operator.IN); }); it('can set its tolerance', function () { cr.setTolerance(144); expect(cr.getTolerance()).toEqual(144); }); it('has a rectangle as clip extents', function () { expect(cr.clipExtents().length).toEqual(4); }); it('has a rectangle as fill extents', function () { expect(cr.fillExtents().length).toEqual(4); }); it('has a rectangle as stroke extents', function () { expect(cr.strokeExtents().length).toEqual(4); }); it('has zero dashes initially', function () { expect(cr.getDashCount()).toEqual(0); }); it('transforms user to device coordinates', function () { expect(cr.userToDevice(0, 0).length).toEqual(2); }); it('transforms user to device distance', function () { expect(cr.userToDeviceDistance(0, 0).length).toEqual(2); }); it('transforms device to user coordinates', function () { expect(cr.deviceToUser(0, 0).length).toEqual(2); }); it('transforms device to user distance', function () { expect(cr.deviceToUserDistance(0, 0).length).toEqual(2); }); it('computes text extents', function () { expect(cr.textExtents('')).toEqual({ xBearing: 0, yBearing: 0, width: 0, height: 0, xAdvance: 0, yAdvance: 0, }); expect(cr.textExtents('trailing spaces ')).toEqual({ xBearing: jasmine.any(Number), yBearing: jasmine.any(Number), width: jasmine.any(Number), height: jasmine.any(Number), xAdvance: jasmine.any(Number), yAdvance: 0, }); }); it('can call various, otherwise untested, methods without crashing', function () { expect(() => { cr.save(); cr.restore(); cr.setSourceSurface(surface, 0, 0); cr.pushGroup(); cr.popGroup(); cr.pushGroupWithContent(Cairo.Content.COLOR); cr.popGroupToSource(); cr.setSourceRGB(1, 2, 3); cr.setSourceRGBA(1, 2, 3, 4); cr.clip(); cr.clipPreserve(); cr.fill(); cr.fillPreserve(); let pattern = Cairo.SolidPattern.createRGB(1, 2, 3); cr.mask(pattern); cr.maskSurface(surface, 0, 0); cr.paint(); cr.paintWithAlpha(1); cr.setDash([1, 0.5], 1); cr.stroke(); cr.strokePreserve(); cr.inFill(0, 0); cr.inStroke(0, 0); cr.copyPage(); cr.showPage(); cr.translate(10, 10); cr.scale(10, 10); cr.rotate(180); cr.identityMatrix(); cr.showText('foobar'); cr.moveTo(0, 0); cr.setDash([], 1); cr.lineTo(1, 0); cr.lineTo(1, 1); cr.lineTo(0, 1); cr.closePath(); let path = cr.copyPath(); cr.fill(); cr.appendPath(path); cr.stroke(); }).not.toThrow(); }); it('has methods when created from a C function', function () { if (GLib.getenv('ENABLE_GTK') !== 'yes') { pending('GTK disabled'); return; } Gtk.init(null); let win = new Gtk.OffscreenWindow(); let da = new Gtk.DrawingArea(); win.add(da); da.realize(); cr = Gdk.cairo_create(da.window); expect(cr.save).toBeDefined(); expect(cr.getTarget()).toBeDefined(); }); }); describe('pattern', function () { it('has typechecks', function () { expect(() => cr.setSource({})).toThrow(); expect(() => cr.setSource(surface)).toThrow(); }); }); describe('solid pattern', function () { it('can be created from RGB static method', function () { let p1 = Cairo.SolidPattern.createRGB(1, 2, 3); expect(_ts(p1)).toEqual('SolidPattern'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); it('can be created from RGBA static method', function () { let p2 = Cairo.SolidPattern.createRGBA(1, 2, 3, 4); expect(_ts(p2)).toEqual('SolidPattern'); cr.setSource(p2); expect(_ts(cr.getSource())).toEqual('SolidPattern'); }); }); describe('surface pattern', function () { it('can be created and added as a source', function () { let p1 = new Cairo.SurfacePattern(surface); expect(_ts(p1)).toEqual('SurfacePattern'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('SurfacePattern'); }); }); describe('linear gradient', function () { it('can be created and added as a source', function () { let p1 = new Cairo.LinearGradient(1, 2, 3, 4); expect(_ts(p1)).toEqual('LinearGradient'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('LinearGradient'); }); }); describe('radial gradient', function () { it('can be created and added as a source', function () { let p1 = new Cairo.RadialGradient(1, 2, 3, 4, 5, 6); expect(_ts(p1)).toEqual('RadialGradient'); cr.setSource(p1); expect(_ts(cr.getSource())).toEqual('RadialGradient'); }); }); describe('path', function () { it('has typechecks', function () { expect(() => cr.appendPath({})).toThrow(); expect(() => cr.appendPath(surface)).toThrow(); }); }); describe('surface', function () { it('has typechecks', function () { expect(() => new Cairo.Context({})).toThrow(); const pattern = new Cairo.SurfacePattern(surface); expect(() => new Cairo.Context(pattern)).toThrow(); }); it('can access the device scale', function () { let [x, y] = surface.getDeviceScale(); expect(x).toEqual(1); expect(y).toEqual(1); surface.setDeviceScale(1.2, 1.2); [x, y] = surface.getDeviceScale(); expect(x).toEqual(1.2); expect(y).toEqual(1.2); }); it('can access the device offset', function () { let [x, y] = surface.getDeviceOffset(); expect(x).toEqual(0); expect(y).toEqual(0); surface.setDeviceOffset(50, 50); [x, y] = surface.getDeviceOffset(); expect(x).toEqual(50); expect(y).toEqual(50); }); it('can be finalized', function () { expect(() => { let _surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10); let _cr = new Cairo.Context(_surface); _surface.finish(); _cr.stroke(); }).toThrow(); expect(() => { let _surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10); _surface.finish(); _surface.flush(); }).not.toThrow(); }); }); describe('GI test suite', function () { describe('for context', function () { it('can be marshalled as a return value', function () { const outCr = Regress.test_cairo_context_full_return(); const outSurface = outCr.getTarget(); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); it('can be marshalled as an in parameter', function () { expect(() => Regress.test_cairo_context_none_in(cr)).not.toThrow(); }); }); describe('for surface', function () { ['none', 'full'].forEach(transfer => { it(`can be marshalled as a transfer-${transfer} return value`, function () { const outSurface = Regress[`test_cairo_surface_${transfer}_return`](); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); }); it('can be marshalled as an in parameter', function () { expect(() => Regress.test_cairo_surface_none_in(surface)).not.toThrow(); }); it('can be marshalled as an out parameter', function () { const outSurface = Regress.test_cairo_surface_full_out(); expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32); expect(outSurface.getWidth()).toEqual(10); expect(outSurface.getHeight()).toEqual(10); }); }); it('can be marshalled through a signal handler', function () { let o = new Regress.TestObj(); let foreignSpy = jasmine.createSpy('sig-with-foreign-struct'); o.connect('sig-with-foreign-struct', foreignSpy); o.emit_sig_with_foreign_struct(); expect(foreignSpy).toHaveBeenCalledWith(o, cr); }); it('can have its type inferred as a foreign struct', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(cr, Cairo.Context)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(surface, Cairo.Surface)) .not.toThrow(); }); }); }); describe('Cairo imported via GI', function () { const giCairo = imports.gi.cairo; it('has the same functionality as imports.cairo', function () { const surface = new giCairo.ImageSurface(Cairo.Format.ARGB32, 1, 1); void new giCairo.Context(surface); }); it('has boxed types from the GIR file', function () { void new giCairo.RectangleInt(); }); }); cjs-128.1/installed-tests/js/testCairoModule.js0000664000175000017500000000153415116312211020420 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Philip Chimento import Cairo from 'cairo'; import giCairo from 'gi://cairo'; describe('Cairo imported as ES module', function () { it('cairo default import', function () { // one from cairoNative, one from cairo JS. expect(typeof Cairo.Context).toBe('function'); expect(typeof Cairo.Format).toBe('object'); }); // cairo doesn't have named exports }); describe('Cairo imported via GI', function () { it('has the same functionality as imports.cairo', function () { const surface = new giCairo.ImageSurface(Cairo.Format.ARGB32, 1, 1); void new giCairo.Context(surface); }); it('has boxed types from the GIR file', function () { void new giCairo.RectangleInt(); }); }); cjs-128.1/installed-tests/js/testConsole.js0000664000175000017500000002770215116312211017624 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh // eslint-disable-next-line /// import GLib from 'gi://GLib'; import {DEFAULT_LOG_DOMAIN} from 'console'; import {decodedStringMatching} from './matchers.js'; function objectContainingLogMessage( message, domain = DEFAULT_LOG_DOMAIN, fields = {}, messageMatcher = decodedStringMatching ) { return jasmine.objectContaining({ MESSAGE: messageMatcher(message), GLIB_DOMAIN: decodedStringMatching(domain), ...fields, }); } function matchStackTrace(log, sourceFile = null, encoding = 'utf-8') { const matcher = jasmine.stringMatching(log); const stackLineMatcher = jasmine.stringMatching(/^[\w./<]*@.*:\d+:\d+/); const sourceMatcher = sourceFile ? jasmine.stringMatching(RegExp( String.raw`^[\w]*@(file|resource):\/\/\/.*\/${sourceFile}\.js:\d+:\d+$`)) : stackLineMatcher; return { asymmetricMatch(compareTo) { const decoder = new TextDecoder(encoding); const decoded = decoder.decode(new Uint8Array(Array.from(compareTo))); const lines = decoded.split('\n').filter(l => !!l.length); if (!matcher.asymmetricMatch(lines[0])) return false; if (!sourceMatcher.asymmetricMatch(lines[1])) return false; return lines.slice(2).every(l => stackLineMatcher.asymmetricMatch(l)); }, jasmineToString() { return ``; }, }; } describe('console', function () { /** @type {jasmine.Spy<(_level: any, _fields: any) => any>} */ let writer_func; /** * @param {RegExp | string} message _ * @param {*} [logLevel] _ * @param {*} [domain] _ * @param {*} [fields] _ */ function expectLog( message, logLevel = GLib.LogLevelFlags.LEVEL_MESSAGE, domain = DEFAULT_LOG_DOMAIN, fields = {} ) { if (logLevel < GLib.LogLevelFlags.LEVEL_WARNING) { const [_, currentFile] = new Error().stack.split('\n').at(0).match( /^[^@]*@(.*):\d+:\d+$/); fields = { ...fields, CODE_FILE: decodedStringMatching(currentFile), }; } expect(writer_func).toHaveBeenCalledOnceWith( logLevel, objectContainingLogMessage(message, domain, fields) ); // Always reset the calls, so that we can assert at the end that no // unexpected messages were logged writer_func.calls.reset(); } beforeAll(function () { writer_func = jasmine.createSpy( 'Console test writer func', function (level, _fields) { if (level === GLib.LogLevelFlags.ERROR) return GLib.LogWriterOutput.UNHANDLED; return GLib.LogWriterOutput.HANDLED; } ); writer_func.and.callThrough(); // @ts-expect-error The existing binding doesn't accept any parameters because // it is a raw pointer. GLib.log_set_writer_func(writer_func); }); beforeEach(function () { writer_func.calls.reset(); }); it('has correct object tag', function () { expect(console.toString()).toBe('[object console]'); }); it('logs a message', function () { console.log('a log'); expect(writer_func).toHaveBeenCalledOnceWith( GLib.LogLevelFlags.LEVEL_MESSAGE, objectContainingLogMessage('a log') ); writer_func.calls.reset(); }); it('logs an empty object correctly', function () { const emptyObject = {}; console.log(emptyObject); expectLog('{}'); }); it('logs an object with custom constructor name', function () { function CustomObject() {} const customInstance = new CustomObject(); console.log(customInstance); expectLog('CustomObject {}'); }); it('logs an object with undefined constructor', function () { const objectWithUndefinedConstructor = Object.create(null); console.log(objectWithUndefinedConstructor); expectLog('{}'); }); it('logs an object with Symbol.toStringTag and __name__', function () { console.log(GLib); expectLog('[GIRepositoryNamespace GLib]'); }); it('logs a warning', function () { console.warn('a warning'); expect(writer_func).toHaveBeenCalledOnceWith( GLib.LogLevelFlags.LEVEL_WARNING, objectContainingLogMessage('a warning') ); writer_func.calls.reset(); }); it('logs an informative message', function () { console.info('an informative message'); expect(writer_func).toHaveBeenCalledOnceWith( GLib.LogLevelFlags.LEVEL_INFO, objectContainingLogMessage('an informative message') ); writer_func.calls.reset(); }); it('traces a line', function () { // eslint-disable-next-line max-statements-per-line console.trace('a trace'); const error = new Error(); const [_, currentFile, errorLine] = error.stack.split('\n').at(0).match( /^[^@]*@(.*):(\d+):\d+$/); expect(writer_func).toHaveBeenCalledOnceWith( GLib.LogLevelFlags.LEVEL_MESSAGE, objectContainingLogMessage('a trace', DEFAULT_LOG_DOMAIN, { CODE_FILE: decodedStringMatching(currentFile), CODE_LINE: decodedStringMatching(errorLine), }, message => matchStackTrace(message, 'testConsole')) ); writer_func.calls.reset(); }); it('traces a empty message', function () { console.trace(); const [_, currentFile] = new Error().stack.split('\n').at(0).match( /^[^@]*@(.*):\d+:\d+$/); expect(writer_func).toHaveBeenCalledOnceWith( GLib.LogLevelFlags.LEVEL_MESSAGE, objectContainingLogMessage('Trace', DEFAULT_LOG_DOMAIN, { CODE_FILE: decodedStringMatching(currentFile), }, message => matchStackTrace(message, 'testConsole')) ); writer_func.calls.reset(); }); it('asserts a true condition', function () { console.assert(true, 'no printed'); expect(writer_func).not.toHaveBeenCalled(); writer_func.calls.reset(); }); it('asserts a false condition', function () { console.assert(false); expectLog('Assertion failed', GLib.LogLevelFlags.LEVEL_CRITICAL); writer_func.calls.reset(); }); it('asserts a false condition with message', function () { console.assert(false, 'asserts false is not true'); expectLog('asserts false is not true', GLib.LogLevelFlags.LEVEL_CRITICAL); writer_func.calls.reset(); }); describe('clear()', function () { it('can be called', function () { console.clear(); }); it('resets indentation', function () { console.group('a group'); expectLog('a group'); console.log('a log'); expectLog(' a log'); console.clear(); console.log('a log'); expectLog('a log'); }); }); describe('table()', function () { it('logs at least something', function () { console.table(['title', 1, 2, 3]); expectLog(/title/); }); }); // %s - string // %d or %i - integer // %f - float // %o - "optimal" object formatting // %O - "generic" object formatting // %c - "CSS" formatting (unimplemented by GJS) describe('string replacement', function () { const functions = { log: GLib.LogLevelFlags.LEVEL_MESSAGE, warn: GLib.LogLevelFlags.LEVEL_WARNING, info: GLib.LogLevelFlags.LEVEL_INFO, error: GLib.LogLevelFlags.LEVEL_CRITICAL, trace: GLib.LogLevelFlags.LEVEL_MESSAGE, }; Object.entries(functions).forEach(([fn, level]) => { it(`console.${fn}() supports %s`, function () { console[fn]('Does this %s substitute correctly?', 'modifier'); expectLog('Does this modifier substitute correctly?', level); }); it(`console.${fn}() supports %d`, function () { console[fn]('Does this %d substitute correctly?', 10); expectLog('Does this 10 substitute correctly?', level); }); it(`console.${fn}() supports %i`, function () { console[fn]('Does this %i substitute correctly?', 26); expectLog('Does this 26 substitute correctly?', level); }); it(`console.${fn}() supports %f`, function () { console[fn]('Does this %f substitute correctly?', 27.56331); expectLog('Does this 27.56331 substitute correctly?', level); }); it(`console.${fn}() supports %o`, function () { console[fn]('Does this %o substitute correctly?', new Error()); expectLog(/Does this Error\n.*substitute correctly\?/s, level); }); it(`console.${fn}() supports %O`, function () { console[fn]('Does this %O substitute correctly?', new Error()); expectLog('Does this {} substitute correctly?', level); }); it(`console.${fn}() ignores %c`, function () { console[fn]('Does this %c substitute correctly?', 'modifier'); expectLog('Does this substitute correctly?', level); }); it(`console.${fn}() supports mixing substitutions`, function () { console[fn]( 'Does this %s and the %f substitute correctly alongside %d?', 'string', 3.14, 14 ); expectLog( 'Does this string and the 3.14 substitute correctly alongside 14?', level ); }); it(`console.${fn}() supports invalid numbers`, function () { console[fn]( 'Does this support parsing %i incorrectly?', 'a string' ); expectLog('Does this support parsing NaN incorrectly?', level); }); it(`console.${fn}() supports missing substitutions`, function () { console[fn]('Does this support a missing %s substitution?'); expectLog( 'Does this support a missing %s substitution?', level ); }); }); }); describe('time()', function () { it('ends correctly', function (done) { console.time('testing time'); // console.time logs nothing. expect(writer_func).not.toHaveBeenCalled(); setTimeout(() => { console.timeLog('testing time'); expectLog(/testing time: (.*)ms/); console.timeEnd('testing time'); expectLog(/testing time: (.*)ms/); console.timeLog('testing time'); expectLog( "No time log found for label: 'testing time'.", GLib.LogLevelFlags.LEVEL_WARNING ); done(); }, 10); }); it("doesn't log initially", function (done) { console.time('testing time'); // console.time logs nothing. expect(writer_func).not.toHaveBeenCalled(); setTimeout(() => { console.timeEnd('testing time'); expectLog(/testing time: (.*)ms/); done(); }, 10); }); afterEach(function () { // Ensure we only got the log lines that we expected expect(writer_func).not.toHaveBeenCalled(); }); }); }); cjs-128.1/installed-tests/js/testESModules.js0000664000175000017500000001742115116312211020057 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh import gettext from 'gettext'; import {ngettext as N_} from 'gettext'; import gi from 'gi'; import Gio from 'gi://Gio'; import system from 'system'; import {exit} from 'system'; import $ from 'resource:///org/cjs/jsunit/modules/exports.js'; import {NamedExport, data} from 'resource:///org/cjs/jsunit/modules/exports.js'; import metaProperties from 'resource:///org/cjs/jsunit/modules/importmeta.js'; // These imports should all refer to the same module and import it only once import 'resource:///org/cjs/jsunit/modules/sideEffect.js'; import 'resource://org/cjs/jsunit/modules/sideEffect.js'; import 'resource:///org/cjs/jsunit/modules/../modules/sideEffect.js'; // Imports with query parameters should not fail and be imported uniquely import 'resource:///org/cjs/jsunit/modules/sideEffect3.js?foo=bar&maple=syrup'; // these should resolve to the same after being canonicalized import 'resource://org/cjs/jsunit/modules/./sideEffect3.js?etag=1'; import 'resource:///org/cjs/jsunit/modules/sideEffect3.js?etag=1'; import greeting1 from 'resource:///org/cjs/jsunit/modules/greet.js?greeting=Hello&name=Test%20Code'; import greeting2 from 'resource:///org/cjs/jsunit/modules/greet.js?greeting=Bonjour&name=Code%20de%20Test'; describe('ES module imports', function () { it('default import', function () { expect($).toEqual(5); }); it('named import', function () { expect(NamedExport).toEqual('Hello, World'); }); it('GObject introspection import', function () { expect(gi.require('GObject').toString()).toEqual('[object GIRepositoryNamespace]'); }); it('import with version parameter', function () { expect(gi.require('GObject', '2.0')).toBe(gi.require('GObject')); expect(imports.gi.versions['GObject']).toBe('2.0'); }); it('import again with other version parameter', function () { expect(() => gi.require('GObject', '1.75')).toThrow(); expect(imports.gi.versions['GObject']).toBe('2.0'); }); it('import for the first time with wrong version', function () { expect(() => gi.require('Gtk', '1.75')).toThrow(); expect(imports.gi.versions['Gtk']).not.toBeDefined(); }); it('import with another version after a failed import', function () { expect(gi.require('Gtk', '3.0').toString()).toEqual('[object GIRepositoryNamespace]'); expect(imports.gi.versions['Gtk']).toBe('3.0'); }); it('import nonexistent module', function () { expect(() => gi.require('PLib')).toThrow(); expect(imports.gi.versions['PLib']).not.toBeDefined(); }); it('GObject introspection import via URL scheme', function () { expect(Gio.toString()).toEqual('[object GIRepositoryNamespace]'); expect(imports.gi.versions['Gio']).toBe('2.0'); }); it('import.meta.url', function () { expect(import.meta.url).toMatch(/\/installed-tests\/(gjs\/)?js\/testESModules\.js$/); }); it('finds files relative to import.meta.url', function () { // this data is loaded inside exports.js relative to import.meta.url expect(data).toEqual(Uint8Array.from('test data\n', c => c.codePointAt())); }); it('does not expose internal import.meta properties to userland modules', function () { expect(metaProperties).toEqual(['url']); }); it('treats equivalent URIs as equal and does not load the module again', function () { expect(globalThis.leakyState).toEqual(1); }); it('can load modules with query parameters uniquely', function () { expect(globalThis.queryLeakyState).toEqual(2); }); it('passes query parameters to imported modules in import.meta.uri', function () { expect(greeting1).toEqual('Hello, Test Code'); expect(greeting2).toEqual('Bonjour, Code de Test'); }); }); describe('Builtin ES modules', function () { it('gettext default import', function () { expect(typeof gettext.ngettext).toBe('function'); }); it('gettext named import', function () { expect(typeof N_).toBe('function'); }); it('gettext named dynamic import', async function () { const localGettext = await import('gettext'); expect(typeof localGettext.ngettext).toEqual('function'); }); it('gettext dynamic import matches static import', async function () { const localGettext = await import('gettext'); expect(localGettext.default).toEqual(gettext); }); it('system default import', function () { expect(typeof system.exit).toBe('function'); }); it('system named import', function () { expect(typeof exit).toBe('function'); expect(exit).toBe(system.exit); }); it('system dynamic import matches static import', async function () { const localSystem = await import('system'); expect(localSystem.default).toEqual(system); }); it('system named dynamic import', async function () { const localSystem = await import('system'); expect(typeof localSystem.exit).toBe('function'); }); }); describe('Dynamic imports', function () { let module; beforeEach(async function () { try { module = await import('resource:///org/cjs/jsunit/modules/say.js'); } catch (err) { logError(err); fail(); } }); it('default import', function () { expect(module.default()).toEqual('default export'); }); it('named import', function () { expect(module.say('hi')).toEqual('<( hi )'); }); it('dynamic gi import matches static', async function () { expect((await import('gi://Gio')).default).toEqual(Gio); }); it('treats equivalent URIs as equal and does not load the module again', async function () { delete globalThis.leakyState; await import('resource:///org/cjs/jsunit/modules/sideEffect2.js'); await import('resource://org/cjs/jsunit/modules/sideEffect2.js'); await import('resource:///org/cjs/jsunit/modules/../modules/sideEffect2.js'); expect(globalThis.leakyState).toEqual(1); }); it('treats query parameters uniquely for absolute URIs', async function () { delete globalThis.queryLeakyState; await import('resource:///org/cjs/jsunit/modules/sideEffect3.js?maple=syrup'); expect(globalThis.queryLeakyState).toEqual(1); }); it('treats query parameters uniquely for relative URIs', async function () { delete globalThis.queryLeakyState; await import('resource:///org/cjs/jsunit/modules/sideEffect4.js'); expect(globalThis.queryLeakyState).toEqual(1); }); it('does not show internal stack frames in an import error', async function () { try { await import('resource:///org/cjs/jsunit/modules/doesNotExist.js'); fail('should not be reached'); } catch (e) { expect(e.name).toBe('ImportError'); expect(e.stack).not.toMatch('internal/'); } }); it('does not show internal stack frames in a module that throws an error', async function () { try { await import('resource:///org/cjs/jsunit/modules/alwaysThrows.js'); fail('should not be reached'); } catch (e) { expect(e.constructor).toBe(Error); expect(e.stack).not.toMatch('internal/'); } }); it('does not show internal stack frames in a module that fails to parse', async function () { try { // invalid JS await import('resource:///org/cjs/jsunit/modules/data.txt'); fail('should not be reached'); } catch (e) { expect(e.constructor).toBe(SyntaxError); expect(e.stack).not.toMatch('internal/'); } }); }); cjs-128.1/installed-tests/js/testEncoding.js0000664000175000017500000003630415116312211017746 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh // Some test inputs are derived from https://github.com/denoland/deno/blob/923214c53725651792f6d55c5401bf6b475622ea/op_crates/web/08_text_encoding.js // Data originally from https://encoding.spec.whatwg.org/encodings.json import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import {arrayLikeWithExactContents} from './matchers.js'; /** * Loads a JSON file from a URI and parses it. * * @param {string} src the URI to load from * @returns {any} */ function loadJSONFromResource(src) { const file = Gio.File.new_for_uri(src); const [, bytes] = file.load_contents(null); const decoder = new TextDecoder(); const jsonRaw = decoder.decode(bytes); const json = JSON.parse(jsonRaw); return json; } /** * Encoded form of '𝓽𝓮𝔁𝓽' * * @returns {number[]} */ function encodedMultibyteCharArray() { return [ 0xf0, 0x9d, 0x93, 0xbd, 0xf0, 0x9d, 0x93, 0xae, 0xf0, 0x9d, 0x94, 0x81, 0xf0, 0x9d, 0x93, 0xbd, ]; } describe('Text Encoding', function () { it('toString() uses spec-compliant tags', function () { const encoder = new TextEncoder(); expect(encoder.toString()).toBe('[object TextEncoder]'); const decoder = new TextDecoder(); expect(decoder.toString()).toBe('[object TextDecoder]'); }); describe('TextEncoder', function () { describe('encode()', function () { it('can encode UTF8 (multi-byte chars)', function () { const input = '𝓽𝓮𝔁𝓽'; const encoder = new TextEncoder(); const encoded = encoder.encode(input); expect(encoded).toEqual( arrayLikeWithExactContents([...encodedMultibyteCharArray()]) ); }); }); describe('encodeInto()', function () { it('can encode UTF8 (Latin chars) into a Uint8Array', function () { const input = 'text'; const encoder = new TextEncoder(); const bytes = new Uint8Array(5); const result = encoder.encodeInto(input, bytes); expect(result.read).toBe(4); expect(result.written).toBe(4); expect(bytes).toEqual( arrayLikeWithExactContents([0x74, 0x65, 0x78, 0x74, 0x00]) ); }); it('can fully encode UTF8 (multi-byte chars) into a Uint8Array', function () { const input = '𝓽𝓮𝔁𝓽'; const encoder = new TextEncoder(); const bytes = new Uint8Array(17); const result = encoder.encodeInto(input, bytes); expect(result.read).toBe(8); expect(result.written).toBe(16); expect(bytes).toEqual( arrayLikeWithExactContents([ ...encodedMultibyteCharArray(), 0x00, ]) ); }); it('can partially encode UTF8 into an under-allocated Uint8Array', function () { const input = '𝓽𝓮𝔁𝓽'; const encoder = new TextEncoder(); const bytes = new Uint8Array(5); const result = encoder.encodeInto(input, bytes); expect(result.read).toBe(2); expect(result.written).toBe(4); expect(bytes).toEqual( arrayLikeWithExactContents([ ...encodedMultibyteCharArray().slice(0, 4), 0x00, ]) ); }); }); }); describe('TextDecoder', function () { describe('decode()', function () { it('fatal is false by default', function () { const decoder = new TextDecoder(); expect(decoder.fatal).toBeFalse(); }); it('ignoreBOM is false by default', function () { const decoder = new TextDecoder(); expect(decoder.ignoreBOM).toBeFalse(); }); it('fatal is true when passed', function () { const decoder = new TextDecoder(undefined, {fatal: true}); expect(decoder.fatal).toBeTrue(); }); it('ignoreBOM is true when passed', function () { const decoder = new TextDecoder(undefined, {ignoreBOM: true}); expect(decoder.ignoreBOM).toBeTrue(); }); it('fatal is coerced to a boolean value', function () { const decoder = new TextDecoder(undefined, {fatal: 1}); expect(decoder.fatal).toBeTrue(); }); it('ignoreBOM is coerced to a boolean value', function () { const decoder = new TextDecoder(undefined, {ignoreBOM: ''}); expect(decoder.ignoreBOM).toBeFalse(); }); it('throws on empty input', function () { const decoder = new TextDecoder(); const input = ''; expect(() => decoder.decode(input)).toThrowError( 'Provided input cannot be converted to ArrayBufferView or ArrayBuffer' ); }); it('throws on null input', function () { const decoder = new TextDecoder(); const input = null; expect(() => decoder.decode(input)).toThrowError( 'Provided input cannot be converted to ArrayBufferView or ArrayBuffer' ); }); it('throws on invalid encoding label', function () { expect(() => new TextDecoder('bad')).toThrowError( "Invalid encoding label: 'bad'" ); }); it('decodes undefined as an empty string', function () { const decoder = new TextDecoder(); const input = undefined; expect(decoder.decode(input)).toBe(''); }); it('decodes UTF-8 byte array (Uint8Array)', function () { const decoder = new TextDecoder(); const input = new Uint8Array([...encodedMultibyteCharArray()]); expect(decoder.decode(input)).toBe('𝓽𝓮𝔁𝓽'); }); it('decodes GLib.Bytes', function () { const decoder = new TextDecoder(); const input = new GLib.Bytes(encodedMultibyteCharArray()); expect(decoder.decode(input)).toBe('𝓽𝓮𝔁𝓽'); }); it('ignores byte order marker (BOM)', function () { const decoder = new TextDecoder('utf-8', {ignoreBOM: true}); const input = new Uint8Array([ 0xef, 0xbb, 0xbf, ...encodedMultibyteCharArray(), ]); expect(decoder.decode(input)).toBe('𝓽𝓮𝔁𝓽'); }); it('handles invalid byte order marker (BOM)', function () { const decoder = new TextDecoder('utf-8', {ignoreBOM: true}); const input = new Uint8Array([ 0xef, 0xbb, 0x89, ...encodedMultibyteCharArray(), ]); expect(decoder.decode(input)).toBe('ﻉ𝓽𝓮𝔁𝓽'); }); }); describe('UTF-8 Encoding Converter', function () { it('can decode (not fatal)', function () { const decoder = new TextDecoder(); const decoded = decoder.decode(new Uint8Array([120, 193, 120])); expect(decoded).toEqual('x�x'); }); it('can decode (fatal)', function () { const decoder = new TextDecoder(undefined, { fatal: true, }); expect(() => { decoder.decode(new Uint8Array([120, 193, 120])); }).toThrowError( TypeError, /malformed UTF-8 character sequence/ ); }); }); describe('Multi-byte Encoding Converter (iconv)', function () { it('can decode Big-5', function () { const decoder = new TextDecoder('big5'); const bytes = [ 164, 164, 177, 192, 183, 124, 177, 181, 168, 252, 184, 103, 192, 217, 179, 161, 188, 208, 183, 199, 192, 203, 197, 231, 167, 189, 169, 101, 176, 85, ]; const decoded = decoder.decode(new Uint8Array(bytes)); expect(decoded).toEqual('中推會接受經濟部標準檢驗局委託'); }); it('can decode Big-5 with incorrect input bytes', function () { const decoder = new TextDecoder('big5'); const bytes = [ 164, 164, 177, 192, 183, 124, // Invalid byte... 0xa1, ]; const decoded = decoder.decode(new Uint8Array(bytes)); expect(decoded).toEqual('中推會�'); }); it('can decode Big-5 with long incorrect input bytes', function () { const decoder = new TextDecoder('big5'); const bytes = [164, 164, 177, 192, 183, 124]; const baseLength = 1000; const longBytes = new Array(baseLength) .fill(bytes, 0, baseLength) .flat(); // Append invalid byte sequence... longBytes.push(0xa3); const decoded = decoder.decode(new Uint8Array(longBytes)); const baseResult = '中推會'; const longResult = [ ...new Array(baseLength).fill(baseResult, 0, baseLength), '�', ].join(''); expect(decoded).toEqual(longResult); }); it('can decode Big-5 HKSCS with supplemental characters', function () { // The characters below roughly mean 'hard' or 'solid' and // 'rooster' respectively. They were chosen for their Unicode // and HKSCS positioning, not meaning. // Big5-HKSCS bytes for the supplemental character 𠕇 const supplementalBytes = [250, 64]; // Big5-HKSCS bytes for the non-supplemental characters 公雞 const nonSupplementalBytes = [164, 189, 194, 251]; const decoder = new TextDecoder('big5-hkscs'); // We currently allocate 12 additional bytes of padding // and a minimum of 256... // This should produce 400 non-supplemental bytes (50 * 2 * 4) // and 16 supplemental bytes (4 * 4) const repeatedNonSupplementalBytes = new Array(50).fill(nonSupplementalBytes).flat(); const bytes = [ ...repeatedNonSupplementalBytes, ...supplementalBytes, ...repeatedNonSupplementalBytes, ...supplementalBytes, ...repeatedNonSupplementalBytes, ...supplementalBytes, ...repeatedNonSupplementalBytes, ...supplementalBytes, ]; const expectedNonSupplemental = new Array(50).fill('公雞'); const expected = [ ...expectedNonSupplemental, '𠕇', ...expectedNonSupplemental, '𠕇', ...expectedNonSupplemental, '𠕇', ...expectedNonSupplemental, '𠕇', ].join(''); // Calculate the number of bytes the UTF-16 characters should // occupy. const expectedU16Bytes = [...expected].reduce((prev, next) => { const utf16code = next.codePointAt(0); // Test whether this unit is supplemental const additionalBytes = utf16code > 0xFFFF ? 2 : 0; return prev + 2 + additionalBytes; }, 0); // We set a minimum buffer allocation of 256 bytes, // this ensures that this test exceeds that. expect(expectedU16Bytes / 2).toBeGreaterThan(256); // The length of the input bytes should always be less // than the expected output because UTF-16 uses 4 bytes // to represent some characters HKSCS needs only 2 for. expect(bytes.length).toBeLessThan(expectedU16Bytes); // 4 supplemental characters, each with two additional bytes. expect(bytes.length + 4 * 2).toBe(expectedU16Bytes); const decoded = decoder.decode(new Uint8Array(bytes)); expect(decoded).toBe(expected); }); }); describe('Single Byte Encoding Converter', function () { it('can decode legacy single byte encoding (not fatal)', function () { const decoder = new TextDecoder('iso-8859-6'); const decoded = decoder.decode(new Uint8Array([161, 200, 200])); expect(decoded).toEqual('�بب'); }); it('can decode legacy single byte encoding (fatal)', function () { const decoder = new TextDecoder('iso-8859-6', { fatal: true, }); expect(() => { decoder.decode(new Uint8Array([161, 200, 200])); }).toThrowError(TypeError); }); it('can decode ASCII', function () { const input = new Uint8Array([0x89, 0x95, 0x9f, 0xbf]); const decoder = new TextDecoder('ascii'); expect(decoder.decode(input)).toBe('‰•Ÿ¿'); }); // Straight from https://encoding.spec.whatwg.org/encodings.json const encodingsTable = loadJSONFromResource( 'resource:///org/cjs/jsunit/modules/encodings.json' ); const singleByteEncodings = encodingsTable.filter(group => { return group.heading === 'Legacy single-byte encodings'; })[0].encodings; const buffer = new ArrayBuffer(255); const view = new Uint8Array(buffer); for (let i = 0, l = view.byteLength; i < l; i++) view[i] = i; for (let i = 0, l = singleByteEncodings.length; i < l; i++) { const encoding = singleByteEncodings[i]; it(`${encoding.name} can be decoded.`, function () { for (const label of encoding.labels) { const decoder = new TextDecoder(label); expect(() => decoder.decode(view)).not.toThrow(); expect(decoder.encoding).toBe( encoding.name.toLowerCase() ); } }); } }); }); }); cjs-128.1/installed-tests/js/testExceptions.js0000664000175000017500000002371315116312211020341 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Red Hat, Inc. // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. const {GIMarshallingTests, Gio, GLib, GObject} = imports.gi; const Foo = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE, ''), }, }, class Foo extends GObject.Object { set prop(v) { throw new Error('set'); } get prop() { throw new Error('get'); } }); const Bar = GObject.registerClass({ Properties: { 'prop': GObject.ParamSpec.string('prop', '', '', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, ''), }, }, class Bar extends GObject.Object {}); describe('Exceptions', function () { it('are thrown from property setter', function () { let foo = new Foo(); expect(() => (foo.prop = 'bar')).toThrowError(/set/); }); it('are thrown from property getter', function () { let foo = new Foo(); expect(() => foo.prop).toThrowError(/get/); }); // FIXME: In the next cases the errors aren't thrown but logged it('are logged from constructor', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); new Foo({prop: 'bar'}); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterFromConstructor'); }); it('are logged from property setter with binding', function () { let foo = new Foo(); let bar = new Bar(); bar.bind_property('prop', foo, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); // wake up the binding so that g_object_set() is called on foo bar.notify('prop'); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertySetterWithBinding'); }); it('are logged from property getter with binding', function () { let foo = new Foo(); let bar = new Bar(); foo.bind_property('prop', bar, 'prop', GObject.BindingFlags.DEFAULT); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: get*'); // wake up the binding so that g_object_get() is called on foo foo.notify('prop'); GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testExceptionInPropertyGetterWithBinding'); }); }); describe('logError', function () { afterEach(function () { GLib.test_assert_expected_messages_internal('Cjs', 'testExceptions.js', 0, 'testGErrorMessages'); }); it('logs a warning for a GError', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e); } }); it('logs a warning with a message if given', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e); } }); it('also logs an error for a created GError that is not thrown', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new Gio.IOErrorEnum({message: 'a message', code: 0})); }); it('logs an error created with the GLib.Error constructor', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*'); logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message')); }); it('logs the quark for a JS-created GError type', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error my-error: a message\nmarker@*'); logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message')); }); it('logs with stack for a GError created from a C struct', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: GLib.Error gi-marshalling-tests-gerror-domain: gi-marshalling-tests-gerror-message\nmarker@*'); logError(GIMarshallingTests.gerror_return()); }); // Now with prefix it('logs an error with a prefix if given', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: *'); try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { logError(e, 'prefix'); } }); it('logs an error with prefix and message', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*'); try { throw new Gio.IOErrorEnum({message: 'a message', code: 0}); } catch (e) { logError(e, 'prefix'); } }); describe('Syntax Error', function () { function throwSyntaxError() { Reflect.parse('!@#$%^&'); } it('logs a SyntaxError', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*'); try { throwSyntaxError(); } catch (e) { logError(e); } }); it('logs a stack trace with the SyntaxError', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: SyntaxError:*throwSyntaxError@*'); try { throwSyntaxError(); } catch (e) { logError(e); } }); }); it('logs an error with cause', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\nmarker@*Caused by: Gio.IOErrorEnum: another error\nmarker2@*'); function marker2() { return new Gio.IOErrorEnum({message: 'another error', code: 0}); } logError(new Error('an error', {cause: marker2()})); }); it('logs a GError with cause', function marker() { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Gio.IOErrorEnum: an error\nmarker@*Caused by: Error: another error\nmarker2@*'); function marker2() { return new Error('another error'); } const e = new Gio.IOErrorEnum({message: 'an error', code: 0}); e.cause = marker2(); logError(e); }); it('logs an error with non-object cause', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: an error\n*Caused by: 3'); logError(new Error('an error', {cause: 3})); }); it('logs an error with a cause tree', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*Caused by: Error: three\n*'); const three = new Error('three'); const two = new Error('two', {cause: three}); logError(new Error('one', {cause: two})); }); it('logs an error with cyclical causes', function () { // We cannot assert here with GLib.test_expect_message that the * at the // end of the string doesn't match more causes, but at least the idea is // that it shouldn't go into an infinite loop GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Error: one\n*Caused by: Error: two\n*'); const one = new Error('one'); one.cause = new Error('two', {cause: one}); logError(one); }); }); describe('Exception from function with too few arguments', function () { it('contains the full function name', function () { expect(() => GLib.get_locale_variants()) .toThrowError(/GLib\.get_locale_variants/); }); it('contains the full method name', function () { let file = Gio.File.new_for_path('foo'); expect(() => file.read()).toThrowError(/Gio\.File\.read/); }); }); describe('thrown GError', function () { let err; beforeEach(function () { try { let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (x) { err = x; } }); it('is an instance of error enum type', function () { expect(err).toEqual(jasmine.any(Gio.IOErrorEnum)); }); it('matches error domain and code', function () { expect(err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND)) .toBeTruthy(); }); it('has properties for domain and code', function () { expect(err.domain).toEqual(Gio.io_error_quark()); expect(err.code).toEqual(Gio.IOErrorEnum.NOT_FOUND); }); }); describe('GError.new_literal', function () { it('constructs a valid GLib.Error', function () { const e = GLib.Error.new_literal( Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'message'); expect(e instanceof GLib.Error).toBeTruthy(); expect(e.code).toEqual(Gio.IOErrorEnum.FAILED); expect(e.message).toEqual('message'); }); it('does not accept invalid domains', function () { expect(() => GLib.Error.new_literal(0, 0, 'message')) .toThrowError(/0 is not a valid domain/); }); }); cjs-128.1/installed-tests/js/testFormat.js0000664000175000017500000000417315116312211017447 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Red Hat, Inc. // eslint-disable-next-line no-restricted-properties const Format = imports.format; String.prototype.format = Format.format; describe('imports.format', function () { it('escapes % with another % character', function () { expect('%d%%'.format(10)).toEqual('10%'); }); it('formats a single string argument', function () { expect('%s'.format('Foo')).toEqual('Foo'); }); it('formats two string arguments', function () { expect('%s %s'.format('Foo', 'Bar')).toEqual('Foo Bar'); }); it('formats two swapped string arguments', function () { expect('%2$s %1$s'.format('Foo', 'Bar')).toEqual('Bar Foo'); }); it('formats a number in base 10', function () { expect('%d'.format(42)).toEqual('42'); }); it('formats a number in base 16', function () { expect('%x'.format(42)).toEqual('2a'); }); it('formats a floating point number with no precision', function () { expect('%f'.format(0.125)).toEqual('0.125'); }); it('formats a floating point number with precision 2', function () { expect('%.2f'.format(0.125)).toEqual('0.13'); }); it('pads with zeroes', function () { let zeroFormat = '%04d'; expect(zeroFormat.format(1)).toEqual('0001'); expect(zeroFormat.format(10)).toEqual('0010'); expect(zeroFormat.format(100)).toEqual('0100'); }); it('pads with spaces', function () { let spaceFormat = '%4d'; expect(spaceFormat.format(1)).toEqual(' 1'); expect(spaceFormat.format(10)).toEqual(' 10'); expect(spaceFormat.format(100)).toEqual(' 100'); }); it('throws an error when given incorrect modifiers for the conversion type', function () { expect(() => '%z'.format(42)).toThrow(); expect(() => '%.2d'.format(42)).toThrow(); expect(() => '%Ix'.format(42)).toThrow(); }); it('throws an error when incorrectly instructed to swap arguments', function () { expect(() => '%2$d %d %1$d'.format(1, 2, 3)).toThrow(); }); }); cjs-128.1/installed-tests/js/testFundamental.js0000664000175000017500000001357315116312211020461 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Lionel Landwerlin // SPDX-FileCopyrightText: 2021 Marco Trevisan const {GObject, Regress} = imports.gi; const TestObj = GObject.registerClass({ Signals: { 'test-fundamental-value-funcs': {param_types: [Regress.TestFundamentalObject.$gtype]}, 'test-fundamental-value-funcs-subtype': {param_types: [Regress.TestFundamentalSubObject.$gtype]}, 'test-fundamental-no-funcs': { param_types: Regress.TestFundamentalObjectNoGetSetFunc ? [Regress.TestFundamentalObjectNoGetSetFunc.$gtype] : [], }, 'test-fundamental-no-funcs-subtype': { param_types: Regress.TestFundamentalSubObjectNoGetSetFunc ? [Regress.TestFundamentalSubObjectNoGetSetFunc.$gtype] : [], }, }, }, class TestObj extends GObject.Object {}); describe('Fundamental type support', function () { it('can marshal a subtype of a custom fundamental type into a supertype GValue', function () { const fund = new Regress.TestFundamentalSubObject('plop'); expect(() => GObject.strdup_value_contents(fund)).not.toThrow(); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-value-funcs', signalSpy); obj.emit('test-fundamental-value-funcs', fund); expect(signalSpy).toHaveBeenCalledWith(obj, fund); }); it('can marshal a subtype of a custom fundamental type into a GValue', function () { const fund = new Regress.TestFundamentalSubObject('plop'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-value-funcs-subtype', signalSpy); obj.emit('test-fundamental-value-funcs-subtype', fund); expect(signalSpy).toHaveBeenCalledWith(obj, fund); }); it('can marshal a custom fundamental type into a GValue if contains a pointer and does not provide setter and getters', function () { const fund = new Regress.TestFundamentalObjectNoGetSetFunc('foo'); expect(() => GObject.strdup_value_contents(fund)).not.toThrow(); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-no-funcs', signalSpy); obj.connect('test-fundamental-no-funcs', (_o, f) => expect(f.get_data()).toBe('foo')); obj.emit('test-fundamental-no-funcs', fund); expect(signalSpy).toHaveBeenCalledWith(obj, fund); }); it('can marshal a subtype of a custom fundamental type into a GValue if contains a pointer and does not provide setter and getters', function () { const fund = new Regress.TestFundamentalSubObjectNoGetSetFunc('foo'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-no-funcs-subtype', signalSpy); obj.connect('test-fundamental-no-funcs-subtype', (_o, f) => expect(f.get_data()).toBe('foo')); obj.emit('test-fundamental-no-funcs-subtype', fund); expect(signalSpy).toHaveBeenCalledWith(obj, fund); }); it('cannot marshal a custom fundamental type into a GValue of different gtype', function () { const fund = new Regress.TestFundamentalObjectNoGetSetFunc('foo'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-value-funcs', signalSpy); expect(() => obj.emit('test-fundamental-value-funcs', fund)).toThrowError( / RegressTestFundamentalObjectNoGetSetFunc .* conversion to a GValue.* RegressTestFundamentalObject/); }); it('can marshal a custom fundamental type into a GValue of super gtype', function () { const fund = new Regress.TestFundamentalSubObjectNoGetSetFunc('foo'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-no-funcs', signalSpy); obj.connect('test-fundamental-no-funcs', (_o, f) => expect(f.get_data()).toBe('foo')); obj.emit('test-fundamental-no-funcs', fund); expect(signalSpy).toHaveBeenCalledWith(obj, fund); }); it('cannot marshal a custom fundamental type into a GValue of sub gtype', function () { const fund = new Regress.TestFundamentalObjectNoGetSetFunc('foo'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-no-funcs-subtype', signalSpy); expect(() => obj.emit('test-fundamental-no-funcs-subtype', fund)).toThrowError( / RegressTestFundamentalObjectNoGetSetFunc .* conversion to a GValue.* RegressTestFundamentalSubObjectNoGetSetFunc/); }); it('can marshal a custom fundamental type into a transformable type', function () { Regress.TestFundamentalObjectNoGetSetFunc.make_compatible_with_fundamental_sub_object(); const fund = new Regress.TestFundamentalObjectNoGetSetFunc('foo'); const obj = new TestObj(); const signalSpy = jasmine.createSpy('signalSpy'); obj.connect('test-fundamental-value-funcs-subtype', signalSpy); obj.connect('test-fundamental-value-funcs-subtype', (_o, f) => expect(f instanceof Regress.TestFundamentalSubObject).toBeTrue()); obj.emit('test-fundamental-value-funcs-subtype', fund); expect(signalSpy).toHaveBeenCalled(); }); it('can marshal to a null value', function () { const v = new GObject.Value(); expect(v.init(Regress.TestFundamentalObject.$gtype)).toBeNull(); }); it('can marshal to a null value if has no getter function', function () { const v = new GObject.Value(); expect(v.init(Regress.TestFundamentalObjectNoGetSetFunc.$gtype)).toBeNull(); }); }); cjs-128.1/installed-tests/js/testGDBus.js0000664000175000017500000010010315116312211017151 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC const {Gio, CjsTestTools, GLib} = imports.gi; // Adapter for compatibility with pre-GLib-2.80 let GioUnix; if (imports.gi.versions.GioUnix === '2.0') { GioUnix = imports.gi.GioUnix; } else { GioUnix = { InputStream: Gio.UnixInputStream, }; } /* The methods list with their signatures. * * *** NOTE: If you add stuff here, you need to update the Test class below. */ var TestIface = ` `; const PROP_READ_ONLY_INITIAL_VALUE = Math.random(); const PROP_READ_WRITE_INITIAL_VALUE = 58; const PROP_WRITE_ONLY_INITIAL_VALUE = 'Initial value'; /* Test is the actual object exporting the dbus methods */ class Test { constructor() { this._propReadOnly = PROP_READ_ONLY_INITIAL_VALUE; this._propWriteOnly = PROP_WRITE_ONLY_INITIAL_VALUE; this._propReadWrite = PROP_READ_WRITE_INITIAL_VALUE; this._impl = Gio.DBusExportedObject.wrapJSObject(TestIface, this); this._impl.export(Gio.DBus.session, '/org/cinnamon/cjs/Test'); } frobateStuff() { return {hello: new GLib.Variant('s', 'world')}; } nonJsonFrobateStuff(i) { if (i === 42) return '42 it is!'; else return 'Oops'; } alwaysThrowException() { throw Error('Exception!'); } thisDoesNotExist() { /* We'll remove this later! */ } noInParameter() { return 'Yes!'; } multipleInArgs(a, b, c, d, e) { return `${a} ${b} ${c} ${d} ${e}`; } emitPropertyChanged(name, value) { this._impl.emit_property_changed(name, value); } emitSignal() { this._impl.emit_signal('signalFoo', GLib.Variant.new('(s)', ['foobar'])); } noReturnValue() { /* Empty! */ } /* The following two functions have identical return values * in JS, but the bus message will be different. * multipleOutValues is "sss", while oneArrayOut is "as" */ multipleOutValues() { return ['Hello', 'World', '!']; } oneArrayOut() { return ['Hello', 'World', '!']; } /* Same thing again. In this case multipleArrayOut is "asas", * while arrayOfArrayOut is "aas". */ multipleArrayOut() { return [['Hello', 'World'], ['World', 'Hello']]; } arrayOfArrayOut() { return [['Hello', 'World'], ['World', 'Hello']]; } arrayOutBadSig() { return Symbol('Hello World!'); } byteArrayEcho(binaryString) { return binaryString; } byteEcho(aByte) { return aByte; } dictEcho(dict) { return dict; } /* This one is implemented asynchronously. Returns * the input arguments */ echoAsync(parameters, invocation) { var [someString, someInt] = parameters; GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { invocation.return_value(new GLib.Variant('(si)', [someString, someInt])); return false; }); } // double get PropReadOnly() { return this._propReadOnly; } // string set PropWriteOnly(value) { this._propWriteOnly = value; } // variant get PropReadWrite() { return new GLib.Variant('s', this._propReadWrite.toString()); } set PropReadWrite(value) { this._propReadWrite = value.deepUnpack(); } get PropPrePacked() { return new GLib.Variant('a{sv}', { member: GLib.Variant.new_string('value'), }); } structArray() { return [[128, 123456], [42, 654321]]; } fdIn(fdIndex, fdList) { const fd = fdList.get(fdIndex); const stream = new GioUnix.InputStream({fd, closeFd: true}); const bytes = stream.read_bytes(4096, null); return bytes; } // Same as fdIn(), but implemented asynchronously fdIn2Async([fdIndex], invocation, fdList) { const fd = fdList.get(fdIndex); const stream = new GioUnix.InputStream({fd, closeFd: true}); stream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null, (obj, res) => { const bytes = obj.read_bytes_finish(res); invocation.return_value(new GLib.Variant('(ay)', [bytes])); }); } fdOut(bytes) { const fd = CjsTestTools.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); return [0, fdList]; } fdOut2Async([bytes], invocation) { GLib.idle_add(GLib.PRIORITY_DEFAULT, function () { const fd = CjsTestTools.open_bytes(bytes); const fdList = Gio.UnixFDList.new_from_array([fd]); invocation.return_value_with_unix_fd_list(new GLib.Variant('(h)', [0]), fdList); return GLib.SOURCE_REMOVE; }); } } const ProxyClass = Gio.DBusProxy.makeProxyWrapper(TestIface); describe('Exported DBus object', function () { let ownNameID; var test; var proxy; let loop; const expectedBytes = new TextEncoder().encode('some bytes'); function waitForServerProperty(property, value = undefined, timeout = 500) { let waitId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout, () => { waitId = 0; throw new Error(`Timeout waiting for property ${property} expired`); }); while (waitId && (!test[property] || value !== undefined && test[property] !== value)) loop.get_context().iteration(true); if (waitId) GLib.source_remove(waitId); expect(waitId).not.toBe(0); return test[property]; } beforeAll(function () { loop = new GLib.MainLoop(null, false); test = new Test(); ownNameID = Gio.DBus.session.own_name('org.cinnamon.cjs.Test', Gio.BusNameOwnerFlags.NONE, name => { log(`Acquired name ${name}`); loop.quit(); }, name => { log(`Lost name ${name}`); }); loop.run(); new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', (obj, error) => { expect(error).toBeNull(); proxy = obj; expect(proxy).not.toBeNull(); loop.quit(); }, Gio.DBusProxyFlags.NONE); loop.run(); }); afterAll(function () { // Not really needed, but if we don't cleanup // memory checking will complain Gio.DBus.session.unown_name(ownNameID); }); beforeEach(function () { loop = new GLib.MainLoop(null, false); }); it('can call a remote method', function () { proxy.frobateStuffRemote({}, ([result], excp) => { expect(excp).toBeNull(); expect(result.hello.deepUnpack()).toEqual('world'); loop.quit(); }); loop.run(); }); it('can call a method with async/await', async function () { const [{hello}] = await proxy.frobateStuffAsync({}); expect(hello.deepUnpack()).toEqual('world'); }); it('can initiate a proxy with promise and call a method with async/await', async function () { const asyncProxy = await ProxyClass.newAsync(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test'); expect(asyncProxy).toBeInstanceOf(Gio.DBusProxy); const [{hello}] = await asyncProxy.frobateStuffAsync({}); expect(hello.deepUnpack()).toEqual('world'); }); it('can call a remote method when not using makeProxyWrapper', function () { let info = Gio.DBusNodeInfo.new_for_xml(TestIface); let iface = info.interfaces[0]; let otherProxy = null; Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.DO_NOT_AUTO_START, iface, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', iface.name, null, (o, res) => { otherProxy = Gio.DBusProxy.new_for_bus_finish(res); loop.quit(); }); loop.run(); otherProxy.frobateStuffRemote({}, ([result], excp) => { expect(excp).toBeNull(); expect(result.hello.deepUnpack()).toEqual('world'); loop.quit(); }); loop.run(); }); /* excp must be exactly the exception thrown by the remote method (more or less) */ it('can handle an exception thrown by a remote method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); proxy.alwaysThrowExceptionRemote({}, function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by a method with async/await', async function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); await expectAsync(proxy.alwaysThrowExceptionAsync({})).toBeRejected(); }); it('can still destructure the return value when an exception is thrown', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); // This test will not fail, but instead if the functionality is not // implemented correctly it will hang. The exception in the function // argument destructuring will not propagate across the FFI boundary // and the main loop will never quit. // https://bugzilla.gnome.org/show_bug.cgi?id=729015 proxy.alwaysThrowExceptionRemote({}, function ([a, b, c], excp) { expect(a).not.toBeDefined(); expect(b).not.toBeDefined(); expect(c).not.toBeDefined(); void excp; loop.quit(); }); loop.run(); }); it('throws an exception when trying to call a method that does not exist', function () { /* First remove the method from the object! */ delete Test.prototype.thisDoesNotExist; proxy.thisDoesNotExistRemote(function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('throws an exception when trying to call an async method that does not exist', async function () { delete Test.prototype.thisDoesNotExist; await expectAsync(proxy.thisDoesNotExistAsync()).toBeRejected(); }); it('can pass a parameter to a remote method that is not a JSON object', function () { proxy.nonJsonFrobateStuffRemote(42, ([result], excp) => { expect(result).toEqual('42 it is!'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can pass a parameter to a method with async/await that is not a JSON object', async function () { const [result] = await proxy.nonJsonFrobateStuffAsync(1); expect(result).toEqual('Oops'); }); it('can call a remote method with no in parameter', function () { proxy.noInParameterRemote(([result], excp) => { expect(result).toEqual('Yes!'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call an async/await method with no in parameter', async function () { const [result] = await proxy.noInParameterAsync(); expect(result).toEqual('Yes!'); }); it('can call a remote method with multiple in parameters', function () { proxy.multipleInArgsRemote(1, 2, 3, 4, 5, ([result], excp) => { expect(result).toEqual('1 2 3 4 5'); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call an async/await method with multiple in parameters', async function () { const [result] = await proxy.multipleInArgsAsync(1, 2, 3, 4, 5); expect(result).toEqual('1 2 3 4 5'); }); it('can call a remote method with no return value', function () { proxy.noReturnValueRemote(([result], excp) => { expect(result).not.toBeDefined(); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call an async/await method with no return value', async function () { const [result] = await proxy.noReturnValueAsync(); expect(result).not.toBeDefined(); }); it('can emit a DBus signal', function () { let handler = jasmine.createSpy('signalFoo'); let id = proxy.connectSignal('signalFoo', handler); handler.and.callFake(() => proxy.disconnectSignal(id)); proxy.emitSignalRemote(([result], excp) => { expect(result).not.toBeDefined(); expect(excp).toBeNull(); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), ['foobar']); loop.quit(); }); loop.run(); }); it('can emit a DBus signal with async/await', async function () { const handler = jasmine.createSpy('signalFoo'); const id = proxy.connectSignal('signalFoo', handler); handler.and.callFake(() => proxy.disconnectSignal(id)); const [result] = await proxy.emitSignalAsync(); expect(result).not.toBeDefined(); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), ['foobar']); }); it('can call a remote method with multiple return values', function () { proxy.multipleOutValuesRemote(function (result, excp) { expect(result).toEqual(['Hello', 'World', '!']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can call an async/await method with multiple return values', async function () { const results = await proxy.multipleOutValuesAsync(); expect(results).toEqual(['Hello', 'World', '!']); }); it('does not coalesce one array into the array of return values', function () { proxy.oneArrayOutRemote(([result], excp) => { expect(result).toEqual(['Hello', 'World', '!']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('does not coalesce one array into the array of return values with async/await', async function () { const [result] = await proxy.oneArrayOutAsync(); expect(result).toEqual(['Hello', 'World', '!']); }); it('does not coalesce an array of arrays into the array of return values', function () { proxy.arrayOfArrayOutRemote(([[a1, a2]], excp) => { expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('does not coalesce an array of arrays into the array of return values with async/await', async function () { const [[a1, a2]] = await proxy.arrayOfArrayOutAsync(); expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); }); it('can return multiple arrays from a remote method', function () { proxy.multipleArrayOutRemote(([a1, a2], excp) => { expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); expect(excp).toBeNull(); loop.quit(); }); loop.run(); }); it('can return multiple arrays from an async/await method', async function () { const [a1, a2] = await proxy.multipleArrayOutAsync(); expect(a1).toEqual(['Hello', 'World']); expect(a2).toEqual(['World', 'Hello']); }); it('handles a bad signature by throwing an exception', function () { proxy.arrayOutBadSigRemote(function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('handles a bad signature in async/await by rejecting the promise', async function () { await expectAsync(proxy.arrayOutBadSigAsync()).toBeRejected(); }); it('can call a remote method that is implemented asynchronously', function () { let someString = 'Hello world!'; let someInt = 42; proxy.echoRemote(someString, someInt, function (result, excp) { expect(excp).toBeNull(); expect(result).toEqual([someString, someInt]); loop.quit(); }); loop.run(); }); it('can call an async/await method that is implemented asynchronously', async function () { const someString = 'Hello world!'; const someInt = 42; const results = await proxy.echoAsync(someString, someInt); expect(results).toEqual([someString, someInt]); }); it('can send and receive bytes from a remote method', function () { let someBytes = [0, 63, 234]; someBytes.forEach(b => { proxy.byteEchoRemote(b, ([result], excp) => { expect(excp).toBeNull(); expect(result).toEqual(b); loop.quit(); }); loop.run(); }); }); it('can send and receive bytes from an async/await method', async function () { let someBytes = [0, 63, 234]; await Promise.allSettled(someBytes.map(async b => { const [byte] = await proxy.byteEchoAsync(b); expect(byte).toEqual(b); })); }); it('can call a remote method that returns an array of structs', function () { proxy.structArrayRemote(([result], excp) => { expect(excp).toBeNull(); expect(result).toEqual([[128, 123456], [42, 654321]]); loop.quit(); }); loop.run(); }); it('can call an async/await method that returns an array of structs', async function () { const [result] = await proxy.structArrayAsync(); expect(result).toEqual([[128, 123456], [42, 654321]]); }); it('can send and receive dicts from a remote method', function () { let someDict = { aDouble: new GLib.Variant('d', 10), // should be an integer after round trip anInteger: new GLib.Variant('i', 10.5), // should remain a double aDoubleBeforeAndAfter: new GLib.Variant('d', 10.5), }; proxy.dictEchoRemote(someDict, ([result], excp) => { expect(excp).toBeNull(); expect(result).not.toBeNull(); // verify the fractional part was dropped off int expect(result['anInteger'].deepUnpack()).toEqual(10); // and not dropped off a double expect(result['aDoubleBeforeAndAfter'].deepUnpack()).toEqual(10.5); // check without type conversion expect(result['aDouble'].deepUnpack()).toBe(10.0); loop.quit(); }); loop.run(); }); it('can send and receive dicts from an async/await method', async function () { // See notes in test above const [result] = await proxy.dictEchoAsync({ aDouble: new GLib.Variant('d', 10), anInteger: new GLib.Variant('i', 10.5), aDoubleBeforeAndAfter: new GLib.Variant('d', 10.5), }); expect(result).not.toBeNull(); expect(result['anInteger'].deepUnpack()).toEqual(10); expect(result['aDoubleBeforeAndAfter'].deepUnpack()).toEqual(10.5); expect(result['aDouble'].deepUnpack()).toBe(10.0); }); it('can call a remote method with a Unix FD', function (done) { const fd = CjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdInRemote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); expect(outFdList).toBeNull(); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an async/await method with a Unix FD', async function () { const fd = CjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); const [bytes, outFdList] = await proxy.fdInAsync(0, fdList); expect(outFdList).not.toBeDefined(); expect(bytes).toEqual(expectedBytes); }); it('can call an asynchronously implemented remote method with a Unix FD', function (done) { const fd = CjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); proxy.fdIn2Remote(0, fdList, ([bytes], exc, outFdList) => { expect(exc).toBeNull(); expect(outFdList).toBeNull(); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an asynchronously implemented async/await method with a Unix FD', async function () { const fd = CjsTestTools.open_bytes(expectedBytes); const fdList = Gio.UnixFDList.new_from_array([fd]); const [bytes, outFdList] = await proxy.fdIn2Async(0, fdList); expect(outFdList).not.toBeDefined(); expect(bytes).toEqual(expectedBytes); }); function readBytesFromFdSync(fd) { const stream = new GioUnix.InputStream({fd, closeFd: true}); const bytes = stream.read_bytes(4096, null); return bytes.toArray(); } it('can call a remote method that returns a Unix FD', function (done) { proxy.fdOutRemote(expectedBytes, ([fdIndex], exc, outFdList) => { expect(exc).toBeNull(); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an async/await method that returns a Unix FD', async function () { const [fdIndex, outFdList] = await proxy.fdOutAsync(expectedBytes); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); }); it('can call an asynchronously implemented remote method that returns a Unix FD', function (done) { proxy.fdOut2Remote(expectedBytes, ([fdIndex], exc, outFdList) => { expect(exc).toBeNull(); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); done(); }); }); it('can call an asynchronously implemented asyc/await method that returns a Unix FD', async function () { const [fdIndex, outFdList] = await proxy.fdOut2Async(expectedBytes); const bytes = readBytesFromFdSync(outFdList.get(fdIndex)); expect(bytes).toEqual(expectedBytes); }); it('throws an exception when not passing a Gio.UnixFDList to a method that requires one', function () { expect(() => proxy.fdInRemote(0, () => {})).toThrow(); }); it('rejects the promise when not passing a Gio.UnixFDList to an async method that requires one', async function () { await expectAsync(proxy.fdInAsync(0)).toBeRejected(); }); it('throws an exception when passing a handle out of range of a Gio.UnixFDList', function () { const fdList = new Gio.UnixFDList(); expect(() => proxy.fdInRemote(0, fdList, () => {})).toThrow(); }); it('rejects the promise when async passing a handle out of range of a Gio.UnixFDList', async function () { const fdList = new Gio.UnixFDList(); await expectAsync(proxy.fdInAsync(0, fdList)).toBeRejected(); }); it('Has defined properties', function () { expect(Object.hasOwn(proxy, 'PropReadWrite')).toBeTruthy(); expect(Object.hasOwn(proxy, 'PropReadOnly')).toBeTruthy(); expect(Object.hasOwn(proxy, 'PropWriteOnly')).toBeTruthy(); expect(Object.hasOwn(proxy, 'PropPrePacked')).toBeTruthy(); }); it('reading readonly property works', function () { expect(proxy.PropReadOnly).toEqual(PROP_READ_ONLY_INITIAL_VALUE); }); it('reading readwrite property works', function () { expect(proxy.PropReadWrite).toEqual( GLib.Variant.new_string(PROP_READ_WRITE_INITIAL_VALUE.toString())); }); it('reading writeonly throws an error', function () { expect(() => proxy.PropWriteOnly).toThrowError('Property PropWriteOnly is not readable'); }); it('Setting a readwrite property works', function () { let testStr = 'GjsVariantValue'; expect(() => { proxy.PropReadWrite = GLib.Variant.new_string(testStr); }).not.toThrow(); expect(proxy.PropReadWrite.deepUnpack()).toEqual(testStr); expect(waitForServerProperty('_propReadWrite', testStr)).toEqual(testStr); }); it('Setting a writeonly property works', function () { let testValue = Math.random().toString(); expect(() => { proxy.PropWriteOnly = testValue; }).not.toThrow(); expect(() => proxy.PropWriteOnly).toThrow(); expect(waitForServerProperty('_propWriteOnly', testValue)).toEqual(testValue); }); it('Setting a readonly property throws an error', function () { let testValue = Math.random().toString(); expect(() => { proxy.PropReadOnly = testValue; }).toThrowError('Property PropReadOnly is not writable'); expect(proxy.PropReadOnly).toBe(PROP_READ_ONLY_INITIAL_VALUE); }); it('Reading a property that prepacks the return value works', function () { expect(proxy.PropPrePacked.member).toBeInstanceOf(GLib.Variant); expect(proxy.PropPrePacked.member.get_type_string()).toEqual('s'); }); it('Marking a property as invalidated works', function () { let changedProps = {}; let invalidatedProps = []; proxy.connect('g-properties-changed', (proxy_, changed, invalidated) => { changedProps = changed.deepUnpack(); invalidatedProps = invalidated; loop.quit(); }); test.emitPropertyChanged('PropReadOnly', null); loop.run(); expect(changedProps).not.toContain('PropReadOnly'); expect(invalidatedProps).toContain('PropReadOnly'); }); }); describe('DBus Proxy wrapper', function () { let loop; let wasPromise; let writerFunc; beforeAll(function () { loop = new GLib.MainLoop(null, false); wasPromise = Gio.DBusProxy.prototype._original_init_async instanceof Function; Gio._promisify(Gio.DBusProxy.prototype, 'init_async'); writerFunc = jasmine.createSpy( 'log writer func', (level, fields) => { const decoder = new TextDecoder('utf-8'); const domain = decoder.decode(fields?.GLIB_DOMAIN); const message = `${decoder.decode(fields?.MESSAGE)}\n`; level |= GLib.LogLevelFlags.FLAG_RECURSION; GLib.log_default_handler(domain, level, message, null); return GLib.LogWriterOutput.HANDLED; }); writerFunc.and.callThrough(); GLib.log_set_writer_func(writerFunc); }); beforeEach(function () { writerFunc.calls.reset(); }); it('init failures are reported in sync mode', function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); expect(() => new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', Gio.DBusProxyFlags.NONE, cancellable)).toThrow(); }); it('init failures are reported in async mode', function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); const initDoneSpy = jasmine.createSpy( 'init finish func', () => loop.quit()); initDoneSpy.and.callThrough(); new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', initDoneSpy, cancellable, Gio.DBusProxyFlags.NONE); loop.run(); expect(initDoneSpy).toHaveBeenCalledTimes(1); const {args: callArgs} = initDoneSpy.calls.mostRecent(); expect(callArgs.at(0)).toBeNull(); expect(callArgs.at(1).matches( Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)).toBeTrue(); }); it('can init a proxy asynchronously when promisified', function () { new ProxyClass(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', () => loop.quit(), Gio.DBusProxyFlags.NONE); loop.run(); expect(writerFunc).not.toHaveBeenCalled(); }); it('can create a proxy from a promise', async function () { const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test'); await expectAsync(proxyPromise).toBeResolved(); }); it('can create fail a proxy from a promise', async function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); const proxyPromise = ProxyClass.newAsync(Gio.DBus.session, 'org.cinnamon.cjs.Test', '/org/cinnamon/cjs/Test', cancellable); await expectAsync(proxyPromise).toBeRejected(); }); afterAll(function () { if (!wasPromise) { // Remove stuff added by Gio._promisify, this can be not needed in future // nor should break other tests, but we didn't want to depend on those. expect(Gio.DBusProxy.prototype._original_init_async).toBeInstanceOf(Function); Gio.DBusProxy.prototype.init_async = Gio.DBusProxy.prototype._original_init_async; delete Gio.DBusProxy.prototype._original_init_async; } }); }); cjs-128.1/installed-tests/js/testGIMarshalling.js0000664000175000017500000025772615116312211020716 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 Collabora, Ltd. // SPDX-FileCopyrightText: 2010 litl, LLC // SPDX-FileCopyrightText: 2010 Giovanni Campagna // SPDX-FileCopyrightText: 2011 Red Hat, Inc. // SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. // SPDX-FileCopyrightText: 2019 Philip Chimento // Load overrides for GIMarshallingTests imports.overrides.searchPath.unshift('resource:///org/cjs/jsunit/modules/overrides'); const GIMarshallingTests = imports.gi.GIMarshallingTests; // We use Gio and GLib to have some objects that we know exist const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; // Some helpers to cut down on repetitive marshalling tests. // - options.omit: the test doesn't exist, don't create a test case // - options.skip: the test does exist, but doesn't pass, either unsupported or // a bug in GJS. Create the test case and mark it pending function testReturnValue(root, value, {omit, skip, funcName = `${root}_return`} = {}) { if (omit) return; it('marshals as a return value', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName]()).toEqual(value); }); } function testInParameter(root, value, {omit, skip, funcName = `${root}_in`} = {}) { if (omit) return; it('marshals as an in parameter', function () { if (skip) pending(skip); expect(() => GIMarshallingTests[funcName](value)).not.toThrow(); }); } function testOutParameter(root, value, {omit, skip, funcName = `${root}_out`} = {}) { if (omit) return; it('marshals as an out parameter', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName]()).toEqual(value); }); } function testUninitializedOutParameter(root, defaultValue, {omit, skip, funcName = `${root}_out_uninitialized`} = {}) { if (omit) return; it("picks a reasonable default value when the function doesn't set the out parameter", function () { if (skip) pending(skip); const [success, defaultVal] = GIMarshallingTests[funcName](); expect(success).toBeFalse(); expect(defaultVal).toEqual(defaultValue); }); } function testInoutParameter(root, inValue, outValue, {omit, skip, funcName = `${root}_inout`} = {}) { if (omit) return; it('marshals as an inout parameter', function () { if (skip) pending(skip); expect(GIMarshallingTests[funcName](inValue)).toEqual(outValue); }); } function testSimpleMarshalling(root, value, inoutValue, defaultValue, options = {}) { testReturnValue(root, value, options.returnv); testInParameter(root, value, options.in); testOutParameter(root, value, options.out); testUninitializedOutParameter(root, defaultValue, options.uninitOut); testInoutParameter(root, value, inoutValue, options.inout); } function testTransferMarshalling(root, value, inoutValue, defaultValue, options = {}) { describe('with transfer none', function () { testSimpleMarshalling(`${root}_none`, value, inoutValue, defaultValue, options.none); }); describe('with transfer full', function () { const fullOptions = { in: { omit: true, // this case is not in the test suite }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192', }, }; Object.assign(fullOptions, options.full); testSimpleMarshalling(`${root}_full`, value, inoutValue, defaultValue, fullOptions); }); } function testContainerMarshalling(root, value, inoutValue, defaultValue, options = {}) { testTransferMarshalling(root, value, inoutValue, defaultValue, options); describe('with transfer container', function () { const containerOptions = { in: { omit: true, // this case is not in the test suite }, inout: { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }, }; Object.assign(containerOptions, options.container); testSimpleMarshalling(`${root}_container`, value, inoutValue, defaultValue, containerOptions); }); } // Integer limits, defined without reference to GLib (because the GLib.MAXINT8 // etc. constants are also subject to marshalling) const Limits = { int8: { min: -(2 ** 7), max: 2 ** 7 - 1, umax: 2 ** 8 - 1, }, int16: { min: -(2 ** 15), max: 2 ** 15 - 1, umax: 2 ** 16 - 1, }, int32: { min: -(2 ** 31), max: 2 ** 31 - 1, umax: 2 ** 32 - 1, }, int64: { min: -(2 ** 63), max: 2 ** 63 - 1, umax: 2 ** 64 - 1, bit64: true, // note: unsafe, values will not be accurate! }, short: {}, int: {}, long: {}, ssize: { utype: 'size', }, }; const BigIntLimits = { int64: { min: -(2n ** 63n), max: 2n ** 63n - 1n, umax: 2n ** 64n - 1n, }, }; Object.assign(Limits.short, Limits.int16); Object.assign(Limits.int, Limits.int32); // Platform dependent sizes; expand definitions as needed if (GLib.SIZEOF_LONG === 8) { Object.assign(Limits.long, Limits.int64); BigIntLimits.long = Object.assign({}, BigIntLimits.int64); } else { Object.assign(Limits.long, Limits.int32); } if (GLib.SIZEOF_SSIZE_T === 8) { Object.assign(Limits.ssize, Limits.int64); BigIntLimits.ssize = Object.assign({utype: 'size'}, BigIntLimits.int64); } else { Object.assign(Limits.ssize, Limits.int32); } // Functions for dealing with tests that require or return unsafe 64-bit ints, // until we get BigInts. // Sometimes tests pass if we are comparing two inaccurate values in JS with // each other. That's fine for now. Then we just have to suppress the warnings. function warn64(is64bit, func, ...args) { if (is64bit) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); } const retval = func(...args); if (is64bit) { GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'Ignore message'); } return retval; } // Other times we compare an inaccurate value marshalled from JS into C, with an // accurate value in C. Those tests we have to skip. function skip64(is64bit) { if (is64bit) pending('https://gitlab.gnome.org/GNOME/gjs/issues/271'); } describe('Boolean', function () { [true, false].forEach(bool => { describe(`${bool}`, function () { testSimpleMarshalling('boolean', bool, !bool, false, { returnv: { funcName: `boolean_return_${bool}`, }, in: { funcName: `boolean_in_${bool}`, }, out: { funcName: `boolean_out_${bool}`, }, uninitOut: { omit: true, }, inout: { funcName: `boolean_inout_${bool}_${!bool}`, }, }); }); }); testUninitializedOutParameter('boolean', false); }); describe('Integer', function () { Object.entries(Limits).forEach(([type, {min, max, umax, bit64, utype = `u${type}`}]) => { describe(`${type}-typed`, function () { it('marshals signed value as a return value', function () { expect(warn64(bit64, GIMarshallingTests[`${type}_return_max`])).toEqual(max); expect(warn64(bit64, GIMarshallingTests[`${type}_return_min`])).toEqual(min); }); it('marshals signed value as an in parameter', function () { skip64(bit64); expect(() => GIMarshallingTests[`${type}_in_max`](max)).not.toThrow(); expect(() => GIMarshallingTests[`${type}_in_min`](min)).not.toThrow(); }); it('marshals signed value as an out parameter', function () { expect(warn64(bit64, GIMarshallingTests[`${type}_out_max`])).toEqual(max); expect(warn64(bit64, GIMarshallingTests[`${type}_out_min`])).toEqual(min); }); testUninitializedOutParameter(type, 0); it('marshals as an inout parameter', function () { skip64(bit64); expect(GIMarshallingTests[`${type}_inout_max_min`](max)).toEqual(min); expect(GIMarshallingTests[`${type}_inout_min_max`](min)).toEqual(max); }); it('marshals unsigned value as a return value', function () { expect(warn64(bit64, GIMarshallingTests[`${utype}_return`])).toEqual(umax); }); it('marshals unsigned value as an in parameter', function () { skip64(bit64); expect(() => GIMarshallingTests[`${utype}_in`](umax)).not.toThrow(); }); it('marshals unsigned value as an out parameter', function () { expect(warn64(bit64, GIMarshallingTests[`${utype}_out`])).toEqual(umax); }); testUninitializedOutParameter(utype, 0); it('marshals unsigned value as an inout parameter', function () { skip64(bit64); expect(GIMarshallingTests[`${utype}_inout`](umax)).toEqual(0); }); }); }); }); describe('BigInt', function () { Object.entries(BigIntLimits).forEach(([type, {min, max, umax, utype = `u${type}`}]) => { describe(`${type}-typed`, function () { it('marshals signed value as an in parameter', function () { expect(() => GIMarshallingTests[`${type}_in_max`](max)).not.toThrow(); expect(() => GIMarshallingTests[`${type}_in_min`](min)).not.toThrow(); }); it('marshals unsigned value as an in parameter', function () { expect(() => GIMarshallingTests[`${utype}_in`](umax)).not.toThrow(); }); }); }); }); describe('Floating point', function () { const FloatLimits = { float: { min: 2 ** -126, max: (2 - 2 ** -23) * 2 ** 127, }, double: { // GLib.MINDOUBLE is the minimum normal value, which is not the same // as the minimum denormal value Number.MIN_VALUE min: 2 ** -1022, max: Number.MAX_VALUE, }, }; Object.entries(FloatLimits).forEach(([type, {min, max}]) => { describe(`${type}-typed`, function () { it('marshals value as a return value', function () { expect(GIMarshallingTests[`${type}_return`]()).toBeCloseTo(max, 10); }); testInParameter(type, max); it('marshals value as an out parameter', function () { expect(GIMarshallingTests[`${type}_out`]()).toBeCloseTo(max, 10); }); testUninitializedOutParameter(type, 0); it('marshals value as an inout parameter', function () { expect(GIMarshallingTests[`${type}_inout`](max)).toBeCloseTo(min, 10); }); it('can handle noncanonical NaN', function () { expect(GIMarshallingTests[`${type}_noncanonical_nan_out`]()).toBeNaN(); }); }); }); }); describe('time_t', function () { testSimpleMarshalling('time_t', 1234567890, 0, 0); }); describe('GType', function () { describe('void', function () { testSimpleMarshalling('gtype', GObject.TYPE_NONE, GObject.TYPE_INT, null); }); describe('string', function () { testSimpleMarshalling('gtype_string', GObject.TYPE_STRING, null, null, { inout: {omit: true}, uninitOut: {omit: true}, }); }); it('can be implicitly converted from a GObject type alias', function () { expect(() => GIMarshallingTests.gtype_in(GObject.VoidType)).not.toThrow(); }); it('can be implicitly converted from a JS type', function () { expect(() => GIMarshallingTests.gtype_string_in(String)).not.toThrow(); }); }); describe('UTF-8 string', function () { testTransferMarshalling('utf8', 'const ♥ utf8', '', null, { full: { uninitOut: {omit: true}, // covered by utf8_dangling_out() test below }, }); it('marshals value as a byte array', function () { expect(() => GIMarshallingTests.utf8_as_uint8array_in('const ♥ utf8')).not.toThrow(); }); it('makes a default out value for a broken C function', function () { expect(GIMarshallingTests.utf8_dangling_out()).toBeNull(); }); }); describe('In-out array in the style of gtk_init()', function () { it('marshals null', function () { const [, newArray] = GIMarshallingTests.init_function(null); expect(newArray).toEqual([]); }); it('marshals an inout empty array', function () { const [ret, newArray] = GIMarshallingTests.init_function([]); expect(ret).toBeTrue(); expect(newArray).toEqual([]); }); it('marshals an inout array', function () { const [ret, newArray] = GIMarshallingTests.init_function(['--foo', '--bar']); expect(ret).toBeTrue(); expect(newArray).toEqual(['--foo']); }); }); describe('Fixed-size C array', function () { describe('of ints', function () { testReturnValue('array_fixed_int', [-1, 0, 1, 2]); testInParameter('array_fixed_int', [-1, 0, 1, 2]); testOutParameter('array_fixed', [-1, 0, 1, 2]); testOutParameter('array_fixed_caller_allocated', [-1, 0, 1, 2]); testInoutParameter('array_fixed', [-1, 0, 1, 2], [2, 1, 0, -1]); }); describe('of shorts', function () { testReturnValue('array_fixed_short', [-1, 0, 1, 2]); testInParameter('array_fixed_short', [-1, 0, 1, 2]); }); it('marshals a struct array as an out parameter', function () { expect(GIMarshallingTests.array_fixed_out_struct()).toEqual([ jasmine.objectContaining({long_: 7, int8: 6}), jasmine.objectContaining({long_: 6, int8: 7}), ]); }); it('marshals a fixed-size struct array as caller allocated out param', function () { expect(GIMarshallingTests.array_fixed_caller_allocated_struct_out()).toEqual([ jasmine.objectContaining({long_: -2, int8: -1}), jasmine.objectContaining({long_: 1, int8: 2}), jasmine.objectContaining({long_: 3, int8: 4}), jasmine.objectContaining({long_: 5, int8: 6}), ]); }); }); describe('C array with length', function () { function createStructArray(StructType = GIMarshallingTests.BoxedStruct) { return [1, 2, 3].map(num => { let struct = new StructType(); struct.long_ = num; return struct; }); } testSimpleMarshalling('array', [-1, 0, 1, 2], [-2, -1, 0, 1, 2], []); it('can be returned along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_return_etc(9, 5); expect(sum).toEqual(14); expect(array).toEqual([9, 0, 1, 5]); }); it('can be passed to a function with its length parameter before it', function () { expect(() => GIMarshallingTests.array_in_len_before([-1, 0, 1, 2])) .not.toThrow(); }); it('can be passed to a function with zero terminator', function () { expect(() => GIMarshallingTests.array_in_len_zero_terminated([-1, 0, 1, 2])) .not.toThrow(); }); describe('of strings', function () { testInParameter('array_string', ['foo', 'bar']); }); it('marshals a byte array as an in parameter', function () { expect(() => GIMarshallingTests.array_uint8_in('abcd')).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in([97, 98, 99, 100])).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in(new TextEncoder().encode('abcd'))) .not.toThrow(); }); describe('of signed 64-bit ints', function () { testInParameter('array_int64', [-1, 0, 1, 2]); }); describe('of unsigned 64-bit ints', function () { testInParameter('array_uint64', [-1, 0, 1, 2]); }); describe('of unichars', function () { testInParameter('array_unichar', 'const ♥ utf8'); testOutParameter('array_unichar', 'const ♥ utf8'); it('marshals from an array of codepoints', function () { const codepoints = [...'const ♥ utf8'].map(c => c.codePointAt(0)); expect(() => GIMarshallingTests.array_unichar_in(codepoints)).not.toThrow(); }); }); describe('of booleans', function () { testInParameter('array_bool', [true, false, true, true]); testOutParameter('array_bool', [true, false, true, true]); it('marshals from an array of numbers', function () { expect(() => GIMarshallingTests.array_bool_in([-1, 0, 1, 2])).not.toThrow(); }); }); describe('of boxed structs', function () { testInParameter('array_struct', createStructArray()); describe('passed by value', function () { testInParameter('array_struct_value', createStructArray(), { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }); }); }); describe('of simple structs', function () { testInParameter('array_simple_struct', createStructArray(GIMarshallingTests.SimpleStruct), { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }); }); it('marshals two arrays with the same length parameter', function () { const keys = ['one', 'two', 'three']; const values = [1, 2, 3]; expect(() => GIMarshallingTests.multi_array_key_value_in(keys, values)).not.toThrow(); }); // Run twice to ensure that copies are correctly made for (transfer full) it('copies correctly on transfer full', function () { let array = createStructArray(); expect(() => { GIMarshallingTests.array_struct_take_in(array); GIMarshallingTests.array_struct_take_in(array); }).not.toThrow(); }); describe('of enums', function () { testInParameter('array_enum', [ GIMarshallingTests.Enum.VALUE1, GIMarshallingTests.Enum.VALUE2, GIMarshallingTests.Enum.VALUE3, ]); }); describe('of flags', function () { testInParameter('array_flags', [ GIMarshallingTests.Flags.VALUE1, GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags.VALUE3, ]); }); it('marshals an array with a 64-bit length parameter', function () { expect(() => GIMarshallingTests.array_in_guint64_len([-1, 0, 1, 2])).not.toThrow(); }); it('marshals an array with an 8-bit length parameter', function () { expect(() => GIMarshallingTests.array_in_guint8_len([-1, 0, 1, 2])).not.toThrow(); }); it('can be an in-out argument', function () { const array = GIMarshallingTests.array_inout([-1, 0, 1, 2]); expect(array).toEqual([-2, -1, 0, 1, 2]); }); it('can be an in-out argument with in length', function () { if (!GIMarshallingTests.array_inout_length_in) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); const array = GIMarshallingTests.array_inout_length_in([-1, 0, 1, 2]); expect(array).toEqual([-2, -1, 1, 2]); }); xit('can be an out argument with in-out length', function () { const array = GIMarshallingTests.array_out_length_inout(5); expect(array).toEqual([-2, -4, -6, 8, -10, -12]); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); it('cannot be an out argument with in-out length', function () { // TODO(3v1n0): remove this test when fixing // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 if (!GIMarshallingTests.array_out_length_inout) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); expect(() => GIMarshallingTests.array_out_length_inout(5)).toThrow(); }); xit('can be an in-out argument with out length', function () { const array = GIMarshallingTests.array_inout_length_out([-1, 0, 1, 2]); expect(array).toEqual([-2, -1, 0, 1, 2]); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); it('cannot be an in-out argument with out length', function () { // TODO(3v1n0): remove this test when fixing // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 if (!GIMarshallingTests.array_inout_length_out) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); expect(() => GIMarshallingTests.array_inout_length_out([-1, 0, 1, 2])).toThrow(); }); xit('can be an out argument with in length', function () { const array = GIMarshallingTests.array_out_length_in([-1, 0, 1, 2]); expect(array).toEqual([-2, 0, -2, -4]); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/560'); it('cannot be an out argument with in length', function () { // TODO(3v1n0): remove this test when fixing // https://gitlab.gnome.org/GNOME/gjs/-/issues/560 if (!GIMarshallingTests.array_out_length_in) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); expect(() => GIMarshallingTests.array_out_length_in([-1, 0, 1, 2])).toThrow(); }); it('can be an out argument along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_out_etc(9, 5); expect(sum).toEqual(14); expect(array).toEqual([9, 0, 1, 5]); }); it('can be an in-out argument along with other arguments', function () { let [array, sum] = GIMarshallingTests.array_inout_etc(9, [-1, 0, 1, 2], 5); expect(sum).toEqual(14); expect(array).toEqual([9, -1, 0, 1, 5]); }); it('does not interpret an unannotated integer as a length parameter', function () { expect(() => GIMarshallingTests.array_in_nonzero_nonlen(42, 'abcd')).not.toThrow(); }); }); describe('Zero-terminated C array', function () { describe('of strings', function () { testSimpleMarshalling('array_zero_terminated', ['0', '1', '2'], ['-1', '0', '1', '2'], null); }); it('marshals null as a zero-terminated array return value', function () { expect(GIMarshallingTests.array_zero_terminated_return_null()).toEqual(null); }); it('marshals an array of structs as a return value', function () { let structArray = GIMarshallingTests.array_zero_terminated_return_struct(); expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]); }); it('marshals an array of unichars as a return value', function () { expect(GIMarshallingTests.array_zero_terminated_return_unichar()) .toEqual('const ♥ utf8'); }); describe('of GLib.Variants', function () { let variantArray; beforeEach(function () { variantArray = [ new GLib.Variant('i', 27), new GLib.Variant('s', 'Hello'), ]; }); ['none', 'container', 'full'].forEach(transfer => { it(`marshals as a transfer-${transfer} in and out parameter`, function () { const returnedArray = GIMarshallingTests[`array_gvariant_${transfer}_in`](variantArray); expect(returnedArray.map(v => v.deepUnpack())).toEqual([27, 'Hello']); }); }); }); }); describe('GArray', function () { describe('of ints with transfer none', function () { testReturnValue('garray_int_none', [-1, 0, 1, 2]); testInParameter('garray_int_none', [-1, 0, 1, 2]); }); it('marshals int64s as a transfer-none return value', function () { expect(warn64(true, GIMarshallingTests.garray_uint64_none_return)) .toEqual([0, Limits.int64.umax]); }); describe('of strings', function () { testContainerMarshalling('garray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1'], null); it('marshals as a transfer-full caller-allocated out parameter', function () { expect(GIMarshallingTests.garray_utf8_full_out_caller_allocated()) .toEqual(['0', '1', '2']); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/106'); // https://gitlab.gnome.org/GNOME/gjs/-/issues/344 // the test should be replaced with the one above when issue // https://gitlab.gnome.org/GNOME/gjs/issues/106 is fixed. it('marshals as a transfer-full caller-allocated out parameter throws errors', function () { // should throw when called, not when the function object is created expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated).not.toThrow(); expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated()) .toThrowError(/type array.*\(out caller-allocates\)/); }); }); it('marshals boxed structs as a transfer-full return value', function () { expect(GIMarshallingTests.garray_boxed_struct_full_return().map(e => e.long_)) .toEqual([42, 43, 44]); }); describe('of booleans with transfer none', function () { testInParameter('garray_bool_none', [-1, 0, 1, 2]); }); describe('of unichars', function () { it('can be passed in with transfer none', function () { expect(() => GIMarshallingTests.garray_unichar_none_in('const \u2665 utf8')) .not.toThrow(); expect(() => GIMarshallingTests.garray_unichar_none_in([0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38])).not.toThrow(); }); }); }); describe('GPtrArray', function () { describe('of strings', function () { testContainerMarshalling('gptrarray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1'], null); }); describe('of structs', function () { it('can be returned with transfer full', function () { expect(GIMarshallingTests.gptrarray_boxed_struct_full_return().map(e => e.long_)) .toEqual([42, 43, 44]); }); }); }); describe('GByteArray', function () { const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]); testReturnValue('bytearray_full', refByteArray); it('can be passed in with transfer none', function () { expect(() => GIMarshallingTests.bytearray_none_in(refByteArray)) .not.toThrow(); expect(() => GIMarshallingTests.bytearray_none_in([0, 49, 0xFF, 51])) .not.toThrow(); }); }); describe('GBytes', function () { const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]); it('marshals as a transfer-full return value', function () { expect(GIMarshallingTests.gbytes_full_return().toArray()).toEqual(refByteArray); }); it('can be created from an array and passed in', function () { let bytes = GLib.Bytes.new([0, 49, 0xFF, 51]); expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow(); }); it('can be created by returning from a function and passed in', function () { var bytes = GIMarshallingTests.gbytes_full_return(); expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow(); expect(bytes.toArray()).toEqual(refByteArray); }); it('can be implicitly converted from a Uint8Array', function () { expect(() => GIMarshallingTests.gbytes_none_in(refByteArray)) .not.toThrow(); }); it('can be created from a string and is encoded in UTF-8', function () { let bytes = GLib.Bytes.new('const \u2665 utf8'); expect(() => GIMarshallingTests.utf8_as_uint8array_in(bytes.toArray())) .not.toThrow(); }); it('cannot be passed to a function expecting a byte array', function () { let bytes = GLib.Bytes.new([97, 98, 99, 100]); expect(() => GIMarshallingTests.array_uint8_in(bytes.toArray())).not.toThrow(); expect(() => GIMarshallingTests.array_uint8_in(bytes)).toThrow(); }); }); describe('GStrv', function () { testSimpleMarshalling('gstrv', ['0', '1', '2'], ['-1', '0', '1', '2'], null); }); describe('Array of GStrv', function () { ['length', 'fixed', 'zero_terminated'].forEach(arrayKind => ['none', 'container', 'full'].forEach(transfer => { const testFunction = returnMode => { const commonName = 'array_of_gstrv_transfer'; const funcName = [arrayKind, commonName, transfer, returnMode].join('_'); const func = GIMarshallingTests[funcName]; if (!func) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/407'); return func; }; ['out', 'return'].forEach(returnMode => it(`${arrayKind} ${returnMode} transfer ${transfer}`, function () { const func = testFunction(returnMode); expect(func()).toEqual([ ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], ]); })); it(`${arrayKind} in transfer ${transfer}`, function () { const func = testFunction('in'); if (transfer === 'container') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); expect(() => func([ ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], ])).not.toThrow(); }); it(`${arrayKind} inout transfer ${transfer}`, function () { const func = testFunction('inout'); if (transfer === 'container') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); const expectedReturn = [ ['-1', '0', '1', '2'], ['-1', '3', '4', '5'], ['-1', '6', '7', '8'], ]; if (arrayKind !== 'fixed') expectedReturn.push(['-1', '9', '10', '11']); expect(func([ ['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'], ])).toEqual(expectedReturn); }); })); }); ['GList', 'GSList'].forEach(listKind => { const list = listKind.toLowerCase(); describe(listKind, function () { describe('of ints with transfer none', function () { testReturnValue(`${list}_int_none`, [-1, 0, 1, 2]); testInParameter(`${list}_int_none`, [-1, 0, 1, 2]); }); if (listKind === 'GList') { describe('of unsigned 32-bit ints with transfer none', function () { testReturnValue('glist_uint32_none', [0, Limits.int32.umax]); testInParameter('glist_uint32_none', [0, Limits.int32.umax]); }); } describe('of strings', function () { testContainerMarshalling(`${list}_utf8`, ['0', '1', '2'], ['-2', '-1', '0', '1'], []); }); }); }); describe('GHashTable', function () { const numberDict = { '-1': -0.1, 0: 0, 1: 0.1, 2: 0.2, }; describe('with integer values', function () { const intDict = { '-1': 1, 0: 0, 1: -1, 2: -2, }; testReturnValue('ghashtable_int_none', intDict); testInParameter('ghashtable_int_none', intDict); }); describe('with string values', function () { const stringDict = { '-1': '1', 0: '0', 1: '-1', 2: '-2', }; const stringDictOut = { '-1': '1', 0: '0', 1: '1', }; testContainerMarshalling('ghashtable_utf8', stringDict, stringDictOut, null); }); describe('with double values', function () { testInParameter('ghashtable_double', numberDict); }); describe('with float values', function () { testInParameter('ghashtable_float', numberDict); }); describe('with 64-bit int values', function () { const int64Dict = { '-1': -1, 0: 0, 1: 1, 2: 0x100000000, }; testInParameter('ghashtable_int64', int64Dict); }); describe('with unsigned 64-bit int values', function () { const uint64Dict = { '-1': 0x100000000, 0: 0, 1: 1, 2: 2, }; testInParameter('ghashtable_uint64', uint64Dict); }); it('symbol keys are ignored', function () { const symbolDict = { [Symbol('foo')]: 2, '-1': 1, 0: 0, 1: -1, 2: -2, }; expect(() => GIMarshallingTests.ghashtable_int_none_in(symbolDict)).not.toThrow(); }); }); describe('GValue', function () { testSimpleMarshalling('gvalue', 42, '42', null, { inout: { skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192', }, }); it('can handle noncanonical float NaN', function () { expect(GIMarshallingTests.gvalue_noncanonical_nan_float()).toBeNaN(); }); it('can handle noncanonical double NaN', function () { expect(GIMarshallingTests.gvalue_noncanonical_nan_double()).toBeNaN(); }); it('marshals as an int64 in parameter', function () { expect(() => GIMarshallingTests.gvalue_int64_in(BigIntLimits.int64.max)) .not.toThrow(); }); it('type objects can be converted from primitive-like types', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.Int)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.Double)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, Number)) .not.toThrow(); }); it('can be passed into a function and modified', function () { expect(() => GIMarshallingTests.gvalue_in_with_modification(42)).not.toThrow(); // Let's assume this test doesn't expect that the modified number makes // it back to the caller; it is not possible to "modify" a JS primitive. // // See the "as a boxed type" test below for passing an explicit GObject.Value }); it('can be passed into a function as a boxed type and modified', function () { const value = new GObject.Value(); value.init(GObject.TYPE_INT); value.set_int(42); expect(() => GIMarshallingTests.gvalue_in_with_modification(value)).not.toThrow(); expect(value.get_int()).toBe(24); }); xit('enum can be passed into a function and packed', function () { expect(() => GIMarshallingTests.gvalue_in_enum(GIMarshallingTests.Enum.VALUE3)) .not.toThrow(); }).pend("we don't know to pack enums in a GValue as enum and not int"); it('enum can be passed into a function as a boxed type and packed', function () { const value = new GObject.Value(); // GIMarshallingTests.Enum is a native enum. value.init(GObject.TYPE_ENUM); value.set_enum(GIMarshallingTests.Enum.VALUE3); expect(() => GIMarshallingTests.gvalue_in_enum(value)) .not.toThrow(); }); xit('flags can be passed into a function and packed', function () { expect(() => GIMarshallingTests.gvalue_in_flags(GIMarshallingTests.Flags.VALUE3)) .not.toThrow(); }).pend("we don't know to pack flags in a GValue as flags and not gint"); it('flags can be passed into a function as a boxed type and packed', function () { const value = new GObject.Value(); value.init(GIMarshallingTests.Flags); value.set_flags(GIMarshallingTests.Flags.VALUE3); expect(() => GIMarshallingTests.gvalue_in_flags(value)) .not.toThrow(); }); it('marshals as an int64 out parameter', function () { expect(GIMarshallingTests.gvalue_int64_out()).toEqual(Limits.int64.max); }); it('marshals as a caller-allocated out parameter', function () { expect(GIMarshallingTests.gvalue_out_caller_allocates()).toEqual(42); }); it('array can be passed into a function and packed', function () { expect(() => GIMarshallingTests.gvalue_flat_array([42, '42', true])) .not.toThrow(); }); it('array of boxed type GValues can be passed into a function', function () { const value0 = new GObject.Value(); value0.init(GObject.TYPE_INT); value0.set_int(42); const value1 = new GObject.Value(); value1.init(String); value1.set_string('42'); const value2 = new GObject.Value(); value2.init(Boolean); value2.set_boolean(true); const values = [value0, value1, value2]; expect(() => GIMarshallingTests.gvalue_flat_array(values)) .not.toThrow(); }); it('array of uninitialized boxed GValues', function () { const values = Array(3).fill().map(() => new GObject.Value()); expect(() => GIMarshallingTests.gvalue_flat_array(values)).toThrow(); }); it('array can be passed as an out argument and unpacked', function () { expect(GIMarshallingTests.return_gvalue_flat_array()) .toEqual([42, '42', true]); }); it('array can be passed as an out argument and unpacked when zero-terminated', function () { expect(GIMarshallingTests.return_gvalue_zero_terminated_array()) .toEqual([42, '42', true]); }); xit('array can roundtrip with GValues intact', function () { expect(GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', true)) .toEqual([42, '42', true]); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272'); it('can have its type inferred from primitive values', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.TYPE_INT)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.TYPE_DOUBLE)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type('42', GObject.TYPE_STRING)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(GObject.TYPE_GTYPE, GObject.TYPE_GTYPE)) .not.toThrow(); }); // supplementary tests for gvalue_in_with_type() it('can have its type inferred as a GObject type', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction)) .not.toThrow(); }); it('can have its type inferred as a superclass', function () { let action = new Gio.SimpleAction(); expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.Object)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.TYPE_OBJECT)) .not.toThrow(); }); it('can have its type inferred as an interface that it implements', function () { expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction)) .not.toThrow(); }); it('can have its type inferred as a boxed type', function () { let keyfile = new GLib.KeyFile(); expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GLib.KeyFile)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GObject.TYPE_BOXED)) .not.toThrow(); let struct = new GIMarshallingTests.BoxedStruct(); expect(() => GIMarshallingTests.gvalue_in_with_type(struct, GIMarshallingTests.BoxedStruct)) .not.toThrow(); }); it('can have its type inferred as GVariant', function () { let variant = GLib.Variant.new('u', 42); expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GLib.Variant)) .not.toThrow(); expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GObject.TYPE_VARIANT)) .not.toThrow(); }); it('can have its type inferred as a union type', function () { let union = GIMarshallingTests.union_returnv(); expect(() => GIMarshallingTests.gvalue_in_with_type(union, GIMarshallingTests.Union)) .not.toThrow(); }); it('can have its type inferred as a GParamSpec', function () { let paramSpec = GObject.ParamSpec.string('my-param', '', '', GObject.ParamFlags.READABLE, ''); expect(() => GIMarshallingTests.gvalue_in_with_type(paramSpec, GObject.TYPE_PARAM)) .not.toThrow(); }); it('can deal with a GValue packed in a GValue', function () { const innerValue = new GObject.Value(); innerValue.init(Number); innerValue.set_double(42); expect(() => GIMarshallingTests.gvalue_in_with_type(innerValue, Number)) .not.toThrow(); const value = new GObject.Value(); value.init(GObject.Value); value.set_boxed(innerValue); expect(() => GIMarshallingTests.gvalue_in_with_type(value, GObject.Value)) .not.toThrow(); }); // See testCairo.js for a test of GIMarshallingTests.gvalue_in_with_type() // on Cairo foreign structs, since it will be skipped if compiling without // Cairo support. }); describe('Callback', function () { describe('GClosure', function () { testInParameter('gclosure', () => 42); xit('marshals a GClosure as a return value', function () { // Currently a GObject.Closure instance is returned, upon which it's // not possible to call invoke() because that method takes a bare // pointer as an argument. expect(GIMarshallingTests.gclosure_return()()).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/80'); }); it('marshals a return value', function () { expect(GIMarshallingTests.callback_return_value_only(() => 42)) .toEqual(42); }); it('marshals one out parameter', function () { expect(GIMarshallingTests.callback_one_out_parameter(() => 43)) .toEqual(43); }); it('marshals multiple out parameters', function () { expect(GIMarshallingTests.callback_multiple_out_parameters(() => [44, 45])) .toEqual([44, 45]); }); it('marshals a return value and one out parameter', function () { expect(GIMarshallingTests.callback_return_value_and_one_out_parameter(() => [46, 47])) .toEqual([46, 47]); }); it('marshals a return value and multiple out parameters', function () { expect(GIMarshallingTests.callback_return_value_and_multiple_out_parameters(() => [48, 49, 50])) .toEqual([48, 49, 50]); }); xit('marshals an array out parameter', function () { expect(GIMarshallingTests.callback_array_out_parameter(() => [50, 51])) .toEqual([50, 51]); }).pend('Function not added to gobject-introspection test suite yet'); it('marshals a callback parameter that can be called from C', function () { expect(GIMarshallingTests.callback_owned_boxed(box => { expect(box.long_).toEqual(1); box.long_ = 52; })).toEqual(52); }); }); describe('Raw pointers', function () { it('gets an allocated return value', function () { expect(GIMarshallingTests.pointer_in_return(null)).toBeFalsy(); }); it('can be roundtripped at least if the pointer is null', function () { expect(GIMarshallingTests.pointer_in_return(null)).toBeNull(); }); }); describe('Registered enum type', function () { testSimpleMarshalling('genum', GIMarshallingTests.GEnum.VALUE3, GIMarshallingTests.GEnum.VALUE1, 0, { returnv: { funcName: 'genum_returnv', }, }); }); describe('Bare enum type', function () { testSimpleMarshalling('enum', GIMarshallingTests.Enum.VALUE3, GIMarshallingTests.Enum.VALUE1, 0, { returnv: { funcName: 'enum_returnv', }, }); }); describe('Registered flags type', function () { testSimpleMarshalling('flags', GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags.VALUE1, 0, { returnv: { funcName: 'flags_returnv', }, }); it('accepts zero', function () { expect(() => GIMarshallingTests.flags_in_zero(0)).not.toThrow(); }); }); describe('Bare flags type', function () { testSimpleMarshalling('no_type_flags', GIMarshallingTests.NoTypeFlags.VALUE2, GIMarshallingTests.NoTypeFlags.VALUE1, 0, { returnv: { funcName: 'no_type_flags_returnv', }, }); it('accepts zero', function () { expect(() => GIMarshallingTests.no_type_flags_in_zero(0)).not.toThrow(); }); }); describe('Simple struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.simple_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 6, int8: 7, })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.SimpleStruct({ long_: 6, int8: 7, }); expect(() => struct.inv()).not.toThrow(); // was this supposed to be static? expect(() => struct.method()).not.toThrow(); }); }); describe('Pointer struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.pointer_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 42, })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.PointerStruct({ long_: 42, }); expect(() => struct.inv()).not.toThrow(); }); }); describe('Boxed struct', function () { it('marshals as a return value', function () { expect(GIMarshallingTests.boxed_struct_returnv()).toEqual(jasmine.objectContaining({ long_: 42, string_: 'hello', g_strv: ['0', '1', '2'], })); }); it('marshals as the this-argument of a method', function () { const struct = new GIMarshallingTests.BoxedStruct({ long_: 42, }); expect(() => struct.inv()).not.toThrow(); }); it('marshals as an out parameter', function () { expect(GIMarshallingTests.boxed_struct_out()).toEqual(jasmine.objectContaining({ long_: 42, })); }); it('marshals as an inout parameter', function () { const struct = new GIMarshallingTests.BoxedStruct({ long_: 42, }); expect(GIMarshallingTests.boxed_struct_inout(struct)).toEqual(jasmine.objectContaining({ long_: 0, })); }); }); describe('Union', function () { let union; beforeEach(function () { union = GIMarshallingTests.union_returnv(); }); xit('marshals as a return value', function () { expect(union.long_).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/273'); it('marshals as the this-argument of a method', function () { expect(() => union.inv()).not.toThrow(); // was this supposed to be static? expect(() => union.method()).not.toThrow(); }); }); describe('GObject', function () { it('has a static method that can be called', function () { expect(() => GIMarshallingTests.Object.static_method()).not.toThrow(); }); it('has a method that can be called', function () { const o = new GIMarshallingTests.Object({int: 42}); expect(() => o.method()).not.toThrow(); }); it('has an overridden method that can be called', function () { const o = new GIMarshallingTests.Object({int: 0}); expect(() => o.overridden_method()).not.toThrow(); }); it('can be created from a static constructor', function () { const o = GIMarshallingTests.Object.new(42); expect(o.int).toEqual(42); }); it('can have a static constructor that fails', function () { expect(() => GIMarshallingTests.Object.new_fail(42)).toThrow(); }); describe('method', function () { let o; beforeEach(function () { o = new GIMarshallingTests.Object(); }); it('marshals an int array as an in parameter', function () { expect(() => o.method_array_in([-1, 0, 1, 2])).not.toThrow(); }); it('marshals an int array as an out parameter', function () { expect(o.method_array_out()).toEqual([-1, 0, 1, 2]); }); it('marshals an int array as an inout parameter', function () { expect(o.method_array_inout([-1, 0, 1, 2])).toEqual([-2, -1, 0, 1, 2]); }); it('marshals an int array as a return value', function () { expect(o.method_array_return()).toEqual([-1, 0, 1, 2]); }); it('with default implementation can be called', function () { o = new GIMarshallingTests.Object({int: 42}); o.method_with_default_implementation(43); expect(o.int).toEqual(43); }); }); ['none', 'full'].forEach(transfer => { ['return', 'out'].forEach(mode => { it(`marshals as a ${mode} parameter with transfer ${transfer}`, function () { expect(GIMarshallingTests.Object[`${transfer}_${mode}`]().int).toEqual(0); }); }); it(`marshals as an inout parameter with transfer ${transfer}`, function () { const o = new GIMarshallingTests.Object({int: 42}); expect(GIMarshallingTests.Object[`${transfer}_inout`](o).int).toEqual(0); }); }); it('marshals as a this value with transfer none', function () { const o = new GIMarshallingTests.Object({int: 42}); expect(() => o.none_in()).not.toThrow(); }); }); let VFuncTester = GObject.registerClass(class VFuncTester extends GIMarshallingTests.Object { vfunc_method_int8_in(i) { this.int = i; } vfunc_method_int8_out() { return 40; } vfunc_method_int8_arg_and_out_caller(i) { return i + 3; } vfunc_method_int8_arg_and_out_callee(i) { return i + 4; } vfunc_method_str_arg_out_ret(s) { return [`Called with ${s}`, 41]; } vfunc_method_with_default_implementation(i) { this.int = i + 2; } // vfunc_vfunc_with_callback(callback) { // this.int = callback(41); // } vfunc_vfunc_return_value_only() { return 42; } vfunc_vfunc_one_out_parameter() { return 43; } vfunc_vfunc_multiple_out_parameters() { return [44, 45]; } vfunc_vfunc_return_value_and_one_out_parameter() { return [46, 47]; } vfunc_vfunc_return_value_and_multiple_out_parameters() { return [48, 49, 50]; } vfunc_vfunc_array_out_parameter() { return [50, 51]; } vfunc_vfunc_caller_allocated_out_parameter() { return 52; } vfunc_vfunc_meth_with_err(x) { switch (x) { case -1: return true; case 0: undefined.throwTypeError(); break; case 1: void referenceError; // eslint-disable-line no-undef break; case 2: throw new Gio.IOErrorEnum({ code: Gio.IOErrorEnum.FAILED, message: 'I FAILED, but the test passed!', }); case 3: throw new GLib.SpawnError({ code: GLib.SpawnError.TOO_BIG, message: 'This test is Too Big to Fail', }); case 4: throw null; // eslint-disable-line no-throw-literal case 5: throw undefined; // eslint-disable-line no-throw-literal case 6: throw 42; // eslint-disable-line no-throw-literal case 7: throw true; // eslint-disable-line no-throw-literal case 8: throw 'a string'; // eslint-disable-line no-throw-literal case 9: throw 42n; // eslint-disable-line no-throw-literal case 10: throw Symbol('a symbol'); case 11: throw {plain: 'object'}; // eslint-disable-line no-throw-literal case 12: // eslint-disable-next-line no-throw-literal throw {name: 'TypeError', message: 'an error message'}; case 13: // eslint-disable-next-line no-throw-literal throw {name: 1, message: 'an error message'}; case 14: // eslint-disable-next-line no-throw-literal throw {name: 'TypeError', message: false}; } } vfunc_vfunc_return_enum() { return GIMarshallingTests.Enum.VALUE2; } vfunc_vfunc_out_enum() { return GIMarshallingTests.Enum.VALUE3; } vfunc_vfunc_return_flags() { return GIMarshallingTests.Flags.VALUE2; } vfunc_vfunc_out_flags() { return GIMarshallingTests.Flags.VALUE3; } vfunc_vfunc_return_object_transfer_none() { if (!this._returnObject) this._returnObject = new GIMarshallingTests.Object({int: 53}); return this._returnObject; } vfunc_vfunc_return_object_transfer_full() { return new GIMarshallingTests.Object({int: 54}); } vfunc_vfunc_out_object_transfer_none() { if (!this._outObject) this._outObject = new GIMarshallingTests.Object({int: 55}); return this._outObject; } vfunc_vfunc_out_object_transfer_full() { return new GIMarshallingTests.Object({int: 56}); } vfunc_vfunc_in_object_transfer_none(object) { void object; } vfunc_vfunc_in_object_transfer_full(object) { this._inObject = object; } }); try { VFuncTester = GObject.registerClass(class VFuncTesterInOut extends VFuncTester { vfunc_vfunc_one_inout_parameter(input) { return input * 5; } vfunc_vfunc_multiple_inout_parameters(inputA, inputB) { return [inputA * 5, inputB * -1]; } vfunc_vfunc_return_value_and_one_inout_parameter(input) { return [49, input * 5]; } vfunc_vfunc_return_value_and_multiple_inout_parameters(inputA, inputB) { return [49, inputA * 5, inputB * -1]; } }); } catch {} describe('Virtual function', function () { let tester; beforeEach(function () { tester = new VFuncTester(); }); it('marshals an in argument', function () { tester.method_int8_in(39); expect(tester.int).toEqual(39); }); it('marshals an out argument', function () { expect(tester.method_int8_out()).toEqual(40); }); it('marshals a POD out argument', function () { expect(tester.method_int8_arg_and_out_caller(39)).toEqual(42); }); it('marshals a callee-allocated pointer out argument', function () { expect(tester.method_int8_arg_and_out_callee(38)).toEqual(42); }); it('marshals a string out argument and return value', function () { expect(tester.method_str_arg_out_ret('a string')).toEqual(['Called with a string', 41]); expect(tester.method_str_arg_out_ret('a 2nd string')).toEqual(['Called with a 2nd string', 41]); }); it('can override a default implementation in JS', function () { tester.method_with_default_implementation(40); expect(tester.int).toEqual(42); }); xit('marshals a callback', function () { tester.call_vfunc_with_callback(); expect(tester.int).toEqual(41); }).pend('callback parameters to vfuncs not supported'); it('marshals a return value', function () { expect(tester.vfunc_return_value_only()).toEqual(42); }); it('marshals one out parameter', function () { expect(tester.vfunc_one_out_parameter()).toEqual(43); }); it('marshals multiple out parameters', function () { expect(tester.vfunc_multiple_out_parameters()).toEqual([44, 45]); }); it('marshals a return value and one out parameter', function () { expect(tester.vfunc_return_value_and_one_out_parameter()) .toEqual([46, 47]); }); it('marshals a return value and multiple out parameters', function () { expect(tester.vfunc_return_value_and_multiple_out_parameters()) .toEqual([48, 49, 50]); }); it('marshals one inout parameter', function () { expect(tester.vfunc_one_inout_parameter(10)).toEqual(50); }); it('marshals multiple inout parameters', function () { expect(tester.vfunc_multiple_inout_parameters(10, 5)).toEqual([50, -5]); }); it('marshals a return value and one inout parameter', function () { expect(tester.vfunc_return_value_and_one_inout_parameter(10)) .toEqual([49, 50]); }); it('marshals a return value and multiple inout parameters', function () { expect(tester.vfunc_return_value_and_multiple_inout_parameters(10, -51)) .toEqual([49, 50, 51]); }); it('marshals an array out parameter', function () { expect(tester.vfunc_array_out_parameter()).toEqual([50, 51]); }); it('marshals a caller-allocated GValue out parameter', function () { expect(tester.vfunc_caller_allocated_out_parameter()).toEqual(52); }); it('marshals an error out parameter when no error', function () { expect(tester.vfunc_meth_with_error(-1)).toBeTruthy(); }); it('marshals an error out parameter with a JavaScript exception', function () { expect(() => tester.vfunc_meth_with_error(0)).toThrowError(TypeError); expect(() => tester.vfunc_meth_with_error(1)).toThrowError(ReferenceError); }); it('marshals an error out parameter with a GError exception', function () { try { tester.vfunc_meth_with_error(2); fail('Exception should be thrown'); } catch (e) { expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED)).toBeTruthy(); expect(e.message).toEqual('I FAILED, but the test passed!'); } try { tester.vfunc_meth_with_error(3); fail('Exception should be thrown'); } catch (e) { expect(e.matches(GLib.SpawnError, GLib.SpawnError.TOO_BIG)).toBeTruthy(); expect(e.message).toEqual('This test is Too Big to Fail'); } }); it('marshals an error out parameter with a primitive value', function () { expect(() => tester.vfunc_meth_with_error(4)).toThrowError(/null/); expect(() => tester.vfunc_meth_with_error(5)).toThrowError(/undefined/); expect(() => tester.vfunc_meth_with_error(6)).toThrowError(/42/); expect(() => tester.vfunc_meth_with_error(7)).toThrowError(/true/); expect(() => tester.vfunc_meth_with_error(8)).toThrowError(/"a string"/); expect(() => tester.vfunc_meth_with_error(9)).toThrowError(/42n/); expect(() => tester.vfunc_meth_with_error(10)).toThrowError(/Symbol\("a symbol"\)/); }); it('marshals an error out parameter with a plain object', function () { expect(() => tester.vfunc_meth_with_error(11)).toThrowError(/Object/); expect(() => tester.vfunc_meth_with_error(12)).toThrowError(TypeError, /an error message/); expect(() => tester.vfunc_meth_with_error(13)).toThrowError(/Object/); expect(() => tester.vfunc_meth_with_error(14)).toThrowError(Error, /Object/); }); it('marshals an enum return value', function () { expect(tester.vfunc_return_enum()).toEqual(GIMarshallingTests.Enum.VALUE2); }); it('marshals an enum out parameter', function () { expect(tester.vfunc_out_enum()).toEqual(GIMarshallingTests.Enum.VALUE3); }); it('marshals a flags return value', function () { expect(tester.vfunc_return_flags()).toEqual(GIMarshallingTests.Flags.VALUE2); }); it('marshals a flags out parameter', function () { expect(tester.vfunc_out_flags()).toEqual(GIMarshallingTests.Flags.VALUE3); }); // These tests check what the refcount is of the returned objects; see // comments in gimarshallingtests.c. // Objects that are exposed in JS always have at least one reference (the // toggle reference.) JS never owns more than one reference. There may be // other references owned on the C side. // In any case the refs should not be floating. We never have floating refs // in JS. function testVfuncRefcount(mode, transfer, expectedRefcount, options = {}, ...args) { it(`marshals an object ${mode} parameter with transfer ${transfer}`, function () { if (options.skip) pending(options.skip); const [refcount, floating] = tester[`get_ref_info_for_vfunc_${mode}_object_transfer_${transfer}`](...args); expect(floating).toBeFalsy(); expect(refcount).toEqual(expectedRefcount); }); } // Running in extra-gc mode can drop the JS reference, since it is not // actually stored anywhere reachable from user code. However, we cannot // force the extra GC under normal conditions because it occurs in the // middle of C++ code. const skipExtraGC = {}; const zeal = GLib.getenv('JS_GC_ZEAL'); if (zeal && zeal.startsWith('2,')) skipExtraGC.skip = 'Skip during extra-gc.'; // 1 reference = the object is owned only by JS. // 2 references = the object is owned by JS and the vfunc caller. testVfuncRefcount('return', 'none', 1); testVfuncRefcount('return', 'full', 2, skipExtraGC); testVfuncRefcount('out', 'none', 1); testVfuncRefcount('out', 'full', 2, skipExtraGC); testVfuncRefcount('in', 'none', 2, skipExtraGC, GIMarshallingTests.Object); testVfuncRefcount('in', 'full', 1, { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/275', }, GIMarshallingTests.Object); }); const WrongVFuncTester = GObject.registerClass(class WrongVFuncTester extends GIMarshallingTests.Object { vfunc_vfunc_return_value_only() { } vfunc_vfunc_one_out_parameter() { } vfunc_vfunc_multiple_out_parameters() { } vfunc_vfunc_return_value_and_one_out_parameter() { } vfunc_vfunc_return_value_and_multiple_out_parameters() { } vfunc_vfunc_array_out_parameter() { } vfunc_vfunc_caller_allocated_out_parameter() { } vfunc_vfunc_return_enum() { } vfunc_vfunc_out_enum() { } vfunc_vfunc_return_flags() { } vfunc_vfunc_out_flags() { } vfunc_vfunc_return_object_transfer_none() { } vfunc_vfunc_return_object_transfer_full() { } vfunc_vfunc_out_object_transfer_none() { } vfunc_vfunc_out_object_transfer_full() { } vfunc_vfunc_in_object_transfer_none() { } }); describe('Wrong virtual functions', function () { let tester; beforeEach(function () { tester = new WrongVFuncTester(); }); it('marshals a return value', function () { expect(tester.vfunc_return_value_only()).toBeUndefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/311'); it('marshals one out parameter', function () { expect(tester.vfunc_one_out_parameter()).toBeUndefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/311'); it('marshals multiple out parameters', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_vfunc_multiple_out_parameters*Array*'); expect(tester.vfunc_multiple_out_parameters()).toEqual([0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and one out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_return_value_and_one_out_parameter*Array*'); expect(tester.vfunc_return_value_and_one_out_parameter()).toEqual([0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and multiple out parameters', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: *vfunc_return_value_and_multiple_out_parameters*Array*'); expect(tester.vfunc_return_value_and_multiple_out_parameters()).toEqual([0, 0, 0]); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an array out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type gfloat for Argument*undefined*'); expect(tester.vfunc_array_out_parameter()).toEqual(null); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum return value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type enum for Return*undefined*'); expect(tester.vfunc_return_enum()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type enum for Argument*undefined*'); expect(tester.vfunc_out_enum()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags return value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type flags for Return*undefined*'); expect(tester.vfunc_return_flags()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags out parameter', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: Expected type flags for Argument*undefined*'); expect(tester.vfunc_out_flags()).toEqual(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); }); describe('Inherited GObject', function () { ['SubObject', 'SubSubObject'].forEach(klass => { describe(klass, function () { it('has a parent method that can be called', function () { const o = new GIMarshallingTests[klass]({int: 42}); expect(() => o.method()).not.toThrow(); }); it('has a method that can be called', function () { const o = new GIMarshallingTests[klass]({int: 0}); expect(() => o.sub_method()).not.toThrow(); }); it('has an overridden method that can be called', function () { const o = new GIMarshallingTests[klass]({int: 0}); expect(() => o.overwritten_method()).not.toThrow(); }); it('has a method with default implementation that can be called', function () { const o = new GIMarshallingTests[klass]({int: 42}); o.method_with_default_implementation(43); expect(o.int).toEqual(43); }); }); }); }); describe('Interface', function () { it('can be returned', function () { let ifaceImpl = new GIMarshallingTests.InterfaceImpl(); let itself = ifaceImpl.get_as_interface(); expect(ifaceImpl).toEqual(itself); }); it('can call an interface vfunc in C', function () { let ifaceImpl = new GIMarshallingTests.InterfaceImpl(); expect(() => ifaceImpl.test_int8_in(42)).not.toThrow(); expect(() => GIMarshallingTests.test_interface_test_int8_in(ifaceImpl, 42)) .not.toThrow(); }); it('can implement a C interface', function () { const I2Impl = GObject.registerClass({ Implements: [GIMarshallingTests.Interface2], }, class I2Impl extends GObject.Object {}); expect(() => new I2Impl()).not.toThrow(); }); it('can implement a C interface with a vfunc', function () { const I3Impl = GObject.registerClass({ Implements: [GIMarshallingTests.Interface3], }, class I3Impl extends GObject.Object { vfunc_test_variant_array_in(variantArray) { this.stuff = variantArray.map(v => { const bit64 = this.bigInt && (v.is_of_type(new GLib.VariantType('t')) || v.is_of_type(new GLib.VariantType('x'))); return warn64(bit64, () => v.deepUnpack()); }); } }); const i3 = new I3Impl(); i3.test_variant_array_in([ new GLib.Variant('b', true), new GLib.Variant('s', 'hello'), new GLib.Variant('i', 42), new GLib.Variant('t', 43), new GLib.Variant('x', 44), ]); expect(i3.stuff).toEqual([true, 'hello', 42, 43, 44]); i3.bigInt = true; i3.test_variant_array_in([ new GLib.Variant('x', BigIntLimits.int64.min), new GLib.Variant('x', BigIntLimits.int64.max), new GLib.Variant('t', BigIntLimits.int64.umax), ]); expect(i3.stuff).toEqual([ Limits.int64.min, Limits.int64.max, Limits.int64.umax, ]); }); }); describe('Configurations of return values', function () { it('can handle two out parameters', function () { expect(GIMarshallingTests.int_out_out()).toEqual([6, 7]); }); it('can handle three in and three out parameters', function () { expect(GIMarshallingTests.int_three_in_three_out(1, 2, 3)).toEqual([1, 2, 3]); }); it('can handle a return value and an out parameter', function () { expect(GIMarshallingTests.int_return_out()).toEqual([6, 7]); }); it('can handle four in parameters, two of which are nullable', function () { expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4')) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', null)) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, '4')) .not.toThrow(); expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, null)) .not.toThrow(); }); it('can handle three in parameters, one of which is nullable and one not', function () { expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3')) .not.toThrow(); expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, null, '3')) .not.toThrow(); expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', null)) .toThrow(); }); it('can handle an array in parameter and two nullable in parameters', function () { expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', null)) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, null)) .not.toThrow(); }); it('can handle an array in parameter and two nullable in parameters, mixed with the array length', function () { expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], null)) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], '2')) .not.toThrow(); expect(() => GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], null)) .not.toThrow(); }); }); describe('GError', function () { it('marshals a GError** signature as an exception', function () { expect(() => GIMarshallingTests.gerror()).toThrow(); }); it('marshals a GError** at the end of the signature as an exception', function () { expect(() => GIMarshallingTests.gerror_array_in([-1, 0, 1, 2])).toThrowMatching(e => e.matches(GLib.quark_from_static_string(GIMarshallingTests.CONSTANT_GERROR_DOMAIN), GIMarshallingTests.CONSTANT_GERROR_CODE) && e.message === GIMarshallingTests.CONSTANT_GERROR_MESSAGE); }); it('marshals a GError** elsewhere in the signature as an out parameter', function () { expect(GIMarshallingTests.gerror_out()).toEqual([ jasmine.any(GLib.Error), 'we got an error, life is shit', ]); }); it('marshals a GError** elsewhere in the signature as an out parameter with transfer none', function () { expect(GIMarshallingTests.gerror_out_transfer_none()).toEqual([ jasmine.any(GLib.Error), 'we got an error, life is shit', ]); }); it('marshals GError as a return value', function () { expect(GIMarshallingTests.gerror_return()).toEqual(jasmine.any(GLib.Error)); }); }); describe('Overrides', function () { it('can add constants', function () { expect(GIMarshallingTests.OVERRIDES_CONSTANT).toEqual(7); }); it('can override a struct method', function () { const struct = new GIMarshallingTests.OverridesStruct(); expect(struct.method()).toEqual(6); }); it('returns the overridden struct', function () { const obj = GIMarshallingTests.OverridesStruct.returnv(); expect(obj).toBeInstanceOf(GIMarshallingTests.OverridesStruct); }); it('can override an object constructor', function () { const obj = new GIMarshallingTests.OverridesObject(42); expect(obj.num).toEqual(42); }); it('can override an object method', function () { const obj = new GIMarshallingTests.OverridesObject(); expect(obj.method()).toEqual(6); }); it('returns the overridden object', function () { const obj = GIMarshallingTests.OverridesObject.returnv(); expect(obj).toBeInstanceOf(GIMarshallingTests.OverridesObject); }); }); describe('Filename', function () { testReturnValue('filename_list', []); }); describe('GObject.ParamSpec', function () { const pspec = GObject.ParamSpec.boolean('mybool', 'My Bool', 'My boolean property', GObject.ParamFlags.READWRITE, true); testInParameter('param_spec', pspec, { funcName: 'param_spec_in_bool', }); const expectedProps = { name: 'test-param', nick: 'test', blurb: 'This is a test', default_value: '42', flags: GObject.ParamFlags.READABLE, value_type: GObject.TYPE_STRING, }; testReturnValue('param_spec', jasmine.objectContaining(expectedProps)); testOutParameter('param_spec', jasmine.objectContaining(expectedProps)); }); describe('GObject properties', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.PropertiesObject(); }); function testPropertyGetSet(type, value1, value2, skip = false) { const snakeCase = `some_${type}`; const paramCase = snakeCase.replaceAll('_', '-'); const camelCase = snakeCase.replace(/(_\w)/g, match => match.toUpperCase().replace('_', '')); [snakeCase, paramCase, camelCase].forEach(propertyName => { it(`gets and sets a ${type} property as ${propertyName}`, function () { if (skip) pending(skip); const handler = jasmine.createSpy(`handle-${paramCase}`); const id = obj.connect(`notify::${paramCase}`, handler); obj[propertyName] = value1; expect(obj[propertyName]).toEqual(value1); expect(handler).toHaveBeenCalledTimes(1); obj[propertyName] = value2; expect(obj[propertyName]).toEqual(value2); expect(handler).toHaveBeenCalledTimes(2); obj.disconnect(id); }); }); } function testPropertyGetSetBigInt(type, value1, value2) { const snakeCase = `some_${type}`; const paramCase = snakeCase.replaceAll('_', '-'); it(`gets and sets a ${type} property with a bigint`, function () { const handler = jasmine.createSpy(`handle-${paramCase}`); const id = obj.connect(`notify::${paramCase}`, handler); obj[snakeCase] = value1; expect(handler).toHaveBeenCalledTimes(1); expect(obj[snakeCase]).toEqual(Number(value1)); obj[snakeCase] = value2; expect(handler).toHaveBeenCalledTimes(2); expect(obj[snakeCase]).toEqual(Number(value2)); obj.disconnect(id); }); } testPropertyGetSet('boolean', true, false); testPropertyGetSet('char', 42, 64); testPropertyGetSet('uchar', 42, 64); testPropertyGetSet('int', 42, 64); testPropertyGetSet('uint', 42, 64); testPropertyGetSet('long', 42, 64); testPropertyGetSet('ulong', 42, 64); testPropertyGetSet('int64', 42, 64); testPropertyGetSet('int64', Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); testPropertyGetSetBigInt('int64', BigIntLimits.int64.min, BigIntLimits.int64.max); testPropertyGetSet('uint64', 42, 64); testPropertyGetSetBigInt('uint64', BigIntLimits.int64.max, BigIntLimits.int64.umax); testPropertyGetSet('string', 'Cjs', 'is cool!'); testPropertyGetSet('string', 'and supports', null); it('get and sets out-of-range values throws', function () { expect(() => { obj.some_int64 = Limits.int64.max; }).toThrowError(/out of range/); expect(() => { obj.some_int64 = BigIntLimits.int64.max + 1n; }).toThrowError(/out of range/); expect(() => { obj.some_int64 = BigIntLimits.int64.min - 1n; }).toThrowError(/out of range/); expect(() => { obj.some_int64 = BigIntLimits.int64.umax; }).toThrowError(/out of range/); expect(() => { obj.some_int64 = -BigIntLimits.int64.umax; }).toThrowError(/out of range/); expect(() => { obj.some_uint64 = Limits.int64.min; }).toThrowError(/out of range/); expect(() => { obj.some_uint64 = BigIntLimits.int64.umax + 100n; }).toThrowError(/out of range/); }); it('gets and sets a float property', function () { const handler = jasmine.createSpy('handle-some-float'); const id = obj.connect('notify::some-float', handler); obj.some_float = Math.E; expect(handler).toHaveBeenCalledTimes(1); expect(obj.some_float).toBeCloseTo(Math.E); obj.some_float = Math.PI; expect(handler).toHaveBeenCalledTimes(2); expect(obj.some_float).toBeCloseTo(Math.PI); obj.disconnect(id); }); it('gets and sets a double property', function () { const handler = jasmine.createSpy('handle-some-double'); const id = obj.connect('notify::some-double', handler); obj.some_double = Math.E; expect(handler).toHaveBeenCalledTimes(1); expect(obj.some_double).toBeCloseTo(Math.E); obj.some_double = Math.PI; expect(handler).toHaveBeenCalledTimes(2); expect(obj.some_double).toBeCloseTo(Math.PI); obj.disconnect(id); }); testPropertyGetSet('strv', ['0', '1', '2'], []); testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(), new GIMarshallingTests.BoxedStruct({long_: 42})); testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(), null); testPropertyGetSet('boxed_glist', null, null); testPropertyGetSet('gvalue', 42, 'foo'); testPropertyGetSetBigInt('gvalue', BigIntLimits.int64.umax, BigIntLimits.int64.min); testPropertyGetSet('variant', new GLib.Variant('b', true), new GLib.Variant('s', 'hello')); testPropertyGetSet('variant', new GLib.Variant('x', BigIntLimits.int64.min), new GLib.Variant('x', BigIntLimits.int64.max)); testPropertyGetSet('variant', new GLib.Variant('t', BigIntLimits.int64.max), new GLib.Variant('t', BigIntLimits.int64.umax)); testPropertyGetSet('object', new GObject.Object(), new GIMarshallingTests.Object({int: 42})); testPropertyGetSet('object', new GIMarshallingTests.PropertiesObject({ 'some-int': 23, 'some-string': '👾', }), null); testPropertyGetSet('flags', GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2); testPropertyGetSet('enum', GIMarshallingTests.GEnum.VALUE2, GIMarshallingTests.GEnum.VALUE3); testPropertyGetSet('byte_array', Uint8Array.of(1, 2, 3), new TextEncoder().encode('👾')); testPropertyGetSet('byte_array', Uint8Array.of(3, 2, 1), null); it('gets a read-only property', function () { expect(obj.some_readonly).toEqual(42); }); it('throws when setting a read-only property', function () { expect(() => (obj.some_readonly = 35)).toThrow(); }); it('allows to set/get deprecated properties', function () { if (!GObject.Object.find_property.call( GIMarshallingTests.PropertiesObject, 'some-deprecated-int')) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/410'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); obj.some_deprecated_int = 35; GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); expect(obj.some_deprecated_int).toBe(35); GLib.test_assert_expected_messages_internal('Cjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); }); const JSOverridingProperty = GObject.registerClass( class Overriding extends GIMarshallingTests.PropertiesObject { constructor(params) { super(params); this.intValue = 55; this.stringValue = 'a string'; } set some_int(v) { this.intValue = v; } get someInt() { return this.intValue; } set someString(v) { this.stringValue = v; } get someString() { return this.stringValue; } }); it('can be overridden from JS', function () { const intHandler = jasmine.createSpy('handle-some-int'); const stringHandler = jasmine.createSpy('handle-some-string'); const overriding = new JSOverridingProperty({ 'someInt': 45, 'someString': 'other string', }); const ids = []; ids.push(overriding.connect('notify::some-int', intHandler)); ids.push(overriding.connect('notify::some-string', stringHandler)); expect(overriding['some-int']).toBe(45); expect(overriding.someInt).toBe(55); expect(overriding.some_int).toBeUndefined(); expect(overriding.intValue).toBe(55); expect(overriding.someString).toBe('a string'); expect(overriding.some_string).toBe('other string'); expect(intHandler).not.toHaveBeenCalled(); expect(stringHandler).not.toHaveBeenCalled(); overriding.some_int = 35; expect(overriding['some-int']).toBe(45); expect(overriding.some_int).toBeUndefined(); expect(overriding.someInt).toBe(35); expect(overriding.intValue).toBe(35); expect(intHandler).not.toHaveBeenCalled(); overriding.someInt = 85; expect(overriding['some-int']).toBe(45); expect(overriding.someInt).toBe(35); expect(overriding.some_int).toBeUndefined(); expect(overriding.intValue).toBe(35); expect(intHandler).not.toHaveBeenCalled(); overriding['some-int'] = 123; expect(overriding['some-int']).toBe(123); expect(overriding.someInt).toBe(35); expect(overriding.some_int).toBeUndefined(); expect(overriding.intValue).toBe(35); expect(intHandler).toHaveBeenCalledTimes(1); overriding['some-string'] = '🐧'; expect(overriding['some-string']).toBe('🐧'); expect(overriding.some_string).toBe('🐧'); expect(overriding.someString).toBe('a string'); expect(overriding.stringValue).toBe('a string'); expect(stringHandler).toHaveBeenCalledTimes(1); overriding.some_string = '🍕'; expect(overriding['some-string']).toBe('🍕'); expect(overriding.some_string).toBe('🍕'); expect(overriding.someString).toBe('a string'); expect(overriding.stringValue).toBe('a string'); expect(stringHandler).toHaveBeenCalledTimes(2); overriding.someString = '🍝'; expect(overriding['some-string']).toBe('🍕'); expect(overriding.some_string).toBe('🍕'); expect(overriding.someString).toBe('🍝'); expect(overriding.stringValue).toBe('🍝'); expect(stringHandler).toHaveBeenCalledTimes(2); ids.forEach(id => overriding.disconnect(id)); }); }); describe('GObject signals', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.SignalsObject(); }); function testSignalEmission(type, transfer, value, skip = false) { it(`checks emission of signal with ${type} argument and transfer ${transfer}`, function () { if (skip) pending(skip); const signalCallback = jasmine.createSpy('signalCallback'); if (transfer !== 'none') type += `-${transfer}`; const signalName = `some_${type}`; const funcName = `emit_${type}`.replaceAll('-', '_'); const signalId = obj.connect(signalName, signalCallback); obj[funcName](); obj.disconnect(signalId); expect(signalCallback).toHaveBeenCalledOnceWith(obj, value); }); } ['none', 'container', 'none'].forEach(transfer => { testSignalEmission('boxed-gptrarray-utf8', transfer, ['0', '1', '2']); testSignalEmission('boxed-gptrarray-boxed-struct', transfer, [ new GIMarshallingTests.BoxedStruct({long_: 42}), new GIMarshallingTests.BoxedStruct({long_: 43}), new GIMarshallingTests.BoxedStruct({long_: 44}), ]); testSignalEmission('hash-table-utf8-int', transfer, { '-1': 1, '0': 0, '1': -1, '2': -2, }); }); ['none', 'full'].forEach(transfer => { let skip = false; if (transfer === 'full') skip = 'https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/470'; testSignalEmission('boxed-struct', transfer, jasmine.objectContaining({ long_: 99, string_: 'a string', g_strv: ['foo', 'bar', 'baz'], }), skip); }); it('with not-ref-counted boxed types with transfer full are properly handled', function () { // When using JS side only we can handle properly the problems of // https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/470 const callbackFunc = jasmine.createSpy('callbackFunc'); const signalId = obj.connect('some-boxed-struct-full', callbackFunc); obj.emit('some-boxed-struct-full', new GIMarshallingTests.BoxedStruct({long_: 44})); obj.disconnect(signalId); expect(callbackFunc).toHaveBeenCalledOnceWith(obj, new GIMarshallingTests.BoxedStruct({long_: 44})); }); }); cjs-128.1/installed-tests/js/testGLib.js0000664000175000017500000003470315116312211017036 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna // SPDX-FileCopyrightText: 2019, 2023 Philip Chimento const GLib = imports.gi.GLib; describe('GVariant constructor', function () { it('constructs a string variant', function () { let strVariant = new GLib.Variant('s', 'mystring'); expect(strVariant.get_string()[0]).toEqual('mystring'); expect(strVariant.deepUnpack()).toEqual('mystring'); }); it('constructs a string variant (backwards compatible API)', function () { let strVariant = new GLib.Variant('s', 'mystring'); let strVariantOld = GLib.Variant.new('s', 'mystring'); expect(strVariant.equal(strVariantOld)).toBeTruthy(); }); it('constructs a struct variant', function () { let structVariant = new GLib.Variant('(sogvau)', [ 'a string', '/a/object/path', 'asig', // nature new GLib.Variant('s', 'variant'), [7, 3], ]); expect(structVariant.n_children()).toEqual(5); let unpacked = structVariant.deepUnpack(); expect(unpacked[0]).toEqual('a string'); expect(unpacked[1]).toEqual('/a/object/path'); expect(unpacked[2]).toEqual('asig'); expect(unpacked[3] instanceof GLib.Variant).toBeTruthy(); expect(unpacked[3].deepUnpack()).toEqual('variant'); expect(Array.isArray(unpacked[4])).toBeTruthy(); expect(unpacked[4].length).toEqual(2); }); it('constructs a maybe variant', function () { let maybeVariant = new GLib.Variant('ms', null); expect(maybeVariant.deepUnpack()).toBeNull(); maybeVariant = new GLib.Variant('ms', 'string'); expect(maybeVariant.deepUnpack()).toEqual('string'); }); it('constructs a byte array variant', function () { const byteArray = new TextEncoder().encode('pizza'); const byteArrayVariant = new GLib.Variant('ay', byteArray); expect(new TextDecoder().decode(byteArrayVariant.deepUnpack())) .toEqual('pizza'); }); it('constructs a byte array variant from a string', function () { const byteArrayVariant = new GLib.Variant('ay', 'pizza'); expect(new TextDecoder().decode(byteArrayVariant.deepUnpack())) .toEqual('pizza\0'); }); it('0-terminates a byte array variant constructed from a string', function () { const byteArrayVariant = new GLib.Variant('ay', 'pizza'); const a = byteArrayVariant.deepUnpack(); [112, 105, 122, 122, 97, 0].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('does not 0-terminate a byte array variant constructed from a Uint8Array', function () { const byteArray = new TextEncoder().encode('pizza'); const byteArrayVariant = new GLib.Variant('ay', byteArray); const a = byteArrayVariant.deepUnpack(); [112, 105, 122, 122, 97].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); }); describe('GVariant unpack', function () { let v; beforeEach(function () { v = new GLib.Variant('a{sv}', {foo: new GLib.Variant('s', 'bar')}); }); it('preserves type information if the unpacked object contains variants', function () { expect(v.deepUnpack().foo instanceof GLib.Variant).toBeTruthy(); expect(v.deep_unpack().foo instanceof GLib.Variant).toBeTruthy(); }); it('recursive leaves no variants in the unpacked object', function () { expect(v.recursiveUnpack().foo instanceof GLib.Variant).toBeFalsy(); expect(v.recursiveUnpack().foo).toEqual('bar'); }); }); describe('GVariant strv', function () { let v; beforeEach(function () { v = new GLib.Variant('as', ['a', 'b', 'c', 'foo']); }); it('unpacked matches constructed', function () { expect(v.deepUnpack()).toEqual(['a', 'b', 'c', 'foo']); }); it('getter matches constructed', function () { expect(v.get_strv()).toEqual(['a', 'b', 'c', 'foo']); }); it('getter (dup) matches constructed', function () { expect(v.dup_strv()).toEqual(['a', 'b', 'c', 'foo']); }); }); describe('GVariantDict lookup', function () { let variantDict; beforeEach(function () { variantDict = new GLib.VariantDict(null); variantDict.insert_value('foo', GLib.Variant.new_string('bar')); }); it('returns the unpacked variant', function () { expect(variantDict.lookup('foo')).toEqual('bar'); expect(variantDict.lookup('foo', null)).toEqual('bar'); expect(variantDict.lookup('foo', 's')).toEqual('bar'); expect(variantDict.lookup('foo', new GLib.VariantType('s'))).toEqual('bar'); }); it("returns null if the key isn't present", function () { expect(variantDict.lookup('bar')).toBeNull(); expect(variantDict.lookup('bar', null)).toBeNull(); expect(variantDict.lookup('bar', 's')).toBeNull(); expect(variantDict.lookup('bar', new GLib.VariantType('s'))).toBeNull(); }); }); describe('GLib spawn processes', function () { it('sync with null envp', function () { const [ret, stdout, stderr, exit_status] = GLib.spawn_sync( null, ['true'], null, GLib.SpawnFlags.SEARCH_PATH, null); expect(ret).toBe(true); expect(stdout).toEqual(new Uint8Array()); expect(stderr).toEqual(new Uint8Array()); expect(exit_status).toBe(0); }).pend('https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3523'); }); describe('GLib string function overrides', function () { let numExpectedWarnings; function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { GLib.test_assert_expected_messages_internal('Cjs', 'testGLib.js', 0, `test GLib.${testName}`); } numExpectedWarnings = 0; } beforeEach(function () { numExpectedWarnings = 0; }); // TODO: Add Regress.func_not_nullable_untyped_gpointer_in and move to testRegress.js it('GLib.str_hash errors when marshalling null to a not-nullable parameter', function () { // This tests that we don't marshal null to a not-nullable untyped gpointer. expect(() => GLib.str_hash(null)).toThrowError( /Argument [a-z]+ may not be null/ ); }); it('GLib.stpcpy', function () { expect(() => GLib.stpcpy('dest', 'src')).toThrowError(/not introspectable/); }); it('GLib.strstr_len', function () { expectWarnings(4); expect(GLib.strstr_len('haystack', -1, 'needle')).toBeNull(); expect(GLib.strstr_len('haystacks', -1, 'stack')).toEqual('stacks'); expect(GLib.strstr_len('haystacks', 4, 'stack')).toBeNull(); expect(GLib.strstr_len('haystack', 4, 'ays')).toEqual('aystack'); assertWarnings('strstr_len'); }); it('GLib.strrstr', function () { expectWarnings(2); expect(GLib.strrstr('haystack', 'needle')).toBeNull(); expect(GLib.strrstr('hackstacks', 'ack')).toEqual('acks'); assertWarnings('strrstr'); }); it('GLib.strrstr_len', function () { expectWarnings(3); expect(GLib.strrstr_len('haystack', -1, 'needle')).toBeNull(); expect(GLib.strrstr_len('hackstacks', -1, 'ack')).toEqual('acks'); expect(GLib.strrstr_len('hackstacks', 4, 'ack')).toEqual('ackstacks'); assertWarnings('strrstr_len'); }); it('GLib.strup', function () { expectWarnings(1); expect(GLib.strup('string')).toEqual('STRING'); assertWarnings('strup'); }); it('GLib.strdown', function () { expectWarnings(1); expect(GLib.strdown('STRING')).toEqual('string'); assertWarnings('strdown'); }); it('GLib.strreverse', function () { expectWarnings(1); expect(GLib.strreverse('abcdef')).toEqual('fedcba'); assertWarnings('strreverse'); }); it('GLib.ascii_dtostr', function () { expectWarnings(2); expect(GLib.ascii_dtostr('', GLib.ASCII_DTOSTR_BUF_SIZE, Math.PI)) .toEqual('3.141592653589793'); expect(GLib.ascii_dtostr('', 4, Math.PI)).toEqual('3.14'); assertWarnings('ascii_dtostr'); }); it('GLib.ascii_formatd', function () { expect(() => GLib.ascii_formatd('', 8, '%e', Math.PI)).toThrowError(/not introspectable/); }); it('GLib.strchug', function () { expectWarnings(2); expect(GLib.strchug('text')).toEqual('text'); expect(GLib.strchug(' text')).toEqual('text'); assertWarnings('strchug'); }); it('GLib.strchomp', function () { expectWarnings(2); expect(GLib.strchomp('text')).toEqual('text'); expect(GLib.strchomp('text ')).toEqual('text'); assertWarnings('strchomp'); }); it('GLib.strstrip', function () { expectWarnings(4); expect(GLib.strstrip('text')).toEqual('text'); expect(GLib.strstrip(' text')).toEqual('text'); expect(GLib.strstrip('text ')).toEqual('text'); expect(GLib.strstrip(' text ')).toEqual('text'); assertWarnings('strstrip'); }); it('GLib.strdelimit', function () { expectWarnings(4); expect(GLib.strdelimit('1a2b3c4', 'abc', '_'.charCodeAt())).toEqual('1_2_3_4'); expect(GLib.strdelimit('1-2_3<4', null, '|'.charCodeAt())).toEqual('1|2|3|4'); expect(GLib.strdelimit('1a2b3c4', 'abc', '_')).toEqual('1_2_3_4'); expect(GLib.strdelimit('1-2_3<4', null, '|')).toEqual('1|2|3|4'); assertWarnings('strdelimit'); }); it('GLib.strcanon', function () { expectWarnings(2); expect(GLib.strcanon('1a2b3c4', 'abc', '?'.charCodeAt())).toEqual('?a?b?c?'); expect(GLib.strcanon('1a2b3c4', 'abc', '?')).toEqual('?a?b?c?'); assertWarnings('strcanon'); }); it('GLib.base64_encode', function () { const ascii = 'hello\0world'; const base64 = 'aGVsbG8Ad29ybGQ='; expect(GLib.base64_encode(ascii)).toBe(base64); const encoded = new TextEncoder().encode(ascii); expect(GLib.base64_encode(encoded)).toBe(base64); }); }); describe('GLib.MatchInfo', function () { let shouldBePatchedProtoype; beforeAll(function () { shouldBePatchedProtoype = GLib.MatchInfo.prototype; }); let regex; beforeEach(function () { regex = new GLib.Regex('h(?el)lo', 0, 0); }); it('cannot be constructed', function () { expect(() => new GLib.MatchInfo()).toThrow(); expect(() => new shouldBePatchedProtoype.constructor()).toThrow(); }); it('is returned from GLib.Regex.match', function () { const [, match] = regex.match('foo', 0); expect(match).toBeInstanceOf(GLib.MatchInfo); expect(match.toString()).toContain('CjsPrivate.MatchInfo'); }); it('stores the string that was matched', function () { const [, match] = regex.match('foo', 0); expect(match.get_string()).toEqual('foo'); }); it('truncates the string when it has zeroes as g_match_info_get_string() would', function () { const [, match] = regex.match_full('ab\0cd', 0, 0); expect(match.get_string()).toEqual('ab'); }); it('is returned from GLib.Regex.match_all', function () { const [, match] = regex.match_all('foo', 0); expect(match).toBeInstanceOf(GLib.MatchInfo); expect(match.toString()).toContain('CjsPrivate.MatchInfo'); }); it('is returned from GLib.Regex.match_all_full', function () { const [, match] = regex.match_all_full('foo', 0, 0); expect(match).toBeInstanceOf(GLib.MatchInfo); expect(match.toString()).toContain('CjsPrivate.MatchInfo'); }); it('is returned from GLib.Regex.match_full', function () { const [, match] = regex.match_full('foo', 0, 0); expect(match).toBeInstanceOf(GLib.MatchInfo); expect(match.toString()).toContain('CjsPrivate.MatchInfo'); }); describe('method', function () { let match; beforeEach(function () { [, match] = regex.match('hello hello world', 0); }); it('expand_references', function () { expect(match.expand_references('\\0-\\1')).toBe('hello-el'); expect(shouldBePatchedProtoype.expand_references.call(match, '\\0-\\1')).toBe('hello-el'); }); it('fetch', function () { expect(match.fetch(0)).toBe('hello'); expect(shouldBePatchedProtoype.fetch.call(match, 0)).toBe('hello'); }); it('fetch_all', function () { expect(match.fetch_all()).toEqual(['hello', 'el']); expect(shouldBePatchedProtoype.fetch_all.call(match)).toEqual(['hello', 'el']); }); it('fetch_named', function () { expect(match.fetch_named('foo')).toBe('el'); expect(shouldBePatchedProtoype.fetch_named.call(match, 'foo')).toBe('el'); }); it('fetch_named_pos', function () { expect(match.fetch_named_pos('foo')).toEqual([true, 1, 3]); expect(shouldBePatchedProtoype.fetch_named_pos.call(match, 'foo')).toEqual([true, 1, 3]); }); it('fetch_pos', function () { expect(match.fetch_pos(1)).toEqual([true, 1, 3]); expect(shouldBePatchedProtoype.fetch_pos.call(match, 1)).toEqual([true, 1, 3]); }); it('get_match_count', function () { expect(match.get_match_count()).toBe(2); expect(shouldBePatchedProtoype.get_match_count.call(match)).toBe(2); }); it('get_string', function () { expect(match.get_string()).toBe('hello hello world'); expect(shouldBePatchedProtoype.get_string.call(match)).toBe('hello hello world'); }); it('is_partial_match', function () { expect(match.is_partial_match()).toBeFalse(); expect(shouldBePatchedProtoype.is_partial_match.call(match)).toBeFalse(); }); it('matches', function () { expect(match.matches()).toBeTrue(); expect(shouldBePatchedProtoype.matches.call(match)).toBeTrue(); }); it('next', function () { expect(match.next()).toBeTrue(); expect(shouldBePatchedProtoype.next.call(match)).toBeFalse(); }); }); }); cjs-128.1/installed-tests/js/testGLibLogWriter.js0000664000175000017500000000546115116312211020674 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh // eslint-disable-next-line /// import GLib from 'gi://GLib'; import {arrayLikeWithExactContents} from './matchers.js'; function encodedString(str) { const encoder = new TextEncoder(); const encoded = encoder.encode(str); return arrayLikeWithExactContents(encoded); } describe('GLib Structured logging handler', function () { /** @type {jasmine.Spy<(_level: any, _fields: any) => any>} */ let writer_func; beforeAll(function () { writer_func = jasmine.createSpy( 'Log test writer func', function (_level, _fields) { return GLib.LogWriterOutput.HANDLED; } ); writer_func.and.callThrough(); GLib.log_set_writer_func(writer_func); }); beforeEach(function () { writer_func.calls.reset(); }); it('writes a message', function () { GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, { MESSAGE: 'a message', }); expect(writer_func).toHaveBeenCalledWith( GLib.LogLevelFlags.LEVEL_MESSAGE, jasmine.objectContaining({MESSAGE: encodedString('a message')}) ); }); it('writes a warning', function () { GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_WARNING, { MESSAGE: 'a warning', }); expect(writer_func).toHaveBeenCalledWith( GLib.LogLevelFlags.LEVEL_WARNING, jasmine.objectContaining({MESSAGE: encodedString('a warning')}) ); }); it('preserves a custom string field', function () { GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, { MESSAGE: 'with a custom field', GJS_CUSTOM_FIELD: 'a custom value', }); expect(writer_func).toHaveBeenCalledWith( GLib.LogLevelFlags.LEVEL_MESSAGE, jasmine.objectContaining({ MESSAGE: encodedString('with a custom field'), GJS_CUSTOM_FIELD: encodedString('a custom value'), }) ); }); it('preserves a custom byte array field', function () { GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, { MESSAGE: 'with a custom field', GJS_CUSTOM_FIELD: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]), }); expect(writer_func).toHaveBeenCalledWith( GLib.LogLevelFlags.LEVEL_MESSAGE, jasmine.objectContaining({ MESSAGE: encodedString('with a custom field'), GJS_CUSTOM_FIELD: arrayLikeWithExactContents([ 0, 1, 2, 3, 4, 5, 6, 7, ]), }) ); }); }); cjs-128.1/installed-tests/js/testGObject.js0000664000175000017500000001405315116312211017532 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna // SPDX-FileCopyrightText: 2018 Red Hat, Inc. // This is where overrides in modules/core/overrides/GObject.js are tested, // except for the class machinery, interface machinery, and GObject.ParamSpec, // which are big enough to get their own files. const {GLib, GObject} = imports.gi; const {system: System} = imports; const TestObj = GObject.registerClass({ Properties: { int: GObject.ParamSpec.int('int', '', '', GObject.ParamFlags.READWRITE, 0, GLib.MAXINT32, 0), string: GObject.ParamSpec.string('string', '', '', GObject.ParamFlags.READWRITE, ''), }, Signals: { test: {}, }, }, class TestObj extends GObject.Object {}); describe('GObject overrides', function () { it('GObject.set()', function () { const o = new TestObj(); o.set({string: 'Answer', int: 42}); expect(o.string).toBe('Answer'); expect(o.int).toBe(42); }); describe('Signal alternative syntax', function () { let o, handler; beforeEach(function () { handler = jasmine.createSpy('handler'); o = new TestObj(); const handlerId = GObject.signal_connect(o, 'test', handler); handler.and.callFake(() => GObject.signal_handler_disconnect(o, handlerId)); GObject.signal_emit_by_name(o, 'test'); }); it('handler is called with the right object', function () { expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(o); }); it('disconnected handler is not called', function () { handler.calls.reset(); GObject.signal_emit_by_name(o, 'test'); expect(handler).not.toHaveBeenCalled(); }); }); it('toString() shows the native object address', function () { const o = new TestObj(); const address = System.addressOfGObject(o); expect(o.toString()).toMatch( new RegExp(`[object instance wrapper .* jsobj@0x[a-f0-9]+ native@${address}`)); }); }); describe('GObject should', function () { const types = ['gpointer', 'GBoxed', 'GParam', 'GInterface', 'GObject', 'GVariant']; types.forEach(type => { it(`be able to create a GType object for ${type}`, function () { const gtype = GObject.Type(type); expect(gtype.name).toEqual(type); }); }); it('be able to query signals', function () { const query = GObject.signal_query(1); expect(query instanceof GObject.SignalQuery).toBeTruthy(); expect(query.param_types).not.toBeNull(); expect(Array.isArray(query.param_types)).toBeTruthy(); expect(query.signal_id).toBe(1); }); }); describe('GObject.Object.new()', function () { const gon = GObject.Object.new; it('can be called with a property bag', function () { const o = gon(TestObj, { string: 'Answer', int: 42, }); expect(o.string).toBe('Answer'); expect(o.int).toBe(42); }); it('can be called to construct an object without setting properties', function () { const o1 = gon(TestObj); expect(o1.string).toBe(''); expect(o1.int).toBe(0); const o2 = gon(TestObj, {}); expect(o2.string).toBe(''); expect(o2.int).toBe(0); }); it('complains about wrong types', function () { expect(() => gon(TestObj, { string: 42, int: 'Answer', })).toThrow(); }); it('complains about wrong properties', function () { expect(() => gon(TestObj, {foo: 'bar'})).toThrow(); }); it('can construct C GObjects as well', function () { const o = gon(GObject.Object, {}); expect(o.constructor.$gtype.name).toBe('GObject'); }); }); describe('GObject.Object.new_with_properties()', function () { const gonwp = GObject.Object.new_with_properties; it('can be called with two arrays', function () { const o = gonwp(TestObj, ['string', 'int'], ['Answer', 42]); expect(o.string).toBe('Answer'); expect(o.int).toBe(42); }); it('can be called to construct an object without setting properties', function () { const o = gonwp(TestObj, [], []); expect(o.string).toBe(''); expect(o.int).toBe(0); }); it('complains about various incorrect usages', function () { expect(() => gonwp(TestObj)).toThrow(); expect(() => gonwp(TestObj, ['string', 'int'])).toThrow(); expect(() => gonwp(TestObj, ['string', 'int'], ['Answer'])).toThrow(); expect(() => gonwp(TestObj, {}, ['Answer', 42])).toThrow(); }); it('complains about wrong types', function () { expect(() => gonwp(TestObj, ['string', 'int'], [42, 'Answer'])).toThrow(); }); it('complains about wrong properties', function () { expect(() => gonwp(TestObj, ['foo'], ['bar'])).toThrow(); }); it('can construct C GObjects as well', function () { const o = gonwp(GObject.Object, [], []); expect(o.constructor.$gtype.name).toBe('GObject'); }); }); describe('Unsupported methods', function () { let o; beforeEach(function () { o = new GObject.Object(); }); it('throws on data stashing methods', function () { expect(() => o.get_data('foo')).toThrow(); expect(() => o.get_qdata(1)).toThrow(); expect(() => o.set_data('foo', 'bar')).toThrow(); expect(() => o.steal_data('foo')).toThrow(); expect(() => o.steal_qdata(1)).toThrow(); }); it('throws on refcounting methods', function () { const refcount = System.refcount(o); const floating = o.is_floating(); expect(() => o.ref()).toThrow(); expect(() => o.unref()).toThrow(); expect(() => o.ref_sink()).toThrow(); expect(() => o.force_floating()).toThrow(); expect(System.refcount(o)).toBe(refcount); expect(o.is_floating()).toBe(floating); }); }); cjs-128.1/installed-tests/js/testGObjectClass.js0000664000175000017500000020143715116312211020524 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna const System = imports.system; imports.gi.versions.Gtk = '3.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const MyObject = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', 'A read write parameter', GObject.ParamFlags.READWRITE, ''), 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly', 'A readonly parameter', GObject.ParamFlags.READABLE, ''), 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly', 'A readwrite construct-only parameter', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 'default'), }, Signals: { 'empty': {}, 'minimal': {param_types: [GObject.TYPE_INT, GObject.TYPE_INT]}, 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_INT, param_types: [], }, 'run-last': {flags: GObject.SignalFlags.RUN_LAST}, 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [GObject.TYPE_STRING], }, }, }, class MyObject extends GObject.Object { get readwrite() { if (typeof this._readwrite === 'undefined') return 'foo'; return this._readwrite; } set readwrite(val) { if (val === 'ignore') return; this._readwrite = val; } get readonly() { if (typeof this._readonly === 'undefined') return 'bar'; return this._readonly; } set readonly(val) { // this should never be called void val; this._readonly = 'bogus'; } get construct() { if (typeof this._constructProp === 'undefined') return null; return this._constructProp; } set construct(val) { this._constructProp = val; } notifyProp() { this._readonly = 'changed'; this.notify('readonly'); } emitEmpty() { this.emit('empty'); } emitMinimal(one, two) { this.emit('minimal', one, two); } emitFull() { return this.emit('full'); } emitDetailed() { this.emit('detailed::one'); this.emit('detailed::two'); } emitRunLast(callback) { this._run_last_callback = callback; this.emit('run-last'); } on_run_last() { this._run_last_callback(); } on_empty() { this.empty_called = true; } on_full() { this.full_default_handler_called = true; return 79; } }); const MyObjectWithCustomConstructor = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', 'A read write parameter', GObject.ParamFlags.READWRITE, ''), 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly', 'A readonly parameter', GObject.ParamFlags.READABLE, ''), 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly', 'A readwrite construct-only parameter', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ''), }, Signals: { 'empty': {}, 'minimal': {param_types: [GObject.TYPE_INT, GObject.TYPE_INT]}, 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_INT, param_types: [], }, 'run-last': {flags: GObject.SignalFlags.RUN_LAST}, 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [GObject.TYPE_STRING], }, }, }, class MyObjectWithCustomConstructor extends GObject.Object { _readwrite; _readonly; _constructProp; constructor({readwrite = 'foo', readonly = 'bar', construct = 'default'} = {}) { super(); this._constructProp = construct; this._readwrite = readwrite; this._readonly = readonly; } get readwrite() { return this._readwrite; } set readwrite(val) { if (val === 'ignore') return; this._readwrite = val; } get readonly() { return this._readonly; } set readonly(val) { // this should never be called void val; this._readonly = 'bogus'; } get construct() { return this._constructProp; } notifyProp() { this._readonly = 'changed'; this.notify('readonly'); } emitEmpty() { this.emit('empty'); } emitMinimal(one, two) { this.emit('minimal', one, two); } emitFull() { return this.emit('full'); } emitDetailed() { this.emit('detailed::one'); this.emit('detailed::two'); } emitRunLast(callback) { this._run_last_callback = callback; this.emit('run-last'); } on_run_last() { this._run_last_callback(); } on_empty() { this.empty_called = true; } on_full() { this.full_default_handler_called = true; return 79; } }); const MyAbstractObject = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, }, class MyAbstractObject extends GObject.Object { }); const MyFinalObject = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.FINAL, }, class extends GObject.Object { }); const MyApplication = GObject.registerClass({ Signals: {'custom': {param_types: [GObject.TYPE_INT]}}, }, class MyApplication extends Gio.Application { emitCustom(n) { this.emit('custom', n); } }); const MyInitable = GObject.registerClass({ Implements: [Gio.Initable], }, class MyInitable extends GObject.Object { vfunc_init(cancellable) { if (!(cancellable instanceof Gio.Cancellable)) throw new Error('Bad argument'); this.inited = true; } }); const Derived = GObject.registerClass(class Derived extends MyObject { _init() { super._init({readwrite: 'yes'}); } }); const DerivedWithCustomConstructor = GObject.registerClass(class DerivedWithCustomConstructor extends MyObjectWithCustomConstructor { constructor() { super({readwrite: 'yes'}); } }); const ObjectWithDefaultConstructor = GObject.registerClass(class ObjectWithDefaultConstructor extends GObject.Object {}); const Cla$$ = GObject.registerClass(class Cla$$ extends MyObject {}); const MyCustomInit = GObject.registerClass(class MyCustomInit extends GObject.Object { _instance_init() { this.foo = true; } }); const NoName = GObject.registerClass(class extends GObject.Object {}); describe('GObject class with decorator', function () { let myInstance; beforeEach(function () { myInstance = new MyObject(); }); it('throws an error when not used with a GObject-derived class', function () { class Foo {} expect(() => GObject.registerClass(class Bar extends Foo {})).toThrow(); }); it('throws an error when used with an abstract class', function () { expect(() => new MyAbstractObject()).toThrow(); }); it('throws if final class is inherited from', function () { try { GObject.registerClass(class extends MyFinalObject {}); fail(); } catch (e) { expect(e.message).toEqual('Cannot inherit from a final type'); } }); it('constructs with default values for properties', function () { expect(myInstance.readwrite).toEqual('foo'); expect(myInstance.readonly).toEqual('bar'); expect(myInstance.construct).toEqual('default'); }); it('constructs with a hash of property values', function () { let myInstance2 = new MyObject({readwrite: 'baz', construct: 'asdf'}); expect(myInstance2.readwrite).toEqual('baz'); expect(myInstance2.readonly).toEqual('bar'); expect(myInstance2.construct).toEqual('asdf'); }); it('warns if more than one argument passed to the default constructor', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_MESSAGE, '*Too many arguments*'); new ObjectWithDefaultConstructor({}, 'this is ignored', 123); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectClassTooManyArguments'); }); it('throws an error if the first argument to the default constructor is not a property hash', function () { expect(() => new MyObject('this is wrong')).toThrow(); }); it('does not accept a property hash that is not a plain object', function () { expect(() => new MyObject(new GObject.Object())).toThrow(); }); const ui = ` baz quz `; it('constructs with property values from Gtk.Builder', function () { let builder = Gtk.Builder.new_from_string(ui, -1); let myInstance3 = builder.get_object('MyObject'); expect(myInstance3.readwrite).toEqual('baz'); expect(myInstance3.readonly).toEqual('bar'); expect(myInstance3.construct).toEqual('quz'); }); it('does not allow changing CONSTRUCT_ONLY properties', function () { myInstance.construct = 'val'; expect(myInstance.construct).toEqual('default'); }); it('has a name', function () { expect(MyObject.name).toEqual('MyObject'); }); // the following would (should) cause a CRITICAL: // myInstance.readonly = 'val'; it('has a notify signal', function () { let notifySpy = jasmine.createSpy('notifySpy'); myInstance.connect('notify::readonly', notifySpy); myInstance.notifyProp(); myInstance.notifyProp(); expect(notifySpy).toHaveBeenCalledTimes(2); }); function asyncIdle() { return new Promise(resolve => { GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { resolve(); return GLib.SOURCE_REMOVE; }); }); } it('disconnects connect_object signals on destruction', async function () { let callback = jasmine.createSpy('callback'); callback.myInstance = myInstance; const instance2 = new MyObject(); instance2.connect_object('empty', callback, myInstance, 0); instance2.emitEmpty(); instance2.emitEmpty(); expect(callback).toHaveBeenCalledTimes(2); const weakRef = new WeakRef(myInstance); myInstance = null; callback = null; await asyncIdle(); System.gc(); expect(weakRef.deref()).toBeUndefined(); }); it('can define its own signals', function () { let emptySpy = jasmine.createSpy('emptySpy'); myInstance.connect('empty', emptySpy); myInstance.emitEmpty(); expect(emptySpy).toHaveBeenCalled(); expect(myInstance.empty_called).toBeTruthy(); }); it('passes emitted arguments to signal handlers', function () { let minimalSpy = jasmine.createSpy('minimalSpy'); myInstance.connect('minimal', minimalSpy); myInstance.emitMinimal(7, 5); expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5); }); it('can return values from signals', function () { let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42); myInstance.connect('full', fullSpy); let result = myInstance.emitFull(); expect(fullSpy).toHaveBeenCalled(); expect(result).toEqual(42); }); it('does not call first-wins signal handlers after one returns a value', function () { let neverCalledSpy = jasmine.createSpy('neverCalledSpy'); myInstance.connect('full', () => 42); myInstance.connect('full', neverCalledSpy); myInstance.emitFull(); expect(neverCalledSpy).not.toHaveBeenCalled(); expect(myInstance.full_default_handler_called).toBeFalsy(); }); it('gets the return value of the default handler', function () { let result = myInstance.emitFull(); expect(myInstance.full_default_handler_called).toBeTruthy(); expect(result).toEqual(79); }); it('calls run-last default handler last', function () { let stack = []; let runLastSpy = jasmine.createSpy('runLastSpy') .and.callFake(() => { stack.push(1); }); myInstance.connect('run-last', runLastSpy); myInstance.emitRunLast(() => { stack.push(2); }); expect(stack).toEqual([1, 2]); }); it("can inherit from something that's not GObject.Object", function () { // ...and still get all the goodies of GObject.Class let instance = new MyApplication({application_id: 'org.gjs.Application'}); let customSpy = jasmine.createSpy('customSpy'); instance.connect('custom', customSpy); instance.emitCustom(73); expect(customSpy).toHaveBeenCalledWith(instance, 73); }); it('can implement an interface', function () { let instance = new MyInitable(); expect(instance instanceof Gio.Initable).toBeTruthy(); expect(instance instanceof Gio.AsyncInitable).toBeFalsy(); // Old syntax, backwards compatible expect(instance.constructor.implements(Gio.Initable)).toBeTruthy(); expect(instance.constructor.implements(Gio.AsyncInitable)).toBeFalsy(); }); it('can implement interface vfuncs', function () { let instance = new MyInitable(); expect(instance.inited).toBeFalsy(); instance.init(new Gio.Cancellable()); expect(instance.inited).toBeTruthy(); }); it('can be a subclass', function () { let derived = new Derived(); expect(derived instanceof Derived).toBeTruthy(); expect(derived instanceof MyObject).toBeTruthy(); expect(derived.readwrite).toEqual('yes'); }); it('can have any valid class name', function () { let obj = new Cla$$(); expect(obj instanceof Cla$$).toBeTruthy(); expect(obj instanceof MyObject).toBeTruthy(); }); it('handles anonymous class expressions', function () { const obj = new NoName(); expect(obj instanceof NoName).toBeTruthy(); const NoName2 = GObject.registerClass(class extends GObject.Object {}); const obj2 = new NoName2(); expect(obj2 instanceof NoName2).toBeTruthy(); }); it('calls its _instance_init() function while chaining up in constructor', function () { let instance = new MyCustomInit(); expect(instance.foo).toBeTruthy(); }); it('can have an interface-valued property', function () { const InterfacePropObject = GObject.registerClass({ Properties: { 'file': GObject.ParamSpec.object('file', 'File', 'File', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, Gio.File.$gtype), }, }, class InterfacePropObject extends GObject.Object {}); let file = Gio.File.new_for_path('dummy'); expect(() => new InterfacePropObject({file})).not.toThrow(); }); it('can have an int64 property', function () { const PropInt64 = GObject.registerClass({ Properties: { 'int64': GObject.ParamSpec.int64('int64', 'int64', 'int64', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GLib.MININT64_BIGINT, GLib.MAXINT64_BIGINT, 0), }, }, class PropInt64 extends GObject.Object {}); let int64 = GLib.MAXINT64_BIGINT - 5n; let obj = new PropInt64({int64}); expect(obj.int64).toEqual(Number(int64)); int64 = GLib.MININT64_BIGINT + 555n; obj = new PropInt64({int64}); expect(obj.int64).toEqual(Number(int64)); }); it('can have a default int64 property', function () { const defaultValue = GLib.MAXINT64_BIGINT - 1000n; const PropInt64Init = GObject.registerClass({ Properties: { 'int64': GObject.ParamSpec.int64('int64', 'int64', 'int64', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GLib.MININT64_BIGINT, GLib.MAXINT64_BIGINT, defaultValue), }, }, class PropDefaultInt64Init extends GObject.Object {}); const obj = new PropInt64Init(); expect(obj.int64).toEqual(Number(defaultValue)); }); it('can have an uint64 property', function () { const PropUint64 = GObject.registerClass({ Properties: { 'uint64': GObject.ParamSpec.uint64('uint64', 'uint64', 'uint64', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, GLib.MAXUINT64_BIGINT, 0), }, }, class PropUint64 extends GObject.Object {}); const uint64 = GLib.MAXUINT64_BIGINT - 5n; const obj = new PropUint64({uint64}); expect(obj.uint64).toEqual(Number(uint64)); }); it('can have a default uint64 property', function () { const defaultValue = GLib.MAXUINT64_BIGINT; const PropUint64Init = GObject.registerClass({ Properties: { 'uint64': GObject.ParamSpec.uint64('uint64', 'uint64', 'uint64', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0n, GLib.MAXUINT64_BIGINT, defaultValue), }, }, class PropDefaultUint64Init extends GObject.Object {}); const obj = new PropUint64Init(); expect(obj.uint64).toEqual(Number(defaultValue)); }); it('can override a property from the parent class', function () { const OverrideObject = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.override('readwrite', MyObject), }, }, class OverrideObject extends MyObject { get readwrite() { return this._subclass_readwrite; } set readwrite(val) { this._subclass_readwrite = `subclass${val}`; } }); let obj = new OverrideObject(); obj.readwrite = 'foo'; expect(obj.readwrite).toEqual('subclassfoo'); }); it('cannot override a non-existent property', function () { expect(() => GObject.registerClass({ Properties: { 'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object), }, }, class BadOverride extends GObject.Object {})).toThrow(); }); it('handles gracefully forgetting to override a C property', function () { GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "*Object class Gjs_ForgottenOverride doesn't implement property " + "'anchors' from interface 'GTlsFileDatabase'*"); // This is a random interface in Gio with a read-write property const ForgottenOverride = GObject.registerClass({ Implements: [Gio.TlsFileDatabase], }, class ForgottenOverride extends Gio.TlsDatabase {}); const obj = new ForgottenOverride(); expect(obj.anchors).not.toBeDefined(); expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); it('handles gracefully overriding a C property but forgetting the accessors', function () { // This is a random interface in Gio with a read-write property const ForgottenAccessors = GObject.registerClass({ Implements: [Gio.TlsFileDatabase], Properties: { 'anchors': GObject.ParamSpec.override('anchors', Gio.TlsFileDatabase), }, }, class ForgottenAccessors extends Gio.TlsDatabase {}); const obj = new ForgottenAccessors(); expect(obj.anchors).toBeNull(); // the property's default value obj.anchors = 'foo'; expect(obj.anchors).toEqual('foo'); const ForgottenAccessors2 = GObject.registerClass(class ForgottenAccessors2 extends ForgottenAccessors {}); const obj2 = new ForgottenAccessors2(); expect(obj2.anchors).toBeNull(); obj2.anchors = 'foo'; expect(obj2.anchors).toEqual('foo'); }); it('does not pollute the wrong prototype with GObject properties', function () { const MyCustomCharset = GObject.registerClass(class MyCustomCharset extends Gio.CharsetConverter { _init() { super._init(); void this.from_charset; } }); const MySecondCustomCharset = GObject.registerClass(class MySecondCustomCharset extends GObject.Object { _init() { super._init(); this.from_charset = 'another value'; } }); expect(() => new MyCustomCharset() && new MySecondCustomCharset()).not.toThrow(); }); it('resolves properties from interfaces', function () { const mon = Gio.NetworkMonitor.get_default(); expect(mon.network_available).toBeDefined(); expect(mon.networkAvailable).toBeDefined(); expect(mon['network-available']).toBeDefined(); }); it('has a toString() defintion', function () { expect(myInstance.toString()).toMatch( /\[object instance wrapper GType:Gjs_MyObject jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); expect(new Derived().toString()).toMatch( /\[object instance wrapper GType:Gjs_Derived jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('does not clobber native parent interface vfunc definitions', function () { const resetImplementationSpy = jasmine.createSpy('vfunc_reset'); expect(() => { // This is a random interface in Gio with a virtual function GObject.registerClass({ // Forgotten interface // Implements: [Gio.Converter], }, class MyZlibConverter extends Gio.ZlibCompressor { vfunc_reset() { resetImplementationSpy(); } }); }).toThrowError('Gjs_MyZlibConverter does not implement Gio.Converter, add Gio.Converter to your implements array'); let potentiallyClobbered = new Gio.ZlibCompressor(); potentiallyClobbered.reset(); expect(resetImplementationSpy).not.toHaveBeenCalled(); }); it('does not clobber dynamic parent interface vfunc definitions', function () { const resetImplementationSpy = jasmine.createSpy('vfunc_reset'); const MyJSConverter = GObject.registerClass({ Implements: [Gio.Converter], }, class MyJSConverter extends GObject.Object { vfunc_reset() { } }); expect(() => { GObject.registerClass({ // Forgotten interface // Implements: [Gio.Converter], }, class MyBadConverter extends MyJSConverter { vfunc_reset() { resetImplementationSpy(); } }); }).toThrowError('Gjs_MyBadConverter does not implement Gio.Converter, add Gio.Converter to your implements array'); let potentiallyClobbered = new MyJSConverter(); potentiallyClobbered.reset(); expect(resetImplementationSpy).not.toHaveBeenCalled(); }); }); describe('GObject class with custom constructor', function () { let myInstance; beforeEach(function () { myInstance = new MyObjectWithCustomConstructor(); }); it('throws an error when not used with a GObject-derived class', function () { class Foo {} expect(() => GObject.registerClass(class Bar extends Foo {})).toThrow(); }); it('constructs with default values for properties', function () { expect(myInstance.readwrite).toEqual('foo'); expect(myInstance.readonly).toEqual('bar'); expect(myInstance.construct).toEqual('default'); }); it('has a toString() defintion', function () { expect(myInstance.toString()).toMatch( /\[object instance wrapper GType:Gjs_MyObjectWithCustomConstructor jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('constructs with a hash of property values', function () { let myInstance2 = new MyObjectWithCustomConstructor({readwrite: 'baz', construct: 'asdf'}); expect(myInstance2.readwrite).toEqual('baz'); expect(myInstance2.readonly).toEqual('bar'); console.log(Object.getOwnPropertyDescriptor(myInstance2, 'construct')); expect(myInstance2.construct).toEqual('asdf'); }); it('accepts a property hash that is not a plain object', function () { expect(() => new MyObjectWithCustomConstructor(new GObject.Object())).not.toThrow(); }); const ui = ` baz quz `; it('constructs with property values from Gtk.Builder', function () { let builder = Gtk.Builder.new_from_string(ui, -1); let myInstance3 = builder.get_object('MyObject'); expect(myInstance3.readwrite).toEqual('baz'); expect(myInstance3.readonly).toEqual('bar'); expect(myInstance3.construct).toEqual('quz'); }); it('does not allow changing CONSTRUCT_ONLY properties', function () { myInstance.construct = 'val'; expect(myInstance.construct).toEqual('default'); }); it('has a name', function () { expect(MyObjectWithCustomConstructor.name).toEqual('MyObjectWithCustomConstructor'); }); it('has a notify signal', function () { let notifySpy = jasmine.createSpy('notifySpy'); myInstance.connect('notify::readonly', notifySpy); myInstance.notifyProp(); myInstance.notifyProp(); expect(notifySpy).toHaveBeenCalledTimes(2); }); it('can define its own signals', function () { let emptySpy = jasmine.createSpy('emptySpy'); myInstance.connect('empty', emptySpy); myInstance.emitEmpty(); expect(emptySpy).toHaveBeenCalled(); expect(myInstance.empty_called).toBeTruthy(); }); it('passes emitted arguments to signal handlers', function () { let minimalSpy = jasmine.createSpy('minimalSpy'); myInstance.connect('minimal', minimalSpy); myInstance.emitMinimal(7, 5); expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5); }); it('can return values from signals', function () { let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42); myInstance.connect('full', fullSpy); let result = myInstance.emitFull(); expect(fullSpy).toHaveBeenCalled(); expect(result).toEqual(42); }); it('does not call first-wins signal handlers after one returns a value', function () { let neverCalledSpy = jasmine.createSpy('neverCalledSpy'); myInstance.connect('full', () => 42); myInstance.connect('full', neverCalledSpy); myInstance.emitFull(); expect(neverCalledSpy).not.toHaveBeenCalled(); expect(myInstance.full_default_handler_called).toBeFalsy(); }); it('gets the return value of the default handler', function () { let result = myInstance.emitFull(); expect(myInstance.full_default_handler_called).toBeTruthy(); expect(result).toEqual(79); }); it('calls run-last default handler last', function () { let stack = []; let runLastSpy = jasmine.createSpy('runLastSpy') .and.callFake(() => { stack.push(1); }); myInstance.connect('run-last', runLastSpy); myInstance.emitRunLast(() => { stack.push(2); }); expect(stack).toEqual([1, 2]); }); it('can be a subclass', function () { let derived = new DerivedWithCustomConstructor(); expect(derived instanceof DerivedWithCustomConstructor).toBeTruthy(); expect(derived instanceof MyObjectWithCustomConstructor).toBeTruthy(); expect(derived.readwrite).toEqual('yes'); }); it('can override a property from the parent class', function () { const OverrideObjectWithCustomConstructor = GObject.registerClass({ Properties: { 'readwrite': GObject.ParamSpec.override('readwrite', MyObjectWithCustomConstructor), }, }, class OverrideObjectWithCustomConstructor extends MyObjectWithCustomConstructor { get readwrite() { return this._subclass_readwrite; } set readwrite(val) { this._subclass_readwrite = `subclass${val}`; } }); let obj = new OverrideObjectWithCustomConstructor(); obj.readwrite = 'foo'; expect(obj.readwrite).toEqual('subclassfoo'); }); }); describe('GObject virtual function', function () { it('can have its property read', function () { expect(GObject.Object.prototype.vfunc_constructed).toBeTruthy(); }); it('can have its property overridden with an anonymous function', function () { let callback; let key = 'vfunc_constructed'; class _SimpleTestClass1 extends GObject.Object {} if (GObject.Object.prototype.vfunc_constructed) { let parentFunc = GObject.Object.prototype.vfunc_constructed; _SimpleTestClass1.prototype[key] = function (...args) { parentFunc.call(this, ...args); callback('123'); }; } else { _SimpleTestClass1.prototype[key] = function () { callback('abc'); }; } callback = jasmine.createSpy('callback'); const SimpleTestClass1 = GObject.registerClass({GTypeName: 'SimpleTestClass1'}, _SimpleTestClass1); new SimpleTestClass1(); expect(callback).toHaveBeenCalledWith('123'); }); it('can access the parent prototype with super()', function () { let callback; class _SimpleTestClass2 extends GObject.Object { vfunc_constructed() { super.vfunc_constructed(); callback('vfunc_constructed'); } } callback = jasmine.createSpy('callback'); const SimpleTestClass2 = GObject.registerClass({GTypeName: 'SimpleTestClass2'}, _SimpleTestClass2); new SimpleTestClass2(); expect(callback).toHaveBeenCalledWith('vfunc_constructed'); }); it('handles non-existing properties', function () { const _SimpleTestClass3 = class extends GObject.Object {}; _SimpleTestClass3.prototype.vfunc_doesnt_exist = function () {}; if (GObject.Object.prototype.vfunc_doesnt_exist) fail('Virtual function should not exist'); expect(() => GObject.registerClass({GTypeName: 'SimpleTestClass3'}, _SimpleTestClass3)).toThrow(); }); it('gracefully bails out when overriding an unsupported vfunc type', function () { expect(() => GObject.registerClass({ Implements: [Gio.AsyncInitable], }, class Foo extends GObject.Object { vfunc_init_async() {} })).toThrow(); }); it('are defined also for static virtual functions', function () { const CustomEmptyGIcon = GObject.registerClass({ Implements: [Gio.Icon], }, class CustomEmptyGIcon extends GObject.Object {}); expect(Gio.Icon.deserialize).toBeInstanceOf(Function); expect(CustomEmptyGIcon.deserialize).toBe(Gio.Icon.deserialize); expect(Gio.Icon.new_for_string).toBeInstanceOf(Function); expect(CustomEmptyGIcon.new_for_string).toBe(Gio.Icon.new_for_string); }); }); describe('GObject creation using base classes without registered GType', function () { it('fails when trying to instantiate a class that inherits from a GObject type', function () { const BadInheritance = class extends GObject.Object {}; const BadDerivedInheritance = class extends Derived {}; expect(() => new BadInheritance()).toThrowError(/Tried to construct an object without a GType/); expect(() => new BadDerivedInheritance()).toThrowError(/Tried to construct an object without a GType/); }); it('fails when trying to register a GObject class that inherits from a non-GObject type', function () { const BadInheritance = class extends GObject.Object {}; expect(() => GObject.registerClass(class BadInheritanceDerived extends BadInheritance {})) .toThrowError(/Object 0x[a-f0-9]+ is not a subclass of GObject_Object, it's a Object/); }); }); describe('Register GType name', function () { beforeAll(function () { expect(GObject.gtypeNameBasedOnJSPath).toBeFalsy(); }); afterEach(function () { GObject.gtypeNameBasedOnJSPath = false; }); it('uses the class name', function () { const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoName extends GObject.Object { }); expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_GTypeTestAutoName'); }); it('uses the sanitized class name', function () { const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoCla$$Name extends GObject.Object { }); expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_GTypeTestAutoCla__Name'); }); it('use the file path and class name', function () { GObject.gtypeNameBasedOnJSPath = true; const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoName extends GObject.Object {}); /* Update this test if the file is moved */ expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_js_testGObjectClass_GTypeTestAutoName'); }); it('use the file path and sanitized class name', function () { GObject.gtypeNameBasedOnJSPath = true; const GTypeTestAutoName = GObject.registerClass( class GTypeTestAutoCla$$Name extends GObject.Object { }); /* Update this test if the file is moved */ expect(GTypeTestAutoName.$gtype.name).toEqual( 'Gjs_js_testGObjectClass_GTypeTestAutoCla__Name'); }); it('use provided class name', function () { const GtypeClass = GObject.registerClass({ GTypeName: 'GTypeTestManualName', }, class extends GObject.Object {}); expect(GtypeClass.$gtype.name).toEqual('GTypeTestManualName'); }); it('sanitizes user provided class name', function () { let gtypeName = 'GType$Test/WithLòt\'s of*bad§chars!'; let expectedSanitized = 'GType_Test_WithL_t_s_of_bad_chars_'; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, `*RangeError: Provided GType name '${gtypeName}' is not valid; ` + `automatically sanitized to '${expectedSanitized}'*`); const GtypeClass = GObject.registerClass({ GTypeName: gtypeName, }, class extends GObject.Object {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectRegisterClassSanitize'); expect(GtypeClass.$gtype.name).toEqual(expectedSanitized); }); }); describe('Signal handler matching', function () { let o, handleEmpty, emptyId, handleDetailed, detailedId, handleDetailedOne, detailedOneId, handleDetailedTwo, detailedTwoId, handleNotifyTwo, notifyTwoId, handleMinimalOrFull, minimalId, fullId; beforeEach(function () { o = new MyObject(); handleEmpty = jasmine.createSpy('handleEmpty'); emptyId = o.connect('empty', handleEmpty); handleDetailed = jasmine.createSpy('handleDetailed'); detailedId = o.connect('detailed', handleDetailed); handleDetailedOne = jasmine.createSpy('handleDetailedOne'); detailedOneId = o.connect('detailed::one', handleDetailedOne); handleDetailedTwo = jasmine.createSpy('handleDetailedTwo'); detailedTwoId = o.connect('detailed::two', handleDetailedTwo); handleNotifyTwo = jasmine.createSpy('handleNotifyTwo'); notifyTwoId = o.connect('notify::two', handleNotifyTwo); handleMinimalOrFull = jasmine.createSpy('handleMinimalOrFull'); minimalId = o.connect('minimal', handleMinimalOrFull); fullId = o.connect('full', handleMinimalOrFull); }); it('finds handlers by signal ID', function () { expect(GObject.signal_handler_find(o, {signalId: 'empty'})).toEqual(emptyId); // when more than one are connected, returns an arbitrary one expect([detailedId, detailedOneId, detailedTwoId]) .toContain(GObject.signal_handler_find(o, {signalId: 'detailed'})); }); it('finds handlers by signal detail', function () { expect(GObject.signal_handler_find(o, {detail: 'one'})).toEqual(detailedOneId); // when more than one are connected, returns an arbitrary one expect([detailedTwoId, notifyTwoId]) .toContain(GObject.signal_handler_find(o, {detail: 'two'})); }); it('finds handlers by callback', function () { expect(GObject.signal_handler_find(o, {func: handleEmpty})).toEqual(emptyId); expect(GObject.signal_handler_find(o, {func: handleDetailed})).toEqual(detailedId); expect(GObject.signal_handler_find(o, {func: handleDetailedOne})).toEqual(detailedOneId); expect(GObject.signal_handler_find(o, {func: handleDetailedTwo})).toEqual(detailedTwoId); expect(GObject.signal_handler_find(o, {func: handleNotifyTwo})).toEqual(notifyTwoId); // when more than one are connected, returns an arbitrary one expect([minimalId, fullId]) .toContain(GObject.signal_handler_find(o, {func: handleMinimalOrFull})); }); it('finds handlers by a combination of parameters', function () { expect(GObject.signal_handler_find(o, {signalId: 'detailed', detail: 'two'})) .toEqual(detailedTwoId); expect(GObject.signal_handler_find(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(detailedId); }); it('blocks a handler by callback', function () { expect(GObject.signal_handlers_block_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).toHaveBeenCalled(); }); it('blocks multiple handlers by callback', function () { expect(GObject.signal_handlers_block_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).toHaveBeenCalledTimes(2); }); it('blocks handlers by a combination of parameters', function () { expect(GObject.signal_handlers_block_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).not.toHaveBeenCalled(); expect(handleDetailedOne).toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).toHaveBeenCalled(); }); it('disconnects a handler by callback', function () { expect(GObject.signal_handlers_disconnect_matched(o, {func: handleEmpty})).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); }); it('blocks multiple handlers by callback', function () { expect(GObject.signal_handlers_disconnect_matched(o, {func: handleMinimalOrFull})).toEqual(2); o.emitMinimal(); o.emitFull(); expect(handleMinimalOrFull).not.toHaveBeenCalled(); }); it('blocks handlers by a combination of parameters', function () { expect(GObject.signal_handlers_disconnect_matched(o, {signalId: 'detailed', func: handleDetailed})) .toEqual(1); o.emit('detailed', ''); o.emit('detailed::one', ''); expect(handleDetailed).not.toHaveBeenCalled(); expect(handleDetailedOne).toHaveBeenCalled(); }); it('blocks a handler by callback, convenience method', function () { expect(GObject.signal_handlers_block_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); expect(GObject.signal_handlers_unblock_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).toHaveBeenCalled(); }); it('disconnects a handler by callback, convenience method', function () { expect(GObject.signal_handlers_disconnect_by_func(o, handleEmpty)).toEqual(1); o.emitEmpty(); expect(handleEmpty).not.toHaveBeenCalled(); }); it('does not support disconnecting a handler by callback data', function () { expect(() => GObject.signal_handlers_disconnect_by_data(o, null)).toThrow(); }); }); describe('Property bindings', function () { const ObjectWithProperties = GObject.registerClass({ Properties: { 'string': GObject.ParamSpec.string('string', 'String', 'String property', GObject.ParamFlags.READWRITE, ''), 'bool': GObject.ParamSpec.boolean('bool', 'Bool', 'Bool property', GObject.ParamFlags.READWRITE, true), }, }, class ObjectWithProperties extends GObject.Object {}); let a, b; beforeEach(function () { a = new ObjectWithProperties(); b = new ObjectWithProperties(); }); it('can bind properties of the same type', function () { a.bind_property('string', b, 'string', GObject.BindingFlags.NONE); a.string = 'foo'; expect(a.string).toEqual('foo'); expect(b.string).toEqual('foo'); }); it('can use custom mappings to bind properties of different types', function () { a.bind_property_full('bool', b, 'string', GObject.BindingFlags.NONE, (bind, source) => [true, `${source}`], null); a.bool = true; expect(a.bool).toEqual(true); expect(b.string).toEqual('true'); }); it('can be set up as a group', function () { if (GObject.BindingGroup === undefined) pending('GLib version too old'); const group = new GObject.BindingGroup({source: a}); group.bind('string', b, 'string', GObject.BindingFlags.NONE); a.string = 'foo'; expect(a.string).toEqual('foo'); expect(b.string).toEqual('foo'); }); it('can be set up as a group with custom mappings', function () { if (GObject.BindingGroup === undefined) pending('GLib version too old'); const group = new GObject.BindingGroup({source: a}); group.bind_full('bool', b, 'string', GObject.BindingFlags.NONE, (bind, source) => [true, `${source}`], null); a.bool = true; expect(a.bool).toEqual(true); expect(b.string).toEqual('true'); }); }); describe('Auto accessor generation', function () { const AutoAccessors = GObject.registerClass({ Properties: { 'simple': GObject.ParamSpec.int('simple', 'Simple', 'Short-named property', GObject.ParamFlags.READWRITE, 0, 100, 24), 'long-long-name': GObject.ParamSpec.int('long-long-name', 'Long long name', 'Long-named property', GObject.ParamFlags.READWRITE, 0, 100, 48), 'construct': GObject.ParamSpec.int('construct', 'Construct', 'Construct', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, 0, 100, 96), 'construct-only': GObject.ParamSpec.int('construct-only', 'Construct only', 'Construct-only property', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, 100, 80), 'construct-only-with-setter': GObject.ParamSpec.int('construct-only-with-setter', 'Construct only with setter', 'Construct-only property with a setter method', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, 100, 80), 'construct-only-was-invalid-in-turkish': GObject.ParamSpec.int( 'construct-only-was-invalid-in-turkish', 'Camel name in Turkish', 'Camel-cased property that was wrongly transformed in Turkish', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, 100, 55), 'snake-name': GObject.ParamSpec.int('snake-name', 'Snake name', 'Snake-cased property', GObject.ParamFlags.READWRITE, 0, 100, 36), 'camel-name': GObject.ParamSpec.int('camel-name', 'Camel name', 'Camel-cased property', GObject.ParamFlags.READWRITE, 0, 100, 72), 'kebab-name': GObject.ParamSpec.int('kebab-name', 'Kebab name', 'Kebab-cased property', GObject.ParamFlags.READWRITE, 0, 100, 12), 'readonly': GObject.ParamSpec.int('readonly', 'Readonly', 'Readonly property', GObject.ParamFlags.READABLE, 0, 100, 54), 'writeonly': GObject.ParamSpec.int('writeonly', 'Writeonly', 'Writeonly property', GObject.ParamFlags.WRITABLE, 0, 100, 60), 'missing-getter': GObject.ParamSpec.int('missing-getter', 'Missing getter', 'Missing a getter', GObject.ParamFlags.READWRITE, 0, 100, 18), 'missing-setter': GObject.ParamSpec.int('missing-setter', 'Missing setter', 'Missing a setter', GObject.ParamFlags.READWRITE, 0, 100, 42), }, }, class AutoAccessors extends GObject.Object { _init(props = {}) { this._constructOnlySetterCalled = 0; super._init(props); this._snakeNameGetterCalled = 0; this._snakeNameSetterCalled = 0; this._camelNameGetterCalled = 0; this._camelNameSetterCalled = 0; this._kebabNameGetterCalled = 0; this._kebabNameSetterCalled = 0; } get snake_name() { this._snakeNameGetterCalled++; return 42; } set snake_name(value) { this._snakeNameSetterCalled++; } get camelName() { this._camelNameGetterCalled++; return 42; } set camelName(value) { this._camelNameSetterCalled++; } get ['kebab-name']() { this._kebabNameGetterCalled++; return 42; } set ['kebab-name'](value) { this._kebabNameSetterCalled++; } set missing_getter(value) { this._missingGetter = value; } get missing_setter() { return 42; } get construct_only_with_setter() { return this._constructOnlyValue; } set constructOnlyWithSetter(value) { this._constructOnlySetterCalled++; this._constructOnlyValue = value; } }); let a; beforeEach(function () { a = new AutoAccessors(); }); it('get and set the property', function () { a.simple = 1; expect(a.simple).toEqual(1); a['long-long-name'] = 1; expect(a['long-long-name']).toEqual(1); a.construct = 1; expect(a.construct).toEqual(1); }); it("initial value is the param spec's default value", function () { expect(a.simple).toEqual(24); expect(a.long_long_name).toEqual(48); expect(a.longLongName).toEqual(48); expect(a['long-long-name']).toEqual(48); expect(a.construct).toEqual(96); expect(a.construct_only).toEqual(80); expect(a.constructOnly).toEqual(80); expect(a['construct-only']).toEqual(80); }); it('set properties at construct time', function () { a = new AutoAccessors({ simple: 1, longLongName: 1, construct: 1, 'construct-only': 1, 'construct-only-with-setter': 2, }); expect(a.simple).toEqual(1); expect(a.long_long_name).toEqual(1); expect(a.longLongName).toEqual(1); expect(a['long-long-name']).toEqual(1); expect(a.construct).toEqual(1); expect(a.construct_only).toEqual(1); expect(a.constructOnly).toEqual(1); expect(a['construct-only']).toEqual(1); expect(a.constructOnlyWithSetter).toEqual(2); expect(a.construct_only_with_setter).toEqual(2); expect(a['construct-only-with-setter']).toEqual(2); expect(a._constructOnlySetterCalled).toEqual(1); }); it('set properties at construct time with locale', function () { const {gettext: Gettext} = imports; const prevLocale = Gettext.setlocale(Gettext.LocaleCategory.ALL, null); Gettext.setlocale(Gettext.LocaleCategory.ALL, 'tr_TR'); a = new AutoAccessors({ 'construct-only-was-invalid-in-turkish': 35, }); Gettext.setlocale(Gettext.LocaleCategory.ALL, prevLocale); expect(a.constructOnlyWasInvalidInTurkish).toEqual(35); expect(a.construct_only_was_invalid_in_turkish).toEqual(35); expect(a['construct-only-was-invalid-in-turkish']).toEqual(35); }); it('notify when the property changes', function () { const notify = jasmine.createSpy('notify'); a.connect('notify::simple', notify); a.simple = 1; expect(notify).toHaveBeenCalledTimes(1); notify.calls.reset(); a.simple = 1; expect(notify).not.toHaveBeenCalled(); }); it('copies accessors for camel and kebab if snake accessors given', function () { a.snakeName = 42; expect(a.snakeName).toEqual(42); a['snake-name'] = 42; expect(a['snake-name']).toEqual(42); expect(a._snakeNameGetterCalled).toEqual(2); expect(a._snakeNameSetterCalled).toEqual(2); }); it('copies accessors for snake and kebab if camel accessors given', function () { a.camel_name = 42; expect(a.camel_name).toEqual(42); a['camel-name'] = 42; expect(a['camel-name']).toEqual(42); expect(a._camelNameGetterCalled).toEqual(2); expect(a._camelNameSetterCalled).toEqual(2); }); it('copies accessors for snake and camel if kebab accessors given', function () { a.kebabName = 42; expect(a.kebabName).toEqual(42); a.kebab_name = 42; expect(a.kebab_name).toEqual(42); expect(a._kebabNameGetterCalled).toEqual(2); expect(a._kebabNameSetterCalled).toEqual(2); }); it('readonly getter throws', function () { expect(() => a.readonly).toThrowError(/getter/); }); it('writeonly setter throws', function () { expect(() => (a.writeonly = 1)).toThrowError(/setter/); }); it('getter throws when setter defined', function () { expect(() => a.missingGetter).toThrowError(/getter/); }); it('setter throws when getter defined', function () { expect(() => (a.missingSetter = 1)).toThrowError(/setter/); }); }); const MyObjectWithJSObjectProperty = GObject.registerClass({ Properties: { 'jsobj-prop': GObject.ParamSpec.jsobject('jsobj-prop', 'jsobj-prop', 'jsobj-prop', GObject.ParamFlags.CONSTRUCT | GObject.ParamFlags.READWRITE), }, }, class MyObjectWithJSObjectProperty extends GObject.Object { }); describe('GObject class with JSObject property', function () { it('assigns a valid JSObject on construct', function () { let date = new Date(); let obj = new MyObjectWithJSObjectProperty({jsobj_prop: date}); expect(obj.jsobj_prop).toEqual(date); expect(obj.jsobj_prop).not.toEqual(new Date(0)); expect(() => obj.jsobj_prop.setFullYear(1985)).not.toThrow(); expect(obj.jsobj_prop.getFullYear()).toEqual(1985); }); it('Set null with an empty JSObject on construct', function () { expect(new MyObjectWithJSObjectProperty().jsobj_prop).toBeNull(); expect(new MyObjectWithJSObjectProperty({}).jsobj_prop).toBeNull(); }); it('assigns a null JSObject on construct', function () { expect(new MyObjectWithJSObjectProperty({jsobj_prop: null}).jsobj_prop) .toBeNull(); }); it('assigns a JSObject Array on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: [1, 2, 3]})) .not.toThrow(); }); it('assigns a Function on construct', function () { expect(() => new MyObjectWithJSObjectProperty({ jsobj_prop: () => true, })).not.toThrow(); }); it('throws an error when using a boolean value on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: true})) .toThrowError(/JSObject expected/); }); it('throws an error when using an int value on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: 1})) .toThrowError(/JSObject expected/); }); it('throws an error when using a numeric value on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: Math.PI})) .toThrowError(/JSObject expected/); }); it('throws an error when using a string value on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: 'string'})) .toThrowError(/JSObject expected/); }); it('throws an error when using an undefined value on construct', function () { expect(() => new MyObjectWithJSObjectProperty({jsobj_prop: undefined})).toThrow(); }); it('property value survives when GObject wrapper is collected', function () { const MyConverter = GObject.registerClass({ Properties: { testprop: GObject.ParamSpec.jsobject('testprop', 'testprop', 'Test property', GObject.ParamFlags.CONSTRUCT | GObject.ParamFlags.READWRITE), }, Implements: [Gio.Converter], }, class MyConverter extends GObject.Object {}); function stashObject() { const base = new Gio.MemoryInputStream(); const converter = new MyConverter({testprop: [1, 2, 3]}); return Gio.ConverterInputStream.new(base, converter); } const stream = stashObject(); System.gc(); expect(stream.get_converter().testprop).toEqual([1, 2, 3]); }); }); const MyObjectWithJSObjectSignals = GObject.registerClass({ Signals: { 'send-object': {param_types: [GObject.TYPE_JSOBJECT]}, 'send-many-objects': { param_types: [GObject.TYPE_JSOBJECT, GObject.TYPE_JSOBJECT, GObject.TYPE_JSOBJECT], }, 'get-object': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_JSOBJECT, param_types: [GObject.TYPE_JSOBJECT], }, }, }, class MyObjectWithJSObjectSignals extends GObject.Object { emitObject(obj) { this.emit('send-object', obj); } }); describe('GObject class with JSObject signals', function () { let myInstance; beforeEach(function () { myInstance = new MyObjectWithJSObjectSignals(); }); it('emits signal with null JSObject parameter', function () { let customSpy = jasmine.createSpy('sendObjectSpy'); myInstance.connect('send-object', customSpy); myInstance.emitObject(null); expect(customSpy).toHaveBeenCalledWith(myInstance, null); }); it('emits signal with JSObject parameter', function () { let customSpy = jasmine.createSpy('sendObjectSpy'); myInstance.connect('send-object', customSpy); let obj = { foo: [1, 2, 3], sub: {a: {}, 'b': this}, desc: 'test', date: new Date(), }; myInstance.emitObject(obj); expect(customSpy).toHaveBeenCalledWith(myInstance, obj); }); it('emits signal with multiple JSObject parameters', function () { let customSpy = jasmine.createSpy('sendManyObjectsSpy'); myInstance.connect('send-many-objects', customSpy); let obj = { foo: [9, 8, 7, 'a', 'b', 'c'], sub: {a: {}, 'b': this}, desc: 'test', date: new RegExp('\\w+'), }; myInstance.emit('send-many-objects', obj, obj.foo, obj.sub); expect(customSpy).toHaveBeenCalledWith(myInstance, obj, obj.foo, obj.sub); }); it('re-emits signal with same JSObject parameter', function () { let obj = { foo: [9, 8, 7, 'a', 'b', 'c'], sub: {a: {}, 'b': this}, func: arg => { return {ret: [arg]}; }, }; myInstance.connect('send-many-objects', (instance, func, args, foo) => { expect(instance).toEqual(myInstance); expect(System.addressOf(instance)).toEqual(System.addressOf(myInstance)); expect(foo).toEqual(obj.foo); expect(System.addressOf(foo)).toEqual(System.addressOf(obj.foo)); expect(func(args).ret[0]).toEqual(args); }); myInstance.connect('send-object', (instance, param) => { expect(instance).toEqual(myInstance); expect(System.addressOf(instance)).toEqual(System.addressOf(myInstance)); expect(param).toEqual(obj); expect(System.addressOf(param)).toEqual(System.addressOf(obj)); expect(() => instance.emit('send-many-objects', param.func, param, param.foo)) .not.toThrow(); }); myInstance.emit('send-object', obj); }); it('throws an error when using a boolean value as parameter', function () { expect(() => myInstance.emit('send-object', true)) .toThrowError(/JSObject expected/); expect(() => myInstance.emit('send-many-objects', ['a'], true, {})) .toThrowError(/JSObject expected/); }); it('throws an error when using an int value as parameter', function () { expect(() => myInstance.emit('send-object', 1)) .toThrowError(/JSObject expected/); expect(() => myInstance.emit('send-many-objects', ['a'], 1, {})) .toThrowError(/JSObject expected/); }); it('throws an error when using a numeric value as parameter', function () { expect(() => myInstance.emit('send-object', Math.PI)) .toThrowError(/JSObject expected/); expect(() => myInstance.emit('send-many-objects', ['a'], Math.PI, {})) .toThrowError(/JSObject expected/); }); it('throws an error when using a string value as parameter', function () { expect(() => myInstance.emit('send-object', 'string')) .toThrowError(/JSObject expected/); expect(() => myInstance.emit('send-many-objects', ['a'], 'string', {})) .toThrowError(/JSObject expected/); }); it('throws an error when using an undefined value as parameter', function () { expect(() => myInstance.emit('send-object', undefined)) .toThrowError(/JSObject expected/); expect(() => myInstance.emit('send-many-objects', ['a'], undefined, {})) .toThrowError(/JSObject expected/); }); it('returns a JSObject', function () { let data = { foo: [9, 8, 7, 'a', 'b', 'c'], sub: {a: {}, 'b': this}, func: arg => { return {ret: [arg]}; }, }; let id = myInstance.connect('get-object', () => { return data; }); expect(myInstance.emit('get-object', {})).toBe(data); myInstance.disconnect(id); myInstance.connect('get-object', (instance, input) => { if (input) { if (typeof input === 'function') input(); return input; } class SubObject { constructor() { this.pi = Math.PI; } method() {} gobject() { return GObject.Object; } get data() { return data; } } return new SubObject(); }); expect(myInstance.emit('get-object', null).constructor.name).toBe('SubObject'); expect(myInstance.emit('get-object', null).data).toBe(data); expect(myInstance.emit('get-object', null).pi).toBe(Math.PI); expect(() => myInstance.emit('get-object', null).method()).not.toThrow(); expect(myInstance.emit('get-object', null).gobject()).toBe(GObject.Object); expect(new (myInstance.emit('get-object', null).gobject())() instanceof GObject.Object) .toBeTruthy(); expect(myInstance.emit('get-object', data)).toBe(data); expect(myInstance.emit('get-object', jasmine.createSpy('callMeSpy'))) .toHaveBeenCalled(); }); it('returns null when returning undefined', function () { myInstance.connect('get-object', () => { return undefined; }); expect(myInstance.emit('get-object', {})).toBeNull(); }); it('returns null when not returning', function () { myInstance.connect('get-object', () => { }); expect(myInstance.emit('get-object', {})).toBeNull(); }); // These tests are intended to throw an error, but currently errors cannot // be caught from signal handlers, so we check for logged messages instead it('throws an error when returning a boolean value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => true); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning an int value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 1); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning a numeric value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => Math.PI); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); it('throws an error when returning a string value', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 'string'); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'throws an error when returning a boolean value'); }); }); describe('GObject class with int64 properties', function () { const MyInt64Class = GObject.registerClass(class MyInt64Class extends GObject.Object { static [GObject.properties] = { 'int64': GObject.ParamSpec.int64('int64', 'int64', 'int64', GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, // GLib.MAXINT64 exceeds JS' ability to safely represent an integer GLib.MININT32 * 2, GLib.MAXINT32 * 2, 0), }; }); it('can set an int64 property', function () { const instance = new MyInt64Class({ int64: GLib.MAXINT32, }); expect(instance.int64).toBe(GLib.MAXINT32); instance.int64 = GLib.MAXINT32 + 1; expect(instance.int64).toBe(GLib.MAXINT32 + 1); }); it('can construct with int64 property', function () { const instance = new MyInt64Class({ int64: GLib.MAXINT32 + 1, }); expect(instance.int64).toBe(GLib.MAXINT32 + 1); }); }); cjs-128.1/installed-tests/js/testGObjectDestructionAccess.js0000664000175000017500000007055415116312211023110 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Canonical, Ltd. imports.gi.versions.Gtk = '3.0'; const {GLib, Gio, CjsTestTools, GObject, Gtk} = imports.gi; const {system: System} = imports; describe('Access to destroyed GObject', function () { let destroyedWindow; beforeAll(function () { Gtk.init(null); }); beforeEach(function () { destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); destroyedWindow.set_title('To be destroyed'); destroyedWindow.destroy(); }); it('Get property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.title).toBe('To be destroyed'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.title = 'I am dead'; expect(destroyedWindow.title).toBe('I am dead'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.expandoProperty = 'Hello!'; GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectExpandoPropertySet'); }); it('Access to unset expando property', function () { expect(destroyedWindow.expandoProperty).toBeUndefined(); }); it('Access previously set expando property', function () { destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); destroyedWindow.expandoProperty = 'Hello!'; destroyedWindow.destroy(); expect(destroyedWindow.expandoProperty).toBe('Hello!'); }); it('Access to getter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.get_title()).toBe('To be destroyed'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.set_title('I am dead'); expect(destroyedWindow.get_title()).toBe('I am dead'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect('foo-signal', () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect_after('foo-signal', () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_disconnect_by_func(destroyedWindow, () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_block_by_func(destroyedWindow, () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(GObject.signal_handlers_unblock_by_func(destroyedWindow, () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsUnblock'); }); it('Proto function toString', function () { expect(destroyedWindow.toString()).toMatch( /\[object \(DISPOSED\) instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('Proto function toString before/after', function () { var validWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); expect(validWindow.toString()).toMatch( /\[object instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); validWindow.destroy(); expect(validWindow.toString()).toMatch( /\[object \(DISPOSED\) instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); }); describe('Access to finalized GObject', function () { let destroyedWindow; beforeAll(function () { Gtk.init(null); }); beforeEach(function () { destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); destroyedWindow.set_title('To be destroyed'); destroyedWindow.destroy(); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); CjsTestTools.unref(destroyedWindow); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); afterEach(function () { destroyedWindow = null; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection'); }); it('Get property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.title).toBeUndefined(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.title = 'I am dead'; expect(destroyedWindow.title).toBeUndefined(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.expandoProperty = 'Hello!'; GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectExpandoPropertySet'); }); it('Access to unset expando property', function () { expect(destroyedWindow.expandoProperty).toBeUndefined(); }); it('Access previously set expando property', function () { destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); destroyedWindow.expandoProperty = 'Hello!'; destroyedWindow.destroy(); expect(destroyedWindow.expandoProperty).toBe('Hello!'); }); it('Access to getter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); expect(destroyedWindow.get_title()).toBeNull(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Gtk', GLib.LogLevelFlags.LEVEL_CRITICAL, '*GTK_IS_WINDOW*'); destroyedWindow.set_title('I am dead'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect('foo-signal', () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect_after('foo-signal', () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_disconnect_by_func(destroyedWindow, () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_block_by_func(destroyedWindow, () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(GObject.signal_handlers_unblock_by_func(destroyedWindow, () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsUnblock'); }); it('Proto function toString', function () { expect(destroyedWindow.toString()).toMatch( /\[object \(FINALIZED\) instance wrapper GIName:Gtk.Window jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); }); describe('Disposed or finalized GObject', function () { beforeAll(function () { CjsTestTools.init(); }); afterEach(function () { CjsTestTools.reset(); }); [true, false].forEach(gc => { it(`is marked as disposed when it is a manually disposed property ${gc ? '' : 'not '}garbage collected`, function () { const emblem = new Gio.EmblemedIcon({ gicon: new Gio.ThemedIcon({name: 'alarm'}), }); let {gicon} = emblem; gicon.run_dispose(); gicon = null; System.gc(); Array(10).fill().forEach(() => { // We need to repeat the test to ensure that we disassociate // wrappers from disposed objects on destruction. gicon = emblem.gicon; expect(gicon.toString()).toMatch( /\[object \(DISPOSED\) instance wrapper .* jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); gicon = null; if (gc) System.gc(); }); }); }); it('calls dispose vfunc on explicit disposal only', function () { const callSpy = jasmine.createSpy('vfunc_dispose'); const DisposeFile = GObject.registerClass(class DisposeFile extends Gio.ThemedIcon { vfunc_dispose(...args) { expect(this.names).toEqual(['dummy']); callSpy(...args); } }); let file = new DisposeFile({name: 'dummy'}); file.run_dispose(); expect(callSpy).toHaveBeenCalledOnceWith(); file.run_dispose(); expect(callSpy).toHaveBeenCalledTimes(2); file = null; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was dispose()*'); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'calls dispose vfunc on explicit disposal only'); expect(callSpy).toHaveBeenCalledTimes(2); }); it('generates a warn on object garbage collection', function () { CjsTestTools.unref(Gio.File.new_for_path('/')); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection'); }); it('generates a warn on object garbage collection if has expando property', function () { let file = Gio.File.new_for_path('/'); file.toggleReferenced = true; CjsTestTools.unref(file); expect(file.toString()).toMatch( /\[object \(FINALIZED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); file = null; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection if has expando property'); }); it('generates a warn if already disposed at garbage collection', function () { const loop = new GLib.MainLoop(null, false); let file = Gio.File.new_for_path('/'); CjsTestTools.delayed_unref(file, 1); // Will happen after dispose file.run_dispose(); let done = false; GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => (done = true)); while (!done) loop.get_context().iteration(true); file = null; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn if already disposed at garbage collection'); }); [true, false].forEach(gc => { it(`created from other function is marked as disposed and ${gc ? '' : 'not '}garbage collected`, function () { let file = Gio.File.new_for_path('/'); CjsTestTools.save_object(file); file.run_dispose(); file = null; System.gc(); Array(10).fill().forEach(() => { // We need to repeat the test to ensure that we disassociate // wrappers from disposed objects on destruction. expect(CjsTestTools.peek_saved()).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); if (gc) System.gc(); }); }); }); it('returned from function is marked as disposed', function () { expect(CjsTestTools.get_disposed(Gio.File.new_for_path('/'))).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('returned from function is marked as disposed and then as finalized', function () { let file = Gio.File.new_for_path('/'); CjsTestTools.save_object(file); CjsTestTools.delayed_unref(file, 30); file.run_dispose(); let disposedFile = CjsTestTools.get_saved(); expect(disposedFile).toEqual(file); expect(disposedFile).toMatch( /\[object \(DISPOSED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); file = null; System.gc(); const loop = new GLib.MainLoop(null, false); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => loop.quit()); loop.run(); expect(disposedFile).toMatch( /\[object \(FINALIZED\) instance wrapper GType:GLocalFile jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); disposedFile = null; System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'returned from function is marked as disposed and then as finalized'); }); it('ignores toggling queued unref toggles', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.ref(file); CjsTestTools.unref_other_thread(file); file.run_dispose(); }); it('ignores toggling queued toggles', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.ref_other_thread(file); CjsTestTools.unref_other_thread(file); file.run_dispose(); }); it('can be disposed from other thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.ref(file); CjsTestTools.unref_other_thread(file); CjsTestTools.run_dispose_other_thread(file); }); it('can be garbage collected once disposed from other thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.run_dispose_other_thread(file); file = null; System.gc(); }); }); describe('GObject with toggle references', function () { beforeAll(function () { CjsTestTools.init(); }); afterEach(function () { CjsTestTools.reset(); }); it('can be re-reffed from other thread delayed', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; const objectAddress = System.addressOfGObject(file); CjsTestTools.save_object_unreffed(file); CjsTestTools.delayed_ref_other_thread(file, 10); file = null; System.gc(); const loop = new GLib.MainLoop(null, false); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => loop.quit()); loop.run(); // We need to cleanup the extra ref we added before now. // However, depending on whether the thread ref happens the object // may be already finalized, and in such case we need to throw try { file = CjsTestTools.steal_saved(); if (file) { expect(System.addressOfGObject(file)).toBe(objectAddress); expect(file instanceof Gio.File).toBeTruthy(); CjsTestTools.unref(file); } } catch (e) { expect(() => { throw e; }).toThrowError(/.*Unhandled GType.*/); } }); it('can be re-reffed and unreffed again from other thread', function () { let file = Gio.File.new_for_path('/'); const objectAddress = System.addressOfGObject(file); file.expandMeWithToggleRef = true; CjsTestTools.save_object(file); CjsTestTools.ref(file); CjsTestTools.delayed_unref_other_thread(file, 10); file = null; System.gc(); const loop = new GLib.MainLoop(null, false); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => loop.quit()); loop.run(); file = CjsTestTools.get_saved(); expect(System.addressOfGObject(file)).toBe(objectAddress); expect(file instanceof Gio.File).toBeTruthy(); }); it('can be re-reffed and unreffed again from other thread with delay', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.delayed_ref_unref_other_thread(file, 10); file = null; System.gc(); const loop = new GLib.MainLoop(null, false); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => loop.quit()); loop.run(); }); it('can be toggled up by getting a GWeakRef', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.save_weak(file); CjsTestTools.get_weak(); }); it('can be toggled up by getting a GWeakRef from another thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.save_weak(file); CjsTestTools.get_weak_other_thread(); }); it('can be toggled up by getting a GWeakRef from another thread and re-reffed in main thread', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.save_weak(file); CjsTestTools.get_weak_other_thread(); // Ok, let's play more dirty now... CjsTestTools.ref(file); // toggle up CjsTestTools.unref(file); // toggle down CjsTestTools.ref(file); CjsTestTools.ref(file); CjsTestTools.unref(file); CjsTestTools.unref(file); }); it('can be toggled up by getting a GWeakRef from another and re-reffed from various threads', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.save_weak(file); CjsTestTools.get_weak_other_thread(); CjsTestTools.ref_other_thread(file); CjsTestTools.unref_other_thread(file); CjsTestTools.ref(file); CjsTestTools.unref(file); CjsTestTools.ref_other_thread(file); CjsTestTools.unref(file); CjsTestTools.ref(file); CjsTestTools.unref_other_thread(file); }); it('can be toggled up-down from various threads when the wrapper is gone', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; // We also check that late thread events won't affect the destroyed wrapper const threads = []; threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 0)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 100000)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 200000)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 300000)); CjsTestTools.save_object(file); CjsTestTools.save_weak(file); file = null; System.gc(); threads.forEach(th => th.join()); CjsTestTools.clear_saved(); System.gc(); expect(CjsTestTools.get_weak()).toBeNull(); }); it('can be toggled up-down from various threads when disposed and the wrapper is gone', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; // We also check that late thread events won't affect the destroyed wrapper const threads = []; threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 0)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 100000)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 200000)); threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 300000)); CjsTestTools.save_object(file); CjsTestTools.save_weak(file); file.run_dispose(); file = null; System.gc(); threads.forEach(th => th.join()); CjsTestTools.clear_saved(); expect(CjsTestTools.get_weak()).toBeNull(); }); it('can be finalized while queued in toggle queue', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.ref(file); CjsTestTools.unref_other_thread(file); CjsTestTools.unref_other_thread(file); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); file = null; System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectDestructionAccess.js', 0, 'can be finalized while queued in toggle queue'); }); xit('can be toggled up-down from various threads while getting a GWeakRef from main', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; CjsTestTools.save_weak(file); const ids = []; let threads = []; ids.push(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { threads = threads.slice(-50); try { threads.push(CjsTestTools.delayed_ref_unref_other_thread(file, 1)); } catch (e) { // If creating the thread failed we're almost going out of memory // so let's first wait for the ones allocated to complete. threads.forEach(th => th.join()); threads = []; } return GLib.SOURCE_CONTINUE; })); const loop = new GLib.MainLoop(null, false); ids.push(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { expect(CjsTestTools.get_weak()).toEqual(file); return GLib.SOURCE_CONTINUE; })); // We must not timeout due to deadlock #404 and finally not crash per #297 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, () => loop.quit()); loop.run(); ids.forEach(id => GLib.source_remove(id)); // We also check that late thread events won't affect the destroyed wrapper CjsTestTools.save_object(file); file = null; System.gc(); threads.forEach(th => th.join()); expect(CjsTestTools.get_saved_ref_count()).toBeGreaterThan(0); CjsTestTools.clear_saved(); System.gc(); expect(CjsTestTools.get_weak()).toBeNull(); }).pend('Flaky, see https://gitlab.gnome.org/GNOME/gjs/-/issues/NNN'); }); cjs-128.1/installed-tests/js/testGObjectInterface.js0000664000175000017500000004133015116312211021351 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const AGObjectInterface = GObject.registerClass({ GTypeName: 'ArbitraryGTypeName', Requires: [GObject.Object], Properties: { 'interface-prop': GObject.ParamSpec.string('interface-prop', 'Interface property', 'Must be overridden in implementation', GObject.ParamFlags.READABLE, 'foobar'), }, Signals: { 'interface-signal': {}, }, }, class AGObjectInterface extends GObject.Interface { requiredG() { throw new GObject.NotImplementedError(); } optionalG() { return 'AGObjectInterface.optionalG()'; } }); const InterfaceRequiringGObjectInterface = GObject.registerClass({ Requires: [AGObjectInterface], }, class InterfaceRequiringGObjectInterface extends GObject.Interface { optionalG() { return `InterfaceRequiringGObjectInterface.optionalG()\n${ AGObjectInterface.optionalG(this)}`; } }); const GObjectImplementingGObjectInterface = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), 'class-prop': GObject.ParamSpec.string('class-prop', 'Class property', 'A property that is not on the interface', GObject.ParamFlags.READABLE, 'meh'), }, Signals: { 'class-signal': {}, }, }, class GObjectImplementingGObjectInterface extends GObject.Object { get interface_prop() { return 'foobar'; } get class_prop() { return 'meh'; } requiredG() {} optionalG() { return AGObjectInterface.optionalG(this); } }); const MinimalImplementationOfAGObjectInterface = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class MinimalImplementationOfAGObjectInterface extends GObject.Object { requiredG() {} }); const ImplementationOfTwoInterfaces = GObject.registerClass({ Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class ImplementationOfTwoInterfaces extends GObject.Object { requiredG() {} optionalG() { return InterfaceRequiringGObjectInterface.optionalG(this); } }); const ImplementationOfIntrospectedInterface = GObject.registerClass({ Implements: [Gio.Action], Properties: { 'enabled': GObject.ParamSpec.override('enabled', Gio.Action), 'name': GObject.ParamSpec.override('name', Gio.Action), 'state': GObject.ParamSpec.override('state', Gio.Action), 'state-type': GObject.ParamSpec.override('state-type', Gio.Action), 'parameter-type': GObject.ParamSpec.override('parameter-type', Gio.Action), }, }, class ImplementationOfIntrospectedInterface extends GObject.Object { get name() { return 'inaction'; } }); describe('GObject interface', function () { it('cannot be instantiated', function () { expect(() => new AGObjectInterface()).toThrow(); }); it('has a name', function () { expect(AGObjectInterface.name).toEqual('AGObjectInterface'); }); it('reports its type name', function () { expect(AGObjectInterface.$gtype.name).toEqual('ArbitraryGTypeName'); }); it('can be implemented by a GObject class', function () { let obj; expect(() => { obj = new GObjectImplementingGObjectInterface(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); }); it('is implemented by a GObject class with the correct class object', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.constructor).toBe(GObjectImplementingGObjectInterface); expect(obj.constructor.name) .toEqual('GObjectImplementingGObjectInterface'); }); it('can have its required function implemented', function () { expect(() => { let obj = new GObjectImplementingGObjectInterface(); obj.requiredG(); }).not.toThrow(); }); it('must have its required function implemented', function () { const BadObject = GObject.registerClass({ Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class BadObject extends GObject.Object {}); expect(() => new BadObject().requiredG()) .toThrowError(GObject.NotImplementedError); }); it("doesn't have to have its optional function implemented", function () { let obj; expect(() => { obj = new MinimalImplementationOfAGObjectInterface(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); }); it('can have its optional function deferred to by the implementation', function () { let obj = new MinimalImplementationOfAGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can have its function chained up to', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj instanceof InterfaceRequiringGObjectInterface).toBeTruthy(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it("defers to the last interface's optional function", function () { const MinimalImplementationOfTwoInterfaces = GObject.registerClass({ Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, }, class MinimalImplementationOfTwoInterfaces extends GObject.Object { requiredG() {} }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it('must be implemented by a class that implements all required interfaces', function () { expect(() => GObject.registerClass({ Implements: [InterfaceRequiringGObjectInterface], }, class BadObject { required() {} })).toThrow(); }); it('must be implemented by a class that implements required interfaces in correct order', function () { expect(() => GObject.registerClass({ Implements: [InterfaceRequiringGObjectInterface, AGObjectInterface], }, class BadObject { required() {} })).toThrow(); }); it('can require an interface from C', function () { const InitableInterface = GObject.registerClass({ Requires: [GObject.Object, Gio.Initable], }, class InitableInterface extends GObject.Interface {}); expect(() => GObject.registerClass({ Implements: [InitableInterface], }, class BadObject {})).toThrow(); }); it('can connect class signals on the implementing class', function (done) { function quitLoop() { expect(classSignalSpy).toHaveBeenCalled(); done(); } let obj = new GObjectImplementingGObjectInterface(); let classSignalSpy = jasmine.createSpy('classSignalSpy') .and.callFake(quitLoop); obj.connect('class-signal', classSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('class-signal'); return GLib.SOURCE_REMOVE; }); }); it('can connect interface signals on the implementing class', function (done) { function quitLoop() { expect(interfaceSignalSpy).toHaveBeenCalled(); done(); } let obj = new GObjectImplementingGObjectInterface(); let interfaceSignalSpy = jasmine.createSpy('interfaceSignalSpy') .and.callFake(quitLoop); obj.connect('interface-signal', interfaceSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('interface-signal'); return GLib.SOURCE_REMOVE; }); }); it('can define properties on the implementing class', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.interface_prop).toEqual('foobar'); expect(obj.class_prop).toEqual('meh'); }); it('must have its properties overridden', function () { // Failing to override an interface property doesn't raise an error but // instead logs a critical warning. GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "Object class * doesn't implement property 'interface-prop' from " + "interface 'ArbitraryGTypeName'"); GObject.registerClass({ Implements: [AGObjectInterface], }, class MyNaughtyObject extends GObject.Object { requiredG() {} }); // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectInterface.js', 253, 'testGObjectMustOverrideInterfaceProperties'); }); it('can have introspected properties overriden', function () { let obj = new ImplementationOfIntrospectedInterface(); expect(obj.name).toEqual('inaction'); }); it('can be implemented by a class as well as its parent class', function () { const SubObject = GObject.registerClass( class SubObject extends GObjectImplementingGObjectInterface {}); let obj = new SubObject(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('can be reimplemented by a subclass of a class that already implements it', function () { const SubImplementer = GObject.registerClass({ Implements: [AGObjectInterface], }, class SubImplementer extends GObjectImplementingGObjectInterface {}); let obj = new SubImplementer(); expect(obj instanceof AGObjectInterface).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('has a toString() defintion', function () { expect(new GObjectImplementingGObjectInterface().toString()).toMatch( /\[object instance wrapper GType:Gjs_GObjectImplementingGObjectInterface jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/); }); it('has instance definition', function () { const obj = new GObjectImplementingGObjectInterface(); const obj2 = new ImplementationOfTwoInterfaces(); const file = Gio.File.new_for_path('/'); expect(obj).toBeInstanceOf(AGObjectInterface); expect(obj).not.toBeInstanceOf(InterfaceRequiringGObjectInterface); expect(obj2).toBeInstanceOf(AGObjectInterface); expect(obj2).toBeInstanceOf(InterfaceRequiringGObjectInterface); expect(new GObject.Object()).not.toBeInstanceOf(AGObjectInterface); expect(file).toBeInstanceOf(Gio.File); expect(file).toBeInstanceOf(GObject.Object); }); it('has instance definition for non-object type', function () { expect(null).not.toBeInstanceOf(AGObjectInterface); expect(true).not.toBeInstanceOf(AGObjectInterface); expect(undefined).not.toBeInstanceOf(AGObjectInterface); expect(123456).not.toBeInstanceOf(AGObjectInterface); expect(54321n).not.toBeInstanceOf(AGObjectInterface); expect('no way!').not.toBeInstanceOf(AGObjectInterface); expect(new Date()).not.toBeInstanceOf(AGObjectInterface); }); it('has instance definition for non-object type for native interface', function () { expect(null).not.toBeInstanceOf(Gio.File); expect(true).not.toBeInstanceOf(Gio.File); expect(undefined).not.toBeInstanceOf(Gio.File); expect(12345).not.toBeInstanceOf(Gio.File); expect(54321n).not.toBeInstanceOf(Gio.File); expect('no way!').not.toBeInstanceOf(Gio.File); expect(new Date()).not.toBeInstanceOf(Gio.File); }); describe('prototype', function () { let file, originalDup; beforeAll(function () { file = Gio.File.new_for_path('/'); originalDup = Gio.File.prototype.dup; }); it('toString is enumerable and defined', function () { expect(Object.getOwnPropertyNames(Gio.File.prototype)).toContain('toString'); expect(Gio.File.prototype.toString).toBeDefined(); }); it('method properties are enumerated', function () { const expectedMethods = [ 'copy_attributes', 'copy_async', 'create_async', 'create_readwrite_async', 'delete_async', 'enumerate_children', ]; const methods = Object.getOwnPropertyNames(Gio.File.prototype); expect(methods).toEqual(jasmine.arrayContaining(expectedMethods)); }); it('method properties are defined', function () { const methods = Object.getOwnPropertyNames(Gio.File.prototype); for (const method of methods) { expect(Gio.File.prototype[method]).toBeDefined(); expect(Gio.File.prototype[method]).toBeInstanceOf(Function); } }); it('overrides are inherited by implementing classes', function () { spyOn(Gio.File.prototype, 'dup'); expect(file).toBeInstanceOf(Gio.File); expect(file).toBeInstanceOf(Gio._LocalFilePrototype.constructor); file.dup(); expect(Gio.File.prototype.dup).toHaveBeenCalledOnceWith(); Gio.File.prototype.dup = originalDup; expect(file.dup).toBe(originalDup); }); it('overrides cannot be changed by instances of child classes', function () { spyOn(Gio.File.prototype, 'dup'); expect(file).toBeInstanceOf(Gio.File); expect(file).toBeInstanceOf(Gio._LocalFilePrototype.constructor); file.dup = 5; expect(Gio.File.prototype.dup).not.toBe(5); expect(Gio._LocalFilePrototype.dup).not.toBe(5); file.dup = originalDup; expect(file.dup).toBe(originalDup); }); it('unknown properties are inherited by implementing classes', function () { Gio.File.prototype._originalDup = originalDup; expect(file._originalDup).toBe(originalDup); Gio.File.prototype._originalDup = 5; expect(file._originalDup).toBe(5); delete Gio.File.prototype._originalDup; expect(file._originalDup).not.toBeDefined(); }); it('original property can be shadowed by class prototype property', function () { spyOn(Gio._LocalFilePrototype, 'dup').and.returnValue(5); expect(file.dup()).toBe(5); expect(Gio._LocalFilePrototype.dup).toHaveBeenCalled(); }); it('overridden property can be shadowed by class prototype property', function () { spyOn(Gio._LocalFilePrototype, 'dup'); spyOn(Gio.File.prototype, 'dup'); file.dup(); expect(Gio._LocalFilePrototype.dup).toHaveBeenCalled(); expect(Gio.File.prototype.dup).not.toHaveBeenCalled(); }); it('shadowed property can be restored', function () { Gio._LocalFilePrototype.dup = 5; expect(file.dup).toBe(5); delete Gio._LocalFilePrototype.dup; expect(file.dup).toBeInstanceOf(Function); }); }); }); describe('Specific class and interface checks', function () { it('Gio.AsyncInitable must implement vfunc_async_init', function () { expect(() => GObject.registerClass({ Implements: [Gio.Initable, Gio.AsyncInitable], }, class BadAsyncInitable extends GObject.Object { vfunc_init() {} })).toThrow(); }); }); cjs-128.1/installed-tests/js/testGObjectValue.js0000664000175000017500000001607215116312211020532 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan const {GLib, GObject, GIMarshallingTests, Regress} = imports.gi; const SIGNED_TYPES = ['schar', 'int', 'int64', 'long']; const UNSIGNED_TYPES = ['char', 'uchar', 'uint', 'uint64', 'ulong']; const FLOATING_TYPES = ['double', 'float']; const NUMERIC_TYPES = [...SIGNED_TYPES, ...UNSIGNED_TYPES, ...FLOATING_TYPES]; const SPECIFIC_TYPES = ['gtype', 'boolean', 'string', 'param', 'variant', 'boxed', 'gvalue']; const INSTANCED_TYPES = ['object', 'instance']; const ALL_TYPES = [...NUMERIC_TYPES, ...SPECIFIC_TYPES, ...INSTANCED_TYPES]; describe('GObject value (GValue)', function () { let v; beforeEach(function () { v = new GObject.Value(); }); function getDefaultContentByType(type) { if (SIGNED_TYPES.includes(type)) return -((Math.random() * 100 | 0) + 1); if (UNSIGNED_TYPES.includes(type)) return -getDefaultContentByType('int') + 2; if (FLOATING_TYPES.includes(type)) return getDefaultContentByType('uint') + 0.5; if (type === 'string') return `Hello GValue! ${getDefaultContentByType('uint')}`; if (type === 'boolean') return !!(getDefaultContentByType('int') % 2); if (type === 'gtype') return getGType(ALL_TYPES[Math.random() * ALL_TYPES.length | 0]); if (type === 'boxed' || type === 'boxed-struct') { return new GIMarshallingTests.BoxedStruct({ long_: getDefaultContentByType('long'), // string_: getDefaultContentByType('string'), not supported }); } if (type === 'object') { const wasCreatingObject = this.creatingObject; this.creatingObject = true; const props = ALL_TYPES.filter(e => (e !== 'object' || !wasCreatingObject) && e !== 'boxed' && e !== 'gtype' && e !== 'instance' && e !== 'param' && e !== 'schar').concat([ 'boxed-struct', ]).reduce((ac, a) => ({ ...ac, [`some-${a}`]: getDefaultContentByType(a), }), {}); delete this.creatingObject; return new GIMarshallingTests.PropertiesObject(props); } if (type === 'param') { return GObject.ParamSpec.string('test-param', '', getDefaultContentByType('string'), GObject.ParamFlags.READABLE, ''); } if (type === 'variant') { return new GLib.Variant('a{sv}', { pasta: new GLib.Variant('s', 'Carbonara (con guanciale)'), pizza: new GLib.Variant('s', 'Verace'), randomString: new GLib.Variant('s', getDefaultContentByType('string')), }); } if (type === 'gvalue') { const value = new GObject.Value(); const valueType = NUMERIC_TYPES[Math.random() * NUMERIC_TYPES.length | 0]; value.init(getGType(valueType)); setContent(value, valueType, getDefaultContentByType(valueType)); return value; } if (type === 'instance') return new Regress.TestFundamentalSubObject(getDefaultContentByType('string')); throw new Error(`No default content set for type ${type}`); } function getGType(type) { if (type === 'schar') return GObject.TYPE_CHAR; if (type === 'boxed' || type === 'gvalue' || type === 'instance') return getDefaultContentByType(type).constructor.$gtype; return GObject[`TYPE_${type.toUpperCase()}`]; } function getContent(gvalue, type) { if (type === 'gvalue') type = 'boxed'; if (type === 'instance') return GIMarshallingTests.gvalue_round_trip(gvalue); return gvalue[`get_${type}`](); } function setContent(gvalue, type, content) { if (type === 'gvalue') type = 'boxed'; if (type === 'instance') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402'); return gvalue[`set_${type}`](content); } function skipUnsupported(type) { if (type === 'boxed') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402'); if (type === 'gvalue') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/272'); } ALL_TYPES.forEach(type => { const gtype = getGType(type); it(`initializes ${type}`, function () { v.init(gtype); }); it(`${type} is compatible with itself`, function () { expect(GObject.Value.type_compatible(gtype, gtype)).toBeTruthy(); }); it(`${type} is transformable to itself`, function () { expect(GObject.Value.type_transformable(gtype, gtype)).toBeTruthy(); }); describe('initialized', function () { let randomContent; beforeEach(function () { v.init(gtype); randomContent = getDefaultContentByType(type); }); it(`sets and gets ${type}`, function () { skipUnsupported(type); setContent(v, type, randomContent); expect(getContent(v, type)).toEqual(randomContent); }); it(`can be passed to a function and returns a ${type}`, function () { skipUnsupported(type); setContent(v, type, randomContent); expect(GIMarshallingTests.gvalue_round_trip(v)).toEqual(randomContent); expect(GIMarshallingTests.gvalue_copy(v)).toEqual(randomContent); }); it(`copies ${type}`, function () { skipUnsupported(type); setContent(v, type, randomContent); const other = new GObject.Value(); other.init(gtype); v.copy(other); expect(getContent(other, type)).toEqual(randomContent); }); }); it(`can be marshalled and un-marshalled from JS ${type}`, function () { if (['gtype', 'gvalue'].includes(type)) pending('Not supported - always implicitly converted'); const content = getDefaultContentByType(type); expect(GIMarshallingTests.gvalue_round_trip(content)).toEqual(content); }); }); ['int', 'uint', 'boolean', 'gtype', ...FLOATING_TYPES].forEach(type => { it(`can be marshalled and un-marshalled from JS gtype of ${type}`, function () { const gtype = getGType(type); expect(GIMarshallingTests.gvalue_round_trip(gtype).constructor.$gtype).toEqual(gtype); }); }); INSTANCED_TYPES.forEach(type => { it(`initializes from instance of ${type}`, function () { skipUnsupported(type); const instance = getDefaultContentByType(type); v.init_from_instance(instance); expect(getContent(v, type)).toEqual(instance); }); }); afterEach(function () { v.unset(); }); }); cjs-128.1/installed-tests/js/testGTypeClass.js0000664000175000017500000000411515116312211020231 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. // SPDX-FileCopyrightText: 2013 Giovanni Campagna // We use Gio to have some objects that we know exist const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; describe('Looking up param specs', function () { let p1, p2; beforeEach(function () { let findProperty = GObject.Object.find_property; p1 = findProperty.call(Gio.ThemedIcon, 'name'); p2 = findProperty.call(Gio.SimpleAction, 'enabled'); }); it('works', function () { expect(p1 instanceof GObject.ParamSpec).toBeTruthy(); expect(p2 instanceof GObject.ParamSpec).toBeTruthy(); }); it('gives the correct name', function () { expect(p1.name).toEqual('name'); expect(p2.name).toEqual('enabled'); }); it('gives the default value if present', function () { expect(p2.default_value).toBeTruthy(); }); }); describe('GType object', function () { it('has a name', function () { expect(GObject.TYPE_NONE.name).toEqual('void'); expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has a read-only name', function () { try { GObject.TYPE_STRING.name = 'foo'; } catch (e) { } expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has an undeletable name', function () { try { delete GObject.TYPE_STRING.name; } catch (e) { } expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has a string representation', function () { expect(GObject.TYPE_NONE.toString()).toEqual("[object GType for 'void']"); expect(GObject.TYPE_STRING.toString()).toEqual("[object GType for 'gchararray']"); }); }); describe('GType marshalling', function () { it('marshals the invalid GType object into JS null', function () { expect(GObject.type_from_name('NonexistentType')).toBeNull(); expect(GObject.type_parent(GObject.TYPE_STRING)).toBeNull(); }); }); cjs-128.1/installed-tests/js/testGettext.js0000664000175000017500000000124115116312211017634 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. const Gettext = imports.gettext; describe('Gettext module', function () { // We don't actually want to mess with the locale, so just use setlocale's // query mode. We also don't want to make this test locale-dependent, so // just assert that it returns a string with at least length 1 (the shortest // locale is "C".) it('setlocale returns a locale', function () { let locale = Gettext.setlocale(Gettext.LocaleCategory.ALL, null); expect(locale.length).not.toBeLessThan(1); }); }); cjs-128.1/installed-tests/js/testGio.js0000664000175000017500000004671415116312211016744 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Patrick Griffis // SPDX-FileCopyrightText: 2019 Philip Chimento const {GLib, Gio, GObject} = imports.gi; let GioUnix; try { GioUnix = imports.gi.GioUnix; } catch {} const Foo = GObject.registerClass({ Properties: { boolval: GObject.ParamSpec.boolean('boolval', '', '', GObject.ParamFlags.READWRITE, false), }, }, class Foo extends GObject.Object { _init(value) { super._init(); this.value = value; } }); describe('ListStore iterator', function () { let list; beforeEach(function () { list = new Gio.ListStore({item_type: Foo}); for (let i = 0; i < 100; i++) list.append(new Foo(i)); }); it('ListStore iterates', function () { let i = 0; for (let f of list) expect(f.value).toBe(i++); }); }); function compareFunc(a, b) { return a.value - b.value; } describe('Sorting in ListStore', function () { let list; beforeEach(function () { list = new Gio.ListStore({ item_type: Foo, }); }); it('test insert_sorted', function () { for (let i = 10; i > 0; i--) list.insert_sorted(new Foo(i), compareFunc); let i = 1; for (let f of list) expect(f.value).toBe(i++); }); it('test sort', function () { for (let i = 10; i > 0; i--) list.append(new Foo(i)); list.sort(compareFunc); let i = 1; for (let f of list) expect(f.value).toBe(i++); }); }); describe('Promisify function', function () { it("doesn't crash when async function is not defined", function () { expect(() => Gio._promisify(Gio.Subprocess.prototype, 'commuicate_utf8_async', 'communicate_utf8_finish')).toThrowError(/commuicate_utf8_async/); }); it("doesn't crash when finish function is not defined", function () { expect(() => Gio._promisify(Gio.Subprocess.prototype, 'communicate_utf8_async', 'commuicate_utf8_finish')).toThrowError(/commuicate_utf8_finish/); }); it('promisifies functions', async function () { Gio._promisify(Gio.File.prototype, 'query_info_async'); const file = Gio.File.new_for_path('.'); const fileInfo = await file.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null); expect(fileInfo.get_file_type()).not.toBe(Gio.FileType.UNKNOWN); }); it('preserves old behavior', function (done) { Gio._promisify(Gio.File.prototype, 'query_info_async'); const file = Gio.File.new_for_path('.'); file.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (_, res) => { const fileInfo = file.query_info_finish(res); expect(fileInfo.get_file_type()).not.toBe(Gio.FileType.UNKNOWN); done(); }); }); it('can guess the finish function', function () { expect(() => Gio._promisify(Gio._LocalFilePrototype, 'read_async')).not.toThrow(); expect(() => Gio._promisify(Gio.DBus, 'get')).not.toThrow(); }); }); describe('Gio.Settings overrides', function () { it("doesn't crash when forgetting to specify a schema ID", function () { expect(() => new Gio.Settings()).toThrowError(/schema/); }); it("doesn't crash when specifying a schema ID that isn't installed", function () { expect(() => new Gio.Settings({schemaId: 'com.example.ThisDoesntExist'})) .toThrowError(/schema/); }); it("doesn't crash when forgetting to specify a schema path", function () { expect(() => new Gio.Settings({schemaId: 'org.cinnamon.CjsTest.Sub'})) .toThrowError(/schema/); }); it("doesn't crash when specifying conflicting schema paths", function () { expect(() => new Gio.Settings({ schemaId: 'org.cinnamon.CjsTest', path: '/conflicting/path/', })).toThrowError(/schema/); }); it('can construct with a settings schema object', function () { const source = Gio.SettingsSchemaSource.get_default(); const settingsSchema = source.lookup('org.cinnamon.CjsTest', false); expect(() => new Gio.Settings({settingsSchema})).not.toThrow(); }); it('throws proper error message when settings schema is specified with a wrong type', function () { expect(() => new Gio.Settings({ settings_schema: 'string.path', }).toThrowError('is not of type Gio.SettingsSchema')); }); describe('with existing schema', function () { const KINDS = ['boolean', 'double', 'enum', 'flags', 'int', 'int64', 'string', 'strv', 'uint', 'uint64', 'value']; let settings; beforeEach(function () { settings = new Gio.Settings({schemaId: 'org.cinnamon.CjsTest'}); }); it("doesn't crash when resetting a nonexistent key", function () { expect(() => settings.reset('foobar')).toThrowError(/key/); }); it("doesn't crash when checking a nonexistent key", function () { KINDS.forEach(kind => { expect(() => settings[`get_${kind}`]('foobar')).toThrowError(/key/); }); }); it("doesn't crash when setting a nonexistent key", function () { KINDS.forEach(kind => { expect(() => settings[`set_${kind}`]('foobar', null)).toThrowError(/key/); }); }); it("doesn't crash when checking writable for a nonexistent key", function () { expect(() => settings.is_writable('foobar')).toThrowError(/key/); }); it("doesn't crash when getting the user value for a nonexistent key", function () { expect(() => settings.get_user_value('foobar')).toThrowError(/key/); }); it("doesn't crash when getting the default value for a nonexistent key", function () { expect(() => settings.get_default_value('foobar')).toThrowError(/key/); }); it("doesn't crash when binding a nonexistent key", function () { const foo = new Foo(); expect(() => settings.bind('foobar', foo, 'boolval', Gio.SettingsBindFlags.GET)) .toThrowError(/key/); expect(() => settings.bind_writable('foobar', foo, 'boolval', false)) .toThrowError(/key/); }); it("doesn't crash when creating actions for a nonexistent key", function () { expect(() => settings.create_action('foobar')).toThrowError(/key/); }); it("doesn't crash when checking info about a nonexistent key", function () { expect(() => settings.settings_schema.get_key('foobar')).toThrowError(/key/); }); it("doesn't crash when getting a nonexistent sub-schema", function () { expect(() => settings.get_child('foobar')).toThrowError(/foobar/); }); it('still works with correct keys', function () { const KEYS = ['window-size', 'maximized', 'fullscreen']; KEYS.forEach(key => expect(settings.is_writable(key)).toBeTruthy()); expect(() => { settings.set_value('window-size', new GLib.Variant('(ii)', [100, 100])); settings.set_boolean('maximized', true); settings.set_boolean('fullscreen', true); }).not.toThrow(); expect(settings.get_value('window-size').deepUnpack()).toEqual([100, 100]); expect(settings.get_boolean('maximized')).toEqual(true); expect(settings.get_boolean('fullscreen')).toEqual(true); expect(() => { KEYS.forEach(key => settings.reset(key)); }).not.toThrow(); KEYS.forEach(key => expect(settings.get_user_value(key)).toBeNull()); expect(settings.get_default_value('window-size').deepUnpack()).toEqual([-1, -1]); expect(settings.get_default_value('maximized').deepUnpack()).toEqual(false); expect(settings.get_default_value('fullscreen').deepUnpack()).toEqual(false); const foo = new Foo({boolval: true}); settings.bind('maximized', foo, 'boolval', Gio.SettingsBindFlags.GET); expect(foo.boolval).toBeFalsy(); Gio.Settings.unbind(foo, 'boolval'); settings.bind_writable('maximized', foo, 'boolval', false); expect(foo.boolval).toBeTruthy(); expect(settings.create_action('maximized')).not.toBeNull(); expect(settings.settings_schema.get_key('fullscreen')).not.toBeNull(); const sub = settings.get_child('sub'); expect(sub.get_uint('marine')).toEqual(10); }); }); }); describe('Gio.content_type_set_mime_dirs', function () { it('can be called with NULL argument', function () { expect(() => Gio.content_type_set_mime_dirs(null)).not.toThrow(); }); }); describe('Gio.add_action_entries override', function () { it('registers each entry as an action', function () { const app = new Gio.Application(); const entries = [ { name: 'foo', parameter_type: 's', }, { name: 'bar', state: 'false', }, ]; app.add_action_entries(entries); expect(app.lookup_action('foo').name).toEqual(entries[0].name); expect(app.lookup_action('foo').parameter_type.dup_string()).toEqual(entries[0].parameter_type); expect(app.lookup_action('bar').name).toEqual(entries[1].name); expect(app.lookup_action('bar').state.print(true)).toEqual(entries[1].state); }); it('connects and binds the activate handler', function (done) { const app = new Gio.Application(); let action; const entries = [ { name: 'foo', parameter_type: 's', activate() { expect(this).toBe(action); done(); }, }, ]; app.add_action_entries(entries); action = app.lookup_action('foo'); action.activate(new GLib.Variant('s', 'hello')); }); it('connects and binds the change_state handler', function (done) { const app = new Gio.Application(); let action; const entries = [ { name: 'bar', state: 'false', change_state() { expect(this).toBe(action); done(); }, }, ]; app.add_action_entries(entries); action = app.lookup_action('bar'); action.change_state(new GLib.Variant('b', 'true')); }); it('throw an error if the parameter_type is invalid', function () { const app = new Gio.Application(); const entries = [ { name: 'foo', parameter_type: '(((', }, ]; expect(() => app.add_action_entries(entries)).toThrow(); }); it('throw an error if the state is invalid', function () { const app = new Gio.Application(); const entries = [ { name: 'bar', state: 'foo', }, ]; expect(() => app.add_action_entries(entries)).toThrow(); }); }); describe('Gio.InputStream.prototype.createSyncIterator', function () { it('iterates synchronously', function () { const [file] = Gio.File.new_tmp(null); file.replace_contents('hello ㊙ world', null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); let totalRead = 0; for (const value of file.read(null).createSyncIterator(2)) { expect(value).toBeInstanceOf(GLib.Bytes); totalRead += value.get_size(); } expect(totalRead).toBe(15); }); }); describe('Gio.InputStream.prototype.createAsyncIterator', function () { it('iterates asynchronously', async function () { const [file] = Gio.File.new_tmp(null); file.replace_contents('hello ㊙ world', null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); let totalRead = 0; for await (const value of file.read(null).createAsyncIterator(2)) { expect(value).toBeInstanceOf(GLib.Bytes); totalRead += value.get_size(); } expect(totalRead).toBe(15); }); }); describe('Gio.FileEnumerator overrides', function () { it('iterates synchronously', function () { const dir = Gio.File.new_for_path('.'); let count = 0; for (const value of dir.enumerate_children( 'standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null )) { expect(value).toBeInstanceOf(Gio.FileInfo); count++; } expect(count).toBeGreaterThan(0); }); it('iterates asynchronously', async function () { const dir = Gio.File.new_for_path('.'); let count = 0; for await (const value of dir.enumerate_children( 'standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null )) { expect(value).toBeInstanceOf(Gio.FileInfo); count++; } expect(count).toBeGreaterThan(0); }); }); describe('Gio.DesktopAppInfo fallback', function () { const requiredVersion = GLib.MAJOR_VERSION > 2 || (GLib.MAJOR_VERSION === 2 && GLib.MINOR_VERSION >= 86); let keyFile; const desktopFileContent = `[Desktop Entry] Version=1.0 Type=Application Name=Some Application Exec=${GLib.find_program_in_path('sh')} `; beforeAll(function () { // Set up log writer for tests to override keyFile = new GLib.KeyFile(); keyFile.load_from_data(desktopFileContent, desktopFileContent.length, GLib.KeyFileFlags.NONE); }); beforeEach(function () { if (!GioUnix) pending('Not supported platform'); if (!requiredVersion) pending('Installed Gio is not new enough for this test'); }); function expectDeprecationWarning(testFunction) { if (!requiredVersion) pending('Installed Gio is not new enough for this test'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.DesktopAppInfo has been moved to a separate platform-specific library. ' + 'Please update your code to use GioUnix.DesktopAppInfo instead*'); testFunction(); GLib.test_assert_expected_messages_internal('Cjs', 'testGio.js', 0, 'Gio.DesktopAppInfo expectWarnsOnNewerGio'); } it('can be created using GioUnix', function () { expect(GioUnix.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull(); }); it('can be created using Gio wrapper', function () { expectDeprecationWarning(() => expect(Gio.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull()); expectDeprecationWarning(() => expect(Gio.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull()); }); describe('provides platform-independent functions', function () { [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () { if (!requiredVersion) pending('Installed Gio is not new enough for this test'); const maybeExpectDeprecationWarning = ns === Gio ? expectDeprecationWarning : tf => tf(); maybeExpectDeprecationWarning(() => { const appInfo = ns.DesktopAppInfo.new_from_keyfile(keyFile); expect(appInfo.get_name()).toBe('Some Application'); }); })); }); describe('provides unix-only functions', function () { [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () { if (!requiredVersion) pending('Installed Gio is not new enough for this test'); const maybeExpectDeprecationWarning = ns === Gio ? expectDeprecationWarning : tf => tf(); maybeExpectDeprecationWarning(() => { const appInfo = ns.DesktopAppInfo.new_from_keyfile(keyFile); expect(appInfo.has_key('Name')).toBeTrue(); expect(appInfo.get_string('Name')).toBe('Some Application'); }); })); }); }); describe('Non-introspectable file attribute overrides', function () { let numExpectedWarnings, file, info; const flags = [Gio.FileQueryInfoFlags.NONE, null]; function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { GLib.test_assert_expected_messages_internal('Cjs', 'testGio.js', 0, `test Gio.${testName}`); } numExpectedWarnings = 0; } beforeEach(function () { numExpectedWarnings = 0; [file] = Gio.File.new_tmp('XXXXXX'); info = file.query_info('standard::*', ...flags); }); it('invalid means unsetting the attribute', function () { expectWarnings(2); expect(() => file.set_attribute('custom::remove', Gio.FileAttributeType.INVALID, null, ...flags)) .toThrowError(/not introspectable/); expect(() => info.set_attribute('custom::remove', Gio.FileAttributeType.INVALID)).not.toThrow(); assertWarnings(); }); it('works for boolean', function () { expectWarnings(2); expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, Gio.FileAttributeType.BOOLEAN, false, ...flags)) .toThrowError(/not introspectable/); expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, Gio.FileAttributeType.BOOLEAN, false)) .not.toThrow(); assertWarnings(); }); it('works for uint32', function () { expectWarnings(2); expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED_USEC, Gio.FileAttributeType.UINT32, 123456, ...flags)) .not.toThrow(); expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED_USEC, Gio.FileAttributeType.UINT32, 654321)) .not.toThrow(); assertWarnings(); }); it('works for uint64', function () { expectWarnings(2); expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileAttributeType.UINT64, Date.now() / 1000, ...flags)) .not.toThrow(); expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileAttributeType.UINT64, Date.now() / 1000)) .not.toThrow(); assertWarnings(); }); it('works for object', function () { expectWarnings(2); const icon = Gio.ThemedIcon.new_from_names(['xsi-list-add-symbolic']); expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileAttributeType.OBJECT, icon, ...flags)) .toThrowError(/not introspectable/); expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileAttributeType.OBJECT, icon)) .not.toThrow(); assertWarnings(); }); afterEach(function () { file.delete_async(GLib.PRIORITY_DEFAULT, null, (obj, res) => obj.delete_finish(res)); }); }); cjs-128.1/installed-tests/js/testGlobal.js0000664000175000017500000000220015116312211017404 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2022 Evan Welsh describe('globalThis', () => { function itIsDefined(value, message) { it(`${message ? `${message} ` : ''}is defined`, function () { expect(value).toBeDefined(); }); } it('is equal to window', function () { expect(globalThis.window).toBe(globalThis); expect(window.globalThis).toBe(globalThis); }); describe('WeakRef', () => { itIsDefined(globalThis.WeakRef); }); describe('console', () => { itIsDefined(globalThis.console); }); describe('TextEncoder', () => { itIsDefined(globalThis.TextEncoder); }); describe('TextDecoder', () => { itIsDefined(globalThis.TextDecoder); }); describe('ARGV', () => { itIsDefined(globalThis.ARGV); }); describe('print function', () => { itIsDefined(globalThis.log, 'log'); itIsDefined(globalThis.print, 'print'); itIsDefined(globalThis.printerr, 'printerr'); itIsDefined(globalThis.logError, 'logError'); }); }); cjs-128.1/installed-tests/js/testGtk3.js0000664000175000017500000003052415116312211017026 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna imports.gi.versions.Gtk = '3.0'; const {GLib, Gio, GObject, Gtk} = imports.gi; const System = imports.system; // This is ugly here, but usually it would be in a resource function createTemplate(className) { return ` `; } const MyComplexGtkSubclass = GObject.registerClass({ Template: new TextEncoder().encode(createTemplate('Gjs_MyComplexGtkSubclass')), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', }, class MyComplexGtkSubclass extends Gtk.Grid { templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } testChildrenExist() { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } }); const MyComplexGtkSubclassFromResource = GObject.registerClass({ Template: 'resource:///org/cjs/jsunit/complex3.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromResource extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const [templateFile, stream] = Gio.File.new_tmp(null); const baseStream = stream.get_output_stream(); const out = new Gio.DataOutputStream({baseStream}); out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null); out.close(null); const MyComplexGtkSubclassFromFile = GObject.registerClass({ Template: templateFile.get_uri(), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromFile extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const SubclassSubclass = GObject.registerClass( class SubclassSubclass extends MyComplexGtkSubclass {}); function validateTemplate(description, ClassName, pending = false) { let suite = pending ? xdescribe : describe; suite(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); content = new ClassName(); content.label_child.emit('grab-focus'); content.label_child2.emit('grab-focus'); win.add(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); it('connects template callbacks to the correct handler', function () { expect(content.callbackEmittedBy).toBe(content.label_child); }); it('binds template callbacks to the correct object', function () { expect(content.label_child2.callbackBoundTo).toBe(content.label_child); }); afterEach(function () { win.destroy(); }); }); } describe('Gtk overrides', function () { beforeAll(function () { Gtk.init(null); }); afterAll(function () { templateFile.delete(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); validateTemplate('Class inheriting from template class', SubclassSubclass, true); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); it('static inheritance works', function () { expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass'); }); it('avoid crashing when GTK vfuncs are called in garbage collection', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was destroy()*'); const BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label { vfunc_destroy() {} }); new BadLabel(); System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, 'Gtk overrides avoid crashing and print a stack trace'); }); it('GTK vfuncs are not called if the object is disposed', function () { const spy = jasmine.createSpy('vfunc_destroy'); const NotSoGoodLabel = GObject.registerClass(class NotSoGoodLabel extends Gtk.Label { vfunc_destroy() { spy(); } }); let label = new NotSoGoodLabel(); label.destroy(); expect(spy).toHaveBeenCalledTimes(1); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was destroy()*'); label = null; System.gc(); GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, 'GTK vfuncs are not called if the object is disposed'); }); it('destroy signal is emitted while disposing objects', function () { const label = new Gtk.Label({label: 'Hello'}); const handleDispose = jasmine.createSpy('handleDispose').and.callFake(() => { expect(label.label).toBe('Hello'); }); label.connect('destroy', handleDispose); label.destroy(); expect(handleDispose).toHaveBeenCalledWith(label); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Label (0x* disposed *'); expect(label.label).toBe('Hello'); GLib.test_assert_expected_messages_internal('Cjs', 'testGtk3.js', 0, 'GTK destroy signal is emitted while disposing objects'); }); it('destroy signal is not emitted when objects are garbage collected', function () { let label = new Gtk.Label({label: 'Hello'}); const handleDispose = jasmine.createSpy('handleDispose').and.callFake(() => { expect(label.label).toBe('Hello'); }); label.connect('destroy', handleDispose); label = null; System.gc(); System.gc(); expect(handleDispose).not.toHaveBeenCalled(); }); it('accepts string in place of GdkAtom', function () { expect(() => Gtk.Clipboard.get(1)).toThrow(); expect(() => Gtk.Clipboard.get(true)).toThrow(); expect(() => Gtk.Clipboard.get(() => undefined)).toThrow(); const clipboard = Gtk.Clipboard.get('CLIPBOARD'); const primary = Gtk.Clipboard.get('PRIMARY'); const anotherClipboard = Gtk.Clipboard.get('CLIPBOARD'); expect(clipboard).toBeTruthy(); expect(primary).toBeTruthy(); expect(clipboard).not.toBe(primary); expect(clipboard).toBe(anotherClipboard); }); it('accepts null in place of GdkAtom as GDK_NONE', function () { const clipboard = Gtk.Clipboard.get('NONE'); const clipboard2 = Gtk.Clipboard.get(null); expect(clipboard2).toBe(clipboard); }); it('uses the correct GType for null child properties', function () { let s = new Gtk.Stack(); let p = new Gtk.Box(); s.add_named(p, 'foo'); expect(s.get_child_by_name('foo')).toBe(p); s.child_set_property(p, 'name', null); expect(s.get_child_by_name('foo')).toBeNull(); }); it('can create a Gtk.TreeIter with accessible stamp field', function () { const iter = new Gtk.TreeIter(); iter.stamp = 42; expect(iter.stamp).toEqual(42); }); it('can get style properties using GObject.Value', function () { let win = new Gtk.ScrolledWindow(); let value = new GObject.Value(); value.init(GObject.TYPE_BOOLEAN); win.style_get_property('scrollbars-within-bevel', value); expect(value.get_boolean()).toBeDefined(); value.unset(); value.init(GObject.TYPE_INT); let preVal = Math.max(512521, Math.random() * Number.MAX_SAFE_INTEGER); value.set_int(preVal); win.style_get_property('scrollbar-spacing', value); expect(value.get_int()).not.toEqual(preVal); win = new Gtk.Window(); value.unset(); value.init(GObject.TYPE_STRING); value.set_string('EMPTY'); win.style_get_property('decoration-button-layout', value); expect(value.get_string()).not.toEqual('EMPTY'); }); it('can pass a parent object to a child at construction', function () { const frame = new Gtk.Frame(); let frameChild = null; frame.connect('add', (_widget, child) => { frameChild = child; }); const widget = new Gtk.Label({parent: frame}); expect(widget).toBe(frameChild); expect(widget instanceof Gtk.Label).toBeTruthy(); expect(frameChild instanceof Gtk.Label).toBeTruthy(); expect(frameChild.visible).toBe(false); expect(() => widget.show()).not.toThrow(); expect(frameChild.visible).toBe(true); }); function asyncIdle() { return new Promise(resolve => { GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { resolve(); return GLib.SOURCE_REMOVE; }); }); } it('does not leak instance when connecting template signal', async function () { const LeakTestWidget = GObject.registerClass({ Template: new TextEncoder().encode(` `), }, class LeakTestWidget extends Gtk.Button { buttonClicked() {} }); const weakRef = new WeakRef(new LeakTestWidget()); await asyncIdle(); // It takes two GC cycles to free the widget, because of the tardy sweep // problem (https://gitlab.gnome.org/GNOME/gjs/-/issues/217) System.gc(); System.gc(); expect(weakRef.deref()).toBeUndefined(); }); }); cjs-128.1/installed-tests/js/testGtk4.js0000664000175000017500000003106215116312211017025 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; const {Gdk, Gio, GObject, Gtk, GLib, CjsTestTools} = imports.gi; const System = imports.system; // Set up log writer for tests to override const writerFunc = jasmine.createSpy('log writer', () => GLib.LogWriterOutput.UNHANDLED); writerFunc.and.callThrough(); GLib.log_set_writer_func(writerFunc); // This is ugly here, but usually it would be in a resource function createTemplate(className) { return ` `; } const MyComplexGtkSubclass = GObject.registerClass({ Template: new TextEncoder().encode(createTemplate('Gjs_MyComplexGtkSubclass')), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', }, class MyComplexGtkSubclass extends Gtk.Grid { templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } testChildrenExist() { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } }); const MyComplexGtkSubclassFromResource = GObject.registerClass({ Template: 'resource:///org/cjs/jsunit/complex4.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromResource extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const MyComplexGtkSubclassFromString = GObject.registerClass({ Template: createTemplate('Gjs_MyComplexGtkSubclassFromString'), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromString extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const [templateFile, stream] = Gio.File.new_tmp(null); const baseStream = stream.get_output_stream(); const out = new Gio.DataOutputStream({baseStream}); out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null); out.close(null); const MyComplexGtkSubclassFromFile = GObject.registerClass({ Template: templateFile.get_uri(), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class MyComplexGtkSubclassFromFile extends Gtk.Grid { testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); } templateCallback(widget) { this.callbackEmittedBy = widget; } boundCallback(widget) { widget.callbackBoundTo = this; } }); const SubclassSubclass = GObject.registerClass( class SubclassSubclass extends MyComplexGtkSubclass {}); const CustomActionWidget = GObject.registerClass( class CustomActionWidget extends Gtk.Widget { static _classInit(klass) { klass = Gtk.Widget._classInit(klass); Gtk.Widget.install_action.call(klass, 'custom.action', null, widget => (widget.action = 42)); return klass; } }); function validateTemplate(description, ClassName, pending = false) { let suite = pending ? xdescribe : describe; suite(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window(); content = new ClassName(); content.label_child.emit('copy-clipboard'); content.label_child2.emit('copy-clipboard'); win.set_child(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); it('connects template callbacks to the correct handler', function () { expect(content.callbackEmittedBy).toBe(content.label_child); }); it('binds template callbacks to the correct object', function () { expect(content.label_child2.callbackBoundTo).toBe(content.label_child); }); afterEach(function () { win.destroy(); }); }); } describe('Gtk overrides', function () { beforeAll(function () { Gtk.init(); }); afterAll(function () { templateFile.delete(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); validateTemplate('UI template from string', MyComplexGtkSubclassFromString); validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); validateTemplate('Class inheriting from template class', SubclassSubclass, true); it('ensures signal handlers are callable', function () { const ClassWithUncallableHandler = GObject.registerClass({ Template: createTemplate('Gjs_ClassWithUncallableHandler'), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], }, class ClassWithUncallableHandler extends Gtk.Grid { templateCallback() {} get boundCallback() { return 'who ya gonna call?'; } }); // The exception is thrown inside a vfunc with a GError out parameter, // and Gtk logs a critical. writerFunc.calls.reset(); writerFunc.and.callFake((level, fields) => { const decoder = new TextDecoder('utf-8'); const domain = decoder.decode(fields?.GLIB_DOMAIN); const message = decoder.decode(fields?.MESSAGE); expect(level).toBe(GLib.LogLevelFlags.LEVEL_CRITICAL); expect(domain).toBe('Gtk'); expect(message).toMatch('is not a function'); return GLib.LogWriterOutput.HANDLED; }); void new ClassWithUncallableHandler(); expect(writerFunc).toHaveBeenCalled(); writerFunc.and.callThrough(); }); it('rejects unsupported template URIs', function () { expect(() => { return GObject.registerClass({ Template: 'https://gnome.org', }, class GtkTemplateInvalid extends Gtk.Widget { }); }).toThrowError(TypeError, /Invalid template URI/); }); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); it('static inheritance works', function () { expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass'); }); it('can create a Gtk.TreeIter with accessible stamp field', function () { const iter = new Gtk.TreeIter(); iter.stamp = 42; expect(iter.stamp).toEqual(42); }); it('can create a Gtk.CustomSorter with callback', function () { const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); const model = Gtk.StringList.new(['hello', 'world']); const sorter = Gtk.CustomSorter.new(sortFunc); void Gtk.SortListModel.new(model, sorter); expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); }); it('can change the callback of a Gtk.CustomSorter', function () { const model = Gtk.StringList.new(['hello', 'world']); const sorter = Gtk.CustomSorter.new(null); void Gtk.SortListModel.new(model, sorter); const sortFunc = jasmine.createSpy('sortFunc').and.returnValue(1); sorter.set_sort_func(sortFunc); expect(sortFunc).toHaveBeenCalledOnceWith(jasmine.any(Gtk.StringObject), jasmine.any(Gtk.StringObject)); sortFunc.calls.reset(); sorter.set_sort_func(null); expect(sortFunc).not.toHaveBeenCalled(); }); }); describe('Gtk 4 regressions', function () { it('Gdk.Event fundamental type should not crash', function () { expect(() => new Gdk.Event()).toThrowError(/Couldn't find a constructor/); }); xit('Actions added via Gtk.WidgetClass.add_action() should not crash', function () { const custom = new CustomActionWidget(); custom.activate_action('custom.action', null); expect(custom.action).toEqual(42); }).pend('https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3796'); it('Gdk.NoSelection section returns valid start/end values', function () { if (!Gtk.NoSelection.prototype.get_section) pending('Gtk 4.12 is required'); let result; try { result = new Gtk.NoSelection().get_section(0); } catch (err) { if (err.message.includes('not introspectable')) pending('This version of GTK has the annotation bug'); throw err; } expect(result).toEqual([0, GLib.MAXUINT32]); }); function createSurface(shouldStash) { // Create a Gdk.Surface that is unreachable after this function ends const display = Gdk.Display.get_default(); const surface = Gdk.Surface.new_toplevel(display); if (shouldStash) CjsTestTools.save_object(surface); } it('Gdk.Surface is destroyed properly', function () { createSurface(false); System.gc(); }); it('Gdk.Surface is not destroyed if a ref is held from C', function () { createSurface(true); System.gc(); const surface = CjsTestTools.steal_saved(); expect(surface.is_destroyed()).toBeFalsy(); }); }); class LeakTestWidget extends Gtk.Button { buttonClicked() {} } GObject.registerClass({ Template: new TextEncoder().encode(` `), }, LeakTestWidget); describe('Gtk template signal', function () { beforeAll(function () { Gtk.init(); }); function asyncIdle() { return new Promise(resolve => { GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { resolve(); return GLib.SOURCE_REMOVE; }); }); } it('does not leak', async function () { const weakRef = new WeakRef(new LeakTestWidget()); await asyncIdle(); // It takes two GC cycles to free the widget, because of the tardy sweep // problem (https://gitlab.gnome.org/GNOME/gjs/-/issues/217) System.gc(); System.gc(); expect(weakRef.deref()).toBeUndefined(); }); }); cjs-128.1/installed-tests/js/testImporter.js0000664000175000017500000002354215116312211020021 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC describe('GI importer', function () { it('can import GI modules', function () { var GLib = imports.gi.GLib; expect(GLib.MAJOR_VERSION).toEqual(2); }); describe('on failure', function () { // For these tests, we provide special overrides files to sabotage the // import, at the path resource:///org/cjs/jsunit/modules/badOverrides. let oldSearchPath; beforeAll(function () { oldSearchPath = imports.overrides.searchPath.slice(); imports.overrides.searchPath = ['resource:///org/cjs/jsunit/modules/badOverrides']; }); afterAll(function () { imports.overrides.searchPath = oldSearchPath; }); it("throws an exception when the overrides file can't be imported", function () { expect(() => imports.gi.WarnLib).toThrowError(SyntaxError); }); it('throws an exception when the overrides import throws one', function () { expect(() => imports.gi.GIMarshallingTests).toThrow('💩'); }); it('throws an exception when the overrides _init throws one', function () { expect(() => imports.gi.Regress).toThrow('💩'); }); it('throws an exception when the overrides _init is a primitive', function () { expect(() => imports.gi.Gio).toThrowError(/_init/); }); }); }); // Jasmine v3 often uses duck-typing (checking for a property to determine a type) to pretty print objects. // Unfortunately, checking for jasmineToString and other properties causes our importer objects to throw when resolving. // Luckily, we can override the default behavior with a custom formatter. function formatImporter(obj) { if (typeof obj === 'object' && obj.toString && (obj.toString()?.startsWith('[object GjsModule') || obj.toString()?.startsWith('[GjsFileImporter '))) return obj.toString(); return undefined; } describe('Importer', function () { let oldSearchPath; let foobar, subA, subB, subFoobar; beforeAll(function () { oldSearchPath = imports.searchPath.slice(); imports.searchPath = ['resource:///org/cjs/jsunit/modules']; foobar = imports.foobar; subA = imports.subA; subB = imports.subA.subB; subFoobar = subB.foobar; }); afterAll(function () { imports.searchPath = oldSearchPath; }); beforeEach(function () { jasmine.addCustomObjectFormatter(formatImporter); }); it('is on the global object (backwards compatibility)', function () { expect(imports instanceof globalThis.GjsFileImporter).toBeTruthy(); }); it('is abstract', function () { expect(() => new globalThis.GjsFileImporter()).toThrow(); }); it('exists', function () { expect(imports).toBeDefined(); }); it('has a toString representation', function () { expect(imports.toString()).toEqual('[GjsFileImporter root]'); expect(subA.toString()).toEqual('[GjsFileImporter subA]'); }); it('throws an import error when trying to import a nonexistent module', function () { expect(() => imports.nonexistentModuleName) .toThrow(jasmine.objectContaining({name: 'ImportError'})); }); it('throws an error when evaluating the module file throws an error', function () { expect(() => imports.alwaysThrows).toThrow(); // Try again to make sure that we properly discarded the module object expect(() => imports.alwaysThrows).toThrow(); }); it('can import a module', function () { expect(foobar).toBeDefined(); expect(foobar.foo).toEqual('This is foo'); expect(foobar.bar).toEqual('This is bar'); }); it('can import a module with a toString property', function () { expect(foobar.testToString('foo')).toEqual('foo'); }); it('makes deleting the import a no-op', function () { expect(delete imports.foobar).toBeFalsy(); expect(imports.foobar).toBe(foobar); }); it('gives the same object when importing a second time', function () { foobar.somethingElse = 'Should remain'; const foobar2 = imports.foobar; expect(foobar2.somethingElse).toEqual('Should remain'); }); it('can import a submodule', function () { expect(subB).toBeDefined(); expect(subFoobar).toBeDefined(); expect(subFoobar.foo).toEqual('This is foo'); expect(subFoobar.bar).toEqual('This is bar'); }); it('imports modules with a toString representation', function () { expect(Object.prototype.toString.call(foobar)) .toEqual('[object GjsModule foobar]'); expect(subFoobar.toString()) .toEqual('[object GjsModule subA.subB.foobar]'); }); it('does not share the same object for a module on a different path', function () { foobar.somethingElse = 'Should remain'; expect(subFoobar.somethingElse).not.toBeDefined(); }); it('gives the same object when importing a submodule a second time', function () { subFoobar.someProp = 'Should be here'; const subFoobar2 = imports.subA.subB.foobar; expect(subFoobar2.someProp).toEqual('Should be here'); }); it('has no meta properties on the toplevel importer', function () { expect(imports.__moduleName__).toBeNull(); expect(imports.__parentModule__).toBeNull(); }); it('sets the names of imported modules', function () { expect(subA.__moduleName__).toEqual('subA'); expect(subB.__moduleName__).toEqual('subB'); }); it('gives a module the importer object as parent module', function () { expect(subA.__parentModule__).toBe(imports); }); it('gives a submodule the module as parent module', function () { expect(subB.__parentModule__).toBe(subA); }); // We want to check that the copy of the 'a' module imported directly // is the same as the copy that 'b' imports, and that we don't have two // copies because of the A imports B imports A loop. it('does not make a separate copy of a module imported in two places', function () { let A = imports.mutualImport.a; A.incrementCount(); expect(A.getCount()).toEqual(1); expect(A.getCountViaB()).toEqual(1); }); it('evaluates an __init__.js file in an imported directory', function () { expect(subB.testImporterFunction()).toEqual('__init__ function tested'); }); it('throws on an __init__.js file with a syntax error', function () { expect(() => imports.subBadInit.SOMETHING).toThrowError(SyntaxError); }); it('throws when an __init__.js throws an error', function () { expect(() => imports.subErrorInit.SOMETHING).toThrowError('a bad init!'); }); it('accesses a class defined in an __init__.js file', function () { let o = new subB.ImporterClass(); expect(o).not.toBeNull(); expect(o.testMethod()).toEqual('__init__ class tested'); }); it('can import a file encoded in UTF-8', function () { const ModUnicode = imports.modunicode; expect(ModUnicode.uval).toEqual('const \u2665 utf8'); }); describe("properties defined in the module's lexical scope", function () { let LexicalScope; beforeAll(function () { globalThis.expectMe = true; LexicalScope = imports.lexicalScope; }); it('will log a compatibility warning when accessed', function () { const GLib = imports.gi.GLib; GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'b' on the module " + "'lexicalScope'.*"); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'c' on the module " + "'lexicalScope'.*"); void LexicalScope.b; void LexicalScope.c; // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Cjs', 'testImporter.js', 179, ''); }); it('can be accessed', function () { expect(LexicalScope.a).toEqual(1); expect(LexicalScope.b).toEqual(2); expect(LexicalScope.c).toEqual(3); expect(LexicalScope.d).toEqual(4); }); it('does not leak module properties into the global scope', function () { expect(globalThis.d).not.toBeDefined(); }); }); describe('enumerating modules', function () { let keys; beforeEach(function () { keys = []; for (let key in imports) keys.push(key); }); it('gets all of them', function () { expect(keys).toContain('foobar', 'subA', 'mutualImport', 'modunicode'); }); it('includes modules that throw on import', function () { expect(keys).toContain('alwaysThrows'); }); it('does not include meta properties', function () { expect(keys).not.toContain('__parentModule__'); expect(keys).not.toContain('__moduleName__'); expect(keys).not.toContain('searchPath'); }); }); it("doesn't crash when resolving a non-string property", function () { expect(imports[0]).not.toBeDefined(); expect(imports.foobar[0]).not.toBeDefined(); }); it('scripts support relative dynamic imports', async function () { const {say} = await import('./modules/say.js'); expect(typeof say).toBe('function'); expect(say('hello')).toBe('<( hello )'); }); it('imported scripts support relative dynamic imports', async function () { const response = await imports.dynamic.test(); expect(response).toBe('<( I did it! )'); }); }); cjs-128.1/installed-tests/js/testImporter2.js0000664000175000017500000000322615116312211020100 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento // This test is in a separate file instead of testImporter.js, because it tests // loading overrides for g-i modules, and in the original file we have literally // run out of g-i modules to override -- at least, the ones that we can assume // will be present on any system where GJS is compiled. describe('GI importer', function () { describe('on failure', function () { // For these tests, we provide special overrides files to sabotage the // import, at the path resource:///org/cjs/jsunit/modules/badOverrides2. let oldSearchPath; beforeAll(function () { oldSearchPath = imports.overrides.searchPath.slice(); imports.overrides.searchPath = ['resource:///org/cjs/jsunit/modules/badOverrides2']; }); afterAll(function () { imports.overrides.searchPath = oldSearchPath; }); it("throws an exception when the overrides _init isn't a function", function () { expect(() => imports.gi.GIMarshallingTests).toThrowError(/_init/); }); it('throws an exception when the overrides _init is null', function () { expect(() => imports.gi.Gio).toThrowError(/_init/); }); it('throws an exception when the overrides _init is undefined', function () { expect(() => imports.gi.Regress).toThrowError(/_init/); }); it('throws an exception when the overrides _init is missing', function () { expect(() => imports.gi.WarnLib).toThrowError(/_init/); }); }); }); cjs-128.1/installed-tests/js/testIntrospection.js0000664000175000017500000002347115116312211021061 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008, 2018 Red Hat, Inc. // SPDX-FileCopyrightText: 2017 Philip Chimento // SPDX-FileCopyrightText: 2020 Ole Jørgen Brønner // Various tests having to do with how introspection is implemented in GJS imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gtk = '3.0'; const {Gdk, Gio, GLib, GObject, Gtk} = imports.gi; const System = imports.system; describe('GLib.DestroyNotify parameter', function () { it('throws when encountering a GDestroyNotify not associated with a callback', function () { // should throw when called, not when the function object is created expect(() => Gio.MemoryInputStream.new_from_data).not.toThrow(); // the 'destroy' argument applies to the data, which is not supported in // gobject-introspection expect(() => Gio.MemoryInputStream.new_from_data('foobar')) .toThrowError(/destroy/); }); }); describe('Unsafe integer marshalling', function () { it('warns when conversion is lossy', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); void GLib.MININT64; void GLib.MAXINT64; void GLib.MAXUINT64; GLib.test_assert_expected_messages_internal('Cjs', 'testEverythingBasic.js', 0, 'Limits warns when conversion is lossy'); }); }); describe('Marshalling empty flat arrays of structs', function () { let widget; let gtkEnabled; beforeAll(function () { gtkEnabled = GLib.getenv('ENABLE_GTK') === 'yes'; if (!gtkEnabled) return; Gtk.init(null); }); beforeEach(function () { if (!gtkEnabled) { pending('GTK disabled'); return; } widget = new Gtk.Label(); }); it('accepts null', function () { widget.drag_dest_set(0, null, Gdk.DragAction.COPY); }); it('accepts an empty array', function () { widget.drag_dest_set(0, [], Gdk.DragAction.COPY); }); }); describe('Constructor', function () { it('throws when constructor called without new', function () { expect(() => Gio.AppLaunchContext()) .toThrowError(/Constructor called as normal method/); }); }); describe('Enum classes', function () { it('enum has a $gtype property', function () { expect(Gio.BusType.$gtype).toBeDefined(); }); it('enum $gtype property is enumerable', function () { expect('$gtype' in Gio.BusType).toBeTruthy(); }); }); describe('GError domains', function () { it('Number converts error to quark', function () { expect(Gio.ResolverError.quark()).toEqual(Number(Gio.ResolverError)); }); }); describe('Object properties on GtkBuilder-constructed objects', function () { let o1; let gtkEnabled; beforeAll(function () { gtkEnabled = GLib.getenv('ENABLE_GTK') === 'yes'; if (!gtkEnabled) return; Gtk.init(null); }); beforeEach(function () { if (!gtkEnabled) { pending('GTK disabled'); return; } const ui = ` Click me `; let builder = Gtk.Builder.new_from_string(ui, -1); o1 = builder.get_object('button'); }); it('are found on the GObject itself', function () { expect(o1.label).toBe('Click me'); }); it('are found on the GObject\'s parents', function () { expect(o1.visible).toBeFalsy(); }); it('are found on the GObject\'s interfaces', function () { expect(o1.action_name).toBeNull(); }); }); describe('Garbage collection of introspected objects', function () { // This tests a regression that would very rarely crash, but // when run under valgrind this code would show use-after-free. it('collects objects properly with signals connected', function (done) { function orphanObject() { let obj = new GObject.Object(); obj.connect('notify', () => {}); } orphanObject(); System.gc(); GLib.idle_add(GLib.PRIORITY_LOW, () => done()); }); // This tests a race condition that would crash; it should warn instead it('handles setting a property from C on an object whose JS wrapper has been collected', function (done) { class SomeObject extends GObject.Object { static [GObject.properties] = { 'screenfull': GObject.ParamSpec.boolean('screenfull', '', '', GObject.ParamFlags.READWRITE, false), }; static { GObject.registerClass(this); } } GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*property screenfull*'); const settings = new Gio.Settings({schemaId: 'org.cinnamon.CjsTest'}); let obj = new SomeObject(); settings.bind('fullscreen', obj, 'screenfull', Gio.SettingsBindFlags.DEFAULT); const handler = settings.connect('changed::fullscreen', () => { obj.run_dispose(); obj = null; settings.disconnect(handler); GLib.idle_add(GLib.PRIORITY_LOW, () => { GLib.test_assert_expected_messages_internal('Cjs', 'testIntrospection.js', 0, 'Warn about setting property on disposed JS object'); done(); }); }); settings.set_boolean('fullscreen', !settings.get_boolean('fullscreen')); settings.reset('fullscreen'); }); }); describe('Gdk.Atom', function () { it('is presented as string', function () { expect(Gdk.Atom.intern('CLIPBOARD', false)).toBe('CLIPBOARD'); expect(Gdk.Atom.intern('NONE', false)).toBe(null); }); }); describe('Complete enumeration (boxed types)', function () { it('enumerates all properties', function () { // Note: this test breaks down if other code access all the methods of Rectangle const rect = new Gdk.Rectangle(); const names = Object.getOwnPropertyNames(Object.getPrototypeOf(rect)); const expectAtLeast = ['equal', 'intersect', 'union', 'x', 'y', 'width', 'height']; expect(names).toEqual(jasmine.arrayContaining(expectAtLeast)); }); }); describe('Complete enumeration of GIRepositoryNamespace (new_enumerate)', function () { it('enumerates all properties (sampled)', function () { const names = Object.getOwnPropertyNames(Gdk); // Note: properties which has been accessed are listed without new_enumerate hook const expectAtLeast = ['KEY_ybelowdot', 'EventSequence', 'ByteOrder', 'Window']; expect(names).toEqual(jasmine.arrayContaining(expectAtLeast)); }); it('all enumerated properties are defined', function () { const names = Object.keys(Gdk); expect(() => { // Access each enumerated property to check it can be defined. names.forEach(name => Gdk[name]); }).not.toThrowError(/API of type .* not implemented, cannot define .*/); }); }); describe('Backwards compatibility for GLib/Gio platform specific GIRs', function () { // Only test this if GioUnix is available const skip = imports.gi.versions.GioUnix !== '2.0'; it('GioUnix objects are looked up in GioUnix, not Gio', function () { if (skip) { pending('GioUnix required for this test'); return; } GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.UnixMountMonitor*'); const monitor = Gio.UnixMountMonitor.get(); expect(monitor.toString()).toContain('GIName:GioUnix.MountMonitor'); GLib.test_assert_expected_messages_internal('Cjs', 'testIntrospection.js', 0, 'Expected deprecation message for Gio.Unix -> GioUnix'); }); it('GioUnix functions are looked up in GioUnix, not Gio', function () { if (skip) { pending('GioUnix required for this test'); return; } GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.unix_mounts_get*GioUnix.mounts_get*instead*'); expect(imports.gi.Gio.unix_mounts_get.name).toBe('g_unix_mounts_get'); GLib.test_assert_expected_messages_internal('Cjs', 'testIntrospection.js', 0, 'Expected deprecation message for Gio.Unix -> GioUnix'); }); it("doesn't print the message if the type isn't resolved directly", function () { if (skip) { pending('GioUnix required for this test'); return; } const launcher = new Gio.SubprocessLauncher({flags: Gio.SubprocessFlags.STDOUT_PIPE}); const proc = launcher.spawnv(['ls', '/dev/null']); expect(proc.get_stdout_pipe().toString()).toContain('GIName:GioUnix.InputStream'); }); it('has some exceptions', function () { expect(Gio.UnixConnection.toString()).toContain('Gio_UnixConnection'); const credentialsMessage = new Gio.UnixCredentialsMessage(); expect(credentialsMessage.toString()).toContain('GIName:Gio.UnixCredentials'); const fdList = new Gio.UnixFDList(); expect(fdList.toString()).toContain('GIName:Gio.UnixFDList'); const socketAddress = Gio.UnixSocketAddress.new_with_type('', Gio.UnixSocketAddressType.ANONYMOUS); expect(socketAddress.toString()).toContain('GIName:Gio.UnixSocketAddress'); }); }); cjs-128.1/installed-tests/js/testLang.js0000664000175000017500000000577415116312211017110 0ustar fabiofabio/* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // tests for imports.lang module // except for Lang.Class and Lang.Interface, which are tested in testLegacyClass const Lang = imports.lang; describe('Lang module', function () { it('counts properties with Lang.countProperties()', function () { var foo = {'a': 10, 'b': 11}; expect(Lang.countProperties(foo)).toEqual(2); }); it('copies properties from one object to another with Lang.copyProperties()', function () { var foo = {'a': 10, 'b': 11}; var bar = {}; Lang.copyProperties(foo, bar); expect(bar).toEqual(foo); }); it('copies properties without an underscore with Lang.copyPublicProperties()', function () { var foo = {'a': 10, 'b': 11, '_c': 12}; var bar = {}; Lang.copyPublicProperties(foo, bar); expect(bar).toEqual({'a': 10, 'b': 11}); }); it('copies property getters and setters', function () { var foo = { 'a': 10, 'b': 11, get c() { return this.a; }, set c(n) { this.a = n; }, }; var bar = {}; Lang.copyProperties(foo, bar); expect(bar.__lookupGetter__('c')).not.toBeNull(); expect(bar.__lookupSetter__('c')).not.toBeNull(); // this should return the value of 'a' expect(bar.c).toEqual(10); // this should set 'a' value bar.c = 13; expect(bar.a).toEqual(13); }); describe('bind()', function () { let o; beforeEach(function () { o = { callback() { return true; }, }; spyOn(o, 'callback').and.callThrough(); }); it('calls the bound function with the supplied this-object', function () { let callback = Lang.bind(o, o.callback); callback(); expect(o.callback.calls.mostRecent()).toEqual(jasmine.objectContaining({ object: o, args: [], returnValue: true, })); }); it('throws an error when no function supplied', function () { expect(() => Lang.bind(o, undefined)).toThrow(); }); it('throws an error when this-object undefined', function () { expect(() => Lang.bind(undefined, function () {})).toThrow(); }); it('supplies extra arguments to the function', function () { let callback = Lang.bind(o, o.callback, 42, 1138); callback(); expect(o.callback).toHaveBeenCalledWith(42, 1138); }); it('appends the extra arguments to any arguments passed', function () { let callback = Lang.bind(o, o.callback, 42, 1138); callback(1, 2, 3); expect(o.callback).toHaveBeenCalledWith(1, 2, 3, 42, 1138); }); }); }); cjs-128.1/installed-tests/js/testLegacyByteArray.js0000664000175000017500000001707615116312211021254 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC // SPDX-FileCopyrightText: 2017 Philip Chimento const ByteArray = imports.byteArray; const {GIMarshallingTests, CjsTestTools, GLib} = imports.gi; describe('Uint8Array with legacy ByteArray functions', function () { it('can be created from a string', function () { let a = ByteArray.fromString('abcd'); expect(a.length).toEqual(4); [97, 98, 99, 100].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be encoded from a string', function () { // Pick a string likely to be stored internally as Latin1 let a = ByteArray.fromString('äbcd', 'LATIN1'); expect(a.length).toEqual(4); [228, 98, 99, 100].forEach((val, ix) => expect(a[ix]).toEqual(val)); // Try again with a string not likely to be Latin1 internally a = ByteArray.fromString('⅜', 'UTF-8'); expect(a.length).toEqual(3); [0xe2, 0x85, 0x9c].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('encodes as UTF-8 by default', function () { let a = ByteArray.fromString('⅜'); expect(a.length).toEqual(3); [0xe2, 0x85, 0x9c].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be converted to a string of ASCII characters', function () { let a = new Uint8Array(4); a[0] = 97; a[1] = 98; a[2] = 99; a[3] = 100; let s = ByteArray.toString(a); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be converted to a string of UTF-8 characters even if it ends with a 0', function () { const a = Uint8Array.of(97, 98, 99, 100, 0); const s = ByteArray.toString(a); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be converted to a string of encoded characters even with a 0 byte', function () { const a = Uint8Array.of(97, 98, 99, 100, 0); const s = ByteArray.toString(a, 'LATIN1'); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('stops converting to a string at an embedded 0 byte', function () { const a = Uint8Array.of(97, 98, 0, 99, 100); const s = ByteArray.toString(a); expect(s.length).toEqual(2); expect(s).toEqual('ab'); }); it('deals gracefully with a 0-length array', function () { const a = new Uint8Array(0); expect(ByteArray.toString(a)).toEqual(''); expect(ByteArray.toGBytes(a).get_size()).toEqual(0); }); it('deals gracefully with a 0-length GLib.Bytes', function () { const noBytes = ByteArray.toGBytes(new Uint8Array(0)); expect(ByteArray.fromGBytes(noBytes).length).toEqual(0); }); it('deals gracefully with a non-aligned GBytes', function () { const unalignedBytes = CjsTestTools.new_unaligned_bytes(48); const arr = ByteArray.fromGBytes(unalignedBytes); expect(arr.length).toEqual(48); expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); }); it('deals gracefully with a GBytes in static storage', function () { const staticBytes = CjsTestTools.new_static_bytes(); const arr = ByteArray.fromGBytes(staticBytes); arr[2] = 42; expect(Array.from(arr)).toEqual([104, 101, 42, 108, 111, 0]); }); it('deals gracefully with a 0-length string', function () { expect(ByteArray.fromString('').length).toEqual(0); expect(ByteArray.fromString('', 'LATIN1').length).toEqual(0); }); it('deals gracefully with a non Uint8Array', function () { const a = [97, 98, 99, 100, 0]; expect(() => ByteArray.toString(a)).toThrow(); expect(() => ByteArray.toGBytes(a)).toThrow(); }); describe('legacy toString() behavior', function () { beforeEach(function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Some code called array.toString()*'); }); it('is preserved when created from a string', function () { let a = ByteArray.fromString('⅜'); expect(a.toString()).toEqual('⅜'); }); it('is preserved when marshalled from GI', function () { let a = GIMarshallingTests.bytearray_full_return(); expect(a.toString()).toEqual(''); }); afterEach(function () { GLib.test_assert_expected_messages_internal('Cjs', 'testByteArray.js', 0, 'testToStringCompatibility'); }); }); }); describe('Legacy byte array object', function () { it('has length 0 for empty array', function () { let a = new ByteArray.ByteArray(); expect(a.length).toEqual(0); }); describe('initially sized to 10', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(10); }); it('has length 10', function () { expect(a.length).toEqual(10); }); it('is initialized to zeroes', function () { for (let i = 0; i < a.length; ++i) expect(a[i]).toEqual(0); }); }); it('assigns values correctly', function () { let a = new ByteArray.ByteArray(256); for (let i = 0; i < a.length; ++i) a[i] = 255 - i; for (let i = 0; i < a.length; ++i) expect(a[i]).toEqual(255 - i); }); describe('assignment past end', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(); a[2] = 5; }); it('implicitly lengthens the array', function () { expect(a.length).toEqual(3); expect(a[2]).toEqual(5); }); it('implicitly creates zero bytes', function () { expect(a[0]).toEqual(0); expect(a[1]).toEqual(0); }); }); it('changes the length when assigning to length property', function () { let a = new ByteArray.ByteArray(20); expect(a.length).toEqual(20); a.length = 5; expect(a.length).toEqual(5); }); describe('conversions', function () { let a; beforeEach(function () { a = new ByteArray.ByteArray(); a[0] = 255; }); it('gives a byte 5 when assigning 5', function () { a[0] = 5; expect(a[0]).toEqual(5); }); it('gives a byte 0 when assigning null', function () { a[0] = null; expect(a[0]).toEqual(0); }); it('gives a byte 0 when assigning undefined', function () { a[0] = undefined; expect(a[0]).toEqual(0); }); it('rounds off when assigning a double', function () { a[0] = 3.14; expect(a[0]).toEqual(3); }); }); it('can be created from an array', function () { let a = ByteArray.fromArray([1, 2, 3, 4]); expect(a.length).toEqual(4); [1, 2, 3, 4].forEach((val, ix) => expect(a[ix]).toEqual(val)); }); it('can be converted to a string of ASCII characters', function () { let a = new ByteArray.ByteArray(4); a[0] = 97; a[1] = 98; a[2] = 99; a[3] = 100; let s = a.toString(); expect(s.length).toEqual(4); expect(s).toEqual('abcd'); }); it('can be passed in with transfer none', function () { const refByteArray = ByteArray.fromArray([0, 49, 0xFF, 51]); expect(() => GIMarshallingTests.bytearray_none_in(refByteArray)).not.toThrow(); }); }); cjs-128.1/installed-tests/js/testLegacyClass.js0000664000175000017500000005557015116312211020420 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Jasper St. Pierre // SPDX-FileCopyrightText: 2011 Giovanni Campagna // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. const Lang = imports.lang; const NormalClass = new Lang.Class({ Name: 'NormalClass', _init() { this.one = 1; }, }); let Subclassed = []; const MetaClass = new Lang.Class({ Name: 'MetaClass', Extends: Lang.Class, _init(params) { Subclassed.push(params.Name); this.parent(params); if (params.Extended) { this.prototype.dynamic_method = this.wrapFunction('dynamic_method', function () { return 73; }); this.DYNAMIC_CONSTANT = 2; } }, }); const CustomMetaOne = new MetaClass({ Name: 'CustomMetaOne', Extends: NormalClass, Extended: false, _init() { this.parent(); this.two = 2; }, }); const CustomMetaTwo = new MetaClass({ Name: 'CustomMetaTwo', Extends: NormalClass, Extended: true, _init() { this.parent(); this.two = 2; }, }); // This should inherit CustomMeta, even though // we use Lang.Class const CustomMetaSubclass = new Lang.Class({ Name: 'CustomMetaSubclass', Extends: CustomMetaOne, Extended: true, _init() { this.parent(); this.three = 3; }, }); describe('A metaclass', function () { it('has its constructor called each time a class is created with it', function () { expect(Subclassed).toEqual(['CustomMetaOne', 'CustomMetaTwo', 'CustomMetaSubclass']); }); it('is an instance of Lang.Class', function () { expect(NormalClass instanceof Lang.Class).toBeTruthy(); expect(MetaClass instanceof Lang.Class).toBeTruthy(); }); it('produces instances that are instances of itself and Lang.Class', function () { expect(CustomMetaOne instanceof Lang.Class).toBeTruthy(); expect(CustomMetaOne instanceof MetaClass).toBeTruthy(); }); it('can dynamically define properties in its constructor', function () { expect(CustomMetaTwo.DYNAMIC_CONSTANT).toEqual(2); expect(CustomMetaOne.DYNAMIC_CONSTANT).not.toBeDefined(); }); describe('instance', function () { let instanceOne, instanceTwo; beforeEach(function () { instanceOne = new CustomMetaOne(); instanceTwo = new CustomMetaTwo(); }); it('gets all the properties from its class and metaclass', function () { expect(instanceOne).toEqual(jasmine.objectContaining({one: 1, two: 2})); expect(instanceTwo).toEqual(jasmine.objectContaining({one: 1, two: 2})); }); it('gets dynamically defined properties from metaclass', function () { expect(() => instanceOne.dynamic_method()).toThrow(); expect(instanceTwo.dynamic_method()).toEqual(73); }); }); it('can be instantiated with Lang.Class but still get the appropriate metaclass', function () { expect(CustomMetaSubclass instanceof MetaClass).toBeTruthy(); expect(CustomMetaSubclass.DYNAMIC_CONSTANT).toEqual(2); let instance = new CustomMetaSubclass(); expect(instance).toEqual(jasmine.objectContaining({one: 1, two: 2, three: 3})); expect(instance.dynamic_method()).toEqual(73); }); it('can be detected with Lang.getMetaClass', function () { expect(Lang.getMetaClass({ Extends: CustomMetaOne, })).toBe(MetaClass); }); }); const MagicBase = new Lang.Class({ Name: 'MagicBase', _init(a, buffer) { if (buffer) buffer.push(a); this.a = a; }, foo(a, buffer) { buffer.push(a); return a * 3; }, bar(a) { return a * 5; }, }); const Magic = new Lang.Class({ Name: 'Magic', Extends: MagicBase, _init(a, b, buffer) { this.parent(a, buffer); if (buffer) buffer.push(b); this.b = b; }, foo(a, b, buffer) { let val = this.parent(a, buffer); buffer.push(b); return val * 2; }, bar(a, buffer) { this.foo(a, 2 * a, buffer); return this.parent(a); }, }); const Accessor = new Lang.Class({ Name: 'AccessorMagic', _init(val) { this._val = val; }, get value() { return this._val; }, set value(val) { if (val !== 42) throw TypeError('Value is not a magic number'); this._val = val; }, }); const AbstractBase = new Lang.Class({ Name: 'AbstractBase', Abstract: true, _init() { this.foo = 42; }, }); describe('Class framework', function () { it('calls _init constructors', function () { let newMagic = new MagicBase('A'); expect(newMagic.a).toEqual('A'); }); it('calls parent constructors', function () { let buffer = []; let newMagic = new Magic('a', 'b', buffer); expect(buffer).toEqual(['a', 'b']); buffer = []; let val = newMagic.foo(10, 20, buffer); expect(buffer).toEqual([10, 20]); expect(val).toEqual(10 * 6); }); it('sets the right constructor properties', function () { expect(Magic.prototype.constructor).toBe(Magic); let newMagic = new Magic(); expect(newMagic.constructor).toBe(Magic); }); it('sets up instanceof correctly', function () { let newMagic = new Magic(); expect(newMagic instanceof Magic).toBeTruthy(); expect(newMagic instanceof MagicBase).toBeTruthy(); }); it('has a name', function () { expect(Magic.name).toEqual('Magic'); }); it('reports a sensible value for toString()', function () { let newMagic = new MagicBase(); expect(newMagic.toString()).toEqual('[object MagicBase]'); }); it('allows overriding toString()', function () { const ToStringOverride = new Lang.Class({ Name: 'ToStringOverride', toString() { let oldToString = this.parent(); return `${oldToString}; hello`; }, }); let override = new ToStringOverride(); expect(override.toString()).toEqual('[object ToStringOverride]; hello'); }); it('is not configurable', function () { let newMagic = new MagicBase(); delete newMagic.foo; expect(newMagic.foo).toBeDefined(); }); it('allows accessors for properties', function () { let newAccessor = new Accessor(11); expect(newAccessor.value).toEqual(11); expect(() => (newAccessor.value = 12)).toThrow(); newAccessor.value = 42; expect(newAccessor.value).toEqual(42); }); it('raises an exception when creating an abstract class', function () { expect(() => new AbstractBase()).toThrow(); }); it('inherits properties from abstract base classes', function () { const AbstractImpl = new Lang.Class({ Name: 'AbstractImpl', Extends: AbstractBase, _init() { this.parent(); this.bar = 42; }, }); let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); expect(newAbstract.bar).toEqual(42); }); it('inherits constructors from abstract base classes', function () { const AbstractImpl = new Lang.Class({ Name: 'AbstractImpl', Extends: AbstractBase, }); let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); }); it('allows ES6 classes to inherit from abstract base classes', function () { class AbstractImpl extends AbstractBase {} let newAbstract = new AbstractImpl(); expect(newAbstract.foo).toEqual(42); }); it('lets methods call other methods without clobbering __caller__', function () { let newMagic = new Magic(); let buffer = []; let res = newMagic.bar(10, buffer); expect(buffer).toEqual([10, 20]); expect(res).toEqual(50); }); it('allows custom return values from constructors', function () { const CustomConstruct = new Lang.Class({ Name: 'CustomConstruct', _construct(one, two) { return [one, two]; }, }); let instance = new CustomConstruct(1, 2); expect(Array.isArray(instance)).toBeTruthy(); expect(instance instanceof CustomConstruct).toBeFalsy(); expect(instance).toEqual([1, 2]); }); it('allows symbol-named methods', function () { const SymbolClass = new Lang.Class({ Name: 'SymbolClass', *[Symbol.iterator]() { yield* [1, 2, 3]; }, }); let instance = new SymbolClass(); expect([...instance]).toEqual([1, 2, 3]); }); }); const AnInterface = new Lang.Interface({ Name: 'AnInterface', required: Lang.Interface.UNIMPLEMENTED, optional() { return 'AnInterface.optional()'; }, optionalGeneric() { return 'AnInterface.optionalGeneric()'; }, argumentGeneric(arg) { return `AnInterface.argumentGeneric(${arg})`; }, usesThis() { return this._interfacePrivateMethod(); }, _interfacePrivateMethod() { return 'interface private method'; }, get some_prop() { return 'AnInterface.some_prop getter'; }, set some_prop(value) { this.some_prop_setter_called = true; }, }); const InterfaceRequiringOtherInterface = new Lang.Interface({ Name: 'InterfaceRequiringOtherInterface', Requires: [AnInterface], optional(...args) { return `InterfaceRequiringOtherInterface.optional()\n${ AnInterface.prototype.optional.apply(this, args)}`; }, optionalGeneric() { return `InterfaceRequiringOtherInterface.optionalGeneric()\n${ AnInterface.optionalGeneric(this)}`; }, }); const ObjectImplementingAnInterface = new Lang.Class({ Name: 'ObjectImplementingAnInterface', Implements: [AnInterface], _init() { this.parent(); }, required() {}, optional(...args) { return AnInterface.prototype.optional.apply(this, args); }, optionalGeneric() { return AnInterface.optionalGeneric(this); }, argumentGeneric(arg) { return AnInterface.argumentGeneric(this, `${arg} (hello from class)`); }, }); const InterfaceRequiringClassAndInterface = new Lang.Interface({ Name: 'InterfaceRequiringClassAndInterface', Requires: [ObjectImplementingAnInterface, InterfaceRequiringOtherInterface], }); const MinimalImplementationOfAnInterface = new Lang.Class({ Name: 'MinimalImplementationOfAnInterface', Implements: [AnInterface], required() {}, }); const ImplementationOfTwoInterfaces = new Lang.Class({ Name: 'ImplementationOfTwoInterfaces', Implements: [AnInterface, InterfaceRequiringOtherInterface], required() {}, optional(...args) { return InterfaceRequiringOtherInterface.prototype.optional.apply(this, args); }, optionalGeneric() { return InterfaceRequiringOtherInterface.optionalGeneric(this); }, }); describe('An interface', function () { it('is an instance of Lang.Interface', function () { expect(AnInterface instanceof Lang.Interface).toBeTruthy(); expect(InterfaceRequiringOtherInterface instanceof Lang.Interface).toBeTruthy(); }); it('has a name', function () { expect(AnInterface.name).toEqual('AnInterface'); }); it('cannot be instantiated', function () { expect(() => new AnInterface()).toThrow(); }); it('can be implemented by a class', function () { let obj; expect(() => { obj = new ObjectImplementingAnInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it("can be implemented by a class's superclass", function () { const ChildWhoseParentImplementsAnInterface = new Lang.Class({ Name: 'ChildWhoseParentImplementsAnInterface', Extends: ObjectImplementingAnInterface, }); let obj = new ChildWhoseParentImplementsAnInterface(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it("doesn't disturb a class's constructor", function () { let obj = new ObjectImplementingAnInterface(); expect(obj.constructor).toEqual(ObjectImplementingAnInterface); }); it('can have its required method implemented', function () { let implementer = new ObjectImplementingAnInterface(); expect(() => implementer.required()).not.toThrow(); }); it('must have a name', function () { expect(() => new Lang.Interface({ required: Lang.Interface.UNIMPLEMENTED, })).toThrow(); }); it('must have its required methods implemented', function () { expect(() => new Lang.Class({ Name: 'MyBadObject', Implements: [AnInterface], })).toThrow(); }); it('does not have to have its optional methods implemented', function () { let obj; expect(() => (obj = new MinimalImplementationOfAnInterface())).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('can have its optional method deferred to by the implementation', function () { let obj = new MinimalImplementationOfAnInterface(); expect(obj.optional()).toEqual('AnInterface.optional()'); }); it('can be chained up to by a class', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.optional()).toEqual('AnInterface.optional()'); }); it('can include arguments when being chained up to by a class', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.argumentGeneric('arg')) .toEqual('AnInterface.argumentGeneric(arg (hello from class))'); }); it('can have its property getter deferred to', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.some_prop).toEqual('AnInterface.some_prop getter'); }); it('can have its property setter deferred to', function () { let obj = new ObjectImplementingAnInterface(); obj.some_prop = 'foobar'; expect(obj.some_prop_setter_called).toBeTruthy(); }); it('can have its property getter overridden', function () { const ObjectWithGetter = new Lang.Class({ Name: 'ObjectWithGetter', Implements: [AnInterface], required() {}, get some_prop() { return 'ObjectWithGetter.some_prop getter'; }, }); let obj = new ObjectWithGetter(); expect(obj.some_prop).toEqual('ObjectWithGetter.some_prop getter'); }); it('can have its property setter overridden', function () { const ObjectWithSetter = new Lang.Class({ Name: 'ObjectWithSetter', Implements: [AnInterface], required() {}, set some_prop(value) { /* setter without getter */// jshint ignore:line this.overridden_some_prop_setter_called = true; }, }); let obj = new ObjectWithSetter(); obj.some_prop = 'foobar'; expect(obj.overridden_some_prop_setter_called).toBeTruthy(); expect(obj.some_prop_setter_called).not.toBeDefined(); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); }); it('can have empty requires', function () { expect(() => new Lang.Interface({ Name: 'InterfaceWithEmptyRequires', Requires: [], })).not.toThrow(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optional()) .toEqual('InterfaceRequiringOtherInterface.optional()\nAnInterface.optional()'); }); it('can be chained up to with a generic', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.optionalGeneric()).toEqual('AnInterface.optionalGeneric()'); }); it('can chain up to another interface with a generic', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalGeneric()) .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()'); }); it('has its optional function defer to that of the last interface', function () { const MinimalImplementationOfTwoInterfaces = new Lang.Class({ Name: 'MinimalImplementationOfTwoInterfaces', Implements: [AnInterface, InterfaceRequiringOtherInterface], required() {}, }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalGeneric()) .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()'); }); it('must have all its required interfaces implemented', function () { expect(() => new Lang.Class({ Name: 'ObjectWithNotEnoughInterfaces', Implements: [InterfaceRequiringOtherInterface], required() {}, })).toThrow(); }); it('must have all its required interfaces implemented in the correct order', function () { expect(() => new Lang.Class({ Name: 'ObjectWithInterfacesInWrongOrder', Implements: [InterfaceRequiringOtherInterface, AnInterface], required() {}, })).toThrow(); }); it('can have its implementation on a parent class', function () { let obj; expect(() => { const ObjectInheritingFromInterfaceImplementation = new Lang.Class({ Name: 'ObjectInheritingFromInterfaceImplementation', Extends: ObjectImplementingAnInterface, Implements: [InterfaceRequiringOtherInterface], }); obj = new ObjectInheritingFromInterfaceImplementation(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); }); it('can require its implementor to be a subclass of some class', function () { let obj; expect(() => { const ObjectImplementingInterfaceRequiringParentObject = new Lang.Class({ Name: 'ObjectImplementingInterfaceRequiringParentObject', Extends: ObjectImplementingAnInterface, Implements: [InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface], }); obj = new ObjectImplementingInterfaceRequiringParentObject(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringClassAndInterface)).toBeTruthy(); }); it('must be implemented by an object which subclasses the required class', function () { expect(() => new Lang.Class({ Name: 'ObjectWithoutRequiredParent', Implements: [AnInterface, InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface], required() {}, })).toThrow(); }); it('can have methods that call others of its methods', function () { let obj = new ObjectImplementingAnInterface(); expect(obj.usesThis()).toEqual('interface private method'); }); it('is implemented by a subclass of a class that implements it', function () { const SubObject = new Lang.Class({ Name: 'SubObject', Extends: ObjectImplementingAnInterface, }); let obj = new SubObject(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('can be reimplemented by a subclass of a class that implements it', function () { const SubImplementer = new Lang.Class({ Name: 'SubImplementer', Extends: ObjectImplementingAnInterface, Implements: [AnInterface], }); let obj = new SubImplementer(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(() => obj.required()).not.toThrow(); }); it('tells what it is with toString()', function () { expect(AnInterface.toString()).toEqual('[interface Interface for AnInterface]'); }); }); describe('ES6 class inheriting from Lang.Class', function () { let Shiny, Legacy; beforeEach(function () { Legacy = new Lang.Class({ Name: 'Legacy', _init(someval) { this.constructorCalledWith = someval; }, instanceMethod() {}, chainUpToMe() {}, overrideMe() {}, get property() { return this._property + 1; }, set property(value) { this._property = value - 2; }, }); Legacy.staticMethod = function () {}; spyOn(Legacy, 'staticMethod'); spyOn(Legacy.prototype, 'instanceMethod'); spyOn(Legacy.prototype, 'chainUpToMe'); spyOn(Legacy.prototype, 'overrideMe'); Shiny = class extends Legacy { chainUpToMe() { super.chainUpToMe(); } overrideMe() {} }; }); it('calls a static method on the parent class', function () { Shiny.staticMethod(); expect(Legacy.staticMethod).toHaveBeenCalled(); }); it('calls a method on the parent class', function () { let instance = new Shiny(); instance.instanceMethod(); expect(Legacy.prototype.instanceMethod).toHaveBeenCalled(); }); it("passes arguments to the parent class's constructor", function () { let instance = new Shiny(42); expect(instance.constructorCalledWith).toEqual(42); }); it('chains up to a method on the parent class', function () { let instance = new Shiny(); instance.chainUpToMe(); expect(Legacy.prototype.chainUpToMe).toHaveBeenCalled(); }); it('overrides a method on the parent class', function () { let instance = new Shiny(); instance.overrideMe(); expect(Legacy.prototype.overrideMe).not.toHaveBeenCalled(); }); it('sets and gets a property from the parent class', function () { let instance = new Shiny(); instance.property = 42; expect(instance.property).toEqual(41); }); }); cjs-128.1/installed-tests/js/testLegacyGObject.js0000664000175000017500000007332615116312211020667 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. imports.gi.versions.Gtk = '3.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Mainloop = imports.mainloop; const MyObject = new GObject.Class({ Name: 'MyObject', Properties: { 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite', 'A read write parameter', GObject.ParamFlags.READWRITE, ''), 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly', 'A readonly parameter', GObject.ParamFlags.READABLE, ''), 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly', 'A readwrite construct-only parameter', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 'default'), }, Signals: { 'empty': { }, 'minimal': {param_types: [GObject.TYPE_INT, GObject.TYPE_INT]}, 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS, return_type: GObject.TYPE_INT, param_types: [], }, 'run-last': {flags: GObject.SignalFlags.RUN_LAST}, 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [GObject.TYPE_STRING], }, }, _init(props) { // check that it's safe to set properties before // chaining up (priv is NULL at this point, remember) this._readwrite = 'foo'; this._readonly = 'bar'; this._constructProp = null; this._constructCalled = false; this.parent(props); }, get readwrite() { return this._readwrite; }, set readwrite(val) { if (val === 'ignore') return; this._readwrite = val; }, get readonly() { return this._readonly; }, set readonly(val) { // this should never be called this._readonly = 'bogus'; }, get construct() { return this._constructProp; }, set construct(val) { this._constructProp = val; }, notify_prop() { this._readonly = 'changed'; this.notify('readonly'); }, emit_empty() { this.emit('empty'); }, emit_minimal(one, two) { this.emit('minimal', one, two); }, emit_full() { return this.emit('full'); }, emit_detailed() { this.emit('detailed::one'); this.emit('detailed::two'); }, emit_run_last(callback) { this._run_last_callback = callback; this.emit('run-last'); }, on_run_last() { this._run_last_callback(); }, on_empty() { this.empty_called = true; }, on_full() { this.full_default_handler_called = true; return 79; }, }); const MyApplication = new Lang.Class({ Name: 'MyApplication', Extends: Gio.Application, Signals: {'custom': {param_types: [GObject.TYPE_INT]}}, _init(params) { this.parent(params); }, emit_custom(n) { this.emit('custom', n); }, }); const MyInitable = new Lang.Class({ Name: 'MyInitable', Extends: GObject.Object, Implements: [Gio.Initable], _init(params) { this.parent(params); this.inited = false; }, vfunc_init(cancellable) { // error? if (!(cancellable instanceof Gio.Cancellable)) throw new Error('Bad argument'); this.inited = true; }, }); const Derived = new Lang.Class({ Name: 'Derived', Extends: MyObject, _init() { this.parent({readwrite: 'yes'}); }, }); const OddlyNamed = new Lang.Class({ Name: 'Legacy.OddlyNamed', Extends: MyObject, }); const MyCustomInit = new Lang.Class({ Name: 'MyCustomInit', Extends: GObject.Object, _init() { this.foo = false; this.parent(); }, _instance_init() { this.foo = true; }, }); describe('GObject class', function () { let myInstance; beforeEach(function () { myInstance = new MyObject(); }); it('constructs with default values for properties', function () { expect(myInstance.readwrite).toEqual('foo'); expect(myInstance.readonly).toEqual('bar'); expect(myInstance.construct).toEqual('default'); }); it('constructs with a hash of property values', function () { let myInstance2 = new MyObject({readwrite: 'baz', construct: 'asdf'}); expect(myInstance2.readwrite).toEqual('baz'); expect(myInstance2.readonly).toEqual('bar'); expect(myInstance2.construct).toEqual('asdf'); }); const ui = ` baz quz `; it('constructs with property values from Gtk.Builder', function () { let builder = Gtk.Builder.new_from_string(ui, -1); let myInstance3 = builder.get_object('MyObject'); expect(myInstance3.readwrite).toEqual('baz'); expect(myInstance3.readonly).toEqual('bar'); expect(myInstance3.construct).toEqual('quz'); }); it('does not allow changing CONSTRUCT_ONLY properties', function () { myInstance.construct = 'val'; expect(myInstance.construct).toEqual('default'); }); it('has a name', function () { expect(MyObject.name).toEqual('MyObject'); }); // the following would (should) cause a CRITICAL: // myInstance.readonly = 'val'; it('has a notify signal', function () { let notifySpy = jasmine.createSpy('notifySpy'); myInstance.connect('notify::readonly', notifySpy); myInstance.notify_prop(); myInstance.notify_prop(); expect(notifySpy).toHaveBeenCalledTimes(2); }); it('can define its own signals', function () { let emptySpy = jasmine.createSpy('emptySpy'); myInstance.connect('empty', emptySpy); myInstance.emit_empty(); expect(emptySpy).toHaveBeenCalled(); expect(myInstance.empty_called).toBeTruthy(); }); it('passes emitted arguments to signal handlers', function () { let minimalSpy = jasmine.createSpy('minimalSpy'); myInstance.connect('minimal', minimalSpy); myInstance.emit_minimal(7, 5); expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5); }); it('can return values from signals', function () { let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42); myInstance.connect('full', fullSpy); let result = myInstance.emit_full(); expect(fullSpy).toHaveBeenCalled(); expect(result).toEqual(42); }); it('does not call first-wins signal handlers after one returns a value', function () { let neverCalledSpy = jasmine.createSpy('neverCalledSpy'); myInstance.connect('full', () => 42); myInstance.connect('full', neverCalledSpy); myInstance.emit_full(); expect(neverCalledSpy).not.toHaveBeenCalled(); expect(myInstance.full_default_handler_called).toBeFalsy(); }); it('gets the return value of the default handler', function () { let result = myInstance.emit_full(); expect(myInstance.full_default_handler_called).toBeTruthy(); expect(result).toEqual(79); }); it('calls run-last default handler last', function () { let stack = []; let runLastSpy = jasmine.createSpy('runLastSpy') .and.callFake(() => { stack.push(1); }); myInstance.connect('run-last', runLastSpy); myInstance.emit_run_last(() => { stack.push(2); }); expect(stack).toEqual([1, 2]); }); it("can inherit from something that's not GObject.Object", function () { // ...and still get all the goodies of GObject.Class let instance = new MyApplication({application_id: 'org.gjs.Application'}); let customSpy = jasmine.createSpy('customSpy'); instance.connect('custom', customSpy); instance.emit_custom(73); expect(customSpy).toHaveBeenCalledWith(instance, 73); }); it('can implement an interface', function () { let instance = new MyInitable(); expect(instance.constructor.implements(Gio.Initable)).toBeTruthy(); }); it('can implement interface vfuncs', function () { let instance = new MyInitable(); expect(instance.inited).toBeFalsy(); instance.init(new Gio.Cancellable()); expect(instance.inited).toBeTruthy(); }); it('can be a subclass', function () { let derived = new Derived(); expect(derived instanceof Derived).toBeTruthy(); expect(derived instanceof MyObject).toBeTruthy(); expect(derived.readwrite).toEqual('yes'); }); it('can have any valid Lang.Class name', function () { let obj = new OddlyNamed(); expect(obj instanceof OddlyNamed).toBeTruthy(); expect(obj instanceof MyObject).toBeTruthy(); }); it('calls its _instance_init() function while chaining up in constructor', function () { let instance = new MyCustomInit(); expect(instance.foo).toBeTruthy(); }); it('can have an interface-valued property', function () { const InterfacePropObject = new Lang.Class({ Name: 'InterfacePropObject', Extends: GObject.Object, Properties: { 'file': GObject.ParamSpec.object('file', 'File', 'File', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, Gio.File.$gtype), }, }); let file = Gio.File.new_for_path('dummy'); expect(() => new InterfacePropObject({file})).not.toThrow(); }); it('can override a property from the parent class', function () { const OverrideObject = new Lang.Class({ Name: 'OverrideObject', Extends: MyObject, Properties: { 'readwrite': GObject.ParamSpec.override('readwrite', MyObject), }, get readwrite() { return this._subclass_readwrite; }, set readwrite(val) { this._subclass_readwrite = `subclass${val}`; }, }); let obj = new OverrideObject(); obj.readwrite = 'foo'; expect(obj.readwrite).toEqual('subclassfoo'); }); it('cannot override a non-existent property', function () { expect(() => new Lang.Class({ Name: 'BadOverride', Extends: GObject.Object, Properties: { 'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object), }, })).toThrow(); }); it('handles gracefully forgetting to override a C property', function () { GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "*Object class Gjs_ForgottenOverride doesn't implement property " + "'anchors' from interface 'GTlsFileDatabase'*"); // This is a random interface in Gio with a read-write property const ForgottenOverride = new Lang.Class({ Name: 'ForgottenOverride', Extends: Gio.TlsDatabase, Implements: [Gio.TlsFileDatabase], }); const obj = new ForgottenOverride(); expect(obj.anchors).not.toBeDefined(); expect(() => (obj.anchors = 'foo')).not.toThrow(); expect(obj.anchors).toEqual('foo'); GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectClass.js', 0, 'testGObjectClassForgottenOverride'); }); it('handles gracefully overriding a C property but forgetting the accessors', function () { // This is a random interface in Gio with a read-write property const ForgottenAccessors = new Lang.Class({ Name: 'ForgottenAccessors', Extends: Gio.TlsDatabase, Implements: [Gio.TlsFileDatabase], Properties: { 'anchors': GObject.ParamSpec.override('anchors', Gio.TlsFileDatabase), }, }); const obj = new ForgottenAccessors(); expect(obj.anchors).toBeNull(); obj.anchors = 'foo'; const ForgottenAccessors2 = new Lang.Class({ Name: 'ForgottenAccessors2', Extends: ForgottenAccessors, }); const obj2 = new ForgottenAccessors2(); expect(obj2.anchors).toBeNull(); obj2.anchors = 'foo'; }); }); const AnInterface = new Lang.Interface({ Name: 'AnInterface', }); const GObjectImplementingLangInterface = new Lang.Class({ Name: 'GObjectImplementingLangInterface', Extends: GObject.Object, Implements: [AnInterface], }); const AGObjectInterface = new Lang.Interface({ Name: 'AGObjectInterface', GTypeName: 'ArbitraryGTypeName', Requires: [GObject.Object], Properties: { 'interface-prop': GObject.ParamSpec.string('interface-prop', 'Interface property', 'Must be overridden in implementation', GObject.ParamFlags.READABLE, 'foobar'), }, Signals: { 'interface-signal': {}, }, requiredG: Lang.Interface.UNIMPLEMENTED, optionalG() { return 'AGObjectInterface.optionalG()'; }, }); const InterfaceRequiringGObjectInterface = new Lang.Interface({ Name: 'InterfaceRequiringGObjectInterface', Requires: [AGObjectInterface], optionalG() { return `InterfaceRequiringGObjectInterface.optionalG()\n${ AGObjectInterface.optionalG(this)}`; }, }); const GObjectImplementingGObjectInterface = new Lang.Class({ Name: 'GObjectImplementingGObjectInterface', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), 'class-prop': GObject.ParamSpec.string('class-prop', 'Class property', 'A property that is not on the interface', GObject.ParamFlags.READABLE, 'meh'), }, Signals: { 'class-signal': {}, }, get interface_prop() { return 'foobar'; }, get class_prop() { return 'meh'; }, requiredG() {}, optionalG() { return AGObjectInterface.optionalG(this); }, }); const MinimalImplementationOfAGObjectInterface = new Lang.Class({ Name: 'MinimalImplementationOfAGObjectInterface', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, }); const ImplementationOfTwoInterfaces = new Lang.Class({ Name: 'ImplementationOfTwoInterfaces', Extends: GObject.Object, Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, optionalG() { return InterfaceRequiringGObjectInterface.optionalG(this); }, }); describe('GObject interface', function () { it('class can implement a Lang.Interface', function () { let obj; expect(() => { obj = new GObjectImplementingLangInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); }); it('throws when an interface requires a GObject interface but not GObject.Object', function () { expect(() => new Lang.Interface({ Name: 'GObjectInterfaceNotRequiringGObject', GTypeName: 'GTypeNameNotRequiringGObject', Requires: [Gio.Initable], })).toThrow(); }); it('can be implemented by a GObject class along with a JS interface', function () { const ObjectImplementingLangInterfaceAndCInterface = new Lang.Class({ Name: 'ObjectImplementingLangInterfaceAndCInterface', Extends: GObject.Object, Implements: [AnInterface, Gio.Initable], }); let obj; expect(() => { obj = new ObjectImplementingLangInterfaceAndCInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(Gio.Initable)).toBeTruthy(); }); it('is an instance of the interface classes', function () { expect(AGObjectInterface instanceof Lang.Interface).toBeTruthy(); expect(AGObjectInterface instanceof GObject.Interface).toBeTruthy(); }); it('cannot be instantiated', function () { expect(() => new AGObjectInterface()).toThrow(); }); it('has a name', function () { expect(AGObjectInterface.name).toEqual('AGObjectInterface'); }); it('reports its type name', function () { expect(AGObjectInterface.$gtype.name).toEqual('ArbitraryGTypeName'); }); it('can be implemented by a GObject class', function () { let obj; expect(() => { obj = new GObjectImplementingGObjectInterface(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('is implemented by a GObject class with the correct class object', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.constructor).toEqual(GObjectImplementingGObjectInterface); expect(obj.constructor.name) .toEqual('GObjectImplementingGObjectInterface'); }); it('can be implemented by a class also implementing a Lang.Interface', function () { const GObjectImplementingBothKindsOfInterface = new Lang.Class({ Name: 'GObjectImplementingBothKindsOfInterface', Extends: GObject.Object, Implements: [AnInterface, AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, required() {}, requiredG() {}, }); let obj; expect(() => { obj = new GObjectImplementingBothKindsOfInterface(); }).not.toThrow(); expect(obj.constructor.implements(AnInterface)).toBeTruthy(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('can have its required function implemented', function () { expect(() => { let obj = new GObjectImplementingGObjectInterface(); obj.requiredG(); }).not.toThrow(); }); it('must have its required function implemented', function () { expect(() => new Lang.Class({ Name: 'BadObject', Extends: GObject.Object, Implements: [AGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, })).toThrow(); }); it("doesn't have to have its optional function implemented", function () { let obj; expect(() => { obj = new MinimalImplementationOfAGObjectInterface(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); }); it('can have its optional function deferred to by the implementation', function () { let obj = new MinimalImplementationOfAGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can have its function chained up to', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()'); }); it('can require another interface', function () { let obj; expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.constructor.implements(InterfaceRequiringGObjectInterface)) .toBeTruthy(); }); it('can chain up to another interface', function () { let obj = new ImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it("defers to the last interface's optional function", function () { const MinimalImplementationOfTwoInterfaces = new Lang.Class({ Name: 'MinimalImplementationOfTwoInterfaces', Extends: GObject.Object, Implements: [AGObjectInterface, InterfaceRequiringGObjectInterface], Properties: { 'interface-prop': GObject.ParamSpec.override('interface-prop', AGObjectInterface), }, requiredG() {}, }); let obj = new MinimalImplementationOfTwoInterfaces(); expect(obj.optionalG()) .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()'); }); it('must be implemented by a class that implements all required interfaces', function () { expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InterfaceRequiringGObjectInterface], required() {}, })).toThrow(); }); it('must be implemented by a class that implements required interfaces in correct order', function () { expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InterfaceRequiringGObjectInterface, AGObjectInterface], required() {}, })).toThrow(); }); it('can require an interface from C', function () { const InitableInterface = new Lang.Interface({ Name: 'InitableInterface', Requires: [GObject.Object, Gio.Initable], }); expect(() => new Lang.Class({ Name: 'BadObject', Implements: [InitableInterface], })).toThrow(); }); it('can define signals on the implementing class', function () { function quitLoop() { Mainloop.quit('signal'); } let obj = new GObjectImplementingGObjectInterface(); let interfaceSignalSpy = jasmine.createSpy('interfaceSignalSpy') .and.callFake(quitLoop); let classSignalSpy = jasmine.createSpy('classSignalSpy') .and.callFake(quitLoop); obj.connect('interface-signal', interfaceSignalSpy); obj.connect('class-signal', classSignalSpy); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('interface-signal'); return GLib.SOURCE_REMOVE; }); Mainloop.run('signal'); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { obj.emit('class-signal'); return GLib.SOURCE_REMOVE; }); Mainloop.run('signal'); expect(interfaceSignalSpy).toHaveBeenCalled(); expect(classSignalSpy).toHaveBeenCalled(); }); it('can define properties on the implementing class', function () { let obj = new GObjectImplementingGObjectInterface(); expect(obj.interface_prop).toEqual('foobar'); expect(obj.class_prop).toEqual('meh'); }); it('must have its properties overridden', function () { // Failing to override an interface property doesn't raise an error but // instead logs a critical warning. GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL, "Object class * doesn't implement property 'interface-prop' from " + "interface 'ArbitraryGTypeName'"); new Lang.Class({ Name: 'MyNaughtyObject', Extends: GObject.Object, Implements: [AGObjectInterface], requiredG() {}, }); // g_test_assert_expected_messages() is a macro, not introspectable GLib.test_assert_expected_messages_internal('Cjs', 'testGObjectInterface.js', 416, 'testGObjectMustOverrideInterfaceProperties'); }); // This makes sure that we catch the case where the metaclass (e.g. // GtkWidgetClass) doesn't specify a meta-interface. In that case we get the // meta-interface from the metaclass's parent. it('gets the correct type for its metaclass', function () { const MyMeta = new Lang.Class({ Name: 'MyMeta', Extends: GObject.Class, }); const MyMetaObject = new MyMeta({ Name: 'MyMetaObject', }); const MyMetaInterface = new Lang.Interface({ Name: 'MyMetaInterface', Requires: [MyMetaObject], }); expect(MyMetaInterface instanceof GObject.Interface).toBeTruthy(); }); it('can be implemented by a class as well as its parent class', function () { const SubObject = new Lang.Class({ Name: 'SubObject', Extends: GObjectImplementingGObjectInterface, }); let obj = new SubObject(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); it('can be reimplemented by a subclass of a class that already implements it', function () { const SubImplementer = new Lang.Class({ Name: 'SubImplementer', Extends: GObjectImplementingGObjectInterface, Implements: [AGObjectInterface], }); let obj = new SubImplementer(); expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy(); expect(obj.interface_prop).toEqual('foobar'); // override not needed }); }); const LegacyInterface1 = new Lang.Interface({ Name: 'LegacyInterface1', Requires: [GObject.Object], Signals: {'legacy-iface1-signal': {}}, }); const LegacyInterface2 = new Lang.Interface({ Name: 'LegacyInterface2', Requires: [GObject.Object], Signals: {'legacy-iface2-signal': {}}, }); const Legacy = new Lang.Class({ Name: 'Legacy', Extends: GObject.Object, Implements: [LegacyInterface1], Properties: { 'property': GObject.ParamSpec.int('property', 'Property', 'A magic property', GObject.ParamFlags.READWRITE, 0, 100, 0), 'override-property': GObject.ParamSpec.int('override-property', 'Override property', 'Another magic property', GObject.ParamFlags.READWRITE, 0, 100, 0), }, Signals: { 'signal': {}, }, _init(someval) { this.constructorCalledWith = someval; this.parent(); }, instanceMethod() {}, chainUpToMe() {}, overrideMe() {}, get property() { return this._property + 1; }, set property(value) { this._property = value - 2; }, get overrideProperty() { return this._overrideProperty + 1; }, set overrideProperty(value) { this._overrideProperty = value - 2; }, }); Legacy.staticMethod = function () {}; const Shiny = GObject.registerClass({ Implements: [LegacyInterface2], Properties: { 'override-property': GObject.ParamSpec.override('override-property', Legacy), }, }, class Shiny extends Legacy { chainUpToMe() { super.chainUpToMe(); } overrideMe() {} get overrideProperty() { return this._overrideProperty + 2; } set overrideProperty(value) { this._overrideProperty = value - 1; } }); describe('ES6 GObject class inheriting from GObject.Class', function () { let instance; beforeEach(function () { spyOn(Legacy, 'staticMethod'); spyOn(Legacy.prototype, 'instanceMethod'); spyOn(Legacy.prototype, 'chainUpToMe'); spyOn(Legacy.prototype, 'overrideMe'); instance = new Shiny(); }); it('calls a static method on the parent class', function () { Shiny.staticMethod(); expect(Legacy.staticMethod).toHaveBeenCalled(); }); it('calls a method on the parent class', function () { instance.instanceMethod(); expect(Legacy.prototype.instanceMethod).toHaveBeenCalled(); }); it("passes arguments to the parent class's constructor", function () { instance = new Shiny(42); expect(instance.constructorCalledWith).toEqual(42); }); it('chains up to a method on the parent class', function () { instance.chainUpToMe(); expect(Legacy.prototype.chainUpToMe).toHaveBeenCalled(); }); it('overrides a method on the parent class', function () { instance.overrideMe(); expect(Legacy.prototype.overrideMe).not.toHaveBeenCalled(); }); it('sets and gets a property from the parent class', function () { instance.property = 42; expect(instance.property).toEqual(41); }); it('overrides a property from the parent class', function () { instance.overrideProperty = 42; expect(instance.overrideProperty).toEqual(43); }); it('inherits a signal from the parent class', function () { let signalSpy = jasmine.createSpy('signalSpy'); expect(() => { instance.connect('signal', signalSpy); instance.emit('signal'); }).not.toThrow(); expect(signalSpy).toHaveBeenCalled(); }); it('inherits legacy interfaces from the parent', function () { expect(() => instance.emit('legacy-iface1-signal')).not.toThrow(); expect(instance instanceof LegacyInterface1).toBeTruthy(); }); it('can implement a legacy interface itself', function () { expect(() => instance.emit('legacy-iface2-signal')).not.toThrow(); expect(instance instanceof LegacyInterface2).toBeTruthy(); }); }); cjs-128.1/installed-tests/js/testLegacyGtk.js0000664000175000017500000001156115116312211020070 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- /* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna imports.gi.versions.Gtk = '3.0'; const {GLib, Gtk} = imports.gi; const Lang = imports.lang; const System = imports.system; const template = ` `; const MyComplexGtkSubclass = new Lang.Class({ Name: 'MyComplexGtkSubclass', Extends: Gtk.Grid, Template: new TextEncoder().encode(template), Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], CssName: 'complex-subclass', testChildrenExist() { this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); expect(this._internalLabel).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }, }); const MyComplexGtkSubclassFromResource = new Lang.Class({ Name: 'MyComplexGtkSubclassFromResource', Extends: Gtk.Grid, Template: 'resource:///org/cjs/jsunit/complex3.ui', Children: ['label-child', 'label-child2'], InternalChildren: ['internal-label-child'], testChildrenExist() { expect(this.label_child).toEqual(jasmine.anything()); expect(this.label_child2).toEqual(jasmine.anything()); expect(this._internal_label_child).toEqual(jasmine.anything()); }, templateCallback() {}, boundCallback() {}, }); function validateTemplate(description, ClassName) { describe(description, function () { let win, content; beforeEach(function () { win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); content = new ClassName(); win.add(content); }); it('sets up internal and public template children', function () { content.testChildrenExist(); }); it('sets up public template children with the correct widgets', function () { expect(content.label_child.get_label()).toEqual('Complex!'); expect(content.label_child2.get_label()).toEqual('Complex as well!'); }); it('sets up internal template children with the correct widgets', function () { expect(content._internal_label_child.get_label()) .toEqual('Complex and internal!'); }); afterEach(function () { win.destroy(); }); }); } describe('Legacy Gtk overrides', function () { beforeAll(function () { Gtk.init(null); }); validateTemplate('UI template', MyComplexGtkSubclass); validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); it('sets CSS names on classes', function () { expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); }); function asyncIdle() { return new Promise(resolve => { GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { resolve(); return GLib.SOURCE_REMOVE; }); }); } it('does not leak instance when connecting template signal', async function () { const LeakTestWidget = new Lang.Class({ Name: 'LeakTestWidget', Extends: Gtk.Button, Template: new TextEncoder().encode(` `), buttonClicked() {}, }); const weakRef = new WeakRef(new LeakTestWidget()); await asyncIdle(); // It takes two GC cycles to free the widget, because of the tardy sweep // problem (https://gitlab.gnome.org/GNOME/gjs/-/issues/217) System.gc(); System.gc(); expect(weakRef.deref()).toBeUndefined(); }); }); cjs-128.1/installed-tests/js/testMainloop.js0000664000175000017500000000641115116312211017772 0ustar fabiofabio/* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC const Mainloop = imports.mainloop; describe('Mainloop.timeout_add()', function () { let runTenTimes, runOnlyOnce, neverRun, neverRunSource; beforeAll(function (done) { let count = 0; runTenTimes = jasmine.createSpy('runTenTimes').and.callFake(() => { if (count === 10) { done(); return false; } count += 1; return true; }); runOnlyOnce = jasmine.createSpy('runOnlyOnce').and.returnValue(false); neverRun = jasmine.createSpy('neverRun').and.throwError(); Mainloop.timeout_add(10, runTenTimes); Mainloop.timeout_add(10, runOnlyOnce); neverRunSource = Mainloop.timeout_add(90000, neverRun); }); it('runs a timeout function', function () { expect(runOnlyOnce).toHaveBeenCalledTimes(1); }); it('runs a timeout function until it returns false', function () { expect(runTenTimes).toHaveBeenCalledTimes(11); }); it('runs a timeout function after an initial timeout', function () { expect(neverRun).not.toHaveBeenCalled(); }); afterAll(function () { Mainloop.source_remove(neverRunSource); }); }); describe('Mainloop.idle_add()', function () { let runOnce, runTwice, neverRuns, quitAfterManyRuns; beforeAll(function (done) { runOnce = jasmine.createSpy('runOnce').and.returnValue(false); runTwice = jasmine.createSpy('runTwice').and.returnValues([true, false]); neverRuns = jasmine.createSpy('neverRuns').and.throwError(); let count = 0; quitAfterManyRuns = jasmine.createSpy('quitAfterManyRuns').and.callFake(() => { count += 1; if (count > 10) { done(); return false; } return true; }); Mainloop.idle_add(runOnce); Mainloop.idle_add(runTwice); let neverRunsId = Mainloop.idle_add(neverRuns); Mainloop.idle_add(quitAfterManyRuns); Mainloop.source_remove(neverRunsId); }); it('runs an idle function', function () { expect(runOnce).toHaveBeenCalledTimes(1); }); it('continues to run idle functions that return true', function () { expect(runTwice).toHaveBeenCalledTimes(2); expect(quitAfterManyRuns).toHaveBeenCalledTimes(11); }); it('does not run idle functions if removed', function () { expect(neverRuns).not.toHaveBeenCalled(); }); it('can remove idle functions while they are being invoked', function (done) { let removeId = Mainloop.idle_add(() => { Mainloop.source_remove(removeId); done(); return false; }); }); // Add an idle before exit, then never run main loop again. // This is to test that we remove idle callbacks when the associated // JSContext is blown away. The leak check in minijasmine will // fail if the idle function is not garbage collected. it('does not leak idle callbacks', function () { Mainloop.idle_add(() => { fail('This should never have been called'); return true; }); }); }); cjs-128.1/installed-tests/js/testNamespace.js0000664000175000017500000000044415116312211020110 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. const Regress = imports.gi.Regress; describe('GI repository namespace', function () { it('supplies a name', function () { expect(Regress.__name__).toEqual('Regress'); }); }); cjs-128.1/installed-tests/js/testPackage.js0000664000175000017500000000605115116312211017547 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Red Hat, Inc. const Pkg = imports.package; describe('Package module', function () { it('finds an existing library', function () { expect(Pkg.checkSymbol('Regress', '1.0')).toEqual(true); }); it('doesn\'t find a non-existent library', function () { expect(Pkg.checkSymbol('Rägräss', '1.0')).toEqual(false); }); it('finds a function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'get_variant')).toEqual(true); }); it('doesn\'t find a non-existent function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'get_väriänt')).toEqual(false); }); it('finds a class', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj')).toEqual(true); }); it('doesn\'t find a non-existent class', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestNoObj')).toEqual(false); }); it('finds a property', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bare')).toEqual(true); }); it('doesn\'t find a non-existent property', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.bäre')).toEqual(false); }); it('finds a static function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.static_method')).toEqual(true); }); it('doesn\'t find a non-existent static function', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.stätic_methöd')).toEqual(false); }); it('finds a method', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.null_out')).toEqual(true); }); it('doesn\'t find a non-existent method', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestObj.nüll_out')).toEqual(false); }); it('finds an interface', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface')).toEqual(true); }); it('doesn\'t find a non-existent interface', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interfäce')).toEqual(false); }); it('finds an interface method', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int8_in')).toEqual(true); }); it('doesn\'t find a non-existent interface method', function () { expect(Pkg.checkSymbol('GIMarshallingTests', '1.0', 'Interface.test_int42_in')).toEqual(false); }); it('finds an enum value', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.VALUE1')).toEqual(true); }); it('doesn\'t find a non-existent enum value', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'TestEnum.value1')).toEqual(false); }); it('finds a constant', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'BOOL_CONSTANT')).toEqual(true); }); it('doesn\'t find a non-existent constant', function () { expect(Pkg.checkSymbol('Regress', '1.0', 'BööL_CONSTANT')).toEqual(false); }); }); cjs-128.1/installed-tests/js/testParamSpec.js0000664000175000017500000000407315116312211020071 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Red Hat, Inc. const Regress = imports.gi.Regress; const GObject = imports.gi.GObject; let name = 'foo-property'; let nick = 'Foo property'; let blurb = 'This is the foo property'; let flags = GObject.ParamFlags.READABLE; function testParamSpec(type, params, defaultValue) { describe(`GObject.ParamSpec.${type}`, function () { let paramSpec; beforeEach(function () { paramSpec = GObject.ParamSpec[type](name, nick, blurb, flags, ...params); }); it('has the correct name strings', function () { expect(paramSpec.name).toEqual(name); expect(paramSpec._nick).toEqual(nick); expect(paramSpec._blurb).toEqual(blurb); }); it('has the correct flags', function () { expect(paramSpec.flags).toEqual(flags); }); it('has the correct default value', function () { expect(paramSpec.default_value).toEqual(defaultValue); }); }); } testParamSpec('string', ['Default Value'], 'Default Value'); testParamSpec('int', [-100, 100, -42], -42); testParamSpec('uint', [20, 100, 42], 42); testParamSpec('int64', [0x4000, 0xffffffff, 0x2266bbff], 0x2266bbff); testParamSpec('uint64', [0, 0xffffffff, 0x2266bbff], 0x2266bbff); testParamSpec('enum', [Regress.TestEnum, Regress.TestEnum.VALUE2], Regress.TestEnum.VALUE2); testParamSpec('flags', [Regress.TestFlags, Regress.TestFlags.FLAG2], Regress.TestFlags.FLAG2); testParamSpec('object', [GObject.Object], null); testParamSpec('jsobject', [], null); describe('GObject.ParamSpec object', function () { it("doesn't crash when resolving a non-string property", function () { let paramSpec = GObject.ParamSpec.string(name, nick, blurb, flags, ''); expect(paramSpec[0]).not.toBeDefined(); }); it('has correct object tag', function () { const paramSpec = GObject.ParamSpec.string(name, nick, blurb, flags, ''); expect(paramSpec.toString()).toEqual('[object GObject_ParamSpec]'); }); }); cjs-128.1/installed-tests/js/testPrint.js0000664000175000017500000001572615116312211017321 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento // SPDX-FileCopyrightText: 2022 Nasah Kuma imports.gi.versions.Gdk = '3.0'; const Gdk = imports.gi.Gdk; const ByteArray = imports.byteArray; const {getPrettyPrintFunction} = imports._print; let prettyPrint = getPrettyPrintFunction(globalThis); describe('print', function () { it('can be spied upon', function () { spyOn(globalThis, 'print'); print('foo'); expect(print).toHaveBeenCalledWith('foo'); }); }); describe('printerr', function () { it('can be spied upon', function () { spyOn(globalThis, 'printerr'); printerr('foo'); expect(printerr).toHaveBeenCalledWith('foo'); }); }); describe('log', function () { it('can be spied upon', function () { spyOn(globalThis, 'log'); log('foo'); expect(log).toHaveBeenCalledWith('foo'); }); }); describe('logError', function () { it('can be spied upon', function () { spyOn(globalThis, 'logError'); logError('foo', 'bar'); expect(logError).toHaveBeenCalledWith('foo', 'bar'); }); }); describe('prettyPrint', function () { it('property value primitive', function () { expect( prettyPrint({greeting: 'hi'}) ).toBe('{ greeting: "hi" }'); }); it('property value is object reference', function () { let obj = {a: 5}; obj.b = obj; expect( prettyPrint(obj) ).toBe('{ a: 5, b: [Circular] }'); }); it('more than one property', function () { expect( prettyPrint({a: 1, b: 2, c: 3}) ).toBe('{ a: 1, b: 2, c: 3 }'); }); it('add property value after property value object reference', function () { let obj = {a: 5}; obj.b = obj; obj.c = 4; expect( prettyPrint(obj) ).toBe('{ a: 5, b: [Circular], c: 4 }'); }); it('array', function () { expect( prettyPrint([1, 2, 3, 4, 5]) ).toBe('[1, 2, 3, 4, 5]'); }); it('property value array', function () { expect( prettyPrint({arr: [1, 2, 3, 4, 5]}) ).toBe('{ arr: [1, 2, 3, 4, 5] }'); }); it('array reference is the only array element', function () { let arr = []; arr.push(arr); expect( prettyPrint(arr) ).toBe('[[Circular]]'); }); it('array reference is one of multiple array elements', function () { let arr = []; arr.push(4); arr.push(arr); arr.push(5); expect( prettyPrint(arr) ).toBe('[4, [Circular], 5]'); }); it('nested array', function () { expect( prettyPrint([1, 2, [3, 4], 5]) ).toBe('[1, 2, [3, 4], 5]'); }); it('property value nested array', function () { expect( prettyPrint({arr: [1, 2, [3, 4], 5]}) ).toBe('{ arr: [1, 2, [3, 4], 5] }'); }); it('function', function () { expect( prettyPrint(function sum(a, b) { return a + b; }) ).toBe('[ Function: sum ]'); }); it('property value function', function () { expect( prettyPrint({ sum: function sum(a, b) { return a + b; }, }) ).toBe('{ sum: [ Function: sum ] }'); }); it('date', function () { expect( prettyPrint(new Date(Date.UTC(2018, 11, 24, 10, 33, 30))) ).toBe('2018-12-24T10:33:30.000Z'); }); it('property value date', function () { expect( prettyPrint({date: new Date(Date.UTC(2018, 11, 24, 10, 33, 30))}) ).toBe('{ date: 2018-12-24T10:33:30.000Z }'); }); it('toString is overridden on object', function () { expect( prettyPrint(new Gdk.Rectangle()) ).toMatch(/\[boxed instance wrapper GIName:.*\]/); }); it('string tag supplied', function () { expect( prettyPrint(Gdk) ).toMatch('[object GIRepositoryNamespace]'); }); it('symbol', function () { expect(prettyPrint(Symbol('foo'))).toEqual('Symbol("foo")'); }); it('property key symbol', function () { expect(prettyPrint({[Symbol('foo')]: 'symbol'})) .toEqual('{ [Symbol("foo")]: "symbol" }'); }); it('property value symbol', function () { expect(prettyPrint({symbol: Symbol('foo')})) .toEqual('{ symbol: Symbol("foo") }'); }); it('registered symbol', function () { expect(prettyPrint(Symbol.for('foo'))).toEqual('Symbol.for("foo")'); }); it('property key registered symbol', function () { expect(prettyPrint({[Symbol.for('foo')]: 'symbol'})) .toEqual('{ [Symbol.for("foo")]: "symbol" }'); }); it('property value registered symbol', function () { expect(prettyPrint({symbol: Symbol.for('foo')})) .toEqual('{ symbol: Symbol.for("foo") }'); }); it('well-known symbol', function () { expect(prettyPrint(Symbol.hasInstance)).toEqual('Symbol.hasInstance'); }); it('property key well-known symbol', function () { expect(prettyPrint({[Symbol.iterator]: 'symbol'})) .toEqual('{ [Symbol.iterator]: "symbol" }'); }); it('property value well-known symbol', function () { expect(prettyPrint({symbol: Symbol.hasInstance})) .toEqual('{ symbol: Symbol.hasInstance }'); }); it('undefined', function () { expect(prettyPrint(undefined)).toEqual('undefined'); }); it('null', function () { expect(prettyPrint(null)).toEqual('null'); }); it('nested null', function () { expect(prettyPrint({'foo': null})).toEqual('{ foo: null }'); }); it('imports root in object', function () { expect(prettyPrint({'foo': imports})) .toEqual('{ foo: [GjsFileImporter root] }'); }); describe('TypedArrays', () => { [ Int8Array, Uint8Array, Uint16Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, ].forEach(constructor => { it(constructor.name, function () { const arr = new constructor([1, 2, 3]); expect(prettyPrint(arr)) .toEqual('[1, 2, 3]'); }); }); [BigInt64Array, BigUint64Array].forEach(constructor => { it(constructor.name, function () { const arr = new constructor([1n, 2n, 3n]); expect(prettyPrint(arr)) .toEqual('[1, 2, 3]'); }); }); }); it('Uint8Array returned from introspected function', function () { let a = ByteArray.fromString('⅜'); expect(prettyPrint(a)).toEqual('[226, 133, 156]'); }); }); cjs-128.1/installed-tests/js/testPromise.js0000664000175000017500000000616615116312211017641 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileContributor: Authored by: Marco Trevisan // SPDX-FileCopyrightText: 2022 Canonical, Ltd. const {GLib} = imports.gi; class SubPromise extends Promise { constructor(executor) { super((resolve, reject) => { executor(resolve, reject); }); } } const PromiseResult = { RESOLVED: 0, FAILED: 1, }; describe('Promise', function () { let loop; beforeEach(function () { loop = GLib.MainLoop.new(null, false); }); function executePromise(promise) { const promiseResult = {}; promise.then(value => { promiseResult.result = PromiseResult.RESOLVED; promiseResult.value = value; }).catch(e => { promiseResult.result = PromiseResult.FAILED; promiseResult.error = e; }); while (promiseResult.result === undefined) loop.get_context().iteration(true); return promiseResult; } it('waits for all promises before handling unhandled, when handled', function () { let error; let resolved; const promise = async () => { await new SubPromise(resolve => resolve('success')); const rejecting = new SubPromise((_resolve, reject) => reject('got error')); try { await rejecting; } catch (e) { error = e; } finally { resolved = true; } expect(resolved).toBeTrue(); expect(error).toBe('got error'); return 'parent-returned'; }; expect(executePromise(promise())).toEqual({ result: PromiseResult.RESOLVED, value: 'parent-returned', }); }); it('waits for all promises before handling unhandled, when unhandled', function () { const thenHandler = jasmine.createSpy('thenHandler'); const promise = async () => { await new Promise(resolve => resolve('success')); await new Promise((_resolve, reject) => reject(new Error('got error'))); return 'parent-returned'; }; promise().then(thenHandler).catch(); expect(thenHandler).not.toHaveBeenCalled(); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => loop.quit()); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Unhandled promise rejection.*'); loop.run(); GLib.test_assert_expected_messages_internal('Cjs', 'testPromise.js', 0, 'warnsIfRejected'); }); it('do not lead to high-priority IDLE starvation', function () { const promise = new Promise(resolve => { const id = GLib.idle_add(GLib.PRIORITY_HIGH, () => { resolve(); return GLib.SOURCE_REMOVE; }); GLib.Source.set_name_by_id(id, `Test Idle source ${id}`); }); expect(executePromise(promise)).toEqual({ result: PromiseResult.RESOLVED, value: undefined, }); }); }); cjs-128.1/installed-tests/js/testRegress.js0000664000175000017500000023631315116312211017634 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2008 Red Hat, Inc. const Regress = imports.gi.Regress; // We use Gio to have some objects that we know exist imports.gi.versions.Gtk = '3.0'; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; function expectWarn64(callable) { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); const ret = callable(); GLib.test_assert_expected_messages_internal('Cjs', 'testRegress.js', 0, 'Ignore message'); return ret; } const bit64Types = ['uint64', 'int64']; if (GLib.SIZEOF_LONG === 8) bit64Types.push('long', 'ulong'); if (GLib.SIZEOF_SIZE_T === 8) bit64Types.push('size'); if (GLib.SIZEOF_SSIZE_T === 8) bit64Types.push('ssize'); describe('Life, the Universe and Everything', function () { it('includes null return value', function () { expect(Regress.test_return_allow_none()).toBeNull(); expect(Regress.test_return_nullable()).toBeNull(); }); it('includes booleans', function () { expect(Regress.test_boolean(false)).toBe(false); expect(Regress.test_boolean(true)).toBe(true); expect(Regress.test_boolean_true(true)).toBe(true); expect(Regress.test_boolean_false(false)).toBe(false); }); [8, 16, 32, 64].forEach(bits => { it(`includes ${bits}-bit integers`, function () { const method = `test_int${bits}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](-42)).toBe(-42); expect(Regress[method](undefined)).toBe(0); expect(Regress[method](42.42)).toBe(42); expect(Regress[method](-42.42)).toBe(-42); if (bits >= 64) { expect(Regress[method](42n)).toBe(42); expect(Regress[method](-42n)).toBe(-42); } else { expect(() => Regress[method](42n)).toThrow(); expect(() => Regress[method](-42n)).toThrow(); } }); it(`includes unsigned ${bits}-bit integers`, function () { const method = `test_uint${bits}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](undefined)).toBe(0); expect(Regress[method](42.42)).toBe(42); if (bits >= 64) expect(Regress[method](42n)).toEqual(42); else expect(() => Regress[method](42n)).toThrow(); }); }); ['short', 'int', 'long', 'ssize', 'float', 'double'].forEach(type => { it(`includes ${type}s`, function () { const method = `test_${type}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](-42)).toBe(-42); if (['float', 'double'].includes(type)) { expect(Regress[method](undefined)).toBeNaN(); expect(Regress[method](42.42)).toBeCloseTo(42.42); expect(Regress[method](-42.42)).toBeCloseTo(-42.42); } else { expect(Regress[method](undefined)).toBe(0); expect(Regress[method](42.42)).toBe(42); expect(Regress[method](-42.42)).toBe(-42); } if (bit64Types.includes(type)) { expect(Regress[method](42n)).toBe(42); expect(Regress[method](-42n)).toBe(-42); } else { expect(() => Regress[method](42n)).toThrow(); expect(() => Regress[method](-42n)).toThrow(); } }); }); ['ushort', 'uint', 'ulong', 'size'].forEach(type => { it(`includes ${type}s`, function () { const method = `test_${type}`; expect(Regress[method](42)).toBe(42); expect(Regress[method](undefined)).toBe(0); expect(Regress[method](42.42)).toBe(42); if (bit64Types.includes(type)) expect(Regress[method](42n)).toBe(42); else expect(() => Regress[method](42n)).toThrow(); }); }); describe('No implicit conversion to unsigned', function () { ['uint8', 'uint16', 'uint32', 'uint64', 'uint', 'size'].forEach(type => { it(`for ${type}`, function () { expect(() => Regress[`test_${type}`](-42)).toThrowError(/out of range/); if (bit64Types.includes(type)) expect(() => Regress[`test_${type}`](-42n)).toThrowError(/out of range/); else expect(() => Regress[`test_${type}`](-42n)).toThrow(); }); }); }); describe('Infinity and NaN', function () { ['int8', 'int16', 'int32', 'int64', 'short', 'int', 'long', 'ssize'].forEach(type => { it(`converts to 0 for ${type}`, function () { expect(Regress[`test_${type}`](Infinity)).toBe(0); expect(Regress[`test_${type}`](-Infinity)).toBe(0); expect(Regress[`test_${type}`](NaN)).toBe(0); }); }); ['uint8', 'uint16', 'uint32', 'uint64', 'ushort', 'uint', 'ulong', 'size'].forEach(type => { it(`converts to 0 for ${type}`, function () { expect(Regress[`test_${type}`](Infinity)).toBe(0); expect(Regress[`test_${type}`](NaN)).toBe(0); }); }); ['float', 'double'].forEach(type => { it(`not for ${type}`, function () { expect(Regress[`test_${type}`](Infinity)).toBe(Infinity); expect(Regress[`test_${type}`](-Infinity)).toBe(-Infinity); expect(Regress[`test_${type}`](NaN)).toBeNaN(); }); }); }); describe('(u)int64 numeric values', function () { const minInt64 = -(2n ** 63n); const maxInt64 = 2n ** 63n - 1n; const maxUint64 = 2n ** 64n - 1n; ['uint64', 'int64', 'long', 'ulong', 'size', 'ssize'].forEach(type => { if (!bit64Types.includes(type)) return; const signed = ['int64', 'long', 'ssize'].includes(type); const limits = { min: signed ? minInt64 : 0n, max: signed ? maxInt64 : maxUint64, }; const testFunc = Regress[`test_${type}`]; it(`can use numeric limits for ${type}`, function () { expect(expectWarn64(() => testFunc(limits.max))) .toEqual(Number(limits.max)); if (signed) { expect(expectWarn64(() => testFunc(limits.min))) .toEqual(Number(limits.min)); } }); }); }); it('includes wide characters', function () { expect(Regress.test_unichar('c')).toBe('c'); expect(Regress.test_unichar('')).toBe(''); expect(Regress.test_unichar('\u2665')).toBe('\u2665'); }); it('includes time_t', function () { const now = Math.floor(new Date().getTime() / 1000); const bounced = Math.floor(Regress.test_timet(now)); expect(bounced).toEqual(now); }); it('includes GTypes', function () { expect(Regress.test_gtype(GObject.TYPE_NONE)).toBe(GObject.TYPE_NONE); expect(Regress.test_gtype(String)).toBe(GObject.TYPE_STRING); expect(Regress.test_gtype(GObject.Object)).toBe(GObject.Object.$gtype); }); it('closures', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); expect(Regress.test_closure(callback)).toEqual(42); expect(callback).toHaveBeenCalledWith(); }); it('closures with one argument', function () { const callback = jasmine.createSpy('callback') .and.callFake(someValue => someValue); expect(Regress.test_closure_one_arg(callback, 42)).toEqual(42); expect(callback).toHaveBeenCalledWith(42); }); it('closure with GLib.Variant argument', function () { const callback = jasmine.createSpy('callback') .and.returnValue(new GLib.Variant('s', 'hello')); const variant = new GLib.Variant('i', 42); expect(Regress.test_closure_variant(callback, variant).deepUnpack()) .toEqual('hello'); expect(callback).toHaveBeenCalledWith(variant); }); describe('GValue marshalling', function () { it('integer in', function () { expect(Regress.test_int_value_arg(42)).toEqual(42); }); it('integer out', function () { expect(Regress.test_value_return(42)).toEqual(42); }); }); // See testCairo.js for the following tests, since that will be skipped if // we are building without Cairo support: // Regress.test_cairo_context_full_return() // Regress.test_cairo_context_none_in() // Regress.test_cairo_surface_none_return() // Regress.test_cairo_surface_full_return() // Regress.test_cairo_surface_none_in() // Regress.test_cairo_surface_full_out() // Regress.TestObj.emit_sig_with_foreign_struct() it('integer GLib.Variant', function () { const ivar = Regress.test_gvariant_i(); expect(ivar.get_type_string()).toEqual('i'); expect(ivar.unpack()).toEqual(1); }); it('string GLib.Variant', function () { const svar = Regress.test_gvariant_s(); expect(String.fromCharCode(svar.classify())).toEqual('s'); expect(svar.unpack()).toEqual('one'); }); it('dictionary GLib.Variant', function () { const asvvar = Regress.test_gvariant_asv(); expect(asvvar.recursiveUnpack()).toEqual({name: 'foo', timeout: 10}); }); it('variant GLib.Variant', function () { const vvar = Regress.test_gvariant_v(); expect(vvar.unpack()).toEqual(jasmine.any(GLib.Variant)); expect(vvar.recursiveUnpack()).toEqual('contents'); }); it('string array GLib.Variant', function () { const asvar = Regress.test_gvariant_as(); expect(asvar.deepUnpack()).toEqual(['one', 'two', 'three']); }); describe('UTF-8 strings', function () { const CONST_STR = 'const ♥ utf8'; const NONCONST_STR = 'nonconst ♥ utf8'; it('as return types', function () { expect(Regress.test_utf8_const_return()).toEqual(CONST_STR); expect(Regress.test_utf8_nonconst_return()).toEqual(NONCONST_STR); }); it('as in parameters', function () { Regress.test_utf8_const_in(CONST_STR); }); it('as out parameters', function () { expect(Regress.test_utf8_out()).toEqual(NONCONST_STR); }); xit('as in-out parameters', function () { expect(Regress.test_utf8_inout(CONST_STR)).toEqual(NONCONST_STR); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192'); }); it('return values in filename encoding', function () { const filenames = Regress.test_filename_return(); expect(filenames).toEqual(['\u00e5\u00e4\u00f6', '/etc/fstab']); }); describe('Various configurations of arguments', function () { it('in after out', function () { const str = 'hello'; const len = Regress.test_int_out_utf8(str); expect(len).toEqual(str.length); }); it('multiple number args', function () { const [times2, times3] = Regress.test_multi_double_args(2.5); expect(times2).toEqual(5); expect(times3).toEqual(7.5); }); it('multiple string out parameters', function () { const [first, second] = Regress.test_utf8_out_out(); expect(first).toEqual('first'); expect(second).toEqual('second'); }); it('strings as return value and output parameter', function () { const [first, second] = Regress.test_utf8_out_nonconst_return(); expect(first).toEqual('first'); expect(second).toEqual('second'); }); it('nullable string in parameter', function () { expect(() => Regress.test_utf8_null_in(null)).not.toThrow(); }); it('nullable string out parameter', function () { expect(Regress.test_utf8_null_out()).toBeNull(); }); }); ['int', 'gint8', 'gint16', 'gint32', 'gint64'].forEach(inttype => { it(`arrays of ${inttype} in`, function () { expect(Regress[`test_array_${inttype}_in`]([1, 2, 3, 4])).toEqual(10); }); }); it('implicit conversions from strings to int arrays', function () { expect(Regress.test_array_gint8_in('\x01\x02\x03\x04')).toEqual(10); expect(Regress.test_array_gint16_in('\x01\x02\x03\x04')).toEqual(10); expect(Regress.test_array_gint16_in('\u0100\u0200\u0300\u0400')).toEqual(2560); }); it('out arrays of integers', function () { expect(Regress.test_array_int_out()).toEqual([0, 1, 2, 3, 4]); }); xit('inout arrays of integers', function () { expect(Regress.test_array_int_inout([0, 1, 2, 3, 4])).toEqual([2, 3, 4, 5]); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192'); describe('String arrays', function () { it('marshalling in', function () { expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy(); expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy(); // Ensure that primitives throw without SEGFAULT expect(() => Regress.test_strv_in(1)).toThrow(); expect(() => Regress.test_strv_in('')).toThrow(); expect(() => Regress.test_strv_in(false)).toThrow(); // Second two are deliberately not strings expect(() => Regress.test_strv_in(['1', 2, 3])).toThrow(); }); it('marshalling out', function () { expect(Regress.test_strv_out()) .toEqual(['thanks', 'for', 'all', 'the', 'fish']); }); it('marshalling return value with container transfer', function () { expect(Regress.test_strv_out_container()).toEqual(['1', '2', '3']); }); it('marshalling out parameter with container transfer', function () { expect(Regress.test_strv_outarg()).toEqual(['1', '2', '3']); }); }); it('GType arrays', function () { expect(Regress.test_array_gtype_in([Gio.SimpleAction, Gio.Icon, GObject.TYPE_BOXED])) .toEqual('[GSimpleAction,GIcon,GBoxed,]'); expect(() => Regress.test_array_gtype_in(42)).toThrow(); expect(() => Regress.test_array_gtype_in([undefined])).toThrow(); // 80 is G_TYPE_OBJECT, but we don't want it to work expect(() => Regress.test_array_gtype_in([80])).toThrow(); }); describe('Fixed arrays of integers', function () { it('marshals as an in parameter', function () { expect(Regress.test_array_fixed_size_int_in([1, 2, 3, 4])).toEqual(10); }); it('marshals as an out parameter', function () { expect(Regress.test_array_fixed_size_int_out()).toEqual([0, 1, 2, 3, 4]); }); it('marshals as a return value', function () { expect(Regress.test_array_fixed_size_int_return()).toEqual([0, 1, 2, 3, 4]); }); }); it('integer array with static length', function () { const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; expect(() => Regress.test_array_static_in_int(arr)).not.toThrow(); }); it("string array that's const in C", function () { expect(Regress.test_strv_out_c()).toEqual(['thanks', 'for', 'all', 'the', 'fish']); }); describe('arrays of integers with length parameter', function () { it('marshals as a return value with transfer full', function () { expect(Regress.test_array_int_full_out()).toEqual([0, 1, 2, 3, 4]); }); it('marshals as a return value with transfer none', function () { expect(Regress.test_array_int_none_out()).toEqual([1, 2, 3, 4, 5]); }); it('marshalls as a nullable in parameter', function () { expect(() => Regress.test_array_int_null_in(null)).not.toThrow(); }); it('marshals as a nullable return value', function () { expect(Regress.test_array_int_null_out()).toEqual([]); }); }); ['glist', 'gslist'].forEach(list => { describe(`${list} types`, function () { const STR_LIST = ['1', '2', '3']; it('return with transfer-none', function () { expect(Regress[`test_${list}_nothing_return`]()).toEqual(STR_LIST); expect(Regress[`test_${list}_nothing_return2`]()).toEqual(STR_LIST); }); it('return with transfer-container', function () { expect(Regress[`test_${list}_container_return`]()).toEqual(STR_LIST); }); it('return with transfer-full', function () { expect(Regress[`test_${list}_everything_return`]()).toEqual(STR_LIST); }); it('in with transfer-none', function () { Regress[`test_${list}_nothing_in`](STR_LIST); Regress[`test_${list}_nothing_in2`](STR_LIST); }); it('nullable in', function () { expect(() => Regress[`test_${list}_null_in`]([])).not.toThrow(); }); it('nullable out', function () { expect(Regress[`test_${list}_null_out`]()).toEqual([]); }); xit('in with transfer-container', function () { Regress[`test_${list}_container_in`](STR_LIST); }).pend('Function not added to gobject-introspection test suite yet'); }); }); it('GList of GTypes in with transfer container', function () { expect(() => Regress.test_glist_gtype_container_in([Regress.TestObj, Regress.TestSubObj])) .not.toThrow(); }); describe('GHash type', function () { const EXPECTED_HASH = {baz: 'bat', foo: 'bar', qux: 'quux'}; it('null GHash out', function () { expect(Regress.test_ghash_null_return()).toBeNull(); }); it('out GHash', function () { expect(Regress.test_ghash_nothing_return()).toEqual(EXPECTED_HASH); expect(Regress.test_ghash_nothing_return2()).toEqual(EXPECTED_HASH); }); const GVALUE_HASH_TABLE = { 'integer': 12, 'boolean': true, 'string': 'some text', 'strings': ['first', 'second', 'third'], 'flags': Regress.TestFlags.FLAG1 | Regress.TestFlags.FLAG3, 'enum': Regress.TestEnum.VALUE2, }; it('with GValue value type out', function () { expect(Regress.test_ghash_gvalue_return()).toEqual(GVALUE_HASH_TABLE); }); xit('with GValue value type in', function () { expect(() => Regress.test_ghash_gvalue_in(GVALUE_HASH_TABLE)).not.toThrow(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272'); it('marshals as a return value with transfer container', function () { expect(Regress.test_ghash_container_return()).toEqual(EXPECTED_HASH); }); it('marshals as a return value with transfer full', function () { expect(Regress.test_ghash_everything_return()).toEqual(EXPECTED_HASH); }); it('null GHash in', function () { Regress.test_ghash_null_in(null); }); it('null GHashTable out', function () { expect(Regress.test_ghash_null_out()).toBeNull(); }); it('in GHash', function () { Regress.test_ghash_nothing_in(EXPECTED_HASH); Regress.test_ghash_nothing_in2(EXPECTED_HASH); }); it('nested GHash', function () { const EXPECTED_NESTED_HASH = {wibble: EXPECTED_HASH}; expect(Regress.test_ghash_nested_everything_return()) .toEqual(EXPECTED_NESTED_HASH); expect(Regress.test_ghash_nested_everything_return2()) .toEqual(EXPECTED_NESTED_HASH); }); }); describe('GArray', function () { it('marshals as a return value with transfer container', function () { expect(Regress.test_garray_container_return()).toEqual(['regress']); }); it('marshals as a return value with transfer full', function () { expect(Regress.test_garray_full_return()).toEqual(['regress']); }); }); it('enum that references its own members has correct values', function () { expect(Regress.TestReferenceEnum.ZERO).toEqual(4); expect(Regress.TestReferenceEnum.ONE).toEqual(2); expect(Regress.TestReferenceEnum.TWO).toEqual(54); expect(Regress.TestReferenceEnum.THREE).toEqual(4); expect(Regress.TestReferenceEnum.FOUR).toEqual(216); expect(Regress.TestReferenceEnum.FIVE).toEqual(-217); }); it('unregistered enum works', function () { expect(Regress.TestEnumNoGEnum.EVALUE1).toEqual(0); expect(Regress.TestEnumNoGEnum.EVALUE2).toEqual(42); expect(Regress.TestEnumNoGEnum.EVALUE3).toEqual('0'.charCodeAt()); }); it('value is not added to enum with #define', function () { expect(Regress.TestEnumNoGEnum.EVALUE_DEPRECATED).not.toBeDefined(); }); it('enum parameter', function () { expect(Regress.test_enum_param(Regress.TestEnum.VALUE1)).toEqual('value1'); expect(Regress.test_enum_param(Regress.TestEnum.VALUE3)).toEqual('value3'); }); it('unsigned enum parameter', function () { expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE1)) .toEqual('value1'); expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE2)) .toEqual('value2'); }); it('flags parameter', function () { expect(Regress.global_get_flags_out()).toEqual(Regress.TestFlags.FLAG1 | Regress.TestFlags.FLAG3); }); describe('Simple introspected struct', function () { let struct; beforeEach(function () { struct = new Regress.TestStructA(); struct.some_int = 42; struct.some_int8 = 43; struct.some_double = 42.5; struct.some_enum = Regress.TestEnum.VALUE3; }); it('sets fields correctly', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can clone', function () { const b = struct.clone(); expect(b.some_int).toEqual(42); expect(b.some_int8).toEqual(43); expect(b.some_double).toEqual(42.5); expect(b.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can be modified by a method', function () { const c = Regress.TestStructA.parse('foobar'); expect(c.some_int).toEqual(23); }); describe('constructors', function () { beforeEach(function () { struct = new Regress.TestStructA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); }); it('"copies" an object from a hash of field values', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('catches bad field names', function () { expect(() => new Regress.TestStructA({junk: 42})).toThrow(); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestStructA(struct); expect(copy.some_int).toEqual(42); expect(copy.some_int8).toEqual(43); expect(copy.some_double).toEqual(42.5); expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3); }); }); }); it('out arrays of structs', function () { const array = Regress.test_array_struct_out(); const ints = array.map(struct => struct.some_int); expect(ints).toEqual([22, 33, 44]); }); describe('Introspected nested struct', function () { let struct; beforeEach(function () { struct = new Regress.TestStructB(); struct.some_int8 = 43; struct.nested_a.some_int8 = 66; }); it('sets fields correctly', function () { expect(struct.some_int8).toEqual(43); expect(struct.nested_a.some_int8).toEqual(66); }); it('can clone', function () { const b = struct.clone(); expect(b.some_int8).toEqual(43); expect(b.nested_a.some_int8).toEqual(66); }); }); // Bare GObject pointer, not currently supported (and possibly not ever) xdescribe('Struct with non-basic member', function () { it('sets fields correctly', function () { const struct = new Regress.TestStructC(); struct.another_int = 43; struct.obj = new GObject.Object(); expect(struct.another_int).toEqual(43); expect(struct.obj).toEqual(jasmine.any(GObject.Object)); }); }); describe('Struct with annotated fields', function () { xit('sets fields correctly', function () { const testObjList = [new Regress.TestObj(), new Regress.TestObj()]; const testStructList = [new Regress.TestStructA(), new Regress.TestStructA()]; const struct = new Regress.TestStructD(); struct.array1 = testStructList; struct.array2 = testObjList; struct.field = testObjList[0]; struct.list = testObjList; struct.garray = testObjList; expect(struct.array1).toEqual(testStructList); expect(struct.array2).toEqual(testObjList); expect(struct.field).toEqual(testObjList[0]); expect(struct.list).toEqual(testObjList); expect(struct.garray).toEqual(testObjList); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/83'); }); describe('Struct with array of anonymous unions', function () { xit('sets fields correctly', function () { const struct = new Regress.TestStructE(); struct.some_type = GObject.Object.$gtype; for (let ix = 0; ix < 1; ix++) { struct.some_union[ix].v_int = 42; struct.some_union[ix].v_uint = 43; struct.some_union[ix].v_long = 44; struct.some_union[ix].v_ulong = 45; struct.some_union[ix].v_int64 = 46; struct.some_union[ix].v_uint64 = 47; struct.some_union[ix].v_float = 48.5; struct.some_union[ix].v_double = 49.5; struct.some_union[ix].v_pointer = null; } expect(struct.some_type).toEqual(GObject.Object.$gtype); for (let ix = 0; ix < 1; ix++) { expect(struct.some_union[ix].v_int).toEqual(42); expect(struct.some_union[ix].v_uint).toEqual(43); expect(struct.some_union[ix].v_long).toEqual(44); expect(struct.some_union[ix].v_ulong).toEqual(45); expect(struct.some_union[ix].v_int64).toEqual(46); expect(struct.some_union[ix].v_uint64).toEqual(47); expect(struct.some_union[ix].v_float).toEqual(48.5); expect(struct.some_union[ix].v_double).toEqual(49.5); expect(struct.some_union[ix].v_pointer).toBeNull(); } }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/273'); }); // Bare int pointers, not currently supported (and possibly not ever) xdescribe('Struct with const/volatile members', function () { it('sets fields correctly', function () { const struct = new Regress.TestStructF(); struct.ref_count = 1; struct.data1 = null; struct.data2 = null; struct.data3 = null; struct.data4 = null; struct.data5 = null; struct.data6 = null; struct.data7 = 42; expect(struct.ref_count).toEqual(1); expect(struct.data1).toBeNull(); expect(struct.data2).toBeNull(); expect(struct.data3).toBeNull(); expect(struct.data4).toBeNull(); expect(struct.data5).toBeNull(); expect(struct.data6).toBeNull(); expect(struct.data7).toEqual(42); }); }); describe('Introspected simple boxed struct', function () { let struct; beforeEach(function () { struct = new Regress.TestSimpleBoxedA(); struct.some_int = 42; struct.some_int8 = 43; struct.some_double = 42.5; struct.some_enum = Regress.TestEnum.VALUE3; }); it('sets fields correctly', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('can be passed to a method', function () { const other = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, }); expect(other.equals(struct)).toBeTruthy(); }); it('can be returned from a method', function () { const other = Regress.TestSimpleBoxedA.const_return(); expect(other.some_int).toEqual(5); expect(other.some_int8).toEqual(6); expect(other.some_double).toEqual(7); }); describe('constructors', function () { beforeEach(function () { struct = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); }); it('"copies" an object from a hash of field values', function () { expect(struct.some_int).toEqual(42); expect(struct.some_int8).toEqual(43); expect(struct.some_double).toEqual(42.5); expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3); }); it('catches bad field names', function () { expect(() => new Regress.TestSimpleBoxedA({junk: 42})).toThrow(); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestSimpleBoxedA(struct); expect(copy).toEqual(jasmine.any(Regress.TestSimpleBoxedA)); expect(copy.some_int).toEqual(42); expect(copy.some_int8).toEqual(43); expect(copy.some_double).toEqual(42.5); expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3); }); }); }); describe('Introspected boxed nested struct', function () { let struct; beforeEach(function () { struct = new Regress.TestSimpleBoxedB(); struct.some_int8 = 42; struct.nested_a.some_int = 43; }); it('reads fields and nested fields', function () { expect(struct.some_int8).toEqual(42); expect(struct.nested_a.some_int).toEqual(43); }); it('assigns nested struct field from an instance', function () { struct.nested_a = new Regress.TestSimpleBoxedA({some_int: 53}); expect(struct.nested_a.some_int).toEqual(53); }); it('assigns nested struct field directly from a hash of field values', function () { struct.nested_a = {some_int: 63}; expect(struct.nested_a.some_int).toEqual(63); }); describe('constructors', function () { it('constructs with a nested hash of field values', function () { const simple2 = new Regress.TestSimpleBoxedB({ some_int8: 42, nested_a: { some_int: 43, some_int8: 44, some_double: 43.5, }, }); expect(simple2.some_int8).toEqual(42); expect(simple2.nested_a.some_int).toEqual(43); expect(simple2.nested_a.some_int8).toEqual(44); expect(simple2.nested_a.some_double).toEqual(43.5); }); it('copies an object from another object of the same type', function () { const copy = new Regress.TestSimpleBoxedB(struct); expect(copy.some_int8).toEqual(42); expect(copy.nested_a.some_int).toEqual(43); }); }); }); describe('Introspected boxed types', function () { describe('Opaque', function () { it('constructs from a default constructor', function () { const boxed = new Regress.TestBoxed(); expect(boxed).toEqual(jasmine.any(Regress.TestBoxed)); }); it('sets fields correctly', function () { const boxed = new Regress.TestBoxed(); boxed.some_int8 = 42; expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor', function () { const boxed = Regress.TestBoxed.new_alternative_constructor1(42); expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor with different args', function () { const boxed = Regress.TestBoxed.new_alternative_constructor2(40, 2); expect(boxed.some_int8).toEqual(42); }); it('constructs from a static constructor with differently typed args', function () { const boxed = Regress.TestBoxed.new_alternative_constructor3('42'); expect(boxed.some_int8).toEqual(42); }); it('constructs from a another object of the same type', function () { const boxed = new Regress.TestBoxed({some_int8: 42}); const copy = new Regress.TestBoxed(boxed); expect(copy.some_int8).toEqual(42); expect(copy.equals(boxed)).toBeTruthy(); }); it('ensures methods are named correctly', function () { const boxed = new Regress.TestBoxed(); expect(boxed.s_not_a_method).not.toBeDefined(); expect(boxed.not_a_method).not.toBeDefined(); expect(() => Regress.test_boxeds_not_a_method(boxed)).not.toThrow(); }); it('ensures static methods are named correctly', function () { expect(Regress.TestBoxed.s_not_a_static).not.toBeDefined(); expect(Regress.TestBoxed.not_a_static).not.toBeDefined(); expect(Regress.test_boxeds_not_a_static).not.toThrow(); }); }); describe('Simple', function () { it('sets fields correctly', function () { const boxed = new Regress.TestBoxedB(); boxed.some_int8 = 7; boxed.some_long = 5; expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); it('constructs from a static constructor', function () { const boxed = Regress.TestBoxedB.new(7, 5); expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); it('constructs from another object of the same type', function () { const boxed = Regress.TestBoxedB.new(7, 5); const copy = new Regress.TestBoxedB(boxed); expect(copy.some_int8).toEqual(7); expect(copy.some_long).toEqual(5); }); // Regress.TestBoxedB has a constructor that takes multiple arguments, // but since it is directly allocatable, we keep the old style of // passing an hash of fields. The two real world structs that have this // behavior are Clutter.Color and Clutter.ActorBox. it('constructs in backwards compatibility mode', function () { const boxed = new Regress.TestBoxedB({some_int8: 7, some_long: 5}); expect(boxed.some_int8).toEqual(7); expect(boxed.some_long).toEqual(5); }); }); describe('Refcounted', function () { it('constructs from a default constructor', function () { const boxed = new Regress.TestBoxedC(); expect(boxed.another_thing).toEqual(42); }); it('constructs from another object of the same type', function () { const boxed = new Regress.TestBoxedC({another_thing: 43}); const copy = new Regress.TestBoxedC(boxed); expect(copy.another_thing).toEqual(43); }); }); describe('Private', function () { it('constructs using a custom constructor', function () { const boxed = new Regress.TestBoxedD('abcd', 8); expect(boxed.get_magic()).toEqual(12); }); it('constructs from another object of the same type', function () { const boxed = new Regress.TestBoxedD('abcd', 8); const copy = new Regress.TestBoxedD(boxed); expect(copy.get_magic()).toEqual(12); }); it('does not construct with a default constructor', function () { expect(() => new Regress.TestBoxedD()).toThrow(); }); }); xit('methods take priority over fields in a name conflict', function () { const boxed = new Regress.TestBoxedC({name_conflict: true}); expect(boxed.name_conflict).not.toBeTrue(); expect(boxed.name_conflict()).toBeTrue(); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/454'); }); describe('wrong type for GBoxed', function () { let simpleBoxed, wrongObject, wrongBoxed; beforeEach(function () { simpleBoxed = new Regress.TestSimpleBoxedA(); wrongObject = new Gio.SimpleAction(); wrongBoxed = new GLib.KeyFile(); }); // simpleBoxed.equals expects a Everything.TestSimpleBoxedA it('function does not accept a GObject of the wrong type', function () { expect(() => simpleBoxed.equals(wrongObject)).toThrow(); }); it('function does not accept a GBoxed of the wrong type', function () { expect(() => simpleBoxed.equals(wrongBoxed)).toThrow(); }); it('function does accept a GBoxed of the correct type', function () { expect(simpleBoxed.equals(simpleBoxed)).toBeTruthy(); }); it('method cannot be called on a GObject', function () { expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(wrongObject)) .toThrow(); }); it('method cannot be called on a GBoxed of the wrong type', function () { expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(wrongBoxed)) .toThrow(); }); it('method can be called on correct GBoxed type', function () { expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(simpleBoxed)) .not.toThrow(); }); }); describe('Introspected GObject', function () { let o; beforeEach(function () { o = new Regress.TestObj({ // These properties have backing public fields with different names int: 42, float: 3.1416, double: 2.71828, }); }); it('can access fields with simple types', function () { // Compare the values gotten through the GObject property getters to the // values of the backing fields expect(o.some_int8).toEqual(o.int); expect(o.some_float).toEqual(o.float); expect(o.some_double).toEqual(o.double); }); it('cannot access fields with complex types (GI limitation)', function () { expect(() => o.parent_instance).toThrow(); expect(() => o.function_ptr).toThrow(); }); it('throws when setting a read-only field', function () { expect(() => (o.some_int8 = 41)).toThrow(); }); it('has normal Object methods', function () { o.ownprop = 'foo'; // eslint-disable-next-line no-prototype-builtins expect(o.hasOwnProperty('ownprop')).toBeTruthy(); }); it('sets write-only properties', function () { expect(o.int).not.toEqual(0); o.write_only = true; expect(o.int).toEqual(0); }); it('gives undefined for write-only properties', function () { expect(o.write_only).not.toBeDefined(); }); it('constructs from constructors annotated with (constructor)', function () { expect(Regress.TestObj.new(o)).toEqual(jasmine.any(Regress.TestObj)); expect(Regress.TestObj.constructor()).toEqual(jasmine.any(Regress.TestObj)); }); it('static methods', function () { const v = Regress.TestObj.new_from_file('/enoent'); expect(v).toEqual(jasmine.any(Regress.TestObj)); }); describe('GProperty', function () { let t, boxed, hashTable, hashTable2, list2, string, gtype, byteArray; const list = null; const int = 42; const double = Math.PI; const double2 = Math.E; beforeEach(function () { boxed = new Regress.TestBoxed({some_int8: 127}); hashTable = {a: 1, b: 2}; hashTable2 = {c: 3, d: 4}; list2 = ['j', 'k', 'l']; string = 'cauliflower'; gtype = GObject.Object.$gtype; byteArray = Uint8Array.from('abcd', c => c.charCodeAt(0)); t = new Regress.TestObj({ boxed, // hashTable, list, // pptrarray: list, // hashTableOld: hashTable, listOld: list, int, float: double, double, string, gtype, // byteArray, }); }); it('Boxed type', function () { expect(t.boxed.some_int8).toBe(127); const boxed2 = new Regress.TestBoxed({some_int8: 31}); t.boxed = boxed2; expect(t.boxed.some_int8).toBe(31); }); xit('Hash table', function () { expect(t.hashTable).toBe(hashTable); t.hashTable = hashTable2; expect(t.hashTable).toBe(hashTable2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); xit('List', function () { expect(t.list).toBe(list); t.list = list2; expect(t.list).toBe(list2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); xit('Pointer array', function () { expect(t.pptrarray).toBe(list); t.pptrarray = list2; expect(t.pptrarray).toBe(list2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); xit('Hash table with old-style annotation', function () { expect(t.hashTableOld).toBe(hashTable); t.hashTableOld = hashTable2; expect(t.hashTableOld).toBe(hashTable2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); xit('List with old-style annotation', function () { expect(t.listOld).toBe(list); t.listOld = list2; expect(t.listOld).toBe(list2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); it('Integer', function () { expect(t.int).toBe(int); t.int = 35; expect(t.int).toBe(35); }); it('Float', function () { expect(t.float).toBeCloseTo(double); t.float = double2; expect(t.float).toBeCloseTo(double2); }); it('Double', function () { expect(t.double).toBeCloseTo(double); t.double = double2; expect(t.double).toBeCloseTo(double2); }); it('String', function () { expect(t.string).toBe(string); t.string = 'string2'; expect(t.string).toBe('string2'); }); xit('GType object', function () { expect(t.gtype).toBe(gtype); const gtype2 = GObject.InitiallyUnowned.$gtype; t.gtype = gtype2; expect(t.gtype).toBe(gtype2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); xit('Byte array', function () { expect(t.byteArray).toBe(byteArray); const byteArray2 = Uint8Array.from('efgh', c => c.charCodeAt(0)); t.byteArray = byteArray2; expect(t.byteArray).toBe(byteArray2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/276'); }); describe('Object-valued GProperty', function () { let o1, t1, t2; beforeEach(function () { o1 = new GObject.Object(); t1 = new Regress.TestObj({bare: o1}); t2 = new Regress.TestSubObj(); t2.bare = o1; }); it('marshals correctly in the getter', function () { expect(t1.bare).toBe(o1); }); it('marshals correctly when inherited', function () { expect(t2.bare).toBe(o1); }); it('marshals into setter function', function () { const o2 = new GObject.Object(); t2.set_bare(o2); expect(t2.bare).toBe(o2); }); it('marshals null', function () { t2.unset_bare(); expect(t2.bare).toBeNull(); }); }); describe('Signal connection', function () { it('calls correct handlers with correct arguments', function () { const handler = jasmine.createSpy('handler'); const handlerId = o.connect('test', handler); handler.and.callFake(() => o.disconnect(handlerId)); o.emit('test'); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(o); handler.calls.reset(); o.emit('test'); expect(handler).not.toHaveBeenCalled(); }); it('throws errors for invalid signals', function () { expect(() => o.connect('invalid-signal', () => {})).toThrow(); expect(() => o.emit('invalid-signal')).toThrow(); }); it('signal handler with static scope arg gets arg passed by reference', function () { const b = new Regress.TestSimpleBoxedA({ some_int: 42, some_int8: 43, some_double: 42.5, some_enum: Regress.TestEnum.VALUE3, }); o.connect('test-with-static-scope-arg', (signalObject, signalArg) => { signalArg.some_int = 44; }); o.emit('test-with-static-scope-arg', b); expect(b.some_int).toEqual(44); }); it('signal with object gets correct arguments', function (done) { o.connect('sig-with-obj', (self, objectParam) => { expect(objectParam.int).toEqual(3); done(); }); o.emit_sig_with_obj(); }); it('signal with object with gets correct arguments from JS', function (done) { o.connect('sig-with-obj', (self, objectParam) => { expect(objectParam.int).toEqual(33); done(); }); const testObj = new Regress.TestObj({int: 33}); o.emit('sig-with-obj', testObj); }); it('signal with object with full transport gets correct arguments', function (done) { o.connect('sig-with-obj-full', (self, objectParam) => { expect(objectParam.int).toEqual(5); done(); }); o.emit_sig_with_obj_full(); }); it('signal with object with full transport gets correct arguments from JS', function (done) { o.connect('sig-with-obj-full', (self, objectParam) => { expect(objectParam.int).toEqual(55); done(); }); const testObj = new Regress.TestObj({int: 55}); o.emit('sig-with-obj-full', testObj); }); // See testCairo.js for a test of // Regress.TestObj::sig-with-foreign-struct. xit('signal with int64 gets correct value', function (done) { o.connect('sig-with-int64-prop', (self, number) => { expect(number).toEqual(GLib.MAXINT64); done(); return GLib.MAXINT64; }); o.emit_sig_with_int64(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); xit('signal with uint64 gets correct value', function (done) { o.connect('sig-with-uint64-prop', (self, number) => { expect(number).toEqual(GLib.MAXUINT64); done(); return GLib.MAXUINT64; }); o.emit_sig_with_uint64(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271'); xit('signal with array parameter is properly handled', function (done) { o.connect('sig-with-array-prop', (signalObj, signalArray, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual([0, 1, 2, 3, 4, 5]); done(); }); o.emit('sig-with-array-prop', [0, 1, 2, 3, 4, 5]); }).pend('Not yet implemented'); xit('signal with hash parameter is properly handled', function (done) { o.connect('sig-with-hash-prop', (signalObj, signalArray, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual([0, 1, 2, 3, 4, 5]); done(); }); o.emit('sig-with-hash-prop', {'0': 1}); }).pend('Not yet implemented'); it('signal with array len parameter is not passed correct array and no length arg', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray, shouldBeUndefined) => { expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual([0, 1, 2, 3, 4]); done(); }); o.emit_sig_with_array_len_prop(); }); it('signal with GStrv parameter is properly handled', function (done) { o.connect('sig-with-strv', (signalObj, signalArray, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual(['a', 'bb', 'ccc']); done(); }); o.emit('sig-with-strv', ['a', 'bb', 'ccc']); }); it('signal with GStrv parameter and transfer full is properly handled from JS', function (done) { o.connect('sig-with-strv-full', (signalObj, signalArray, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual(['a', 'bb', 'ccc']); done(); }); o.emit('sig-with-strv-full', ['a', 'bb', 'ccc']); }); xit('signal with GStrv parameter and transfer full is properly handled', function (done) { o.connect('sig-with-strv-full', (signalObj, signalArray, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalArray).toEqual(['foo', 'bar', 'baz']); done(); }); o.emit_sig_with_gstrv_full(); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/470'); xit('signal with int array ret parameter is properly handled', function (done) { o.connect('sig-with-intarray-ret', (signalObj, signalInt, shouldBeUndefined) => { expect(signalObj).toBe(o); expect(shouldBeUndefined).not.toBeDefined(); expect(signalInt).toEqual(5); const ret = []; for (let i = 0; i < signalInt; ++i) ret.push(i); done(); }); expect(o.emit('sig-with-intarray-ret', 5)).toBe([0, 1, 2, 3, 4]); }).pend('Not yet implemented'); xit('can pass parameter to signal with array len parameter via emit', function (done) { o.connect('sig-with-array-len-prop', (signalObj, signalArray) => { expect(signalArray).toEqual([0, 1, 2, 3, 4]); done(); }); o.emit('sig-with-array-len-prop', [0, 1, 2, 3, 4]); }).pend('Not yet implemented'); xit('can pass null to signal with array len parameter', function () { const handler = jasmine.createSpy('handler'); o.connect('sig-with-array-len-prop', handler); o.emit('sig-with-array-len-prop', null); expect(handler).toHaveBeenCalledWith([jasmine.any(Object), null]); }).pend('Not yet implemented'); xit('signal with int in-out parameter', function () { const handler = jasmine.createSpy('handler').and.callFake(() => 43); o.connect('sig-with-inout-int', handler); o.emit_sig_with_inout_int(); expect(handler.toHaveBeenCalledWith([jasmine.any(Object), 42])); }).pend('Not yet implemented'); it('GError signal with GError set', function (done) { o.connect('sig-with-gerror', (obj, e) => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.FAILED); done(); }); o.emit_sig_with_error(); }); it('GError signal with no GError set', function (done) { o.connect('sig-with-gerror', (obj, e) => { expect(e).toBeNull(); done(); }); o.emit_sig_with_null_error(); }); it('GError signal with no GError set from js', function (done) { o.connect('sig-with-gerror', (obj, e) => { expect(e).toBeNull(); done(); }); o.emit('sig-with-gerror', null); }); it('GError signal with no GError set from js', function (done) { o.connect('sig-with-gerror', (obj, e) => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.EXISTS); done(); }); o.emit('sig-with-gerror', new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS, 'We support this!')); }); }); it('can call an instance method', function () { expect(o.instance_method()).toEqual(-1); }); it('can call a transfer-full instance method', function () { expect(() => o.instance_method_full()).not.toThrow(); }); it('can call a static method', function () { expect(Regress.TestObj.static_method(5)).toEqual(5); }); it('can call a method annotated with (method)', function () { expect(() => o.forced_method()).not.toThrow(); }); describe('Object torture signature', function () { it('0', function () { const [y, z, q] = o.torture_signature_0(42, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); it('1 fail', function () { expect(() => o.torture_signature_1(42, 'foo', 7)).toThrow(); }); it('1 success', function () { const [, y, z, q] = o.torture_signature_1(11, 'barbaz', 8); expect(Math.floor(y)).toEqual(11); expect(z).toEqual(22); expect(q).toEqual(14); }); }); describe('Introspected function length', function () { it('skips over instance parameters of methods', function () { expect(o.set_bare.length).toEqual(1); }); it('skips over out and GError parameters', function () { expect(o.torture_signature_1.length).toEqual(3); }); it('does not skip over inout parameters', function () { expect(o.skip_return_val.length).toEqual(5); }); xit('skips over return value annotated with skip', function () { const [b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); const retval = o.skip_return_val_no_out(1); expect(retval).not.toBeDefined(); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over parameters annotated with skip', function () { expect(o.skip_param.length).toEqual(4); const [success, b, d, sum] = o.skip_param(1, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(3); expect(sum).toEqual(43); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over out parameters annotated with skip', function () { const [success, d, sum] = o.skip_out_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); expect(d).toEqual(4); expect(sum).toEqual(54); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); xit('skips over inout parameters annotated with skip', function () { expect(o.skip_inout_param.length).toEqual(4); const [success, b, sum] = o.skip_inout_param(1, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(sum).toEqual(43); }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59'); it('gives number of arguments for static methods', function () { expect(Regress.TestObj.new_from_file.length).toEqual(1); }); it('skips over destroy-notify and user-data parameters', function () { expect(Regress.TestObj.new_callback.length).toEqual(1); }); }); it('virtual function', function () { expect(o.do_matrix('meaningless string')).toEqual(42); }); describe('wrong type for GObject', function () { let wrongObject, wrongBoxed, subclassObject; beforeEach(function () { wrongObject = new Gio.SimpleAction(); wrongBoxed = new GLib.KeyFile(); subclassObject = new Regress.TestSubObj(); }); // Regress.func_obj_null_in expects a Regress.TestObj it('function does not accept a GObject of the wrong type', function () { expect(() => Regress.func_obj_null_in(wrongObject)).toThrow(); }); it('function does not accept a GBoxed instead of GObject', function () { expect(() => Regress.func_obj_null_in(wrongBoxed)).toThrow(); }); it('function does not accept returned GObject of the wrong type', function () { const wrongReturnedObject = Gio.File.new_for_path('/'); expect(() => Regress.func_obj_null_in(wrongReturnedObject)).toThrow(); }); it('function accepts GObject of subclass of expected type', function () { expect(() => Regress.func_obj_null_in(subclassObject)).not.toThrow(); }); it('method cannot be called on a GObject of the wrong type', function () { expect(() => Regress.TestObj.prototype.instance_method.call(wrongObject)) .toThrow(); }); it('method cannot be called on a GBoxed', function () { expect(() => Regress.TestObj.prototype.instance_method.call(wrongBoxed)) .toThrow(); }); it('method can be called on a GObject of subclass of expected type', function () { expect(() => Regress.TestObj.prototype.instance_method.call(subclassObject)) .not.toThrow(); }); }); it('marshals a null object in', function () { expect(() => Regress.func_obj_null_in(null)).not.toThrow(); expect(() => Regress.func_obj_nullable_in(null)).not.toThrow(); }); it('marshals a null object out', function () { expect(Regress.TestObj.null_out()).toBeNull(); }); it('marshals a gpointer with a type annotation in', function () { const o2 = new GObject.Object(); expect(() => o.not_nullable_typed_gpointer_in(o2)).not.toThrow(); }); it('marshals a gpointer with an element-type annotation in', function () { expect(() => o.not_nullable_element_typed_gpointer_in([1, 2])).not.toThrow(); }); // This test is not meant to be normative; a GObject behaving like this is // doing something unsupported. However, we have been handling this so far // in a certain way, and we don't want to break user code because of badly // behaved libraries. This test ensures that any change to the behaviour // must be intentional. it('resolves properties when they are shadowed by methods', function () { expect(o.name_conflict).toEqual(42); expect(o.name_conflict).not.toEqual(jasmine.any(Function)); }); }); it('marshals a fixed-size array of objects out', function () { expect(Regress.test_array_fixed_out_objects()).toEqual([ jasmine.any(Regress.TestObj), jasmine.any(Regress.TestObj), ]); }); describe('Inherited GObject', function () { let subobj; beforeEach(function () { subobj = new Regress.TestSubObj({ int: 42, float: Math.PI, double: Math.E, boolean: true, }); }); it('can read fields from a parent class', function () { // see "can access fields with simple types" above expect(subobj.some_int8).toEqual(subobj.int); expect(subobj.some_float).toEqual(subobj.float); expect(subobj.some_double).toEqual(subobj.double); }); it('can be constructed from a static constructor', function () { expect(Regress.TestSubObj.new).not.toThrow(); }); it('can call an instance method that overrides the parent class', function () { expect(subobj.instance_method()).toEqual(0); }); it('can have its own properties', function () { expect(subobj.boolean).toBeTruthy(); subobj.boolean = false; expect(subobj.boolean).toBeFalsy(); }); }); describe('Overridden properties on interfaces', function () { it('set and get properly', function () { const o = new Regress.TestSubObj(); o.number = 4; expect(o.number).toEqual(4); }); it('default properly', function () { const o = new Regress.TestSubObj(); expect(o.number).toBeDefined(); expect(o.number).toEqual(0); }); it('construct properly', function () { const o = new Regress.TestSubObj({number: 4}); expect(o.number).toEqual(4); }); }); describe('Fundamental type', function () { it('constructs a subtype of a fundamental type', function () { expect(() => new Regress.TestFundamentalSubObject('plop')).not.toThrow(); }); it('constructs a subtype of a hidden (no introspection data) fundamental type', function () { expect(() => Regress.test_create_fundamental_hidden_class_instance()).not.toThrow(); }); }); it('callbacks', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); expect(Regress.test_callback(callback)).toEqual(42); }); it('null / undefined callback', function () { expect(Regress.test_callback(null)).toEqual(0); expect(() => Regress.test_callback(undefined)).toThrow(); }); it('callback called more than once', function () { const callback = jasmine.createSpy('callback').and.returnValue(21); expect(Regress.test_multi_callback(callback)).toEqual(42); expect(callback).toHaveBeenCalledTimes(2); }); it('null callback called more than once', function () { expect(Regress.test_multi_callback(null)).toEqual(0); }); it('array callbacks', function () { const callback = jasmine.createSpy('callback').and.returnValue(7); expect(Regress.test_array_callback(callback)).toEqual(14); expect(callback).toHaveBeenCalledWith([-1, 0, 1, 2], ['one', 'two', 'three']); }); it('null array callback', function () { expect(() => Regress.test_array_callback(null)).toThrow(); }); xit('callback with inout array', function () { const callback = jasmine.createSpy('callback').and.callFake(arr => arr.slice(1)); expect(Regress.test_array_inout_callback(callback)).toEqual(3); expect(callback).toHaveBeenCalledWith([-2, -1, 0, 1, 2], [-1, 0, 1, 2]); }); // assertion failed, "Use gjs_value_from_explicit_array() for arrays with length param"" ['simple', 'noptr'].forEach(type => { it(`${type} callback`, function () { const callback = jasmine.createSpy('callback'); Regress[`test_${type}_callback`](callback); expect(callback).toHaveBeenCalled(); }); it(`null ${type} callback`, function () { expect(() => Regress[`test_${type}_callback`](null)).not.toThrow(); }); }); it('gobject-introspected function as callback parameter', function () { const expected = GLib.get_num_processors(); expect(Regress.test_callback(GLib.get_num_processors)).toEqual(expected); }); it('callback with user data', function () { const callback = jasmine.createSpy('callback').and.returnValue(7); expect(Regress.test_callback_user_data(callback)).toEqual(7); expect(callback).toHaveBeenCalled(); }); it('callback with transfer-full return value', function () { const callback = jasmine.createSpy('callback') .and.returnValue(Regress.TestObj.new_from_file('/enoent')); Regress.test_callback_return_full(callback); expect(callback).toHaveBeenCalled(); }); it('callback with destroy-notify', function () { const callback1 = jasmine.createSpy('callback').and.returnValue(42); const callback2 = jasmine.createSpy('callback').and.returnValue(58); expect(Regress.test_callback_destroy_notify(callback1)).toEqual(42); expect(callback1).toHaveBeenCalledTimes(1); expect(Regress.test_callback_destroy_notify(callback2)).toEqual(58); expect(callback2).toHaveBeenCalledTimes(1); expect(Regress.test_callback_thaw_notifications()).toEqual(100); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); }); xit('callback with destroy-notify and no user data', function () { const callback1 = jasmine.createSpy('callback').and.returnValue(42); const callback2 = jasmine.createSpy('callback').and.returnValue(58); expect(Regress.test_callback_destroy_notify_no_user_data(callback1)).toEqual(42); expect(callback1).toHaveBeenCalledTimes(1); expect(Regress.test_callback_destroy_notify_no_user_data(callback2)).toEqual(58); expect(callback2).toHaveBeenCalledTimes(1); expect(Regress.test_callback_thaw_notifications()).toEqual(100); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); }).pend('Callback with destroy-notify and no user data not currently supported'); // If this is ever supported, then replace it with the above test. it('callback with destroy-notify and no user data throws error', function () { // should throw when called, not when the function object is created expect(() => Regress.test_callback_destroy_notify_no_user_data).not.toThrow(); expect(() => Regress.test_callback_destroy_notify_no_user_data(() => {})) .toThrowError(/no user data/); }); it('async callback', function () { Regress.test_callback_async(() => 44); expect(Regress.test_callback_thaw_async()).toEqual(44); }); it('Gio.AsyncReadyCallback', function (done) { Regress.test_async_ready_callback((obj, res) => { expect(obj).toBeNull(); expect(res).toEqual(jasmine.any(Gio.SimpleAsyncResult)); done(); }); }); it('instance method taking a callback', function () { const o = new Regress.TestObj(); const callback = jasmine.createSpy('callback'); o.instance_method_callback(callback); expect(callback).toHaveBeenCalled(); }); it('static method taking a callback', function () { const callback = jasmine.createSpy('callback'); Regress.TestObj.static_method_callback(callback); expect(callback).toHaveBeenCalled(); }); it('constructor taking a callback', function () { const callback = jasmine.createSpy('callback').and.returnValue(42); void Regress.TestObj.new_callback(callback); expect(callback).toHaveBeenCalled(); expect(Regress.test_callback_thaw_notifications()).toEqual(42); expect(callback).toHaveBeenCalledTimes(2); }); it('hash table passed to callback', function () { const hashtable = { a: 1, b: 2, c: 3, }; const callback = jasmine.createSpy('callback'); Regress.test_hash_table_callback(hashtable, callback); expect(callback).toHaveBeenCalledWith(hashtable); }); it('GError callback', function (done) { Regress.test_gerror_callback(e => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.NOT_SUPPORTED); done(); }); }); it('null GError callback', function () { const callback = jasmine.createSpy('callback'); Regress.test_null_gerror_callback(callback); expect(callback).toHaveBeenCalledWith(null); }); it('owned GError callback', function (done) { Regress.test_owned_gerror_callback(e => { expect(e).toEqual(jasmine.any(Gio.IOErrorEnum)); expect(e.domain).toEqual(Gio.io_error_quark()); expect(e.code).toEqual(Gio.IOErrorEnum.PERMISSION_DENIED); done(); }); }); describe('Introspected interface', function () { const Implementor = GObject.registerClass({ Implements: [Regress.TestInterface], Properties: { number: GObject.ParamSpec.override('number', Regress.TestInterface), }, }, class Implementor extends GObject.Object { get number() { return 5; } }); it('correctly emits interface signals', function () { const obj = new Implementor(); const handler = jasmine.createSpy('handler').and.callFake(() => {}); obj.connect('interface-signal', handler); obj.emit_signal(); expect(handler).toHaveBeenCalled(); }); }); describe('GObject with nonstandard prefix', function () { let o; beforeEach(function () { o = new Regress.TestWi8021x(); }); it('sets and gets properties', function () { expect(o.testbool).toBeTruthy(); o.testbool = false; expect(o.testbool).toBeFalsy(); }); it('constructs via a static constructor', function () { expect(Regress.TestWi8021x.new()).toEqual(jasmine.any(Regress.TestWi8021x)); }); it('calls methods', function () { expect(o.get_testbool()).toBeTruthy(); o.set_testbool(false); expect(o.get_testbool()).toBeFalsy(); }); it('calls a static method', function () { expect(Regress.TestWi8021x.static_method(21)).toEqual(42); }); }); describe('GObject.InitiallyUnowned', function () { it('constructs', function () { expect(new Regress.TestFloating()).toEqual(jasmine.any(Regress.TestFloating)); }); it('constructs via a static constructor', function () { expect(Regress.TestFloating.new()).toEqual(jasmine.any(Regress.TestFloating)); }); }); it('torture signature 0', function () { const [y, z, q] = Regress.test_torture_signature_0(42, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); it('torture signature 1 fail', function () { expect(() => Regress.test_torture_signature_1(42, 'foo', 7)).toThrow(); }); it('torture signature 1 success', function () { const [, y, z, q] = Regress.test_torture_signature_1(11, 'barbaz', 8); expect(Math.floor(y)).toEqual(11); expect(z).toEqual(22); expect(q).toEqual(14); }); it('torture signature 2', function () { const [y, z, q] = Regress.test_torture_signature_2(42, () => 0, 'foo', 7); expect(Math.floor(y)).toEqual(42); expect(z).toEqual(84); expect(q).toEqual(10); }); describe('GValue boxing and unboxing', function () { it('date in', function () { const date = Regress.test_date_in_gvalue(); expect(date.get_year()).toEqual(1984); expect(date.get_month()).toEqual(GLib.DateMonth.DECEMBER); expect(date.get_day()).toEqual(5); }); it('strv in', function () { expect(Regress.test_strv_in_gvalue()).toEqual(['one', 'two', 'three']); }); it('correctly converts a NULL strv in a GValue to an empty array', function () { expect(Regress.test_null_strv_in_gvalue()).toEqual([]); }); }); it("code coverage for documentation tests that don't do anything", function () { expect(() => { Regress.test_multiline_doc_comments(); Regress.test_nested_parameter(5); Regress.test_versioning(); }).not.toThrow(); }); it('marshals an aliased type', function () { // GLib.PtrArray is not introspectable, so neither is an alias of it // Regress.introspectable_via_alias(new GLib.PtrArray()); expect(Regress.aliased_caller_alloc()).toEqual(jasmine.any(Regress.TestBoxed)); }); it('deals with a fixed-size array in a struct', function () { const struct = new Regress.TestStructFixedArray(); struct.frob(); expect(struct.just_int).toEqual(7); expect(struct.array).toEqual([42, 43, 44, 45, 46, 47, 48, 49, 50, 51]); }); it('marshals a fixed-size int array as a gpointer', function () { expect(() => Regress.has_parameter_named_attrs(0, Array(32).fill(42))).not.toThrow(); }); it('deals with a fixed-size and also zero-terminated array in a struct', function () { const x = new Regress.LikeXklConfigItem(); x.set_name('foo'); expect(x.name).toEqual([...'foo'].map(c => c.codePointAt()).concat(Array(29).fill(0))); x.set_name('*'.repeat(33)); expect(x.name).toEqual(Array(31).fill('*'.codePointAt()).concat([0])); }); it('marshals a transfer-floating GLib.Variant', function () { expect(Regress.get_variant().unpack()).toEqual(42); }); describe('Flat array of structs', function () { it('out parameter with transfer none', function () { const expected = [111, 222, 333].map(some_int => jasmine.objectContaining({some_int})); expect(Regress.test_array_struct_out_none()).toEqual(expected); }); it('out parameter with transfer container', function () { const expected = [11, 13, 17, 19, 23].map(some_int => jasmine.objectContaining({some_int})); expect(Regress.test_array_struct_out_container()).toEqual(expected); }); it('out parameter with transfer full', function () { const expected = [2, 3, 5, 7].map(some_int => jasmine.objectContaining({some_int})); expect(Regress.test_array_struct_out_full_fixed()).toEqual(expected); }); xit('caller-allocated out parameter', function () { // With caller-allocated array in, there's no way to supply the // length. This happens in GLib.MainContext.query() expect(Regress.test_array_struct_out_caller_alloc()).toEqual([]); }).pend('Not supported'); it('transfer-full in parameter', function () { const array = [201, 202].map(some_int => new Regress.TestStructA({some_int})); expect(() => Regress.test_array_struct_in_full(array)).not.toThrow(); }); it('transfer-none in parameter', function () { const array = [301, 302, 303].map(some_int => new Regress.TestStructA({some_int})); expect(() => Regress.test_array_struct_in_none(array)).not.toThrow(); }); }); }); cjs-128.1/installed-tests/js/testSignals.js0000664000175000017500000001700215116312211017612 0ustar fabiofabio/* eslint-disable no-restricted-properties */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC const GLib = imports.gi.GLib; const Lang = imports.lang; const Signals = imports.signals; const Foo = new Lang.Class({ Name: 'Foo', Implements: [Signals.WithSignals], _init() {}, }); describe('Legacy object with signals', function () { testSignals(Foo); }); class FooWithoutSignals {} Signals.addSignalMethods(FooWithoutSignals.prototype); describe('Object with signals added', function () { testSignals(FooWithoutSignals); }); function testSignals(klass) { let foo, bar; beforeEach(function () { foo = new klass(); bar = jasmine.createSpy('bar'); }); it('emit works with no connections', function () { expect(() => foo.emit('random-event')).not.toThrow(); }); ['connect', 'connectAfter'].forEach(connectMethod => { describe(`using ${connectMethod}`, function () { it('calls a signal handler when a signal is emitted', function () { foo[connectMethod]('bar', bar); foo.emit('bar', 'This is a', 'This is b'); expect(bar).toHaveBeenCalledWith(foo, 'This is a', 'This is b'); }); it('calls remaining handlers after one is disconnected', function () { const id1 = foo[connectMethod]('bar', bar); const bar2 = jasmine.createSpy('bar2'); const id2 = foo[connectMethod]('bar', bar2); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(1); expect(bar2).toHaveBeenCalledTimes(1); foo.disconnect(id1); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(1); expect(bar2).toHaveBeenCalledTimes(2); foo.disconnect(id2); }); it('does not call a signal handler after the signal is disconnected', function () { let id = foo[connectMethod]('bar', bar); foo.emit('bar', 'This is a', 'This is b'); bar.calls.reset(); foo.disconnect(id); // this emission should do nothing foo.emit('bar', 'Another a', 'Another b'); expect(bar).not.toHaveBeenCalled(); }); it('can disconnect a signal handler during signal emission', function () { var toRemove = []; let firstId = foo[connectMethod]('bar', function (theFoo) { theFoo.disconnect(toRemove[0]); theFoo.disconnect(toRemove[1]); }); toRemove.push(foo[connectMethod]('bar', bar)); toRemove.push(foo[connectMethod]('bar', bar)); // emit signal; what should happen is that the second two handlers are // disconnected before they get invoked foo.emit('bar'); expect(bar).not.toHaveBeenCalled(); // clean up the last handler foo.disconnect(firstId); expect(() => foo.disconnect(firstId)).toThrowError( `No signal connection ${firstId} found`); // poke in private implementation to verify no handlers left expect(Object.keys(foo._signalConnections).length).toEqual(0); }); it('distinguishes multiple signals', function () { let bonk = jasmine.createSpy('bonk'); foo[connectMethod]('bar', bar); foo[connectMethod]('bonk', bonk); foo[connectMethod]('bar', bar); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(2); expect(bonk).not.toHaveBeenCalled(); foo.emit('bonk'); expect(bar).toHaveBeenCalledTimes(2); expect(bonk).toHaveBeenCalledTimes(1); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(4); expect(bonk).toHaveBeenCalledTimes(1); foo.disconnectAll(); bar.calls.reset(); bonk.calls.reset(); // these post-disconnect emissions should do nothing foo.emit('bar'); foo.emit('bonk'); expect(bar).not.toHaveBeenCalled(); expect(bonk).not.toHaveBeenCalled(); }); it('determines if a signal is connected on a JS object', function () { let id = foo[connectMethod]('bar', bar); expect(foo.signalHandlerIsConnected(id)).toEqual(true); foo.disconnect(id); expect(foo.signalHandlerIsConnected(id)).toEqual(false); }); it('does not call a subsequent connected callbacks if stopped by earlier', function () { const afterBar = jasmine.createSpy('bar'); const afterAfterBar = jasmine.createSpy('barBar'); foo[connectMethod]('bar', bar.and.returnValue(true)); foo[connectMethod]('bar', afterBar); foo[connectMethod]('bar', afterAfterBar); foo.emit('bar', 'This is a', 123); expect(bar).toHaveBeenCalledWith(foo, 'This is a', 123); expect(afterBar).not.toHaveBeenCalled(); expect(afterAfterBar).not.toHaveBeenCalled(); }); describe('with exception in signal handler', function () { let bar2; beforeEach(function () { bar.and.throwError('Exception we are throwing on purpose'); bar2 = jasmine.createSpy('bar'); foo[connectMethod]('bar', bar); foo[connectMethod]('bar', bar2); GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); }); it('does not affect other callbacks', function () { expect(bar).toHaveBeenCalledTimes(1); expect(bar2).toHaveBeenCalledTimes(1); }); it('does not disconnect the callback', function () { GLib.test_expect_message('Cjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in callback for signal: *'); foo.emit('bar'); expect(bar).toHaveBeenCalledTimes(2); expect(bar2).toHaveBeenCalledTimes(2); }); }); }); }); it('using connectAfter calls a signal handler later than when using connect when a signal is emitted', function () { const afterBar = jasmine.createSpy('bar'); foo.connectAfter('bar', (...args) => { expect(bar).toHaveBeenCalledWith(foo, 'This is a', 'This is b'); afterBar(...args); }); foo.connect('bar', bar); foo.emit('bar', 'This is a', 'This is b'); expect(afterBar).toHaveBeenCalledWith(foo, 'This is a', 'This is b'); }); it('does not call a connected after handler when stopped by connect', function () { const afterBar = jasmine.createSpy('bar'); foo.connectAfter('bar', afterBar); foo.connect('bar', bar.and.returnValue(true)); foo.emit('bar', 'This is a', 'This is b'); expect(bar).toHaveBeenCalledWith(foo, 'This is a', 'This is b'); expect(afterBar).not.toHaveBeenCalled(); }); } cjs-128.1/installed-tests/js/testSystem.js0000664000175000017500000000563115116312211017503 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Pavel Vasin // SPDX-FileCopyrightText: 2013 Giovanni Campagna // SPDX-FileCopyrightText: 2017 Claudio André // SPDX-FileCopyrightText: 2019 Philip Chimento // SPDX-FileCopyrightText: 2019 Canonical, Ltd. const System = imports.system; const {Gio, GObject} = imports.gi; describe('System.addressOf()', function () { it('gives different results for different objects', function () { let a = {some: 'object'}; let b = {different: 'object'}; expect(System.addressOf(a)).not.toEqual(System.addressOf(b)); }); }); describe('System.refcount()', function () { it('gives the correct number', function () { let o = new GObject.Object({}); expect(System.refcount(o)).toEqual(1); }); }); describe('System.addressOfGObject()', function () { it('gives different results for different objects', function () { let a = new GObject.Object({}); let b = new GObject.Object({}); expect(System.addressOfGObject(a)).toEqual(System.addressOfGObject(a)); expect(System.addressOfGObject(a)).not.toEqual(System.addressOfGObject(b)); }); it('throws for non GObject objects', function () { expect(() => System.addressOfGObject({})) .toThrowError(/Object 0x[a-f0-9]+ is not a GObject/); }); }); describe('System.gc()', function () { it('does not crash the application', function () { expect(System.gc).not.toThrow(); }); }); describe('System.dumpHeap()', function () { it('throws but does not crash when given a nonexistent path', function () { expect(() => System.dumpHeap('/does/not/exist')).toThrow(); }); }); describe('System.dumpMemoryInfo()', function () { it('', function () { expect(() => System.dumpMemoryInfo('memory.md')).not.toThrow(); expect(() => Gio.File.new_for_path('memory.md').delete(null)).not.toThrow(); }); it('throws but does not crash when given a nonexistent path', function () { expect(() => System.dumpMemoryInfo('/does/not/exist')).toThrowError(/\/does\/not\/exist/); }); }); describe('System.programPath', function () { it('is null when executed from minijasmine', function () { expect(System.programPath).toBe(null); }); }); describe('System.programArgs', function () { it('System.programArgs is an array', function () { expect(Array.isArray(System.programArgs)).toBeTruthy(); }); it('modifications persist', function () { System.programArgs.push('--foo'); expect(System.programArgs.pop()).toBe('--foo'); }); it('System.programArgs is equal to ARGV', function () { expect(System.programArgs).toEqual(ARGV); ARGV.push('--foo'); expect(System.programArgs.pop()).toBe('--foo'); }); }); cjs-128.1/installed-tests/js/testTimers.js0000664000175000017500000002667415116312211017474 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018-2019 the Deno authors. All rights reserved. // SPDX-FileCopyrightText: 2022 Evan Welsh // Derived from https://github.com/denoland/deno/blob/eda6e58520276786bd87e411d0284eb56d9686a6/cli/tests/unit/timers_test.ts import GLib from 'gi://GLib'; function deferred() { let resolve_; let reject_; function resolve() { resolve_(); } function reject() { reject_(); } const promise = new Promise((res, rej) => { resolve_ = res; reject_ = rej; }); return { promise, resolve, reject, }; } /** * @param {number} ms the number of milliseconds to wait * @returns {Promise} */ function waitFor(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * @param {(resolve?: () => void, reject?: () => void) => void} callback a callback to call with handlers once the promise executes * @returns {jasmine.AsyncMatchers} */ function expectPromise(callback) { return expectAsync( new Promise((resolve, reject) => { callback(resolve, reject); }) ); } describe('Timers', () => { it('times out successfully', async function () { const startTime = GLib.get_monotonic_time(); const ms = 500; let count = 0; let endTime; await expectPromise(resolve => { setTimeout(() => { endTime = GLib.get_monotonic_time(); count++; resolve(); }, ms); }).toBeResolved(); expect(count).toBe(1); expect(endTime - startTime).toBeGreaterThanOrEqual(ms); return 5; }); it('has correct timeout args', async function () { const arg = 1; await expectPromise(resolve => { setTimeout( (a, b, c) => { expect(a).toBe(arg); expect(b).toBe(arg.toString()); expect(c).toEqual(jasmine.arrayWithExactContents([arg])); resolve(); }, 10, arg, arg.toString(), [arg] ); }).toBeResolved(); }); it('cancels successfully', async function () { let count = 0; const timeout = setTimeout(() => { count++; }, 1); // Cancelled, count should not increment clearTimeout(timeout); await waitFor(600); expect(count).toBe(0); }); it('cancels multiple correctly', async function () { const uncalled = jasmine.createSpy('uncalled'); // Set timers and cancel them in the same order. const t1 = setTimeout(uncalled, 10); const t2 = setTimeout(uncalled, 10); const t3 = setTimeout(uncalled, 10); clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); // Set timers and cancel them in reverse order. const t4 = setTimeout(uncalled, 20); const t5 = setTimeout(uncalled, 20); const t6 = setTimeout(uncalled, 20); clearTimeout(t6); clearTimeout(t5); clearTimeout(t4); // Sleep until we're certain that the cancelled timers aren't gonna fire. await waitFor(50); expect(uncalled).not.toHaveBeenCalled(); }); it('cancels invalid silent fail', async function () { // Expect no panic const {promise, resolve} = deferred(); let count = 0; const id = setTimeout(() => { count++; // Should have no effect clearTimeout(id); resolve(); }, 500); await promise; expect(count).toBe(1); // Should silently fail (no panic) clearTimeout(2147483647); }); it('interval success', async function () { const {promise, resolve} = deferred(); let count = 0; const id = setInterval(() => { count++; clearInterval(id); resolve(); }, 100); await promise; // Clear interval clearInterval(id); // count should increment twice expect(count).toBe(1); }); it('cancels interval successfully', async function () { let count = 0; const id = setInterval(() => { count++; }, 1); clearInterval(id); await waitFor(500); expect(count).toBe(0); }); it('ordering interval', async function () { const timers = []; let timeouts = 0; function onTimeout() { ++timeouts; for (let i = 1; i < timers.length; i++) clearTimeout(timers[i]); } for (let i = 0; i < 10; i++) timers[i] = setTimeout(onTimeout, 1); await waitFor(500); expect(timeouts).toBe(1); }); it('cancel invalid silent fail', function () { // Should silently fail (no panic) clearInterval(2147483647); }); it('callback this', async function () { const {promise, resolve} = deferred(); const obj = { foo() { expect(this).toBe(window); resolve(); }, }; setTimeout(obj.foo, 1); await promise; }); it('bind this', function () { function noop() { } const thisCheckPassed = [null, undefined, window, globalThis]; const thisCheckFailed = [ 0, '', true, false, {}, [], 'foo', () => { }, Object.prototype, ]; thisCheckPassed.forEach(thisArg => { expect(() => { setTimeout.call(thisArg, noop, 1); }).not.toThrow(); }); thisCheckFailed.forEach(thisArg => { expect(() => { setTimeout.call(thisArg, noop, 1); }).toThrowError(TypeError); }); }); it('function names match spec', function testFunctionName() { expect(clearTimeout.name).toBe('clearTimeout'); expect(clearInterval.name).toBe('clearInterval'); }); it('argument lengths match spec', function testFunctionParamsLength() { expect(setTimeout.length).toBe(1); expect(setInterval.length).toBe(1); expect(clearTimeout.length).toBe(0); expect(clearInterval.length).toBe(0); }); it('clear and interval are unique functions', function clearTimeoutAndClearIntervalNotBeEquals() { expect(clearTimeout).not.toBe(clearInterval); }); // Based on https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ // and https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/html/webappapis/scripting/event-loops/task_microtask_ordering.html it('microtask ordering', async function () { const executionOrder = []; const expectedExecutionOrder = [ 'promise', 'timeout and promise', 'timeout', 'callback', ]; await expectPromise(resolve => { function execute(label) { executionOrder.push(label); if (executionOrder.length === expectedExecutionOrder.length) resolve(); } setTimeout(() => { execute('timeout'); }); setTimeout(() => { Promise.resolve().then(() => { execute('timeout and promise'); }); }); Promise.resolve().then(() => { execute('promise'); }); execute('callback'); }).toBeResolved(); expect(executionOrder).toEqual( jasmine.arrayWithExactContents(expectedExecutionOrder) ); }); it('nested microtask ordering', async function () { const executionOrder = []; const expectedExecutionOrder = [ 'promise 1', 'promise 2', 'promise 3', 'promise 4', 'promise 4 > nested promise', 'promise 4 > returned promise', 'timeout 1', 'timeout 2', 'timeout 3', 'timeout 4', 'promise 2 > nested timeout', 'promise 3 > nested timeout', 'promise 3 > nested timeout > nested promise', 'timeout 1 > nested timeout', 'timeout 2 > nested timeout', 'timeout 2 > nested timeout > nested promise', 'timeout 3 > nested timeout', 'timeout 3 > nested timeout > promise', 'timeout 3 > nested timeout > promise > nested timeout', ]; await expectPromise(resolve => { function execute(label) { executionOrder.push(label); } setTimeout(() => { execute('timeout 1'); setTimeout(() => { execute('timeout 1 > nested timeout'); }); }); setTimeout(() => { execute('timeout 2'); setTimeout(() => { execute('timeout 2 > nested timeout'); Promise.resolve().then(() => { execute('timeout 2 > nested timeout > nested promise'); }); }); }); setTimeout(() => { execute('timeout 3'); setTimeout(() => { execute('timeout 3 > nested timeout'); Promise.resolve().then(() => { execute('timeout 3 > nested timeout > promise'); setTimeout(() => { execute( 'timeout 3 > nested timeout > promise > nested timeout' ); // The most deeply nested setTimeout will be the last to resolve // because all queued promises should resolve prior to timeouts // and timeouts execute in order resolve(); }); }); }); }); setTimeout(() => { execute('timeout 4'); }); Promise.resolve().then(() => { execute('promise 1'); }); Promise.resolve().then(() => { execute('promise 2'); setTimeout(() => { execute('promise 2 > nested timeout'); }); }); Promise.resolve().then(() => { execute('promise 3'); setTimeout(() => { execute('promise 3 > nested timeout'); Promise.resolve().then(() => { execute('promise 3 > nested timeout > nested promise'); }); }); }); Promise.resolve().then(() => { execute('promise 4'); Promise.resolve().then(() => { execute('promise 4 > nested promise'); }); return Promise.resolve().then(() => { execute('promise 4 > returned promise'); }); }); }).toBeResolved(); expect(executionOrder).toEqual( jasmine.arrayWithExactContents(expectedExecutionOrder) ); }); }); cjs-128.1/installed-tests/js/testTweener.js0000664000175000017500000002774115116312211017636 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2009 Red Hat, Inc. const Tweener = imports.tweener.tweener; function installFrameTicker() { // Set up Tweener to have a "frame pulse" that the Jasmine clock functions // can influence let ticker = { FRAME_RATE: 50, _init() { }, start() { this._currentTime = 0; this._timeoutID = setInterval(() => { this._currentTime += 1000 / this.FRAME_RATE; this.emit('prepare-frame'); }, Math.floor(1000 / this.FRAME_RATE)); }, stop() { if ('_timeoutID' in this) { clearInterval(this._timeoutID); delete this._timeoutID; } this._currentTime = 0; }, getTime() { return this._currentTime; }, }; imports.signals.addSignalMethods(ticker); Tweener.setFrameTicker(ticker); } describe('Tweener', function () { beforeAll(function () { jasmine.clock().install(); installFrameTicker(); }); afterAll(function () { jasmine.clock().uninstall(); }); let start, update, overwrite, complete; beforeEach(function () { start = jasmine.createSpy('start'); update = jasmine.createSpy('update'); overwrite = jasmine.createSpy('overwrite'); complete = jasmine.createSpy('complete'); }); it('runs a simple tween', function () { var objectA = { x: 0, y: 0, }; var objectB = { x: 0, y: 0, }; Tweener.addTween(objectA, {x: 10, y: 10, time: 1, transition: 'linear'}); Tweener.addTween(objectB, {x: 10, y: 10, time: 1, delay: 0.5, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(10); expect(objectB.x).toEqual(5); expect(objectB.y).toEqual(5); }); it('calls callbacks during the tween', function () { Tweener.addTween({}, { time: 0.1, onStart: start, onUpdate: update, onComplete: complete, }); jasmine.clock().tick(101); expect(start).toHaveBeenCalled(); expect(update).toHaveBeenCalled(); expect(complete).toHaveBeenCalled(); }); it('can pause tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; var objectC = { baaz: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectC, {baaz: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.pauseTweens(objectA); // This should do nothing expect(Tweener.pauseTweens(objectB, 'quux')).toBeFalsy(); /* Pause and resume should be equal to doing nothing */ Tweener.pauseTweens(objectC, 'baaz'); Tweener.resumeTweens(objectC, 'baaz'); jasmine.clock().tick(101); expect(objectA.foo).toEqual(0); expect(objectB.bar).toEqual(100); expect(objectC.baaz).toEqual(100); }); it('can remove tweens', function () { var object = { foo: 0, bar: 0, baaz: 0, }; Tweener.addTween(object, {foo: 50, time: 0.1}); Tweener.addTween(object, {bar: 50, time: 0.1}); Tweener.addTween(object, {baaz: 50, time: 0.1}); /* The Tween on property foo should still be run after removing the other two */ Tweener.removeTweens(object, 'bar', 'baaz'); jasmine.clock().tick(101); expect(object.foo).toEqual(50); expect(object.bar).toEqual(0); expect(object.baaz).toEqual(0); }); it('overrides a tween with another one acting on the same object and property at the same time', function () { var objectA = { foo: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectA, {foo: 0, time: 0.1}); jasmine.clock().tick(101); expect(objectA.foo).toEqual(0); }); it('does not override a tween with another one acting not at the same time', function () { var objectB = { bar: 0, }; /* In this case both tweens should be executed, as they don't * act on the object at the same time (the second one has a * delay equal to the running time of the first one) */ Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 150, time: 0.1, delay: 0.1}); jasmine.clock(0).tick(201); expect(objectB.bar).toEqual(150); }); it('can pause and resume all tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.pauseAllTweens(); jasmine.clock().tick(10); Tweener.resumeAllTweens(); jasmine.clock().tick(101); expect(objectA.foo).toEqual(100); expect(objectB.bar).toEqual(100); }); it('can remove all tweens', function () { var objectA = { foo: 0, }; var objectB = { bar: 0, }; Tweener.addTween(objectA, {foo: 100, time: 0.1}); Tweener.addTween(objectB, {bar: 100, time: 0.1}); Tweener.removeAllTweens(); jasmine.clock().tick(200); expect(objectA.foo).toEqual(0); expect(objectB.bar).toEqual(0); }); it('runs a tween with a time of 0 immediately', function () { var object = { foo: 100, }; Tweener.addTween(object, {foo: 50, time: 0, delay: 0}); Tweener.addTween(object, { foo: 200, time: 0.1, onStart: () => { /* The immediate tween should set it to 50 before we run */ expect(object.foo).toEqual(50); }, }); jasmine.clock().tick(101); expect(object.foo).toEqual(200); }); it('can call a callback a certain number of times', function () { var object = { foo: 0, }; Tweener.addCaller(object, { onUpdate: () => { object.foo += 1; }, count: 10, time: 0.1, }); jasmine.clock().tick(101); expect(object.foo).toEqual(10); }); it('can count the number of tweens on an object', function () { var object = { foo: 0, bar: 0, baaz: 0, quux: 0, }; expect(Tweener.getTweenCount(object)).toEqual(0); Tweener.addTween(object, {foo: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(1); Tweener.addTween(object, {bar: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(2); Tweener.addTween(object, {baaz: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(3); Tweener.addTween(object, {quux: 100, time: 0.1}); expect(Tweener.getTweenCount(object)).toEqual(4); Tweener.removeTweens(object, 'bar', 'baaz'); expect(Tweener.getTweenCount(object)).toEqual(2); }); it('can register special properties', function () { Tweener.registerSpecialProperty( 'negative_x', function (obj) { return -obj.x; }, function (obj, val) { obj.x = -val; } ); var objectA = { x: 0, y: 0, }; Tweener.addTween(objectA, {negative_x: 10, y: 10, time: 1, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(-10); expect(objectA.y).toEqual(10); }); it('can register special modifiers for properties', function () { Tweener.registerSpecialPropertyModifier('discrete', discreteModifier, discreteGet); function discreteModifier(props) { return props.map(function (prop) { return {name: prop, parameters: null}; }); } function discreteGet(begin, end, time) { return Math.floor(begin + time * (end - begin)); } var objectA = { x: 0, y: 0, xFraction: false, yFraction: false, }; Tweener.addTween(objectA, { x: 10, y: 10, time: 1, discrete: ['x'], transition: 'linear', onUpdate() { if (objectA.x !== Math.floor(objectA.x)) objectA.xFraction = true; if (objectA.y !== Math.floor(objectA.y)) objectA.yFraction = true; }, }); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(10); expect(objectA.xFraction).toBeFalsy(); expect(objectA.yFraction).toBeTruthy(); }); it('can split properties into more than one special property', function () { Tweener.registerSpecialPropertySplitter( 'xnegy', function (val) { return [{name: 'x', value: val}, {name: 'y', value: -val}]; } ); var objectA = { x: 0, y: 0, }; Tweener.addTween(objectA, {xnegy: 10, time: 1, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(10); expect(objectA.y).toEqual(-10); }); it('calls an overwrite callback when a tween is replaced', function () { var object = { a: 0, b: 0, c: 0, d: 0, }; var tweenA = { a: 10, b: 10, c: 10, d: 10, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; var tweenB = { a: 20, b: 20, c: 20, d: 20, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; Tweener.addTween(object, tweenA); Tweener.addTween(object, tweenB); jasmine.clock().tick(101); expect(start).toHaveBeenCalledTimes(1); expect(overwrite).toHaveBeenCalledTimes(1); expect(complete).toHaveBeenCalledTimes(1); }); it('can still overwrite a tween after it has started', function () { var object = { a: 0, b: 0, c: 0, d: 0, }; var tweenA = { a: 10, b: 10, c: 10, d: 10, time: 0.1, onStart: () => { start(); Tweener.addTween(object, tweenB); }, onOverwrite: overwrite, onComplete: complete, }; var tweenB = { a: 20, b: 20, c: 20, d: 20, time: 0.1, onStart: start, onOverwrite: overwrite, onComplete: complete, }; Tweener.addTween(object, tweenA); jasmine.clock().tick(121); expect(start).toHaveBeenCalledTimes(2); expect(overwrite).toHaveBeenCalledTimes(1); expect(complete).toHaveBeenCalledTimes(1); }); it('stays within min and max values', function () { var objectA = { x: 0, y: 0, }; var objectB = { x: 0, y: 0, }; Tweener.addTween(objectA, {x: 300, y: 300, time: 1, max: 255, transition: 'linear'}); Tweener.addTween(objectB, {x: -200, y: -200, time: 1, delay: 0.5, min: 0, transition: 'linear'}); jasmine.clock().tick(1001); expect(objectA.x).toEqual(255); expect(objectA.y).toEqual(255); expect(objectB.x).toEqual(0); expect(objectB.y).toEqual(0); }); }); cjs-128.1/installed-tests/js/testWarnLib.js0000664000175000017500000000265515116312211017560 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. // SPDX-FileCopyrightText: 2019 Philip Chimento // File with tests from the WarnLib-1.0.gir test suite from GI const {Gio, GObject, WarnLib} = imports.gi; describe('WarnLib', function () { // Calling matches() on an unpaired error used to JSUnit.assert: // https://bugzilla.gnome.org/show_bug.cgi?id=689482 it('bug 689482', function () { try { WarnLib.throw_unpaired(); fail(); } catch (e) { expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)).toBeFalsy(); } }); const WhateverImpl = GObject.registerClass({ Implements: [WarnLib.Whatever], }, class WhateverImpl extends GObject.Object { vfunc_do_moo(x) { expect(x).toEqual(5); this.mooCalled = true; } vfunc_do_boo(x) { expect(x).toEqual(6); this.booCalled = true; } }); it('calls vfuncs with unnamed parameters', function () { const o = new WhateverImpl(); o.do_moo(5, null); o.do_boo(6, null); expect(o.mooCalled).toBeTruthy(); // spies don't work on vfuncs expect(o.booCalled).toBeTruthy(); }); it('handles enum members that start with a digit', function () { expect(WarnLib.NumericEnum['1ST']).toEqual(1); }); }); cjs-128.1/installed-tests/js/testWeakRef.js0000664000175000017500000000473115116312211017543 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento import System from 'system'; const PromiseInternal = imports._promiseNative; describe('WeakRef', function () { it('works', function () { let obj = {}; const weakRef = new WeakRef(obj); expect(weakRef.deref()).toBe(obj); obj = null; // Do not use this in real code to process microtasks. This is only for // making the test execute synchronously. Instead, in real code, return // control to the event loop, e.g. with setTimeout(). PromiseInternal.drainMicrotaskQueue(); System.gc(); expect(weakRef.deref()).not.toBeDefined(); }); }); describe('FinalizationRegistry', function () { let registry, callback; beforeEach(function () { callback = jasmine.createSpy('FinalizationRegistry callback'); registry = new FinalizationRegistry(callback); }); it('works', function () { let obj = {}; registry.register(obj, 'marker'); obj = null; System.gc(); PromiseInternal.drainMicrotaskQueue(); expect(callback).toHaveBeenCalledOnceWith('marker'); }); it('works if a microtask is enqueued from the callback', function () { let obj = {}; let secondCallback = jasmine.createSpy('async callback'); callback.and.callFake(function () { return Promise.resolve().then(secondCallback); }); registry.register(obj); obj = null; System.gc(); PromiseInternal.drainMicrotaskQueue(); expect(callback).toHaveBeenCalled(); expect(secondCallback).toHaveBeenCalled(); }); it('works if the object is collected in a microtask', async function () { let obj = {}; registry.register(obj, 'marker'); await Promise.resolve(); obj = null; System.gc(); await Promise.resolve(); expect(callback).toHaveBeenCalled(); }); it('works if another collection is queued from the callback', function () { let obj = {}; let obj2 = {}; callback.and.callFake(function () { obj2 = null; System.gc(); }); registry.register(obj, 'marker'); registry.register(obj2, 'marker2'); obj = null; System.gc(); PromiseInternal.drainMicrotaskQueue(); expect(callback).toHaveBeenCalledTimes(2); }); }); cjs-128.1/installed-tests/js/testself.js0000664000175000017500000000360115116312211017143 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC describe('Test harness internal consistency', function () { it('', function () { var someUndefined; var someNumber = 1; var someOtherNumber = 42; var someString = 'hello'; var someOtherString = 'world'; expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(someNumber).toEqual(someNumber); expect(someString).toEqual(someString); expect(someNumber).not.toEqual(someOtherNumber); expect(someString).not.toEqual(someOtherString); expect(null).toBeNull(); expect(someNumber).not.toBeNull(); expect(someNumber).toBeDefined(); expect(someUndefined).not.toBeDefined(); expect(0 / 0).toBeNaN(); expect(someNumber).not.toBeNaN(); expect(() => { throw new Error(); }).toThrow(); expect(() => expect(true).toThrow()).toThrow(); expect(() => true).not.toThrow(); }); describe('awaiting', function () { it('a Promise resolves', async function () { await Promise.resolve(); expect(true).toBe(true); }); async function nested() { await Promise.resolve(); } it('a nested async function resolves', async function () { await nested(); expect(true).toBe(true); }); }); }); describe('SpiderMonkey features check', function () { it('Intl API was compiled into SpiderMonkey', function () { expect(Intl).toBeDefined(); }); it('WeakRef is enabled', function () { expect(WeakRef).toBeDefined(); }); it('class static blocks are enabled', function () { class Test { static { Test.x = 4; } } expect(Test.x).toBe(4); }); }); cjs-128.1/installed-tests/meson.build0000664000175000017500000000555115116312211016510 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan ### Installed tests ############################################################ # Simple shell script tests # simple_tests = [] tests_dependencies = [ cjs_console, cjs_private_typelib, ] # The test scripts need to be ported from shell scripts # for clang-cl builds, which do not use BASH-style shells if cxx.get_argument_syntax() != 'msvc' simple_tests += [ 'CommandLine', 'CommandLineModules', 'Warnings', ] endif foreach test : simple_tests test_file = files('scripts' / 'test@0@.sh'.format(test)) test(test, test_file, env: tests_environment, protocol: 'tap', suite: 'Scripts', depends: tests_dependencies) test_description_subst = { 'name': 'test@0@.sh'.format(test), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file(configuration: test_description_subst, input: 'script.test.in', output: 'test@0@.sh.test'.format(test), install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_tests_execdir / 'scripts') endif endforeach # Jasmine tests # subdir('js') # Debugger script tests # debugger_tests = [ 'backtrace', 'breakpoint', 'continue', 'delete', 'detach', 'down-up', 'finish', 'frame', 'keys', 'lastvalues', 'list', 'next', 'print', 'quit', 'return', 'set', 'step', 'throw', 'throw-ignored', 'until', ] debugger_test_driver = find_program(files('debugger-test.sh')) if get_option('installed_tests') install_data('debugger-test.sh', install_dir: installed_tests_execdir) endif foreach test : debugger_tests test_file = files('debugger' / '@0@.debugger'.format(test)) test('@0@ command'.format(test), debugger_test_driver, args: test_file, env: tests_environment, protocol: 'tap', suite: 'Debugger', depends: tests_dependencies) test_description_subst = { 'name': '@0@.debugger'.format(test), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file(configuration: test_description_subst, input: 'debugger.test.in', output: '@0@.test'.format(test), install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(test_file, install_dir: installed_tests_execdir / 'debugger') install_data('debugger' / '@0@.debugger.js'.format(test), 'debugger' / '@0@.debugger.output'.format(test), install_dir: installed_tests_execdir / 'debugger') endif endforeach cjs-128.1/installed-tests/minijasmine-module.test.in0000664000175000017500000000036115116312211021434 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Philip Chimento [Test] Type=session Exec=@installed_tests_execdir@/minijasmine @installed_tests_execdir@/js/@name@ -m Output=TAP cjs-128.1/installed-tests/minijasmine.cpp0000664000175000017500000000643115116312211017353 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2016 Philip Chimento #include // for setlocale, LC_ALL #include #include // for exit #include #include #include #include #include #include [[noreturn]] static void bail_out(GjsContext* gjs_context, const char* msg) { g_object_unref(gjs_context); g_print("Bail out! %s\n", msg); exit(1); } [[noreturn]] static void bail_out(GjsContext* gjs_context, GError* error) { g_print("Bail out! %s\n", error->message); g_object_unref(gjs_context); g_error_free(error); exit(1); } int main(int argc, char **argv) { if (argc < 2) g_error("Need a test file"); g_setenv("GJS_DEBUG_OUTPUT", "stderr", false); setlocale(LC_ALL, ""); if (g_getenv("GJS_USE_UNINSTALLED_FILES") != NULL) { g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR")); } else { g_irepository_prepend_search_path(INSTTESTDIR); g_irepository_prepend_library_path(INSTTESTDIR); } const char *coverage_prefix = g_getenv("GJS_UNIT_COVERAGE_PREFIX"); const char *coverage_output_path = g_getenv("GJS_UNIT_COVERAGE_OUTPUT"); const char *search_path[] = { "resource:///org/cjs/jsunit", NULL }; if (coverage_prefix) gjs_coverage_enable(); GjsContext *cx = gjs_context_new_with_search_path((char **)search_path); GjsCoverage *coverage = NULL; if (coverage_prefix) { const char *coverage_prefixes[2] = { coverage_prefix, NULL }; if (!coverage_output_path) { bail_out(cx, "GJS_UNIT_COVERAGE_OUTPUT is required when using GJS_UNIT_COVERAGE_PREFIX"); } GFile *output = g_file_new_for_commandline_arg(coverage_output_path); coverage = gjs_coverage_new(coverage_prefixes, cx, output); g_object_unref(output); } GError *error = NULL; bool success; uint8_t code; uint8_t u8_exitcode_ignored; int exitcode_ignored; if (!gjs_context_eval_module_file( cx, "resource:///org/cjs/jsunit/minijasmine.js", &u8_exitcode_ignored, &error)) bail_out(cx, error); bool eval_as_module = argc >= 3 && strcmp(argv[2], "-m") == 0; if (eval_as_module) { success = gjs_context_eval_module_file(cx, argv[1], &u8_exitcode_ignored, &error); } else { success = gjs_context_eval_file(cx, argv[1], &exitcode_ignored, &error); } if (!success) bail_out(cx, error); success = gjs_context_eval_module_file( cx, "resource:///org/cjs/jsunit/minijasmine-executor.js", &code, &error); if (!success) bail_out(cx, error); if (coverage) { gjs_coverage_write_statistics(coverage); g_clear_object(&coverage); } gjs_memory_report("before destroying context", false); g_object_unref(cx); gjs_memory_report("after destroying context", true); /* For TAP, should actually be return 0; as a nonzero return code would * indicate an error in the test harness. But that would be quite silly * when running the tests outside of the TAP driver. */ return code; } cjs-128.1/installed-tests/minijasmine.test.in0000664000175000017500000000035615116312211020155 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2016 Philip Chimento [Test] Type=session Exec=@installed_tests_execdir@/minijasmine @installed_tests_execdir@/js/@name@ Output=TAP cjs-128.1/installed-tests/script.test.in0000664000175000017500000000027315116312211017154 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2013 Red Hat, Inc. [Test] Type=session Exec=sh @prefix@/@installed_tests_execdir@/scripts/@name@ Output=TAP cjs-128.1/installed-tests/scripts/0000775000175000017500000000000015116312211016027 5ustar fabiofabiocjs-128.1/installed-tests/scripts/common.sh0000664000175000017500000000201615116312211017652 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2016 Philip Chimento if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi # Avoid interference in the profiler tests from stray environment variable unset GJS_ENABLE_PROFILER total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1 [EXIT CODE: $exit_code]" fi } report_timeout () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0 -o $exit_code -eq 124; then echo "ok $total - $1" else echo "not ok $total - $1 [EXIT CODE: $exit_code]" fi } report_xfail () { exit_code=$? total=$((total + 1)) if test $exit_code -ne 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } skip () { total=$((total + 1)) echo "ok $total - $1 # SKIP $2" } cjs-128.1/installed-tests/scripts/testCommandLine.sh0000664000175000017500000003114515116312211021455 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. # SPDX-FileCopyrightText: 2016 Philip Chimento if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi # Avoid interference in the profiler tests from stray environment variable unset GJS_ENABLE_PROFILER # Avoid interference in the warning tests from G_DEBUG=fatal-warnings/criticals OLD_G_DEBUG="$G_DEBUG" # This JS script should exit immediately with code 42. If that is not working, # then it will exit after 3 seconds as a fallback, with code 0. cat <exit.js const GLib = imports.gi.GLib; let loop = GLib.MainLoop.new(null, false); GLib.idle_add(GLib.PRIORITY_LOW, () => imports.system.exit(42)); GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 3, () => loop.quit()); loop.run(); EOF # this JS script fails if either 1) --help is not passed to it, or 2) the string # "sentinel" is not in its search path cat <help.js const System = imports.system; if (imports.searchPath.indexOf('sentinel') == -1) System.exit(1); if (ARGV.indexOf('--help') == -1) System.exit(1); System.exit(0); EOF # this JS script should print one string (jobs are run before the interpreter # finishes) and should not print the other (jobs should not be run after the # interpreter is instructed to quit) cat <promise.js const System = imports.system; Promise.resolve().then(() => { print('Should be printed'); System.exit(42); }); Promise.resolve().then(() => print('Should not be printed')); EOF # this JS script should not cause an unhandled promise rejection cat <awaitcatch.js async function foo() { throw new Error('foo'); } async function bar() { try { await foo(); } catch (e) {} } bar(); EOF # this JS script should fail to import a second version of the same namespace cat <doublegi.js import 'gi://Gio?version=2.0'; import 'gi://Gio?version=75.94'; EOF # this JS script is used to test ARGV handling cat <argv.js const System = imports.system; if (System.programPath.endsWith('/argv.js')) System.exit(0); else System.exit(1); EOF # this JS script is used to test correct exiting from signal callbacks cat <signalexit.js import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import { exit } from 'system'; const Button = GObject.registerClass({ Signals: { 'clicked': {}, }, }, class Button extends GObject.Object { go() { this.emit('clicked'); } }); const button = new Button(); button.connect('clicked', () => exit(15)); let n = 1; GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => { print(\`click \${n++}\`); button.go(); return GLib.SOURCE_CONTINUE; }); const loop = new GLib.MainLoop(null, false); loop.run(); EOF # this is similar to exit.js but should exit with an unhandled promise rejection cat <promiseexit.js const {GLib} = imports.gi; const System = imports.system; const loop = GLib.MainLoop.new(null, false); Promise.reject(); GLib.idle_add(GLib.PRIORITY_LOW, () => System.exit(42)); GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 3, () => loop.quit()); loop.run(); EOF total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } report_xfail () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 23; then echo "not ok $total - $1 (leaked memory)" elif test $exit_code -ne 0; then echo "ok $total - $1 (exit code $exit_code)" else echo "not ok $total - $1" fi } skip () { total=$((total + 1)) echo "ok $total - $1 # SKIP $2" } $gjs --invalid-option >/dev/null 2>/dev/null report_xfail "Invalid option should exit with failure" $gjs --invalid-option 2>&1 | grep -q invalid-option report "Invalid option should print a relevant message" # Test that System.exit() works in cjs-console $gjs -c 'imports.system.exit(0)' report "System.exit(0) should exit successfully" $gjs -c 'imports.system.exit(42)' test $? -eq 42 report "System.exit(42) should exit with the correct exit code" # Test the System.programPath works in cjs-console $gjs argv.js report "System.programPath should end in '/argv.js' when gjs argv.js is run" # FIXME: should check -eq 42 specifically, but in debug mode we will be # hitting an assertion. For this reason, skip when running under valgrind # since nothing will be freed. Also suppress LSan for the same reason. echo "# VALGRIND = $VALGRIND" if test -z $VALGRIND; then ASAN_OPTIONS=detect_leaks=0 $gjs exit.js test $? -ne 0 report "System.exit() should still exit across an FFI boundary" # https://gitlab.gnome.org/GNOME/gjs/-/issues/417 output="$(ASAN_OPTIONS=detect_leaks=0 $gjs promiseexit.js 2>&1)" test $? -ne 0 && printf '%s' "$output" | grep -q "Unhandled promise rejection" report "Unhandled promise rejections should still be printed when exiting" else skip "System.exit() should still exit across an FFI boundary" "running under valgrind" skip "Unhandled promise rejections should still be printed when exiting" "running under valgrind" fi # ensure the encoding of argv is being properly handled $gjs -c 'imports.system.exit((ARGV[0] !== "Valentín") ? 1 : 0)' "Valentín" report "Basic unicode encoding (accents, etc) should be functioning properly for ARGV and imports." $gjs -c 'imports.system.exit((ARGV[0] !== "☭") ? 1 : 0)' "☭" report "Unicode encoding for symbols should be functioning properly for ARGV and imports." # gjs --help prints GJS help $gjs --help >/dev/null report "--help should succeed" test -n "$($gjs --help)" report "--help should print something" # print GJS help even if it's not the first argument $gjs -I . --help >/dev/null report "should succeed when --help is not first arg" test -n "$($gjs -I . --help)" report "should print something when --help is not first arg" # --help before a script file name prints GJS help $gjs --help help.js >/dev/null report "--help should succeed before a script file" test -n "$($gjs --help help.js)" report "--help should print something before a script file" # --help before a -c argument prints GJS help script='imports.system.exit(1)' $gjs --help -c "$script" >/dev/null report "--help should succeed before -c" test -n "$($gjs --help -c "$script")" report "--help should print something before -c" # --help after a script file name is passed to the script $gjs -I sentinel help.js --help report "--help after script file should be passed to script" test -z "$($gjs -I sentinel help.js --help)" report "--help after script file should not print anything" # --help after a -c argument is passed to the script script='if(ARGV[0] !== "--help") imports.system.exit(1)' $gjs -c "$script" --help report "--help after -c should be passed to script" test -z "$($gjs -c "$script" --help)" report "--help after -c should not print anything" # -I after a program is not consumed by GJS # Temporary behaviour: still consume the argument, but give a warning # "$gjs" help.js --help -I sentinel # report_xfail "-I after script file should not be added to search path" # fi G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//') $gjs help.js --help -I sentinel 2>&1 | grep -q 'Cjs-WARNING.*--include-path' report "-I after script should succeed but give a warning" $gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-prefix' report "--coverage-prefix after script should succeed but give a warning" $gjs -c 'imports.system.exit(0)' --coverage-prefix=foo --coverage-output=foo 2>&1 | grep -q 'Cjs-WARNING.*--coverage-output' report "--coverage-output after script should succeed but give a warning" rm -f foo/coverage.lcov G_DEBUG="$OLD_G_DEBUG" for version_arg in --version --jsversion; do # --version and --jsversion work $gjs $version_arg >/dev/null report "$version_arg should work" test -n "$($gjs $version_arg)" report "$version_arg should print something" # --version and --jsversion after a script go to the script script="if(ARGV[0] !== '$version_arg') imports.system.exit(1)" $gjs -c "$script" $version_arg report "$version_arg after -c should be passed to script" test -z "$($gjs -c "$script" $version_arg)" report "$version_arg after -c should not print anything" done # --profile rm -f gjs-*.syscap foo.syscap $gjs -c 'imports.system.exit(0)' && ! stat gjs-*.syscap > /dev/null 2>&1 report "no profiling data should be dumped without --profile" # Skip some tests if built without profiler support if $gjs --profile -c 1 2>&1 | grep -q 'Cjs-Message.*Profiler is disabled'; then reason="profiler is disabled" skip "--profile should dump profiling data to the default file name" "$reason" skip "--profile with argument should dump profiling data to the named file" "$reason" skip "GJS_ENABLE_PROFILER=1 should enable the profiler" "$reason" else rm -f gjs-*.syscap $gjs --profile -c 'imports.system.exit(0)' && stat gjs-*.syscap > /dev/null 2>&1 report "--profile should dump profiling data to the default file name" rm -f gjs-*.syscap $gjs --profile=foo.syscap -c 'imports.system.exit(0)' && test -f foo.syscap report "--profile with argument should dump profiling data to the named file" rm -f foo.syscap && rm -f gjs-*.syscap GJS_ENABLE_PROFILER=1 $gjs -c 'imports.system.exit(0)' && stat gjs-*.syscap > /dev/null 2>&1 report "GJS_ENABLE_PROFILER=1 should enable the profiler" rm -f gjs-*.syscap fi # interpreter handles queued promise jobs correctly output=$($gjs promise.js) test $? -eq 42 report "interpreter should exit with the correct exit code from a queued promise job" test -n "$output" -a -z "${output##*Should be printed*}" report "interpreter should run queued promise jobs before finishing" test -n "${output##*Should not be printed*}" report "interpreter should stop running jobs when one calls System.exit()" $gjs -c "Promise.resolve().then(() => { throw new Error(); });" 2>&1 | grep -q 'Cjs-WARNING.*Unhandled promise rejection.*[sS]tack trace' report "unhandled promise rejection should be reported" test -z "$($gjs awaitcatch.js)" report "catching an await expression should not cause unhandled rejection" # https://gitlab.gnome.org/GNOME/gjs/issues/18 G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//') $gjs -c "(async () => await true)(); void foobar;" 2>&1 | grep -q 'ReferenceError: foobar is not defined' report "main program exceptions are not swallowed by queued promise jobs" G_DEBUG="$OLD_G_DEBUG" # https://gitlab.gnome.org/GNOME/gjs/issues/26 $gjs -c 'new imports.gi.Gio.Subprocess({argv: ["true"]}).init(null);' report "object unref from other thread after shutdown should not race" # https://gitlab.gnome.org/GNOME/gjs/issues/212 if test -n "$ENABLE_GTK"; then G_DEBUG=$(echo "$G_DEBUG" | sed -e 's/fatal-warnings,\{0,1\}//' -e 's/fatal-criticals,\{0,1\}//') $gjs -c 'imports.gi.versions.Gtk = "3.0"; const Gtk = imports.gi.Gtk; const GObject = imports.gi.GObject; Gtk.init(null); let BadWidget = GObject.registerClass(class BadWidget extends Gtk.Widget { vfunc_destroy() {}; }); let w = new BadWidget ();' report "avoid crashing when GTK vfuncs are called on context destroy" G_DEBUG="$OLD_G_DEBUG" else skip "avoid crashing when GTK vfuncs are called on context destroy" "GTK disabled" fi # https://gitlab.gnome.org/GNOME/gjs/-/issues/322 $gjs --coverage-prefix=$(pwd) --coverage-output=$(pwd) awaitcatch.js grep -q TN: coverage.lcov report "coverage prefix is treated as an absolute path" rm -f coverage.lcov $gjs -m doublegi.js 2>&1 | grep -q 'already loaded' report "avoid statically importing two versions of the same module" # https://gitlab.gnome.org/GNOME/gjs/-/issues/19 echo "# VALGRIND = $VALGRIND" if test -z $VALGRIND; then output=$(env LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 \ $gjs -m signalexit.js) test $? -eq 15 report "exit with correct code from a signal callback" test -n "$output" -a -z "${output##*click 1*}" report "avoid asserting when System.exit is called from a signal callback" test -n "${output##*click 2*}" report "exit after first System.exit call in a signal callback" else skip "exit with correct code from a signal callback" "running under valgrind" skip "avoid asserting when System.exit is called from a signal callback" "running under valgrind" skip "exit after first System.exit call in a signal callback" "running under valgrind" fi rm -f exit.js help.js promise.js awaitcatch.js doublegi.js argv.js \ signalexit.js promiseexit.js echo "1..$total" cjs-128.1/installed-tests/scripts/testCommandLineModules.sh0000664000175000017500000000440415116312211023004 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2016 Endless Mobile, Inc. # SPDX-FileCopyrightText: 2016 Philip Chimento if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } # Avoid interference in the profiler tests from stray environment variable unset GJS_ENABLE_PROFILER # Avoid interference in the warning tests from G_DEBUG=fatal-warnings/criticals OLD_G_DEBUG="$G_DEBUG" cat <doubledynamicImportee.js export function noop() {} EOF # this JS script should succeed without an error on the second import cat <doubledynamic.js let done = false; import("./doubledynamicImportee.js") .then(ddi => { ddi.noop(); }) .finally(() => { if (done) imports.mainloop.quit(); done = true; }); import("./doubledynamicImportee.js") .then(ddi => { ddi.noop(); }) .finally(() => { if (done) imports.mainloop.quit(); done = true; }); imports.mainloop.run(); EOF $gjs doubledynamic.js report "ensure dynamic imports load even if the same import resolves elsewhere first" cat <dynamicImplicitMainloopImportee.js export const EXIT_CODE = 21; EOF cat <dynamicImplicitMainloop.js import("./dynamicImplicitMainloopImportee.js") .then(({ EXIT_CODE }) => { imports.system.exit(EXIT_CODE); }); EOF $gjs dynamicImplicitMainloop.js test $? -eq 21 report "ensure dynamic imports resolve without an explicit mainloop" cat <dynamicTopLevelAwaitImportee.js export const EXIT_CODE = 32; EOF cat <dynamicTopLevelAwait.js const {EXIT_CODE} = await import("./dynamicTopLevelAwaitImportee.js") const system = await import('system'); system.exit(EXIT_CODE); EOF $gjs -m dynamicTopLevelAwait.js test $? -eq 32 report "ensure top level await can import modules" rm -f doubledynamic.js doubledynamicImportee.js \ dynamicImplicitMainloop.js dynamicImplicitMainloopImportee.js \ dynamicTopLevelAwait.js dynamicTopLevelAwaitImportee.js echo "1..$total" cjs-128.1/installed-tests/scripts/testExamples.sh0000664000175000017500000000211615116312211021041 0ustar fabiofabio#!/bin/bash # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Claudio André DIR="$( cd "$( dirname "${0}" )" && pwd )" source "${DIR}"/common.sh # Run the examples $gjs -m examples/gio-cat.js meson.build report "run the gio-cat.js example" if [[ -n "${ENABLE_GTK}" ]]; then export graphical_gjs="xvfb-run -a dbus-run-session -- $gjs" eval timeout 5s $graphical_gjs -m examples/calc.js report_timeout "run the calc.js example" eval timeout 5s $graphical_gjs -m examples/gtk3.js report_timeout "run the gtk3.js example" eval timeout 5s $graphical_gjs -m examples/gtk-application.js report_timeout "run the gtk-application.js example" eval timeout 5s $graphical_gjs -m examples/gettext.js report_timeout "run the gettext.js example" else skip "run the calc.js example" "running without GTK" skip "run the gtk3.js example" "running without GTK" skip "run the gtk-application.js example" "running without GTK" skip "run the gettext.js example" "running without GTK" fi echo "1..$total" cjs-128.1/installed-tests/scripts/testWarnings.sh0000664000175000017500000000172215116312211021055 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2017 Philip Chimento if test "$GJS_USE_UNINSTALLED_FILES" = "1"; then gjs="$TOP_BUILDDIR/cjs-console" else gjs="cjs-console" fi total=0 report () { exit_code=$? total=$((total + 1)) if test $exit_code -eq 0; then echo "ok $total - $1" else echo "not ok $total - $1" fi } $gjs -c 'imports.signals.addSignalMethods({connect: "foo"})' 2>&1 | \ grep -q 'addSignalMethods is replacing existing .* connect method' report "overwriting method with Signals.addSignalMethods() should warn" $gjs -c 'imports.gi.GLib.get_home_dir("foobar")' 2>&1 | \ grep -q 'Too many arguments to .*: expected 0, got 1' report "passing too many arguments to a GI function should warn" $gjs -c '**' 2>&1 | \ grep -q 'SyntaxError.*@ :1:1' report "file and line number are logged for syntax errors" echo "1..$total" cjs-128.1/js.gresource.xml0000664000175000017500000000376515116312211014367 0ustar fabiofabio modules/internal/loader.js modules/esm/_bootstrap/default.js modules/esm/_encoding/encoding.js modules/esm/_encoding/encodingMap.js modules/esm/_encoding/util.js modules/esm/_timers.js modules/esm/cairo.js modules/esm/gettext.js modules/esm/console.js modules/esm/gi.js modules/esm/system.js modules/script/_bootstrap/debugger.js modules/script/_bootstrap/default.js modules/script/_bootstrap/coverage.js modules/script/tweener/equations.js modules/script/tweener/tweener.js modules/script/tweener/tweenList.js modules/script/byteArray.js modules/script/cairo.js modules/script/gettext.js modules/script/lang.js modules/script/_legacy.js modules/script/mainloop.js modules/script/jsUnit.js modules/script/signals.js modules/script/format.js modules/script/package.js modules/core/overrides/cairo.js modules/core/overrides/GLib.js modules/core/overrides/Gio.js modules/core/overrides/GObject.js modules/core/overrides/Gtk.js modules/core/_cairo.js modules/core/_common.js modules/core/_format.js modules/core/_gettext.js modules/core/_signals.js cjs-128.1/jsconfig.json0000664000175000017500000000007515116312211013720 0ustar fabiofabio{ "compilerOptions": { "lib": ["es2020"], } }cjs-128.1/libcjs.map0000664000175000017500000000025515116312211013170 0ustar fabiofabio/* SPDX-License-Identifier: MIT OR LGPL-2.0-or-later */ /* SPDX-FileCopyrightText: 2019 Philip Chimento */ { global: gjs_*; local: *; }; cjs-128.1/libcjs.symbols0000664000175000017500000000065715116312211014111 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Philip Chimento # Workaround for https://github.com/mesonbuild/meson/issues/3047 # Linker scripts are not understood by the macOS linker, we need to use a # symbol export file instead. # With autotools, this was all done transparently by -export-symbols-regex. _gjs_* __Z*[0-9]gjs_* __ZN*GjsContextPrivate*from_object* cjs-128.1/libgjs-private/0000775000175000017500000000000015116312211014143 5ustar fabiofabiocjs-128.1/libgjs-private/gjs-gdbus-wrapper.c0000664000175000017500000004420415116312211017656 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2011 Giovanni Campagna */ #include #include // for strcmp #include #include #include #include "libgjs-private/gjs-gdbus-wrapper.h" enum { PROP_0, PROP_G_INTERFACE_INFO, PROP_LAST }; enum { SIGNAL_HANDLE_METHOD, SIGNAL_HANDLE_PROPERTY_GET, SIGNAL_HANDLE_PROPERTY_SET, SIGNAL_LAST, }; static guint signals[SIGNAL_LAST]; struct _GjsDBusImplementationPrivate { GDBusInterfaceVTable vtable; GDBusInterfaceInfo *ifaceinfo; // from gchar* to GVariant* GHashTable *outstanding_properties; guint idle_id; }; G_DEFINE_TYPE_WITH_PRIVATE(GjsDBusImplementation, gjs_dbus_implementation, G_TYPE_DBUS_INTERFACE_SKELETON); static inline GVariant* _g_variant_ref_sink0(void* value) { if (value) g_variant_ref_sink(value); return value; } static inline void _g_variant_unref0(void* value) { if (value) g_variant_unref(value); } static gboolean gjs_dbus_implementation_check_interface( GjsDBusImplementation* self, GDBusConnection* connection, const char* object_path, const char* interface_name, GError** error) { const char* exported_object_path; if (!g_dbus_interface_skeleton_has_connection( G_DBUS_INTERFACE_SKELETON(self), connection)) { g_set_error_literal(error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED, "Wrong connection"); return FALSE; } exported_object_path = g_dbus_interface_skeleton_get_object_path( G_DBUS_INTERFACE_SKELETON(self)); if (!exported_object_path || strcmp(object_path, exported_object_path)) { g_set_error( error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT, "Wrong object path %s for %s", object_path, exported_object_path ? exported_object_path : "unexported object"); return FALSE; } if (strcmp(interface_name, self->priv->ifaceinfo->name) != 0) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface %s on %s", interface_name, self->priv->ifaceinfo->name); return FALSE; } return TRUE; } static gboolean gjs_dbus_implementation_check_property( GjsDBusImplementation* self, const char* interface_name, const char* property_name, GError** error) { if (!g_dbus_interface_info_lookup_property(self->priv->ifaceinfo, property_name)) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property %s on %s", property_name, interface_name); return FALSE; } return TRUE; } static void gjs_dbus_implementation_method_call( GDBusConnection* connection, const char* sender G_GNUC_UNUSED, const char* object_path, const char* interface_name, const char* method_name, GVariant* parameters, GDBusMethodInvocation* invocation, void* user_data) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data); GError* error = NULL; if (!gjs_dbus_implementation_check_interface(self, connection, object_path, interface_name, &error)) { g_dbus_method_invocation_take_error(invocation, error); return; } if (!g_dbus_interface_info_lookup_method(self->priv->ifaceinfo, method_name)) { g_dbus_method_invocation_return_error( invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s on %s", method_name, interface_name); return; } g_signal_emit(self, signals[SIGNAL_HANDLE_METHOD], 0, method_name, parameters, invocation); g_object_unref (invocation); } static GVariant* gjs_dbus_implementation_property_get( GDBusConnection* connection, const char* sender G_GNUC_UNUSED, const char* object_path, const char* interface_name, const char* property_name, GError** error, void* user_data) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data); GVariant *value; if (!gjs_dbus_implementation_check_interface(self, connection, object_path, interface_name, error) || !gjs_dbus_implementation_check_property(self, interface_name, property_name, error)) return NULL; g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_GET], 0, property_name, &value); /* Marshaling GErrors is not supported, so this is the best we can do (GIO will assert if value is NULL and error is not set) */ if (!value) g_set_error(error, g_quark_from_static_string("gjs-error-domain"), 0, "Property retrieval failed"); return value; } static gboolean gjs_dbus_implementation_property_set( GDBusConnection* connection, const char* sender G_GNUC_UNUSED, const char* object_path, const char* interface_name, const char* property_name, GVariant* value, GError** error, void* user_data) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data); if (!gjs_dbus_implementation_check_interface(self, connection, object_path, interface_name, error) || !gjs_dbus_implementation_check_property(self, interface_name, property_name, error)) return FALSE; g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_SET], 0, property_name, value); return TRUE; } static void gjs_dbus_implementation_init(GjsDBusImplementation *self) { GjsDBusImplementationPrivate* priv = gjs_dbus_implementation_get_instance_private(self); self->priv = priv; priv->vtable.method_call = gjs_dbus_implementation_method_call; priv->vtable.get_property = gjs_dbus_implementation_property_get; priv->vtable.set_property = gjs_dbus_implementation_property_set; priv->outstanding_properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref0); } static void gjs_dbus_implementation_dispose(GObject* object) { GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(object); g_clear_handle_id(&self->priv->idle_id, g_source_remove); G_OBJECT_CLASS(gjs_dbus_implementation_parent_class)->dispose(object); } static void gjs_dbus_implementation_finalize(GObject *object) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (object); g_dbus_interface_info_unref (self->priv->ifaceinfo); g_hash_table_destroy(self->priv->outstanding_properties); G_OBJECT_CLASS(gjs_dbus_implementation_parent_class)->finalize(object); } static void gjs_dbus_implementation_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (object); switch (property_id) { case PROP_G_INTERFACE_INFO: self->priv->ifaceinfo = (GDBusInterfaceInfo*) g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static GDBusInterfaceInfo * gjs_dbus_implementation_get_info (GDBusInterfaceSkeleton *skeleton) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton); return self->priv->ifaceinfo; } static GDBusInterfaceVTable * gjs_dbus_implementation_get_vtable (GDBusInterfaceSkeleton *skeleton) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton); return &(self->priv->vtable); } static GVariant * gjs_dbus_implementation_get_properties (GDBusInterfaceSkeleton *skeleton) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton); GDBusInterfaceInfo *info = self->priv->ifaceinfo; GDBusPropertyInfo **props; GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); for (props = info->properties; *props; ++props) { GDBusPropertyInfo *prop = *props; GVariant *value; /* If we have a cached value, we use that instead of querying again */ if ((value = (GVariant*) g_hash_table_lookup(self->priv->outstanding_properties, prop->name))) { g_variant_builder_add(&builder, "{sv}", prop->name, value); continue; } g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_GET], 0, prop->name, &value); g_variant_builder_add(&builder, "{sv}", prop->name, value); } return g_variant_builder_end(&builder); } static void gjs_dbus_implementation_flush (GDBusInterfaceSkeleton *skeleton) { GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION(skeleton); GVariantBuilder changed_props; GVariantBuilder invalidated_props; GHashTableIter iter; GVariant *val; gchar *prop_name; g_variant_builder_init(&changed_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init(&invalidated_props, G_VARIANT_TYPE_STRING_ARRAY); g_hash_table_iter_init(&iter, self->priv->outstanding_properties); while (g_hash_table_iter_next(&iter, (void**) &prop_name, (void**) &val)) { if (val) g_variant_builder_add(&changed_props, "{sv}", prop_name, val); else g_variant_builder_add(&invalidated_props, "s", prop_name); } GList *connections = g_dbus_interface_skeleton_get_connections(skeleton); const char *object_path = g_dbus_interface_skeleton_get_object_path(skeleton); GVariant *properties = g_variant_new("(s@a{sv}@as)", self->priv->ifaceinfo->name, g_variant_builder_end(&changed_props), g_variant_builder_end(&invalidated_props)); g_variant_ref_sink(properties); for (const GList *iter = connections; iter; iter = iter->next) { g_dbus_connection_emit_signal(G_DBUS_CONNECTION(iter->data), NULL, /* bus name */ object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged", properties, NULL /* error */); g_object_unref(iter->data); } g_variant_unref(properties); g_list_free(connections); g_hash_table_remove_all(self->priv->outstanding_properties); g_clear_handle_id(&self->priv->idle_id, g_source_remove); } void gjs_dbus_implementation_class_init(GjsDBusImplementationClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS(klass); gobject_class->dispose = gjs_dbus_implementation_dispose; gobject_class->finalize = gjs_dbus_implementation_finalize; gobject_class->set_property = gjs_dbus_implementation_set_property; skeleton_class->get_info = gjs_dbus_implementation_get_info; skeleton_class->get_vtable = gjs_dbus_implementation_get_vtable; skeleton_class->get_properties = gjs_dbus_implementation_get_properties; skeleton_class->flush = gjs_dbus_implementation_flush; g_object_class_install_property(gobject_class, PROP_G_INTERFACE_INFO, g_param_spec_boxed("g-interface-info", "Interface Info", "A DBusInterfaceInfo representing the exported object", G_TYPE_DBUS_INTERFACE_INFO, (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); signals[SIGNAL_HANDLE_METHOD] = g_signal_new("handle-method-call", G_TYPE_FROM_CLASS(klass), (GSignalFlags) 0, /* flags */ 0, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ NULL, /* C marshal */ G_TYPE_NONE, 3, G_TYPE_STRING, /* method name */ G_TYPE_VARIANT, /* parameters */ G_TYPE_DBUS_METHOD_INVOCATION); signals[SIGNAL_HANDLE_PROPERTY_GET] = g_signal_new("handle-property-get", G_TYPE_FROM_CLASS(klass), (GSignalFlags) 0, /* flags */ 0, /* closure */ g_signal_accumulator_first_wins, NULL, /* accumulator data */ NULL, /* C marshal */ G_TYPE_VARIANT, 1, G_TYPE_STRING /* property name */); signals[SIGNAL_HANDLE_PROPERTY_SET] = g_signal_new("handle-property-set", G_TYPE_FROM_CLASS(klass), (GSignalFlags) 0, /* flags */ 0, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ NULL, /* C marshal */ G_TYPE_NONE, 2, G_TYPE_STRING, /* property name */ G_TYPE_VARIANT /* parameters */); } static gboolean idle_cb (gpointer data) { GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON (data); g_dbus_interface_skeleton_flush(skeleton); return G_SOURCE_REMOVE; } /** * gjs_dbus_implementation_emit_property_changed: * @self: a #GjsDBusImplementation * @property: the name of the property that changed * @newvalue: (allow-none): the new value, or %NULL to just invalidate it * * Queue a PropertyChanged signal for emission, or update the one queued * adding @property */ void gjs_dbus_implementation_emit_property_changed (GjsDBusImplementation *self, gchar *property, GVariant *newvalue) { g_hash_table_replace(self->priv->outstanding_properties, g_strdup(property), _g_variant_ref_sink0(newvalue)); if (!self->priv->idle_id) self->priv->idle_id = g_idle_add(idle_cb, self); } /** * gjs_dbus_implementation_emit_signal: * @self: a #GjsDBusImplementation * @signal_name: the name of the signal * @parameters: (allow-none): signal parameters, or %NULL for none * * Emits a signal named @signal_name from the object and interface represented * by @self. This signal has no destination. */ void gjs_dbus_implementation_emit_signal (GjsDBusImplementation *self, gchar *signal_name, GVariant *parameters) { GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self); GList *connections = g_dbus_interface_skeleton_get_connections(skeleton); const char *object_path = g_dbus_interface_skeleton_get_object_path(skeleton); _g_variant_ref_sink0(parameters); for (const GList *iter = connections; iter; iter = iter->next) { g_dbus_connection_emit_signal(G_DBUS_CONNECTION(iter->data), NULL, object_path, self->priv->ifaceinfo->name, signal_name, parameters, NULL); g_object_unref(iter->data); } _g_variant_unref0(parameters); g_list_free(connections); } /** * gjs_dbus_implementation_unexport: * @self: a #GjsDBusImplementation * * Stops exporting @self on all connections it is exported on. * * To unexport @self from only a single connection, use * gjs_dbus_implementation_skeleton_unexport_from_connection() */ void gjs_dbus_implementation_unexport(GjsDBusImplementation *self) { GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self); g_hash_table_remove_all(self->priv->outstanding_properties); g_clear_handle_id(&self->priv->idle_id, g_source_remove); g_dbus_interface_skeleton_unexport(skeleton); } /** * gjs_dbus_implementation_unexport_from_connection: * @self: a #GjsDBusImplementation * @connection: a #GDBusConnection * * Stops exporting @self on @connection. * * To stop exporting on all connections the interface is exported on, * use gjs_dbus_implementation_unexport(). */ void gjs_dbus_implementation_unexport_from_connection(GjsDBusImplementation *self, GDBusConnection *connection) { GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self); GList *connections = g_dbus_interface_skeleton_get_connections(skeleton); if (g_list_length(connections) <= 1) { g_hash_table_remove_all(self->priv->outstanding_properties); g_clear_handle_id(&self->priv->idle_id, g_source_remove); } g_list_free_full(connections, g_object_unref); g_dbus_interface_skeleton_unexport_from_connection(skeleton, connection); } cjs-128.1/libgjs-private/gjs-gdbus-wrapper.h0000664000175000017500000000420615116312211017661 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2011 Giovanni Campagna */ #ifndef LIBGJS_PRIVATE_GJS_GDBUS_WRAPPER_H_ #define LIBGJS_PRIVATE_GJS_GDBUS_WRAPPER_H_ #include #include #include #include "cjs/macros.h" G_BEGIN_DECLS typedef struct _GjsDBusImplementationPrivate GjsDBusImplementationPrivate; #define GJS_TYPE_DBUS_IMPLEMENTATION (gjs_dbus_implementation_get_type ()) #define GJS_DBUS_IMPLEMENTATION(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GJS_TYPE_DBUS_IMPLEMENTATION, GjsDBusImplementation)) #define GJS_DBUS_IMPLEMENTATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GJS_TYPE_DBUS_IMPLEMENTATION, GjsDBusImplementationClass)) #define GJS_IS_DBUS_IMPLEMENTATION(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GJS_TYPE_DBUS_IMPLEMENTATION)) #define GJS_IS_DBUS_IMPLEMENTATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GJS_TYPE_DBUS_IMPLEMENTATION)) #define GJS_DBUS_IMPLEMENTATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GJS_TYPE_DBUS_IMPLEMENTATION, GjsDBusImplementationClass)) struct _GjsDBusImplementation { GDBusInterfaceSkeleton parent; GjsDBusImplementationPrivate *priv; }; typedef struct _GjsDBusImplementation GjsDBusImplementation; struct _GjsDBusImplementationClass { GDBusInterfaceSkeletonClass parent_class; }; typedef struct _GjsDBusImplementationClass GjsDBusImplementationClass; GJS_EXPORT GType gjs_dbus_implementation_get_type (void); GJS_EXPORT void gjs_dbus_implementation_emit_property_changed (GjsDBusImplementation *self, gchar *property, GVariant *newvalue); GJS_EXPORT void gjs_dbus_implementation_emit_signal (GjsDBusImplementation *self, gchar *signal_name, GVariant *parameters); GJS_EXPORT void gjs_dbus_implementation_unexport(GjsDBusImplementation* self); GJS_EXPORT void gjs_dbus_implementation_unexport_from_connection( GjsDBusImplementation* self, GDBusConnection* connection); G_END_DECLS #endif /* LIBGJS_PRIVATE_GJS_GDBUS_WRAPPER_H_ */ cjs-128.1/libgjs-private/gjs-match-info.c0000664000175000017500000002506715116312211017127 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2023 Philip Chimento */ #include #include #include /* for NULL */ #include #include /* for ssize_t */ #include #include #include "libgjs-private/gjs-match-info.h" G_DEFINE_BOXED_TYPE(GjsMatchInfo, gjs_match_info, gjs_match_info_ref, gjs_match_info_unref) struct _GjsMatchInfo { gatomicrefcount refcount; GMatchInfo* base; /* owned */ char* str; }; /* Takes ownership of string */ static GjsMatchInfo* new_match_info(GMatchInfo* base, char* s) { GjsMatchInfo* retval = g_new0(GjsMatchInfo, 1); g_atomic_ref_count_init(&retval->refcount); retval->base = base; retval->str = s; return retval; } /** * gjs_match_info_get_regex: * @self: a #GjsMatchInfo * * Wrapper for g_match_info_get_regex(). * * Returns: (transfer none): #GRegex object */ GRegex* gjs_match_info_get_regex(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, NULL); return g_match_info_get_regex(self->base); } /** * gjs_match_info_get_string: * @self: a #GjsMatchInfo * * Replacement for g_match_info_get_string(), but the string is owned by @self. * * Returns: (transfer none): the string searched with @match_info */ const char* gjs_match_info_get_string(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, NULL); return self->str; } /** * gjs_match_info_ref: * @self: a #GjsMatchInfo * * Replacement for g_match_info_ref(). * * Returns: @self */ GjsMatchInfo* gjs_match_info_ref(GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, NULL); g_atomic_ref_count_inc(&self->refcount); return self; } /** * gjs_match_info_unref: * @self: a #GjsMatchInfo * * Replacement for g_match_info_unref(). */ void gjs_match_info_unref(GjsMatchInfo* self) { g_return_if_fail(self != NULL); if (g_atomic_ref_count_dec(&self->refcount)) { g_match_info_unref(self->base); g_free(self->str); g_free(self); } } /** * gjs_match_info_free: * @self: (nullable): a #GjsMatchInfo, or %NULL * * Replacement for g_match_info_free(). */ void gjs_match_info_free(GjsMatchInfo* self) { g_return_if_fail(self != NULL); if (self == NULL) return; gjs_match_info_unref(self); } /** * gjs_match_info_next: * @self: a #GjsMatchInfo * @error: location to store the error occurring, or %NULL to ignore errors * * Wrapper for g_match_info_next(). * * Returns: %TRUE or %FALSE */ gboolean gjs_match_info_next(GjsMatchInfo* self, GError** error) { g_return_val_if_fail(self != NULL, FALSE); return g_match_info_next(self->base, error); } /** * gjs_match_info_matches: * @self: a #GjsMatchInfo * * Wrapper for g_match_info_matches(). * * Returns: %TRUE or %FALSE */ gboolean gjs_match_info_matches(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, FALSE); return g_match_info_matches(self->base); } /** * gjs_match_info_get_match_count: * @self: a #GjsMatchInfo * * Wrapper for g_match_info_get_match_count(). * * Returns: Number of matched substrings, or -1 if an error occurred */ int gjs_match_info_get_match_count(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, -1); return g_match_info_get_match_count(self->base); } /** * gjs_match_info_is_partial_match: * @self: a #GjsMatchInfo * * Wrapper for g_match_info_is_partial_match(). * * Returns: %TRUE or %FALSE */ gboolean gjs_match_info_is_partial_match(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, FALSE); return g_match_info_is_partial_match(self->base); } /** * gjs_match_info_expand_references: * @self: (nullable): a #GjsMatchInfo or %NULL * @string_to_expand: the string to expand * @error: location to store the error occurring, or %NULL to ignore errors * * Wrapper for g_match_info_expand_references(). * * Returns: (nullable): the expanded string, or %NULL if an error occurred */ char* gjs_match_info_expand_references(const GjsMatchInfo* self, const char* string_to_expand, GError** error) { return g_match_info_expand_references(self->base, string_to_expand, error); } /** * gjs_match_info_fetch: * @self: a #GjsMatchInfo * @match_num: number of the sub expression * * Wrapper for g_match_info_fetch(). * * Returns: (nullable): The matched substring, or %NULL if an error occurred. */ char* gjs_match_info_fetch(const GjsMatchInfo* self, int match_num) { g_return_val_if_fail(self != NULL, NULL); return g_match_info_fetch(self->base, match_num); } /** * gjs_match_info_fetch_pos: * @self: a #GMatchInfo * @match_num: number of the sub expression * @start_pos: (out) (optional): pointer to location for the start position * @end_pos: (out) (optional): pointer to location for the end position * * Wrapper for g_match_info_fetch_pos(). * * Returns: %TRUE or %FALSE */ gboolean gjs_match_info_fetch_pos(const GjsMatchInfo* self, int match_num, int* start_pos, int* end_pos) { g_return_val_if_fail(self != NULL, FALSE); return g_match_info_fetch_pos(self->base, match_num, start_pos, end_pos); } /** * gjs_match_info_fetch_named: * @self: a #GjsMatchInfo * @name: name of the subexpression * * Wrapper for g_match_info_fetch_named(). * * Returns: (nullable): The matched substring, or %NULL if an error occurred. */ char* gjs_match_info_fetch_named(const GjsMatchInfo* self, const char* name) { g_return_val_if_fail(self != NULL, NULL); return g_match_info_fetch_named(self->base, name); } /** * gjs_match_info_fetch_named_pos: * @self: a #GMatchInfo * @name: name of the subexpression * @start_pos: (out) (optional): pointer to location for the start position * @end_pos: (out) (optional): pointer to location for the end position * * Wrapper for g_match_info_fetch_named_pos(). * * Returns: %TRUE or %FALSE */ gboolean gjs_match_info_fetch_named_pos(const GjsMatchInfo* self, const char* name, int* start_pos, int* end_pos) { g_return_val_if_fail(self != NULL, FALSE); return g_match_info_fetch_named_pos(self->base, name, start_pos, end_pos); } /** * gjs_match_info_fetch_all: * @self: a #GMatchInfo * * Wrapper for g_match_info_fetch_all(). * * Returns: (transfer full): a %NULL-terminated array of strings. If the * previous match failed %NULL is returned */ char** gjs_match_info_fetch_all(const GjsMatchInfo* self) { g_return_val_if_fail(self != NULL, NULL); return g_match_info_fetch_all(self->base); } /** * gjs_regex_match: * @regex: a #GRegex * @s: the string to scan for matches * @match_options: match options * @match_info: (out) (optional): pointer to location for the #GjsMatchInfo * * Wrapper for g_regex_match() that doesn't require the string to be kept alive. * * Returns: %TRUE or %FALSE */ gboolean gjs_regex_match(const GRegex* regex, const char* s, GRegexMatchFlags match_options, GjsMatchInfo** match_info) { return gjs_regex_match_full(regex, (const uint8_t*)s, -1, 0, match_options, match_info, NULL); } /** * gjs_regex_match_full: * @regex: a #GRegex * @bytes: (array length=len): the string to scan for matches * @len: the length of @bytes * @start_position: starting index of the string to match, in bytes * @match_options: match options * @match_info: (out) (optional): pointer to location for the #GjsMatchInfo * @error: location to store the error occurring, or %NULL to ignore errors * * Wrapper for g_regex_match_full() that doesn't require the string to be kept * alive. * * Returns: %TRUE or %FALSE */ gboolean gjs_regex_match_full(const GRegex* regex, const uint8_t* bytes, ssize_t len, int start_position, GRegexMatchFlags match_options, GjsMatchInfo** match_info, GError** error) { const char* s = (const char*)bytes; if (match_info == NULL) return g_regex_match_full(regex, s, len, start_position, match_options, NULL, error); char* string_copy = len < 0 ? g_strdup(s) : g_strndup(s, len); GMatchInfo* base = NULL; bool retval = g_regex_match_full(regex, string_copy, len, start_position, match_options, &base, error); if (base) *match_info = new_match_info(base, string_copy); return retval; } /** * gjs_regex_match_all: * @regex: a #GRegex * @s: the string to scan for matches * @match_options: match options * @match_info: (out) (optional): pointer to location for the #GjsMatchInfo * * Wrapper for g_regex_match_all() that doesn't require the string to be kept * alive. * * Returns: %TRUE or %FALSE */ gboolean gjs_regex_match_all(const GRegex* regex, const char* s, GRegexMatchFlags match_options, GjsMatchInfo** match_info) { return gjs_regex_match_all_full(regex, (const uint8_t*)s, -1, 0, match_options, match_info, NULL); } /** * gjs_regex_match_all_full: * @regex: a #GRegex * @bytes: (array length=len): the string to scan for matches * @len: the length of @bytes * @start_position: starting index of the string to match, in bytes * @match_options: match options * @match_info: (out) (optional): pointer to location for the #GMatchInfo * @error: location to store the error occurring, or %NULL to ignore errors * * Wrapper for g_regex_match_all_full() that doesn't require the string to be * kept alive. * * Returns: %TRUE or %FALSE */ gboolean gjs_regex_match_all_full(const GRegex* regex, const uint8_t* bytes, ssize_t len, int start_position, GRegexMatchFlags match_options, GjsMatchInfo** match_info, GError** error) { const char* s = (const char*)bytes; if (match_info == NULL) return g_regex_match_all_full(regex, s, len, start_position, match_options, NULL, error); char* string_copy = len < 0 ? g_strdup(s) : g_strndup(s, len); GMatchInfo* base = NULL; bool retval = g_regex_match_all_full( regex, string_copy, len, start_position, match_options, &base, error); if (base) *match_info = new_match_info(base, string_copy); return retval; } cjs-128.1/libgjs-private/gjs-match-info.h0000664000175000017500000000607515116312211017132 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2023 Philip Chimento */ #pragma once #include #include /* for ssize_t */ #include #include #include "cjs/macros.h" G_BEGIN_DECLS /** * GjsMatchInfo: * * A GjsMatchInfo is an opaque struct used to return information about * matches. */ typedef struct _GjsMatchInfo GjsMatchInfo; /** * GJS_TYPE_MATCH_INFO: * * The #GType for a boxed type holding a #GjsMatchInfo reference. */ #define GJS_TYPE_MATCH_INFO (gjs_match_info_get_type()) GJS_EXPORT GType gjs_match_info_get_type(void) G_GNUC_CONST; GJS_EXPORT GRegex* gjs_match_info_get_regex(const GjsMatchInfo* self); GJS_EXPORT const char* gjs_match_info_get_string(const GjsMatchInfo* self); GJS_EXPORT GjsMatchInfo* gjs_match_info_ref(GjsMatchInfo* self); GJS_EXPORT void gjs_match_info_unref(GjsMatchInfo* self); GJS_EXPORT void gjs_match_info_free(GjsMatchInfo* self); GJS_EXPORT gboolean gjs_match_info_next(GjsMatchInfo* self, GError** error); GJS_EXPORT gboolean gjs_match_info_matches(const GjsMatchInfo* self); GJS_EXPORT int gjs_match_info_get_match_count(const GjsMatchInfo* self); GJS_EXPORT gboolean gjs_match_info_is_partial_match(const GjsMatchInfo* self); GJS_EXPORT char* gjs_match_info_expand_references(const GjsMatchInfo* self, const char* string_to_expand, GError** error); GJS_EXPORT char* gjs_match_info_fetch(const GjsMatchInfo* self, int match_num); GJS_EXPORT gboolean gjs_match_info_fetch_pos(const GjsMatchInfo* self, int match_num, int* start_pos, int* end_pos); GJS_EXPORT char* gjs_match_info_fetch_named(const GjsMatchInfo* self, const char* name); GJS_EXPORT gboolean gjs_match_info_fetch_named_pos(const GjsMatchInfo* self, const char* name, int* start_pos, int* end_pos); GJS_EXPORT char** gjs_match_info_fetch_all(const GjsMatchInfo* self); GJS_EXPORT gboolean gjs_regex_match(const GRegex* regex, const char* s, GRegexMatchFlags match_options, GjsMatchInfo** match_info); GJS_EXPORT gboolean gjs_regex_match_full(const GRegex* regex, const uint8_t* bytes, ssize_t len, int start_position, GRegexMatchFlags match_options, GjsMatchInfo** match_info, GError** error); GJS_EXPORT gboolean gjs_regex_match_all(const GRegex* regex, const char* s, GRegexMatchFlags match_options, GjsMatchInfo** match_info); GJS_EXPORT gboolean gjs_regex_match_all_full(const GRegex* regex, const uint8_t* bytes, ssize_t len, int start_position, GRegexMatchFlags match_options, GjsMatchInfo** match_info, GError** error); G_END_DECLS cjs-128.1/libgjs-private/gjs-util.c0000664000175000017500000003624415116312211016056 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2012 Giovanni Campagna */ #include #include /* for setlocale */ #include #include /* for size_t */ #include #include #include #include #include /* for bindtextdomain, bind_textdomain_codeset, textdomain */ #include "libgjs-private/gjs-util.h" #include "util/console.h" char * gjs_format_int_alternative_output(int n) { #ifdef HAVE_PRINTF_ALTERNATIVE_INT return g_strdup_printf("%Id", n); #else return g_strdup_printf("%d", n); #endif } GType gjs_locale_category_get_type(void) { static size_t gjs_locale_category_get_type = 0; if (g_once_init_enter(&gjs_locale_category_get_type)) { static const GEnumValue v[] = { {GJS_LOCALE_CATEGORY_ALL, "GJS_LOCALE_CATEGORY_ALL", "all"}, {GJS_LOCALE_CATEGORY_COLLATE, "GJS_LOCALE_CATEGORY_COLLATE", "collate"}, {GJS_LOCALE_CATEGORY_CTYPE, "GJS_LOCALE_CATEGORY_CTYPE", "ctype"}, {GJS_LOCALE_CATEGORY_MESSAGES, "GJS_LOCALE_CATEGORY_MESSAGES", "messages"}, {GJS_LOCALE_CATEGORY_MONETARY, "GJS_LOCALE_CATEGORY_MONETARY", "monetary"}, {GJS_LOCALE_CATEGORY_NUMERIC, "GJS_LOCALE_CATEGORY_NUMERIC", "numeric"}, {GJS_LOCALE_CATEGORY_TIME, "GJS_LOCALE_CATEGORY_TIME", "time"}, {0, NULL, NULL}}; GType g_define_type_id = g_enum_register_static( g_intern_static_string("GjsLocaleCategory"), v); g_once_init_leave(&gjs_locale_category_get_type, g_define_type_id); } return gjs_locale_category_get_type; } /** * gjs_setlocale: * @category: * @locale: (allow-none): * * Returns: */ const char * gjs_setlocale(GjsLocaleCategory category, const char *locale) { /* According to man setlocale(3), the return value may be allocated in * static storage. */ return (const char *) setlocale(category, locale); } void gjs_textdomain(const char *domain) { textdomain(domain); } void gjs_bindtextdomain(const char *domain, const char *location) { bindtextdomain(domain, location); /* Always use UTF-8; we assume it internally here */ bind_textdomain_codeset(domain, "UTF-8"); } GParamFlags gjs_param_spec_get_flags(GParamSpec *pspec) { return pspec->flags; } GType gjs_param_spec_get_value_type(GParamSpec *pspec) { return pspec->value_type; } GType gjs_param_spec_get_owner_type(GParamSpec *pspec) { return pspec->owner_type; } #define G_CLOSURE_NOTIFY(func) ((GClosureNotify)(void (*)(void))func) GBinding* gjs_g_object_bind_property_full( GObject* source, const char* source_property, GObject* target, const char* target_property, GBindingFlags flags, GjsBindingTransformFunc to_callback, void* to_data, GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify) { GClosure* to_closure = NULL; GClosure* from_closure = NULL; if (to_callback) to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data, G_CLOSURE_NOTIFY(to_notify)); if (from_callback) from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data, G_CLOSURE_NOTIFY(from_notify)); return g_object_bind_property_with_closures(source, source_property, target, target_property, flags, to_closure, from_closure); } #if GLIB_CHECK_VERSION(2, 72, 0) void gjs_g_binding_group_bind_full( GBindingGroup* source, const char* source_property, GObject* target, const char* target_property, GBindingFlags flags, GjsBindingTransformFunc to_callback, void* to_data, GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify) { GClosure* to_closure = NULL; GClosure* from_closure = NULL; if (to_callback) to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data, G_CLOSURE_NOTIFY(to_notify)); if (from_callback) from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data, G_CLOSURE_NOTIFY(from_notify)); g_binding_group_bind_with_closures(source, source_property, target, target_property, flags, to_closure, from_closure); } #endif #undef G_CLOSURE_NOTIFY static GParamSpec* gjs_gtk_container_class_find_child_property( GIObjectInfo* container_info, GObject* container, const char* property) { GIBaseInfo* class_info = NULL; GIBaseInfo* find_child_property_fun = NULL; GIArgument ret; GIArgument find_child_property_args[2]; class_info = g_object_info_get_class_struct(container_info); find_child_property_fun = g_struct_info_find_method(class_info, "find_child_property"); find_child_property_args[0].v_pointer = G_OBJECT_GET_CLASS(container); find_child_property_args[1].v_string = (char*)property; g_function_info_invoke(find_child_property_fun, find_child_property_args, 2, NULL, 0, &ret, NULL); g_clear_pointer(&class_info, g_base_info_unref); g_clear_pointer(&find_child_property_fun, g_base_info_unref); return (GParamSpec*)ret.v_pointer; } void gjs_gtk_container_child_set_property(GObject* container, GObject* child, const char* property, const GValue* value) { GParamSpec* pspec = NULL; GIBaseInfo* base_info = NULL; GIBaseInfo* child_set_property_fun = NULL; GIObjectInfo* container_info; GValue value_arg = G_VALUE_INIT; GIArgument ret; GIArgument child_set_property_args[4]; base_info = g_irepository_find_by_name(NULL, "Gtk", "Container"); container_info = (GIObjectInfo*)base_info; pspec = gjs_gtk_container_class_find_child_property(container_info, container, property); if (pspec == NULL) { g_warning("%s does not have a property called %s", g_type_name(G_OBJECT_TYPE(container)), property); goto out; } if ((G_VALUE_TYPE(value) == G_TYPE_POINTER) && (g_value_get_pointer(value) == NULL) && !g_value_type_transformable(G_VALUE_TYPE(value), pspec->value_type)) { /* Set an empty value. This will happen when we set a NULL value from * JS. Since GJS doesn't know the GParamSpec for this property, it will * just put NULL into a G_TYPE_POINTER GValue, which will later fail * when trying to transform it to the GParamSpec's GType. */ g_value_init(&value_arg, pspec->value_type); } else { g_value_init(&value_arg, G_VALUE_TYPE(value)); g_value_copy(value, &value_arg); } child_set_property_fun = g_object_info_find_method(container_info, "child_set_property"); child_set_property_args[0].v_pointer = container; child_set_property_args[1].v_pointer = child; child_set_property_args[2].v_string = (char*)property; child_set_property_args[3].v_pointer = &value_arg; g_function_info_invoke(child_set_property_fun, child_set_property_args, 4, NULL, 0, &ret, NULL); g_value_unset(&value_arg); out: g_clear_pointer(&base_info, g_base_info_unref); g_clear_pointer(&child_set_property_fun, g_base_info_unref); } /** * gjs_list_store_insert_sorted: * @store: a #GListStore * @item: the new item * @compare_func: (scope call): pairwise comparison function for sorting * @user_data: user data for @compare_func * * Inserts @item into @store at a position to be determined by the * @compare_func. * * The list must already be sorted before calling this function or the * result is undefined. Usually you would approach this by only ever * inserting items by way of this function. * * This function takes a ref on @item. * * Returns: the position at which @item was inserted */ unsigned int gjs_list_store_insert_sorted(GListStore *store, GObject *item, GjsCompareDataFunc compare_func, void *user_data) { return g_list_store_insert_sorted(store, item, (GCompareDataFunc)compare_func, user_data); } /** * gjs_list_store_sort: * @store: a #GListStore * @compare_func: (scope call): pairwise comparison function for sorting * @user_data: user data for @compare_func * * Sort the items in @store according to @compare_func. */ void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func, void *user_data) { g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data); } /** * gjs_gtk_custom_sorter_new: * @sort_func: (nullable) (scope call): function to sort items * @user_data: user data for @sort_func * @destroy: destroy notify for @user_data * * Creates a new `GtkSorter` that works by calling @sort_func to compare items. * * If @sort_func is %NULL, all items are considered equal. * * Returns: (transfer full): a new `GtkCustomSorter` */ GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy) { GIObjectInfo* container_info = g_irepository_find_by_name(NULL, "Gtk", "CustomSorter"); GIBaseInfo* custom_sorter_new_fun = g_object_info_find_method(container_info, "new"); GIArgument ret; GIArgument custom_sorter_new_args[3]; custom_sorter_new_args[0].v_pointer = sort_func; custom_sorter_new_args[1].v_pointer = user_data; custom_sorter_new_args[2].v_pointer = destroy; g_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3, NULL, 0, &ret, NULL); g_clear_pointer(&container_info, g_base_info_unref); g_clear_pointer(&custom_sorter_new_fun, g_base_info_unref); return (GObject*)ret.v_pointer; } /** * gjs_gtk_custom_sorter_set_sort_func: * @sorter: a `GtkCustomSorter` * @sort_func: (nullable) (scope call): function to sort items * @user_data: user data to pass to @sort_func * @destroy: destroy notify for @user_data * * Sets (or unsets) the function used for sorting items. * * If @sort_func is %NULL, all items are considered equal. * * If the sort func changes its sorting behavior, gtk_sorter_changed() needs to * be called. * * If a previous function was set, its @user_destroy will be called now. */ void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter, GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy) { GIObjectInfo* container_info = g_irepository_find_by_name(NULL, "Gtk", "CustomSorter"); GIBaseInfo* set_sort_func_fun = g_object_info_find_method(container_info, "set_sort_func"); GIArgument unused_ret; GIArgument set_sort_func_args[4]; set_sort_func_args[0].v_pointer = sorter; set_sort_func_args[1].v_pointer = sort_func; set_sort_func_args[2].v_pointer = user_data; set_sort_func_args[3].v_pointer = destroy; g_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0, &unused_ret, NULL); g_clear_pointer(&container_info, g_base_info_unref); g_clear_pointer(&set_sort_func_fun, g_base_info_unref); } static void* log_writer_user_data = NULL; static GDestroyNotify log_writer_user_data_free = NULL; static GThread* log_writer_thread = NULL; static GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level, const GLogField* fields, size_t n_fields, void* user_data) { g_assert(log_writer_thread); // If the wrapper is called from a thread other than the one that set it, // return unhandled so the fallback logger is used. if (g_thread_self() != log_writer_thread) return g_log_writer_default(log_level, fields, n_fields, NULL); GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data; GVariantDict dict; g_variant_dict_init(&dict, NULL); size_t f; for (f = 0; f < n_fields; f++) { const GLogField* field = &fields[f]; GVariant* value; if (field->length < 0) { size_t bytes_len = strlen(field->value); GBytes* bytes = g_bytes_new(field->value, bytes_len); value = g_variant_new_maybe( G_VARIANT_TYPE_BYTESTRING, g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes, true)); g_bytes_unref(bytes); } else if (field->length > 0) { GBytes* bytes = g_bytes_new(field->value, field->length); value = g_variant_new_maybe( G_VARIANT_TYPE_BYTESTRING, g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes, true)); g_bytes_unref(bytes); } else { value = g_variant_new_maybe(G_VARIANT_TYPE_STRING, NULL); } g_variant_dict_insert_value(&dict, field->key, value); } GVariant* string_fields = g_variant_dict_end(&dict); g_variant_ref(string_fields); GLogWriterOutput output = func(log_level, string_fields, log_writer_user_data); g_variant_unref(string_fields); // If the function did not handle the log, fallback to the default // handler. if (output == G_LOG_WRITER_UNHANDLED) return g_log_writer_default(log_level, fields, n_fields, NULL); return output; } /** * gjs_log_set_writer_default: * * Sets the structured logging writer function back to the platform default. */ void gjs_log_set_writer_default() { if (log_writer_user_data_free) { log_writer_user_data_free(log_writer_user_data); } g_log_set_writer_func(g_log_writer_default, NULL, NULL); log_writer_user_data_free = NULL; log_writer_user_data = NULL; log_writer_thread = NULL; } /** * gjs_log_set_writer_func: * @func: (scope notified): callback with log data * @user_data: user data for @func * @user_data_free: (destroy user_data_free): destroy for @user_data * * Sets a given function as the writer function for structured logging, * passing log fields as a variant. If called from JavaScript the application * must call gjs_log_set_writer_default prior to exiting. */ void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data, GDestroyNotify user_data_free) { log_writer_user_data = user_data; log_writer_user_data_free = user_data_free; log_writer_thread = g_thread_self(); g_log_set_writer_func(gjs_log_writer_func_wrapper, func, NULL); } /** * gjs_clear_terminal: * * Clears the terminal, if possible. */ void gjs_clear_terminal(void) { if (!gjs_console_is_tty(stdout_fd)) return; gjs_console_clear(); } cjs-128.1/libgjs-private/gjs-util.h0000664000175000017500000001265315116312211016061 0ustar fabiofabio/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2012 Giovanni Campagna */ #ifndef LIBGJS_PRIVATE_GJS_UTIL_H_ #define LIBGJS_PRIVATE_GJS_UTIL_H_ #include #include #include #include #include "cjs/macros.h" G_BEGIN_DECLS /* For imports.format */ GJS_EXPORT char * gjs_format_int_alternative_output (int n); /** * GjsCompareDataFunc: * @a: a value * @b: a value to compare with * @user_data: user data * * Specifies the type of a comparison function used to compare two * values. The function should return a negative integer if the first * value comes before the second, 0 if they are equal, or a positive * integer if the first value comes after the second. * * Returns: negative value if @a < @b; zero if @a = @b; positive * value if @a > @b */ typedef int (*GjsCompareDataFunc)(const GObject *a, const GObject *b, void *user_data); GJS_EXPORT unsigned gjs_list_store_insert_sorted(GListStore *store, GObject *item, GjsCompareDataFunc compare_func, void *user_data); GJS_EXPORT void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func, void *user_data); GJS_EXPORT GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy); GJS_EXPORT void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter, GjsCompareDataFunc sort_func, void* user_data, GDestroyNotify destroy); /** * GjsGLogWriterFunc: * @level: the log level * @fields: a dictionary variant with type a{sms} * @user_data: user data */ typedef GLogWriterOutput (*GjsGLogWriterFunc)(GLogLevelFlags level, const GVariant* fields, void* user_data); GJS_EXPORT void gjs_log_set_writer_func(GjsGLogWriterFunc func, gpointer user_data, GDestroyNotify user_data_free); GJS_EXPORT void gjs_log_set_writer_default(); /* For imports.gettext */ typedef enum { GJS_LOCALE_CATEGORY_ALL = LC_ALL, GJS_LOCALE_CATEGORY_COLLATE = LC_COLLATE, GJS_LOCALE_CATEGORY_CTYPE = LC_CTYPE, GJS_LOCALE_CATEGORY_MESSAGES = LC_MESSAGES, GJS_LOCALE_CATEGORY_MONETARY = LC_MONETARY, GJS_LOCALE_CATEGORY_NUMERIC = LC_NUMERIC, GJS_LOCALE_CATEGORY_TIME = LC_TIME } GjsLocaleCategory; GJS_EXPORT const char *gjs_setlocale (GjsLocaleCategory category, const char *locale); GJS_EXPORT void gjs_textdomain (const char *domain); GJS_EXPORT void gjs_bindtextdomain (const char *domain, const char *location); GJS_EXPORT GType gjs_locale_category_get_type (void) G_GNUC_CONST; /* For imports.overrides.GObject */ GJS_EXPORT GParamFlags gjs_param_spec_get_flags (GParamSpec *pspec); GJS_EXPORT GType gjs_param_spec_get_value_type (GParamSpec *pspec); GJS_EXPORT GType gjs_param_spec_get_owner_type (GParamSpec *pspec); /** * GjsBindingTransformFunc: * @binding: * @from_value: * @to_value: (out): * @user_data: */ typedef gboolean (*GjsBindingTransformFunc)(GBinding* binding, const GValue* from_value, GValue* to_value, void* user_data); /** * gjs_g_object_bind_property_full: * @source: * @source_property: * @target: * @target_property: * @flags: * @to_callback: (scope notified) (nullable) (closure to_data): * @to_data: * @to_notify: (destroy to_data): * @from_callback: (scope notified) (nullable) (closure from_data): * @from_data: * @from_notify: (destroy from_data): * * Returns: (transfer none): */ GJS_EXPORT GBinding* gjs_g_object_bind_property_full( GObject* source, const char* source_property, GObject* target, const char* target_property, GBindingFlags flags, GjsBindingTransformFunc to_callback, void* to_data, GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify); #if GLIB_CHECK_VERSION(2, 72, 0) /** * gjs_g_binding_group_bind_full: * @source: * @source_property: * @target: * @target_property: * @flags: * @to_callback: (scope notified) (nullable) (closure to_data): * @to_data: * @to_notify: (destroy to_data): * @from_callback: (scope notified) (nullable) (closure from_data): * @from_data: * @from_notify: (destroy from_data): */ GJS_EXPORT void gjs_g_binding_group_bind_full( GBindingGroup* source, const char* source_property, GObject* target, const char* target_property, GBindingFlags flags, GjsBindingTransformFunc to_callback, void* to_data, GDestroyNotify to_notify, GjsBindingTransformFunc from_callback, void* from_data, GDestroyNotify from_notify); #endif /* For imports.overrides.Gtk */ GJS_EXPORT void gjs_gtk_container_child_set_property(GObject* container, GObject* child, const char* property, const GValue* value); GJS_EXPORT void gjs_clear_terminal(void); G_END_DECLS #endif /* LIBGJS_PRIVATE_GJS_UTIL_H_ */ cjs-128.1/meson.build0000664000175000017500000007146715116312211013402 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Philip Chimento # SPDX-FileCopyrightText: 2019 Chun-wei Fan project( 'cjs', 'cpp', 'c', version : '128.1', license : ['MIT', 'LGPL2+'], meson_version : '>= 0.62.0', default_options : [ 'cpp_std=c++17', 'cpp_rtti=false', 'cpp_eh=none', 'c_std=c99', 'warning_level=2', 'b_pch=true' ] ) # cpp_rtti: SpiderMonkey can be compiled with or without runtime type # information, and the default is without. We must match that option because we # need to derive from SpiderMonkey classes. api_version = '1.0' api_name = '@0@-@1@'.format(meson.project_name(), api_version) gnome = import('gnome') pkg = import('pkgconfig') top_include = include_directories('.') prefix = get_option('prefix') bindir = get_option('bindir') libdir = get_option('libdir') datadir = get_option('datadir') libexecdir = get_option('libexecdir') cjsjsdir = datadir / api_name pkglibdir = libdir / meson.project_name() installed_tests_execdir = libexecdir / 'installed-tests' / meson.project_name() installed_tests_metadir = datadir / 'installed-tests' / meson.project_name() ### Check for conflicting build options ######################################## if get_option('systemtap') and not get_option('dtrace') error('-Ddtrace=true is required for -Dsystemtap=true') endif release_build = get_option('buildtype').startswith('release') if release_build and get_option('verbose_logs') error('-Dverbose_logs=true is not allowed with --buildtype=release') endif ### Check for compiler args #################################################### cxx = meson.get_compiler('cpp') cc = meson.get_compiler('c') if cc.get_id() == 'msvc' add_project_arguments(cxx.get_supported_arguments([ '-utf-8', # Use UTF-8 mode '/Zc:externConstexpr', # Required for 'extern constexpr' on MSVC '/Zc:preprocessor', # Required to consume the mozjs-128 headers on MSVC # Ignore spurious compiler warnings for things that GLib and SpiderMonkey # header files commonly do '-FImsvc_recommended_pragmas.h', '-EHsc', '-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS', # Don't worry about the C++17 deprecations '-D__PRETTY_FUNCTION__=__FUNCSIG__', '-wd4099', '-wd4251', '-wd4291', '-wd4800', '-wd5030', ]), language: ['cpp', 'c']) else # Ignore spurious compiler warnings for things that GLib and SpiderMonkey # header files commonly do add_project_arguments(cxx.get_supported_arguments([ '-fno-strict-aliasing', '-Wno-variadic-macros', # GLib uses these in header files '-Wno-missing-field-initializers', # SpiderMonkey JSClass, among others '-Wno-dangling-pointer', # Root list in JS::Rooted with GCC 12 ]), language: 'cpp') add_project_arguments(cc.get_supported_arguments([ '-Wno-typedef-redefinition', # GLib does this in header files ]), language: 'c') endif if cc.get_argument_syntax() == 'msvc' add_project_arguments(cxx.get_supported_arguments([ '-Dssize_t=gssize', # Windows SDK/MSVC headers do not come with ssize_t '-DNOMINMAX', # We don't want 'min' or 'max' to interfere '-DSSIZE_MAX=G_MAXSSIZE', # Windows SDK/MSVC headers do not come with SSIZE_MAX ]), language: ['cpp', 'c']) else if get_option('bsymbolic_functions') if not cxx.has_link_argument('-Bsymbolic-functions') error('''-Bsymbolic-functions not supported, configure with -Dbsymbolic_functions=false''') endif add_project_link_arguments('-Bsymbolic-functions', language: ['cpp', 'c']) if cc.has_argument('-fno-semantic-interposition') add_project_arguments('-fno-semantic-interposition', language: 'c') endif if cxx.has_argument('-fno-semantic-interposition') add_project_arguments('-fno-semantic-interposition', language: 'cpp') endif endif endif # -fno-rtti is not compatible with the vptr sanitizer (part of ubsan) if not get_option('cpp_rtti') and get_option('b_sanitize') != 'none' and \ cxx.has_argument('-fno-sanitize=vptr') add_project_arguments('-fno-sanitize=vptr', language: 'cpp') endif if get_option('verbose_logs') add_project_arguments([ '-DGJS_VERBOSE_ENABLE_PROPS=1', '-DGJS_VERBOSE_ENABLE_MARSHAL=1', '-DGJS_VERBOSE_ENABLE_LIFECYCLE=1', '-DGJS_VERBOSE_ENABLE_GI_USAGE=1', '-DGJS_VERBOSE_ENABLE_GCLOSURE=1', '-DGJS_VERBOSE_ENABLE_GSIGNAL=1', ], language: 'cpp') endif if release_build add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: ['c', 'cpp']) endif ### Check for required libraries ############################################### null_dep = dependency('', required : false) # Note: Notify GNOME release team when adding or updating dependencies glib_required_version = '>= 2.66.0' glib = dependency('glib-2.0', version: glib_required_version, fallback: ['glib', 'libglib_dep']) gthread = dependency('gthread-2.0', version: glib_required_version, fallback: ['glib', 'libgthread_dep']) gobject = dependency('gobject-2.0', version: glib_required_version, fallback: ['glib', 'libgobject_dep']) gio = dependency('gio-2.0', version: glib_required_version, fallback: ['glib', 'libgio_dep']) ffi = dependency('libffi', fallback: ['libffi', 'ffi_dep']) gi = dependency('gobject-introspection-1.0', version: '>= 1.66.0', fallback: ['gobject-introspection', 'girepo_dep']) cairo = dependency('cairo', fallback: ['cairo', 'libcairo_dep']) cairo_gobject = dependency('cairo-gobject', fallback: ['cairo', 'libcairogobject_dep']) cairo_xlib = dependency('cairo-xlib', required: false) spidermonkey = dependency('mozjs-128') sysprof_capture = dependency('sysprof-capture-4', required: get_option('profiler'), include_type: 'system', fallback: ['sysprof', 'libsysprof_capture_dep'], default_options: [ 'agent=false', 'examples=false', 'gtk=false', 'tests=false', 'tools=false', 'libsysprof=false', 'sysprofd=none', 'help=false', ]) readline = cxx.find_library('readline', required: get_option('readline')) # On some systems we need to link readline to a termcap compatible library readline_code = ''' #include #include int main(void) { readline("foo"); return 0; }''' readline_deps = [readline] if readline.found() and not cxx.links(readline_code, dependencies: readline) extra_readline_libs = ['ncursesw', 'ncurses', 'curses', 'termcap'] found = false foreach lib : extra_readline_libs termcap = cxx.find_library(lib, required: false) if cxx.links(readline_code, dependencies: [readline, termcap]) found = true readline_deps += termcap break endif endforeach if not found error('''Couldn't figure out how to link readline library. Configure with -Dreadline=disabled to skip the readline features.''') endif endif if cxx.links(''' #include int main(void) { std::atomic_int64_t value = ATOMIC_VAR_INIT(0); return value.load(); } ''', name: '64-bit atomics built-in') libatomic = null_dep else libatomic = cc.find_library('atomic', required: false) endif build_profiler = sysprof_capture.found() profiler_deps = [sysprof_capture] if build_profiler and not cxx.has_function('timer_settime') extra_timer_libs = ['rt', 'posix4'] found = false foreach lib : extra_timer_libs timer_lib = cxx.find_library(lib, required: false) if cxx.has_function('timer_settime', dependencies: timer_lib) found = true profiler_deps += timer_lib break endif endforeach if not found or not cxx.has_header_symbol('signal.h', 'SIGEV_THREAD_ID') if get_option('profiler').enabled() error('''The profiler is currently only supported on Linux. The standard library must support timer_settime() and SIGEV_THREAD_ID. Configure with -Dprofiler=auto or -Dprofiler=disabled to skip it on other platforms.''') endif build_profiler = false endif endif build_readline = readline.found() ### Check for library features ################################################# # Check if SpiderMonkey was compiled with --enable-debug. If this is the case, # you must compile all your sources with -DDEBUG=1 # See https://bugzilla.mozilla.org/show_bug.cgi?id=1261161 debug_arg = [] nondebug_spidermonkey = cxx.compiles(''' #include #ifdef JS_DEBUG #error debug yes, if we did not already error out due to DEBUG not being defined #endif ''', dependencies: spidermonkey, name: 'SpiderMonkey is a non-debug build') if not nondebug_spidermonkey debug_arg = ['-DDEBUG'] # for compile tests endif if release_build and not nondebug_spidermonkey error('''You are trying to make a release build with a debug-enabled copy of SpiderMonkey. This is probably not what you want, since it will have bad performance and is not binary-compatible with release builds of SpiderMonkey. Try configuring SpiderMonkey with --disable-debug.''') endif # Check if a minimal SpiderMonkey program compiles, links, and runs. If not, # it's most likely the case that SpiderMonkey was configured incorrectly, for # example by building mozglue as a shared library. minimal_program = cxx.run(''' #include int main(void) { if (!JS_Init()) return 1; JS_ShutDown(); return 0; } ''', args: debug_arg, dependencies: spidermonkey, name: 'SpiderMonkey sanity check') recommended_configuration = ''' Check the recommended configuration: https://github.com/spidermonkey-embedders/spidermonkey-embedding-examples/blob/esr91/docs/Building%20SpiderMonkey.md''' if not minimal_program.compiled() error('''A minimal SpiderMonkey program could not be compiled or linked. Most likely you should build it with a different configuration.''' + recommended_configuration) elif meson.is_cross_build() warning('''This is a cross build. A check that a minimal SpiderMonkey program executes will not be performed. Before shipping GJS, you should check that it does not crash on startup, since building SpiderMonkey with the wrong configuration may cause that.''' + recommended_configuration) elif minimal_program.returncode() != 0 error('''A minimal SpiderMonkey program failed to execute. Most likely you should build it with a different configuration.''' + recommended_configuration) endif have_printf_alternative_int = cc.compiles(''' #include int main(void) { printf("%Id", (int)0); return 0; } ''', args: ['-Werror', '-Wformat'], name: 'printf() supports %I alternative int syntax') ### Check for external programs ################################################ dtrace = find_program('dtrace', required: get_option('dtrace')) dbus_run_session = find_program('dbus-run-session', required: not get_option('skip_dbus_tests')) glib_compile_schemas = find_program('glib-compile-schemas') ### Generate config.h ########################################################## header_conf = configuration_data() versions = meson.project_version().split('.') major_version = versions[0].to_int() header_conf.set_quoted('VERSION', meson.project_version()) header_conf.set('GJS_VERSION', major_version, description: 'The GJS version as an integer') header_conf.set_quoted('PACKAGE_STRING', '@0@ @1@'.format(meson.project_name(), meson.project_version())) header_conf.set('ENABLE_PROFILER', build_profiler, description: 'Build the profiler') # COMPAT: SpiderMonkey headers in some places use DEBUG instead of JS_DEBUG # https://bugzilla.mozilla.org/show_bug.cgi?id=1261161 */ header_conf.set('DEBUG', not nondebug_spidermonkey, description: 'SpiderMonkey was compiled with --enable-debug') header_conf.set('HAVE_DTRACE', get_option('dtrace'), description: 'Using dtrace probes') header_conf.set('HAVE_PRINTF_ALTERNATIVE_INT', have_printf_alternative_int, description: 'printf() accepts "%Id" for alternative integer output') if build_readline header_conf.set('HAVE_READLINE_READLINE_H', cxx.check_header('readline/readline.h', prefix: '#include ', required: readline.found())) endif header_conf.set('USE_UNITY_BUILD', get_option('unity')) header_conf.set('HAVE_SYS_SYSCALL_H', cxx.check_header('sys/syscall.h')) header_conf.set('HAVE_UNISTD_H', cxx.check_header('unistd.h')) header_conf.set('HAVE_SIGNAL_H', cxx.check_header('signal.h', required: build_profiler)) # enable GNU extensions on systems that have them header_conf.set('_GNU_SOURCE', 1) configure_file(output: 'config.h', configuration: header_conf) ### Build dtrace probes ######################################################## if get_option('dtrace') probes_header_gen = generator(dtrace, output: '@BASENAME@.h', arguments: ['-C', '-h', '-s', '@INPUT@', '-o', '@OUTPUT@']) probes_objfile_gen = generator(dtrace, output: '@BASENAME@.o', arguments: ['-G', '-s', '@INPUT@', '-o', '@OUTPUT@']) probes_header = probes_header_gen.process('gi/cjs_gi_probes.d') probes_objfile = probes_objfile_gen.process('gi/cjs_gi_probes.d') else probes_header = [] probes_objfile = [] endif tapset_subst = configuration_data({'EXPANDED_LIBDIR': libdir}) tapset = configure_file(input: 'cjs/cjs.stp.in', output: 'cjs.stp', configuration: tapset_subst) if get_option('systemtap') install_data(tapset, install_dir: datadir / 'systemtap' / 'tapset') endif ### Build library ############################################################## directory_defines = [ '-DGJS_JS_DIR="@0@"'.format(prefix / cjsjsdir), '-DPKGLIBDIR="@0@"'.format(prefix / pkglibdir), ] cjs_public_headers = [ 'cjs/context.h', 'cjs/coverage.h', 'cjs/error-types.h', 'cjs/gjs.h', 'cjs/macros.h', 'cjs/mem.h', 'cjs/profiler.h', ] # For historical reasons, some files live in gi/ # Some headers in the following list were formerly public libcjs_sources = [ 'gi/arg.cpp', 'gi/arg.h', 'gi/arg-inl.h', 'gi/arg-cache.cpp', 'gi/arg-cache.h', 'gi/boxed.cpp', 'gi/boxed.h', 'gi/closure.cpp', 'gi/closure.h', 'gi/cwrapper.cpp', 'gi/cwrapper.h', 'gi/enumeration.cpp', 'gi/enumeration.h', 'gi/foreign.cpp', 'gi/foreign.h', 'gi/fundamental.cpp', 'gi/fundamental.h', 'gi/function.cpp', 'gi/function.h', 'gi/gerror.cpp', 'gi/gerror.h', 'gi/cjs_gi_trace.h', 'gi/gobject.cpp', 'gi/gobject.h', 'gi/gtype.cpp', 'gi/gtype.h', 'gi/interface.cpp', 'gi/interface.h', 'gi/ns.cpp', 'gi/ns.h', 'gi/object.cpp', 'gi/object.h', 'gi/param.cpp', 'gi/param.h', 'gi/private.cpp', 'gi/private.h', 'gi/repo.cpp', 'gi/repo.h', 'gi/toggle.cpp', 'gi/toggle.h', 'gi/union.cpp', 'gi/union.h', 'gi/utils-inl.h', 'gi/value.cpp', 'gi/value.h', 'gi/wrapperutils.cpp', 'gi/wrapperutils.h', 'cjs/atoms.cpp', 'cjs/atoms.h', 'cjs/byteArray.cpp', 'cjs/byteArray.h', 'cjs/context.cpp', 'cjs/context-private.h', 'cjs/coverage.cpp', 'cjs/debugger.cpp', 'cjs/deprecation.cpp', 'cjs/deprecation.h', 'cjs/engine.cpp', 'cjs/engine.h', 'cjs/error-types.cpp', 'cjs/global.cpp', 'cjs/global.h', 'cjs/importer.cpp', 'cjs/importer.h', 'cjs/internal.cpp', 'cjs/internal.h', 'cjs/mainloop.cpp', 'cjs/mainloop.h', 'cjs/mem.cpp', 'cjs/mem-private.h', 'cjs/module.cpp', 'cjs/module.h', 'cjs/native.cpp', 'cjs/native.h', 'cjs/objectbox.cpp', 'cjs/objectbox.h', 'cjs/profiler.cpp', 'cjs/profiler-private.h', 'cjs/text-encoding.cpp', 'cjs/text-encoding.h', 'cjs/promise.cpp', 'cjs/promise.h', 'cjs/stack.cpp', 'modules/console.cpp', 'modules/console.h', 'modules/print.cpp', 'modules/print.h', 'modules/system.cpp', 'modules/system.h', 'modules/cairo-private.h', 'modules/cairo-module.h', 'modules/cairo-region.cpp', 'modules/cairo-context.cpp', 'modules/cairo-path.cpp', 'modules/cairo-surface.cpp', 'modules/cairo-image-surface.cpp', 'modules/cairo-ps-surface.cpp', 'modules/cairo-pdf-surface.cpp', 'modules/cairo-svg-surface.cpp', 'modules/cairo-pattern.cpp', 'modules/cairo-gradient.cpp', 'modules/cairo-linear-gradient.cpp', 'modules/cairo-radial-gradient.cpp', 'modules/cairo-surface-pattern.cpp', 'modules/cairo-solid-pattern.cpp', 'modules/cairo.cpp', ] # CjsPrivate introspection sources libcjs_private_sources = [ 'libgjs-private/gjs-gdbus-wrapper.c', 'libgjs-private/gjs-gdbus-wrapper.h', 'libgjs-private/gjs-match-info.c', 'libgjs-private/gjs-match-info.h', 'libgjs-private/gjs-util.c', 'libgjs-private/gjs-util.h', ] libcjs_jsapi_sources = [ 'cjs/jsapi-class.h', 'cjs/jsapi-dynamic-class.cpp', 'cjs/jsapi-util-args.h', 'cjs/jsapi-util-error.cpp', 'cjs/jsapi-util-root.h', 'cjs/jsapi-util-string.cpp', 'cjs/jsapi-util.cpp', 'cjs/jsapi-util.h', 'util/console.cpp', 'util/console.h', 'util/log.cpp', 'util/log.h', 'util/misc.cpp', 'util/misc.h', ] module_resource_srcs = gnome.compile_resources('js-resources', 'js.gresource.xml', c_name: 'js_resources') module_resource_lib = static_library('js-resources', module_resource_srcs, dependencies: gio, override_options: ['unity=off']) libcjs_dependencies = [glib, gobject, gthread, gio, gi, ffi, cairo, cairo_gobject, spidermonkey, readline, libatomic] pkg_dependencies = [glib, gobject, gthread, gio, gi, ffi, cairo, cairo_gobject, spidermonkey] if cairo_xlib.found() libcjs_dependencies += cairo_xlib pkg_dependencies += cairo_xlib endif if build_readline libcjs_dependencies += readline_deps endif libcjs_cpp_args = ['-DGJS_COMPILATION'] + directory_defines # Check G-I and/or Meson on this one. libcjs_cpp_args += ['-DG_LOG_DOMAIN="Cjs"'] if host_machine.system() == 'windows' # We need these defines to build properly for all Windows builds libcjs_cpp_args += ['-DWIN32', '-DXP_WIN', '-DWIN32_LEAN_AND_MEAN'] endif # This dependency should provide everything that is needed to compile cjs except # the sources themselves, is used to compile both the static libraries and the # tests base_build_dep = declare_dependency( compile_args: libcjs_cpp_args, dependencies: libcjs_dependencies) internal_build_dep = declare_dependency( compile_args: (release_build ? ['-DG_DISABLE_ASSERT'] : []), dependencies: [ base_build_dep, build_profiler ? profiler_deps : [], ]) libcjs_jsapi = static_library(meson.project_name() + '-jsapi', libcjs_jsapi_sources, probes_header, probes_objfile, cpp_pch: 'cjs/cjs_pch.hh', dependencies: internal_build_dep, install: false) # We need to create an internal static library to be able to link with the tests # that may use internal APIs. This is also used to generate the actual shared # library so that we compile its sources just once. libcjs_internal = static_library('cjs-internal', libcjs_sources, probes_header, probes_objfile, cpp_pch: 'cjs/cjs_pch.hh', dependencies: internal_build_dep, link_with: libcjs_jsapi) link_args = [] symbol_map = 'libcjs.map' symbol_list = 'libcjs.symbols' link_args += cxx.get_supported_link_arguments([ '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), symbol_map), '-Wl,-exported_symbols_list,@0@/@1@'.format(meson.current_source_dir(), symbol_list), # macOS linker ]) libcjs = shared_library(meson.project_name(), sources: libcjs_private_sources, link_args: link_args, link_depends: [symbol_map, symbol_list], link_whole: [libcjs_internal, module_resource_lib], dependencies: base_build_dep, version: '0.0.0', soversion: '0', gnu_symbol_visibility: 'hidden', install: true) install_headers(cjs_public_headers, subdir: api_name / 'cjs') # Allow using libcjs as a subproject libcjs_dep = declare_dependency(link_with: [libcjs, libcjs_jsapi], dependencies: base_build_dep, include_directories: top_include) ### Build CjsPrivate introspection library ##################################### cjs_private_gir = gnome.generate_gir(libcjs, includes: ['GObject-2.0', 'Gio-2.0'], sources: libcjs_private_sources, namespace: 'CjsPrivate', nsversion: '1.0', identifier_prefix: 'Gjs', symbol_prefix: 'gjs_', fatal_warnings: get_option('werror'), install: true, install_gir: false, install_dir_typelib: pkglibdir / 'girepository-1.0') cjs_private_typelib = cjs_private_gir[1] ### Build cjs-console interpreter ############################################## cjs_console_srcs = ['cjs/console.cpp'] cjs_console = executable('cjs-console', cjs_console_srcs, dependencies: libcjs_dep, install: true) meson.add_install_script('build/symlink-cjs.py', bindir) ### Install data files ######################################################### install_data('installed-tests/extra/gjs.supp', install_dir: cjsjsdir / 'valgrind') install_data('installed-tests/extra/lsan.supp', install_dir: cjsjsdir / 'lsan') if get_option('installed_tests') schemadir = datadir / 'glib-2.0' / 'schemas' install_data('installed-tests/js/org.cinnamon.CjsTest.gschema.xml', install_dir: schemadir) meson.add_install_script(glib_compile_schemas, prefix / schemadir, skip_if_destdir: true) endif ### Generate pkg-config file ################################################### pkg.generate(libcjs, name: api_name, description: 'JS bindings for GObjects', requires: [glib, gobject, gio], requires_private: pkg_dependencies, subdirs: api_name, variables: [ 'exec_prefix=${prefix}', 'datarootdir=${datadir}', 'cjs_console=${bindir}/cjs-console', ]) ### Test environment ########################################################### tests_environment = environment() gi_tests_builddir = meson.project_build_root() / 'subprojects' / 'gobject-introspection-tests' js_tests_builddir = meson.current_build_dir() / 'installed-tests' / 'js' libcjs_test_tools_builddir = js_tests_builddir / 'libgjstesttools' # GJS_PATH is empty here since we want to force the use of our own # resources. G_FILENAME_ENCODING ensures filenames are not UTF-8 tests_environment.set('TOP_BUILDDIR', meson.project_build_root()) tests_environment.set('GJS_USE_UNINSTALLED_FILES', '1') tests_environment.set('GJS_PATH', '') tests_environment.set('GJS_DEBUG_OUTPUT', 'stderr') tests_environment.prepend('GI_TYPELIB_PATH', meson.current_build_dir(), gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) tests_environment.prepend('LD_LIBRARY_PATH', meson.current_build_dir(), gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) tests_environment.prepend('DYLD_LIBRARY_PATH', meson.current_build_dir(), gi_tests_builddir, js_tests_builddir, libcjs_test_tools_builddir) tests_environment.set('G_FILENAME_ENCODING', 'latin1') # Workaround for https://github.com/google/sanitizers/issues/1322 tests_environment.set('ASAN_OPTIONS', 'intercept_tls_get_addr=0') tests_environment.set('LSAN_OPTIONS', 'fast_unwind_on_malloc=0,exitcode=23,suppressions=@0@'.format( meson.current_source_dir() / 'installed-tests' / 'extra' / 'lsan.supp')) tests_environment.set('TSAN_OPTIONS', 'history_size=5,force_seq_cst_atomics=1,suppressions=@0@'.format( meson.current_source_dir() / 'installed-tests' / 'extra' / 'tsan.supp')) tests_environment.set('G_SLICE', 'always-malloc') tests_environment.set('NO_AT_BRIDGE', '1') tests_environment.set('GSETTINGS_SCHEMA_DIR', js_tests_builddir) tests_environment.set('GSETTINGS_BACKEND', 'memory') tests_environment.set('G_DEBUG', 'fatal-warnings,fatal-criticals') tests_locale = 'N/A' if cxx.get_argument_syntax() != 'msvc' result = run_command('build/choose-tests-locale.sh', check: false) if result.returncode() == 0 tests_locale = result.stdout().strip() tests_environment.set('LC_ALL', tests_locale) endif endif if not get_option('skip_gtk_tests') tests_environment.set('ENABLE_GTK', 'yes') endif if get_option('b_coverage') tests_environment.set('GJS_UNIT_COVERAGE_OUTPUT', 'lcov') tests_environment.set('GJS_UNIT_COVERAGE_PREFIX', 'resource:///org/cinnamon/cjs') endif ### Tests and test setups ###################################################### # External code should not error out even when building with -Werror gi_tests = subproject('gobject-introspection-tests', default_options: ['werror=false', 'cairo=true', 'install_dir=@0@'.format(installed_tests_execdir)]) subdir('installed-tests') # Note: The test program in test/ needs to be ported # to Windows before we can build it on Windows. if host_machine.system() != 'windows' subdir('test') endif valgrind_environment = environment() valgrind_environment.set('G_SLICE', 'always-malloc,debug-blocks') valgrind_environment.set('G_DEBUG', 'fatal-warnings,fatal-criticals,gc-friendly') valgrind_environment.set('VALGRIND', 'valgrind') glib_prefix = glib.get_variable(pkgconfig: 'prefix', default_value: '/usr') glib_suppresssions = (glib_prefix / 'share' / 'glib-2.0' / 'valgrind' / 'glib.supp') cjs_suppressions = (meson.current_source_dir() / 'installed-tests' / 'extra' / 'cjs.supp') valgrind_args = [ '--suppressions=@0@'.format(glib_suppresssions), '--suppressions=@0@'.format(cjs_suppressions), '--leak-check=full', '--num-callers=15', '--trace-children=yes', '--trace-children-skip=*basename,*cat,*diff,*echo,*grep,*rm,*sed,*stat,*true', '--error-exitcode=1' ] add_test_setup('quiet', env: ['GJS_DEBUG_TOPICS='], is_default: true) add_test_setup('verbose') add_test_setup('valgrind', timeout_multiplier: 40, env: valgrind_environment, exe_wrapper: ['valgrind'] + valgrind_args) zeal2_environment = environment() zeal2_environment.set('JS_GC_ZEAL', '2,10') add_test_setup('extra_gc', timeout_multiplier: 40, env: zeal2_environment) zeal4_environment = environment() zeal4_environment.set('JS_GC_ZEAL', '4') add_test_setup('pre_verify', timeout_multiplier: 40, env: zeal4_environment) zeal11_environment = environment() zeal11_environment.set('JS_GC_ZEAL', '11') add_test_setup('post_verify', timeout_multiplier: 2, env: zeal11_environment) ### Warn about conditions that may affect runtime ############################## if tests_locale == 'C' or tests_locale == 'N/A' warning('''Your libc does not have the C.UTF-8 locale and no other suitable UTF-8 fallback locale could be found. You can still build GJS, but some tests will fail.''') endif if get_option('buildtype').startswith('debug') and nondebug_spidermonkey warning('''Your copy of SpiderMonkey is not debug-enabled, but you are building a debug or debugoptimized build. This will make development more difficult. Consider reconfiguring SpiderMonkey with --enable-debug.''') endif if get_option('skip_gtk_tests') warning('Not using GTK, not all tests will be run.') endif if get_option('skip_dbus_tests') warning('Not using DBus, not all tests will be run.') endif ### Summarize options ########################################################## summary({ 'prefix': prefix, 'bindir': prefix / bindir, 'libdir': prefix / libdir, 'datadir': prefix / datadir, 'libexecdir': prefix / libexecdir, }, section: 'Directories') locations = [] foreach dep: [ffi, glib, gi, spidermonkey, readline, sysprof_capture] if dep.type_name() == 'pkgconfig' locations += 'in @0@'.format(dep.get_variable(pkgconfig: 'prefix')) else locations += dep.type_name() endif endforeach summary({ 'libffi': '@0@ (@1@)'.format(ffi.version(), locations[0]), 'GLib': '@0@ (@1@)'.format(glib.version(), locations[1]), 'GObject introspection': '@0@ (@1@)'.format(gi.version(), locations[2]), 'SpiderMonkey': '@0@ (@1@, @2@ build)'.format(spidermonkey.version(), locations[3], nondebug_spidermonkey ? 'release' : 'debug'), }, section: 'Dependencies') if build_readline summary('Readline', '(@0@)'.format(locations[4]), section: 'Dependencies') endif if build_profiler summary('Sysprof', '@0@ (@1@)'.format(sysprof_capture.version(), locations[5]), section: 'Dependencies') endif summary({ 'Build type': get_option('buildtype'), 'Installed tests': get_option('installed_tests'), '-Bsymbolic-functions': get_option('bsymbolic_functions'), 'Skip DBus tests': get_option('skip_dbus_tests'), 'Skip GTK tests': get_option('skip_gtk_tests'), 'Extra debug logs': get_option('verbose_logs'), 'Precompiled headers': get_option('b_pch'), }, section: 'Build options', bool_yn: true) summary({ 'Use readline for input': build_readline, 'Profiler (Linux only)': build_profiler, 'Dtrace debugging': get_option('dtrace'), 'Systemtap debugging': get_option('systemtap'), }, section: 'Optional features', bool_yn: true) ### Development environment #################################################### meson.add_devenv({'GJS_USE_UNINSTALLED_FILES': '1'}) ### Maintainer scripts ######################################################### run_target('maintainer-upload-release', command: ['build/maintainer-upload-release.sh', meson.project_name(), meson.project_version()]) cjs-128.1/meson_options.txt0000664000175000017500000000244715116312211014665 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2019 Philip Chimento # Features option('readline', type: 'feature', value: 'auto', description: 'Use readline for input in interactive shell and debugger') option('profiler', type: 'feature', value: 'auto', description: 'Build profiler (Linux only)') # Flags option('installed_tests', type: 'boolean', value: true, description: 'Install test programs') option('dtrace', type: 'boolean', value: false, description: 'Include dtrace trace support') option('systemtap', type: 'boolean', value: false, description: 'Include systemtap trace support (requires -Ddtrace=true)') option('bsymbolic_functions', type: 'boolean', value: true, description: 'Link with -Bsymbolic-functions linker flag used to avoid intra-library PLT jumps, if supported; not used for Visual Studio and clang-cl builds') option('skip_dbus_tests', type: 'boolean', value: false, description: 'Skip tests that use a DBus session bus') option('skip_gtk_tests', type: 'boolean', value: false, description: 'Skip tests that need a display connection') option('verbose_logs', type: 'boolean', value: false, description: 'Enable extra log messages that may decrease performance (not allowed in release builds)') cjs-128.1/modules/0000775000175000017500000000000015116312211012671 5ustar fabiofabiocjs-128.1/modules/cairo-context.cpp0000664000175000017500000011607515116312211016166 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include // for size_t #include #include #include #include #include // for JS::NewArrayObject #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for UniqueChars #include #include // for JS_NewPlainObject #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" #define _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj) \ GJS_GET_THIS(cx, argc, vp, argv, obj); \ cairo_t* cr; \ if (!CairoContext::for_js_typecheck(cx, obj, &cr, &argv)) \ return false; \ if (!cr) \ return true; #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(mname) \ GJS_JSAPI_RETURN_CONVENTION \ static bool mname##_func(JSContext* context, unsigned argc, \ JS::Value* vp) { \ _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj) #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END \ return gjs_cairo_check_status(context, cairo_status(cr), "context"); \ } #define _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(m) \ if (argc > 0) { \ gjs_throw(context, "Context." #m "() takes no arguments"); \ return false; \ } #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ cfunc(cr); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ int ret; \ _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ ret = (int)cfunc(cr); \ argv.rval().setInt32(ret); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0B(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ cairo_bool_t ret; \ _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ ret = cfunc(cr); \ argv.rval().setBoolean(ret); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC2FFAFF(method, cfunc, n1, n2) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ double arg1, arg2; \ if (!gjs_parse_call_args(context, #method, argv, "ff", #n1, &arg1, #n2, \ &arg2)) \ return false; \ cfunc(cr, &arg1, &arg2); \ if (cairo_status(cr) == CAIRO_STATUS_SUCCESS) { \ JS::RootedObject array(context, JS::NewArrayObject(context, 2)); \ if (!array) \ return false; \ JS::RootedValue r{context, \ JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(context, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(context, array, 1, r)) \ return false; \ argv.rval().setObject(*array); \ } \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFF(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ double arg1, arg2; \ _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ cfunc(cr, &arg1, &arg2); \ if (cairo_status(cr) == CAIRO_STATUS_SUCCESS) { \ JS::RootedObject array(context, JS::NewArrayObject(context, 2)); \ if (!array) \ return false; \ JS::RootedValue r{context, \ JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(context, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(context, array, 1, r)) \ return false; \ argv.rval().setObject(*array); \ } \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFFFF(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ double arg1, arg2, arg3, arg4; \ _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ cfunc(cr, &arg1, &arg2, &arg3, &arg4); \ { \ JS::RootedObject array(context, JS::NewArrayObject(context, 4)); \ if (!array) \ return false; \ JS::RootedValue r{context, \ JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(context, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(context, array, 1, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg3)); \ if (!JS_SetElement(context, array, 2, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg4)); \ if (!JS_SetElement(context, array, 3, r)) \ return false; \ argv.rval().setObject(*array); \ } \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC0F(method, cfunc) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ double ret; \ _GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ ret = cfunc(cr); \ argv.rval().setNumber(JS::CanonicalizeNaN(ret)); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(method, cfunc, fmt, t1, n1) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1)) \ return false; \ cfunc(cr, arg1); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(method, cfunc, fmt, t1, n1, t2, n2) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2)) \ return false; \ cfunc(cr, arg1, arg2); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC2B(method, cfunc, fmt, t1, n1, t2, n2) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ cairo_bool_t ret; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2)) \ return false; \ ret = cfunc(cr, arg1, arg2); \ argv.rval().setBoolean(ret); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC3(method, cfunc, fmt, t1, n1, t2, n2, t3, n3) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ t3 arg3; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2, #n3, &arg3)) \ return false; \ cfunc(cr, arg1, arg2, arg3); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC4(method, cfunc, fmt, t1, n1, t2, n2, t3, n3, t4, n4) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ t3 arg3; \ t4 arg4; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2, \ #n3, &arg3, #n4, &arg4)) \ return false; \ cfunc(cr, arg1, arg2, arg3, arg4); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC5(method, cfunc, fmt, t1, n1, t2, n2, t3, n3, t4, n4, t5, n5) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ t3 arg3; \ t4 arg4; \ t5 arg5; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2, #n3, &arg3, \ #n4, &arg4, #n5, &arg5)) \ return false; \ cfunc(cr, arg1, arg2, arg3, arg4, arg5); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END #define _GJS_CAIRO_CONTEXT_DEFINE_FUNC6(method, cfunc, fmt, t1, n1, t2, n2, t3, n3, t4, n4, t5, n5, t6, n6) \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_BEGIN(method) \ t1 arg1; \ t2 arg2; \ t3 arg3; \ t4 arg4; \ t5 arg5; \ t6 arg6; \ if (!gjs_parse_call_args(context, #method, argv, fmt, \ #n1, &arg1, #n2, &arg2, #n3, &arg3, \ #n4, &arg4, #n5, &arg5, #n6, &arg6)) \ return false; \ cfunc(cr, arg1, arg2, arg3, arg4, arg5, arg6); \ argv.rval().setUndefined(); \ _GJS_CAIRO_CONTEXT_DEFINE_FUNC_END GJS_JSAPI_RETURN_CONVENTION cairo_t* CairoContext::constructor_impl(JSContext* context, const JS::CallArgs& argv) { cairo_t *cr; JS::RootedObject surface_wrapper(context); if (!gjs_parse_call_args(context, "Context", argv, "o", "surface", &surface_wrapper)) return nullptr; cairo_surface_t* surface = CairoSurface::for_js(context, surface_wrapper); if (!surface) return nullptr; cr = cairo_create(surface); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return nullptr; return cr; } void CairoContext::finalize_impl(JS::GCContext*, cairo_t* cr) { if (!cr) return; cairo_destroy(cr); } /* Properties */ // clang-format off const JSPropertySpec CairoContext::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Context", JSPROP_READONLY), JS_PS_END}; // clang-format on /* Methods */ _GJS_CAIRO_CONTEXT_DEFINE_FUNC5(arc, cairo_arc, "fffff", double, xc, double, yc, double, radius, double, angle1, double, angle2) _GJS_CAIRO_CONTEXT_DEFINE_FUNC5(arcNegative, cairo_arc_negative, "fffff", double, xc, double, yc, double, radius, double, angle1, double, angle2) _GJS_CAIRO_CONTEXT_DEFINE_FUNC6(curveTo, cairo_curve_to, "ffffff", double, x1, double, y1, double, x2, double, y2, double, x3, double, y3) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(clip, cairo_clip) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(clipPreserve, cairo_clip_preserve) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFFFF(clipExtents, cairo_clip_extents) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(closePath, cairo_close_path) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(copyPage, cairo_copy_page) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2FFAFF(deviceToUser, cairo_device_to_user, "x", "y") _GJS_CAIRO_CONTEXT_DEFINE_FUNC2FFAFF(deviceToUserDistance, cairo_device_to_user_distance, "x", "y") _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(fill, cairo_fill) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(fillPreserve, cairo_fill_preserve) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFFFF(fillExtents, cairo_fill_extents) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getAntialias, cairo_get_antialias) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFF(getCurrentPoint, cairo_get_current_point) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getDashCount, cairo_get_dash_count) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getFillRule, cairo_get_fill_rule) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getLineCap, cairo_get_line_cap) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getLineJoin, cairo_get_line_join) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0F(getLineWidth, cairo_get_line_width) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0F(getMiterLimit, cairo_get_miter_limit) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0I(getOperator, cairo_get_operator) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0F(getTolerance, cairo_get_tolerance) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0B(hasCurrentPoint, cairo_has_current_point) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(identityMatrix, cairo_identity_matrix) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2B(inFill, cairo_in_fill, "ff", double, x, double, y) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2B(inStroke, cairo_in_stroke, "ff", double, x, double, y) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(lineTo, cairo_line_to, "ff", double, x, double, y) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(moveTo, cairo_move_to, "ff", double, x, double, y) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(newPath, cairo_new_path) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(newSubPath, cairo_new_sub_path) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(paint, cairo_paint) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(paintWithAlpha, cairo_paint_with_alpha, "f", double, alpha) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFFFF(pathExtents, cairo_path_extents) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(pushGroup, cairo_push_group) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(pushGroupWithContent, cairo_push_group_with_content, "i", cairo_content_t, content) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(popGroupToSource, cairo_pop_group_to_source) _GJS_CAIRO_CONTEXT_DEFINE_FUNC4(rectangle, cairo_rectangle, "ffff", double, x, double, y, double, width, double, height) _GJS_CAIRO_CONTEXT_DEFINE_FUNC6(relCurveTo, cairo_rel_curve_to, "ffffff", double, dx1, double, dy1, double, dx2, double, dy2, double, dx3, double, dy3) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(relLineTo, cairo_rel_line_to, "ff", double, dx, double, dy) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(relMoveTo, cairo_rel_move_to, "ff", double, dx, double, dy) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(resetClip, cairo_reset_clip) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(restore, cairo_restore) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(rotate, cairo_rotate, "f", double, angle) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(save, cairo_save) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(scale, cairo_scale, "ff", double, sx, double, sy) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setAntialias, cairo_set_antialias, "i", cairo_antialias_t, antialias) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setFillRule, cairo_set_fill_rule, "i", cairo_fill_rule_t, fill_rule) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setFontSize, cairo_set_font_size, "f", double, size) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setLineCap, cairo_set_line_cap, "i", cairo_line_cap_t, line_cap) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setLineJoin, cairo_set_line_join, "i", cairo_line_join_t, line_join) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setLineWidth, cairo_set_line_width, "f", double, width) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setMiterLimit, cairo_set_miter_limit, "f", double, limit) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setOperator, cairo_set_operator, "i", cairo_operator_t, op) _GJS_CAIRO_CONTEXT_DEFINE_FUNC1(setTolerance, cairo_set_tolerance, "f", double, tolerance) _GJS_CAIRO_CONTEXT_DEFINE_FUNC3(setSourceRGB, cairo_set_source_rgb, "fff", double, red, double, green, double, blue) _GJS_CAIRO_CONTEXT_DEFINE_FUNC4(setSourceRGBA, cairo_set_source_rgba, "ffff", double, red, double, green, double, blue, double, alpha) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(showPage, cairo_show_page) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(stroke, cairo_stroke) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0(strokePreserve, cairo_stroke_preserve) _GJS_CAIRO_CONTEXT_DEFINE_FUNC0AFFFF(strokeExtents, cairo_stroke_extents) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2(translate, cairo_translate, "ff", double, tx, double, ty) _GJS_CAIRO_CONTEXT_DEFINE_FUNC2FFAFF(userToDevice, cairo_user_to_device, "x", "y") _GJS_CAIRO_CONTEXT_DEFINE_FUNC2FFAFF(userToDeviceDistance, cairo_user_to_device_distance, "x", "y") bool CairoContext::dispose(JSContext* context, unsigned argc, JS::Value* vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, rec, obj); cairo_destroy(cr); CairoContext::unset_private(obj); rec.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool appendPath_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::RootedObject path_wrapper(context); if (!gjs_parse_call_args(context, "path", argv, "o", "path", &path_wrapper)) return false; cairo_path_t* path; if (!CairoPath::for_js_typecheck(context, path_wrapper, &path, &argv)) return false; cairo_append_path(cr, path); argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool copyPath_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); cairo_path_t *path; if (!gjs_parse_call_args(context, "", argv, "")) return false; path = cairo_copy_path(cr); JSObject* retval = CairoPath::take_c_ptr(context, path); if (!retval) return false; argv.rval().setObject(*retval); return true; } GJS_JSAPI_RETURN_CONVENTION static bool copyPathFlat_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); cairo_path_t *path; if (!gjs_parse_call_args(context, "", argv, "")) return false; path = cairo_copy_path_flat(cr); JSObject* retval = CairoPath::take_c_ptr(context, path); if (!retval) return false; argv.rval().setObject(*retval); return true; } GJS_JSAPI_RETURN_CONVENTION static bool mask_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::RootedObject pattern_wrapper(context); if (!gjs_parse_call_args(context, "mask", argv, "o", "pattern", &pattern_wrapper)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, pattern_wrapper); if (!pattern) return false; cairo_mask(cr, pattern); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool maskSurface_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::RootedObject surface_wrapper(context); double x, y; if (!gjs_parse_call_args(context, "maskSurface", argv, "off", "surface", &surface_wrapper, "x", &x, "y", &y)) return false; cairo_surface_t* surface = CairoSurface::for_js(context, surface_wrapper); if (!surface) return false; cairo_mask_surface(cr, surface, x, y); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setDash_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); guint i; JS::RootedObject dashes(context); double offset; guint len; bool is_array; if (!gjs_parse_call_args(context, "setDash", argv, "of", "dashes", &dashes, "offset", &offset)) return false; if (!JS::IsArrayObject(context, dashes, &is_array)) return false; if (!is_array) { gjs_throw(context, "dashes must be an array"); return false; } if (!JS::GetArrayLength(context, dashes, &len)) { gjs_throw(context, "Can't get length of dashes"); return false; } std::unique_ptr dashes_c = std::make_unique(len); size_t dashes_c_size = 0; JS::RootedValue elem(context); for (i = 0; i < len; ++i) { double b; elem.setUndefined(); if (!JS_GetElement(context, dashes, i, &elem)) { return false; } if (elem.isUndefined()) continue; if (!JS::ToNumber(context, elem, &b)) return false; if (b <= 0) { gjs_throw(context, "Dash value must be positive"); return false; } dashes_c[dashes_c_size++] = b; } cairo_set_dash(cr, dashes_c.get(), dashes_c_size, offset); argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setSource_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::RootedObject pattern_wrapper(context); if (!gjs_parse_call_args(context, "setSource", argv, "o", "pattern", &pattern_wrapper)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, pattern_wrapper); if (!pattern) return false; cairo_set_source(cr, pattern); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setSourceSurface_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::RootedObject surface_wrapper(context); double x, y; if (!gjs_parse_call_args(context, "setSourceSurface", argv, "off", "surface", &surface_wrapper, "x", &x, "y", &y)) return false; cairo_surface_t* surface = CairoSurface::for_js(context, surface_wrapper); if (!surface) return false; cairo_set_source_surface(cr, surface, x, y); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool showText_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::UniqueChars utf8; if (!gjs_parse_call_args(context, "showText", argv, "s", "utf8", &utf8)) return false; cairo_show_text(cr, utf8.get()); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool selectFontFace_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, argv, obj); JS::UniqueChars family; cairo_font_slant_t slant; cairo_font_weight_t weight; if (!gjs_parse_call_args(context, "selectFontFace", argv, "sii", "family", &family, "slang", &slant, "weight", &weight)) return false; cairo_select_font_face(cr, family.get(), slant, weight); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool popGroup_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, rec, obj); cairo_pattern_t *pattern; JSObject *pattern_wrapper; if (argc > 0) { gjs_throw(context, "Context.popGroup() takes no arguments"); return false; } pattern = cairo_pop_group(cr); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; pattern_wrapper = gjs_cairo_pattern_from_pattern(context, pattern); cairo_pattern_destroy(pattern); if (!pattern_wrapper) { gjs_throw(context, "failed to create pattern"); return false; } rec.rval().setObject(*pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getSource_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, rec, obj); cairo_pattern_t *pattern; JSObject *pattern_wrapper; if (argc > 0) { gjs_throw(context, "Context.getSource() takes no arguments"); return false; } pattern = cairo_get_source(cr); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; /* pattern belongs to the context, so keep the reference */ pattern_wrapper = gjs_cairo_pattern_from_pattern(context, pattern); if (!pattern_wrapper) { gjs_throw(context, "failed to create pattern"); return false; } rec.rval().setObject(*pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getTarget_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, rec, obj); cairo_surface_t *surface; if (argc > 0) { gjs_throw(context, "Context.getTarget() takes no arguments"); return false; } surface = cairo_get_target(cr); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; /* surface belongs to the context, so keep the reference */ JSObject* surface_wrapper = CairoSurface::from_c_ptr(context, surface); if (!surface_wrapper) { /* exception already set */ return false; } rec.rval().setObject(*surface_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getGroupTarget_func(JSContext *context, unsigned argc, JS::Value *vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(context, argc, vp, rec, obj); cairo_surface_t *surface; if (argc > 0) { gjs_throw(context, "Context.getGroupTarget() takes no arguments"); return false; } surface = cairo_get_group_target(cr); if (!gjs_cairo_check_status(context, cairo_status(cr), "context")) return false; /* surface belongs to the context, so keep the reference */ JSObject* surface_wrapper = CairoSurface::from_c_ptr(context, surface); if (!surface_wrapper) { /* exception already set */ return false; } rec.rval().setObject(*surface_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool textExtents_func(JSContext* cx, unsigned argc, JS::Value* vp) { _GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, args, this_obj); JS::UniqueChars utf8; if (!gjs_parse_call_args(cx, "textExtents", args, "s", "utf8", &utf8)) return false; cairo_text_extents_t extents; cairo_text_extents(cr, utf8.get(), &extents); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; JS::RootedObject extents_obj(cx, JS_NewPlainObject(cx)); if (!extents_obj) return false; JSPropertySpec properties[] = { JS_DOUBLE_PS("xBearing", extents.x_bearing, JSPROP_ENUMERATE), JS_DOUBLE_PS("yBearing", extents.y_bearing, JSPROP_ENUMERATE), JS_DOUBLE_PS("width", extents.width, JSPROP_ENUMERATE), JS_DOUBLE_PS("height", extents.height, JSPROP_ENUMERATE), JS_DOUBLE_PS("xAdvance", extents.x_advance, JSPROP_ENUMERATE), JS_DOUBLE_PS("yAdvance", extents.y_advance, JSPROP_ENUMERATE), JS_PS_END}; if (!JS_DefineProperties(cx, extents_obj, properties)) return false; args.rval().setObject(*extents_obj); return true; } // clang-format off const JSFunctionSpec CairoContext::proto_funcs[] = { JS_FN("$dispose", &CairoContext::dispose, 0, 0), JS_FN("appendPath", appendPath_func, 0, 0), JS_FN("arc", arc_func, 0, 0), JS_FN("arcNegative", arcNegative_func, 0, 0), JS_FN("clip", clip_func, 0, 0), JS_FN("clipExtents", clipExtents_func, 0, 0), JS_FN("clipPreserve", clipPreserve_func, 0, 0), JS_FN("closePath", closePath_func, 0, 0), JS_FN("copyPage", copyPage_func, 0, 0), JS_FN("copyPath", copyPath_func, 0, 0), JS_FN("copyPathFlat", copyPathFlat_func, 0, 0), JS_FN("curveTo", curveTo_func, 0, 0), JS_FN("deviceToUser", deviceToUser_func, 0, 0), JS_FN("deviceToUserDistance", deviceToUserDistance_func, 0, 0), JS_FN("fill", fill_func, 0, 0), JS_FN("fillPreserve", fillPreserve_func, 0, 0), JS_FN("fillExtents", fillExtents_func, 0, 0), // fontExtents JS_FN("getAntialias", getAntialias_func, 0, 0), JS_FN("getCurrentPoint", getCurrentPoint_func, 0, 0), // getDash JS_FN("getDashCount", getDashCount_func, 0, 0), JS_FN("getFillRule", getFillRule_func, 0, 0), // getFontFace // getFontMatrix // getFontOptions JS_FN("getGroupTarget", getGroupTarget_func, 0, 0), JS_FN("getLineCap", getLineCap_func, 0, 0), JS_FN("getLineJoin", getLineJoin_func, 0, 0), JS_FN("getLineWidth", getLineWidth_func, 0, 0), // getMatrix JS_FN("getMiterLimit", getMiterLimit_func, 0, 0), JS_FN("getOperator", getOperator_func, 0, 0), // getScaledFont JS_FN("getSource", getSource_func, 0, 0), JS_FN("getTarget", getTarget_func, 0, 0), JS_FN("getTolerance", getTolerance_func, 0, 0), // glyphPath // glyphExtents JS_FN("hasCurrentPoint", hasCurrentPoint_func, 0, 0), JS_FN("identityMatrix", identityMatrix_func, 0, 0), JS_FN("inFill", inFill_func, 0, 0), JS_FN("inStroke", inStroke_func, 0, 0), JS_FN("lineTo", lineTo_func, 0, 0), JS_FN("mask", mask_func, 0, 0), JS_FN("maskSurface", maskSurface_func, 0, 0), JS_FN("moveTo", moveTo_func, 0, 0), JS_FN("newPath", newPath_func, 0, 0), JS_FN("newSubPath", newSubPath_func, 0, 0), JS_FN("paint", paint_func, 0, 0), JS_FN("paintWithAlpha", paintWithAlpha_func, 0, 0), JS_FN("pathExtents", pathExtents_func, 0, 0), JS_FN("popGroup", popGroup_func, 0, 0), JS_FN("popGroupToSource", popGroupToSource_func, 0, 0), JS_FN("pushGroup", pushGroup_func, 0, 0), JS_FN("pushGroupWithContent", pushGroupWithContent_func, 0, 0), JS_FN("rectangle", rectangle_func, 0, 0), JS_FN("relCurveTo", relCurveTo_func, 0, 0), JS_FN("relLineTo", relLineTo_func, 0, 0), JS_FN("relMoveTo", relMoveTo_func, 0, 0), JS_FN("resetClip", resetClip_func, 0, 0), JS_FN("restore", restore_func, 0, 0), JS_FN("rotate", rotate_func, 0, 0), JS_FN("save", save_func, 0, 0), JS_FN("scale", scale_func, 0, 0), JS_FN("selectFontFace", selectFontFace_func, 0, 0), JS_FN("setAntialias", setAntialias_func, 0, 0), JS_FN("setDash", setDash_func, 0, 0), // setFontFace // setFontMatrix // setFontOptions JS_FN("setFontSize", setFontSize_func, 0, 0), JS_FN("setFillRule", setFillRule_func, 0, 0), JS_FN("setLineCap", setLineCap_func, 0, 0), JS_FN("setLineJoin", setLineJoin_func, 0, 0), JS_FN("setLineWidth", setLineWidth_func, 0, 0), // setMatrix JS_FN("setMiterLimit", setMiterLimit_func, 0, 0), JS_FN("setOperator", setOperator_func, 0, 0), // setScaledFont JS_FN("setSource", setSource_func, 0, 0), JS_FN("setSourceRGB", setSourceRGB_func, 0, 0), JS_FN("setSourceRGBA", setSourceRGBA_func, 0, 0), JS_FN("setSourceSurface", setSourceSurface_func, 0, 0), JS_FN("setTolerance", setTolerance_func, 0, 0), // showGlyphs JS_FN("showPage", showPage_func, 0, 0), JS_FN("showText", showText_func, 0, 0), // showTextGlyphs JS_FN("stroke", stroke_func, 0, 0), JS_FN("strokeExtents", strokeExtents_func, 0, 0), JS_FN("strokePreserve", strokePreserve_func, 0, 0), // textPath JS_FN("textExtents", textExtents_func, 1, 0), // transform JS_FN("translate", translate_func, 0, 0), JS_FN("userToDevice", userToDevice_func, 0, 0), JS_FN("userToDeviceDistance", userToDeviceDistance_func, 0, 0), JS_FS_END}; // clang-format on [[nodiscard]] static bool context_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, argument_type); gjs_throw(context, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } JS::RootedObject obj(context, &value.toObject()); cairo_t* cr = CairoContext::for_js(context, obj); if (!cr) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_reference(cr); gjs_arg_set(arg, cr); return true; } GJS_JSAPI_RETURN_CONVENTION static bool context_from_gi_argument(JSContext* context, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoContext::from_c_ptr( context, static_cast(arg->v_pointer)); if (!obj) { gjs_throw(context, "Could not create Cairo context"); return false; } value_p.setObject(*obj); return true; } static bool context_release_argument(JSContext*, GITransfer transfer, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) cairo_destroy(gjs_arg_get(arg)); return true; } void gjs_cairo_context_init(void) { static GjsForeignInfo foreign_info = {context_to_gi_argument, context_from_gi_argument, context_release_argument}; gjs_struct_foreign_register("cairo", "Context", &foreign_info); } cjs-128.1/modules/cairo-gradient.cpp0000664000175000017500000000570715116312211016276 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" JSObject* CairoGradient::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoPattern::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } /* Properties */ // clang-format off const JSPropertySpec CairoGradient::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Gradient", JSPROP_READONLY), JS_PS_END}; // clang-format on /* Methods */ GJS_JSAPI_RETURN_CONVENTION static bool addColorStopRGB_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); double offset, red, green, blue; if (!gjs_parse_call_args(context, "addColorStopRGB", argv, "ffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgb(pattern, offset, red, green, blue); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool addColorStopRGBA_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); double offset, red, green, blue, alpha; if (!gjs_parse_call_args(context, "addColorStopRGBA", argv, "fffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgba(pattern, offset, red, green, blue, alpha); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } const JSFunctionSpec CairoGradient::proto_funcs[] = { JS_FN("addColorStopRGB", addColorStopRGB_func, 0, 0), JS_FN("addColorStopRGBA", addColorStopRGBA_func, 0, 0), // getColorStopRGB // getColorStopRGBA JS_FS_END}; cjs-128.1/modules/cairo-image-surface.cpp0000664000175000017500000001267215116312211017210 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" JSObject* CairoImageSurface::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoSurface::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_surface_t* CairoImageSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { int format, width, height; cairo_surface_t *surface; // create_for_data optional parameter if (!gjs_parse_call_args(context, "ImageSurface", argv, "iii", "format", &format, "width", &width, "height", &height)) return nullptr; surface = cairo_image_surface_create((cairo_format_t) format, width, height); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return nullptr; return surface; } // clang-format off const JSPropertySpec CairoImageSurface::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "ImageSurface", JSPROP_READONLY), JS_PS_END}; // clang-format on GJS_JSAPI_RETURN_CONVENTION static bool createFromPNG_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); GjsAutoChar filename; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "createFromPNG", argv, "F", "filename", &filename)) return false; surface = cairo_image_surface_create_from_png(filename); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; JSObject* surface_wrapper = CairoImageSurface::from_c_ptr(context, surface); if (!surface_wrapper) return false; cairo_surface_destroy(surface); argv.rval().setObject(*surface_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getFormat_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); cairo_format_t format; if (argc > 1) { gjs_throw(context, "ImageSurface.getFormat() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; format = cairo_image_surface_get_format(surface); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(format); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getWidth_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); int width; if (argc > 1) { gjs_throw(context, "ImageSurface.getWidth() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; width = cairo_image_surface_get_width(surface); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(width); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getHeight_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); int height; if (argc > 1) { gjs_throw(context, "ImageSurface.getHeight() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; height = cairo_image_surface_get_height(surface); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(height); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getStride_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); int stride; if (argc > 1) { gjs_throw(context, "ImageSurface.getStride() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; stride = cairo_image_surface_get_stride(surface); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(stride); return true; } const JSFunctionSpec CairoImageSurface::proto_funcs[] = { JS_FN("createFromPNG", createFromPNG_func, 0, 0), // getData JS_FN("getFormat", getFormat_func, 0, 0), JS_FN("getWidth", getWidth_func, 0, 0), JS_FN("getHeight", getHeight_func, 0, 0), JS_FN("getStride", getStride_func, 0, 0), JS_FS_END}; const JSFunctionSpec CairoImageSurface::static_funcs[] = { JS_FN("createFromPNG", createFromPNG_func, 1, GJS_MODULE_PROP_FLAGS), JS_FS_END}; cjs-128.1/modules/cairo-linear-gradient.cpp0000664000175000017500000000311315116312211017533 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "modules/cairo-private.h" namespace JS { class CallArgs; } JSObject* CairoLinearGradient::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoGradient::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_pattern_t* CairoLinearGradient::constructor_impl( JSContext* context, const JS::CallArgs& argv) { double x0, y0, x1, y1; cairo_pattern_t* pattern; if (!gjs_parse_call_args(context, "LinearGradient", argv, "ffff", "x0", &x0, "y0", &y0, "x1", &x1, "y1", &y1)) return nullptr; pattern = cairo_pattern_create_linear(x0, y0, x1, y1); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return nullptr; return pattern; } const JSPropertySpec CairoLinearGradient::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "LinearGradient", JSPROP_READONLY), JS_PS_END}; const JSFunctionSpec CairoLinearGradient::proto_funcs[] = { // getLinearPoints JS_FS_END}; cjs-128.1/modules/cairo-module.h0000664000175000017500000000075615116312211015432 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #ifndef MODULES_CAIRO_MODULE_H_ #define MODULES_CAIRO_MODULE_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_js_define_cairo_stuff(JSContext *context, JS::MutableHandleObject module); #endif // MODULES_CAIRO_MODULE_H_ cjs-128.1/modules/cairo-path.cpp0000664000175000017500000000261715116312211015432 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 Red Hat, Inc. // SPDX-FileCopyrightText: 2020 Philip Chimento #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include "modules/cairo-private.h" // clang-format off const JSPropertySpec CairoPath::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Path", JSPROP_READONLY), JS_PS_END}; // clang-format on /* * CairoPath::take_c_ptr(): * Same as CWrapper::from_c_ptr(), but always takes ownership of the pointer * rather than copying it. It's not possible to copy a cairo_path_t*. */ JSObject* CairoPath::take_c_ptr(JSContext* cx, cairo_path_t* ptr) { JS::RootedObject proto(cx, CairoPath::prototype(cx)); if (!proto) return nullptr; JS::RootedObject wrapper( cx, JS_NewObjectWithGivenProto(cx, &CairoPath::klass, proto)); if (!wrapper) return nullptr; CairoPath::init_private(wrapper, ptr); debug_lifecycle(ptr, wrapper, "take_c_ptr"); return wrapper; } void CairoPath::finalize_impl(JS::GCContext*, cairo_path_t* path) { if (!path) return; cairo_path_destroy(path); } cjs-128.1/modules/cairo-pattern.cpp0000664000175000017500000000773015116312211016154 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include #include #include // for GetClass #include // for JSPROP_READONLY #include #include #include #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" /* Properties */ // clang-format off const JSPropertySpec CairoPattern::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Pattern", JSPROP_READONLY), JS_PS_END}; // clang-format on /* Methods */ GJS_JSAPI_RETURN_CONVENTION bool CairoPattern::getType_func(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, obj); cairo_pattern_type_t type; if (argc > 1) { gjs_throw(context, "Pattern.getType() takes no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; type = cairo_pattern_get_type(pattern); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; rec.rval().setInt32(type); return true; } const JSFunctionSpec CairoPattern::proto_funcs[] = { // getMatrix JS_FN("getType", getType_func, 0, 0), // setMatrix JS_FS_END}; /* Public API */ /** * CairoPattern::finalize_impl: * @pattern: pointer to free * * Destroys the resources associated with a pattern wrapper. * * This is mainly used for subclasses. */ void CairoPattern::finalize_impl(JS::GCContext*, cairo_pattern_t* pattern) { if (!pattern) return; cairo_pattern_destroy(pattern); } /** * gjs_cairo_pattern_from_pattern: * @context: the context * @pattern: cairo_pattern to attach to the object * * Constructs a pattern wrapper given cairo pattern. * A reference to @pattern will be taken. * */ JSObject * gjs_cairo_pattern_from_pattern(JSContext *context, cairo_pattern_t *pattern) { g_return_val_if_fail(context, nullptr); g_return_val_if_fail(pattern, nullptr); switch (cairo_pattern_get_type(pattern)) { case CAIRO_PATTERN_TYPE_SOLID: return CairoSolidPattern::from_c_ptr(context, pattern); case CAIRO_PATTERN_TYPE_SURFACE: return CairoSurfacePattern::from_c_ptr(context, pattern); case CAIRO_PATTERN_TYPE_LINEAR: return CairoLinearGradient::from_c_ptr(context, pattern); case CAIRO_PATTERN_TYPE_RADIAL: return CairoRadialGradient::from_c_ptr(context, pattern); case CAIRO_PATTERN_TYPE_MESH: case CAIRO_PATTERN_TYPE_RASTER_SOURCE: default: gjs_throw(context, "failed to create pattern, unsupported pattern type %d", cairo_pattern_get_type(pattern)); return nullptr; } } /** * CairoPattern::for_js: * @cx: the context * @pattern_wrapper: pattern wrapper * * Returns: the pattern attached to the wrapper. */ cairo_pattern_t* CairoPattern::for_js(JSContext* cx, JS::HandleObject pattern_wrapper) { g_return_val_if_fail(cx, nullptr); g_return_val_if_fail(pattern_wrapper, nullptr); JS::RootedObject proto(cx, CairoPattern::prototype(cx)); bool is_pattern_subclass = false; if (!gjs_object_in_prototype_chain(cx, proto, pattern_wrapper, &is_pattern_subclass)) return nullptr; if (!is_pattern_subclass) { gjs_throw(cx, "Expected Cairo.Pattern but got %s", JS::GetClass(pattern_wrapper)->name); return nullptr; } return JS::GetMaybePtrFromReservedSlot( pattern_wrapper, CairoPattern::POINTER); } cjs-128.1/modules/cairo-pdf-surface.cpp0000664000175000017500000000430115116312211016665 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. // SPDX-FileCopyrightText: 2020 Philip Chimento #include #include // for CAIRO_HAS_PDF_SURFACE #include #if CAIRO_HAS_PDF_SURFACE # include #endif #include #if CAIRO_HAS_PDF_SURFACE # include // for JSPROP_READONLY # include # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey #endif #include "cjs/jsapi-util.h" #if CAIRO_HAS_PDF_SURFACE # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" namespace JS { class CallArgs; } JSObject* CairoPDFSurface::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoSurface::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_surface_t* CairoPDFSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { GjsAutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "PDFSurface", argv, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; surface = cairo_pdf_surface_create(filename, width, height); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return nullptr; return surface; } // clang-format off JSPropertySpec gjs_cairo_pdf_surface_proto_props[] = { JS_STRING_SYM_PS(toStringTag, "PDFSurface", JSPROP_READONLY), JS_PS_END}; // clang-format on #else JSObject* CairoPDFSurface::from_c_ptr(JSContext* context, cairo_surface_t* surface) { gjs_throw(context, "could not create PDF surface, recompile cairo and gjs with " "PDF support."); return nullptr; } #endif /* CAIRO_HAS_PDF_SURFACE */ cjs-128.1/modules/cairo-private.h0000664000175000017500000005507615116312211015624 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. // SPDX-FileCopyrightText: 2020 Philip Chimento #ifndef MODULES_CAIRO_PRIVATE_H_ #define MODULES_CAIRO_PRIVATE_H_ #include #include // for CAIRO_HAS_PDF_SURFACE, CAIRO_HAS_PS_SURFACE #include #include #include #include #include #include #include // for JSProtoKey #include "gi/cwrapper.h" #include "cjs/global.h" #include "cjs/macros.h" #include "util/log.h" namespace JS { class CallArgs; } GJS_JSAPI_RETURN_CONVENTION bool gjs_cairo_check_status (JSContext *context, cairo_status_t status, const char *name); class CairoRegion : public CWrapper { friend CWrapperPointerOps; friend CWrapper; CairoRegion() = delete; CairoRegion(CairoRegion&) = delete; CairoRegion(CairoRegion&&) = delete; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_region; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 0; static GType gtype() { return CAIRO_GOBJECT_TYPE_REGION; } static cairo_region_t* copy_ptr(cairo_region_t* region) { return cairo_region_reference(region); } GJS_JSAPI_RETURN_CONVENTION static cairo_region_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); static void finalize_impl(JS::GCContext*, cairo_region_t* cr); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties CairoRegion::proto_funcs, CairoRegion::proto_props, CairoRegion::define_gtype_prop, }; static constexpr JSClass klass = { "Region", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoRegion::class_ops, &CairoRegion::class_spec}; }; void gjs_cairo_region_init(void); class CairoContext : public CWrapper { friend CWrapperPointerOps; friend CWrapper; CairoContext() = delete; CairoContext(CairoContext&) = delete; CairoContext(CairoContext&&) = delete; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_context; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 1; static GType gtype() { return CAIRO_GOBJECT_TYPE_CONTEXT; } static cairo_t* copy_ptr(cairo_t* cr) { return cairo_reference(cr); } GJS_JSAPI_RETURN_CONVENTION static cairo_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); static void finalize_impl(JS::GCContext*, cairo_t* cr); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties CairoContext::proto_funcs, CairoContext::proto_props, CairoContext::define_gtype_prop, }; static constexpr JSClass klass = { "Context", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoContext::class_ops, &CairoContext::class_spec}; GJS_JSAPI_RETURN_CONVENTION static bool dispose(JSContext* cx, unsigned argc, JS::Value* vp); }; void gjs_cairo_context_init(void); void gjs_cairo_surface_init(void); /* path */ class CairoPath : public CWrapper { friend CWrapperPointerOps; friend CWrapper; CairoPath() = delete; CairoPath(CairoPath&) = delete; CairoPath(CairoPath&&) = delete; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_path; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static void finalize_impl(JS::GCContext*, cairo_path_t* path); static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { CairoPath::create_abstract_constructor, nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties nullptr, // prototypeFunctions CairoPath::proto_props, nullptr, // finishInit }; static constexpr JSClass klass = { "Path", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPath::class_ops, &CairoPath::class_spec}; public: GJS_JSAPI_RETURN_CONVENTION static JSObject* take_c_ptr(JSContext* cx, cairo_path_t* ptr); }; /* surface */ class CairoSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; friend class CairoImageSurface; // "inherits" from CairoSurface friend class CairoPSSurface; friend class CairoPDFSurface; friend class CairoSVGSurface; CairoSurface() = delete; CairoSurface(CairoSurface&) = delete; CairoSurface(CairoSurface&&) = delete; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_surface; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static GType gtype() { return CAIRO_GOBJECT_TYPE_SURFACE; } static void finalize_impl(JS::GCContext*, cairo_surface_t* surface); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { &CairoSurface::create_abstract_constructor, nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties CairoSurface::proto_funcs, CairoSurface::proto_props, &CairoSurface::define_gtype_prop, }; static constexpr JSClass klass = { "Surface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoSurface::class_ops, &CairoSurface::class_spec}; static cairo_surface_t* copy_ptr(cairo_surface_t* surface) { return cairo_surface_reference(surface); } GJS_JSAPI_RETURN_CONVENTION static bool getType_func(JSContext* context, unsigned argc, JS::Value* vp); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext* cx, cairo_surface_t* surface); GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* for_js(JSContext* cx, JS::HandleObject surface_wrapper); }; class CairoImageSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_image_surface; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 3; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec static_funcs[]; static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor, &CairoImageSurface::new_proto, CairoImageSurface::static_funcs, nullptr, // constructorProperties CairoImageSurface::proto_funcs, CairoImageSurface::proto_props, &CairoSurface::define_gtype_prop, }; static constexpr JSClass klass = { "ImageSurface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoSurface::class_ops, &CairoImageSurface::class_spec}; static cairo_surface_t* copy_ptr(cairo_surface_t* surface) { return cairo_surface_reference(surface); } static void finalize_impl(JS::GCContext*, cairo_surface_t*) {} GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); }; #ifdef CAIRO_HAS_PS_SURFACE class CairoPSSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_ps_surface; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 3; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor, &CairoPSSurface::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoPSSurface::proto_funcs, CairoPSSurface::proto_props, &CairoSurface::define_gtype_prop, }; static constexpr JSClass klass = { "PSSurface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoSurface::class_ops, &CairoPSSurface::class_spec}; static cairo_surface_t* copy_ptr(cairo_surface_t* surface) { return cairo_surface_reference(surface); } static void finalize_impl(JS::GCContext*, cairo_surface_t*) {} GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); }; #else class CairoPSSurface { GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext* cx, cairo_surface_t* surface); }; #endif // CAIRO_HAS_PS_SURFACE #ifdef CAIRO_HAS_PDF_SURFACE class CairoPDFSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_pdf_surface; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 3; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor, &CairoPDFSurface::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoSurface::proto_funcs, CairoSurface::proto_props, &CairoSurface::define_gtype_prop, }; static constexpr JSClass klass = { "PDFSurface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoSurface::class_ops, &CairoPDFSurface::class_spec}; static cairo_surface_t* copy_ptr(cairo_surface_t* surface) { return cairo_surface_reference(surface); } static void finalize_impl(JS::GCContext*, cairo_surface_t*) {} GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); }; #else class CairoPDFSurface { public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext* cx, cairo_surface_t* surface); }; #endif // CAIRO_HAS_PDF_SURFACE #ifdef CAIRO_HAS_SVG_SURFACE class CairoSVGSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_svg_surface; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 3; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor, &CairoSVGSurface::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties nullptr, // prototypeFunctions CairoSVGSurface::proto_props, &CairoSurface::define_gtype_prop, }; static constexpr JSClass klass = { "SVGSurface", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoSurface::class_ops, &CairoSVGSurface::class_spec}; static cairo_surface_t* copy_ptr(cairo_surface_t* surface) { return cairo_surface_reference(surface); } static void finalize_impl(JS::GCContext*, cairo_surface_t*) {} GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); }; #else class CairoSVGSurface { public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext* cx, cairo_surface_t* surface); }; #endif // CAIRO_HAS_SVG_SURFACE /* pattern */ class CairoPattern : public CWrapper { friend CWrapperPointerOps; friend CWrapper; friend class CairoGradient; // "inherits" from CairoPattern friend class CairoLinearGradient; friend class CairoRadialGradient; friend class CairoSurfacePattern; friend class CairoSolidPattern; CairoPattern() = delete; CairoPattern(CairoPattern&) = delete; CairoPattern(CairoPattern&&) = delete; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_pattern; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { &CairoPattern::create_abstract_constructor, nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties CairoPattern::proto_funcs, CairoPattern::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "Pattern", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoPattern::class_spec}; static GType gtype() { return CAIRO_GOBJECT_TYPE_PATTERN; } static cairo_pattern_t* copy_ptr(cairo_pattern_t* pattern) { return cairo_pattern_reference(pattern); } GJS_JSAPI_RETURN_CONVENTION static bool getType_func(JSContext* context, unsigned argc, JS::Value* vp); protected: static void finalize_impl(JS::GCContext*, cairo_pattern_t* pattern); public: static cairo_pattern_t* for_js(JSContext* cx, JS::HandleObject pattern_wrapper); }; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_cairo_pattern_from_pattern (JSContext *context, cairo_pattern_t *pattern); class CairoGradient : public CWrapper { friend CWrapperPointerOps; friend CWrapper; friend class CairoLinearGradient; // "inherits" from CairoGradient friend class CairoRadialGradient; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_gradient; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { &CairoGradient::create_abstract_constructor, &CairoGradient::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoGradient::proto_funcs, CairoGradient::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "Gradient", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoGradient::class_spec}; static void finalize_impl(JS::GCContext*, cairo_pattern_t*) {} }; class CairoLinearGradient : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_linear_gradient; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 4; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor &CairoLinearGradient::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoLinearGradient::proto_funcs, CairoLinearGradient::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "LinearGradient", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoLinearGradient::class_spec}; static cairo_pattern_t* copy_ptr(cairo_pattern_t* pattern) { return cairo_pattern_reference(pattern); } GJS_JSAPI_RETURN_CONVENTION static cairo_pattern_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); static void finalize_impl(JS::GCContext*, cairo_pattern_t*) {} }; class CairoRadialGradient : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_radial_gradient; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 6; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor &CairoRadialGradient::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoRadialGradient::proto_funcs, CairoRadialGradient::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "RadialGradient", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoRadialGradient::class_spec}; static cairo_pattern_t* copy_ptr(cairo_pattern_t* pattern) { return cairo_pattern_reference(pattern); } GJS_JSAPI_RETURN_CONVENTION static cairo_pattern_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); static void finalize_impl(JS::GCContext*, cairo_pattern_t*) {} }; class CairoSurfacePattern : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_surface_pattern; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; static constexpr unsigned constructor_nargs = 1; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec proto_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor &CairoSurfacePattern::new_proto, nullptr, // constructorFunctions nullptr, // constructorProperties CairoSurfacePattern::proto_funcs, CairoSurfacePattern::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "SurfacePattern", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoSurfacePattern::class_spec}; static cairo_pattern_t* copy_ptr(cairo_pattern_t* pattern) { return cairo_pattern_reference(pattern); } GJS_JSAPI_RETURN_CONVENTION static cairo_pattern_t* constructor_impl(JSContext* cx, const JS::CallArgs& args); static void finalize_impl(JS::GCContext*, cairo_pattern_t*) {} }; class CairoSolidPattern : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_cairo_solid_pattern; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_CAIRO; GJS_JSAPI_RETURN_CONVENTION static JSObject* new_proto(JSContext* cx, JSProtoKey); static const JSFunctionSpec static_funcs[]; static const JSPropertySpec proto_props[]; static constexpr js::ClassSpec class_spec = { &CairoSolidPattern::create_abstract_constructor, &CairoSolidPattern::new_proto, CairoSolidPattern::static_funcs, nullptr, // constructorProperties nullptr, // prototypeFunctions CairoSolidPattern::proto_props, &CairoPattern::define_gtype_prop, }; static constexpr JSClass klass = { "SolidPattern", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE, &CairoPattern::class_ops, &CairoSolidPattern::class_spec}; static cairo_pattern_t* copy_ptr(cairo_pattern_t* pattern) { return cairo_pattern_reference(pattern); } static void finalize_impl(JS::GCContext*, cairo_pattern_t*) {} }; #endif // MODULES_CAIRO_PRIVATE_H_ cjs-128.1/modules/cairo-ps-surface.cpp0000664000175000017500000000464215116312211016546 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. // SPDX-FileCopyrightText: 2020 Philip Chimento #include #include // for CAIRO_HAS_PS_SURFACE #include #if CAIRO_HAS_PS_SURFACE # include #endif #include #if CAIRO_HAS_PS_SURFACE # include // for JSPROP_READONLY # include # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey #endif #include "cjs/jsapi-util.h" #if CAIRO_HAS_PS_SURFACE # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" namespace JS { class CallArgs; } JSObject* CairoPSSurface::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoSurface::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_surface_t* CairoPSSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { GjsAutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "PSSurface", argv, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; surface = cairo_ps_surface_create(filename, width, height); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return nullptr; return surface; } // clang-format off const JSPropertySpec CairoPSSurface::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "PSSurface", JSPROP_READONLY), JS_PS_END}; // clang-format on const JSFunctionSpec CairoPSSurface::proto_funcs[] = { // restrictToLevel // getLevels // levelToString // setEPS // getEPS // setSize // dscBeginSetup // dscBeginPageSetup // dscComment JS_FS_END}; #else JSObject* CairoPSSurface::from_c_ptr(JSContext* context, cairo_surface_t* surface) { gjs_throw(context, "could not create PS surface, recompile cairo and gjs with " "PS support."); return nullptr; } #endif /* CAIRO_HAS_PS_SURFACE */ cjs-128.1/modules/cairo-radial-gradient.cpp0000664000175000017500000000334615116312211017525 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "modules/cairo-private.h" namespace JS { class CallArgs; } JSObject* CairoRadialGradient::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoGradient::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_pattern_t* CairoRadialGradient::constructor_impl( JSContext* context, const JS::CallArgs& argv) { double cx0, cy0, radius0, cx1, cy1, radius1; cairo_pattern_t* pattern; if (!gjs_parse_call_args(context, "RadialGradient", argv, "ffffff", "cx0", &cx0, "cy0", &cy0, "radius0", &radius0, "cx1", &cx1, "cy1", &cy1, "radius1", &radius1)) return nullptr; pattern = cairo_pattern_create_radial(cx0, cy0, radius0, cx1, cy1, radius1); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return nullptr; return pattern; } const JSPropertySpec CairoRadialGradient::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "RadialGradient", JSPROP_READONLY), JS_PS_END}; const JSFunctionSpec CairoRadialGradient::proto_funcs[] = { // getRadialCircles JS_FS_END}; cjs-128.1/modules/cairo-region.cpp0000664000175000017500000002344615116312211015764 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2014 Red Hat, Inc. #include #include #include #include #include #include #include // for JSPROP_READONLY #include #include #include #include #include // for JS_NewPlainObject #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" GJS_JSAPI_RETURN_CONVENTION static bool fill_rectangle(JSContext *context, JS::HandleObject obj, cairo_rectangle_int_t *rect); #define PRELUDE \ GJS_GET_THIS(context, argc, vp, argv, obj); \ cairo_region_t* this_region; \ if (!CairoRegion::for_js_typecheck(context, obj, &this_region, &argv)) \ return false; #define RETURN_STATUS \ return gjs_cairo_check_status(context, cairo_region_status(this_region), "region"); #define REGION_DEFINE_REGION_FUNC(method) \ GJS_JSAPI_RETURN_CONVENTION \ static bool method##_func(JSContext* context, unsigned argc, \ JS::Value* vp) { \ PRELUDE; \ JS::RootedObject other_obj(context); \ if (!gjs_parse_call_args(context, #method, argv, "o", "other_region", \ &other_obj)) \ return false; \ \ cairo_region_t* other_region = \ CairoRegion::for_js(context, other_obj); \ \ cairo_region_##method(this_region, other_region); \ argv.rval().setUndefined(); \ RETURN_STATUS; \ } #define REGION_DEFINE_RECT_FUNC(method) \ GJS_JSAPI_RETURN_CONVENTION \ static bool method##_rectangle_func(JSContext* context, unsigned argc, \ JS::Value* vp) { \ PRELUDE; \ JS::RootedObject rect_obj(context); \ if (!gjs_parse_call_args(context, #method, argv, "o", "rect", \ &rect_obj)) \ return false; \ \ cairo_rectangle_int_t rect; \ if (!fill_rectangle(context, rect_obj, &rect)) \ return false; \ \ cairo_region_##method##_rectangle(this_region, &rect); \ argv.rval().setUndefined(); \ RETURN_STATUS; \ } REGION_DEFINE_REGION_FUNC(union) REGION_DEFINE_REGION_FUNC(subtract) REGION_DEFINE_REGION_FUNC(intersect) REGION_DEFINE_REGION_FUNC(xor) REGION_DEFINE_RECT_FUNC(union) REGION_DEFINE_RECT_FUNC(subtract) REGION_DEFINE_RECT_FUNC(intersect) REGION_DEFINE_RECT_FUNC(xor) GJS_JSAPI_RETURN_CONVENTION static bool fill_rectangle(JSContext *context, JS::HandleObject obj, cairo_rectangle_int_t *rect) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedValue val(context); if (!JS_GetPropertyById(context, obj, atoms.x(), &val)) return false; if (!JS::ToInt32(context, val, &rect->x)) return false; if (!JS_GetPropertyById(context, obj, atoms.y(), &val)) return false; if (!JS::ToInt32(context, val, &rect->y)) return false; if (!JS_GetPropertyById(context, obj, atoms.width(), &val)) return false; if (!JS::ToInt32(context, val, &rect->width)) return false; if (!JS_GetPropertyById(context, obj, atoms.height(), &val)) return false; if (!JS::ToInt32(context, val, &rect->height)) return false; return true; } GJS_JSAPI_RETURN_CONVENTION static JSObject * make_rectangle(JSContext *context, cairo_rectangle_int_t *rect) { const GjsAtoms& atoms = GjsContextPrivate::atoms(context); JS::RootedObject rect_obj(context, JS_NewPlainObject(context)); if (!rect_obj) return nullptr; JS::RootedValue val(context); val = JS::Int32Value(rect->x); if (!JS_SetPropertyById(context, rect_obj, atoms.x(), val)) return nullptr; val = JS::Int32Value(rect->y); if (!JS_SetPropertyById(context, rect_obj, atoms.y(), val)) return nullptr; val = JS::Int32Value(rect->width); if (!JS_SetPropertyById(context, rect_obj, atoms.width(), val)) return nullptr; val = JS::Int32Value(rect->height); if (!JS_SetPropertyById(context, rect_obj, atoms.height(), val)) return nullptr; return rect_obj; } GJS_JSAPI_RETURN_CONVENTION static bool num_rectangles_func(JSContext *context, unsigned argc, JS::Value *vp) { PRELUDE; int n_rects; if (!gjs_parse_call_args(context, "num_rectangles", argv, "")) return false; n_rects = cairo_region_num_rectangles(this_region); argv.rval().setInt32(n_rects); RETURN_STATUS; } GJS_JSAPI_RETURN_CONVENTION static bool get_rectangle_func(JSContext *context, unsigned argc, JS::Value *vp) { PRELUDE; int i; JSObject *rect_obj; cairo_rectangle_int_t rect; if (!gjs_parse_call_args(context, "get_rectangle", argv, "i", "rect", &i)) return false; cairo_region_get_rectangle(this_region, i, &rect); rect_obj = make_rectangle(context, &rect); argv.rval().setObjectOrNull(rect_obj); RETURN_STATUS; } // clang-format off const JSPropertySpec CairoRegion::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Region", JSPROP_READONLY), JS_PS_END}; // clang-format on const JSFunctionSpec CairoRegion::proto_funcs[] = { JS_FN("union", union_func, 0, 0), JS_FN("subtract", subtract_func, 0, 0), JS_FN("intersect", intersect_func, 0, 0), JS_FN("xor", xor_func, 0, 0), JS_FN("unionRectangle", union_rectangle_func, 0, 0), JS_FN("subtractRectangle", subtract_rectangle_func, 0, 0), JS_FN("intersectRectangle", intersect_rectangle_func, 0, 0), JS_FN("xorRectangle", xor_rectangle_func, 0, 0), JS_FN("numRectangles", num_rectangles_func, 0, 0), JS_FN("getRectangle", get_rectangle_func, 0, 0), JS_FS_END}; cairo_region_t* CairoRegion::constructor_impl(JSContext* context, const JS::CallArgs& argv) { if (!gjs_parse_call_args(context, "Region", argv, "")) return nullptr; return cairo_region_create(); } void CairoRegion::finalize_impl(JS::GCContext*, cairo_region_t* region) { if (!region) return; cairo_region_destroy(region); } [[nodiscard]] static bool region_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, argument_type); gjs_throw(context, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } JS::RootedObject obj(context, &value.toObject()); cairo_region_t *region; if (!CairoRegion::for_js_typecheck(context, obj, ®ion)) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_region_destroy(region); gjs_arg_set(arg, region); return true; } GJS_JSAPI_RETURN_CONVENTION static bool region_from_gi_argument(JSContext* context, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoRegion::from_c_ptr(context, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } static bool region_release_argument(JSContext*, GITransfer transfer, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) cairo_region_destroy(gjs_arg_get(arg)); return true; } void gjs_cairo_region_init(void) { static GjsForeignInfo foreign_info = {region_to_gi_argument, region_from_gi_argument, region_release_argument}; gjs_struct_foreign_register("cairo", "Region", &foreign_info); } cjs-128.1/modules/cairo-solid-pattern.cpp0000664000175000017500000000563415116312211017265 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "cjs/macros.h" #include "modules/cairo-private.h" JSObject* CairoSolidPattern::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoPattern::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } // clang-format off const JSPropertySpec CairoSolidPattern::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "SolidPattern", JSPROP_READONLY), JS_PS_END}; // clang-format on GJS_JSAPI_RETURN_CONVENTION static bool createRGB_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); double red, green, blue; cairo_pattern_t *pattern; if (!gjs_parse_call_args(context, "createRGB", argv, "fff", "red", &red, "green", &green, "blue", &blue)) return false; pattern = cairo_pattern_create_rgb(red, green, blue); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; JSObject* pattern_wrapper = CairoSolidPattern::from_c_ptr(context, pattern); if (!pattern_wrapper) return false; cairo_pattern_destroy(pattern); argv.rval().setObjectOrNull(pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool createRGBA_func(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp (argc, vp); double red, green, blue, alpha; cairo_pattern_t *pattern; if (!gjs_parse_call_args(context, "createRGBA", argv, "ffff", "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; pattern = cairo_pattern_create_rgba(red, green, blue, alpha); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; JSObject* pattern_wrapper = CairoSolidPattern::from_c_ptr(context, pattern); if (!pattern_wrapper) return false; cairo_pattern_destroy(pattern); argv.rval().setObjectOrNull(pattern_wrapper); return true; } // clang-format off const JSFunctionSpec CairoSolidPattern::static_funcs[] = { JS_FN("createRGB", createRGB_func, 0, 0), JS_FN("createRGBA", createRGBA_func, 0, 0), JS_FS_END}; // clang-format on cjs-128.1/modules/cairo-surface-pattern.cpp0000664000175000017500000001067215116312211017601 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include // for JSPROP_READONLY #include #include #include #include // for JS_NewObjectWithGivenProto #include // for JSProtoKey #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" JSObject* CairoSurfacePattern::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoPattern::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_pattern_t* CairoSurfacePattern::constructor_impl( JSContext* context, const JS::CallArgs& argv) { cairo_pattern_t *pattern; JS::RootedObject surface_wrapper(context); if (!gjs_parse_call_args(context, "SurfacePattern", argv, "o", "surface", &surface_wrapper)) return nullptr; cairo_surface_t* surface = CairoSurface::for_js(context, surface_wrapper); if (!surface) return nullptr; pattern = cairo_pattern_create_for_surface(surface); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return nullptr; return pattern; } const JSPropertySpec CairoSurfacePattern::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "SurfacePattern", JSPROP_READONLY), JS_PS_END}; GJS_JSAPI_RETURN_CONVENTION static bool setExtend_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); cairo_extend_t extend; if (!gjs_parse_call_args(context, "setExtend", argv, "i", "extend", &extend)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; cairo_pattern_set_extend(pattern, extend); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getExtend_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); cairo_extend_t extend; if (argc > 0) { gjs_throw(context, "SurfacePattern.getExtend() requires no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; extend = cairo_pattern_get_extend(pattern); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; rec.rval().setInt32(extend); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setFilter_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); cairo_filter_t filter; if (!gjs_parse_call_args(context, "setFilter", argv, "i", "filter", &filter)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; cairo_pattern_set_filter(pattern, filter); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getFilter_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, rec, obj); cairo_filter_t filter; if (argc > 0) { gjs_throw(context, "SurfacePattern.getFilter() requires no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(context, obj); if (!pattern) return false; filter = cairo_pattern_get_filter(pattern); if (!gjs_cairo_check_status(context, cairo_pattern_status(pattern), "pattern")) return false; rec.rval().setInt32(filter); return true; } // clang-format off const JSFunctionSpec CairoSurfacePattern::proto_funcs[] = { JS_FN("setExtend", setExtend_func, 0, 0), JS_FN("getExtend", getExtend_func, 0, 0), JS_FN("setFilter", setFilter_func, 0, 0), JS_FN("getFilter", getFilter_func, 0, 0), JS_FS_END}; // clang-format on cjs-128.1/modules/cairo-surface.cpp0000664000175000017500000002643415116312211016131 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include #include #include #include #include #include #include // for GetClass #include // for JSPROP_READONLY #include #include #include #include #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/cwrapper.h" #include "gi/foreign.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/cairo-private.h" /* Properties */ // clang-format off const JSPropertySpec CairoSurface::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "Surface", JSPROP_READONLY), JS_PS_END}; // clang-format on /* Methods */ GJS_JSAPI_RETURN_CONVENTION static bool writeToPNG_func(JSContext *context, unsigned argc, JS::Value *vp) { GJS_GET_THIS(context, argc, vp, argv, obj); GjsAutoChar filename; if (!gjs_parse_call_args(context, "writeToPNG", argv, "F", "filename", &filename)) return false; cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; cairo_surface_write_to_png(surface, filename); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION bool flush_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); if (argc > 1) { gjs_throw(cx, "Surface.flush() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_flush(surface); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION bool finish_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); if (argc > 1) { gjs_throw(cx, "Surface.finish() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_finish(surface); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION bool CairoSurface::getType_func(JSContext* context, unsigned argc, JS::Value* vp) { GJS_GET_THIS(context, argc, vp, rec, obj); cairo_surface_type_t type; if (argc > 1) { gjs_throw(context, "Surface.getType() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(context, obj); if (!surface) return false; type = cairo_surface_get_type(surface); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(type); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); double x_offset = 0.0, y_offset = 0.0; if (!gjs_parse_call_args(cx, "setDeviceOffset", args, "ff", "x_offset", &x_offset, "y_offset", &y_offset)) return false; cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_set_device_offset(surface, x_offset, y_offset); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); if (argc > 0) { gjs_throw(cx, "Surface.getDeviceOffset() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; double x_offset, y_offset; cairo_surface_get_device_offset(surface, &x_offset, &y_offset); // cannot error JS::RootedValueArray<2> elements(cx); elements[0].setNumber(JS::CanonicalizeNaN(x_offset)); elements[1].setNumber(JS::CanonicalizeNaN(y_offset)); JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements)); if (!retval) return false; args.rval().setObject(*retval); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); double x_scale = 1.0, y_scale = 1.0; if (!gjs_parse_call_args(cx, "setDeviceScale", args, "ff", "x_scale", &x_scale, "y_scale", &y_scale)) return false; cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_set_device_scale(surface, x_scale, y_scale); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); if (argc > 0) { gjs_throw(cx, "Surface.getDeviceScale() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; double x_scale, y_scale; cairo_surface_get_device_scale(surface, &x_scale, &y_scale); // cannot error JS::RootedValueArray<2> elements(cx); elements[0].setNumber(JS::CanonicalizeNaN(x_scale)); elements[1].setNumber(JS::CanonicalizeNaN(y_scale)); JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements)); if (!retval) return false; args.rval().setObject(*retval); return true; } const JSFunctionSpec CairoSurface::proto_funcs[] = { JS_FN("flush", flush_func, 0, 0), JS_FN("finish", finish_func, 0, 0), // getContent // getFontOptions JS_FN("getType", getType_func, 0, 0), // markDirty // markDirtyRectangle JS_FN("setDeviceOffset", setDeviceOffset_func, 2, 0), JS_FN("getDeviceOffset", getDeviceOffset_func, 0, 0), JS_FN("setDeviceScale", setDeviceScale_func, 2, 0), JS_FN("getDeviceScale", getDeviceScale_func, 0, 0), // setFallbackResolution // getFallbackResolution // copyPage // showPage // hasShowTextGlyphs JS_FN("writeToPNG", writeToPNG_func, 0, 0), JS_FS_END}; /* Public API */ /** * CairoSurface::finalize_impl: * @surface: the pointer to finalize * * Destroys the resources associated with a surface wrapper. * * This is mainly used for subclasses. */ void CairoSurface::finalize_impl(JS::GCContext*, cairo_surface_t* surface) { if (!surface) return; cairo_surface_destroy(surface); } /** * CairoSurface::from_c_ptr: * @context: the context * @surface: cairo_surface to attach to the object * * Constructs a surface wrapper given cairo surface. * A reference to @surface will be taken. * */ JSObject* CairoSurface::from_c_ptr(JSContext* context, cairo_surface_t* surface) { g_return_val_if_fail(context, nullptr); g_return_val_if_fail(surface, nullptr); cairo_surface_type_t type = cairo_surface_get_type(surface); if (type == CAIRO_SURFACE_TYPE_IMAGE) return CairoImageSurface::from_c_ptr(context, surface); if (type == CAIRO_SURFACE_TYPE_PDF) return CairoPDFSurface::from_c_ptr(context, surface); if (type == CAIRO_SURFACE_TYPE_PS) return CairoPSSurface::from_c_ptr(context, surface); if (type == CAIRO_SURFACE_TYPE_SVG) return CairoSVGSurface::from_c_ptr(context, surface); return CairoSurface::CWrapper::from_c_ptr(context, surface); } /** * CairoSurface::for_js: * @cx: the context * @surface_wrapper: surface wrapper * * Overrides NativeObject::for_js(). * * Returns: the surface attached to the wrapper. */ cairo_surface_t* CairoSurface::for_js(JSContext* cx, JS::HandleObject surface_wrapper) { g_return_val_if_fail(cx, nullptr); g_return_val_if_fail(surface_wrapper, nullptr); JS::RootedObject proto(cx, CairoSurface::prototype(cx)); bool is_surface_subclass = false; if (!gjs_object_in_prototype_chain(cx, proto, surface_wrapper, &is_surface_subclass)) return nullptr; if (!is_surface_subclass) { gjs_throw(cx, "Expected Cairo.Surface but got %s", JS::GetClass(surface_wrapper)->name); return nullptr; } return JS::GetMaybePtrFromReservedSlot( surface_wrapper, CairoSurface::POINTER); } [[nodiscard]] static bool surface_to_gi_argument( JSContext* context, JS::Value value, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, argument_type); gjs_throw(context, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } if (!value.isObject()) { GjsAutoChar display_name = gjs_argument_display_name(arg_name, argument_type); gjs_throw(context, "%s is not a Cairo.Surface", display_name.get()); return false; } JS::RootedObject surface_wrapper(context, &value.toObject()); cairo_surface_t* s = CairoSurface::for_js(context, surface_wrapper); if (!s) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_surface_destroy(s); gjs_arg_set(arg, s); return true; } GJS_JSAPI_RETURN_CONVENTION static bool surface_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoSurface::from_c_ptr(cx, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } static bool surface_release_argument(JSContext*, GITransfer transfer, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) cairo_surface_destroy(gjs_arg_get(arg)); return true; } void gjs_cairo_surface_init(void) { static GjsForeignInfo foreign_info = {surface_to_gi_argument, surface_from_gi_argument, surface_release_argument}; gjs_struct_foreign_register("cairo", "Surface", &foreign_info); } cjs-128.1/modules/cairo-svg-surface.cpp0000664000175000017500000000430315116312211016715 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. // SPDX-FileCopyrightText: 2020 Philip Chimento #include #include // for CAIRO_HAS_SVG_SURFACE #include #if CAIRO_HAS_SVG_SURFACE # include #endif #include #if CAIRO_HAS_SVG_SURFACE # include // for JSPROP_READONLY # include # include # include // for JS_NewObjectWithGivenProto # include // for JSProtoKey #endif #include "cjs/jsapi-util.h" #if CAIRO_HAS_SVG_SURFACE # include "cjs/jsapi-util-args.h" # include "modules/cairo-private.h" namespace JS { class CallArgs; } JSObject* CairoSVGSurface::new_proto(JSContext* cx, JSProtoKey) { JS::RootedObject parent_proto(cx, CairoSurface::prototype(cx)); return JS_NewObjectWithGivenProto(cx, nullptr, parent_proto); } cairo_surface_t* CairoSVGSurface::constructor_impl(JSContext* context, const JS::CallArgs& argv) { GjsAutoChar filename; double width, height; cairo_surface_t *surface; if (!gjs_parse_call_args(context, "SVGSurface", argv, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; surface = cairo_svg_surface_create(filename, width, height); if (!gjs_cairo_check_status(context, cairo_surface_status(surface), "surface")) return nullptr; return surface; } // clang-format off const JSPropertySpec CairoSVGSurface::proto_props[] = { JS_STRING_SYM_PS(toStringTag, "SVGSurface", JSPROP_READONLY), JS_PS_END}; // clang-format on #else JSObject* CairoSVGSurface::from_c_ptr(JSContext* context, cairo_surface_t* surface) { gjs_throw(context, "could not create SVG surface, recompile cairo and gjs with " "SVG support."); return nullptr; } #endif /* CAIRO_HAS_SVG_SURFACE */ cjs-128.1/modules/cairo.cpp0000664000175000017500000000500315116312211014470 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. #include #include // for CAIRO_HAS_PDF_SURFACE, CAIRO_HAS_PS_SURFA... #include #ifdef CAIRO_HAS_XLIB_SURFACE # include # undef None // X11 defines a global None macro. Rude! This conflicts with None used as an // enum member in SpiderMonkey headers, e.g. JS::ExceptionStatus::None. #endif #include #include #include // for JS_NewPlainObject #include "cjs/jsapi-util.h" #include "modules/cairo-private.h" #ifdef CAIRO_HAS_XLIB_SURFACE class XLibConstructor { public: XLibConstructor() { XInitThreads(); } }; static XLibConstructor constructor; #endif bool gjs_cairo_check_status(JSContext *context, cairo_status_t status, const char *name) { if (status != CAIRO_STATUS_SUCCESS) { gjs_throw(context, "cairo error on %s: \"%s\" (%d)", name, cairo_status_to_string(status), status); return false; } return true; } bool gjs_js_define_cairo_stuff(JSContext *context, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(context)); if (!CairoRegion::create_prototype(context, module)) return false; gjs_cairo_region_init(); if (!CairoContext::create_prototype(context, module)) return false; gjs_cairo_context_init(); if (!CairoSurface::create_prototype(context, module)) return false; gjs_cairo_surface_init(); return CairoImageSurface::create_prototype(context, module) && CairoPath::create_prototype(context, module) && #if CAIRO_HAS_PS_SURFACE CairoPSSurface::create_prototype(context, module) && #endif #if CAIRO_HAS_PDF_SURFACE CairoPDFSurface::create_prototype(context, module) && #endif #if CAIRO_HAS_SVG_SURFACE CairoSVGSurface::create_prototype(context, module) && #endif CairoPattern::create_prototype(context, module) && CairoGradient::create_prototype(context, module) && CairoLinearGradient::create_prototype(context, module) && CairoRadialGradient::create_prototype(context, module) && CairoSurfacePattern::create_prototype(context, module) && CairoSolidPattern::create_prototype(context, module); } cjs-128.1/modules/console.cpp0000664000175000017500000002450115116312211015041 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* vim: set ts=8 sw=4 et tw=78: */ // SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later // SPDX-FileCopyrightText: 1998 Netscape Communications Corporation #include // for HAVE_READLINE_READLINE_H #ifdef HAVE_SIGNAL_H # include # include # ifdef _WIN32 # define sigjmp_buf jmp_buf # define siglongjmp(e, v) longjmp (e, v) # define sigsetjmp(v, m) setjmp (v) # endif #endif #ifdef HAVE_READLINE_READLINE_H # include // include before readline/readline.h # include # include #endif #include #include #include // for g_fprintf #include #include #include // for JS_EncodeStringToUTF8 #include #include #include #include #include // for CurrentGlobalOrNull #include #include #include #include #include // for UniqueChars #include #include #include #include // for JS_NewPlainObject #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/console.h" namespace mozilla { union Utf8Unit; } static void gjs_console_warning_reporter(JSContext*, JSErrorReport* report) { JS::PrintError(stderr, report, /* reportWarnings = */ true); } /* Based on js::shell::AutoReportException from SpiderMonkey. */ class AutoReportException { JSContext *m_cx; public: explicit AutoReportException(JSContext *cx) : m_cx(cx) {} ~AutoReportException() { if (!JS_IsExceptionPending(m_cx)) return; /* Get exception object before printing and clearing exception. */ JS::ExceptionStack exnStack(m_cx); JS::ErrorReportBuilder report(m_cx); if (!JS::StealPendingExceptionStack(m_cx, &exnStack) || !report.init(m_cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) { g_printerr("(Unable to print exception)\n"); JS_ClearPendingException(m_cx); return; } g_assert(!report.report()->isWarning()); JS::PrintError(stderr, report, /* reportWarnings = */ false); if (exnStack.stack()) { JS::UniqueChars stack_str{ format_saved_frame(m_cx, exnStack.stack(), 2)}; if (!stack_str) { g_printerr("(Unable to print stack trace)\n"); } else { GjsAutoChar encoded_stack_str{g_filename_from_utf8( stack_str.get(), -1, nullptr, nullptr, nullptr)}; if (!encoded_stack_str) g_printerr("(Unable to print stack trace)\n"); else g_printerr("%s", stack_str.get()); } } JS_ClearPendingException(m_cx); } }; // Adapted from https://stackoverflow.com/a/17035073/172999 class AutoCatchCtrlC { #ifdef HAVE_SIGNAL_H void (*m_prev_handler)(int); static void handler(int signal) { if (signal == SIGINT) siglongjmp(jump_buffer, 1); } public: static sigjmp_buf jump_buffer; AutoCatchCtrlC() { m_prev_handler = signal(SIGINT, &AutoCatchCtrlC::handler); } ~AutoCatchCtrlC() { if (m_prev_handler != SIG_ERR) signal(SIGINT, m_prev_handler); } void raise_default() { if (m_prev_handler != SIG_ERR) signal(SIGINT, m_prev_handler); raise(SIGINT); } #endif // HAVE_SIGNAL_H }; #ifdef HAVE_SIGNAL_H sigjmp_buf AutoCatchCtrlC::jump_buffer; #endif // HAVE_SIGNAL_H [[nodiscard]] static bool gjs_console_readline(char** bufp, const char* prompt) { #ifdef HAVE_READLINE_READLINE_H char *line; line = readline(prompt); if (!line) return false; if (line[0] != '\0') add_history(line); *bufp = line; #else // !HAVE_READLINE_READLINE_H char line[256]; fprintf(stdout, "%s", prompt); fflush(stdout); if (!fgets(line, sizeof line, stdin)) return false; *bufp = g_strdup(line); #endif // !HAVE_READLINE_READLINE_H return true; } std::string print_string_value(JSContext* cx, JS::HandleValue v_string) { if (!v_string.isString()) return "[unexpected result from printing value]"; JS::RootedString printed_string(cx, v_string.toString()); JS::AutoSaveExceptionState exc_state(cx); JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, printed_string)); exc_state.restore(); if (!chars) return "[error printing value]"; return chars.get(); } /* Return value of false indicates an uncatchable exception, rather than any * exception. (This is because the exception should be auto-printed around the * invocation of this function.) */ [[nodiscard]] static bool gjs_console_eval_and_print(JSContext* cx, JS::HandleObject global, const std::string& bytes, int lineno) { JS::SourceText source; if (!source.init(cx, bytes.c_str(), bytes.size(), JS::SourceOwnership::Borrowed)) return false; JS::CompileOptions options(cx); options.setFileAndLine("typein", lineno); JS::RootedValue result(cx); if (!JS::Evaluate(cx, options, source, &result)) { if (!JS_IsExceptionPending(cx)) return false; } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); gjs->schedule_gc_if_needed(); JS::AutoSaveExceptionState exc_state(cx); JS::RootedValue v_printed_string(cx); JS::RootedValue v_pretty_print( cx, gjs_get_global_slot(global, GjsGlobalSlot::PRETTY_PRINT_FUNC)); bool ok = JS::Call(cx, global, v_pretty_print, JS::HandleValueArray(result), &v_printed_string); if (!ok) gjs_log_exception(cx); exc_state.restore(); if (ok) { g_fprintf(stdout, "%s\n", print_string_value(cx, v_printed_string).c_str()); } else { g_fprintf(stdout, "[error printing value]\n"); } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_console_interact(JSContext *context, unsigned argc, JS::Value *vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); volatile bool eof, exit_warning; // accessed after setjmp() JS::RootedObject global{context, JS::CurrentGlobalOrNull(context)}; char* temp_buf; volatile int lineno; // accessed after setjmp() volatile int startline; // accessed after setjmp() #ifndef HAVE_READLINE_READLINE_H int rl_end = 0; // nonzero if using readline and any text is typed in #endif JS::SetWarningReporter(context, gjs_console_warning_reporter); AutoCatchCtrlC ctrl_c; // Separate initialization from declaration because of possible overwriting // when siglongjmp() jumps into this function eof = exit_warning = false; temp_buf = nullptr; lineno = 1; do { /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles * cleanly. This should be whenever we get a complete statement that * coincides with the end of a line. */ startline = lineno; std::string buffer; do { #ifdef HAVE_SIGNAL_H // sigsetjmp() returns 0 if control flow encounters it normally, and // nonzero if it's been jumped to. In the latter case, use a while // loop so that we call sigsetjmp() a second time to reinit the jump // buffer. while (sigsetjmp(AutoCatchCtrlC::jump_buffer, 1) != 0) { g_fprintf(stdout, "\n"); if (buffer.empty() && rl_end == 0) { if (!exit_warning) { g_fprintf(stdout, "(To exit, press Ctrl+C again or Ctrl+D)\n"); exit_warning = true; } else { ctrl_c.raise_default(); } } else { exit_warning = false; } buffer.clear(); startline = lineno = 1; } #endif // HAVE_SIGNAL_H if (!gjs_console_readline( &temp_buf, startline == lineno ? "gjs> " : ".... ")) { eof = true; break; } buffer += temp_buf; buffer += "\n"; g_free(temp_buf); lineno++; } while (!JS_Utf8BufferIsCompilableUnit(context, global, buffer.c_str(), buffer.size())); bool ok; { AutoReportException are(context); ok = gjs_console_eval_and_print(context, global, buffer, startline); } exit_warning = false; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context); ok = gjs->run_jobs_fallible() && ok; if (!ok) { /* If this was an uncatchable exception, throw another uncatchable * exception on up to the surrounding JS::Evaluate() in main(). This * happens when you run cjs-console and type imports.system.exit(0); * at the prompt. If we don't throw another uncatchable exception * here, then it's swallowed and main() won't exit. */ return false; } } while (!eof); g_fprintf(stdout, "\n"); argv.rval().setUndefined(); return true; } bool gjs_define_console_stuff(JSContext *context, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(context)); const GjsAtoms& atoms = GjsContextPrivate::atoms(context); return JS_DefineFunctionById(context, module, atoms.interact(), gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS); } cjs-128.1/modules/console.h0000664000175000017500000000073415116312211014510 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC #ifndef MODULES_CONSOLE_H_ #define MODULES_CONSOLE_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_console_stuff(JSContext *context, JS::MutableHandleObject module); #endif // MODULES_CONSOLE_H_ cjs-128.1/modules/core/0000775000175000017500000000000015116312211013621 5ustar fabiofabiocjs-128.1/modules/core/.eslintrc.yml0000664000175000017500000000023615116312211016246 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh rules: jsdoc/require-jsdoc: 'off' cjs-128.1/modules/core/_cairo.js0000664000175000017500000000357615116312211015426 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC. /* exported Antialias, Content, Extend, FillRule, Filter, FontSlant, FontWeight, Format, LineCap, LineJoin, Operator, PatternType, SurfaceType */ var Antialias = { DEFAULT: 0, NONE: 1, GRAY: 2, SUBPIXEL: 3, }; var Content = { COLOR: 0x1000, ALPHA: 0x2000, COLOR_ALPHA: 0x3000, }; var Extend = { NONE: 0, REPEAT: 1, REFLECT: 2, PAD: 3, }; var FillRule = { WINDING: 0, EVEN_ODD: 1, }; var Filter = { FAST: 0, GOOD: 1, BEST: 2, NEAREST: 3, BILINEAR: 4, GAUSSIAN: 5, }; var FontSlant = { NORMAL: 0, ITALIC: 1, OBLIQUE: 2, }; var FontWeight = { NORMAL: 0, BOLD: 1, }; var Format = { ARGB32: 0, RGB24: 1, A8: 2, A1: 3, RGB16_565: 4, }; var LineCap = { BUTT: 0, ROUND: 1, SQUARE: 2, /** @deprecated Historical typo of {@link LineCap.Square}, kept for compatibility reasons */ SQUASH: 2, }; var LineJoin = { MITER: 0, ROUND: 1, BEVEL: 2, }; var Operator = { CLEAR: 0, SOURCE: 1, OVER: 2, IN: 3, OUT: 4, ATOP: 5, DEST: 6, DEST_OVER: 7, DEST_IN: 8, DEST_OUT: 9, DEST_ATOP: 10, XOR: 11, ADD: 12, SATURATE: 13, MULTIPLY: 14, SCREEN: 15, OVERLAY: 16, DARKEN: 17, LIGHTEN: 18, COLOR_DODGE: 19, COLOR_BURN: 20, HARD_LIGHT: 21, SOFT_LIGHT: 22, DIFFERENCE: 23, EXCLUSION: 24, HSL_HUE: 25, HSL_SATURATION: 26, HSL_COLOR: 27, HSL_LUMINOSITY: 28, }; var PatternType = { SOLID: 0, SURFACE: 1, LINEAR: 2, RADIAL: 3, }; var SurfaceType = { IMAGE: 0, PDF: 1, PS: 2, XLIB: 3, XCB: 4, GLITZ: 5, QUARTZ: 6, WIN32: 7, BEOS: 8, DIRECTFB: 9, SVG: 10, OS2: 11, WIN32_PRINTING: 12, QUARTZ_IMAGE: 13, }; cjs-128.1/modules/core/_common.js0000664000175000017500000001074115116312211015611 0ustar fabiofabio// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento /* exported _checkAccessors, _createBuilderConnectFunc, _createClosure, _registerType */ // This is a helper module in which to put code that is common between the // legacy GObject.Class system and the new GObject.registerClass system. var _registerType = Symbol('GObject register type hook'); function _generateAccessors(pspec, propdesc, GObject) { const {name, flags} = pspec; const readable = flags & GObject.ParamFlags.READABLE; const writable = flags & GObject.ParamFlags.WRITABLE; if (!propdesc) { propdesc = { configurable: true, enumerable: true, }; } if (readable && writable) { if (!propdesc.get && !propdesc.set) { const privateName = Symbol(`__autogeneratedAccessor__${name}`); const defaultValue = pspec.get_default_value(); propdesc.get = function () { if (!(privateName in this)) this[privateName] = defaultValue; return this[privateName]; }; propdesc.set = function (value) { if (value !== this[privateName]) { this[privateName] = value; this.notify(name); } }; } else if (!propdesc.get) { propdesc.get = function () { throw new Error(`setter defined without getter for property ${name}`); }; } else if (!propdesc.set) { propdesc.set = function () { throw new Error(`getter defined without setter for property ${name}`); }; } } else if (readable && !propdesc.get) { propdesc.get = function () { throw new Error(`missing getter for read-only property ${name}`); }; } else if (writable && !propdesc.set) { propdesc.set = function () { throw new Error(`missing setter for write-only property ${name}`); }; } return propdesc; } function _checkAccessors(proto, pspec, GObject) { const {name, flags} = pspec; if (flags & GObject.ParamFlags.CONSTRUCT_ONLY) return; const underscoreName = name.replace(/-/g, '_'); const camelName = name.replace(/-([a-z])/g, match => match[1].toUpperCase()); let propdesc = Object.getOwnPropertyDescriptor(proto, name); let dashPropdesc = propdesc, underscorePropdesc, camelPropdesc; const nameIsCompound = name.includes('-'); if (nameIsCompound) { underscorePropdesc = Object.getOwnPropertyDescriptor(proto, underscoreName); camelPropdesc = Object.getOwnPropertyDescriptor(proto, camelName); if (!propdesc) propdesc = underscorePropdesc; if (!propdesc) propdesc = camelPropdesc; } const readable = flags & GObject.ParamFlags.READABLE; const writable = flags & GObject.ParamFlags.WRITABLE; if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set)) propdesc = _generateAccessors(pspec, propdesc, GObject); if (!dashPropdesc) Object.defineProperty(proto, name, propdesc); if (nameIsCompound) { if (!underscorePropdesc) Object.defineProperty(proto, underscoreName, propdesc); if (!camelPropdesc) Object.defineProperty(proto, camelName, propdesc); } } function _createClosure(builder, thisArg, handlerName, swapped, connectObject) { connectObject ??= thisArg; if (swapped) { throw new Error('Unsupported template signal flag "swapped"'); } else if (typeof thisArg[handlerName] === 'undefined') { throw new Error(`A handler called ${handlerName} was not ` + `defined on ${thisArg}`); } return thisArg[handlerName].bind(connectObject); } function _createBuilderConnectFunc(klass) { const {GObject} = imports.gi; return function (builder, obj, signalName, handlerName, connectObj, flags) { const objects = builder.get_objects(); const thisObj = objects.find(o => o instanceof klass); const swapped = flags & GObject.ConnectFlags.SWAPPED; const closure = _createClosure(builder, thisObj, handlerName, swapped, connectObj); if (flags & GObject.ConnectFlags.AFTER) obj.connect_after(signalName, closure); else obj.connect(signalName, closure); }; } cjs-128.1/modules/core/_format.js0000664000175000017500000000470215116312211015611 0ustar fabiofabio// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. // SPDX-FileCopyrightText: 2012 Giovanni Campagna /* exported vprintf */ const CjsPrivate = imports.gi.CjsPrivate; function vprintf(string, args) { let i = 0; let usePos = false; return string.replace(/%(?:([1-9][0-9]*)\$)?(I+)?([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, posGroup, flagsGroup, widthGroup, precisionGroup, genericGroup) { if (precisionGroup !== '' && precisionGroup !== undefined && genericGroup !== 'f') throw new Error("Precision can only be specified for 'f'"); let hasAlternativeIntFlag = flagsGroup && flagsGroup.indexOf('I') !== -1; if (hasAlternativeIntFlag && genericGroup !== 'd') throw new Error("Alternative output digits can only be specified for 'd'"); let pos = parseInt(posGroup, 10) || 0; if (!usePos && i === 0) usePos = pos > 0; if (usePos && pos === 0 || !usePos && pos > 0) throw new Error('Numbered and unnumbered conversion specifications cannot be mixed'); let fillChar = widthGroup && widthGroup[0] === '0' ? '0' : ' '; let width = parseInt(widthGroup, 10) || 0; function fillWidth(s, c, w) { let fill = c.repeat(w); return fill.substr(s.length) + s; } function getArg() { return usePos ? args[pos - 1] : args[i++]; } let s = ''; switch (genericGroup) { case '%': return '%'; case 's': s = String(getArg()); break; case 'd': { let intV = parseInt(getArg()); if (hasAlternativeIntFlag) s = CjsPrivate.format_int_alternative_output(intV); else s = intV.toString(); break; } case 'x': s = parseInt(getArg()).toString(16); break; case 'f': if (precisionGroup === '' || precisionGroup === undefined) s = parseFloat(getArg()).toString(); else s = parseFloat(getArg()).toFixed(parseInt(precisionGroup)); break; default: throw new Error(`Unsupported conversion character %${genericGroup}`); } return fillWidth(s, fillChar, width); }); } cjs-128.1/modules/core/_gettext.js0000664000175000017500000000424615116312211016010 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2009 Red Hat, Inc. /* exported bindtextdomain, dcgettext, dgettext, dngettext, domain, dpgettext, gettext, LocaleCategory, ngettext, pgettext, setlocale, textdomain */ /** * This module provides a convenience layer for the "gettext" family of functions, * relying on GLib for the actual implementation. * * Usage: * * const Gettext = imports.gettext; * * Gettext.textdomain("myapp"); * Gettext.bindtextdomain("myapp", "/usr/share/locale"); * * let translated = Gettext.gettext("Hello world!"); */ const GLib = imports.gi.GLib; const CjsPrivate = imports.gi.CjsPrivate; var LocaleCategory = CjsPrivate.LocaleCategory; function setlocale(category, locale) { return CjsPrivate.setlocale(category, locale); } function textdomain(dom) { return CjsPrivate.textdomain(dom); } function bindtextdomain(dom, location) { return CjsPrivate.bindtextdomain(dom, location); } function gettext(msgid) { return GLib.dgettext(null, msgid); } function dgettext(dom, msgid) { return GLib.dgettext(dom, msgid); } function dcgettext(dom, msgid, category) { return GLib.dcgettext(dom, msgid, category); } function ngettext(msgid1, msgid2, n) { return GLib.dngettext(null, msgid1, msgid2, n); } function dngettext(dom, msgid1, msgid2, n) { return GLib.dngettext(dom, msgid1, msgid2, n); } // FIXME: missing dcngettext ? function pgettext(context, msgid) { return GLib.dpgettext2(null, context, msgid); } function dpgettext(dom, context, msgid) { return GLib.dpgettext2(dom, context, msgid); } /** * Create an object with bindings for gettext, ngettext, * and pgettext bound to a particular translation domain. * * @param {string} domainName Translation domain string * @returns {object} an object with gettext bindings */ function domain(domainName) { return { gettext(msgid) { return GLib.dgettext(domainName, msgid); }, ngettext(msgid1, msgid2, n) { return GLib.dngettext(domainName, msgid1, msgid2, n); }, pgettext(context, msgid) { return GLib.dpgettext2(domainName, context, msgid); }, }; } cjs-128.1/modules/core/_signals.js0000664000175000017500000001226115116312211015760 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2022 Canonical Ltd. // SPDX-FileContributor: Marco Trevisan /* exported addSignalMethods */ // A couple principals of this simple signal system: // 1) should look just like our GObject signal binding // 2) memory and safety matter more than speed of connect/disconnect/emit // 3) the expectation is that a given object will have a very small number of // connections, but they may be to different signal names function _connectFull(name, callback, after) { // be paranoid about callback arg since we'd start to throw from emit() // if it was messed up if (typeof callback !== 'function') throw new Error('When connecting signal must give a callback that is a function'); // we instantiate the "signal machinery" only on-demand if anything // gets connected. if (this._signalConnections === undefined) { this._signalConnections = Object.create(null); this._signalConnectionsByName = Object.create(null); this._nextConnectionId = 1; } const id = this._nextConnectionId; this._nextConnectionId += 1; this._signalConnections[id] = { name, callback, after, }; const connectionsByName = this._signalConnectionsByName[name] ?? []; if (!connectionsByName.length) this._signalConnectionsByName[name] = connectionsByName; connectionsByName.push(id); return id; } function _connect(name, callback) { return _connectFull.call(this, name, callback, false); } function _connectAfter(name, callback) { return _connectFull.call(this, name, callback, true); } function _disconnect(id) { const connection = this._signalConnections?.[id]; if (!connection) throw new Error(`No signal connection ${id} found`); if (connection.disconnected) throw new Error(`Signal handler id ${id} already disconnected`); connection.disconnected = true; delete this._signalConnections[id]; const ids = this._signalConnectionsByName[connection.name]; if (!ids) return; const indexOfId = ids.indexOf(id); if (indexOfId !== -1) ids.splice(indexOfId, 1); if (ids.length === 0) delete this._signalConnectionsByName[connection.name]; } function _signalHandlerIsConnected(id) { const connection = this._signalConnections?.[id]; return !!connection && !connection.disconnected; } function _disconnectAll() { Object.values(this._signalConnections ?? {}).forEach(c => (c.disconnected = true)); delete this._signalConnections; delete this._signalConnectionsByName; } function _emit(name, ...args) { const connections = this._signalConnectionsByName?.[name]; // may not be any signal handlers at all, if not then return if (!connections) return; // To deal with re-entrancy (removal/addition while // emitting), we copy out a list of what was connected // at emission start; and just before invoking each // handler we check its disconnected flag. const handlers = connections.map(id => this._signalConnections[id]); // create arg array which is emitter + everything passed in except // signal name. Would be more convenient not to pass emitter to // the callback, but trying to be 100% consistent with GObject // which does pass it in. Also if we pass in the emitter here, // people don't create closures with the emitter in them, // which would be a cycle. const argArray = [this, ...args]; const afterHandlers = []; const beforeHandlers = handlers.filter(c => { if (!c.after) return true; afterHandlers.push(c); return false; }); if (!_callHandlers(beforeHandlers, argArray)) _callHandlers(afterHandlers, argArray); } function _callHandlers(handlers, argArray) { for (const handler of handlers) { if (handler.disconnected) continue; try { // since we pass "null" for this, the global object will be used. const ret = handler.callback.apply(null, argArray); // if the callback returns true, we don't call the next // signal handlers if (ret === true) return true; } catch (e) { // just log any exceptions so that callbacks can't disrupt // signal emission logError(e, `Exception in callback for signal: ${handler.name}`); } } return false; } function _addSignalMethod(proto, functionName, func) { if (proto[functionName] && proto[functionName] !== func) log(`WARNING: addSignalMethods is replacing existing ${proto} ${functionName} method`); proto[functionName] = func; } function addSignalMethods(proto) { _addSignalMethod(proto, 'connect', _connect); _addSignalMethod(proto, 'connectAfter', _connectAfter); _addSignalMethod(proto, 'disconnect', _disconnect); _addSignalMethod(proto, 'emit', _emit); _addSignalMethod(proto, 'signalHandlerIsConnected', _signalHandlerIsConnected); // this one is not in GObject, but useful _addSignalMethod(proto, 'disconnectAll', _disconnectAll); } cjs-128.1/modules/core/overrides/0000775000175000017500000000000015116312211015623 5ustar fabiofabiocjs-128.1/modules/core/overrides/.eslintrc.yml0000664000175000017500000000031115116312211020242 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento rules: no-unused-vars: - error - varsIgnorePattern: ^_init$ cjs-128.1/modules/core/overrides/GLib.js0000664000175000017500000005162315116312211017005 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna // SPDX-FileCopyrightText: 2023 Philip Chimento const {setMainLoopHook} = imports._promiseNative; let GLib; const SIMPLE_TYPES = ['b', 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd', 's', 'o', 'g']; function _readSingleType(signature, forceSimple) { let char = signature.shift(); let isSimple = false; if (!SIMPLE_TYPES.includes(char)) { if (forceSimple) throw new TypeError('Invalid GVariant signature (a simple type was expected)'); } else { isSimple = true; } if (char === 'm' || char === 'a') return [char].concat(_readSingleType(signature, false)); if (char === '{') { let key = _readSingleType(signature, true); let val = _readSingleType(signature, false); let close = signature.shift(); if (close !== '}') throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}"'); return [char].concat(key, val, close); } if (char === '(') { let res = [char]; while (true) { if (signature.length === 0) throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")'); let next = signature[0]; if (next === ')') { signature.shift(); return res.concat(next); } let el = _readSingleType(signature); res = res.concat(el); } } // Valid types are simple types, arrays, maybes, tuples, dictionary entries and variants if (!isSimple && char !== 'v') throw new TypeError(`Invalid GVariant signature (${char} is not a valid type)`); return [char]; } function _packVariant(signature, value) { if (signature.length === 0) throw new TypeError('GVariant signature cannot be empty'); let char = signature.shift(); switch (char) { case 'b': return GLib.Variant.new_boolean(value); case 'y': return GLib.Variant.new_byte(value); case 'n': return GLib.Variant.new_int16(value); case 'q': return GLib.Variant.new_uint16(value); case 'i': return GLib.Variant.new_int32(value); case 'u': return GLib.Variant.new_uint32(value); case 'x': return GLib.Variant.new_int64(value); case 't': return GLib.Variant.new_uint64(value); case 'h': return GLib.Variant.new_handle(value); case 'd': return GLib.Variant.new_double(value); case 's': return GLib.Variant.new_string(value); case 'o': return GLib.Variant.new_object_path(value); case 'g': return GLib.Variant.new_signature(value); case 'v': return GLib.Variant.new_variant(value); case 'm': if (value !== null) { return GLib.Variant.new_maybe(null, _packVariant(signature, value)); } else { return GLib.Variant.new_maybe(new GLib.VariantType( _readSingleType(signature, false).join('')), null); } case 'a': { let arrayType = _readSingleType(signature, false); if (arrayType[0] === 's') { // special case for array of strings return GLib.Variant.new_strv(value); } if (arrayType[0] === 'y') { // special case for array of bytes if (typeof value === 'string') value = Uint8Array.of(...new TextEncoder().encode(value), 0); const bytes = new GLib.Bytes(value); return GLib.Variant.new_from_bytes(new GLib.VariantType('ay'), bytes, true); } let arrayValue = []; if (arrayType[0] === '{') { // special case for dictionaries for (let key in value) { let copy = [].concat(arrayType); let child = _packVariant(copy, [key, value[key]]); arrayValue.push(child); } } else { for (let i = 0; i < value.length; i++) { let copy = [].concat(arrayType); let child = _packVariant(copy, value[i]); arrayValue.push(child); } } return GLib.Variant.new_array(new GLib.VariantType(arrayType.join('')), arrayValue); } case '(': { let children = []; for (let i = 0; i < value.length; i++) { let next = signature[0]; if (next === ')') break; children.push(_packVariant(signature, value[i])); } if (signature[0] !== ')') throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")'); signature.shift(); return GLib.Variant.new_tuple(children); } case '{': { let key = _packVariant(signature, value[0]); let child = _packVariant(signature, value[1]); if (signature[0] !== '}') throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}")'); signature.shift(); return GLib.Variant.new_dict_entry(key, child); } default: throw new TypeError(`Invalid GVariant signature (unexpected character ${char})`); } } function _unpackVariant(variant, deep, recursive = false) { switch (String.fromCharCode(variant.classify())) { case 'b': return variant.get_boolean(); case 'y': return variant.get_byte(); case 'n': return variant.get_int16(); case 'q': return variant.get_uint16(); case 'i': return variant.get_int32(); case 'u': return variant.get_uint32(); case 'x': return variant.get_int64(); case 't': return variant.get_uint64(); case 'h': return variant.get_handle(); case 'd': return variant.get_double(); case 'o': case 'g': case 's': // g_variant_get_string has length as out argument return variant.get_string()[0]; case 'v': { const ret = variant.get_variant(); if (deep && recursive && ret instanceof GLib.Variant) return _unpackVariant(ret, deep, recursive); return ret; } case 'm': { let val = variant.get_maybe(); if (deep && val) return _unpackVariant(val, deep, recursive); else return val; } case 'a': if (variant.is_of_type(new GLib.VariantType('a{?*}'))) { // special case containers let ret = { }; let nElements = variant.n_children(); for (let i = 0; i < nElements; i++) { // always unpack the dictionary entry, and always unpack // the key (or it cannot be added as a key) let val = _unpackVariant(variant.get_child_value(i), deep, recursive); let key; if (!deep) key = _unpackVariant(val[0], true); else key = val[0]; ret[key] = val[1]; } return ret; } if (variant.is_of_type(new GLib.VariantType('ay'))) { // special case byte arrays return variant.get_data_as_bytes().toArray(); } // fall through case '(': case '{': { let ret = []; let nElements = variant.n_children(); for (let i = 0; i < nElements; i++) { let val = variant.get_child_value(i); if (deep) ret.push(_unpackVariant(val, deep, recursive)); else ret.push(val); } return ret; } } throw new Error('Assertion failure: this code should not be reached'); } function _notIntrospectableError(funcName, replacement) { return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`); } function _warnNotIntrospectable(funcName, replacement) { logError(_notIntrospectableError(funcName, replacement)); } function _escapeCharacterSetChars(char) { if ('-^]\\'.includes(char)) return `\\${char}`; return char; } function _init() { // this is imports.gi.GLib GLib = this; GLib.MainLoop.prototype.runAsync = function (...args) { return new Promise((resolve, reject) => { setMainLoopHook(() => { try { resolve(this.run(...args)); } catch (error) { reject(error); } }); }); }; // For convenience in property min or max values, since GLib.MAXINT64 and // friends will log a warning when used this.MAXINT64_BIGINT = 0x7fff_ffff_ffff_ffffn; this.MININT64_BIGINT = -this.MAXINT64_BIGINT - 1n; this.MAXUINT64_BIGINT = 0xffff_ffff_ffff_ffffn; // small HACK: we add a matches() method to standard Errors so that // you can do "if (e.matches(Ns.FooError, Ns.FooError.SOME_CODE))" // without checking instanceof Error.prototype.matches = function () { return false; }; // Guard against domains that aren't valid quarks and would lead // to a crash const quarkToString = this.quark_to_string; const realNewLiteral = this.Error.new_literal; this.Error.new_literal = function (domain, code, message) { if (quarkToString(domain) === null) throw new TypeError(`Error.new_literal: ${domain} is not a valid domain`); return realNewLiteral(domain, code, message); }; this.Variant._new_internal = function (sig, value) { let signature = Array.prototype.slice.call(sig); let variant = _packVariant(signature, value); if (signature.length !== 0) throw new TypeError('Invalid GVariant signature (more than one single complete type)'); return variant; }; // Deprecate version of new GLib.Variant() this.Variant.new = function (sig, value) { return new GLib.Variant(sig, value); }; this.Variant.prototype.unpack = function () { return _unpackVariant(this, false); }; this.Variant.prototype.deepUnpack = function () { return _unpackVariant(this, true); }; // backwards compatibility alias this.Variant.prototype.deep_unpack = this.Variant.prototype.deepUnpack; // Note: discards type information, if the variant contains any 'v' types this.Variant.prototype.recursiveUnpack = function () { return _unpackVariant(this, true, true); }; this.Variant.prototype.toString = function () { return `[object variant of type "${this.get_type_string()}"]`; }; this.Bytes.prototype.toArray = function () { return imports._byteArrayNative.fromGBytes(this); }; this.log_structured = /** * @param {string} logDomain Log domain. * @param {GLib.LogLevelFlags} logLevel Log level, either from GLib.LogLevelFlags, or a user-defined level. * @param {Record} fields Key-value pairs of structured data to add to the log entry. * @returns {void} */ function log_structured(logDomain, logLevel, fields) { /** @type {Record} */ let variantFields = {}; for (let key in fields) { const field = fields[key]; if (field instanceof Uint8Array) { variantFields[key] = new GLib.Variant('ay', field); } else if (typeof field === 'string') { variantFields[key] = new GLib.Variant('s', field); } else if (field instanceof GLib.Variant) { // GLib.log_variant converts all Variants that are // not 'ay' or 's' type to strings by printing // them. // // https://gitlab.gnome.org/GNOME/glib/-/blob/a380bfdf93cb3bfd3cd4caedc0127c4e5717545b/glib/gmessages.c#L1894 variantFields[key] = field; } else { throw new TypeError(`Unsupported value ${field}, log_structured supports GLib.Variant, Uint8Array, and string values.`); } } GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', variantFields)); }; // CjsPrivate depends on GLib so we cannot import it // before GLib is fully resolved. this.log_set_writer_func_variant = function (...args) { const {log_set_writer_func} = imports.gi.CjsPrivate; log_set_writer_func(...args); }; this.log_set_writer_default = function (...args) { const {log_set_writer_default} = imports.gi.CjsPrivate; log_set_writer_default(...args); }; this.log_set_writer_func = function (writer_func) { const {log_set_writer_func} = imports.gi.CjsPrivate; if (typeof writer_func !== 'function') { log_set_writer_func(writer_func); } else { log_set_writer_func(function (logLevel, stringFields) { const stringFieldsObj = {...stringFields.recursiveUnpack()}; return writer_func(logLevel, stringFieldsObj); }); } }; this.VariantDict.prototype.lookup = function (key, variantType = null, deep = false) { if (typeof variantType === 'string') variantType = new GLib.VariantType(variantType); const variant = this.lookup_value(key, variantType); if (variant === null) return null; return _unpackVariant(variant, deep); }; // Prevent user code from calling GLib string manipulation functions that // return the same string that was passed in. These can't be annotated // properly, and will mostly crash. // Here we provide approximate implementations of the functions so that if // they had happened to work in the past, they will continue working, but // log a stack trace and a suggestion of what to use instead. // Exceptions are thrown instead for GLib.stpcpy() of which the return value // is useless anyway and GLib.ascii_formatd() which is too complicated to // implement here. this.stpcpy = function () { throw _notIntrospectableError('GLib.stpcpy()', 'the + operator'); }; this.strstr_len = function (haystack, len, needle) { _warnNotIntrospectable('GLib.strstr_len()', 'String.indexOf()'); let searchString = haystack; if (len !== -1) searchString = searchString.slice(0, len); const index = searchString.indexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strrstr = function (haystack, needle) { _warnNotIntrospectable('GLib.strrstr()', 'String.lastIndexOf()'); const index = haystack.lastIndexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strrstr_len = function (haystack, len, needle) { _warnNotIntrospectable('GLib.strrstr_len()', 'String.lastIndexOf()'); let searchString = haystack; if (len !== -1) searchString = searchString.slice(0, len); const index = searchString.lastIndexOf(needle); if (index === -1) return null; return haystack.slice(index); }; this.strup = function (string) { _warnNotIntrospectable('GLib.strup()', 'String.toUpperCase() or GLib.ascii_strup()'); return string.toUpperCase(); }; this.strdown = function (string) { _warnNotIntrospectable('GLib.strdown()', 'String.toLowerCase() or GLib.ascii_strdown()'); return string.toLowerCase(); }; this.strreverse = function (string) { _warnNotIntrospectable('GLib.strreverse()', 'Array.reverse() and String.join()'); return [...string].reverse().join(''); }; this.ascii_dtostr = function (unused, len, number) { _warnNotIntrospectable('GLib.ascii_dtostr()', 'JS string conversion'); return `${number}`.slice(0, len); }; this.ascii_formatd = function () { throw _notIntrospectableError('GLib.ascii_formatd()', 'Number.toExponential() and string interpolation'); }; this.strchug = function (string) { _warnNotIntrospectable('GLib.strchug()', 'String.trimStart()'); return string.trimStart(); }; this.strchomp = function (string) { _warnNotIntrospectable('GLib.strchomp()', 'String.trimEnd()'); return string.trimEnd(); }; // g_strstrip() is a macro and therefore doesn't even appear in the GIR // file, but we may as well include it here since it's trivial this.strstrip = function (string) { _warnNotIntrospectable('GLib.strstrip()', 'String.trim()'); return string.trim(); }; this.strdelimit = function (string, delimiters, newDelimiter) { _warnNotIntrospectable('GLib.strdelimit()', 'String.replace()'); if (delimiters === null) delimiters = GLib.STR_DELIMITERS; if (typeof newDelimiter === 'number') newDelimiter = String.fromCharCode(newDelimiter); const delimiterChars = delimiters.split(''); const escapedDelimiterChars = delimiterChars.map(_escapeCharacterSetChars); const delimiterRegex = new RegExp(`[${escapedDelimiterChars.join('')}]`, 'g'); return string.replace(delimiterRegex, newDelimiter); }; this.strcanon = function (string, validChars, substitutor) { _warnNotIntrospectable('GLib.strcanon()', 'String.replace()'); if (typeof substitutor === 'number') substitutor = String.fromCharCode(substitutor); const validArray = validChars.split(''); const escapedValidArray = validArray.map(_escapeCharacterSetChars); const invalidRegex = new RegExp(`[^${escapedValidArray.join('')}]`, 'g'); return string.replace(invalidRegex, substitutor); }; // Prevent user code from calling GThread functions which always crash this.Thread.new = function () { throw _notIntrospectableError('GLib.Thread.new()', 'GIO asynchronous methods or Promise()'); }; this.Thread.try_new = function () { throw _notIntrospectableError('GLib.Thread.try_new()', 'GIO asynchronous methods or Promise()'); }; this.Thread.exit = function () { throw new Error('\'GLib.Thread.exit()\' may not be called in GJS'); }; this.Thread.prototype.ref = function () { throw new Error('\'GLib.Thread.ref()\' may not be called in GJS'); }; this.Thread.prototype.unref = function () { throw new Error('\'GLib.Thread.unref()\' may not be called in GJS'); }; // Override GLib.MatchInfo with a type that keeps the UTF-8 encoded search // string alive. const oldMatchInfo = this.MatchInfo; let matchInfoPatched = false; function patchMatchInfo(GLibModule) { if (matchInfoPatched) return; const {MatchInfo} = imports.gi.CjsPrivate; const originalMatchInfoMethods = new Set(Object.keys(oldMatchInfo.prototype)); const overriddenMatchInfoMethods = new Set(Object.keys(MatchInfo.prototype)); const symmetricDifference = new Set(originalMatchInfoMethods); for (const method of overriddenMatchInfoMethods) { if (symmetricDifference.has(method)) symmetricDifference.delete(method); else symmetricDifference.add(method); } if (symmetricDifference.size !== 0) throw new Error(`Methods of GMatchInfo and GjsMatchInfo don't match: ${[...symmetricDifference]}`); GLibModule.MatchInfo = MatchInfo; matchInfoPatched = true; } // We can't monkeypatch GLib.MatchInfo directly at override time, because // importing CjsPrivate requires GLib. So this monkeypatches GLib.MatchInfo // with a Proxy that overwrites itself with the real CjsPrivate.MatchInfo // as soon as you try to do anything with it. const allProxyOperations = ['apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf']; function delegateToMatchInfo(op) { return function (target, ...params) { patchMatchInfo(GLib); return Reflect[op](GLib.MatchInfo, ...params); }; } this.MatchInfo = new Proxy(function () {}, Object.fromEntries(allProxyOperations.map(op => [op, delegateToMatchInfo(op)]))); this.Regex.prototype.match = function (...args) { patchMatchInfo(GLib); return imports.gi.CjsPrivate.regex_match(this, ...args); }; this.Regex.prototype.match_full = function (...args) { patchMatchInfo(GLib); return imports.gi.CjsPrivate.regex_match_full(this, ...args); }; this.Regex.prototype.match_all = function (...args) { patchMatchInfo(GLib); return imports.gi.CjsPrivate.regex_match_all(this, ...args); }; this.Regex.prototype.match_all_full = function (...args) { patchMatchInfo(GLib); return imports.gi.CjsPrivate.regex_match_all_full(this, ...args); }; } cjs-128.1/modules/core/overrides/GObject.js0000664000175000017500000010563015116312211017503 0ustar fabiofabio/* exported _init, interfaces, properties, registerClass, requires, signals */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Jasper St. Pierre // SPDX-FileCopyrightText: 2017 Philip Chimento , const Gi = imports._gi; const {CjsPrivate, GLib} = imports.gi; const {_checkAccessors, _registerType} = imports._common; const Legacy = imports._legacy; let GObject; var GTypeName = Symbol('GType name'); var GTypeFlags = Symbol('GType flags'); var interfaces = Symbol('GObject interfaces'); var properties = Symbol('GObject properties'); var requires = Symbol('GObject interface requires'); var signals = Symbol('GObject signals'); // These four will be aliased to GTK var _gtkChildren = Symbol('GTK widget template children'); var _gtkCssName = Symbol('GTK widget CSS name'); var _gtkInternalChildren = Symbol('GTK widget template internal children'); var _gtkTemplate = Symbol('GTK widget template'); function registerClass(...args) { let klass = args[0]; if (args.length === 2) { // The two-argument form is the convenient syntax without ESnext // decorators and class data properties. The first argument is an // object with meta info such as properties and signals. The second // argument is the class expression for the class itself. // // var MyClass = GObject.registerClass({ // Properties: { ... }, // Signals: { ... }, // }, class MyClass extends GObject.Object { // _init() { ... } // }); // // When decorators and class data properties become part of the JS // standard, this function can be used directly as a decorator. let metaInfo = args[0]; klass = args[1]; if ('GTypeName' in metaInfo) klass[GTypeName] = metaInfo.GTypeName; if ('GTypeFlags' in metaInfo) klass[GTypeFlags] = metaInfo.GTypeFlags; if ('Implements' in metaInfo) klass[interfaces] = metaInfo.Implements; if ('Properties' in metaInfo) klass[properties] = metaInfo.Properties; if ('Signals' in metaInfo) klass[signals] = metaInfo.Signals; if ('Requires' in metaInfo) klass[requires] = metaInfo.Requires; if ('CssName' in metaInfo) klass[_gtkCssName] = metaInfo.CssName; if ('Template' in metaInfo) klass[_gtkTemplate] = metaInfo.Template; if ('Children' in metaInfo) klass[_gtkChildren] = metaInfo.Children; if ('InternalChildren' in metaInfo) klass[_gtkInternalChildren] = metaInfo.InternalChildren; } if (!(klass.prototype instanceof GObject.Object) && !(klass.prototype instanceof GObject.Interface)) { throw new TypeError('GObject.registerClass() used with invalid base ' + `class (is ${Object.getPrototypeOf(klass).name})`); } if ('_classInit' in klass) { klass = klass._classInit(klass); } else { // Lang.Class compatibility. klass = _resolveLegacyClassFunction(klass, '_classInit')(klass); } return klass; } function _resolveLegacyClassFunction(klass, func) { // Find the "least derived" class with a _classInit static function; there // definitely is one, since this class must inherit from GObject let initclass = klass; while (typeof initclass[func] === 'undefined') initclass = Object.getPrototypeOf(initclass.prototype).constructor; return initclass[func]; } function _defineGType(klass, giPrototype, registeredType) { const config = { enumerable: false, configurable: false, }; /** * class Example { * // The JS object for this class' ObjectPrototype * static [Gi.gobject_prototype_symbol] = ... * static get $gtype () { * return ...; * } * } * * // Equal to the same property on the constructor * Example.prototype[Gi.gobject_prototype_symbol] = ... */ Object.defineProperty(klass, '$gtype', { ...config, get() { return registeredType; }, }); Object.defineProperty(klass.prototype, Gi.gobject_prototype_symbol, { ...config, writable: false, value: giPrototype, }); } // Some common functions between GObject.Class and GObject.Interface function _createSignals(gtype, sigs) { for (let signalName in sigs) { let obj = sigs[signalName]; let flags = obj.flags !== undefined ? obj.flags : GObject.SignalFlags.RUN_FIRST; let accumulator = obj.accumulator !== undefined ? obj.accumulator : GObject.AccumulatorType.NONE; let rtype = obj.return_type !== undefined ? obj.return_type : GObject.TYPE_NONE; let paramtypes = obj.param_types !== undefined ? obj.param_types : []; try { obj.signal_id = Gi.signal_new(gtype, signalName, flags, accumulator, rtype, paramtypes); } catch (e) { throw new TypeError(`Invalid signal ${signalName}: ${e.message}`); } } } function _getCallerBasename() { const stackLines = new Error().stack.trim().split('\n'); const lineRegex = new RegExp(/@(.+:\/\/)?(.*\/)?(.+)\.js:\d+(:[\d]+)?$/); let thisFile = null; let thisDir = null; for (let line of stackLines) { let match = line.match(lineRegex); if (match) { let scriptDir = match[2]; let scriptBasename = match[3]; if (!thisFile) { thisDir = scriptDir; thisFile = scriptBasename; continue; } if (scriptDir === thisDir && scriptBasename === thisFile) continue; if (scriptDir && scriptDir.startsWith('/org/cinnamon/cjs/')) continue; let basename = scriptBasename; if (scriptDir) { scriptDir = scriptDir.replace(/^\/|\/$/g, ''); basename = `${scriptDir.split('/').reverse()[0]}_${basename}`; } return basename; } } return null; } function _createGTypeName(klass) { const sanitizeGType = s => s.replace(/[^a-z0-9+_-]/gi, '_'); if (Object.hasOwn(klass, GTypeName)) { let sanitized = sanitizeGType(klass[GTypeName]); if (sanitized !== klass[GTypeName]) { logError(new RangeError(`Provided GType name '${klass[GTypeName]}' ` + `is not valid; automatically sanitized to '${sanitized}'`)); } return sanitized; } let gtypeClassName = klass.name; if (GObject.gtypeNameBasedOnJSPath) { let callerBasename = _getCallerBasename(); if (callerBasename) gtypeClassName = `${callerBasename}_${gtypeClassName}`; } if (gtypeClassName === '') gtypeClassName = `anonymous_${GLib.uuid_string_random()}`; return sanitizeGType(`Gjs_${gtypeClassName}`); } function _propertiesAsArray(klass) { let propertiesArray = []; if (Object.hasOwn(klass, properties)) { for (let prop in klass[properties]) propertiesArray.push(klass[properties][prop]); } return propertiesArray; } function _copyInterfacePrototypeDescriptors(targetPrototype, sourceInterface) { Object.entries(Object.getOwnPropertyDescriptors(sourceInterface)) .filter(([key, descriptor]) => // Don't attempt to copy the constructor or toString implementations !['constructor', 'toString'].includes(key) && // Ignore properties starting with __ (typeof key !== 'string' || !key.startsWith('__')) && // Don't override an implementation on the target !Object.hasOwn(targetPrototype, key) && descriptor && // Only copy if the descriptor has a getter, is a function, or is enumerable. (typeof descriptor.value === 'function' || descriptor.get || descriptor.enumerable)) .forEach(([key, descriptor]) => { Object.defineProperty(targetPrototype, key, descriptor); }); } function _interfacePresent(required, klass) { if (!klass[interfaces]) return false; if (klass[interfaces].includes(required)) return true; // implemented here // Might be implemented on a parent class return _interfacePresent(required, Object.getPrototypeOf(klass)); } function _checkInterface(iface, proto) { // Checks for specific interfaces // Default vfunc_async_init() will run vfunc_init() in a thread and crash. // Change error message when https://gitlab.gnome.org/GNOME/gjs/issues/72 // has been solved. if (iface.$gtype.name === 'GAsyncInitable' && !Object.getOwnPropertyNames(proto).includes('vfunc_init_async')) throw new Error("It's not currently possible to implement Gio.AsyncInitable."); // Check that proto implements all of this interface's required interfaces. // "proto" refers to the object's prototype (which implements the interface) // whereas "iface.prototype" is the interface's prototype (which may still // contain unimplemented methods.) if (typeof iface[requires] === 'undefined') return; let unfulfilledReqs = iface[requires].filter(required => { // Either the interface is not present or it is not listed before the // interface that requires it or the class does not inherit it. This is // so that required interfaces don't copy over properties from other // interfaces that require them. let ifaces = proto.constructor[interfaces]; return (!_interfacePresent(required, proto.constructor) || ifaces.indexOf(required) > ifaces.indexOf(iface)) && !(proto instanceof required); }).map(required => // required.name will be present on JS classes, but on introspected // GObjects it will be the C name. The alternative is just so that // we print something if there is garbage in Requires. required.name || required); if (unfulfilledReqs.length > 0) { throw new Error('The following interfaces must be implemented before ' + `${iface.name}: ${unfulfilledReqs.join(', ')}`); } } function _registerGObjectType(klass) { const gtypename = _createGTypeName(klass); const gflags = Object.hasOwn(klass, GTypeFlags) ? klass[GTypeFlags] : 0; const gobjectInterfaces = Object.hasOwn(klass, interfaces) ? klass[interfaces] : []; const propertiesArray = _propertiesAsArray(klass); const parent = Object.getPrototypeOf(klass); const gobjectSignals = Object.hasOwn(klass, signals) ? klass[signals] : []; // Default to the GObject-specific prototype, fallback on the JS prototype // for GI native classes. const parentPrototype = parent.prototype[Gi.gobject_prototype_symbol] ?? parent.prototype; const [giPrototype, registeredType] = Gi.register_type_with_class(klass, parentPrototype, gtypename, gflags, gobjectInterfaces, propertiesArray); _defineGType(klass, giPrototype, registeredType); _createSignals(klass.$gtype, gobjectSignals); // Reverse the interface array to give the last required interface // precedence over the first. const requiredInterfaces = [...gobjectInterfaces].reverse(); requiredInterfaces.forEach(iface => _copyInterfacePrototypeDescriptors(klass, iface)); requiredInterfaces.forEach(iface => _copyInterfacePrototypeDescriptors(klass.prototype, iface.prototype)); Object.getOwnPropertyNames(klass.prototype) .filter(name => name.startsWith('vfunc_') || name.startsWith('on_')) .forEach(name => { let descr = Object.getOwnPropertyDescriptor(klass.prototype, name); if (typeof descr.value !== 'function') return; let func = klass.prototype[name]; if (name.startsWith('vfunc_')) { giPrototype[Gi.hook_up_vfunc_symbol](name.slice(6), func); } else if (name.startsWith('on_')) { let id = GObject.signal_lookup(name.slice(3).replace('_', '-'), klass.$gtype); if (id !== 0) { GObject.signal_override_class_closure(id, klass.$gtype, function (...argArray) { let emitter = argArray.shift(); return func.apply(emitter, argArray); }); } } }); gobjectInterfaces.forEach(iface => _checkInterface(iface, klass.prototype)); // Lang.Class parent classes don't support static inheritance if (!('implements' in klass)) klass.implements = GObject.Object.implements; } function _interfaceInstanceOf(instance) { if (instance && typeof instance === 'object' && GObject.Interface.prototype.isPrototypeOf(this.prototype)) return GObject.type_is_a(instance, this); return false; } function _registerInterfaceType(klass) { const gtypename = _createGTypeName(klass); const gobjectInterfaces = Object.hasOwn(klass, requires) ? klass[requires] : []; const props = _propertiesAsArray(klass); const gobjectSignals = Object.hasOwn(klass, signals) ? klass[signals] : []; const [giPrototype, registeredType] = Gi.register_interface_with_class( klass, gtypename, gobjectInterfaces, props); _defineGType(klass, giPrototype, registeredType); _createSignals(klass.$gtype, gobjectSignals); Object.defineProperty(klass, Symbol.hasInstance, { value: _interfaceInstanceOf, }); } function _checkProperties(klass) { if (!Object.hasOwn(klass, properties)) return; for (let pspec of Object.values(klass[properties])) _checkAccessors(klass.prototype, pspec, GObject); } function _init() { GObject = this; function _makeDummyClass(obj, name, upperName, gtypeName, actual) { let gtype = GObject.type_from_name(gtypeName); obj[`TYPE_${upperName}`] = gtype; obj[name] = function (v) { return actual(v); }; obj[name].$gtype = gtype; } GObject.gtypeNameBasedOnJSPath = false; _makeDummyClass(GObject, 'VoidType', 'NONE', 'void', function () {}); _makeDummyClass(GObject, 'Char', 'CHAR', 'gchar', Number); _makeDummyClass(GObject, 'UChar', 'UCHAR', 'guchar', Number); _makeDummyClass(GObject, 'Unichar', 'UNICHAR', 'gint', String); GObject.TYPE_BOOLEAN = GObject.type_from_name('gboolean'); GObject.Boolean = Boolean; Boolean.$gtype = GObject.TYPE_BOOLEAN; _makeDummyClass(GObject, 'Int', 'INT', 'gint', Number); _makeDummyClass(GObject, 'UInt', 'UINT', 'guint', Number); _makeDummyClass(GObject, 'Long', 'LONG', 'glong', Number); _makeDummyClass(GObject, 'ULong', 'ULONG', 'gulong', Number); _makeDummyClass(GObject, 'Int64', 'INT64', 'gint64', Number); _makeDummyClass(GObject, 'UInt64', 'UINT64', 'guint64', Number); GObject.TYPE_ENUM = GObject.type_from_name('GEnum'); GObject.TYPE_FLAGS = GObject.type_from_name('GFlags'); _makeDummyClass(GObject, 'Float', 'FLOAT', 'gfloat', Number); GObject.TYPE_DOUBLE = GObject.type_from_name('gdouble'); GObject.Double = Number; Number.$gtype = GObject.TYPE_DOUBLE; GObject.TYPE_STRING = GObject.type_from_name('gchararray'); GObject.String = String; String.$gtype = GObject.TYPE_STRING; GObject.TYPE_JSOBJECT = GObject.type_from_name('JSObject'); GObject.JSObject = Object; Object.$gtype = GObject.TYPE_JSOBJECT; GObject.TYPE_POINTER = GObject.type_from_name('gpointer'); GObject.TYPE_BOXED = GObject.type_from_name('GBoxed'); GObject.TYPE_PARAM = GObject.type_from_name('GParam'); GObject.TYPE_INTERFACE = GObject.type_from_name('GInterface'); GObject.TYPE_OBJECT = GObject.type_from_name('GObject'); GObject.TYPE_VARIANT = GObject.type_from_name('GVariant'); _makeDummyClass(GObject, 'Type', 'GTYPE', 'GType', GObject.type_from_name); GObject.ParamSpec.char = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_char(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uchar = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uchar(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.int = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_int(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uint = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uint(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.long = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_long(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.ulong = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_ulong(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.int64 = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_int64(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.uint64 = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_uint64(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.float = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_float(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.boolean = function (name, nick, blurb, flags, defaultValue) { return GObject.param_spec_boolean(name, nick, blurb, defaultValue, flags); }; GObject.ParamSpec.flags = function (name, nick, blurb, flags, flagsType, defaultValue) { return GObject.param_spec_flags(name, nick, blurb, flagsType, defaultValue, flags); }; GObject.ParamSpec.enum = function (name, nick, blurb, flags, enumType, defaultValue) { return GObject.param_spec_enum(name, nick, blurb, enumType, defaultValue, flags); }; GObject.ParamSpec.double = function (name, nick, blurb, flags, minimum, maximum, defaultValue) { return GObject.param_spec_double(name, nick, blurb, minimum, maximum, defaultValue, flags); }; GObject.ParamSpec.string = function (name, nick, blurb, flags, defaultValue) { return GObject.param_spec_string(name, nick, blurb, defaultValue, flags); }; GObject.ParamSpec.boxed = function (name, nick, blurb, flags, boxedType) { return GObject.param_spec_boxed(name, nick, blurb, boxedType, flags); }; GObject.ParamSpec.object = function (name, nick, blurb, flags, objectType) { return GObject.param_spec_object(name, nick, blurb, objectType, flags); }; GObject.ParamSpec.jsobject = function (name, nick, blurb, flags) { return GObject.param_spec_boxed(name, nick, blurb, Object.$gtype, flags); }; GObject.ParamSpec.param = function (name, nick, blurb, flags, paramType) { return GObject.param_spec_param(name, nick, blurb, paramType, flags); }; GObject.ParamSpec.override = Gi.override_property; Object.defineProperties(GObject.ParamSpec.prototype, { 'name': { configurable: false, enumerable: false, get() { return this.get_name(); }, }, '_nick': { configurable: false, enumerable: false, get() { return this.get_nick(); }, }, 'nick': { configurable: false, enumerable: false, get() { return this.get_nick(); }, }, '_blurb': { configurable: false, enumerable: false, get() { return this.get_blurb(); }, }, 'blurb': { configurable: false, enumerable: false, get() { return this.get_blurb(); }, }, 'default_value': { configurable: false, enumerable: false, get() { return this.get_default_value(); }, }, 'flags': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_flags(this); }, }, 'value_type': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_value_type(this); }, }, 'owner_type': { configurable: false, enumerable: false, get() { return CjsPrivate.param_spec_get_owner_type(this); }, }, }); let {GObjectMeta, GObjectInterface} = Legacy.defineGObjectLegacyObjects(GObject); GObject.Class = GObjectMeta; GObject.Interface = GObjectInterface; GObject.Object.prototype.__metaclass__ = GObject.Class; // For compatibility with Lang.Class... we need a _construct // or the Lang.Class constructor will fail. GObject.Object.prototype._construct = function (...args) { this._init(...args); return this; }; GObject.registerClass = registerClass; GObject.Object.new = function (gtype, props = {}) { const constructor = Gi.lookupConstructor(gtype); if (!constructor) throw new Error(`Constructor for gtype ${gtype} not found`); return new constructor(props); }; GObject.Object.new_with_properties = function (gtype, names, values) { if (!Array.isArray(names) || !Array.isArray(values)) throw new Error('new_with_properties takes two arrays (names, values)'); if (names.length !== values.length) throw new Error('Arrays passed to new_with_properties must be the same length'); const props = Object.fromEntries(names.map((name, ix) => [name, values[ix]])); return GObject.Object.new(gtype, props); }; GObject.Object._classInit = function (klass) { _checkProperties(klass); if (_registerType in klass) klass[_registerType](klass); else _resolveLegacyClassFunction(klass, _registerType)(klass); return klass; }; // For backwards compatibility only. Use instanceof instead. GObject.Object.implements = function (iface) { if (iface.$gtype) return GObject.type_is_a(this, iface.$gtype); return false; }; Object.defineProperty(GObject.Object, _registerType, { value: _registerGObjectType, writable: false, configurable: false, enumerable: false, }); Object.defineProperty(GObject.Interface, _registerType, { value: _registerInterfaceType, writable: false, configurable: false, enumerable: false, }); GObject.Interface._classInit = function (klass) { if (_registerType in klass) klass[_registerType](klass); else _resolveLegacyClassFunction(klass, _registerType)(klass); Object.getOwnPropertyNames(klass.prototype) .filter(key => key !== 'constructor') .concat(Object.getOwnPropertySymbols(klass.prototype)) .forEach(key => { let descr = Object.getOwnPropertyDescriptor(klass.prototype, key); // Create wrappers on the interface object so that generics work (e.g. // SomeInterface.some_function(this, blah) instead of // SomeInterface.prototype.some_function.call(this, blah) if (typeof descr.value === 'function') { let interfaceProto = klass.prototype; // capture in closure klass[key] = function (thisObj, ...args) { return interfaceProto[key].call(thisObj, ...args); }; } Object.defineProperty(klass.prototype, key, descr); }); return klass; }; /** * Use this to signify a function that must be overridden in an * implementation of the interface. */ GObject.NotImplementedError = class NotImplementedError extends Error { get name() { return 'NotImplementedError'; } }; // These will be copied in the Gtk overrides // Use __X__ syntax to indicate these variables should not be used publicly. GObject.__gtkCssName__ = _gtkCssName; GObject.__gtkTemplate__ = _gtkTemplate; GObject.__gtkChildren__ = _gtkChildren; GObject.__gtkInternalChildren__ = _gtkInternalChildren; // Expose GObject static properties for ES6 classes GObject.GTypeName = GTypeName; GObject.requires = requires; GObject.interfaces = interfaces; GObject.properties = properties; GObject.signals = signals; // Replacement for non-introspectable g_object_set() GObject.Object.prototype.set = function (params) { Object.assign(this, params); }; GObject.Object.prototype.bind_property_full = function (...args) { return CjsPrivate.g_object_bind_property_full(this, ...args); }; if (GObject.BindingGroup !== undefined) { GObject.BindingGroup.prototype.bind_full = function (...args) { return CjsPrivate.g_binding_group_bind_full(this, ...args); }; } // fake enum for signal accumulators, keep in sync with gi/object.c GObject.AccumulatorType = { NONE: 0, FIRST_WINS: 1, TRUE_HANDLED: 2, }; GObject.Object.prototype.disconnect = function (id) { return GObject.signal_handler_disconnect(this, id); }; GObject.Object.prototype.block_signal_handler = function (id) { return GObject.signal_handler_block(this, id); }; GObject.Object.prototype.unblock_signal_handler = function (id) { return GObject.signal_handler_unblock(this, id); }; GObject.Object.prototype.stop_emission_by_name = function (detailedName) { return GObject.signal_stop_emission_by_name(this, detailedName); }; // A simple workaround if you have a class with .connect, .disconnect or .emit // methods (such as Gio.Socket.connect or NMClient.Device.disconnect) // The original g_signal_* functions are not introspectable anyway, because // we need our own handling of signal argument marshalling GObject.signal_connect = function (object, name, handler) { return GObject.Object.prototype.connect.call(object, name, handler); }; GObject.signal_connect_after = function (object, name, handler) { return GObject.Object.prototype.connect_after.call(object, name, handler); }; GObject.signal_emit_by_name = function (object, ...nameAndArgs) { return GObject.Object.prototype.emit.apply(object, nameAndArgs); }; // Replacements for signal_handler_find() and similar functions, which can't // work normally since we connect private closures GObject._real_signal_handler_find = GObject.signal_handler_find; GObject._real_signal_handlers_block_matched = GObject.signal_handlers_block_matched; GObject._real_signal_handlers_unblock_matched = GObject.signal_handlers_unblock_matched; GObject._real_signal_handlers_disconnect_matched = GObject.signal_handlers_disconnect_matched; /** * Finds the first signal handler that matches certain selection criteria. * The criteria are passed as properties of a match object. * The match object has to be non-empty for successful matches. * If no handler was found, a falsy value is returned. * * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} [match.func] - the callback function the handler will * invoke. * @returns {number | bigint | object | null} A valid non-0 signal handler ID for * a successful match. */ GObject.signal_handler_find = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handler_find(...arguments); return instance[Gi.signal_find_symbol](match); }; /** * Blocks all handlers on an instance that match certain selection criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of blocked handlers * otherwise. * * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_block_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_block_matched(...arguments); return instance[Gi.signals_block_symbol](match); }; /** * Unblocks all handlers on an instance that match certain selection * criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of unblocked * handlers otherwise. * The match criteria should not apply to any handlers that are not * currently blocked. * * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_unblock_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_unblock_matched(...arguments); return instance[Gi.signals_unblock_symbol](match); }; /** * Disconnects all handlers on an instance that match certain selection * criteria. * The criteria are passed as properties of a match object. * The match object has to have at least `func` for successful matches. * If no handlers were found, 0 is returned, the number of disconnected * handlers otherwise. * * @function * @param {GObject.Object} instance - the instance owning the signal handler * to be found. * @param {object} match - a properties object indicating whether to match * by signal ID, detail, or callback function. * @param {string} [match.signalId] - signal the handler has to be connected * to. * @param {string} [match.detail] - signal detail the handler has to be * connected to. * @param {Function} match.func - the callback function the handler will * invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_disconnect_matched = function (instance, match) { // For backwards compatibility if (arguments.length === 7) // eslint-disable-next-line prefer-rest-params return GObject._real_signal_handlers_disconnect_matched(...arguments); return instance[Gi.signals_disconnect_symbol](match); }; // Also match the macros used in C APIs, even though they're not introspected /** * Blocks all handlers on an instance that match `func`. * * @function * @param {GObject.Object} instance - the instance to block handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_block_by_func = function (instance, func) { return instance[Gi.signals_block_symbol]({func}); }; /** * Unblocks all handlers on an instance that match `func`. * * @function * @param {GObject.Object} instance - the instance to unblock handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_unblock_by_func = function (instance, func) { return instance[Gi.signals_unblock_symbol]({func}); }; /** * Disconnects all handlers on an instance that match `func`. * * @function * @param {GObject.Object} instance - the instance to remove handlers from. * @param {Function} func - the callback function the handler will invoke. * @returns {number} The number of handlers that matched. */ GObject.signal_handlers_disconnect_by_func = function (instance, func) { return instance[Gi.signals_disconnect_symbol]({func}); }; GObject.signal_handlers_disconnect_by_data = function () { throw new Error('GObject.signal_handlers_disconnect_by_data() is not \ introspectable. Use GObject.signal_handlers_disconnect_by_func() instead.'); }; function unsupportedDataMethod() { throw new Error('Data access methods are unsupported. Use normal JS properties instead.'); } GObject.Object.prototype.get_data = unsupportedDataMethod; GObject.Object.prototype.get_qdata = unsupportedDataMethod; GObject.Object.prototype.set_data = unsupportedDataMethod; GObject.Object.prototype.steal_data = unsupportedDataMethod; GObject.Object.prototype.steal_qdata = unsupportedDataMethod; function unsupportedRefcountingMethod() { throw new Error("Don't modify an object's reference count in JS."); } GObject.Object.prototype.force_floating = unsupportedRefcountingMethod; GObject.Object.prototype.ref = unsupportedRefcountingMethod; GObject.Object.prototype.ref_sink = unsupportedRefcountingMethod; GObject.Object.prototype.unref = unsupportedRefcountingMethod; } cjs-128.1/modules/core/overrides/Gio.js0000664000175000017500000010057015116312211016702 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna var GLib = imports.gi.GLib; var CjsPrivate = imports.gi.CjsPrivate; var Signals = imports.signals; const { warnDeprecatedOncePerCallsite, PLATFORM_SPECIFIC_TYPELIB } = imports._print; var Gio; // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with // a parameter type that includes 'h' somewhere, actually has entries in it for // each of the indices being passed as an 'h' parameter. function _validateFDVariant(variant, fdList) { switch (String.fromCharCode(variant.classify())) { case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': case 'x': case 't': case 'd': case 'o': case 'g': case 's': return; case 'h': { const val = variant.get_handle(); const numFds = fdList.get_length(); if (val >= numFds) { throw new Error(`handle ${val} is out of range of Gio.UnixFDList ` + `containing ${numFds} FDs`); } return; } case 'v': _validateFDVariant(variant.get_variant(), fdList); return; case 'm': { let val = variant.get_maybe(); if (val) _validateFDVariant(val, fdList); return; } case 'a': case '(': case '{': { let nElements = variant.n_children(); for (let ix = 0; ix < nElements; ix++) _validateFDVariant(variant.get_child_value(ix), fdList); return; } } throw new Error('Assertion failure: this code should not be reached'); } function _proxyInvoker(methodName, sync, inSignature, argArray) { var replyFunc; var flags = 0; var cancellable = null; let fdList = null; /* Convert argArray to a *real* array */ argArray = Array.prototype.slice.call(argArray); /* The default replyFunc only logs the responses */ replyFunc = _logReply; var signatureLength = inSignature.length; var minNumberArgs = signatureLength; var maxNumberArgs = signatureLength + 4; if (argArray.length < minNumberArgs) { throw new Error(`Not enough arguments passed for method: ${ methodName}. Expected ${minNumberArgs}, got ${argArray.length}`); } else if (argArray.length > maxNumberArgs) { throw new Error(`Too many arguments passed for method ${methodName}. ` + `Maximum is ${maxNumberArgs} including one callback, ` + 'Gio.Cancellable, Gio.UnixFDList, and/or flags'); } while (argArray.length > signatureLength) { var argNum = argArray.length - 1; var arg = argArray.pop(); if (typeof arg === 'function' && !sync) { replyFunc = arg; } else if (typeof arg === 'number') { flags = arg; } else if (arg instanceof Gio.Cancellable) { cancellable = arg; } else if (arg instanceof Gio.UnixFDList) { fdList = arg; } else { throw new Error(`Argument ${argNum} of method ${methodName} is ` + `${typeof arg}. It should be a callback, flags, ` + 'Gio.UnixFDList, or a Gio.Cancellable'); } } const inTypeString = `(${inSignature.join('')})`; const inVariant = new GLib.Variant(inTypeString, argArray); if (inTypeString.includes('h')) { if (!fdList) { throw new Error(`Method ${methodName} with input type containing ` + '\'h\' must have a Gio.UnixFDList as an argument'); } _validateFDVariant(inVariant, fdList); } var asyncCallback = (proxy, result) => { try { const [outVariant, outFdList] = proxy.call_with_unix_fd_list_finish(result); replyFunc(outVariant.deepUnpack(), null, outFdList); } catch (e) { replyFunc([], e, null); } }; if (sync) { const [outVariant, outFdList] = this.call_with_unix_fd_list_sync( methodName, inVariant, flags, -1, fdList, cancellable); if (fdList) return [outVariant.deepUnpack(), outFdList]; return outVariant.deepUnpack(); } return this.call_with_unix_fd_list(methodName, inVariant, flags, -1, fdList, cancellable, asyncCallback); } function _logReply(result, exc) { if (exc !== null) log(`Ignored exception from dbus method: ${exc}`); } function _makeProxyMethod(method, sync) { var i; var name = method.name; var inArgs = method.in_args; var inSignature = []; for (i = 0; i < inArgs.length; i++) inSignature.push(inArgs[i].signature); return function (...args) { return _proxyInvoker.call(this, name, sync, inSignature, args); }; } function _convertToNativeSignal(proxy, senderName, signalName, parameters) { Signals._emit.call(proxy, signalName, senderName, parameters.deepUnpack()); } function _propertyGetter(name) { let value = this.get_cached_property(name); return value ? value.deepUnpack() : null; } function _propertySetter(name, signature, value) { let variant = new GLib.Variant(signature, value); this.set_cached_property(name, variant); this.call('org.freedesktop.DBus.Properties.Set', new GLib.Variant('(ssv)', [this.g_interface_name, name, variant]), Gio.DBusCallFlags.NONE, -1, null, (proxy, result) => { try { this.call_finish(result); } catch (e) { log(`Could not set property ${name} on remote object ${ this.g_object_path}: ${e.message}`); } }); } function _addDBusConvenience() { let info = this.g_interface_info; if (!info) return; if (info.signals.length > 0) this.connect('g-signal', _convertToNativeSignal); let i, methods = info.methods; for (i = 0; i < methods.length; i++) { var method = methods[i]; let remoteMethod = _makeProxyMethod(methods[i], false); this[`${method.name}Remote`] = remoteMethod; this[`${method.name}Sync`] = _makeProxyMethod(methods[i], true); this[`${method.name}Async`] = function (...args) { return new Promise((resolve, reject) => { args.push((result, error, fdList) => { if (error) reject(error); else if (fdList) resolve([result, fdList]); else resolve(result); }); remoteMethod.call(this, ...args); }); }; } let properties = info.properties; for (i = 0; i < properties.length; i++) { let name = properties[i].name; let signature = properties[i].signature; let flags = properties[i].flags; let getter = () => { throw new Error(`Property ${name} is not readable`); }; let setter = () => { throw new Error(`Property ${name} is not writable`); }; if (flags & Gio.DBusPropertyInfoFlags.READABLE) getter = _propertyGetter.bind(this, name); if (flags & Gio.DBusPropertyInfoFlags.WRITABLE) setter = _propertySetter.bind(this, name, signature); Object.defineProperty(this, name, { get: getter, set: setter, configurable: false, enumerable: true, }); } } function _makeProxyWrapper(interfaceXml) { var info = _newInterfaceInfo(interfaceXml); var iname = info.name; function wrapper(bus, name, object, asyncCallback, cancellable, flags = Gio.DBusProxyFlags.NONE) { var obj = new Gio.DBusProxy({ g_connection: bus, g_interface_name: iname, g_interface_info: info, g_name: name, g_flags: flags, g_object_path: object, }); if (!cancellable) cancellable = null; if (asyncCallback) { obj.init_async(GLib.PRIORITY_DEFAULT, cancellable).then( () => asyncCallback(obj, null)).catch(e => asyncCallback(null, e)); } else { obj.init(cancellable); } return obj; } wrapper.newAsync = function newAsync(bus, name, object, cancellable, flags = Gio.DBusProxyFlags.NONE) { const obj = new Gio.DBusProxy({ g_connection: bus, g_interface_name: info.name, g_interface_info: info, g_name: name, g_flags: flags, g_object_path: object, }); return new Promise((resolve, reject) => obj.init_async(GLib.PRIORITY_DEFAULT, cancellable ?? null).then( () => resolve(obj)).catch(reject)); }; return wrapper; } function _newNodeInfo(constructor, value) { if (typeof value === 'string') return constructor(value); throw TypeError(`Invalid type ${Object.prototype.toString.call(value)}`); } function _newInterfaceInfo(value) { var nodeInfo = Gio.DBusNodeInfo.new_for_xml(value); return nodeInfo.interfaces[0]; } function _injectToMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { addition.apply(this, args); return previous.apply(this, args); }; } function _injectToStaticMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...parameters) { let obj = previous.apply(this, parameters); addition.apply(obj, parameters); return obj; }; } function _wrapFunction(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { args.unshift(previous); return addition.apply(this, args); }; } function _makeOutSignature(args) { var ret = '('; for (var i = 0; i < args.length; i++) ret += args[i].signature; return `${ret})`; } function _handleMethodCall(info, impl, methodName, parameters, invocation) { // prefer a sync version if available if (this[methodName]) { let retval; try { const fdList = invocation.get_message().get_unix_fd_list(); retval = this[methodName](...parameters.deepUnpack(), fdList); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { let name = e.name; if (!name.includes('.')) { // likely to be a normal JS error name = `org.gnome.gjs.JSError.${name}`; } logError(e, `Exception in method call: ${methodName}`); invocation.return_dbus_error(name, e.message); } return; } if (retval === undefined) { // undefined (no return value) is the empty tuple retval = new GLib.Variant('()', []); } try { let outFdList = null; if (!(retval instanceof GLib.Variant)) { // attempt packing according to out signature let methodInfo = info.lookup_method(methodName); let outArgs = methodInfo.out_args; let outSignature = _makeOutSignature(outArgs); if (outSignature.includes('h') && retval[retval.length - 1] instanceof Gio.UnixFDList) { outFdList = retval.pop(); } else if (outArgs.length === 1) { // if one arg, we don't require the handler wrapping it // into an Array retval = [retval]; } retval = new GLib.Variant(outSignature, retval); } invocation.return_value_with_unix_fd_list(retval, outFdList); } catch (e) { // if we don't do this, the other side will never see a reply invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type'); } } else if (this[`${methodName}Async`]) { const fdList = invocation.get_message().get_unix_fd_list(); this[`${methodName}Async`](parameters.deepUnpack(), invocation, fdList); } else { log(`Missing handler for DBus method ${methodName}`); invocation.return_gerror(new Gio.DBusError({ code: Gio.DBusError.UNKNOWN_METHOD, message: `Method ${methodName} is not implemented`, })); } } function _handlePropertyGet(info, impl, propertyName) { let propInfo = info.lookup_property(propertyName); let jsval = this[propertyName]; if (jsval?.get_type_string?.() === propInfo.signature) return jsval; else if (jsval !== undefined) return new GLib.Variant(propInfo.signature, jsval); else return null; } function _handlePropertySet(info, impl, propertyName, newValue) { this[propertyName] = newValue.deepUnpack(); } function _wrapJSObject(interfaceInfo, jsObj) { var info; if (interfaceInfo instanceof Gio.DBusInterfaceInfo) info = interfaceInfo; else info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo); info.cache_build(); var impl = new CjsPrivate.DBusImplementation({g_interface_info: info}); impl.connect('handle-method-call', function (self, methodName, parameters, invocation) { return _handleMethodCall.call(jsObj, info, self, methodName, parameters, invocation); }); impl.connect('handle-property-get', function (self, propertyName) { return _handlePropertyGet.call(jsObj, info, self, propertyName); }); impl.connect('handle-property-set', function (self, propertyName, value) { return _handlePropertySet.call(jsObj, info, self, propertyName, value); }); return impl; } function* _listModelIterator() { let _index = 0; const _len = this.get_n_items(); while (_index < _len) yield this.get_item(_index++); } function _promisify(proto, asyncFunc, finishFunc = undefined) { if (proto[asyncFunc] === undefined) throw new Error(`${proto} has no method named ${asyncFunc}`); if (finishFunc === undefined) { if (asyncFunc.endsWith('_begin') || asyncFunc.endsWith('_async')) finishFunc = `${asyncFunc.slice(0, -5)}finish`; else finishFunc = `${asyncFunc}_finish`; } if (proto[finishFunc] === undefined) throw new Error(`${proto} has no method named ${finishFunc}`); const originalFuncName = `_original_${asyncFunc}`; if (proto[originalFuncName] !== undefined) return; proto[originalFuncName] = proto[asyncFunc]; proto[asyncFunc] = function (...args) { if (args.length === this[originalFuncName].length) return this[originalFuncName](...args); return new Promise((resolve, reject) => { let {stack: callStack} = new Error(); this[originalFuncName](...args, function (source, res) { try { const result = source !== null && source[finishFunc] !== undefined ? source[finishFunc](res) : proto[finishFunc](res); if (Array.isArray(result) && result.length > 1 && result[0] === true) result.shift(); resolve(result); } catch (error) { callStack = callStack.split('\n').filter(line => line.indexOf('_promisify/') === -1).join('\n'); if (error.stack) error.stack += `### Promise created here: ###\n${callStack}`; else error.stack = callStack; reject(error); } }); }); }; } function _notIntrospectableError(funcName, replacement) { return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`); } function _warnNotIntrospectable(funcName, replacement) { logError(_notIntrospectableError(funcName, replacement)); } function _init() { Gio = this; let GioPlatform = {}; Gio.Application.prototype.runAsync = GLib.MainLoop.prototype.runAsync; if (GLib.MAJOR_VERSION > 2 || (GLib.MAJOR_VERSION === 2 && GLib.MINOR_VERSION >= 86)) { // Redefine Gio functions with platform-specific implementations to be // backward compatible with gi-repository 1.0, however when possible we // notify a deprecation warning, to ensure that the surrounding code is // updated. try { GioPlatform = imports.gi.GioUnix; } catch { try { GioPlatform = imports.gi.GioWin32; } catch {} } } const platformName = `${GioPlatform?.__name__?.slice(3 /* 'Gio'.length */)}`; const platformNameLower = platformName.toLowerCase(); Object.entries(Object.getOwnPropertyDescriptors(GioPlatform)).forEach(([prop, desc]) => { let genericProp = prop; const originalValue = GioPlatform[prop]; const gtypeName = originalValue.$gtype?.name; if (gtypeName?.startsWith(`G${platformName}`)) genericProp = `${platformName}${prop}`; else if (originalValue instanceof Function && originalValue.name.startsWith(`g_${platformNameLower}_`)) genericProp = `${platformNameLower}_${prop}`; if (Object.hasOwn(Gio, genericProp)) { console.debug(`Gio already contains property ${genericProp}`); Gio[genericProp] = originalValue; return; } Object.defineProperty(Gio, genericProp, { enumerable: true, configurable: false, get() { warnDeprecatedOncePerCallsite(PLATFORM_SPECIFIC_TYPELIB, `Gio.${genericProp}`, `${GioPlatform.__name__}.${prop}`); return desc.get?.() ?? desc.value; }, }); }); Gio.DBus = { // Namespace some functions get: Gio.bus_get, get_finish: Gio.bus_get_finish, get_sync: Gio.bus_get_sync, own_name: Gio.bus_own_name, own_name_on_connection: Gio.bus_own_name_on_connection, unown_name: Gio.bus_unown_name, watch_name: Gio.bus_watch_name, watch_name_on_connection: Gio.bus_watch_name_on_connection, unwatch_name: Gio.bus_unwatch_name, }; Object.defineProperties(Gio.DBus, { 'session': { get() { return Gio.bus_get_sync(Gio.BusType.SESSION, null); }, enumerable: false, }, 'system': { get() { return Gio.bus_get_sync(Gio.BusType.SYSTEM, null); }, enumerable: false, }, }); Gio.DBusConnection.prototype.watch_name = function (name, flags, appeared, vanished) { return Gio.bus_watch_name_on_connection(this, name, flags, appeared, vanished); }; Gio.DBusConnection.prototype.unwatch_name = function (id) { return Gio.bus_unwatch_name(id); }; Gio.DBusConnection.prototype.own_name = function (name, flags, acquired, lost) { return Gio.bus_own_name_on_connection(this, name, flags, acquired, lost); }; Gio.DBusConnection.prototype.unown_name = function (id) { return Gio.bus_unown_name(id); }; _injectToMethod(Gio.DBusProxy.prototype, 'init', _addDBusConvenience); _promisify(Gio.DBusProxy.prototype, 'init_async'); _injectToMethod(Gio.DBusProxy.prototype, 'init_async', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_finish', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_finish', _addDBusConvenience); Gio.DBusProxy.prototype.connectSignal = Signals._connect; Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect; Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper; // Some helpers _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo); Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo; Gio.DBusExportedObject = CjsPrivate.DBusImplementation; Gio.DBusExportedObject.wrapJSObject = _wrapJSObject; // ListStore Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator; Gio.ListStore.prototype.insert_sorted = function (item, compareFunc) { return CjsPrivate.list_store_insert_sorted(this, item, compareFunc); }; Gio.ListStore.prototype.sort = function (compareFunc) { return CjsPrivate.list_store_sort(this, compareFunc); }; // Promisify Gio._promisify = _promisify; // Temporary Gio.File.prototype fix Gio._LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype; Gio.File.prototype.replace_contents_async = function replace_contents_async(contents, etag, make_backup, flags, cancellable, callback) { return this.replace_contents_bytes_async(contents, etag, make_backup, flags, cancellable, callback); }; // Best-effort attempt to replace set_attribute(), which is not // introspectable due to the pointer argument Gio.File.prototype.set_attribute = function set_attribute(attribute, type, value, flags, cancellable) { _warnNotIntrospectable('Gio.File.prototype.set_attribute', 'set_attribute_{type}'); switch (type) { case Gio.FileAttributeType.STRING: return this.set_attribute_string(attribute, value, flags, cancellable); case Gio.FileAttributeType.BYTE_STRING: return this.set_attribute_byte_string(attribute, value, flags, cancellable); case Gio.FileAttributeType.UINT32: return this.set_attribute_uint32(attribute, value, flags, cancellable); case Gio.FileAttributeType.INT32: return this.set_attribute_int32(attribute, value, flags, cancellable); case Gio.FileAttributeType.UINT64: return this.set_attribute_uint64(attribute, value, flags, cancellable); case Gio.FileAttributeType.INT64: return this.set_attribute_int64(attribute, value, flags, cancellable); case Gio.FileAttributeType.INVALID: case Gio.FileAttributeType.BOOLEAN: case Gio.FileAttributeType.OBJECT: case Gio.FileAttributeType.STRINGV: throw _notIntrospectableError('This attribute type', 'Gio.FileInfo'); } }; Gio.FileInfo.prototype.set_attribute = function set_attribute(attribute, type, value) { _warnNotIntrospectable('Gio.FileInfo.prototype.set_attribute', 'set_attribute_{type}'); switch (type) { case Gio.FileAttributeType.INVALID: return this.remove_attribute(attribute); case Gio.FileAttributeType.STRING: return this.set_attribute_string(attribute, value); case Gio.FileAttributeType.BYTE_STRING: return this.set_attribute_byte_string(attribute, value); case Gio.FileAttributeType.BOOLEAN: return this.set_attribute_boolean(attribute, value); case Gio.FileAttributeType.UINT32: return this.set_attribute_uint32(attribute, value); case Gio.FileAttributeType.INT32: return this.set_attribute_int32(attribute, value); case Gio.FileAttributeType.UINT64: return this.set_attribute_uint64(attribute, value); case Gio.FileAttributeType.INT64: return this.set_attribute_int64(attribute, value); case Gio.FileAttributeType.OBJECT: return this.set_attribute_object(attribute, value); case Gio.FileAttributeType.STRINGV: return this.set_attribute_stringv(attribute, value); } }; Gio.InputStream.prototype.createSyncIterator = function* createSyncIterator(count) { while (true) { const bytes = this.read_bytes(count, null); if (bytes.get_size() === 0) return; yield bytes; } }; Gio.InputStream.prototype.createAsyncIterator = async function* createAsyncIterator( count, ioPriority = GLib.PRIORITY_DEFAULT) { const self = this; function next() { return new Promise((resolve, reject) => { self.read_bytes_async(count, ioPriority, null, (_self, res) => { try { const bytes = self.read_bytes_finish(res); resolve(bytes); } catch (err) { reject(err); } }); }); } while (true) { // eslint-disable-next-line no-await-in-loop const bytes = await next(count); if (bytes.get_size() === 0) return; yield bytes; } }; Gio.FileEnumerator.prototype[Symbol.iterator] = function* FileEnumeratorIterator() { while (true) { try { const info = this.next_file(null); if (info === null) break; yield info; } catch (err) { this.close(null); throw err; } } this.close(null); }; Gio.FileEnumerator.prototype[Symbol.asyncIterator] = async function* AsyncFileEnumeratorIterator() { const self = this; function next() { return new Promise((resolve, reject) => { self.next_files_async(1, GLib.PRIORITY_DEFAULT, null, (_self, res) => { try { const files = self.next_files_finish(res); resolve(files.length === 0 ? null : files[0]); } catch (err) { reject(err); } }); }); } function close() { return new Promise((resolve, reject) => { self.close_async(GLib.PRIORITY_DEFAULT, null, (_self, res) => { try { resolve(self.close_finish(res)); } catch (err) { reject(err); } }); }); } while (true) { try { // eslint-disable-next-line no-await-in-loop const info = await next(); if (info === null) break; yield info; } catch (err) { // eslint-disable-next-line no-await-in-loop await close(); throw err; } } return close(); }; // Override Gio.Settings and Gio.SettingsSchema - the C API asserts if // trying to access a nonexistent schema or key, which is not handy for // shell-extension writers Gio.SettingsSchema.prototype._realGetKey = Gio.SettingsSchema.prototype.get_key; Gio.SettingsSchema.prototype.get_key = function (key) { if (!this.has_key(key)) throw new Error(`GSettings key ${key} not found in schema ${this.get_id()}`); return this._realGetKey(key); }; Gio.Settings.prototype._realMethods = Object.assign({}, Gio.Settings.prototype); function createCheckedMethod(method, checkMethod = '_checkKey') { return function (id, ...args) { this[checkMethod](id); return this._realMethods[method].call(this, id, ...args); }; } Object.assign(Gio.Settings.prototype, { _realInit: Gio.Settings.prototype._init, // add manually, not enumerable _init(props = {}) { // 'schema' is a deprecated alias for schema_id const schemaIdProp = ['schema', 'schema-id', 'schema_id', 'schemaId'].find(prop => prop in props); const settingsSchemaProp = ['settings-schema', 'settings_schema', 'settingsSchema'].find(prop => prop in props); if (!schemaIdProp && !settingsSchemaProp) { throw new Error('One of property \'schema-id\' or ' + '\'settings-schema\' are required for Gio.Settings'); } if (settingsSchemaProp && !(props[settingsSchemaProp] instanceof Gio.SettingsSchema)) throw new Error(`Value of property '${settingsSchemaProp}' is not of type Gio.SettingsSchema`); const source = Gio.SettingsSchemaSource.get_default(); const settingsSchema = settingsSchemaProp ? props[settingsSchemaProp] : source.lookup(props[schemaIdProp], true); if (!settingsSchema) throw new Error(`GSettings schema ${props[schemaIdProp]} not found`); const settingsSchemaPath = settingsSchema.get_path(); if (props['path'] === undefined && !settingsSchemaPath) { throw new Error('Attempting to create schema ' + `'${settingsSchema.get_id()}' without a path`); } if (props['path'] !== undefined && settingsSchemaPath && props['path'] !== settingsSchemaPath) { throw new Error(`GSettings created for path '${props['path']}'` + `, but schema specifies '${settingsSchemaPath}'`); } return this._realInit(props); }, _checkKey(key) { // Avoid using has_key(); checking a JS array is faster than calling // through G-I. if (!this._keys) this._keys = this.settings_schema.list_keys(); if (!this._keys.includes(key)) throw new Error(`GSettings key ${key} not found in schema ${this.schema_id}`); }, _checkChild(name) { if (!this._children) this._children = this.list_children(); if (!this._children.includes(name)) throw new Error(`Child ${name} not found in GSettings schema ${this.schema_id}`); }, get_boolean: createCheckedMethod('get_boolean'), set_boolean: createCheckedMethod('set_boolean'), get_double: createCheckedMethod('get_double'), set_double: createCheckedMethod('set_double'), get_enum: createCheckedMethod('get_enum'), set_enum: createCheckedMethod('set_enum'), get_flags: createCheckedMethod('get_flags'), set_flags: createCheckedMethod('set_flags'), get_int: createCheckedMethod('get_int'), set_int: createCheckedMethod('set_int'), get_int64: createCheckedMethod('get_int64'), set_int64: createCheckedMethod('set_int64'), get_string: createCheckedMethod('get_string'), set_string: createCheckedMethod('set_string'), get_strv: createCheckedMethod('get_strv'), set_strv: createCheckedMethod('set_strv'), get_uint: createCheckedMethod('get_uint'), set_uint: createCheckedMethod('set_uint'), get_uint64: createCheckedMethod('get_uint64'), set_uint64: createCheckedMethod('set_uint64'), get_value: createCheckedMethod('get_value'), set_value: createCheckedMethod('set_value'), bind: createCheckedMethod('bind'), bind_writable: createCheckedMethod('bind_writable'), create_action: createCheckedMethod('create_action'), get_default_value: createCheckedMethod('get_default_value'), get_user_value: createCheckedMethod('get_user_value'), is_writable: createCheckedMethod('is_writable'), reset: createCheckedMethod('reset'), get_child: createCheckedMethod('get_child', '_checkChild'), }); // ActionMap // add_action_entries is not introspectable // https://gitlab.gnome.org/GNOME/gjs/-/issues/407 Gio.ActionMap.prototype.add_action_entries = function add_action_entries(entries) { for (const {name, activate, parameter_type, state, change_state} of entries) { if (typeof parameter_type === 'string' && !GLib.variant_type_string_is_valid(parameter_type)) throw new Error(`parameter_type "${parameter_type}" is not a valid VariantType`); const action = new Gio.SimpleAction({ name, parameter_type: typeof parameter_type === 'string' ? new GLib.VariantType(parameter_type) : null, state: typeof state === 'string' ? GLib.Variant.parse(null, state, null, null) : null, }); if (typeof activate === 'function') action.connect('activate', activate.bind(action)); if (typeof change_state === 'function') action.connect('change-state', change_state.bind(action)); this.add_action(action); } }; } cjs-128.1/modules/core/overrides/Gtk.js0000664000175000017500000001200115116312211016700 0ustar fabiofabio// application/javascript;version=1.8 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna const Legacy = imports._legacy; const {Gio, CjsPrivate, GLib, GObject} = imports.gi; const {_createBuilderConnectFunc, _createClosure, _registerType} = imports._common; const Gi = imports._gi; let Gtk; let BuilderScope; function _init() { Gtk = this; Gtk.children = GObject.__gtkChildren__; Gtk.cssName = GObject.__gtkCssName__; Gtk.internalChildren = GObject.__gtkInternalChildren__; Gtk.template = GObject.__gtkTemplate__; let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk); Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass; if (Gtk.Container && Gtk.Container.prototype.child_set_property) { Gtk.Container.prototype.child_set_property = function (child, property, value) { CjsPrivate.gtk_container_child_set_property(this, child, property, value); }; } if (Gtk.CustomSorter) { Gtk.CustomSorter.new = CjsPrivate.gtk_custom_sorter_new; Gtk.CustomSorter.prototype.set_sort_func = function (sortFunc) { CjsPrivate.gtk_custom_sorter_set_sort_func(this, sortFunc); }; } Gtk.Widget.prototype._init = function (params) { const klass = this.constructor; const wrapper = GObject.Object.prototype._init.call(this, params) ?? this; if (klass[Gtk.template]) { let children = klass[Gtk.children] ?? []; for (let child of children) { wrapper[child.replace(/-/g, '_')] = wrapper.get_template_child(klass, child); } let internalChildren = klass[Gtk.internalChildren] ?? []; for (let child of internalChildren) { wrapper[`_${child.replace(/-/g, '_')}`] = wrapper.get_template_child(klass, child); } } return wrapper; }; Gtk.Widget._classInit = function (klass) { return GObject.Object._classInit(klass); }; Object.defineProperty(Gtk.Widget, _registerType, { value: _registerWidgetType, writable: false, configurable: false, enumerable: false, }); if (Gtk.Widget.prototype.get_first_child) { Gtk.Widget.prototype[Symbol.iterator] = function* () { for (let c = this.get_first_child(); c; c = c.get_next_sibling()) yield c; }; } if (Gtk.BuilderScope) { BuilderScope = GObject.registerClass({ Implements: [Gtk.BuilderScope], }, class extends GObject.Object { vfunc_create_closure(builder, handlerName, flags, connectObject) { const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED; const thisArg = builder.get_current_object(); return Gi.associateClosure( connectObject ?? thisArg, _createClosure(builder, thisArg, handlerName, swapped, connectObject) ); } }); } } function _registerWidgetType(klass) { const template = klass[Gtk.template]; const cssName = klass[Gtk.cssName]; const children = klass[Gtk.children]; const internalChildren = klass[Gtk.internalChildren]; if (template) { klass.prototype._instance_init = function () { this.init_template(); }; } GObject.Object[_registerType](klass); if (cssName) Gtk.Widget.set_css_name.call(klass, cssName); if (template) { if (typeof template === 'string') { try { const uri = GLib.Uri.parse(template, GLib.UriFlags.NONE); const scheme = uri.get_scheme(); if (scheme === 'resource') { Gtk.Widget.set_template_from_resource.call(klass, uri.get_path()); } else if (scheme === 'file') { const file = Gio.File.new_for_uri(template); const [, contents] = file.load_contents(null); Gtk.Widget.set_template.call(klass, contents); } else { throw new TypeError(`Invalid template URI: ${template}`); } } catch (err) { if (!(err instanceof GLib.UriError)) throw err; const contents = new TextEncoder().encode(template); Gtk.Widget.set_template.call(klass, contents); } } else { Gtk.Widget.set_template.call(klass, template); } if (BuilderScope) Gtk.Widget.set_template_scope.call(klass, new BuilderScope()); else Gtk.Widget.set_connect_func.call(klass, _createBuilderConnectFunc(klass)); } if (children) { children.forEach(child => Gtk.Widget.bind_template_child_full.call(klass, child, false, 0)); } if (internalChildren) { internalChildren.forEach(child => Gtk.Widget.bind_template_child_full.call(klass, child, true, 0)); } } cjs-128.1/modules/core/overrides/cairo.js0000664000175000017500000000051115116312211017253 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento // This override adds the builtin Cairo bindings to imports.gi.cairo. // (It's confusing to have two incompatible ways to import Cairo.) function _init() { Object.assign(this, imports.cairo); } cjs-128.1/modules/esm/0000775000175000017500000000000015116312211013455 5ustar fabiofabiocjs-128.1/modules/esm/.eslintrc.yml0000664000175000017500000000032315116312211016077 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh extends: '../../.eslintrc.yml' parserOptions: sourceType: 'module' ecmaVersion: 2022 cjs-128.1/modules/esm/_bootstrap/0000775000175000017500000000000015116312211015631 5ustar fabiofabiocjs-128.1/modules/esm/_bootstrap/default.js0000664000175000017500000000050015116312211017606 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh // Bootstrap file which supports ESM imports. // Bootstrap the Encoding API import '_encoding/encoding'; // Bootstrap the Console API import 'console'; // Bootstrap the Timers API import '_timers'; cjs-128.1/modules/esm/_encoding/0000775000175000017500000000000015116312211015402 5ustar fabiofabiocjs-128.1/modules/esm/_encoding/encoding.js0000664000175000017500000001137515116312211017535 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh import {getEncodingFromLabel} from './encodingMap.js'; class TextDecoder { /** * @type {string} */ encoding; /** * @type {boolean} */ ignoreBOM; /** * @type {boolean} */ fatal; /** * @private * @type {string} */ _internalEncoding; get [Symbol.toStringTag]() { return 'TextDecoder'; } /** * @param {string} encoding The encoding to decode into * @param {object} [options] Decoding options * @param {boolean=} options.fatal Whether to throw or substitute when invalid characters are encountered * @param {boolean=} options.ignoreBOM Whether to ignore the byte order for UTF-8 arrays */ constructor(encoding = 'utf-8', options = {}) { const {fatal = false, ignoreBOM = false} = options; const encodingDefinition = getEncodingFromLabel(`${encoding}`); if (!encodingDefinition) throw new RangeError(`Invalid encoding label: '${encoding}'`); if (encodingDefinition.label === 'replacement') { throw new RangeError( `Unsupported replacement encoding: '${encoding}'` ); } Object.defineProperty(this, '_internalEncoding', { value: encodingDefinition.internalLabel, enumerable: false, writable: false, configurable: false, }); Object.defineProperty(this, 'encoding', { value: encodingDefinition.label, enumerable: true, writable: false, configurable: false, }); Object.defineProperty(this, 'ignoreBOM', { value: Boolean(ignoreBOM), enumerable: true, writable: false, configurable: false, }); Object.defineProperty(this, 'fatal', { value: Boolean(fatal), enumerable: true, writable: false, configurable: false, }); } /** * @param {unknown} bytes a typed array of bytes to decode * @param {object} [options] Decoding options * @param {boolean=} options.stream Unsupported option. Whether to stream the decoded bytes. * @returns */ decode(bytes, options = {}) { const {stream = false} = options; if (stream) { throw new Error( 'TextDecoder does not implement the \'stream\' option.' ); } /** @type {Uint8Array} */ let input; if (bytes instanceof ArrayBuffer) { input = new Uint8Array(bytes); } else if (bytes instanceof Uint8Array) { input = bytes; } else if (ArrayBuffer.isView(bytes)) { let {buffer, byteLength, byteOffset} = bytes; input = new Uint8Array(buffer, byteOffset, byteLength); } else if (bytes === undefined) { input = new Uint8Array(0); } else if (bytes instanceof import.meta.importSync('gi').GLib.Bytes) { input = bytes.toArray(); } else { throw new Error( 'Provided input cannot be converted to ArrayBufferView or ArrayBuffer' ); } if ( this.ignoreBOM && input.length > 2 && input[0] === 0xef && input[1] === 0xbb && input[2] === 0xbf ) { if (this.encoding !== 'utf-8') throw new Error('Cannot ignore BOM for non-UTF8 encoding.'); let {buffer, byteLength, byteOffset} = input; input = new Uint8Array(buffer, byteOffset + 3, byteLength - 3); } const Encoding = import.meta.importSync('_encodingNative'); return Encoding.decode(input, this._internalEncoding, this.fatal); } } class TextEncoder { get [Symbol.toStringTag]() { return 'TextEncoder'; } get encoding() { return 'utf-8'; } encode(input = '') { const Encoding = import.meta.importSync('_encodingNative'); // The TextEncoder specification only allows for UTF-8 encoding. return Encoding.encode(`${input}`, 'utf-8'); } encodeInto(input = '', output = new Uint8Array()) { const Encoding = import.meta.importSync('_encodingNative'); // The TextEncoder specification only allows for UTF-8 encoding. return Encoding.encodeInto(`${input}`, output); } } Object.defineProperty(globalThis, 'TextEncoder', { configurable: false, enumerable: true, writable: false, value: TextEncoder, }); Object.defineProperty(globalThis, 'TextDecoder', { configurable: false, enumerable: true, writable: false, value: TextDecoder, }); cjs-128.1/modules/esm/_encoding/encodingMap.js0000664000175000017500000001676415116312211020202 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh import {trimAsciiWhitespace} from './util.js'; // Data derived from https://encoding.spec.whatwg.org/encodings.json const encodingMap = { 'utf-8': [ 'unicode-1-1-utf-8', 'unicode11utf8', 'unicode20utf8', 'utf-8', 'utf8', 'x-unicode20utf8', ], ibm866: ['866', 'cp866', 'csibm866', 'ibm866'], 'iso-8859-2': [ 'csisolatin2', 'iso-8859-2', 'iso-ir-101', 'iso8859-2', 'iso88592', 'iso_8859-2', 'iso_8859-2:1987', 'l2', 'latin2', ], 'iso-8859-3': [ 'csisolatin3', 'iso-8859-3', 'iso-ir-109', 'iso8859-3', 'iso88593', 'iso_8859-3', 'iso_8859-3:1988', 'l3', 'latin3', ], 'iso-8859-4': [ 'csisolatin4', 'iso-8859-4', 'iso-ir-110', 'iso8859-4', 'iso88594', 'iso_8859-4', 'iso_8859-4:1988', 'l4', 'latin4', ], 'iso-8859-5': [ 'csisolatincyrillic', 'cyrillic', 'iso-8859-5', 'iso-ir-144', 'iso8859-5', 'iso88595', 'iso_8859-5', 'iso_8859-5:1988', ], 'iso-8859-6': [ 'arabic', 'asmo-708', 'csiso88596e', 'csiso88596i', 'csisolatinarabic', 'ecma-114', 'iso-8859-6', 'iso-8859-6-e', 'iso-8859-6-i', 'iso-ir-127', 'iso8859-6', 'iso88596', 'iso_8859-6', 'iso_8859-6:1987', ], 'iso-8859-7': [ 'csisolatingreek', 'ecma-118', 'elot_928', 'greek', 'greek8', 'iso-8859-7', 'iso-ir-126', 'iso8859-7', 'iso88597', 'iso_8859-7', 'iso_8859-7:1987', 'sun_eu_greek', ], 'iso-8859-8': [ 'csiso88598e', 'csisolatinhebrew', 'hebrew', 'iso-8859-8', 'iso-8859-8-e', 'iso-ir-138', 'iso8859-8', 'iso88598', 'iso_8859-8', 'iso_8859-8:1988', 'visual', ], 'iso-8859-8-i': ['csiso88598i', 'iso-8859-8-i', 'logical'], 'iso-8859-10': [ 'csisolatin6', 'iso-8859-10', 'iso-ir-157', 'iso8859-10', 'iso885910', 'l6', 'latin6', ], 'iso-8859-13': ['iso-8859-13', 'iso8859-13', 'iso885913'], 'iso-8859-14': ['iso-8859-14', 'iso8859-14', 'iso885914'], 'iso-8859-15': [ 'csisolatin9', 'iso-8859-15', 'iso8859-15', 'iso885915', 'iso_8859-15', 'l9', ], 'iso-8859-16': ['iso-8859-16'], 'koi8-r': ['cskoi8r', 'koi', 'koi8', 'koi8-r', 'koi8_r'], 'koi8-u': ['koi8-ru', 'koi8-u'], macintosh: ['csmacintosh', 'mac', 'macintosh', 'x-mac-roman'], 'windows-874': [ 'dos-874', 'iso-8859-11', 'iso8859-11', 'iso885911', 'tis-620', 'windows-874', ], 'windows-1250': ['cp1250', 'windows-1250', 'x-cp1250'], 'windows-1251': ['cp1251', 'windows-1251', 'x-cp1251'], 'windows-1252': [ 'ansi_x3.4-1968', 'ascii', 'cp1252', 'cp819', 'csisolatin1', 'ibm819', 'iso-8859-1', 'iso-ir-100', 'iso8859-1', 'iso88591', 'iso_8859-1', 'iso_8859-1:1987', 'l1', 'latin1', 'us-ascii', 'windows-1252', 'x-cp1252', ], 'windows-1253': ['cp1253', 'windows-1253', 'x-cp1253'], 'windows-1254': [ 'cp1254', 'csisolatin5', 'iso-8859-9', 'iso-ir-148', 'iso8859-9', 'iso88599', 'iso_8859-9', 'iso_8859-9:1989', 'l5', 'latin5', 'windows-1254', 'x-cp1254', ], 'windows-1255': ['cp1255', 'windows-1255', 'x-cp1255'], 'windows-1256': ['cp1256', 'windows-1256', 'x-cp1256'], 'windows-1257': ['cp1257', 'windows-1257', 'x-cp1257'], 'windows-1258': ['cp1258', 'windows-1258', 'x-cp1258'], 'x-mac-cyrillic': ['x-mac-cyrillic', 'x-mac-ukrainian'], gbk: [ 'chinese', 'csgb2312', 'csiso58gb231280', 'gb2312', 'gb_2312', 'gb_2312-80', 'gbk', 'iso-ir-58', 'x-gbk', ], gb18030: ['gb18030'], big5: [ 'big5', // Unlike the standard WHATWG encoder // the Hong Kong Supplementary Character Set // is not bundled in big5 by iconv // "big5-hkscs", 'cn-big5', 'csbig5', 'x-x-big5', ], 'euc-jp': ['cseucpkdfmtjapanese', 'euc-jp', 'x-euc-jp'], 'iso-2022-jp': ['csiso2022jp', 'iso-2022-jp'], shift_jis: [ 'csshiftjis', 'ms932', 'ms_kanji', 'shift-jis', 'shift_jis', 'sjis', 'windows-31j', 'x-sjis', ], 'euc-kr': [ 'cseuckr', 'csksc56011987', 'euc-kr', 'iso-ir-149', 'korean', 'ks_c_5601-1987', 'ks_c_5601-1989', 'ksc5601', 'ksc_5601', 'windows-949', ], 'utf-16be': ['unicodefffe', 'utf-16be'], 'utf-16le': [ 'csunicode', 'iso-10646-ucs-2', 'ucs-2', 'unicode', 'unicodefeff', 'utf-16', 'utf-16le', ], }; /** * Construct a map from each potential label to the canonical label * for an encoding. */ const encodings = new Map( Object.entries(encodingMap).flatMap(([encoding, labels]) => { return labels.map(label => [label, encoding]); }) ); // Maps WHATWG specified labels to the appropriate iconv // encoding label if iconv does not support the WHATWG label. // // Mapping here preserves the WHATWG as the label on the // TextDecoder so this change is transparent to API users. const internalEncodings = new Map([ // iso-8859-8-i is functionally equivalent to iso-8859-8 // as we are not encoding or decoding control characters. ['iso-8859-8-i', 'iso-8859-8'], // iconv follows a different naming convention for this // encoding ['x-mac-cyrillic', 'MacCyrillic'], // Support HKSCS as a standalone encoding, iconv doesn't // bundle it with Big5 like WHATWG does... ['big5-hkscs', 'big5-hkscs'], ]); /** * @typedef Encoding * @property {string} internalLabel * @property {string} label */ /** * @param {string} label the encoding label * @returns {Encoding | null} */ export function getEncodingFromLabel(label) { const formattedLabel = trimAsciiWhitespace(label.toLowerCase()); let canonicalLabel = encodings.get(formattedLabel); // Lookup an internal mapping using the canonical name, if found, or // the formatted label otherwise. // // x-mac-ukrainian > x-mac-cyrillic > MacCyrillic // (canonical label) (internal label) // // big5-hkscs > undefined > big5-hkscs // (canonical label) (internal label) // let internalLabel = internalEncodings.get( canonicalLabel ?? formattedLabel ); // If both the canonical label and the internal encoding // are not found, this encoding is unsupported. if (!canonicalLabel && !internalLabel) return null; if (internalLabel) { return { label: canonicalLabel ?? formattedLabel, internalLabel, }; } return { label: canonicalLabel, internalLabel: canonicalLabel, }; } cjs-128.1/modules/esm/_encoding/util.js0000664000175000017500000000174215116312211016721 0ustar fabiofabio// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: Node.js contributors. All rights reserved. // Modified from https://github.com/nodejs/node/blob/78680c1cbc8b0c435963bc512e826b2a6227c315/lib/internal/encoding.js /** * Trims ASCII whitespace from a string. * `String.prototype.trim` removes non-ASCII whitespace. * * @param {string} label the label to trim * @returns {string} */ export const trimAsciiWhitespace = label => { let s = 0; let e = label.length; while ( s < e && (label[s] === '\u0009' || label[s] === '\u000a' || label[s] === '\u000c' || label[s] === '\u000d' || label[s] === '\u0020') ) s++; while ( e > s && (label[e - 1] === '\u0009' || label[e - 1] === '\u000a' || label[e - 1] === '\u000c' || label[e - 1] === '\u000d' || label[e - 1] === '\u0020') ) e--; return label.slice(s, e); }; cjs-128.1/modules/esm/_timers.js0000664000175000017500000001015015116312211015452 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh /* exported setTimeout, setInterval, clearTimeout, clearInterval */ /* eslint no-implicit-coercion: ["error", {"allow": ["+"]}] */ // Note: implicit coercion with + is used to perform the ToNumber algorithm from // the timers specification /** * @param {number} delay a number value (in milliseconds) */ function validateDelay(delay) { // |0 always returns a signed 32-bit integer. return Math.max(0, +delay | 0); } /** @type {Map} */ const timeouts = new Map(); /** * @param {GLib.Source} source the source to add to our map */ function addSource(source) { const id = source.attach(null); timeouts.set(source, id); } /** * @param {GLib.Source} source the source object to remove from our map */ function releaseSource(source) { timeouts.delete(source); } /** * @param {unknown} thisArg 'this' argument * @returns {asserts thisArg is (null | undefined | typeof globalThis)} */ function checkThis(thisArg) { if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) throw new TypeError('Illegal invocation'); } /** * @param {number} timeout a timeout in milliseconds * @param {(...args) => any} handler a callback * @returns {GLib.Source} */ function createTimeoutSource(timeout, handler) { const source = imports.gi.GLib.timeout_source_new(timeout); source.set_priority(imports.gi.GLib.PRIORITY_DEFAULT); imports.gi.GObject.source_set_closure(source, handler); return source; } /** * @this {typeof globalThis} * @param {(...args) => any} callback a callback function * @param {number} delay the duration in milliseconds to wait before running callback * @param {...any} args arguments to pass to callback */ function setTimeout(callback, delay = 0, ...args) { checkThis(this); delay = validateDelay(delay); const boundCallback = callback.bind(globalThis, ...args); const source = createTimeoutSource(delay, () => { if (!timeouts.has(source)) return imports.gi.GLib.SOURCE_REMOVE; boundCallback(); releaseSource(source); import.meta.importSync('_promiseNative').drainMicrotaskQueue(); return imports.gi.GLib.SOURCE_REMOVE; }); addSource(source); return source; } /** * @this {typeof globalThis} * @param {(...args) => any} callback a callback function * @param {number} delay the duration in milliseconds to wait between calling callback * @param {...any} args arguments to pass to callback */ function setInterval(callback, delay = 0, ...args) { checkThis(this); delay = validateDelay(delay); const boundCallback = callback.bind(globalThis, ...args); const source = createTimeoutSource(delay, () => { if (!timeouts.has(source)) return imports.gi.GLib.SOURCE_REMOVE; boundCallback(); import.meta.importSync('_promiseNative').drainMicrotaskQueue(); return imports.gi.GLib.SOURCE_CONTINUE; }); addSource(source); return source; } /** * @param {GLib.Source} source the timeout to clear */ function _clearTimer(source) { if (!timeouts.has(source)) return; if (source) { source.destroy(); releaseSource(source); } } /** * @param {GLib.Source} timeout the timeout to clear */ function clearTimeout(timeout = null) { _clearTimer(timeout); } /** * @param {Glib.Source} timeout the timeout to clear */ function clearInterval(timeout = null) { _clearTimer(timeout); } Object.defineProperty(globalThis, 'setTimeout', { configurable: false, enumerable: true, writable: true, value: setTimeout, }); Object.defineProperty(globalThis, 'setInterval', { configurable: false, enumerable: true, writable: true, value: setInterval, }); Object.defineProperty(globalThis, 'clearTimeout', { configurable: false, enumerable: true, writable: true, value: clearTimeout, }); Object.defineProperty(globalThis, 'clearInterval', { configurable: false, enumerable: true, writable: true, value: clearInterval, }); cjs-128.1/modules/esm/cairo.js0000664000175000017500000000036615116312211015115 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh const cairo = import.meta.importSync('cairoNative'); export default Object.assign( {}, imports._cairo, cairo ); cjs-128.1/modules/esm/console.js0000664000175000017500000004544215116312211015466 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh const DEFAULT_LOG_DOMAIN = 'Cjs-Console'; // A line-by-line implementation of https://console.spec.whatwg.org/. // 2.2.1. Formatting specifiers // https://console.spec.whatwg.org/#formatting-specifiers // // %s - string // %d or %i - integer // %f - float // %o - "optimal" object formatting // %O - "generic" object formatting // %c - "CSS" formatting (unimplemented by GJS) /** * A simple regex to capture formatting specifiers */ const specifierTest = /%(d|i|s|f|o|O|c)/; /** * @param {string} str a string to check for format specifiers like %s or %i * @returns {boolean} */ function hasFormatSpecifiers(str) { return specifierTest.test(str); } /** * @param {any} item an item to format */ function formatGenerically(item) { return JSON.stringify(item, null, 4); } /** * @param {any} item an item to format * @returns {string} */ function formatOptimally(item) { const GLib = imports.gi.GLib; // Handle optimal error formatting. if (item instanceof Error || item instanceof GLib.Error) { return `${item.toString()}${item.stack ? '\n' : ''}${item.stack ?.split('\n') // Pad each stacktrace line. .map(line => line.padStart(2, ' ')) .join('\n')}`; } // TODO: Enhance 'optimal' formatting. // There is a current work on a better object formatter for GJS in // https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/587 if (typeof item === 'object' && item !== null) { if (item.constructor?.name !== 'Object') return `${item.constructor?.name} ${JSON.stringify(item, null, 4)}`; else if (item[Symbol.toStringTag] === 'GIRepositoryNamespace') return `[${item[Symbol.toStringTag]} ${item.__name__}]`; } return JSON.stringify(item, null, 4); } /** * Implementation of the WHATWG Console object. */ class Console { #groupIndentation = ''; #countLabels = {}; #timeLabels = {}; #logDomain = DEFAULT_LOG_DOMAIN; get [Symbol.toStringTag]() { return 'Console'; } // 1.1 Logging functions // https://console.spec.whatwg.org/#logging /** * Logs a critical message if the condition is not truthy. * {@see console.error()} for additional information. * * @param {boolean} condition a boolean condition which, if false, causes * the log to print * @param {...any} data formatting substitutions, if applicable * @returns {void} */ assert(condition, ...data) { if (condition) return; const message = 'Assertion failed'; if (data.length === 0) data.push(message); if (typeof data[0] !== 'string') { data.unshift(message); } else { const first = data.shift(); data.unshift(`${message}: ${first}`); } this.#logger('assert', data); } /** * Resets grouping and clears the terminal on systems supporting ANSI * terminal control sequences. * * In file-based stdout or systems which do not support clearing, * console.clear() has no visual effect. * * @returns {void} */ clear() { this.#groupIndentation = ''; imports.gi.CjsPrivate.clear_terminal(); } /** * Logs a message with severity equal to {@see GLib.LogLevelFlags.DEBUG}. * * @param {...any} data formatting substitutions, if applicable */ debug(...data) { this.#logger('debug', data); } /** * Logs a message with severity equal to {@see GLib.LogLevelFlags.CRITICAL}. * Does not use {@see GLib.LogLevelFlags.ERROR} to avoid asserting and * forcibly shutting down the application. * * @param {...any} data formatting substitutions, if applicable */ error(...data) { this.#logger('error', data); } /** * Logs a message with severity equal to {@see GLib.LogLevelFlags.INFO}. * * @param {...any} data formatting substitutions, if applicable */ info(...data) { this.#logger('info', data); } /** * Logs a message with severity equal to {@see GLib.LogLevelFlags.MESSAGE}. * * @param {...any} data formatting substitutions, if applicable */ log(...data) { this.#logger('log', data); } // 1.1.7 table(tabularData, properties) table(tabularData, _properties) { this.log(tabularData); } /** * @param {...any} data formatting substitutions, if applicable * @returns {void} */ trace(...data) { if (data.length === 0) data = ['Trace']; this.#logger('trace', data); } /** * Logs a message with severity equal to {@see GLib.LogLevelFlags.WARNING}. * * @param {...any} data formatting substitutions, if applicable * @returns {void} */ warn(...data) { this.#logger('warn', data); } /** * @param {object} item an item to format generically * @param {never} [options] any additional options for the formatter. Unused * in our implementation. */ dir(item, options) { const object = formatGenerically(item); this.#printer('dir', [object], options); } /** * @param {...any} data formatting substitutions, if applicable * @returns {void} */ dirxml(...data) { this.log(...data); } // 1.2 Counting functions // https://console.spec.whatwg.org/#counting /** * Logs how many times console.count(label) has been called with a given * label. * {@see console.countReset()} for resetting a count. * * @param {string} label unique identifier for this action * @returns {void} */ count(label) { this.#countLabels[label] ??= 0; const count = ++this.#countLabels[label]; const concat = `${label}: ${count}`; this.#logger('count', [concat]); } /** * @param {string} label the unique label to reset the count for * @returns {void} */ countReset(label) { const count = this.#countLabels[label]; if (typeof count !== 'number') this.#printer('reportWarning', [`No count found for label: '${label}'.`]); else this.#countLabels[label] = 0; } // 1.3 Grouping functions // https://console.spec.whatwg.org/#grouping /** * @param {...any} data formatting substitutions, if applicable * @returns {void} */ group(...data) { this.#logger('group', data); this.#groupIndentation += ' '; } /** * Alias for console.group() * * @param {...any} data formatting substitutions, if applicable * @returns {void} */ groupCollapsed(...data) { // We can't 'collapse' output in a terminal, so we alias to // group() this.group(...data); } /** * @returns {void} */ groupEnd() { this.#groupIndentation = this.#groupIndentation.slice(0, -2); } // 1.4 Timing functions // https://console.spec.whatwg.org/#timing /** * @param {string} label unique identifier for this action, pass to * console.timeEnd() to complete * @returns {void} */ time(label) { this.#timeLabels[label] = imports.gi.GLib.get_monotonic_time(); } /** * Logs the time since the last call to console.time(label) where label is * the same. * * @param {string} label unique identifier for this action, pass to * console.timeEnd() to complete * @param {...any} data string substitutions, if applicable * @returns {void} */ timeLog(label, ...data) { const startTime = this.#timeLabels[label]; if (typeof startTime !== 'number') { this.#printer('reportWarning', [ `No time log found for label: '${label}'.`, ]); } else { const durationMs = (imports.gi.GLib.get_monotonic_time() - startTime) / 1000; const concat = `${label}: ${durationMs.toFixed(3)} ms`; data.unshift(concat); this.#printer('timeLog', data); } } /** * Logs the time since the last call to console.time(label) and completes * the action. * Call console.time(label) again to re-measure. * * @param {string} label unique identifier for this action * @returns {void} */ timeEnd(label) { const startTime = this.#timeLabels[label]; if (typeof startTime !== 'number') { this.#printer('reportWarning', [ `No time log found for label: '${label}'.`, ]); } else { delete this.#timeLabels[label]; const durationMs = (imports.gi.GLib.get_monotonic_time() - startTime) / 1000; const concat = `${label}: ${durationMs.toFixed(3)} ms`; this.#printer('timeEnd', [concat]); } } // Non-standard functions which are de-facto standards. // Similar to Node, we define these as no-ops for now. /** * @deprecated Not implemented in GJS * * @param {string} _label unique identifier for this action, pass to * console.profileEnd to complete * @returns {void} */ profile(_label) {} /** * @deprecated Not implemented in GJS * * @param {string} _label unique identifier for this action * @returns {void} */ profileEnd(_label) {} /** * @deprecated Not implemented in GJS * * @param {string} _label unique identifier for this action * @returns {void} */ timeStamp(_label) {} // GJS-specific extensions for integrating with GLib structured logging /** * @param {string} logDomain the GLib log domain this Console should print * with. Defaults to 'Gjs-Console'. * @returns {void} */ setLogDomain(logDomain) { this.#logDomain = String(logDomain); } /** * @returns {string} */ get logDomain() { return this.#logDomain; } // 2. Supporting abstract operations // https://console.spec.whatwg.org/#supporting-ops /** * 2.1. Logger * https://console.spec.whatwg.org/#logger * * Conditionally applies formatting based on the inputted arguments, * and prints at the provided severity (logLevel) * * @param {string} logLevel the severity (log level) the args should be * emitted with * @param {unknown[]} args the arguments to pass to the printer * @returns {void} */ #logger(logLevel, args) { if (args.length === 0) return; const [first, ...rest] = args; if (rest.length === 0) { this.#printer(logLevel, [first]); return undefined; } // If first does not contain any format specifiers, don't call Formatter if (typeof first !== 'string' || !hasFormatSpecifiers(first)) { this.#printer(logLevel, args); return undefined; } // Otherwise, perform print the result of Formatter. this.#printer(logLevel, this.#formatter([first, ...rest])); return undefined; } /** * 2.2. Formatter * https://console.spec.whatwg.org/#formatter * * @param {[string, ...any[]]} args an array of format strings followed by * their arguments */ #formatter(args) { // The initial formatting string is the first arg let target = args[0]; if (args.length === 1) return target; const current = args[1]; // Find the index of the first format specifier. const specifierIndex = specifierTest.exec(target).index; const specifier = target.slice(specifierIndex, specifierIndex + 2); let converted = null; switch (specifier) { case '%s': converted = String(current); break; case '%d': case '%i': if (typeof current === 'symbol') converted = Number.NaN; else converted = parseInt(current, 10); break; case '%f': if (typeof current === 'symbol') converted = Number.NaN; else converted = parseFloat(current); break; case '%o': converted = formatOptimally(current); break; case '%O': converted = formatGenerically(current); break; case '%c': converted = ''; break; } // If any of the previous steps set converted, replace the specifier in // target with the converted value. if (converted !== null) { target = target.slice(0, specifierIndex) + converted + target.slice(specifierIndex + 2); } /** * Create the next format input... * * @type {[string, ...any[]]} */ const result = [target, ...args.slice(2)]; if (!hasFormatSpecifiers(target)) return result; if (result.length === 1) return result; return this.#formatter(result); } /** * @typedef {object} PrinterOptions * @param {Array.} [stackTrace] an error stacktrace to append * @param {Record} [fields] fields to include in the structured * logging call */ /** * 2.3. Printer * https://console.spec.whatwg.org/#printer * * This implementation of Printer maps WHATWG log severity to * {@see GLib.LogLevelFlags} and outputs using GLib structured logging. * * @param {string} logLevel the log level (log tag) the args should be * emitted with * @param {unknown[]} args the arguments to print, either a format string * with replacement args or multiple strings * @param {PrinterOptions} [options] additional options for the * printer * @returns {void} */ #printer(logLevel, args, options) { const GLib = imports.gi.GLib; let severity; switch (logLevel) { case 'log': case 'dir': case 'dirxml': case 'trace': case 'group': case 'groupCollapsed': case 'timeLog': case 'timeEnd': severity = GLib.LogLevelFlags.LEVEL_MESSAGE; break; case 'debug': severity = GLib.LogLevelFlags.LEVEL_DEBUG; break; case 'count': case 'info': severity = GLib.LogLevelFlags.LEVEL_INFO; break; case 'warn': case 'countReset': case 'reportWarning': severity = GLib.LogLevelFlags.LEVEL_WARNING; break; case 'error': case 'assert': severity = GLib.LogLevelFlags.LEVEL_CRITICAL; break; default: severity = GLib.LogLevelFlags.LEVEL_MESSAGE; } const output = args .map(a => { if (a === null) return 'null'; else if (typeof a === 'object') return formatOptimally(a); else if (typeof a === 'function') return a.toString(); else if (typeof a === 'undefined') return 'undefined'; else if (typeof a === 'bigint') return `${a}n`; else return String(a); }) .join(' '); let formattedOutput = this.#groupIndentation + output; const extraFields = {}; let stackTrace = options?.stackTrace; if (!stackTrace && (logLevel === 'trace' || severity <= GLib.LogLevelFlags.LEVEL_WARNING)) { stackTrace = new Error().stack; const currentFile = stackTrace.match(/^[^@]*@(.*):\d+:\d+$/m)?.at(1); const index = stackTrace.lastIndexOf(currentFile) + currentFile.length; stackTrace = stackTrace.substring(index).split('\n'); // Remove the remainder of the first line stackTrace.shift(); } if (logLevel === 'trace') { if (stackTrace?.length) { formattedOutput += `\n${stackTrace.map(s => `${this.#groupIndentation}${s}`).join('\n')}`; } else { formattedOutput += `\n${this.#groupIndentation}No trace available`; } } if (stackTrace?.length) { const [stackLine] = stackTrace; const match = stackLine.match(/^([^@]*)@(.*):(\d+):\d+$/); if (match) { const [_, func, file, line] = match; if (func) extraFields.CODE_FUNC = func; if (file) extraFields.CODE_FILE = file; if (line) extraFields.CODE_LINE = line; } } GLib.log_structured(this.#logDomain, severity, { MESSAGE: formattedOutput, ...extraFields, ...options?.fields ?? {}, }); } } const console = new Console(); /** * @param {string} domain set the GLib log domain for the global console object. */ function setConsoleLogDomain(domain) { console.setLogDomain(domain); } /** * @returns {string} */ function getConsoleLogDomain() { return console.logDomain; } /** * For historical web-compatibility reasons, the namespace object for * console must have {} as its [[Prototype]]. * * @type {Omit} */ const globalConsole = Object.create({}); const propertyNames = /** @type {['constructor', ...Array]} */ // eslint-disable-next-line no-extra-parens (Object.getOwnPropertyNames(Console.prototype)); const propertyDescriptors = Object.getOwnPropertyDescriptors(Console.prototype); for (const key of propertyNames) { if (key === 'constructor') continue; // This non-standard function shouldn't be included. if (key === 'setLogDomain') continue; const descriptor = propertyDescriptors[key]; if (typeof descriptor.value !== 'function') continue; Object.defineProperty(globalConsole, key, { ...descriptor, value: descriptor.value.bind(console), }); } Object.defineProperties(globalConsole, { [Symbol.toStringTag]: { configurable: false, enumerable: true, get() { return 'console'; }, }, }); Object.freeze(globalConsole); Object.defineProperty(globalThis, 'console', { configurable: false, enumerable: true, writable: false, value: globalConsole, }); export { getConsoleLogDomain, setConsoleLogDomain, DEFAULT_LOG_DOMAIN }; export default { getConsoleLogDomain, setConsoleLogDomain, DEFAULT_LOG_DOMAIN, }; cjs-128.1/modules/esm/gettext.js0000664000175000017500000000103715116312211015500 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh export let { LocaleCategory, setlocale, textdomain, bindtextdomain, gettext, dgettext, dcgettext, ngettext, dngettext, pgettext, dpgettext, domain, } = imports._gettext; export default { LocaleCategory, setlocale, textdomain, bindtextdomain, gettext, dgettext, dcgettext, ngettext, dngettext, pgettext, dpgettext, domain, }; cjs-128.1/modules/esm/gi.js0000664000175000017500000000207215116312211014413 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh const gi = import.meta.importSync('gi'); const Gi = { require(namespace, version = undefined) { if (namespace === 'versions') throw new Error('Cannot import namespace "versions", use the version parameter of Gi.require to specify versions.'); let oldVersion = gi.versions[namespace]; if (version !== undefined) gi.versions[namespace] = version; try { const module = gi[namespace]; if (version !== undefined && version !== module.__version__) { throw new Error(`Version ${module.__version__} of GI module ${ namespace} already loaded, cannot load version ${version}`); } return module; } catch (error) { // Roll back change to versions object if import failed gi.versions[namespace] = oldVersion; throw error; } }, }; Object.freeze(Gi); export default Gi; cjs-128.1/modules/esm/system.js0000664000175000017500000000120515116312211015335 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh const system = import.meta.importSync('system'); export let { addressOf, addressOfGObject, breakpoint, clearDateCaches, dumpHeap, dumpMemoryInfo, exit, gc, programArgs, programInvocationName, programPath, refcount, version, } = system; export default { addressOf, addressOfGObject, breakpoint, clearDateCaches, dumpHeap, dumpMemoryInfo, exit, gc, programArgs, programInvocationName, programPath, refcount, version, }; cjs-128.1/modules/internal/0000775000175000017500000000000015116312211014505 5ustar fabiofabiocjs-128.1/modules/internal/.eslintrc.yml0000664000175000017500000000126415116312211017134 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh extends: ../../.eslintrc.yml parserOptions: sourceType: 'module' ecmaVersion: 2022 globals: ARGV: off Debugger: readonly GIRepositoryGType: off imports: off Intl: readonly log: off logError: off print: off printerr: off moduleGlobalThis: readonly compileModule: readonly compileInternalModule: readonly loadResourceOrFile: readonly loadResourceOrFileAsync: readonly parseURI: readonly uriExists: readonly resolveRelativeResourceOrFile: readonly setGlobalModuleLoader: readonly setModulePrivate: readonly getRegistry: readonly cjs-128.1/modules/internal/loader.js0000664000175000017500000003343215116312211016316 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh /** @typedef {{ uri: string; scheme: string; host: string; path: string; query: Query }} Uri */ /** * Use '__internal: never' to prevent any object from being type compatible with Module * because it is an internal type. * * @typedef {{__internal: never;}} Module */ /** @typedef {typeof moduleGlobalThis | typeof globalThis} Global */ /** @typedef {{ load(uri: Uri): [contents: string, internal: boolean]; }} SchemeHandler */ /** @typedef {{ [key: string]: string | undefined; }} Query */ /** @typedef {(uri: string, contents: string) => Module} CompileFunc */ /** * Thrown when there is an error importing a module. */ class ImportError extends moduleGlobalThis.Error { name = 'ImportError'; } /** * ModulePrivate is the "private" object of every module. */ class ModulePrivate { /** * * @param {string} id the module's identifier * @param {string} uri the module's URI * @param {boolean} [internal] whether this module is "internal" */ constructor(id, uri, internal = false) { this.id = id; this.uri = uri; this.internal = internal; } } /** * Returns whether a string represents a relative path (e.g. ./, ../) * * @param {string} path a path to check if relative * @returns {boolean} */ function isRelativePath(path) { // Check if the path is relative. Note that this doesn't mean "relative // path" in the GLib sense, as in "not absolute" — it means a relative path // module specifier, which must start with a '.' or '..' path component. return path.startsWith('./') || path.startsWith('../'); } /** * Handles resolving and loading URIs. * * @class */ class InternalModuleLoader { /** * @param {typeof globalThis} global the global object to handle module * resolution * @param {(string, string) => import("../types").Module} compileFunc the * function to compile a source into a module for a particular global * object. Should be compileInternalModule() for InternalModuleLoader, * but overridden in ModuleLoader */ constructor(global, compileFunc) { this.global = global; this.compileFunc = compileFunc; } /** * Loads a file or resource URI synchronously * * @param {Uri} uri the file or resource URI to load * @returns {[contents: string, internal?: boolean] | null} */ loadURI(uri) { if (uri.scheme === 'file' || uri.scheme === 'resource') return [loadResourceOrFile(uri.uri)]; return null; } /** * Resolves an import specifier given an optional parent importer. * * @param {string} specifier the import specifier * @param {string | null} [parentURI] the URI of the module importing the specifier * @returns {Uri | null} */ resolveSpecifier(specifier, parentURI = null) { try { const uri = parseURI(specifier); if (uri) return uri; } catch (err) { // If it can't be parsed as a URI, try a relative path or return null. } if (isRelativePath(specifier)) { if (!parentURI) throw new ImportError('Cannot import relative path when module path is unknown.'); return this.resolveRelativePath(specifier, parentURI); } return null; } /** * Resolves a path relative to a URI, throwing an ImportError if * the parentURI isn't valid. * * @param {string} relativePath the relative path to resolve against the base URI * @param {string} importingModuleURI the URI of the module triggering this * resolve * @returns {Uri} */ resolveRelativePath(relativePath, importingModuleURI) { // Ensure the parent URI is valid. parseURI(importingModuleURI); // Handle relative imports from URI-based modules. const relativeURI = resolveRelativeResourceOrFile(importingModuleURI, relativePath); if (!relativeURI) throw new ImportError('File does not have a valid parent!'); return parseURI(relativeURI); } /** * Compiles a module source text with the module's URI * * @param {ModulePrivate} priv a module private object * @param {string} text the module source text to compile * @returns {Module} */ compileModule(priv, text) { const compiled = this.compileFunc(priv.uri, text); setModulePrivate(compiled, priv); return compiled; } /** * @param {string} specifier the specifier (e.g. relative path, root package) to resolve * @param {string | null} importingModuleURI the URI of the module * triggering this resolve * * @returns {Module | null} */ resolveModule(specifier, importingModuleURI) { const registry = getRegistry(this.global); // Check if the module has already been loaded let module = registry.get(specifier); if (module) return module; // 1) Resolve path and URI-based imports. const uri = this.resolveSpecifier(specifier, importingModuleURI); if (uri) { module = registry.get(uri.uriWithQuery); // Check if module is already loaded (relative handling) if (module) return module; const result = this.loadURI(uri); if (!result) return null; const [text, internal = false] = result; const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); const compiled = this.compileModule(priv, text); registry.set(uri.uriWithQuery, compiled); return compiled; } return null; } moduleResolveHook(importingModulePriv, specifier) { const resolved = this.resolveModule(specifier, importingModulePriv.uri ?? null); if (!resolved) throw new ImportError(`Module not found: ${specifier}`); return resolved; } moduleLoadHook(id, uri) { const priv = new ModulePrivate(id, uri); const result = this.loadURI(parseURI(uri)); // result can only be null if `this` is InternalModuleLoader. If `this` // is ModuleLoader, then loadURI() will have thrown if (!result) throw new ImportError(`URI not found: ${uri}`); const [text] = result; const compiled = this.compileModule(priv, text); const registry = getRegistry(this.global); registry.set(id, compiled); return compiled; } } class ModuleLoader extends InternalModuleLoader { /** * @param {typeof moduleGlobalThis} global the global object to register modules with. */ constructor(global) { // Sets 'compileFunc' in InternalModuleLoader to be 'compileModule' super(global, compileModule); /** * The set of "module" URI globs (the module search path) * * For example, having `"resource:///org/cinnamon/cjs/modules/esm/*.js"` in this * set allows `import "system"` if * `"resource:///org/cinnamon/cjs/modules/esm/system.js"` exists. * * Only `*` is supported as a replacement character, `**` is not supported. * * @type {Set} */ this.moduleURIs = new Set([ 'resource:///org/cinnamon/cjs/modules/esm/*.js', ]); /** * @type {Map} * * A map of handlers for URI schemes (e.g. gi://) */ this.schemeHandlers = new Map(); } /** * @param {string} specifier the package specifier * @returns {string[]} the possible internal URIs */ buildInternalURIs(specifier) { const {moduleURIs} = this; const builtURIs = []; for (const uri of moduleURIs) { const builtURI = uri.replace('*', specifier); builtURIs.push(builtURI); } return builtURIs; } /** * @param {string} scheme the URI scheme to register * @param {SchemeHandler} handler a handler */ registerScheme(scheme, handler) { this.schemeHandlers.set(scheme, handler); } /** * Overrides InternalModuleLoader.loadURI * * @param {Uri} uri a Uri object to load */ loadURI(uri) { if (uri.scheme) { const loader = this.schemeHandlers.get(uri.scheme); if (loader) return loader.load(uri); } const result = super.loadURI(uri); if (result) return result; throw new ImportError(`Invalid module URI: ${uri.uri}`); } /** * Resolves a bare specifier like 'system' against internal resources, * erroring if no resource is found. * * @param {string} specifier the module specifier to resolve for an import * @returns {import("./internalLoader").Module} */ resolveBareSpecifier(specifier) { // 2) Resolve internal imports. const uri = this.buildInternalURIs(specifier).find(uriExists); if (!uri) throw new ImportError(`Unknown module: '${specifier}'`); const parsed = parseURI(uri); if (parsed.scheme !== 'file' && parsed.scheme !== 'resource') throw new ImportError('Only file:// and resource:// URIs are currently supported.'); const text = loadResourceOrFile(parsed.uri); const priv = new ModulePrivate(specifier, uri, true); const compiled = this.compileModule(priv, text); const registry = getRegistry(this.global); if (!registry.has(specifier)) registry.set(specifier, compiled); return compiled; } /** * Resolves a module import with optional handling for relative imports. * Overrides InternalModuleLoader.moduleResolveHook * * @param {ModulePrivate | null} importingModulePriv - the private object of * the module initiating the import, null if the import is not coming from * a file that can resolve relative imports * @param {string} specifier the module specifier to resolve for an import * @returns {import("./internalLoader").Module} */ moduleResolveHook(importingModulePriv, specifier) { const module = this.resolveModule(specifier, importingModulePriv?.uri); if (module) return module; return this.resolveBareSpecifier(specifier); } /** * Resolves a module import with optional handling for relative imports asynchronously. * * @param {ModulePrivate | null} importingModulePriv - the private object of * the module initiating the import, null if the import is not coming from * a file that can resolve relative imports * @param {string} specifier - the specifier (e.g. relative path, root * package) to resolve * @returns {Promise} */ async moduleResolveAsyncHook(importingModulePriv, specifier) { const importingModuleURI = importingModulePriv?.uri; const registry = getRegistry(this.global); // Check if the module has already been loaded let module = registry.get(specifier); if (module) return module; // 1) Resolve path and URI-based imports. const uri = this.resolveSpecifier(specifier, importingModuleURI); if (uri) { module = registry.get(uri.uriWithQuery); // Check if module is already loaded (relative handling) if (module) return module; const result = await this.loadURIAsync(uri); if (!result) return null; // Check if module loaded while awaiting. module = registry.get(uri.uriWithQuery); if (module) return module; const [text, internal = false] = result; const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); const compiled = this.compileModule(priv, text); registry.set(uri.uriWithQuery, compiled); return compiled; } // 2) Resolve internal imports. return this.resolveBareSpecifier(specifier); } /** * Loads a file or resource URI asynchronously * * @param {Uri} uri the file or resource URI to load * @returns {Promise<[string] | [string, boolean] | null>} */ async loadURIAsync(uri) { if (uri.scheme) { const loader = this.schemeHandlers.get(uri.scheme); if (loader) return loader.loadAsync(uri); } if (uri.scheme === 'file' || uri.scheme === 'resource') { const result = await loadResourceOrFileAsync(uri.uri); return [result]; } return null; } } const moduleLoader = new ModuleLoader(moduleGlobalThis); setGlobalModuleLoader(moduleGlobalThis, moduleLoader); /** * Creates a module source text to expose a GI namespace via a default export. * * @param {string} namespace the GI namespace to import * @param {string} [version] the version string of the namespace * * @returns {string} the generated module source text */ function generateGIModule(namespace, version) { return ` import $$gi from 'gi'; export default $$gi.require('${namespace}'${version !== undefined ? `, '${version}'` : ''}); `; } moduleLoader.registerScheme('gi', { /** * @param {Uri} uri the URI to load */ load(uri) { const namespace = uri.host; const version = uri.query.version; return [generateGIModule(namespace, version), true]; }, /** * @param {Uri} uri the URI to load asynchronously */ loadAsync(uri) { // gi: only does string manipulation, so it is safe to use the same code for sync and async. return this.load(uri); }, }); cjs-128.1/modules/print.cpp0000664000175000017500000002036115116312211014533 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2009 Red Hat, Inc. #include #include // for size_t #include #include #include #include #include #include // for JS_EncodeStringToUTF8 #include #include #include // for JS_DefineFunctions #include // for JS_FN, JSFunctionSpec, JS_FS_END #include #include #include // for UniqueChars #include #include // for JS_NewPlainObject #include "cjs/deprecation.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/print.h" GJS_JSAPI_RETURN_CONVENTION static bool gjs_log(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); if (argc != 1) { gjs_throw(cx, "Must pass a single argument to log()"); return false; } /* JS::ToString might throw, in which case we will only log that the value * could not be converted to string */ JS::AutoSaveExceptionState exc_state(cx); JS::RootedString jstr(cx, JS::ToString(cx, argv[0])); exc_state.restore(); if (!jstr) { g_message("JS LOG: "); return true; } JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr)); if (!s) return false; g_message("JS LOG: %s", s.get()); argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_log_error(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); if ((argc != 1 && argc != 2) || !argv[0].isObject()) { gjs_throw( cx, "Must pass an exception and optionally a message to logError()"); return false; } JS::RootedString jstr(cx); if (argc == 2) { /* JS::ToString might throw, in which case we will only log that the * value could not be converted to string */ JS::AutoSaveExceptionState exc_state(cx); jstr = JS::ToString(cx, argv[1]); exc_state.restore(); } gjs_log_exception_full(cx, argv[0], jstr, G_LOG_LEVEL_WARNING); argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_print_parse_args(JSContext* cx, const JS::CallArgs& argv, std::string* buffer) { g_assert(buffer && "forgot out parameter"); buffer->clear(); for (unsigned n = 0; n < argv.length(); ++n) { /* JS::ToString might throw, in which case we will only log that the * value could not be converted to string */ JS::AutoSaveExceptionState exc_state(cx); JS::RootedString jstr(cx, JS::ToString(cx, argv[n])); exc_state.restore(); if (jstr) { JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr)); if (!s) return false; *buffer += s.get(); if (n < (argv.length() - 1)) *buffer += ' '; } else { *buffer = ""; return true; } } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_print(JSContext* context, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); std::string buffer; if (!gjs_print_parse_args(context, argv, &buffer)) return false; g_print("%s\n", buffer.c_str()); argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_printerr(JSContext* context, unsigned argc, JS::Value* vp) { JS::CallArgs argv = JS::CallArgsFromVp(argc, vp); std::string buffer; if (!gjs_print_parse_args(context, argv, &buffer)) return false; g_printerr("%s\n", buffer.c_str()); argv.rval().setUndefined(); return true; } // The pretty-print functionality is best written in JS, but needs to be used // from C++ code. This stores the prettyPrint() function in a slot on the global // object so that it can be used internally by the Console module. // This function is not available to user code. GJS_JSAPI_RETURN_CONVENTION static bool set_pretty_print_function(JSContext*, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); // can only be called internally, so OK to assert correct arguments g_assert(args.length() == 2 && "setPrettyPrintFunction takes 2 arguments"); JS::Value v_global = args[0]; JS::Value v_func = args[1]; g_assert(v_global.isObject() && "first argument must be an object"); g_assert(v_func.isObject() && "second argument must be an object"); gjs_set_global_slot(&v_global.toObject(), GjsGlobalSlot::PRETTY_PRINT_FUNC, v_func); args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool get_pretty_print_function(JSContext*, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); g_assert(args.length() == 1 && "getPrettyPrintFunction takes 1 arguments"); JS::Value v_global = args[0]; g_assert(v_global.isObject() && "argument must be an object"); JS::Value pretty_print = gjs_get_global_slot( &v_global.toObject(), GjsGlobalSlot::PRETTY_PRINT_FUNC); args.rval().set(pretty_print); return true; } GJS_JSAPI_RETURN_CONVENTION static bool warn_deprecated_once_per_callsite(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); g_assert(args.length() >= 1 && "warnDeprecatedOncePerCallsite takes at least 1 argument"); g_assert( args[0].isInt32() && "warnDeprecatedOncePerCallsite argument 1 must be a message ID number"); int32_t message_id = args[0].toInt32(); g_assert( message_id >= 0 && uint32_t(message_id) < GjsDeprecationMessageId::LastValue && "warnDeprecatedOncePerCallsite argument 1 must be a message ID number"); if (args.length() == 1) { _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId(message_id), 2); return true; } std::vector format_args_str; std::vector format_args; for (size_t ix = 1; ix < args.length(); ix++) { g_assert(args[ix].isString() && "warnDeprecatedOncePerCallsite subsequent arguments must be " "strings"); JS::RootedString v_format_arg{cx, args[ix].toString()}; JS::UniqueChars format_arg = JS_EncodeStringToUTF8(cx, v_format_arg); if (!format_arg) return false; format_args_str.emplace_back(format_arg.get()); format_args.emplace_back(format_args_str.back().c_str()); } _gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId(message_id), format_args, 2); return true; } // clang-format off static constexpr JSFunctionSpec funcs[] = { JS_FN("log", gjs_log, 1, GJS_MODULE_PROP_FLAGS), JS_FN("logError", gjs_log_error, 2, GJS_MODULE_PROP_FLAGS), JS_FN("print", gjs_print, 0, GJS_MODULE_PROP_FLAGS), JS_FN("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS), JS_FN("setPrettyPrintFunction", set_pretty_print_function, 1, GJS_MODULE_PROP_FLAGS), JS_FN("getPrettyPrintFunction", get_pretty_print_function, 1, GJS_MODULE_PROP_FLAGS), JS_FN("warnDeprecatedOncePerCallsite", warn_deprecated_once_per_callsite, 1, GJS_MODULE_PROP_FLAGS), JS_FS_END}; // clang-format on static constexpr JSPropertySpec props[] = { JSPropertySpec::int32Value("PLATFORM_SPECIFIC_TYPELIB", GJS_MODULE_PROP_FLAGS, GjsDeprecationMessageId::PlatformSpecificTypelib), JS_PS_END}; bool gjs_define_print_stuff(JSContext* context, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(context)); if (!module) return false; return JS_DefineFunctions(context, module, funcs) && JS_DefineProperties(context, module, props); } cjs-128.1/modules/print.h0000664000175000017500000000070215116312211014175 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh #ifndef MODULES_PRINT_H_ #define MODULES_PRINT_H_ #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_print_stuff(JSContext* context, JS::MutableHandleObject module); #endif // MODULES_PRINT_H_ cjs-128.1/modules/script/0000775000175000017500000000000015116312211014175 5ustar fabiofabiocjs-128.1/modules/script/.eslintrc.yml0000664000175000017500000000023615116312211016622 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh rules: jsdoc/require-jsdoc: 'off' cjs-128.1/modules/script/_bootstrap/0000775000175000017500000000000015116312211016351 5ustar fabiofabiocjs-128.1/modules/script/_bootstrap/.eslintrc.yml0000664000175000017500000000034115116312211020773 0ustar fabiofabio--- # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2020 Evan Welsh rules: require-jsdoc: "off" globals: log: "off" logError: "off" print: "off" printerr: "off" cjs-128.1/modules/script/_bootstrap/coverage.js0000664000175000017500000000044315116312211020503 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Philip Chimento (function (exports) { 'use strict'; exports.debugger = new Debugger(exports.debuggee); exports.debugger.collectCoverageInfo = true; })(globalThis); cjs-128.1/modules/script/_bootstrap/debugger.js0000664000175000017500000007051415116312211020502 0ustar fabiofabio/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ /* global debuggee, quit, loadNative, readline, uneval */ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2011 Mozilla Foundation and contributors /* * This is a simple command-line debugger for GJS programs. It is based on * jorendb, which is a toy debugger for shell-js programs included in the * SpiderMonkey source. * * To run it: gjs -d path/to/file.js * Execution will stop at debugger statements, and you'll get a prompt before * the first frame is executed. */ const {print, logError} = loadNative('_print'); // Debugger state. var focusedFrame = null; var topFrame = null; var debuggeeValues = {}; var nextDebuggeeValueIndex = 1; var lastExc = null; var options = {pretty: true, colors: true, ignoreCaughtExceptions: true}; var breakpoints = [undefined]; // Breakpoint numbers start at 1 var skipUnwindHandler = false; // Cleanup functions to run when we next re-enter the repl. var replCleanups = []; // Convert a debuggee value v to a string. function dvToString(v) { if (typeof v === 'undefined') return 'undefined'; // uneval(undefined) === '(void 0)', confusing if (typeof v === 'object' && v !== null) return `[object ${v.class}]`; const s = uneval(v); if (s.length > 400) return `${s.substr(0, 400)}...<${s.length - 400} more bytes>...`; return s; } function debuggeeValueToString(dv, style = {pretty: options.pretty}) { // Special sentinel values returned by Debugger.Environment.getVariable() if (typeof dv === 'object' && dv !== null) { if (dv.missingArguments) return ['', undefined]; if (dv.optimizedOut) return ['', undefined]; if (dv.uninitialized) return ['', undefined]; if (!(dv instanceof Debugger.Object)) return ['', JSON.stringify(dv, null, 4)]; } const dvrepr = dvToString(dv); if (!style.pretty || (typeof dv !== 'object') || (dv === null)) return [dvrepr, undefined]; const exec = debuggeeGlobalWrapper.executeInGlobalWithBindings.bind(debuggeeGlobalWrapper); if (['TypeError', 'Error', 'GIRespositoryNamespace', 'GObject_Object'].includes(dv.class)) { const errval = exec('v.toString()', {v: dv}); return [dvrepr, errval['return']]; } if (style.brief) return [dvrepr, dvrepr]; const str = exec('imports._print.getPrettyPrintFunction(globalThis)(v)', {v: dv}); if ('throw' in str) { if (style.noerror) return [dvrepr, undefined]; const substyle = {...style, noerror: true}; return [dvrepr, debuggeeValueToString(str.throw, substyle)]; } return [dvrepr, str['return']]; } function showDebuggeeValue(dv, style = {pretty: options.pretty}) { const i = nextDebuggeeValueIndex++; debuggeeValues[`$${i}`] = dv; debuggeeValues['$$'] = dv; const [brief, full] = debuggeeValueToString(dv, style); print(`$${i} = ${brief}`); if (full !== undefined) print(full); } Object.defineProperty(Debugger.Frame.prototype, 'num', { configurable: true, enumerable: false, get() { let i = 0; let f; for (f = topFrame; f && f !== this; f = f.older) i++; return f === null ? undefined : i; }, }); Debugger.Frame.prototype.describeFrame = function () { if (this.type === 'call') { return `${this.callee.name || ''}(${ this.arguments.map(dvToString).join(', ')})`; } else if (this.type === 'global') { return 'toplevel'; } else { return `${this.type} code`; } }; Debugger.Frame.prototype.describePosition = function () { if (this.script) return this.script.describeOffset(this.offset); return null; }; Debugger.Frame.prototype.describeFull = function () { const fr = this.describeFrame(); const pos = this.describePosition(); if (pos) return `${fr} at ${pos}`; return fr; }; Object.defineProperty(Debugger.Frame.prototype, 'line', { configurable: true, enumerable: false, get() { if (this.script) return this.script.getOffsetLocation(this.offset).lineNumber; else return null; }, }); Debugger.Script.prototype.describeOffset = function describeOffset(offset) { const {lineNumber, columnNumber} = this.getOffsetLocation(offset); const url = this.url || ''; return `${url}:${lineNumber}:${columnNumber}`; }; function showFrame(f, n, option = {btCommand: false, fullOption: false}) { if (f === undefined || f === null) { f = focusedFrame; if (f === null) { print('No stack.'); return; } } if (n === undefined) { n = f.num; if (n === undefined) throw new Error('Internal error: frame not on stack'); } print(`#${n.toString().padEnd(4)} ${f.describeFull()}`); if (option.btCommand) { if (option.fullOption) { const variables = f.environment.names(); for (let i = 0; i < variables.length; i++) { if (variables.length === 0) print('No locals.'); const value = f.environment.getVariable(variables[i]); const [brief] = debuggeeValueToString(value, {brief: false, pretty: false}); print(`${variables[i]} = ${brief}`); } } } else { let lineNumber = f.line; print(` ${lineNumber}\t${f.script.source.text.split('\n')[lineNumber - 1]}`); } } function saveExcursion(fn) { const tf = topFrame, ff = focusedFrame; try { return fn(); } finally { topFrame = tf; focusedFrame = ff; } } // Evaluate @expr in the current frame, logging and suppressing any exceptions function evalInFrame(expr) { if (!focusedFrame) { print('No stack'); return; } skipUnwindHandler = true; let cv; try { cv = saveExcursion( () => focusedFrame.evalWithBindings(`(${expr})`, debuggeeValues)); } finally { skipUnwindHandler = false; } if (cv === null) { print(`Debuggee died while evaluating ${expr}`); return; } const {throw: exc, return: dv} = cv; if (exc) { print(`Exception caught while evaluating ${expr}: ${dvToString(exc)}`); return; } return {value: dv}; } // Accept debugger commands starting with '#' so that scripting the debugger // can be annotated function commentCommand(comment) { void comment; } // Evaluate an expression in the Debugger global - used for debugging the // debugger function evalCommand(expr) { eval(expr); } function quitCommand() { dbg.removeAllDebuggees(); quit(0); } quitCommand.summary = 'Quit the debugger'; quitCommand.helpText = `USAGE quit`; function backtraceCommand(option) { if (topFrame === null) print('No stack.'); if (option === '') { for (let i = 0, f = topFrame; f; i++, f = f.older) showFrame(f, i, {btCommand: true, fullOption: false}); } else if (option === 'full') { for (let i = 0, f = topFrame; f; i++, f = f.older) showFrame(f, i, {btCommand: true, fullOption: true}); } else { print('Invalid option'); } } backtraceCommand.summary = 'Print backtrace of all stack frames and details of all local variables if the full option is added'; backtraceCommand.helpText = `USAGE bt