cjs-140.0/0000775000175000017500000000000015167114161011224 5ustar fabiofabiocjs-140.0/.clang-format0000664000175000017500000000205615167114161013602 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$" Macros: - GJS_ALWAYS_INLINE=[[always_inline]] # COMPAT: switch in C++20 - GJS_JSAPI_RETURN_CONVENTION=[[nodiscard]] - GJS_USE=[[nodiscard]] 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-140.0/.clangd0000664000175000017500000000025415167114161012456 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Philip Chimento Diagnostics: ClangTidy: Remove: none* cjs-140.0/.editorconfig0000664000175000017500000000053115167114161013700 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-140.0/.github/0000775000175000017500000000000015167114161012564 5ustar fabiofabiocjs-140.0/.github/workflows/0000775000175000017500000000000015167114161014621 5ustar fabiofabiocjs-140.0/.github/workflows/build.yml0000664000175000017500000000107215167114161016443 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-140.0/.gitignore0000664000175000017500000000056215167114161013217 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-dev debian/cjs debian/.debhelper debian/libcjs-dbg debian/*.substvars debian/*.log debian/debhelper-build-stamp debian/files cjs-140.0/.gitmodules0000664000175000017500000000043215167114161013400 0ustar fabiofabio# SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2024 Philip Chimento [submodule "subprojects/gobject-introspection-tests"] path = subprojects/gobject-introspection-tests url = ../../GNOME/gobject-introspection-tests.git shallow = true cjs-140.0/CONTRIBUTING.md0000664000175000017500000003437315167114161013467 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 `Gjs::AutoChar` 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-140.0/COPYING0000664000175000017500000000021215167114161012252 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-140.0/CPPLINT.cfg0000664000175000017500000000124615167114161013021 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-140.0/LICENSES/0000775000175000017500000000000015167114161012431 5ustar fabiofabiocjs-140.0/LICENSES/BSD-3-Clause.txt0000664000175000017500000000271015167114161015154 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-140.0/LICENSES/CC-BY-3.0.txt0000664000175000017500000005334015167114161014272 0ustar fabiofabioCreative Commons Legal Code Attribution-ShareAlike 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. c. "Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR 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, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at https://creativecommons.org/. cjs-140.0/LICENSES/CC0-1.0.txt0000664000175000017500000001540415167114161014037 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-140.0/LICENSES/GPL-2.0-or-later.txt0000664000175000017500000004232615167114161015643 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-140.0/LICENSES/GPL-3.0-or-later.txt0000664000175000017500000010324615167114161015643 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-140.0/LICENSES/LGPL-2.0-or-later.txt0000664000175000017500000006030615167114161015755 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-140.0/LICENSES/LGPL-2.1-or-later.txt0000664000175000017500000006245615167114161015766 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-140.0/LICENSES/MIT.txt0000664000175000017500000000212415167114161013622 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-140.0/LICENSES/MPL-1.1.txt0000664000175000017500000005554715167114161014137 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-140.0/LICENSES/MPL-2.0.txt0000664000175000017500000003520115167114161014120 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-140.0/NEWS0000664000175000017500000056132115167114161011733 0ustar fabiofabioVersion 1.88.0 -------------- No changes since 1.87.90, only maintenance for CI. Version 1.87.90 --------------- - Closed bugs and merge requests: * Enabling various clang-tidy checks [!1066, !1070, Philip Chimento] * Various maintenance [!1067, Philip Chimento] * Error when importing Gio after manipulating Object.prototype [#728, !1068, Philip Chimento] * Investigate union fields being garbage collected [#726, !1069, Philip Chimento] * Follow-up from "Gtk: Builder constructor overrides in GTK4" [#727, !1071, Philip Chimento] * Tweaks to examples [!1074, Justin Donnelly] Version 1.87.2 -------------- - The beta release for the GNOME 50 cycle brings more modernization of the code base and type-safety. - Known defects: The new Gtk.Builder convenience API does not work correctly in some cases when connecting signals without a widget template, which may lead to the signals being disconnected after a garbage collection. This is planned to be addressed in 1.87.90 (https://gitlab.gnome.org/GNOME/gjs/-/issues/727). - Closed bugs and merge requests: * overrides/GLib: Add wrappers for GLib platform-depedent namespace split [!1054, Marco Trevisan] * Various maintenance [!1058, Philip Chimento] * Update CI images [!1060, !1061, Philip Chimento] * Enabling various clang-tidy checks [!1062, !1064, !1065, Philip Chimento] Version 1.87.1 -------------- - In this alpha release for the GNOME 50 cycle, the main focus has been on modernizing the code base and making it more type-safe. Expect more of this in the beta release. - We have better support for boxed C unions now in gobject-introspection. - Gtk.Builder has gained some JS-only conveniences, which should make it less tedious to use from JS code. The following construct properties are now recognized: * "data" accepts a string or UTF-8-encoded Uint8Array containing Gtk.Builder XML, and automatically calls add_from_string(). * "resource" accepts a string resource path, and automatically calls add_from_resource(). * "filename" accepts a string filename, and automatically calls add_from_file(). * "callbacks" accepts a property bag with properties equal to callbacks that are referenced in the Gtk.Builder XML. * "objects" accepts a property bag with properties equal to objects that should be exposed to the Gtk.Builder XML. Gtk.Builder has also gained a few JS-only convenience methods: * exposeObjects() calls expose_object() for each name and object in a property bag, much like the new "objects" construct property. * get_objects() overrides the original C get_objects() method, but on the returned array, additionally, there are named properties representing the exposed objects. - Gio.ActionMap.add_action_entries() now supports additional types in its array of entries: GLib.Variant and GLib.VariantType. It also interprets JS values as unpacked GLib.Variants where appropriate. - GLib.idle_add_once(), GLib.timeout_add_once(), and GLib.timeout_add_seconds_once() are now introspectable. - Closed bugs and merge requests: * Support accessing fields of unions [#273, !770, Marco Trevisan, Philip Chimento] * Default union constructor should be guessed the same way that struct constructors are [#657, !770, Marco Trevisan, Philip Chimento] * Gtk.BuilderScope only works with template classes [#631, !1031, Philip Chimento, Sonny Piers] * installed-tests/testCommandLine: Use bash to write escaped sequences [!1032, Marco Trevisan] * Remove link to dead developer-old.gnome.org [#619, !1034, Philip Chimento] * Remove copy constructor from GjsAutoError [#654, !1034, Philip Chimento] * Various maintenance and big coding style fixes [!1034, !1040, !1044, !1045, !1050, !1053, Philip Chimento] * Unknown signal error on array properties [#717, !1035, Philip Chimento] * overrides/Gio: Improve handling of errors and return values in GDBus servers async functions [!1036, Marco Trevisan] * Investigate improvements to Gio.ActionGroup.add_action_entries override [#713, !1037, Andy Holmes] * Fix IWYU jobs for current Clang version and Fedora-packaged IWYU [!1038, Philip Chimento] * ci, test-ci: Check the installed tests with shellcheck [!1039, Marco Trevisan] * ci: Save cobertura reports as artifacts and run coverage on MRs [!1041, Marco Trevisan] * Consistent style and targeted shells for all shell scripts in project [#720, !1042, Philip Chimento] * overrides/GLib: Add overrides for one-shot timeout/idle functions [!1043, Florian Müllner] * gi/boxed.cpp: fix gcc-16 build [!1046, Sergei Trofimovich] * gi: Allow optional inout arguments to be null [!1047, Florian Müllner] * Remove inline overloads [!1048, Philip Chimento] * Remove uses of JS::Value::setObjectOrNull [!1049, Philip Chimento] * jsapi-util: Improve reading of process RSS [!1051, !1056, Philip Chimento] * CI: Run cpplint on the whole codebase [!1052, Philip Chimento] * ci/coverage: Strip the build path from coverage report on JS files (and other CI cleanups) [!1055, Marco Trevisan] * info: Fix return type of GI::CallableInfo::closure_native_address() [!1057, Philip Chimento] Version 1.86.0 -------------- - GJS now requires GLib 2.86 to build. - Closed bugs and merge requests: * Fix coverage CI job [!1023, Philip Chimento] * Test failure: Package module doesn't find a non-existent interface method (caused by TypeError: GObject.type_default_interface_ref is not a function) [#711, !1024, Florian Müllner] * Test failure: not ok 1 - Issue 443 GObject wrapper disposed warning 1..1 [#712, !1025, !1026, Marco Trevisan, Michael Catanzaro] * gi/Gio: Improve mapping for platform-specific symbols into Gio [!1027, Marco Trevisan] * Remove .eslintignore [!1028, Florian Müllner] * (type filepath) returned from c is decoded as utf8 (and can fail) [#706, !1029, Gary Li] * Various maintenance [!1030, Philip Chimento] Version 1.85.90 --------------- - Closed bugs and merge requests: * Support flat C array return values [#603, 1015, Gary Li] * Make Gtk4Warnings test work again on CI [#698, !1017, Gary Li] * overrides/Gio: Add wrappers for platform-specific Gio functions [!1018, Marco Trevisan, Philip Chimento] * Consider whether to handle invalid UTF-8 coming from C when converting to JS string [#658, !1019, Gary Li] * cleanup: Remove dead code using older GLib [!1020, Marco Trevisan] * gjs -m makes mojibake [#707, !1021, Gary Li] * Various maintenance [!1022, Philip Chimento] Version 1.85.2 -------------- - New JavaScript features! This version of GJS is based on SpiderMonkey 140, an upgrade from the previous ESR (Extended Support Release) of SpiderMonkey 128. Here are the highlights of the new JavaScript features, taken from SpiderMonkey's release notes. For more information, look them up on MDN or devdocs.io. * Float16Array typed arrays are now supported, along with `DataView.prototype.getFloat16()` and `DataView.prototype.setFloat16()` for reading and setting Float16Array values from a DataView, and the `Math.f16round()` static method, which can be used to round numbers to 16 bits. * Regular expressions can now use the same name for named capturing groups in different disjunction alternatives. This is allowed because only one alternative in a disjunction will match, so a name declared in several alternatives can only reference one captured group. The names must still be unique within a particular alternative, and across the rest of the pattern. * Support for synchronous iterator helper methods has been added, including: `Iterator.prototype.drop()`, `Iterator.prototype.every()`, `Iterator.prototype.filter()`, `Iterator.prototype.find()`, `Iterator.prototype.flatMap()`, `Iterator.prototype.forEach()`, `Iterator.prototype.map()`, `Iterator.prototype.reduce()`, `Iterator.prototype.some()`, and `Iterator.prototype.take()`. These helpers allow Array-like operations on iterators without having to create intermediate Array objects. They can also be used with very large data sets where creating an intermediate Array would not even be possible. * The `(?ims-ims:...)` regular expression modifiers allow you to make changes to only take effect in a specific part of a regex pattern. * Support for Uint8Array methods to ease conversions between base64- and hex-encoded strings and byte arrays. The new methods include: + `Uint8Array.fromBase64()` and `Uint8Array.fromHex()` static methods for constructing a new Uint8Array object from a base64- and hex-encoded string, respectively. + `Uint8Array.prototype.setFromBase64()`, and `Uint8Array.prototype.setFromHex()` instance methods for populating an existing Uint8Array object with bytes from a base64- or hex-encoded string. + `Uint8Array.prototype.toBase64()` and `Uint8Array.prototype.toHex() instance methods, which return a base64- and hex- encoded string from the data in a Uint8Array object. * Support for the `RegExp.escape()` static method that can be used to escape any potential regex syntax characters in a string, returning a new string that can be safely used as a literal pattern for the `RegExp()` constructor. * The `Promise.try()` convenience method is now supported. The method takes a callback of any kind (a function that returns or throws, synchronously or asynchronously) and wraps its result in a Promise. This allows you to use promise semantics (`.then()`, `.catch()`) to handle the result from any kind of method. * The JSON parse with source proposal is now supported, which aims to provide features to mitigate issues around loss of precision when converting values such as large floats and date values between JavaScript values and JSON text. Specifically, the following features are now available: + The `JSON.parse()` reviver parameter context argument: Provides access to the original JSON source text that was parsed. + `JSON.isRawJSON()`: Tests whether a value is an object returned by `JSON.rawJSON()`. + `JSON.rawJSON()`: Creates a "raw JSON" object containing a piece of JSON text, which can then be included in an object to preserve the specified value when that object is stringified. * `Intl.DurationFormat` is now supported, enabling locale-sensitive formatting of durations. * The `Math.sumPrecise()` static method is now supported. This takes an iterable (such as an Array) of numbers and returns their sum. It is more precise than summing the numbers in a loop because it avoids floating point precision loss in intermediate results. * The `Atomics.pause()` static method is now supported. This method provides a hint to the CPU that the current thread is in a spinlock while waiting on access to a shared resource. The system can then reduce the resources allocated to the core (such as power) or thread, without yielding the current thread. * The `Error.captureStackTrace()` static method is now supported. This installs stack trace information on a provided object as the `Error.stack` property. Its main use case is to install a stack trace on a custom error object that does not derive from the Error interface. * The `Error.isError()` static method can now be used to check whether or not an object is an instance of an Error or a GError. This is more reliable than using `instanceof` for the same purpose. * The `import` declaration now supports importing JSON modules using the `with` attribute. * The Temporal API is now supported, this aims to simplify working with dates and times in various scenarios, with built-in time zone and calendar representations. This includes: + A duration (difference between two time points): `Temporal.Duration` + Points in time: - As a unique instant in history: * A timestamp: `Temporal.Instant` * A date-time with a time zone: `Temporal.ZonedDateTime` - Time-zone-unaware date/time ("Plain"): * Date (year, month, day) + time (hour, minute, second, millisecond, nanosecond): `Temporal.PlainDateTime` * Date (year, month, day): `Temporal.PlainDate` * Year, month: `Temporal.PlainYearMonth` * Month, day: `Temporal.PlainMonthDay` * Time (hour, minute, second, millisecond, nanosecond): `Temporal.PlainTime` + Now (current time) as various class instances, or in a specific format: `Temporal.Now` - Closed bugs and merge requests: * Port to libgirepository-2.0 needed [#684, !1001, Philip Chimento] * installed-tests: install sourcemap-number-module.js [!1002, Jeremy Bicha] * gjs-1.84.1 fails testsuite on s390x [#685, !1003, Pranav P, Jeremy Bicha] * build: Add a mozjs_dep_name pkgconfig variable [!1004, Philip Chimento] * Assertion when calling Gtk.MapListModel's map function [#691, !1005, Philip Chimento] * Various maintenance [!1006, !1014, Philip Chimento] * package: Fix port to gobject-introspection-2.0 [!1007, Florian Müllner] * SpiderMonkey 140 [#690, !1008, Xi Ruoyao, Philip Chimento] * build: Fix libffi dependency in .pc [!1009, Florian Müllner] * maint: Switch to flat eslint config [!1010, Florian Müllner] * Update Docker images to Fedora 42 and mozjs140 [!1011, !1012, !1013, Philip Chimento] Version 1.85.1 -------------- - Closed bugs and merge requests: * Various maintenance [!985, !990, !991, !1000, Philip Chimento] * ObjectInstance::unlink is slow since s_wrapped_gobject_list is a vector [#682, !989, road2react] * Make GTK3 tests optional [#679, !993, Philip Chimento] * Preparation for gobject-introspection-2.0 [!994, Philip Chimento] * Add aarch64 CI job [#364, !996, Gary Li] * Add null-safe C++ wrappers for libgirepository [!997, Philip Chimento] * Extra handling for enum/flags in setter and getter callers [!998, Pranav P] * Register Cairo.Path and Cairo.Pattern as foreign structs [#659, !999, Gary Li] * Crash when passing certain Cairo types as transfer-full in arguments [#660, !999, Gary Li] Version 1.84.2 -------------- - Closed bugs and merge requests: * GtkNotebook.pages GListModel is inaccessible from GJS [#686, !992, Philip Chimento] Version 1.84.1 -------------- - 1.84.0 was never released due to Freedesktop outage. 1.84.1 is identical. Version 1.84.0 -------------- - Closed bugs and merge requests: * tests: Prevent failures when GTK4/DISPLAY is missing [!986, Jan Tojnar] * testWarnings: run gc wrapper test only under Gtk4 [!987, Gary Li] Version 1.83.90 --------------- - Closed bugs and merge requests: * Various maintenance [!982, Philip Chimento] * Add type checking job [!983, Philip Chimento] * Write g-i regression tests for flags and enum values with gaps [#538, !984, Gary Li] Version 1.83.4 -------------- - Brown bag release to fix codespell error in NEWS file. Version 1.83.3 -------------- - The gjs-console REPL is now asynchronous. You can, for example, create a window with a button, connect a signal handler, click the button, and the signal handler will run when the button is clicked. Previously, the signal handler wouldn't run because it was blocked by the console waiting for input. This doesn't yet make `await` work in the console, but it is a prerequisite. - Usually for C APIs that use GValue, GJS transparently substitutes native JS values. However, in some cases you need to use the GObject.Value wrapper in JS. There is now a new API to construct GObject.Value. Instead of constructing an empty Value object, calling `init()` with the type, and then `set_...` to fill it, you can now do it in one: `new GObject.Value(String, 'a string')`. (The old way still works.) - Closed bugs and merge requests: * interactive interpreter + mainloop [#67, !670, !975, Evan Welsh, Philip Chimento] * object: Add support for static virtual functions [!802, Marco Trevisan, Philip Chimento] * "%Id" support in format strings for alternative digits disabled due to error in detection at configure/build time [#671, !972, Philip Chimento] * null-prototype objects should be pretty-printed less confusingly [#626, !973, Gary Li] * Missing property with gjs 1.83.2 [#677, !976, Philip Chimento] * arg-types-inl: Replace `` pairs with a single TAG [!977, Philip Chimento] * Introduce simpler override for GObject.Value [#456, !978, Gary Li] * Use Meson 1.4 and full_path() feature [!979, Philip Chimento] * Update gobject-introspection-tests [!981, Philip Chimento] Version 1.83.2 -------------- - Closed bugs and merge requests: * profiler: only build dynamic string for profiler label if profiling [#668, !971, Gary Li] * object: Fix missing static_type_name template parameter [!974, Philip Chimento] Version 1.83.1 -------------- - GJS now supports source maps. If you use build tools such as TypeScript for source code transformation, you can ship source map files alongside your built JS files and make sure your build tool adds the magic source map comment. You will then get the original source locations printed in stack traces and in the debugger. - In the interactive interpreter (gjs-console), command history is now saved between runs. You can set the environment variable GJS_REPL_HISTORY to save the command history to a custom file, or set it to an empty string to switch this feature off. - The debugger now supports examining private fields. - Some performance and memory usage improvements around calling GNOME platform functions and accessing properties of GNOME platform objects. - Backwards-incompatible change: Gettext.setlocale() now only affects the locale of the current thread. This will not affect your JS code, but it may affect your app if you use a C library with worker threads and you relied on being able to set the locale in those worker threads from JS. - Closed bugs and merge requests: * Rewrite arguments cache using C++ inheritance [!519, Marco Trevisan, Philip Chimento] * package: Try to load resource module name if available [!839, Marco Trevisan] * object, args-cache: Improve performance with properties basic types [!866, Marco Trevisan, Philip Chimento] * Use property accessors and setters directly [#524, !867, Marco Trevisan, Philip Chimento] * gjs-util: make gjs_setlocale thread-safe [!893, Ray Strode] * Support Source Maps [#474, !938, Gary Li] * Fix return value of load_contents_async [!956, Sebastian Wiesner] * Various maintenance [!957, !961, !967, Philip Chimento] * Add history support to REPL [#645, !958, Gary Li] * Some prep for type safety refactors [!959, Philip Chimento] * Update to latest gobject-introspection-tests [!962, Philip Chimento] * Build failure regression for i686 [#669, !963, Philip Chimento] * Segfault when using GtkListView and custom widgets [#443, !964, Gary Li] * ci: Switch to GNOME GitLab mirror of ci-templates [!965, BartÅ‚omiej Piotrowski] * Connecting to signal of a GstElement errors with "too much recursion" [#557, !966, Gary Li] * Update to use GNOME Release Service [!968, Philip Chimento] * Enable inspecting symbol properties and private fields in the debugger [#455, !969, Gary Li] Version 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 gjs-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 GjsPrivate [!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] * GjsPrivate: 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(): gjs-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 GjsPrivate-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 gjs-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 gjs-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 gjs-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/gjs-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-140.0/README.MSVC.md0000664000175000017500000002141615167114161013256 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 140 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 140.x (mozjs-140). 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 140.x sources via the FireFox (ESR) or Thunderbird 140.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 140.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/mozjs140.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/mozjs140.bin`: Some installation path, change as needed If your GJS build crashes upon launch, use Dependency Walker to ensure that mozjs-140.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-140.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-140 for convenience. You will need to place the generated mozjs-140.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-140.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-140.0/README.md0000664000175000017500000000766615167114161012522 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-140.0/build/0000775000175000017500000000000015167114161012323 5ustar fabiofabiocjs-140.0/build/choose-tests-locale.sh0000775000175000017500000000165515167114161016546 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-140.0/build/flatpak/0000775000175000017500000000000015167114161013745 5ustar fabiofabiocjs-140.0/build/flatpak/org.gnome.GjsConsole.json0000664000175000017500000000205015167114161020575 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": "gjs-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-140.0/build/maintainer-tag-release.sh0000775000175000017500000000305115167114161017177 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_version="${1:?project version is required}" # 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 ;; 1.8[56].*) gnome_series=49 ;; 1.8[78].*) gnome_series=50 ;; *) 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 git tag -s "$project_version" -m "Version $project_version" fi git push --atomic origin "$branch" "$project_version" popd cjs-140.0/build/symlink-gjs.py0000664000175000017500000000151015167114161015141 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 gjs-console.exe. shutil.copyfile('gjs-console.exe', os.path.join(installed_bin_dir, 'gjs.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-140.0/cjs/0000775000175000017500000000000015167114161012003 5ustar fabiofabiocjs-140.0/cjs/atoms.cpp0000664000175000017500000000317115167114161013634 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; #undef INITIALIZE_ATOM } 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-140.0/cjs/atoms.h0000664000175000017500000000776315167114161013314 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 #pragma once #include #include #include #include #include "cjs/macros.h" class JSTracer; // clang-format off #define FOR_EACH_ATOM(macro) \ macro(cause, "cause") \ macro(clear_cache, "clearCache") \ 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*, 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*, const char* str); }; class GjsAtoms { public: GjsAtoms() = default; ~GjsAtoms() = default; // prevents giant destructor from being inlined GJS_JSAPI_RETURN_CONVENTION bool init_atoms(JSContext*); void trace(JSTracer*); #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 cjs-140.0/cjs/auto.h0000664000175000017500000002022215167114161013122 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 // SPDX-FileCopyrightText: 2018-2020 Canonical, Ltd // SPDX-FileCopyrightText: 2018, 2024 Philip Chimento #pragma once #include #include #include #include #include #include // IWYU pragma: no_forward_declare _GVariant #include // for UniqueChars // Auto pointers. We don't use GLib's g_autofree and friends because they only // work on GCC and Clang, and we try to support MSVC where possible. But this is // C++, so we use C++ classes. namespace Gjs { // A sentinel object used to pick the AutoPointer constructor that adds a // reference: AutoFoo foo{pointer, TakeOwnership{}}; struct TakeOwnership {}; template using AutoPointerRefFunction = F* (*)(F*); template using AutoPointerFreeFunction = void (*)(F*); template free_func = free, AutoPointerRefFunction ref_func = nullptr> struct AutoPointer { 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 = AutoPointer; 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 AutoPointer(Ptr ptr = nullptr) // NOLINT(runtime/explicit) : m_ptr(ptr) {} template && std::is_array_v>> explicit constexpr AutoPointer(U ptr[]) : m_ptr(ptr) {} constexpr AutoPointer(Ptr ptr, const TakeOwnership&) : AutoPointer(ptr) { m_ptr = copy(); } constexpr AutoPointer(ConstPtr ptr, const TakeOwnership& o) : AutoPointer(const_cast(ptr), o) {} constexpr AutoPointer(AutoPointer&& other) : AutoPointer() { this->swap(other); } constexpr AutoPointer(AutoPointer const& other) : AutoPointer() { *this = other; } constexpr AutoPointer& operator=(Ptr ptr) { reset(ptr); return *this; } constexpr AutoPointer& operator=(AutoPointer&& other) { this->swap(other); return *this; } constexpr AutoPointer& operator=(AutoPointer const& other) { AutoPointer dup{other.get(), TakeOwnership{}}; 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; } [[nodiscard]] constexpr Ptr get() const { return m_ptr; } [[nodiscard]] constexpr Ptr* out() { return &m_ptr; } [[nodiscard]] 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(AutoPointer& other) { std::swap(this->m_ptr, other.m_ptr); } /* constexpr */ ~AutoPointer() { // 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, AutoPointerRefFunction ref_func> constexpr bool operator==(AutoPointer const& lhs, AutoPointer const& rhs) { return lhs.get() == rhs.get(); } template using AutoFree = AutoPointer; struct AutoCharFuncs { static char* dup(char* str) { return g_strdup(str); } static void free(char* str) { g_free(str); } }; using AutoChar = AutoPointer; // This moves a string owned by the JS runtime into the GLib domain. This is // only possible because currently, js_free() and g_free() both ultimately call // free(). It would cause crashes if SpiderMonkey were to stop supporting // embedders using the system allocator in the future. In that case, this // function would have to copy the string. [[nodiscard]] inline AutoChar js_chars_to_glib(JS::UniqueChars&& js_chars) { return {js_chars.release()}; } using AutoStrv = AutoPointer; template using AutoUnref = AutoPointer; using AutoGVariant = AutoPointer; using AutoParam = AutoPointer; using AutoGClosure = AutoPointer; template constexpr void AutoPointerDeleter(T v) { if constexpr (std::is_array_v) delete[] reinterpret_cast*>(v); else delete v; } template using AutoCppPointer = AutoPointer>; template class AutoTypeClass : public AutoPointer { explicit AutoTypeClass(void* ptr = nullptr) : AutoPointer(static_cast(ptr)) {} public: explicit AutoTypeClass(GType gtype) : AutoTypeClass(g_type_class_ref(gtype)) {} }; template struct SmartPointer : AutoPointer { using AutoPointer::AutoPointer; }; template <> struct SmartPointer : AutoStrv { using AutoStrv::AutoPointer; }; template <> struct SmartPointer : AutoStrv { using AutoStrv::AutoPointer; }; template <> struct SmartPointer : AutoUnref { using AutoUnref::AutoPointer; }; template <> struct SmartPointer : AutoGVariant { using AutoGVariant::AutoPointer; }; template <> struct SmartPointer : AutoPointer { using AutoPointer::AutoPointer; }; template <> struct SmartPointer : AutoPointer { using AutoPointer::AutoPointer; }; } // namespace Gjs cjs-140.0/cjs/byteArray.cpp0000664000175000017500000001600215167114161014450 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/struct.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* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject bytes_obj{cx}; if (!gjs_parse_call_args(cx, "fromGBytes", args, "o", "bytes", &bytes_obj)) return false; if (!StructBase::typecheck(cx, bytes_obj, G_TYPE_BYTES)) return false; GBytes* gbytes = StructBase::to_c_ptr(cx, 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{cx, JS_NewUint8Array(cx, 0)}; if (!empty_array || !define_legacy_tostring(cx, empty_array)) return false; args.rval().setObject(*empty_array); return true; } JS::RootedObject array_buffer{cx, JS::NewArrayBuffer(cx, 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{cx, JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1)}; if (!obj || !define_legacy_tostring(cx, obj)) return false; args.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-140.0/cjs/byteArray.h0000664000175000017500000000135015167114161014115 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 #pragma once #include #include // for size_t #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_byte_array_stuff(JSContext*, JS::MutableHandleObject module); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_byte_array_from_data_copy(JSContext*, size_t nbytes, void* data); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_byte_array_from_byte_array(JSContext*, GByteArray*); [[nodiscard]] GByteArray* gjs_byte_array_get_byte_array(JSObject*); [[nodiscard]] GBytes* gjs_byte_array_get_bytes(JSObject*); cjs-140.0/cjs/cjs.stp.in0000664000175000017500000000143115167114161013716 0ustar fabiofabio/* * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later * SPDX-FileCopyrightText: 2010 Red Hat, Inc. */ probe gjs.object_wrapper_new = process("@EXPANDED_LIBDIR@/libgjs-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("gjs.object_wrapper_new(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } probe gjs.object_wrapper_finalize = process("@EXPANDED_LIBDIR@/libgjs-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("gjs.object_wrapper_finalize(%p, %s, %s)", wrapper_address, gi_namespace, gi_name); } cjs-140.0/cjs/cjs_pch.hh0000664000175000017500000000767615167114161013755 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 #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 #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-140.0/cjs/console.cpp0000664000175000017500000003552315167114161014161 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 "cjs/auto.h" #include "cjs/gerror-result.h" #include "cjs/gjs.h" #include "util/console.h" static Gjs::AutoStrv include_path; static Gjs::AutoStrv coverage_prefixes; static Gjs::AutoChar coverage_output_path; static Gjs::AutoChar profile_output_path; static Gjs::AutoChar 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 = Gjs::AutoPointer; 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 DIR to the list of paths 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"}, {nullptr}}; [[nodiscard]] static Gjs::AutoStrv strndupv(int n, char* const* strv) { Gjs::AutoPointer 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); } [[nodiscard]] static Gjs::AutoStrv strcatv(char** strv1, char** strv2) { if (strv1 == nullptr && strv2 == nullptr) return nullptr; if (strv1 == nullptr) return g_strdupv(strv2); if (strv2 == nullptr) return g_strdupv(strv1); Gjs::AutoPointer 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); } static gboolean parse_profile_arg(const char* option_name [[maybe_unused]], const char* value, void*, GError**) { enable_profiler = true; profile_output_path = Gjs::AutoChar{value, Gjs::TakeOwnership{}}; return true; } static void check_script_args_for_stray_gjs_args(int argc, char* const* argv) { Gjs::AutoError error; Gjs::AutoStrv new_coverage_prefixes; Gjs::AutoChar new_coverage_output_path; Gjs::AutoStrv 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. 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()}, {nullptr}}; Gjs::AutoStrv argv_copy{g_new(char*, argc + 2)}; argv_copy[0] = g_strdup("dummy"); // Fake argv[0] for GOptionContext for (int ix = 0; ix < argc; ix++) argv_copy[ix + 1] = g_strdup(argv[ix]); argv_copy[argc + 1] = nullptr; GjsAutoGOptionContext script_options = g_option_context_new(nullptr); 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, nullptr); 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* gjs_context, int argc, char* const* argv, const char* script, size_t len, const char* filename) { gjs_context_set_argv(gjs_context, argc, const_cast(argv)); Gjs::AutoError error; // evaluate the script int code = 0; if (exec_as_module) { Gjs::AutoUnref output{g_file_new_for_commandline_arg(filename)}; Gjs::AutoChar uri{g_file_get_uri(output)}; if (!gjs_context_register_module(gjs_context, uri, uri, &error)) { g_critical("%s", error->message); code = 1; } uint8_t code_u8 = 0; if (!code && !gjs_context_eval_module(gjs_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(gjs_context, script, len, filename, &code, &error)) { if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT)) g_critical("%s", error->message); } return code; } class TraceFD { int m_fd = -1; public: TraceFD() = default; ~TraceFD() { close(); } TraceFD(const TraceFD&) = delete; TraceFD& operator=(const TraceFD&) = delete; operator bool() const { return m_fd != -1; } TraceFD& operator=(int fd) { if (m_fd != fd) { close(); m_fd = fd; } return *this; } void close() { if (m_fd != -1) ::close(m_fd); m_fd = -1; } int release() { int fd = m_fd; m_fd = -1; return fd; } }; int main(int argc, char** argv) { Gjs::AutoError error; int gjs_argc = argc; bool interactive_mode = false; setlocale(LC_ALL, ""); GjsAutoGOptionContext context = g_option_context_new(nullptr); g_option_context_set_ignore_unknown_options(context, true); g_option_context_set_help_enabled(context, false); Gjs::AutoStrv argv_copy_addr{g_strdupv(argv)}; char** argv_copy = argv_copy_addr; g_option_context_add_main_entries(context, entries, nullptr); 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 (int 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; } } Gjs::AutoStrv gjs_argv_addr{strndupv(gjs_argc, argv)}; char** gjs_argv = gjs_argv_addr; int script_argc = argc - gjs_argc; char* const* 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)) { Gjs::AutoChar 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; } Gjs::AutoChar program_path; gjs_argc = g_strv_length(gjs_argv); Gjs::AutoChar script; size_t len; const char* filename; const char* program_name; 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); Gjs::AutoUnref 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"); TraceFD tracefd; if (env_tracefd) { tracefd = g_ascii_strtoll(env_tracefd, nullptr, 10); g_setenv("GJS_TRACE_FD", "", true); if (tracefd) 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(); #ifdef HAVE_READLINE_READLINE_H Gjs::AutoChar repl_history_path = gjs_console_get_repl_history_path(); #else Gjs::AutoChar repl_history_path = nullptr; #endif Gjs::AutoUnref gjs_context{GJS_CONTEXT( g_object_new(GJS_TYPE_CONTEXT, // clang-format off "search-path", include_path.get(), "program-name", program_name, "program-path", program_path.get(), "profiler-enabled", enable_profiler, "exec-as-module", exec_as_module, "repl-history-path", repl_history_path.get(), // clang-format on nullptr))}; const char* env_coverage_output_path = g_getenv("GJS_COVERAGE_OUTPUT"); if (env_coverage_output_path != nullptr) { g_free(coverage_output_path); coverage_output_path = g_strdup(env_coverage_output_path); } Gjs::AutoUnref coverage; if (coverage_prefixes) { if (!coverage_output_path) g_error( "--coverage-output is required when taking coverage " "statistics"); Gjs::AutoUnref output{ g_file_new_for_commandline_arg(coverage_output_path)}; coverage = gjs_coverage_new(coverage_prefixes, gjs_context, output); } if (enable_profiler && profile_output_path) { GjsProfiler* profiler = gjs_context_get_profiler(gjs_context); gjs_profiler_set_filename(profiler, profile_output_path); } else if (enable_profiler && tracefd) { GjsProfiler* profiler = gjs_context_get_profiler(gjs_context); gjs_profiler_set_fd(profiler, tracefd.release()); } tracefd.close(); /* If we're debugging, set up the debugger. It will break on the first * frame. */ if (debugging) gjs_context_setup_debugger_console(gjs_context); int code = define_argv_and_eval_script(gjs_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-140.0/cjs/context-private.h0000664000175000017500000002601415167114161015313 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 #pragma once #include #include // for size_t #include #include #include // for hash #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/auto.h" #include "cjs/context.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-util-root.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: struct destroy_data_hash { size_t operator()(const std::pair& p) const { return std::hash()(reinterpret_cast(p.second)); } }; 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; char* m_repl_history_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; Gjs::AutoUnref m_memory_monitor; std::unordered_set, destroy_data_hash> 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 stop_draining_job_queue(); void warn_about_unhandled_promise_rejections(); GJS_JSAPI_RETURN_CONVENTION bool run_main_loop_hook(); [[nodiscard]] Gjs::GErrorResult<> handle_exit_code(bool no_sync_error_pending, const char* source_type, const char* identifier, uint8_t* exit_code); [[nodiscard]] bool auto_profile_enter(); void auto_profile_exit(bool auto_profile_is_on); 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*, GjsContext* public_context); ~GjsContextPrivate() override; // 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() const { return m_program_path; } GJS_USE const char* repl_history_path() const { return m_repl_history_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_repl_history_path(char* value) { m_repl_history_path = 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(); } void register_non_module_sourcemap(const char* script, const char* filename); [[nodiscard]] Gjs::GErrorResult<> eval(const char* script, size_t script_len, const char* filename, int* exit_status_p); GJS_JSAPI_RETURN_CONVENTION bool eval_with_scope(JS::HandleObject scope_object, const char* source, size_t source_len, const char* filename, JS::MutableHandleValue retval); [[nodiscard]] Gjs::GErrorResult<> eval_module(const char* identifier, uint8_t* exit_code_p); 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() { schedule_gc_internal(true); } void schedule_gc_if_needed(); 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 bool getHostDefinedData(JSContext*, JS::MutableHandleObject) const override; GJS_JSAPI_RETURN_CONVENTION bool enqueuePromiseJob(JSContext*, JS::HandleObject promise, JS::HandleObject job, JS::HandleObject allocation_site, JS::HandleObject incumbent_global) override; void runJobs(JSContext*) 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*) 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, void* data); void unregister_notifier(DestroyNotify, void* data); void async_closure_enqueue_for_gc(Gjs::Closure*); [[nodiscard]] Gjs::GErrorResult<> register_module(const char* identifier, const char* uri); static void trace(JSTracer*, void* data); void free_profiler(); void dispose(); }; std::string gjs_dumpstack_string(); namespace Gjs { class AutoMainRealm : public JSAutoRealm { public: explicit AutoMainRealm(GjsContextPrivate*); explicit AutoMainRealm(JSContext*); }; class AutoInternalRealm : public JSAutoRealm { public: explicit AutoInternalRealm(GjsContextPrivate*); explicit AutoInternalRealm(JSContext*); }; } // namespace Gjs cjs-140.0/cjs/context.cpp0000664000175000017500000017730415167114161014207 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 PRIu64 #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 #ifdef HAVE_READLINE_READLINE_H # include #endif #include #include // for u16string #include // for get_id #include #include #include // for move #include #include #include #include #include // for Call, JS_CallFunctionValue #include // for UndefinedHandleValue #include #include #include #include #include #include #include // for StealPendingExceptionStack #include // for JS_GC, JS_AddExtraGCRootsTr... #include // for WeakCache #include // for CurrentGlobalOrNull #include // for ExposeObjectToActiveJS #include #include #include // for JobQueue::SavedJobQueue #include #include // for JSPROP_PERMANENT, JSPROP_RE... #include #include #include #include #include // for JS_NewStringCopyZ #include #include #include #include // for DeletePolicy via WeakCache #include #include #include #include // for JS_GetFunctionObject, JS_Ge... #include // for ScriptEnvironmentPreparer #include #include // for UniquePtr::get #include "gi/function.h" #include "gi/info.h" #include "gi/object.h" #include "gi/private.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" // IWYU pragma: associated #include "cjs/context.h" #include "cjs/engine.h" #include "cjs/error-types.h" #include "cjs/gerror-result.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; } using Gjs::GErrorResult; using mozilla::Err, mozilla::Ok; static void gjs_context_dispose(GObject*); static void gjs_context_finalize(GObject*); static void gjs_context_constructed(GObject*); static void gjs_context_get_property(GObject*, unsigned prop_id, GValue*, GParamSpec*); static void gjs_context_set_property(GObject*, unsigned prop_id, const GValue*, GParamSpec*); 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* public_context) { g_return_val_if_fail(GJS_IS_CONTEXT(public_context), nullptr); return static_cast( gjs_context_get_instance_private(GJS_CONTEXT(public_context))); } GjsContextPrivate* GjsContextPrivate::from_object(GjsContext* public_context) { g_return_val_if_fail(GJS_IS_CONTEXT(public_context), nullptr); return static_cast( gjs_context_get_instance_private(public_context)); } GjsContextPrivate* GjsContextPrivate::from_current_context() { return from_object(gjs_context_get_current()); } enum : uint8_t { PROP_CONTEXT_0, PROP_PROGRAM_PATH, PROP_SEARCH_PATH, PROP_PROGRAM_NAME, PROP_PROFILER_ENABLED, PROP_PROFILER_SIGUSR2, PROP_EXEC_AS_MODULE, PROP_REPL_HISTORY_PATH }; static GMutex contexts_lock; static GList* all_contexts = nullptr; static Gjs::AutoChar 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() { static unsigned counter = 0; gjs_memory_report("signal handler", false); // dump to sequential files to allow easier comparisons Gjs::AutoChar 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 G_SOURCE_REMOVE; } 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() { 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* self) { gjs_log_init(); gjs_context_make_current(self); } static void gjs_context_class_init(GjsContextClass* klass) { GObjectClass* object_class = G_OBJECT_CLASS(klass); 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; GParamSpec* 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); /** * GjsContext:repl-history-path: * * Set this property to persist repl command history in the console or * debugger. If NULL, then command history will not be persisted. */ pspec = g_param_spec_string( "repl-history-path", "REPL History Path", "The writable path to persist repl history", nullptr, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_REPL_HISTORY_PATH, pspec); g_param_spec_unref(pspec); // For GjsPrivate if (!g_getenv("GJS_USE_UNINSTALLED_FILES")) { #ifdef G_OS_WIN32 extern HMODULE gjs_dll; Gjs::AutoChar basedir{ g_win32_get_package_installation_directory_of_module(gjs_dll)}; Gjs::AutoChar priv_typelib_dir{g_build_filename( basedir, "lib", "gjs", "girepository-1.0", nullptr)}; #else Gjs::AutoChar priv_typelib_dir{ g_build_filename(PKGLIBDIR, "girepository-1.0", nullptr)}; #endif GI::Repository repo; repo.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() { 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() { 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.insert({notify_func, data}); } void GjsContextPrivate::unregister_notifier(DestroyNotify notify_func, void* data) { auto target = std::make_pair(notify_func, data); m_destroy_notifications.erase(target); } void GjsContextPrivate::dispose() { 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() { g_clear_pointer(&m_search_path, g_strfreev); g_clear_pointer(&m_program_path, g_free); g_clear_pointer(&m_program_name, g_free); g_clear_pointer(&m_repl_history_path, g_free); } static void gjs_context_finalize(GObject* object) { if (gjs_context_get_current() == GJS_CONTEXT(object)) gjs_context_make_current(nullptr); 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* self = 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, self); g_mutex_lock(&contexts_lock); all_contexts = g_list_prepend(all_contexts, object); g_mutex_unlock(&contexts_lock); setup_dump_heap(); #ifdef HAVE_READLINE_READLINE_H const char* path = gjs_context_get_repl_history_path(self); // Populate command history from persisted file if (path) { int err = read_history(path); if (err != 0 && g_getenv("GJS_REPL_HISTORY")) g_warning("Could not read REPL history file %s: %s", path, g_strerror(err)); } #endif } 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, "internalLoader")) { gjs_log_exception(cx); g_error("Failed to load internal module loaders."); } load_context_module(cx, "resource:///org/cinnamon/cjs/modules/internal/loader.js", "module loader"); { 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* self, GMemoryMonitorWarningLevel level) { auto* cx = static_cast(gjs_context_get_native_context(self)); 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, unsigned 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; case PROP_REPL_HISTORY_PATH: g_value_set_string(value, gjs->repl_history_path()); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gjs_context_set_property(GObject* object, unsigned 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; case PROP_REPL_HISTORY_PATH: gjs->set_repl_history_path(g_value_dup_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } GjsContext* gjs_context_new() { return GJS_CONTEXT(g_object_new(GJS_TYPE_CONTEXT, nullptr)); } GjsContext* gjs_context_new_with_search_path(char** search_path) { return GJS_CONTEXT( g_object_new(GJS_TYPE_CONTEXT, "search-path", search_path, nullptr)); } 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() { // 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: 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 != nullptr) *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() { m_dispatcher.start(); } void GjsContextPrivate::stop_draining_job_queue() { m_draining_job_queue = false; m_dispatcher.stop(); } bool GjsContextPrivate::getHostDefinedData(JSContext* cx, JS::MutableHandleObject data) const { // This is equivalent to SpiderMonkey's behavior. data.set(JS::CurrentGlobalOrNull(cx)); return true; } // 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.empty()) { // 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() override { gjs_debug(GJS_DEBUG_CONTEXT, "Unpausing job queue"); g_assert(m_gjs->m_job_queue.empty() && "Current queue should be empty when restoring saved 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 %" PRIu64 " 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->cx() || trampoline->cx() == m_cx); m_async_closures.emplace_back(trampoline); } /** * gjs_context_maybe_gc: * @self: 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* self) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); gjs_maybe_gc(gjs->context()); } /** * gjs_context_gc: * @self: 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* self) { GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); 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() { g_mutex_lock(&contexts_lock); GList* result = g_list_copy(all_contexts); for (GList* iter = result; iter; iter = iter->next) g_object_ref(G_OBJECT(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* self) { g_return_val_if_fail(GJS_IS_CONTEXT(self), nullptr); GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); return gjs->context(); } static inline bool result_to_c(GErrorResult<> result, GError** error_out) { if (result.isOk()) return true; *error_out = result.unwrapErr().release(); return false; } bool gjs_context_eval(GjsContext* self, const char* script, ssize_t script_len, const char* filename, int* exit_status_p, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(self), false); size_t real_len = script_len < 0 ? strlen(script) : script_len; Gjs::AutoUnref self_ref{self, Gjs::TakeOwnership{}}; GjsContextPrivate* gjs = GjsContextPrivate::from_object(self_ref); gjs->register_non_module_sourcemap(script, filename); return result_to_c(gjs->eval(script, real_len, filename, exit_status_p), error); } bool gjs_context_eval_module(GjsContext* self, const char* identifier, uint8_t* exit_code, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(self), false); Gjs::AutoUnref self_ref{self, Gjs::TakeOwnership{}}; GjsContextPrivate* gjs = GjsContextPrivate::from_object(self_ref); return result_to_c(gjs->eval_module(identifier, exit_code), error); } bool gjs_context_register_module(GjsContext* self, const char* identifier, const char* uri, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(self), false); GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); return result_to_c(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_is_on) { if (auto_profile_is_on) gjs_profiler_stop(m_profiler); } GErrorResult<> GjsContextPrivate::handle_exit_code(bool no_sync_error_pending, const char* source_type, const char* identifier, uint8_t* exit_code) { 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 */ Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_SYSTEM_EXIT, "Exit with code %d", code); *exit_code = code; return Err(error.release()); // 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)) { Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); gjs_log_exception_uncaught(m_cx); *exit_code = 1; return Err(error.release()); } if (m_unhandled_exception) { Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "%s %s threw an exception", source_type, identifier); *exit_code = 1; return Err(error.release()); } // Assume success if no error was thrown and should exit isn't set if (no_sync_error_pending) { *exit_code = 0; return Ok{}; } g_critical("%s %s terminated with an uncatchable exception", source_type, identifier); Gjs::AutoError error; g_set_error(error.out(), 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 Err(error.release()); } 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); } // source maps parsing is built upon hooks for resolving or loading modules // for non-module runs we need to invoke this logic manually void GjsContextPrivate::register_non_module_sourcemap(const char* script, const char* filename) { using AutoURI = Gjs::AutoPointer; Gjs::AutoMainRealm ar{this}; JS::RootedObject global{m_cx, JS::CurrentGlobalOrNull(m_cx)}; JS::RootedValue v_loader{ m_cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER)}; g_assert(v_loader.isObject()); JS::RootedObject v_loader_obj{m_cx, &v_loader.toObject()}; JS::RootedValueArray<3> args{m_cx}; JS::RootedString script_str{m_cx, JS_NewStringCopyZ(m_cx, script)}; JS::RootedString file_name_str{m_cx, JS_NewStringCopyZ(m_cx, filename)}; args[0].setString(script_str); args[1].setString(file_name_str); // if the file uri is not an absolute uri with a scheme, build one assuming // file:// this parameter is used to help locate non-inlined source map file AutoURI uri = g_uri_parse(filename, G_URI_FLAGS_NONE, nullptr); if (!uri) { Gjs::AutoUnref file = g_file_new_for_path(filename); Gjs::AutoChar file_uri = g_file_get_uri(file); JS::RootedString abs_filename_scheme_str{ m_cx, JS_NewStringCopyZ(m_cx, file_uri.get())}; args[2].setString(abs_filename_scheme_str); } JS::RootedValue ignored{m_cx}; JS::Call(m_cx, v_loader_obj, "populateSourceMap", args, &ignored); } GErrorResult<> GjsContextPrivate::eval(const char* script, size_t script_len, const char* filename, int* exit_status_p) { 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; GErrorResult<> result = handle_exit_code(ok, "Script", filename, &out_code); if (exit_status_p) { if (result.isOk() && 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 result; } GErrorResult<> GjsContextPrivate::eval_module(const char* identifier, uint8_t* exit_code_p) { 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) { Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Cannot load module with identifier: '%s'", identifier); if (exit_code_p) *exit_code_p = 1; return Err(error.release()); } if (!JS::ModuleLink(m_cx, obj)) { gjs_log_exception(m_cx); Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Failed to resolve imports for module: '%s'", identifier); if (exit_code_p) *exit_code_p = 1; return Err(error.release()); } 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; GErrorResult<> result = handle_exit_code(ok, "Module", identifier, &out_code); if (exit_code_p) *exit_code_p = out_code; return result; } GErrorResult<> GjsContextPrivate::register_module(const char* identifier, const char* uri) { Gjs::AutoMainRealm ar{this}; if (gjs_module_load(m_cx, identifier, uri)) return Ok{}; 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); } Gjs::AutoError error; g_set_error(error.out(), GJS_ERROR, GJS_ERROR_FAILED, "Failed to parse module '%s': %s", identifier, msg ? msg : "unknown"); return Err(error.release()); } bool gjs_context_eval_file(GjsContext* self, const char* filename, int* exit_status_p, GError** error) { Gjs::AutoChar script; size_t script_len; Gjs::AutoUnref 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(self, script, script_len, filename, exit_status_p, error); } bool gjs_context_eval_module_file(GjsContext* self, const char* filename, uint8_t* exit_status_p, GError** error) { Gjs::AutoUnref file{g_file_new_for_commandline_arg(filename)}; Gjs::AutoChar uri{g_file_get_uri(file)}; return gjs_context_register_module(self, uri, uri, error) && gjs_context_eval_module(self, 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::EnvironmentChain scope_chain{m_cx, JS::SupportUnscopables::No}; if (!scope_chain.append(eval_obj)) { JS_ReportOutOfMemory(m_cx); return false; } JS::CompileOptions options(m_cx); options.setFileAndLine(filename, 1).setNonSyntacticScope(true); Gjs::AutoUnref file{g_file_new_for_commandline_arg(filename)}; Gjs::AutoChar 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* self, const char* array_name, ssize_t array_length, const char** array_values, GError** error) { g_return_val_if_fail(GJS_IS_CONTEXT(self), false); GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); 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* self, ssize_t array_length, const char** array_values) { g_return_if_fail(GJS_IS_CONTEXT(self)); GjsContextPrivate* gjs = GjsContextPrivate::from_object(self); std::vector args(array_values, array_values + array_length); gjs->set_args(std::move(args)); } static GjsContext* current_context; GjsContext* gjs_context_get_current() { return current_context; } void gjs_context_make_current(GjsContext* self) { g_assert(self == nullptr || current_context == nullptr); current_context = self; } 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() { return JS_GetImplementationVersion(); } /** * gjs_context_get_repl_history_path: * * Returns the path property for persisting REPL command history * * Returns: a string */ const char* gjs_context_get_repl_history_path(GjsContext* self) { return GjsContextPrivate::from_object(self)->repl_history_path(); } /** * 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-140.0/cjs/context.h0000664000175000017500000000710015167114161013636 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 ()) /* clang-format off */ GJS_EXPORT GJS_USE G_DECLARE_FINAL_TYPE(GjsContext, gjs_context, GJS, CONTEXT, GObject); /* clang-format on */ /* 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* self, const char* filename, int* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval_module_file(GjsContext* self, const char* filename, uint8_t* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval(GjsContext* self, const char* script, ssize_t script_len, const char* filename, int* exit_status_p, GError** error); GJS_EXPORT GJS_USE bool gjs_context_register_module(GjsContext* self, const char* identifier, const char* uri, GError** error); GJS_EXPORT GJS_USE bool gjs_context_eval_module(GjsContext* self, const char* identifier, uint8_t* exit_code, GError** error); GJS_EXPORT GJS_USE bool gjs_context_define_string_array(GjsContext* self, const char* array_name, ssize_t array_length, const char** array_values, GError** error); GJS_EXPORT void gjs_context_set_argv(GjsContext* self, 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* self); GJS_EXPORT void* gjs_context_get_native_context(GjsContext* self); GJS_EXPORT void gjs_context_run_in_realm(GjsContext* self, GjsContextInRealmFunc func, void* user_data); GJS_EXPORT void gjs_context_print_stack_stderr(GjsContext* self); GJS_EXPORT void gjs_context_maybe_gc(GjsContext* self); GJS_EXPORT void gjs_context_gc(GjsContext* self); 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* self); GJS_EXPORT GJS_USE const char* gjs_context_get_repl_history_path(GjsContext* self); G_END_DECLS #endif /* GJS_CONTEXT_H_ */ cjs-140.0/cjs/coverage.cpp0000664000175000017500000004160215167114161014305 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 #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 #include #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" #include "cjs/coverage.h" #include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" using Gjs::GErrorResult; using mozilla::Err, mozilla::Ok; static bool s_coverage_enabled = false; struct _GjsCoverage { GObject parent; }; struct GjsCoveragePrivate { char** prefixes; GjsContext* coverage_context; JS::Heap global; GFile* output_dir; }; G_DEFINE_TYPE_WITH_PRIVATE(GjsCoverage, gjs_coverage, G_TYPE_OBJECT) enum : uint8_t { PROP_COVERAGE_0, PROP_PREFIXES, PROP_CONTEXT, PROP_CACHE, PROP_OUTPUT_DIRECTORY, PROP_N }; static GParamSpec* properties[PROP_N]; [[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 GErrorResult<> write_source_file_header(GOutputStream* stream, GFile* source_file) { Gjs::AutoChar path{get_file_identifier(source_file)}; Gjs::AutoError error; if (!g_output_stream_printf(stream, nullptr, nullptr, error.out(), "SF:%s\n", path.get())) return Err(error.release()); return Ok{}; } [[nodiscard]] static GErrorResult<> copy_source_file_to_coverage_output( GFile* source_file, GFile* destination_file) { /* We need to recursively make the directory we want to copy to, as * g_file_copy doesn't do that */ Gjs::AutoError error; Gjs::AutoUnref destination_dir{g_file_get_parent(destination_file)}; if (!g_file_make_directory_with_parents(destination_dir, nullptr, error.out())) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_EXISTS)) return Err(error.release()); error.reset(); } if (!g_file_copy(source_file, destination_file, G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, error.out())) return Err(error.release()); return Ok{}; } // This function will strip a URI scheme and return the string with the URI // scheme stripped or nullptr 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) { size_t 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 nullptr; } /* 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) { Gjs::AutoUnref ancestor{parent, Gjs::TakeOwnership{}}; 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.) */ Gjs::AutoUnref root{g_file_new_for_path("/")}; char* child_path = g_file_get_relative_path(root, child); if (child_path) return child_path; Gjs::AutoChar 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)); Gjs::AutoChar workdir{g_get_current_dir()}; Gjs::AutoChar abs_filename{g_canonicalize_filename(filename, workdir)}; for (const char* const* prefix = priv->prefixes; *prefix; prefix++) { Gjs::AutoChar abs_prefix{g_canonicalize_filename(*prefix, workdir)}; if (g_str_has_prefix(abs_filename, abs_prefix)) return true; } return false; } [[nodiscard]] static inline GErrorResult<> write_line(GOutputStream* out, const char* line) { Gjs::AutoError error; if (!g_output_stream_printf(out, nullptr, nullptr, error.out(), "%s\n", line)) return Err(error.release()); return Ok{}; } [[nodiscard]] GErrorResult> write_statistics_internal(GjsCoverage* self, JSContext* cx) { 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 Gjs::AutoUnref{}; } auto* priv = static_cast( gjs_coverage_get_instance_private(self)); // Create output directory if it doesn't exist Gjs::AutoError error; if (!g_file_make_directory_with_parents(priv->output_dir, nullptr, error.out())) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_EXISTS)) return Err(error.release()); error.reset(); } GFile* output_file = g_file_get_child(priv->output_dir, "coverage.lcov"); size_t lcov_length; JS::UniqueChars lcov = js::GetCodeCoverageSummary(cx, &lcov_length); Gjs::AutoUnref ostream{G_OUTPUT_STREAM(g_file_append_to( output_file, G_FILE_CREATE_NONE, nullptr, error.out()))}; if (!ostream) return Err(error.release()); Gjs::AutoStrv lcov_lines{g_strsplit(lcov.get(), "\n", -1)}; const char* test_name = nullptr; 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; } if (g_str_has_prefix(*iter, "SF:")) { const char* filename = *iter + 3; if (!filename_has_coverage_prefixes(self, filename)) { ignoring_file = true; continue; } // Now we can write the test name before writing the source file MOZ_TRY(write_line(ostream, test_name)); /* The source file could be a resource, so we must use * g_file_new_for_commandline_arg() to disambiguate between URIs and * filesystem paths. */ Gjs::AutoUnref source_file{ g_file_new_for_commandline_arg(filename)}; Gjs::AutoChar diverged_paths{ find_diverging_child_components(source_file, priv->output_dir)}; Gjs::AutoUnref destination_file{ g_file_resolve_relative_path(priv->output_dir, diverged_paths)}; MOZ_TRY(copy_source_file_to_coverage_output(source_file, destination_file)); /* Rewrite the source file path to be relative to the output dir so * that genhtml will find it */ MOZ_TRY(write_source_file_header(ostream, destination_file)); continue; } MOZ_TRY(write_line(ostream, *iter)); } return Gjs::AutoUnref{output_file}; } /** * gjs_coverage_write_statistics: * @self: 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* self) { auto* priv = static_cast( gjs_coverage_get_instance_private(self)); auto* cx = static_cast( gjs_context_get_native_context(priv->coverage_context)); Gjs::AutoMainRealm ar{cx}; GErrorResult> result{ write_statistics_internal(self, cx)}; if (result.isErr()) { g_critical("Error writing coverage data: %s", result.inspectErr()->message); return; } Gjs::AutoChar output_file_path{g_file_get_path(result.unwrap())}; 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* self = GJS_COVERAGE(data); auto* priv = static_cast( gjs_coverage_get_instance_private(self)); JS::TraceEdge(trc, &priv->global, "Coverage global object"); } GJS_JSAPI_RETURN_CONVENTION static bool bootstrap_coverage(GjsCoverage* self) { auto* priv = static_cast( gjs_coverage_get_instance_private(self)); auto* gjs = GjsContextPrivate::from_object(priv->coverage_context); JSContext* cx = gjs->context(); JS::RootedObject debugger_global{ cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER)}; { JSAutoRealm ar{cx, debugger_global}; JS::RootedObject debuggee{cx, gjs->global()}; if (!JS_WrapObject(cx, &debuggee)) return false; JS::RootedValue v_debuggee{cx, JS::ObjectValue(*debuggee)}; if (!JS_SetPropertyById(cx, debugger_global, gjs->atoms().debuggee(), v_debuggee) || !gjs_define_global_properties(cx, debugger_global, GjsGlobalType::DEBUGGER, "GJS coverage", "coverage")) return false; // Add a tracer, as suggested by jdm on #jsapi JS_AddExtraGCRootsTracer(cx, coverage_tracer, self); priv->global = debugger_global; } return true; } static void gjs_coverage_constructed(GObject* object) { G_OBJECT_CLASS(gjs_coverage_parent_class)->constructed(object); GjsCoverage* self = GJS_COVERAGE(object); auto* priv = static_cast( gjs_coverage_get_instance_private(self)); new (&priv->global) JS::Heap(); if (!bootstrap_coverage(self)) { JSContext* cx = static_cast( gjs_context_get_native_context(priv->coverage_context)); Gjs::AutoMainRealm ar{cx}; gjs_log_exception(cx); } } static void gjs_coverage_set_property(GObject* object, unsigned prop_id, const GValue* value, GParamSpec* pspec) { GjsCoverage* self = GJS_COVERAGE(object); auto* priv = static_cast( gjs_coverage_get_instance_private(self)); switch (prop_id) { case PROP_PREFIXES: g_assert(priv->prefixes == nullptr); priv->prefixes = static_cast(g_value_dup_boxed(value)); break; case PROP_CONTEXT: priv->coverage_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* self = GJS_COVERAGE(object); auto* priv = static_cast( gjs_coverage_get_instance_private(self)); /* Decomission objects inside of the JSContext before disposing of the * context */ auto* cx = static_cast( gjs_context_get_native_context(priv->coverage_context)); JS_RemoveExtraGCRootsTracer(cx, coverage_tracer, self); priv->global = nullptr; g_clear_object(&priv->coverage_context); G_OBJECT_CLASS(gjs_coverage_parent_class)->dispose(object); } static void gjs_coverage_finalize(GObject* object) { GjsCoverage* self = GJS_COVERAGE(object); auto* priv = static_cast( gjs_coverage_get_instance_private(self)); 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 = G_OBJECT_CLASS(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 * @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 @coverage_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* coverage_context, GFile* output_dir) { return GJS_COVERAGE(g_object_new(GJS_TYPE_COVERAGE, "prefixes", prefixes, "context", coverage_context, "output-directory", output_dir, nullptr)); } /** * 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-140.0/cjs/coverage.h0000664000175000017500000000201015167114161013740 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-140.0/cjs/debugger.cpp0000664000175000017500000001143115167114161014273 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 // for ReportUncatchableException #include #include #include #include #include #include #include // for UniqueChars #include #include // for JS_WrapObject #include "cjs/atoms.h" #include "cjs/auto.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 "cjs/module.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); JS::ReportUncatchableException(cx); return false; } 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; Gjs::AutoChar 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 GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); add_history(line); gjs_console_write_repl_history(gjs->repl_history_path()); #endif args.rval().setString(JS_NewStringCopyZ(cx, line)); return true; } static bool get_source_map_registry(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); JS::RootedObject registry{cx, gjs_get_source_map_registry(gjs->global())}; if (!JS_WrapObject(cx, ®istry)) { gjs_log_exception(cx); return false; } args.rval().setObject(*registry); return true; } static JSFunctionSpec debugger_funcs[] = { JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS), JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS), JS_FN("getSourceMapRegistry", get_source_map_registry, 0, GJS_MODULE_PROP_FLAGS), JS_FS_END}; 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-140.0/cjs/deprecation.cpp0000664000175000017500000001343715167114161015014 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."), // Renamed: ("{} has been renamed. 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 ? a_loc : "unknown") {} 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 /* = 1 */) { 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 /* = 1 */) { 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 message_formatted = message.str(); warn_deprecated_unsafe_internal(cx, id, message_formatted.c_str(), max_frames); } cjs-140.0/cjs/deprecation.h0000664000175000017500000000164515167114161014457 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 #pragma once #include #include #include #include struct JSContext; enum GjsDeprecationMessageId : uint8_t { None, ByteArrayInstanceToString, DeprecatedGObjectProperty, ModuleExportedLetOrConst, PlatformSpecificTypelib, Renamed, LastValue, // insert new elements before this one }; void gjs_warn_deprecated_once_per_callsite(JSContext*, GjsDeprecationMessageId, unsigned max_frames = 1); void gjs_warn_deprecated_once_per_callsite(JSContext*, GjsDeprecationMessageId, const std::vector& args, unsigned max_frames = 1); cjs-140.0/cjs/engine.cpp0000664000175000017500000002135215167114161013757 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 SetDOMCallbacks, DOMCallbacks #include #ifndef G_DISABLE_ASSERT # include // for Atomic::operator== #endif #include "gi/gerror.h" #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/profiler-private.h" #include "util/log.h" 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) { Gjs::AutoError 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) override { // 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: { } } 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) { return first == &the_principals || second != &the_principals; } 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, /* codeForEvalGets = */ nullptr, &ModuleLoaderPrincipals::subsumes, }; static bool instance_class_is_error(const JSClass* klass) { return klass == &ErrorBase::klass; } static const js::DOMCallbacks dom_callbacks = { /* instanceClassHasProtoAtDepth = */ nullptr, &instance_class_is_error, }; JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) { g_assert(gjs_is_inited); JSContext* cx = JS_NewContext(/* max bytes = */ 32 * 1024 * 1024); 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, 1024UL * 1024UL); JS_SetGCParameter(cx, JSGC_MAX_BYTES, -1); JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, 1); JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10); // 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); js::SetDOMCallbacks(cx, &dom_callbacks); // 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-140.0/cjs/engine.h0000664000175000017500000000107515167114161013424 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 #pragma once #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*, const char* filename, char** src, size_t* length); JSPrincipals* get_internal_principals(); cjs-140.0/cjs/enum-utils.h0000664000175000017500000000624115167114161014261 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-140.0/cjs/error-types.cpp0000664000175000017500000000233215167114161015002 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() { 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-140.0/cjs/error-types.h0000664000175000017500000000215615167114161014453 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 GjsError { 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 GjsJSError { 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-140.0/cjs/gerror-result.h0000664000175000017500000000567215167114161015002 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018-2020 Canonical, Ltd #pragma once #include #include #include #include // IWYU pragma: keep (see Result::Impl) #include "cjs/auto.h" namespace mozilla::detail { template class ResultImplementation; } // Auto pointer type for GError, as well as a Result type that can be used as a // type-safe return type for fallible GNOME-platform operations. // To indicate success, return Ok{}, and to indicate failure, return Err(error) // or Err(error.release()) if using AutoError. // When calling a function that returns GErrorResult inside another function // that returns GErrorResult, you can use the MOZ_TRY() macro as if it were the // Rust ? operator, to bail out on error even if the GErrorResult's success // types are different. // COMPAT: We use Mozilla's Result type because std::expected does not appear in // the standard library until C++23. namespace Gjs { struct AutoError : AutoPointer { 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(); } }; template <> struct SmartPointer : AutoError { using AutoError::AutoError; using AutoError::operator=; using AutoError::operator&; }; template using GErrorResult = mozilla::Result; } // namespace Gjs namespace mozilla::detail { // Custom packing for GErrorResult<> template <> class SelectResultImpl { public: class Type { Gjs::AutoError m_value; public: explicit constexpr Type(Ok) : m_value() {} explicit constexpr Type(GError* error) : m_value(error) {} constexpr Type(Type&& other) : m_value(other.m_value.release()) {} Type& operator=(Type&& other) { m_value = other.m_value.release(); return *this; } [[nodiscard]] constexpr bool isOk() const { return !m_value; } [[nodiscard]] static constexpr Ok inspect() { return {}; } [[nodiscard]] static constexpr Ok unwrap() { return {}; } [[nodiscard]] constexpr const GError* inspectErr() const { return m_value.get(); } [[nodiscard]] Gjs::AutoError unwrapErr() { return m_value.release(); } }; }; // Packing for any other pointer. Unlike in SpiderMonkey, GLib-allocated // pointers may not be aligned, so their bottom bit cannot be used for a flag. template class SelectResultImpl { public: using Type = ResultImplementation; }; } // namespace mozilla::detail cjs-140.0/cjs/gjs.h0000664000175000017500000000065215167114161012742 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-140.0/cjs/global.cpp0000664000175000017500000005245715167114161013764 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/auto.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, const 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) { Gjs::AutoChar 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 args = 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(args[0].isString()); JS::RootedString str{m_cx, args[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; } args.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::RootedObject source_map_registry{cx, JS::NewMapObject(cx)}; if (!source_map_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY, JS::ObjectValue(*source_map_registry)); JS::Value v_importer = gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS); g_assert(v_importer.isObject() && "importer should be defined before passing null importer to " "GjsGlobal::define_properties"); 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("getSourceMapRegistry", gjs_internal_get_source_map_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_FN("atob", gjs_internal_atob, 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)); JS::RootedObject source_map_registry{cx, JS::NewMapObject(cx)}; if (!source_map_registry) return false; gjs_set_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY, JS::ObjectValue(*source_map_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: * @cx: the current #JSContext * @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) { JSObject* 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: * @cx: the current #JSContext * @registry: a JS Map object * @key: a module identifier, typically a string or symbol * @module: a module object * * 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. * * Returns: false if an exception is pending, otherwise true. */ 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: * @cx: the current #JSContext * @registry: a JS Map object * @key: a module identifier, typically a string or symbol * @module_out: (out): handle where a module object will be stored * * 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. * * Returns: false if an exception is pending, otherwise true. */ 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_global_source_map_get: * @cx: the current #JSContext * @registry: a JS Map object * @key: a source string, such as retrieved from a stack frame * @source_map_consumer_obj: handle where a source map consumer object will be * stored * * This function retrieves a source map consumer from the source map registry, * or null if the source does not have a source map consumer. * * Returns: false if an exception is pending, otherwise true. */ bool gjs_global_source_map_get( JSContext* cx, JS::HandleObject registry, JS::HandleString key, JS::MutableHandleObject source_map_consumer_obj) { JS::RootedValue v_key{cx, JS::StringValue(key)}; JS::RootedValue v_value{cx}; if (!JS::MapGet(cx, registry, v_key, &v_value)) return false; g_assert((v_value.isUndefined() || v_value.isObject()) && "Invalid value in source map registry"); if (v_value.isObject()) { source_map_consumer_obj.set(&v_value.toObject()); return true; } source_map_consumer_obj.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 * nullptr 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); } cjs-140.0/cjs/global.h0000664000175000017500000001004215167114161013411 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 #pragma once #include #include #include #include // for Handle #include #include #include "cjs/macros.h" namespace JS { struct PropertyKey; } enum class GjsGlobalType : uint8_t { 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, // Stores the source map registry (a Map object) SOURCE_MAP_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*, GjsGlobalType); GjsGlobalType gjs_global_get_type(JSContext*); GjsGlobalType gjs_global_get_type(JSObject* global); GJS_JSAPI_RETURN_CONVENTION bool gjs_global_registry_set(JSContext*, JS::HandleObject registry, JS::PropertyKey, JS::HandleObject value); GJS_JSAPI_RETURN_CONVENTION bool gjs_global_registry_get(JSContext*, JS::HandleObject registry, JS::PropertyKey, JS::MutableHandleObject value); GJS_JSAPI_RETURN_CONVENTION bool gjs_global_source_map_get(JSContext*, JS::HandleObject registry, JS::HandleString key, JS::MutableHandleObject value); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_global_object(JSContext*, GjsGlobalType, JS::HandleObject existing_global = nullptr); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_global_properties(JSContext*, JS::HandleObject global, GjsGlobalType, const char* realm_name, const char* bootstrap_script); namespace detail { void set_global_slot(JSObject* global, uint32_t slot, JS::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)); } cjs-140.0/cjs/importer.cpp0000664000175000017500000007775315167114161014373 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 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 // 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 #ifndef G_DISABLE_ASSERT # include // for JS_IsExceptionPending #endif #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/gerror-result.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); Gjs::AutoChar 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* cx, JS::HandleObject module_obj, const char* parse_name, const char* module_name, JS::HandleObject parent) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); /* 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 */ bool parent_is_module = parent && JS_InstanceOf(cx, 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{cx}; if (!gjs_string_from_utf8(cx, parse_name, &file) || !JS_DefinePropertyById(cx, 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{cx, JS::NullValue()}; JS::RootedValue parent_module_val{cx, JS::NullValue()}; JS::RootedValue module_path{cx, JS::NullValue()}; JS::RootedValue to_string_tag{cx}; if (parent_is_module) { if (!gjs_string_from_utf8(cx, module_name, &module_name_val)) return false; parent_module_val.setObject(*parent); JS::RootedValue parent_module_path{cx}; if (!JS_GetPropertyById(cx, parent, atoms.module_path(), &parent_module_path)) return false; Gjs::AutoChar module_path_buf; if (parent_module_path.isNull()) { module_path_buf = g_strdup(module_name); } else { JS::UniqueChars parent_path = gjs_string_to_utf8(cx, 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(cx, module_path_buf, &module_path)) return false; Gjs::AutoChar to_string_tag_buf{ g_strdup_printf("GjsModule %s", module_path_buf.get())}; if (!gjs_string_from_utf8(cx, to_string_tag_buf, &to_string_tag)) return false; } else { to_string_tag.setString(JS_AtomizeString(cx, "GjsModule")); } if (!JS_DefinePropertyById(cx, module_obj, atoms.module_name(), module_name_val, attrs) || !JS_DefinePropertyById(cx, module_obj, atoms.parent_module(), parent_module_val, attrs) || !JS_DefinePropertyById(cx, module_obj, atoms.module_path(), module_path, attrs)) return false; JS::RootedId to_string_tag_name{ cx, JS::PropertyKey::Symbol( JS::GetWellKnownSymbol(cx, JS::SymbolCode::toStringTag))}; return JS_DefinePropertyById(cx, module_obj, to_string_tag_name, to_string_tag, attrs); } GJS_JSAPI_RETURN_CONVENTION static bool import_directory(JSContext* cx, 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(cx, 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; } /* Clear a cached module so it can be re-imported. * This allows extension/applet reloading without restarting. */ GJS_JSAPI_RETURN_CONVENTION static bool importer_clear_cache(JSContext *cx, unsigned argc, JS::Value *vp) { GJS_GET_THIS(cx, argc, vp, args, importer); if (args.length() < 1 || !args[0].isString()) { gjs_throw(cx, "clearCache requires a string argument"); return false; } JS::RootedString name_str(cx, args[0].toString()); JS::UniqueChars name(JS_EncodeStringToUTF8(cx, name_str)); if (!name) return false; // Check if the property exists bool has_prop; if (!JS_HasOwnProperty(cx, importer, name.get(), &has_prop)) return false; if (!has_prop) { args.rval().setBoolean(false); return true; } gjs_debug(GJS_DEBUG_IMPORTER, "Clearing cached import '%s'", name.get()); // Delete the cached module property JS::ObjectOpResult result; if (!JS_DeleteProperty(cx, importer, name.get(), result)) return false; args.rval().setBoolean(result.succeed()); 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* cx, JS::HandleObject obj, const char* name) { gjs_debug(GJS_DEBUG_IMPORTER, "Cleaning up from failed import of '%s'", name); if (!JS_DeleteProperty(cx, 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* cx, GFile* file, JS::HandleObject module_obj) { gsize script_len = 0; // Do not use size_t, may be different width Gjs::AutoError error; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); JS::RootedValue ignored{cx}; Gjs::AutoChar 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(cx, error); return false; } return true; } g_assert(script); Gjs::AutoChar 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(); Gjs::AutoChar 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; Gjs::AutoUnref 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; Gjs::AutoChar full_path{g_file_get_parse_name(file)}; if (!define_meta_properties(cx, module_obj, full_path, module_name, obj)) return false; // Only seal imports on the root importer (where parent is null). // Sub-importers (like xlet importers) remain unsealed so their modules // can be cleared from cache and re-imported for xlet reloading. const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue parent(cx); if (!JS_GetPropertyById(cx, obj, atoms.parent_module(), &parent)) return false; if (parent.isNull()) { if (!seal_import(cx, obj, module_id, module_name)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool import_file_on_module(JSContext* cx, JS::HandleObject obj, JS::HandleId id, const char* name, GFile* file) { if (!attempt_import(cx, obj, id, name, file)) { cancel_import(cx, obj, name); return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool do_import(JSContext* cx, JS::HandleObject obj, JS::HandleId id) { JS::RootedObject search_path{cx}; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, obj, "importer", atoms.search_path(), &search_path)) return false; bool is_array; if (!JS::IsArrayObject(cx, search_path, &is_array)) return false; if (!is_array) { gjs_throw(cx, "searchPath property on importer is not an array"); return false; } uint32_t search_path_len; if (!JS::GetArrayLength(cx, search_path, &search_path_len)) { gjs_throw(cx, "searchPath array has no length"); return false; } JS::UniqueChars name; if (!gjs_get_string_id(cx, id, &name)) return false; if (!name) { gjs_throw(cx, "Importing invalid module name"); return false; } // null if this is the root importer JS::RootedValue parent{cx}; if (!JS_GetPropertyById(cx, 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(cx, obj, name.get())) return false; gjs_debug(GJS_DEBUG_IMPORTER, "successfully imported module '%s'", name.get()); return true; } Gjs::AutoChar filename{g_strdup_printf("%s.js", name.get())}; std::vector directories; JS::RootedValue elem{cx}; JS::RootedString str{cx}; for (uint32_t i = 0; i < search_path_len; ++i) { elem.setUndefined(); if (!JS_GetElement(cx, 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(cx, "importer searchPath contains non-string"); return false; } str = elem.toString(); JS::UniqueChars dirname{JS_EncodeStringToUTF8(cx, str)}; if (!dirname) return false; // Ignore empty path elements if (dirname[0] == '\0') continue; Gjs::AutoUnref 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(cx, obj, directory, name.get(), &found)) return false; if (found) return true; // Second try importing a directory (a sub-importer) Gjs::AutoUnref file{g_file_get_child(directory, name.get())}; if (g_file_query_file_type(file, GFileQueryInfoFlags(0), nullptr) == G_FILE_TYPE_DIRECTORY) { Gjs::AutoChar 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.emplace_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()); bool exists = g_file_query_exists(file, nullptr); if (!exists) { Gjs::AutoChar 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(cx, 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(cx, 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(cx)); gjs_throw_custom(cx, 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* cx, JS::HandleObject object, JS::MutableHandleIdVector properties, bool enumerable_only [[maybe_unused]]) { bool is_array; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject search_path{cx}; if (!gjs_object_require_property(cx, object, "importer", atoms.search_path(), &search_path)) return false; if (!JS::IsArrayObject(cx, search_path, &is_array)) return false; if (!is_array) { gjs_throw(cx, "searchPath property on importer is not an array"); return false; } uint32_t search_path_len; if (!JS::GetArrayLength(cx, search_path, &search_path_len)) { gjs_throw(cx, "searchPath array has no length"); return false; } JS::RootedValue elem{cx}; JS::RootedString str{cx}; for (uint32_t i = 0; i < search_path_len; ++i) { elem.setUndefined(); if (!JS_GetElement(cx, 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(cx, "importer searchPath contains non-string"); return false; } str = elem.toString(); JS::UniqueChars dirname{JS_EncodeStringToUTF8(cx, str)}; if (!dirname) return false; Gjs::AutoUnref directory{ g_file_new_for_commandline_arg(dirname.get())}; Gjs::AutoUnref file{ g_file_get_child(directory, MODULE_INIT_FILENAME)}; if (!load_module_elements(cx, object, properties, file)) return false; // new_for_commandline_arg handles resource:/// paths Gjs::AutoUnref 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, nullptr, nullptr)) break; if (info == nullptr || file == nullptr) break; Gjs::AutoChar 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(cx, filename); if (id.isVoid()) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(cx); return false; } } else if (g_str_has_suffix(filename, ".js")) { Gjs::AutoChar filename_noext{ g_strndup(filename, strlen(filename) - 3)}; jsid id = gjs_intern_string_to_id(cx, filename_noext); if (id.isVoid()) return false; if (!properties.append(id)) { JS_ReportOutOfMemory(cx); 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* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { if (!id.isString()) { *resolved = false; return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (id == atoms.module_init() || id == atoms.to_string() || id == atoms.value_of() || id == atoms.clear_cache()) { *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(cx, 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_FN("clearCache", importer_clear_cache, 1, 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) { // in order of priority // $GJS_PATH const char* envstr = g_getenv("GJS_PATH"); if (envstr) { // we assume the array and strings are allocated separately Gjs::AutoFree dirs{ g_strsplit(envstr, G_SEARCHPATH_SEPARATOR_S, 0)}; for (char** d = dirs; *d != nullptr; d++) gjs_search_path.emplace_back(*d); } gjs_search_path.emplace_back( "resource:///org/cinnamon/cjs/modules/script/"); gjs_search_path.emplace_back("resource:///org/cinnamon/cjs/modules/core/"); // $XDG_DATA_DIRS /gjs-1.0 const char* const* system_data_dirs = g_get_system_data_dirs(); for (size_t i = 0; system_data_dirs[i] != nullptr; ++i) { Gjs::AutoChar s{ g_build_filename(system_data_dirs[i], "gjs-1.0", nullptr)}; gjs_search_path.emplace_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); Gjs::AutoChar gjs_data_dir{ g_build_filename(basedir, "share", "gjs-1.0", nullptr)}; gjs_search_path.emplace_back(gjs_data_dir.get()); g_free(basedir); #else gjs_search_path.emplace_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* cx, 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{cx, gjs_importer_define_proto(cx)}; if (!proto) return nullptr; JS::RootedObject importer{ cx, JS_NewObjectWithGivenProto(cx, &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( cx, importer, "searchPath", search_paths, // settable (no READONLY) but not deletable (PERMANENT) JSPROP_PERMANENT | JSPROP_RESOLVING)) return nullptr; if (!define_meta_properties(cx, importer, nullptr, importer_name, in_object)) return nullptr; return importer; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_define_importer( JSContext* cx, JS::HandleObject in_object, const char* importer_name, const std::vector& initial_search_path, bool add_standard_search_path) { JS::RootedObject importer{ cx, gjs_create_importer(cx, importer_name, initial_search_path, add_standard_search_path, in_object)}; if (!JS_DefineProperty(cx, 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-140.0/cjs/importer.h0000664000175000017500000000112015167114161014007 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 #pragma once #include #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_root_importer(JSContext*, const std::vector& search_path); GJS_JSAPI_RETURN_CONVENTION bool gjs_import_native_module(JSContext*, JS::HandleObject importer, const char* id_str); cjs-140.0/cjs/internal.cpp0000664000175000017500000005345715167114161014341 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 // for JSEXN_ERR #include #include // for PropertyKey #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/auto.h" #include "cjs/context-private.h" #include "cjs/engine.h" #include "cjs/gerror-result.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: * @cx: the current JSContext * @identifier: the identifier of the internal module * * 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. * * Returns: whether an error occurred while loading or evaluating the module. */ bool gjs_load_internal_module(JSContext* cx, const char* identifier) { Gjs::AutoChar 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()); Gjs::AutoChar 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}; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); JS::RootedObject internal_global{cx, gjs->internal_global()}; JS::RootedObject module{cx, JS::CompileModule(cx, options, buf)}; if (!module) return false; JS::RootedObject registry{cx, gjs_get_module_registry(internal_global)}; JS::RootedId key{cx, gjs_intern_string_to_id(cx, full_path)}; if (key.isVoid()) return false; JS::RootedValue ignore{cx}; return gjs_global_registry_set(cx, registry, key, module) && JS::ModuleLink(cx, module) && JS::ModuleEvaluate(cx, module, &ignore); } 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: * @global: the JS global object * @loader: the JS module loader object to store in @global * * JS function exposed as `setGlobalModuleLoader` in the internal global scope. * * Sets the MODULE_LOADER slot of @global. @loader should be an instance of * ModuleLoader or InternalModuleLoader. Its `moduleResolveHook` and * `moduleLoadHook` properties will be called. * * Returns: JS undefined */ 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: * @cx: the current JSContext * @uri: The URI of the module * @source: The source text of the module * @v_module_out: (out): Return location for the module as a JS value * * Compiles the a module source text into an internal #Module object given the * module's URI as the first argument. * * 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: * @uri: The URI of the module (JS string) * @source: The source text of the module (JS string) * * JS function exposed as `compileInternalModule` in the internal global scope. * * 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. * * Returns: The compiled JS module object. */ 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: * @uri: The URI of the module (JS string) * @source: The source text of the module (JS string) * * JS function exposed as `compileModule` in the internal global scope. * * Compiles a module source text within the main realm. * * NOTE: Modules compiled with this function can only be executed * within the main realm. * * Returns: The compiled JS module object. */ 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: * @module: The JS module object * @private: The JS module private object for @module * * JS function exposed as `setModulePrivate` in the internal global scope. * * Sets the private object of an internal #Module object. * * Returns: JS undefined */ 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: * @global: The JS global object * * JS function exposed as `getRegistry` in the internal global scope. * * Retrieves the module registry for @global. * * Returns: the module registry, a JS Map object. */ 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; } /** * gjs_internal_get_source_map_registry: * @global: The JS global object * * JS function exposed as `getSourceMapRegistry` in the internal global scope. * * Retrieves the source map registry for @global. * * Returns: the source map registry, a JS Map object. */ bool gjs_internal_get_source_map_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, "getSourceMapRegistry", args, "o", "global", &global)) return handle_wrong_args(cx); JSAutoRealm ar{cx, global}; JSObject* registry = gjs_get_source_map_registry(global); args.rval().setObject(*registry); return true; } /** * gjs_uri_object: * @cx: the current JSContext * @uri: the URI to parse into a JS URI object * @rval: (out): handle to a JSValue where the URI object will be stored * * Parses @uri and creates a JS object with the various parsed parts available * as properties. See type `Uri` in modules/internal/environment.d.ts. * * Basically a JS wrapper for g_uri_parse() for use in the internal global scope * where we don't have access to gobject-introspection. * * Returns: false if an exception is pending, otherwise true */ static bool gjs_uri_object(JSContext* cx, const char* uri, JS::MutableHandleValue rval) { using AutoHashTable = Gjs::AutoPointer; using AutoURI = Gjs::AutoPointer; Gjs::AutoError error; AutoURI parsed = g_uri_parse(uri, G_URI_FLAGS_ENCODED, &error); if (!parsed) { Gjs::AutoMainRealm ar{cx}; gjs_throw_custom(cx, JSEXN_ERR, "ImportError", "Attempted to import invalid URI: %s (%s)", uri, 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, 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)) { const auto* key = static_cast(key_ptr); const 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::RootedValue v_uri{cx}; Gjs::AutoChar uri_string{g_uri_to_string(parsed)}; if (!gjs_string_from_utf8(cx, uri_string, &v_uri)) 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; Gjs::AutoChar 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", v_uri, 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; rval.setObject(*return_obj); return true; } bool gjs_internal_parse_uri(JSContext* cx, unsigned argc, JS::Value* vp) { 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; return gjs_uri_object(cx, uri.get(), args.rval()); } 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); Gjs::AutoChar output_uri{g_uri_resolve_relative( uri.get(), relative_path.get(), G_URI_FLAGS_NONE, nullptr)}; return gjs_uri_object(cx, output_uri.get(), args.rval()); } 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); Gjs::AutoUnref file{g_file_new_for_uri(uri.get())}; char* contents; size_t length; Gjs::AutoError 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); Gjs::AutoUnref file{g_file_new_for_uri(uri.get())}; args.rval().setBoolean(g_file_query_exists(file, nullptr)); return true; } bool gjs_internal_atob(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::UniqueChars text; size_t result_len; if (!gjs_parse_call_args(cx, "atob", args, "!s", "text", &text)) return handle_wrong_args(cx); Gjs::AutoChar decoded = reinterpret_cast(g_base64_decode(text.get(), &result_len)); JS::ConstUTF8CharsZ contents_chars{decoded, result_len}; JS::RootedString contents_str{cx, JS_NewStringCopyUTF8Z(cx, contents_chars)}; if (!contents_str) return false; args.rval().setString(contents_str); 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; Gjs::AutoError error; if (!g_file_load_contents_finish(G_FILE(file), res, &contents, &length, /* etag_out = */ nullptr, &error)) { Gjs::AutoChar 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"); Gjs::AutoUnref 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); Gjs::AutoUnref 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-140.0/cjs/internal.h0000664000175000017500000000304115167114161013766 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh #pragma once #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_load_internal_module(JSContext*, const char* identifier); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_compile_module(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_compile_internal_module(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_get_registry(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_get_source_map_registry(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_set_global_module_loader(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_set_module_private(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_parse_uri(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_resolve_relative_resource_or_file(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_load_resource_or_file(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_load_resource_or_file_async(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_uri_exists(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION bool gjs_internal_atob(JSContext*, unsigned, JS::Value*); cjs-140.0/cjs/jsapi-class.h0000664000175000017500000000347015167114161014371 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 #pragma once #include #include // for JSNative #include #include #include #include "cjs/macros.h" struct JSFunctionSpec; struct JSPropertySpec; GJS_JSAPI_RETURN_CONVENTION bool gjs_init_class_dynamic( JSContext*, JS::HandleObject in_object, JS::HandleObject parent_proto, const char* ns_name, const char* class_name, const JSClass*, 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*, JS::HandleObject, const JSClass* static_clasp, bool throw_error); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_construct_object_dynamic(JSContext*, 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); [[nodiscard]] JS::Value gjs_dynamic_property_private_slot(JSObject* accessor_obj); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_in_prototype_chain(JSContext*, JS::HandleObject proto, JS::HandleObject check_obj, bool* is_in_chain); cjs-140.0/cjs/jsapi-dynamic-class.cpp0000664000175000017500000002375515167114161016356 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 #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/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-class.h" // IWYU pragma: associated #include "cjs/jsapi-util.h" #include "cjs/macros.h" struct JSFunctionSpec; struct JSPropertySpec; // Reserved slots of JSNative accessor wrappers enum JSNativeAccessorSlot : uint32_t { DYNAMIC_PROPERTY_PRIVATE_SLOT, }; 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* 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 != nullptr); // gjs_init_class_dynamic only makes sense for instantiable classes, use // JS_InitClass for static classes like Math g_assert(constructor_native != nullptr); // 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(cx, 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(cx, clasp)); } if (!prototype) return false; if (proto_ps && !JS_DefineProperties(cx, prototype, proto_ps)) return false; if (proto_fs && !JS_DefineFunctions(cx, prototype, proto_fs)) return false; Gjs::AutoChar full_function_name{ g_strdup_printf("%s_%s", ns_name, class_name)}; JSFunction* constructor_fun = JS_NewFunction( cx, constructor_native, nargs, JSFUN_CONSTRUCTOR, full_function_name); if (!constructor_fun) return false; constructor.set(JS_GetFunctionObject(constructor_fun)); if (static_ps && !JS_DefineProperties(cx, constructor, static_ps)) return false; if (static_fs && !JS_DefineFunctions(cx, constructor, static_fs)) return false; if (!JS_LinkConstructorAndPrototype(cx, 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(cx, 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_"); return name; } bool gjs_typecheck_instance(JSContext* cx, JS::HandleObject obj, const JSClass* static_clasp, bool throw_error) { if (!JS_InstanceOf(cx, obj, static_clasp, nullptr)) { if (throw_error) { const JSClass* obj_class = JS::GetClass(obj); gjs_throw_custom(cx, 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* cx, JS::HandleObject proto, const JS::HandleValueArray& args) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject constructor{cx}; if (!gjs_object_require_property(cx, proto, "prototype", atoms.constructor(), &constructor)) return nullptr; JS::RootedValue v_constructor{cx, JS::ObjectValue(*constructor)}; JS::RootedObject object{cx}; if (!JS::Construct(cx, 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) { Gjs::AutoChar getter_name{ g_strconcat(func_namespace, "_get::", prop_name, nullptr)}; Gjs::AutoChar 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-140.0/cjs/jsapi-util-args.h0000664000175000017500000003473415167114161015202 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 #pragma once #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/auto.h" #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(*fchar != '\0' && "Invalid format string, parameter required after '?'"); return true; } class ParseArgsErr { Gjs::AutoChar m_message; public: explicit ParseArgsErr(const char* literal_msg) : m_message(literal_msg, Gjs::TakeOwnership{}) {} template ParseArgsErr(const char* format_string, F param) : m_message(g_strdup_printf(format_string, param)) {} [[nodiscard]] const char* message() const { return m_message.get(); } }; template 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, Gjs::AutoChar* ref) { if (c != 'F') return Err("Wrong type for %c, got Gjs::AutoChar*", 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 > INT32_MAX || 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(*fchar != '\0' && "Wrong number of parameters passed to gjs_parse_call_args()"); 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(*format == '\0' && "Wrong number of parameters passed to gjs_parse_call_args()"); 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: * @cx: * @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 Gjs::AutoChar *) * - 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) { unsigned n_required = 0, n_total = 0; bool optional_args = false, ignore_trailing_args = false; if (*format == '!') { ignore_trailing_args = true; format++; } for (const char* 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(sizeof...(Args) / 2 == n_total && "Wrong number of parameters passed to gjs_parse_call_args()"); 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; } Gjs::AutoStrv parts{g_strsplit(format, "|", 2)}; const char* fmt_required = parts.get()[0]; const char* fmt_optional = parts.get()[1]; // may be null return detail::parse_call_args_helper(cx, function_name, args, fmt_required, fmt_optional, 0, params...); } cjs-140.0/cjs/jsapi-util-error.cpp0000664000175000017500000002165215167114161015725 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/auto.h" #include "cjs/context-private.h" #include "cjs/gerror-result.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) { Gjs::AutoChar 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* cx, const char* format, ...) { va_list args; va_start(args, format); gjs_throw_valist(cx, 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* cx, const char* string) { gjs_throw(cx, "%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, Gjs::AutoError 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) { 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:%u:%u.", report->filename.c_str(), report->lineno, report->column.oneOriginValue()); } const char* warning; 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:%u:%u: %s", warning, report->filename.c_str(), report->lineno, report->column.oneOriginValue(), report->message().c_str()); } cjs-140.0/cjs/jsapi-util-root.h0000664000175000017500000001771515167114161015231 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. #pragma once #include #include #include #include // IWYU pragma: keep (actually for clangd) #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. // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 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 = nullptr; 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 cjs-140.0/cjs/jsapi-util-string.cpp0000664000175000017500000005012415167114161016076 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/auto.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" class JSLinearString; Gjs::AutoChar gjs_hyphen_to_underscore(const char* str) { char* s = g_strdup(str); char* retval = s; while (*(s++) != '\0') { if (*s == '-') *s = '_'; } return retval; } Gjs::AutoChar gjs_hyphen_to_camel(const char* str) { Gjs::AutoChar 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: * @cx: the current #JSContext * @str: string to encode in UTF-8 * @output: (out): return location for the UTF-8-encoded string * @output_len: (out): return location for the length of @output * * Converts a JSString to UTF-8 and puts the char array in @output and its * length in @output_len. * * This function handles the boilerplate for unpacking @str, 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. * * Returns: false if an exception is pending, otherwise true. */ 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: * @cx: the current #JSContext * @utf8_string: a zero-terminated array of UTF-8 characters to decode * * Converts @utf8_string to a JS string. Instead of throwing, any invalid * characters will be converted to the UTF-8 invalid character fallback. * * Returns: The decoded string. */ JSString* gjs_lossy_string_from_utf8(JSContext* cx, const char* utf8_string) { JS::UTF8Chars 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: * @cx: the current #JSContext * @utf8_string: an array of UTF-8 characters to decode * @len: length of @utf8_string * * Provides the same conversion behavior as gjs_lossy_string_from_utf8 * with a fixed length. See gjs_lossy_string_from_utf8(). * * Returns: The decoded string. */ 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* cx, const char* utf8_string, JS::MutableHandleValue value_p) { JS::ConstUTF8CharsZ chars(utf8_string, strlen(utf8_string)); JS::RootedString str{cx, JS_NewStringCopyUTF8Z(cx, 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* cx, const JS::Value filename_val, Gjs::AutoChar* filename_string) { // gjs_string_to_utf8() verifies that filename_val is a string JS::UniqueChars tmp{gjs_string_to_utf8(cx, filename_val)}; if (!tmp) return false; Gjs::AutoError error; *filename_string = g_filename_from_utf8(tmp.get(), -1, nullptr, nullptr, &error); if (!*filename_string) return gjs_throw_gerror_message(cx, error); return true; } bool gjs_string_from_filename(JSContext* cx, const char* filename_string, ssize_t n_bytes, JS::MutableHandleValue value_p) { gsize written; // Do not use size_t, may be different width Gjs::AutoError error; Gjs::AutoChar utf8_string{g_filename_to_utf8(filename_string, n_bytes, nullptr, &written, &error)}; if (error) { Gjs::AutoChar escaped_char{g_strescape(filename_string, nullptr)}; gjs_throw( cx, "Could not convert filename string to UTF-8 for string: %s. If " "string is invalid UTF-8 and used for display purposes, try GLib " "attribute standard::display-name. The reason is: %s. ", escaped_char.get(), error->message); return false; } return gjs_string_from_utf8_n(cx, 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 == nullptr) 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: * @cx: 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* cx, JS::HandleString str, char16_t** data_p, size_t* len_p) { if (JS::StringHasLatin1Chars(str)) return from_latin1(cx, 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(cx, nogc, str, len_p); if (js_data == nullptr) return false; mozilla::CheckedInt len_bytes = mozilla::CheckedInt(*len_p) * sizeof(*js_data); if (!len_bytes.isValid()) { JS_ReportOutOfMemory(cx); // cannot call gjs_throw, it may GC return false; } *data_p = static_cast(g_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 == nullptr) return true; size_t len; Gjs::AutoError 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 == nullptr) { gjs_throw(cx, "Failed to get UTF-16 string data"); return false; } if (ucs4_string_p != nullptr) { long length; *ucs4_string_p = g_utf16_to_ucs4(reinterpret_cast(utf16), len, nullptr, &length, &error); if (*ucs4_string_p == nullptr) { gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s", error->message); return false; } if (len_p != nullptr) *len_p = static_cast(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; Gjs::AutoError 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* cx, JS::Value value, gunichar* result) { JS::UniqueChars utf8_str{gjs_string_to_utf8(cx, 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 : uint8_t { 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-140.0/cjs/jsapi-util.cpp0000664000175000017500000005723415167114161014603 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 #ifdef _WIN32 # include #endif #include // for duration, operator""us #include #include #include // for move #include #include #include #include // for Call #include #include #include #include // for TaggedColumnNumberOneOrigin #include #include #include #include // for JS_MaybeGC, NonIncrementalGC, GCRe... #include // for GCHashSet #include // for RootedVector #include // for DefaultHasher #include // for GetClass #include #include // for JSPROP_ENUMERATE #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/auto.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/misc.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* cx, JS::HandleObject obj, const char* obj_description, JS::HandleId property_name, JS::MutableHandleValue value) { value.setUndefined(); if (G_UNLIKELY(!JS_GetPropertyById(cx, obj, property_name, value))) return false; if (G_LIKELY(!value.isUndefined())) return true; throw_property_lookup_error(cx, 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* cx) { gjs_throw(cx, "Constructor called as normal method. Use 'new SomeObject()' not " "'SomeObject()'"); } void gjs_throw_abstract_constructor_error(JSContext* cx, const JS::CallArgs& args) { const char* name = "anonymous"; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject callee{cx, &args.callee()}; JS::RootedValue prototype{cx}; if (JS_GetPropertyById(cx, callee, atoms.prototype(), &prototype)) { const JSClass* proto_class = JS::GetClass(&prototype.toObject()); name = proto_class->name; } gjs_throw(cx, "You cannot construct new instances of '%s'", name); } JSObject* gjs_build_string_array(JSContext* cx, const std::vector& strings) { JS::RootedValueVector elems{cx}; if (!elems.reserve(strings.size())) { JS_ReportOutOfMemory(cx); return nullptr; } for (const std::string& string : strings) { JS::ConstUTF8CharsZ chars(string.c_str(), string.size()); JS::RootedValue element{ cx, JS::StringValue(JS_NewStringCopyUTF8Z(cx, chars))}; elems.infallibleAppend(element); } return JS::NewArrayObject(cx, elems); } JSObject* gjs_define_string_array(JSContext* cx, JS::HandleObject in_object, const char* array_name, const std::vector& strings, unsigned attrs) { JS::RootedObject array{cx, gjs_build_string_array(cx, strings)}; if (!array) return nullptr; if (!JS_DefineProperty(cx, 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: log and clear the pending exception, without calling into // any JS APIs that might cause more exceptions to be thrown. static void log_exception_brief(JSContext* cx) { JS::RootedValue exc{cx}; if (!JS_GetPendingException(cx, &exc)) return; JS_ClearPendingException(cx); if (!exc.isObject()) { g_warning("Value thrown while printing exception: %s", gjs_debug_value(exc).c_str()); return; } JS::RootedObject exc_obj{cx, &exc.toObject()}; JSErrorReport* report = JS_ErrorFromException(cx, exc_obj); if (!report) { g_warning("Non-Error Object thrown while printing exception: %s", gjs_debug_object(exc_obj).c_str()); return; } g_warning("Exception thrown while printing exception: %s:%u:%u: %s", report->filename.c_str(), report->lineno, report->column.oneOriginValue(), report->message().c_str()); } // Helper function: format the error's stack property. static std::string format_exception_stack(JSContext* cx, JS::HandleObject exc) { auto Ok = JS::SavedFrameResult::Ok; 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) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); Gjs::AutoMainRealm ar{gjs}; JS::RootedObject global{cx, gjs->global()}; JS::RootedObject registry{cx, gjs_get_source_map_registry(global)}; JS::UniqueChars utf8_stack{format_saved_frame(cx, saved_frame)}; if (!utf8_stack) return {}; char* utf8_stack_str = utf8_stack.get(); std::stringstream ss{utf8_stack_str}; // append source map info when available to each line while (saved_frame) { JS::RootedObject consumer{cx}; JS::RootedString source_string{cx}; uint32_t line; JS::TaggedColumnNumberOneOrigin column; std::string stack_line; // print the original stack trace std::getline(ss, stack_line, '\n'); out << '\n' << stack_line; bool success = JS::GetSavedFrameSource(cx, nullptr, saved_frame, &source_string) == Ok && JS::GetSavedFrameLine(cx, nullptr, saved_frame, &line) == Ok && JS::GetSavedFrameColumn(cx, nullptr, saved_frame, &column) == Ok; if (JS::GetSavedFrameParent(cx, nullptr, saved_frame, &saved_frame) != Ok) { // If we can't iterate, bail out and don't print source map info break; } if (!success) { continue; } if (!gjs_global_source_map_get(cx, registry, source_string, &consumer) || !consumer) { log_exception_brief(cx); continue; // no source map for this file } // build query obj for consumer JS::RootedObject input_obj{cx, JS_NewPlainObject(cx)}; if (!input_obj || !JS_DefineProperty(cx, input_obj, "line", line, JSPROP_ENUMERATE) || !JS_DefineProperty(cx, input_obj, "column", column.oneOriginValue() - 1, JSPROP_ENUMERATE)) { log_exception_brief(cx); continue; } JS::RootedValue val{cx, JS::ObjectValue(*input_obj)}; if (!JS::Call(cx, consumer, "originalPositionFor", JS::HandleValueArray(val), &val)) { log_exception_brief(cx); continue; } JS::RootedObject rvalObj{cx, &val.toObject()}; out << " -> "; if (!JS_GetProperty(cx, rvalObj, "name", &val)) { log_exception_brief(cx); continue; } if (val.isString()) { JS::UniqueChars name{gjs_string_to_utf8(cx, val)}; if (name) out << name.get() << "@"; log_exception_brief(cx); } if (!JS_GetProperty(cx, rvalObj, "source", &val)) { log_exception_brief(cx); continue; } if (val.isString()) { JS::UniqueChars source{gjs_string_to_utf8(cx, val)}; if (source) out << source.get(); log_exception_brief(cx); } if (!JS_GetProperty(cx, rvalObj, "line", &val)) { log_exception_brief(cx); continue; } if (val.isInt32()) { out << ":" << val.toInt32(); } if (!JS_GetProperty(cx, rvalObj, "column", &val)) { log_exception_brief(cx); continue; } if (val.isInt32()) { out << ":" << val.toInt32() + 1; } } return out.str(); } JS::RootedValue stack{cx}; if (!JS_GetPropertyById(cx, exc, atoms.stack(), &stack) || !stack.isString()) { log_exception_brief(cx); return {}; } JS::RootedString str{cx, stack.toString()}; bool is_empty; if (!JS_StringEqualsLiteral(cx, str, "", &is_empty) || is_empty) { log_exception_brief(cx); return {}; } JS::UniqueChars utf8_stack{JS_EncodeStringToUTF8(cx, str)}; if (!utf8_stack) { log_exception_brief(cx); 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(); } log_exception_brief(cx); int32_t column = 0; if (JS_GetPropertyById(cx, exc, atoms.column_number(), &property)) { if (property.isInt32()) column = property.toInt32(); } log_exception_brief(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); } } log_exception_brief(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)) log_exception_brief(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(); } log_exception_brief(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); log_exception_brief(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(); } log_exception_brief(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* 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_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; } void gjs_gc_if_needed(JSContext* cx) { #ifdef __linux__ using std::chrono_literals::operator""us; // We initiate a GC if RSS has grown by this much. It is initialized to 0, // so currently we always do a full GC early. static uint64_t linux_rss_trigger; static GLib::MonotonicTime last_gc_check_time; // We rate limit GCs to at most one per 5 frames. One frame is 16666 // microseconds (1000000/60) GLib::MonotonicTime now{GLib::MonotonicClock::now()}; if (now - last_gc_check_time < 5 * 16666us) return; last_gc_check_time = now; Gjs::AutoChar contents; if (!g_file_get_contents("/proc/self/statm", contents.out(), nullptr, nullptr)) { g_critical("Error reading contents of /proc/self/statm"); return; } Gjs::StatmParseResult result = Gjs::parse_statm_file_rss(contents); if (result.isErr()) { std::string message{result.unwrapErr()}; g_critical("%s", message.c_str()); return; } uint64_t rss = result.unwrap(); // Here we see if the RSS has grown by 25% since our last look; if so, // initiate a full compacting 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 > linux_rss_trigger) { linux_rss_trigger = MIN(UINT32_MAX, rss * 1.25); JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, Gjs::GCReason::LINUX_RSS_TRIGGER); } else if (rss < (0.75 * linux_rss_trigger)) { // If we've shrunk back to 75%, lower the trigger to 25% above the // current value linux_rss_trigger = rss * 1.25; } #else // !__linux__ (void)cx; #endif } /** * gjs_maybe_gc: * * Low level version of gjs_context_maybe_gc(). */ void gjs_maybe_gc(JSContext* cx) { JS_MaybeGC(cx); gjs_gc_if_needed(cx); } const char* gjs_explain_gc_reason(JS::GCReason reason) { if (JS::InternalGCReason(reason)) return JS::ExplainGCReason(reason); static const char* reason_strings[] = { "RSS above threshold", "GjsContext disposed", "Big Hammer hit", "gjs_context_gc() called", "Memory usage is low", }; 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-140.0/cjs/jsapi-util.h0000664000175000017500000002430315167114161014237 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 #pragma once #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 #include #include #include #include // for JSExnType #include #include // for IgnoreGCPolicy #include #include #include // for UniqueChars #include "cjs/auto.h" #include "cjs/gerror-result.h" #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 /* 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*); void gjs_throw_abstract_constructor_error(JSContext*, const JS::CallArgs&); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_build_string_array(JSContext*, const std::vector&); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_string_array(JSContext*, JS::HandleObject, const char* array_name, const std::vector&, unsigned attrs); [[gnu::format(printf, 2, 3)]] void gjs_throw(JSContext*, const char* format, ...); [[gnu::format(printf, 4, 5)]] void gjs_throw_custom(JSContext*, JSExnType, const char* error_name, const char* format, ...); void gjs_throw_literal(JSContext*, const char* string); bool gjs_throw_gerror_message(JSContext*, Gjs::AutoError const&); bool gjs_log_exception(JSContext*); bool gjs_log_exception_uncaught(JSContext*); void gjs_log_exception_full(JSContext*, JS::HandleValue exc, JS::HandleString message, GLogLevelFlags); void gjs_warning_reporter(JSContext*, JSErrorReport*); GJS_JSAPI_RETURN_CONVENTION JS::UniqueChars gjs_string_to_utf8(JSContext*, JS::Value string_val); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_utf8_n(JSContext*, JS::HandleString str, JS::UniqueChars* output, size_t* output_len); GJS_JSAPI_RETURN_CONVENTION JSString* gjs_lossy_string_from_utf8(JSContext*, const char* utf8_string); GJS_JSAPI_RETURN_CONVENTION JSString* gjs_lossy_string_from_utf8_n(JSContext*, const char* utf8_string, size_t len); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_utf8(JSContext*, const char* utf8_string, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_utf8_n(JSContext*, const char* utf8_chars, size_t len, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_filename(JSContext*, JS::Value, Gjs::AutoChar* filename_string); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_filename(JSContext*, const char* filename_string, ssize_t n_bytes, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_get_char16_data(JSContext*, JS::HandleString, char16_t** data_p, size_t* len_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_to_ucs4(JSContext*, JS::HandleString, gunichar** ucs4_string_p, size_t* len_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_string_from_ucs4(JSContext*, const gunichar* ucs4_string, ssize_t n_chars, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION bool gjs_get_string_id(JSContext*, jsid, JS::UniqueChars* name_p); GJS_JSAPI_RETURN_CONVENTION jsid gjs_intern_string_to_id(JSContext*, const char* string); GJS_JSAPI_RETURN_CONVENTION bool gjs_unichar_from_string(JSContext*, JS::Value string, gunichar* result); // Functions intended for more "internal" use void gjs_maybe_gc(JSContext*); void gjs_gc_if_needed(JSContext*); GJS_JSAPI_RETURN_CONVENTION JS::UniqueChars format_saved_frame(JSContext*, JS::HandleObject saved_frame, size_t indent = 0); /* Overloaded functions. More types are intended to be added as the opportunity * arises. */ GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext*, JS::HandleObject, const char* obj_description, JS::HandleId property_name, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext*, JS::HandleObject, const char* description, JS::HandleId property_name, bool* value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext*, JS::HandleObject, const char* description, JS::HandleId property_name, int32_t* value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext*, JS::HandleObject, const char* description, JS::HandleId property_name, JS::UniqueChars* value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_property(JSContext*, JS::HandleObject, const char* description, JS::HandleId property_name, JS::MutableHandleObject value); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_require_converted_property(JSContext*, JS::HandleObject, const char* description, JS::HandleId property_name, uint32_t*); [[nodiscard]] std::string gjs_debug_bigint(JS::BigInt*); [[nodiscard]] std::string gjs_debug_string(JSString*); [[nodiscard]] std::string gjs_debug_symbol(JS::Symbol*); [[nodiscard]] std::string gjs_debug_object(JSObject*); [[nodiscard]] std::string gjs_debug_callable(JSObject* callable); [[nodiscard]] std::string gjs_debug_value(JS::Value); [[nodiscard]] std::string gjs_debug_id(jsid); [[nodiscard]] Gjs::AutoChar gjs_hyphen_to_underscore(const char*); [[nodiscard]] Gjs::AutoChar gjs_hyphen_to_camel(const char*); #if defined(G_OS_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1900)) [[nodiscard]] std::wstring gjs_win32_vc140_utf8_to_utf16(const char*); #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 // NOLINT(bugprone-macro-parentheses) 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); cjs-140.0/cjs/macros.h0000664000175000017500000000334615167114161013446 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-140.0/cjs/mainloop.cpp0000664000175000017500000000316315167114161014330 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/auto.h" #include "cjs/context-private.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; } Gjs::AutoPointer 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-140.0/cjs/mainloop.h0000664000175000017500000000401115167114161013766 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 = false; 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() { 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-140.0/cjs/mem-private.h0000664000175000017500000000537615167114161014415 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 #pragma once #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::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 Gjs::Memory #define COUNT(name, ix) +1 // NOLINT(bugprone-macro-parentheses) 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()) cjs-140.0/cjs/mem.cpp0000664000175000017500000000313315167114161013265 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" // IWYU pragma: associated #include "cjs/mem.h" #include "util/log.h" namespace Gjs::Memory::Counters { #define GJS_DEFINE_COUNTER(name, ix) Counter name(#name); GJS_DEFINE_COUNTER(everything, -1) GJS_FOR_EACH_COUNTER(GJS_DEFINE_COUNTER) } // namespace Gjs::Memory::Counters #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) { gjs_debug(GJS_DEBUG_MEMORY, "Memory report: %s", where); size_t n_counters = G_N_ELEMENTS(counters); int64_t total_objects = 0; for (size_t 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, " %" PRId64 " objects currently alive", GJS_GET_COUNTER(everything)); if (GJS_GET_COUNTER(everything) != 0) { for (size_t i = 0; i < n_counters; ++i) { gjs_debug(GJS_DEBUG_MEMORY, " %24s = %" PRId64, counters[i]->name, counters[i]->value.load()); } if (die_if_leaks) g_error("%s: JavaScript objects were leaked.", where); } } cjs-140.0/cjs/mem.h0000664000175000017500000000103715167114161012733 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-140.0/cjs/module.cpp0000664000175000017500000006503015167114161014000 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 #include // for vector #include #include #include #include #include #include // for ConstUTF8CharsZ #include #include #include #include #include #include // for JS_ReportOutOfMemory #include #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/auto.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/gerror-result.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 { Gjs::AutoChar m_name; // Reserved slots static const size_t POINTER = 0; explicit GjsScriptModule(const char* name) : m_name(g_strdup(name)) { GJS_INC_COUNTER(module); } ~GjsScriptModule() { GJS_DEC_COUNTER(module); } // Private data accessors [[nodiscard]] static 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::EnvironmentChain scope_chain{cx, JS::SupportUnscopables::No}; 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) { Gjs::AutoError error; Gjs::AutoChar 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); Gjs::AutoChar full_path{g_file_get_parse_name(file)}; Gjs::AutoChar 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), m_name.get()}); 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; } GjsScriptModule(GjsScriptModule&) = delete; GjsScriptModule& operator=(GjsScriptModule&) = delete; }; /** * gjs_script_module_build_private: * @cx: the #JSContext * @uri: the URI this script module is loaded from * * 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); } /** * gjs_get_native_registry: * @global: The JS global object * * 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. * * Returns: the native module registry, a JS Map object. */ 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: * @global: the JS global object * * Retrieves a global's module registry from the MODULE_REGISTRY slot. * Registries are JS Maps. See gjs_get_native_registry() for more detail. * * Returns: the module registry, a JS Map object */ 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_get_source_map_registry: * @global: The JS global object * * Retrieves a global's source map registry from the SOURCE_MAP_REGISTRY slot. * Registries are JS Maps. * * Returns: the source map registry, a JS Map object */ JSObject* gjs_get_source_map_registry(JSObject* global) { JS::Value source_map_registry = gjs_get_global_slot(global, GjsGlobalSlot::SOURCE_MAP_REGISTRY); g_assert(source_map_registry.isObject()); return &source_map_registry.toObject(); } /** * gjs_module_load: * @cx: the current JSContext * @identifier: specifier of the module to load * @file_uri: URI to load the module from * * 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 load 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: * @identifier: the specifier for the module to import * * JS function exposed as `import.meta.importSync` in internal modules only. * * 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. * * Returns: the imported JS module object. */ 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: * @cx: the current JSContext * @private_ref: the JS private value for the #Module object, as a JS Object * @meta: the JS `import.meta` object * * 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. * * 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; Gjs::AutoChar 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 Gjs::AutoChar for_file_uri{g_uri_join(G_URI_FLAGS_NONE, scheme.get(), nullptr, host.get(), -1, path.get(), nullptr, nullptr)}; Gjs::AutoUnref 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; } Gjs::AutoChar 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: * @cx: the current JSContext * @importing_module_priv: the private JS Object of the #Module object * initiating the import, or a JS null value * @module_request: the module request object * * This function resolves import specifiers. It is called internally by * SpiderMonkey as a hook. * * Returns: whether an error occurred while resolving the specifier. */ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importing_module_priv, 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(importing_module_priv); 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(importing_module_priv).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-140.0/cjs/module.h0000664000175000017500000000300415167114161013436 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 #pragma once #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_import(JSContext*, JS::HandleObject importer, JS::HandleId, const char* name, GFile*); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_script_module_build_private(JSContext*, 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_get_source_map_registry(JSObject* global); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_load(JSContext*, const char* identifier, const char* uri); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_module_resolve(JSContext*, JS::HandleValue importing_module_priv, JS::HandleObject module_request); GJS_JSAPI_RETURN_CONVENTION bool gjs_populate_module_meta(JSContext*, JS::HandleValue private_ref, JS::HandleObject meta_object); GJS_JSAPI_RETURN_CONVENTION bool gjs_dynamic_module_resolve(JSContext*, JS::HandleValue importing_module_priv, JS::HandleObject module_request, JS::HandleObject internal_promise); cjs-140.0/cjs/native.cpp0000664000175000017500000000373515167114161014005 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 tie #include #include // for ignore #include #include #include #include "cjs/jsapi-util.h" #include "cjs/native.h" #include "util/log.h" void Gjs::NativeModuleDefineFuncs::add(const char* module_id, DefineModuleFunc 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: * @cx: 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* cx, 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(cx, "No native module '%s' has registered itself", id); return false; } return iter->second(cx, module_out); } cjs-140.0/cjs/native.h0000664000175000017500000000226115167114161013443 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 #pragma once #include #include #include #include // for MutableHandle #include #include "cjs/macros.h" namespace Gjs { class NativeModuleDefineFuncs { NativeModuleDefineFuncs() {} using DefineModuleFunc = bool (*)(JSContext*, 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, DefineModuleFunc); // 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*, const char* name, JS::MutableHandleObject module_out) const; }; }; // namespace Gjs cjs-140.0/cjs/objectbox.cpp0000664000175000017500000000752215167114161014474 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/auto.h" #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; } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 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-140.0/cjs/objectbox.h0000664000175000017500000000172115167114161014134 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/auto.h" #include "cjs/macros.h" class JSTracer; class ObjectBox { static void destroy(ObjectBox*); public: using Ptr = Gjs::AutoPointer; [[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*); private: explicit ObjectBox(JSObject*); static void* boxed_copy(void*); static void boxed_free(void*); struct impl; static void destroy_impl(impl*); Gjs::AutoPointer m_impl; }; cjs-140.0/cjs/profiler-private.h0000664000175000017500000000442715167114161015455 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. #pragma once #include #include #include #include // for nano #include #include // for JSFinalizeStatus, JSGCStatus, GCReason #include #include #include #include #include "cjs/context.h" #include "cjs/profiler.h" #include "util/misc.h" #define GJS_PROFILER_DYNAMIC_STRING(cx, str) \ js::GetContextProfilingStackIfEnabled(cx) ? (str) : "" class AutoProfilerLabel { public: explicit AutoProfilerLabel(JSContext* cx, const char* label, const std::string& dynamicString, JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::OTHER, uint32_t flags = 0) : m_stack(js::GetContextProfilingStackIfEnabled(cx)) { if (m_stack) m_stack->pushLabelFrame(label, dynamicString.c_str(), this, categoryPair, flags); } ~AutoProfilerLabel() { if (m_stack) m_stack->pop(); } private: ProfilingStack* m_stack; }; namespace Gjs { enum GCCounters : uint8_t { GC_HEAP_BYTES, MALLOC_HEAP_BYTES, N_COUNTERS }; } // namespace Gjs GjsProfiler* gjs_profiler_new(GjsContext*); void gjs_profiler_free(GjsProfiler*); using ProfilerTimePoint = std::chrono::time_point; using ProfilerDuration = std::chrono::duration; void gjs_profiler_add_mark(GjsProfiler*, ProfilerTimePoint, ProfilerDuration, const char* group, const char* name, const char* message); [[nodiscard]] bool gjs_profiler_sample_gc_memory_info( GjsProfiler*, const int64_t gc_counters[Gjs::GCCounters::N_COUNTERS]); [[nodiscard]] bool gjs_profiler_is_running(GjsProfiler*); void gjs_profiler_setup_signals(GjsProfiler*, GjsContext*); void gjs_profiler_set_finalize_status(GjsProfiler*, JSFinalizeStatus); void gjs_profiler_set_gc_status(GjsProfiler*, JSGCStatus, JS::GCReason); cjs-140.0/cjs/profiler.cpp0000664000175000017500000007760315167114161014346 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 #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 #include "cjs/auto.h" #include "cjs/context.h" #include "cjs/jsapi-util.h" // for gjs_explain_gc_reason #include "cjs/mem-private.h" #include "cjs/profiler-private.h" // IWYU pragma: associated #include "cjs/profiler.h" #include "util/misc.h" namespace mozilla::detail { template struct MaybeStorage; } using mozilla::Maybe, mozilla::Nothing, mozilla::Some, std::chrono_literals::operator""s, std::chrono_literals::operator""ms; /* * 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. */ static const std::chrono::seconds FLUSH_DELAY = 3s; static const std::chrono::milliseconds SAMPLING_PERIOD = 1ms; // Custom packing for Maybe. We assume that 0 is not a value // that comes out of the monotonic clock and so can be used to indicate Nothing namespace mozilla::detail { template <> struct MaybeStorage { protected: union { struct { ProfilerTimePoint val; } mStorage; ProfilerTimePoint::rep mIsSome; }; explicit MaybeStorage(ProfilerTimePoint some) : mStorage({some}) { g_assert(some.time_since_epoch().count() != 0 && "0 is not a valid value for Maybe"); } MaybeStorage() : mIsSome(0) {} }; static_assert(sizeof(Maybe) == sizeof(ProfilerTimePoint), "Maybe should pack"); } // namespace mozilla::detail 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 Maybe gc_begin_time; Maybe sweep_begin_time; Maybe 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 [[nodiscard]] static inline ProfilerTimePoint profiler_timestamp() { return std::chrono::time_point_cast( GLib::MonotonicClock::now()); } /** * 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) { ProfilerTimePoint now = profiler_timestamp(); g_assert(self && "Profiler must be set up before extracting maps"); Gjs::AutoChar path{g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid))}; Gjs::AutoChar content; size_t len; if (!g_file_get_contents(path, content.out(), &len, nullptr)) return false; Gjs::AutoStrv 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.time_since_epoch().count(), -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 = static_cast(counter_base + ix); counter->type = SYSPROF_CAPTURE_COUNTER_INT64; counter->value.v64 = 0; } [[nodiscard]] static bool gjs_profiler_define_counters(GjsProfiler* self) { ProfilerTimePoint now = profiler_timestamp(); 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.data() + (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.time_since_epoch().count(), -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 = static_cast(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.time_since_epoch().count(), -1, self->pid, gc_counters.data(), Gjs::GCCounters::N_COUNTERS); } #endif /* ENABLE_PROFILER */ /** * _gjs_profiler_new: * @gjs_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* gjs_context) { g_return_val_if_fail(gjs_context, nullptr); if (profiling_context == gjs_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.", gjs_context); return nullptr; } GjsProfiler* self = g_new0(GjsProfiler, 1); #ifdef ENABLE_PROFILER self->cx = static_cast(gjs_context_get_native_context(gjs_context)); self->pid = getpid(); #endif self->fd = -1; profiling_context = gjs_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(info && "SIGPROF handler called with invalid signal info"); g_assert(info->si_signo == SIGPROF && "SIGPROF handler called with other signal"); /* * 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; ProfilerTimePoint now = profiler_timestamp(); auto* addrs = static_cast( alloca(sizeof(SysprofCaptureAddress) * 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.time_since_epoch().count(), -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.time_since_epoch().count(), -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 = {{nullptr}}; 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 { Gjs::AutoChar 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.count()); 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 = std::chrono::nanoseconds{SAMPLING_PERIOD}.count(); its.it_value.tv_sec = 0; its.it_value.tv_nsec = std::chrono::nanoseconds{SAMPLING_PERIOD}.count(); // 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* gjs_context = GJS_CONTEXT(data); GjsProfiler* current_profiler = gjs_context_get_profiler(gjs_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: * @gjs_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* gjs_context) { g_return_if_fail(gjs_context == profiling_context); #ifdef ENABLE_PROFILER if (self->sigusr2_id != 0) return; self->sigusr2_id = g_unix_signal_add(SIGUSR2, gjs_profiler_sigusr2, gjs_context); #else // !ENABLE_PROFILER g_message("Profiler is disabled. Not setting up signals."); (void)self; #endif // ENABLE_PROFILER } /** * gjs_profiler_chain_signal: * @gjs_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* gjs_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(gjs_context); return true; } } #else // !ENABLE_PROFILER (void)gjs_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, void* 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)) : nullptr; #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, ProfilerTimePoint time, ProfilerDuration duration, 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.time_since_epoch().count(), -1, self->pid, duration.count(), group, name, message); } #else // Unused in the no-profiler case (void)time; (void)duration; (void)message; #endif } bool gjs_profiler_sample_gc_memory_info( GjsProfiler* self, const 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]; } ProfilerTimePoint time = profiler_timestamp(); if (!sysprof_capture_writer_set_counters( self->capture, time.time_since_epoch().count(), -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-140: // // 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 // js/src/gc/GC.cpp, GCRuntime::beginSweepPhase, beginSweepingSweepGroup, // and endSweepPhase, all called from incrementalSlice). // // 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. ProfilerTimePoint now = profiler_timestamp(); switch (status) { case JSFINALIZE_GROUP_PREPARE: self->sweep_begin_time = Some(now); break; case JSFINALIZE_GROUP_START: self->group_sweep_begin_time = Some(now); break; case JSFINALIZE_GROUP_END: if (self->group_sweep_begin_time) { 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 = Nothing{}; break; case JSFINALIZE_COLLECTION_END: if (self->sweep_begin_time) { gjs_profiler_add_mark(self, *self->sweep_begin_time, now - *self->sweep_begin_time, "GJS", "Sweep", nullptr); } self->sweep_begin_time = Nothing{}; 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 ProfilerTimePoint now = profiler_timestamp(); switch (status) { case JSGC_BEGIN: self->gc_begin_time = Some(now); self->gc_reason = gjs_explain_gc_reason(reason); break; case JSGC_END: if (self->gc_begin_time) { gjs_profiler_add_mark(self, *self->gc_begin_time, now - *self->gc_begin_time, "GJS", "Garbage collection", self->gc_reason); } self->gc_begin_time = Nothing{}; self->gc_reason = nullptr; break; default: g_assert_not_reached(); } #else (void)self; (void)status; (void)reason; #endif } cjs-140.0/cjs/profiler.h0000664000175000017500000000163015167114161013776 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()) GJS_EXPORT G_DECLARE_FINAL_TYPE(GjsProfiler, gjs_profiler, GJS, PROFILER, GObject); 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-140.0/cjs/promise.cpp0000664000175000017500000002046615167114161014175 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 // for string methods #include #include #include // for JS::IsCallable #include #include // for JS_DefineFunctions #include #include #include #include // for JS_NewPlainObject #include // for RunJobs #include "cjs/auto.h" #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 { /** * PromiseJobDispatcher::Source: * * 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. AutoMainContext m_main_context; // The cancellable that stops this source. AutoUnref m_cancellable; AutoPointer 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: /** * Source::Source: * @gjs: the GJS object * @main_context: GLib main context to associate with the source * * Constructs a new GSource for the PromiseJobDispatcher and adds a * reference to the associated main context. */ Source(GjsContextPrivate* gjs, GMainContext* main_context) : m_gjs(gjs), m_main_context(main_context, TakeOwnership{}), m_cancellable(g_cancellable_new()), m_cancellable_source(g_cancellable_source_new(m_cancellable)) { g_source_set_priority(this, PRIORITY); g_source_set_static_name(this, "GjsPromiseJobQueueSource"); // 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); } /** * Source::cancel: * * Trigger the cancellable, detaching our source. */ void cancel() { g_cancellable_cancel(m_cancellable); } /** * Source::uncancel: * * Reset the cancellable and prevent the source from stopping, overriding a * previous cancel() call. Called by PromiseJobDispatcher::start() to ensure * the custom source will start. */ void uncancel() { 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->uncancel(); // 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-140.0/cjs/promise.h0000664000175000017500000000300415167114161013627 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/auto.h" class GjsContextPrivate; namespace Gjs { using AutoMainContext = AutoPointer; /** * PromiseJobDispatcher: * * 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 AutoMainContext m_main_context; // The custom source. std::unique_ptr m_source; public: explicit PromiseJobDispatcher(GjsContextPrivate*); ~PromiseJobDispatcher(); /** * PromiseJobDispatcher::start: * * Starts (or resumes) dispatching jobs from the promise job queue. */ void start(); /** * PromiseJobDispatcher::stop: * * Stops dispatching jobs from the promise job queue. */ void stop(); /** * PromiseJobDispatcher::is_running: * * Returns: Whether the dispatcher is currently running. */ bool is_running(); }; }; // namespace Gjs bool gjs_define_native_promise_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/cjs/stack.cpp0000664000175000017500000000364515167114161013624 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/auto.h" #include "cjs/context-private.h" #include "cjs/context.h" void gjs_context_print_stack_stderr(GjsContext* self) { auto* cx = static_cast(gjs_context_get_native_context(self)); g_printerr("== Stack trace for context %p ==\n", self); js::DumpBacktrace(cx, stderr); } void gjs_dumpstack() { Gjs::SmartPointer contexts{gjs_context_get_all()}; for (GList* iter = contexts; iter; iter = iter->next) { Gjs::AutoUnref gjs_context{GJS_CONTEXT(iter->data)}; gjs_context_print_stack_stderr(gjs_context); } } std::string gjs_dumpstack_string() { std::ostringstream all_traces; Gjs::SmartPointer contexts{gjs_context_get_all()}; js::Sprinter printer; for (GList* iter = contexts; iter; iter = iter->next) { Gjs::AutoUnref gjs_context{GJS_CONTEXT(iter->data)}; if (!printer.init()) { all_traces << "No stack trace for context " << gjs_context.get() << ": out of memory\n\n"; break; } auto* cx = static_cast( gjs_context_get_native_context(gjs_context)); js::DumpBacktrace(cx, printer); JS::UniqueChars trace = printer.release(); all_traces << "== Stack trace for context " << gjs_context.get() << " ==\n" << trace.get() << "\n"; } std::string out = all_traces.str(); out.resize(MAX(out.size() - 2, 0)); return out; } cjs-140.0/cjs/text-encoding.cpp0000664000175000017500000005142215167114161015263 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 #include // for strcmp, memchr, strlen #include #include // for nullptr_t, size_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/auto.h" #include "cjs/gerror-result.h" #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, Gjs::AutoError 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) { Gjs::AutoError error; Gjs::AutoUnref 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; do { Gjs::AutoError 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; Gjs::AutoError error; Gjs::AutoChar 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; Gjs::AutoChar 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(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 { Gjs::AutoError error; Gjs::AutoChar encoded; 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-140.0/cjs/text-encoding.h0000664000175000017500000000156315167114161014731 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 #include "cjs/macros.h" enum class GjsStringTermination : uint8_t { ZERO_TERMINATED, EXPLICIT_LENGTH, }; GJS_JSAPI_RETURN_CONVENTION JSString* gjs_decode_from_uint8array(JSContext*, JS::HandleObject uint8array, const char* encoding, GjsStringTermination, bool fatal); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_encode_to_uint8array(JSContext*, JS::HandleString, const char* encoding, GjsStringTermination); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_text_encoding_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/debian/0000775000175000017500000000000015167114161012446 5ustar fabiofabiocjs-140.0/doc/0000775000175000017500000000000015167114161011771 5ustar fabiofabiocjs-140.0/doc/ByteArray.md0000664000175000017500000000647515167114161014231 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-140.0/doc/CPP_Style_Guide.md0000664000175000017500000010675115167114161015244 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. C++ style comments are preferred if nothing needs to come after the comment on the same line. 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, annotate the variable with the `GJS_USED_ASSERT` macro. 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 GJS_USED_ASSERT = my_set.insert(value); 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.) #### Function annotations Annotations go on a separate line, _unless_ the entire function can fit on one line, including the annotation and the body: ```c++ [[nodiscard]] std::unique_ptr get_complicated_thing() { return compute_complicated(m_int); } [[nodiscard]] int get_int() { return m_int; } ``` cjs-140.0/doc/Console.md0000664000175000017500000001554415167114161013726 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-140.0/doc/Custom-GSources.md0000664000175000017500000000247415167114161015324 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-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-source]: https://gitlab.gnome.org/Archive/gnome-devel-docs/-/blob/703816cec292293fd337b6db8520b9b0afa7b3c9/platform-demos/C/custom-gsource.c.page cjs-140.0/doc/ESModules.md0000664000175000017500000001635515167114161014165 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-140.0/doc/Encoding.md0000664000175000017500000001063715167114161014050 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-140.0/doc/Environment.md0000664000175000017500000000576315167114161014632 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. * `GJS_REPL_HISTORY` When not set, GJS persists REPL history in `gjs_repl_history` under the XDG user cache folder which is usually `~/.cache/`. Set this variable to a writable path to save REPL command history in an alternate location. If set to an empty string, then command history is not persisted. ## 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-140.0/doc/Format.md0000664000175000017500000000615515167114161013552 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-140.0/doc/Gettext.md0000664000175000017500000001542515167114161013746 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-140.0/doc/Hacking.md0000664000175000017500000002047715167114161013671 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 "mozjs140" 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:
Ubuntu sudo apt-get install libmozjs-140-dev
Fedora sudo dnf install mozjs140-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/esr140/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 gjs-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 js140 ``` 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 There are scripts in the `tools/` folder that allow running various checking tools on the codebase. ### 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/esr140/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-140.0/doc/Home.md0000664000175000017500000000013015167114161013175 0ustar fabiofabio# GJS: Javascript Bindings for GNOME This page has moved to [`README.md`](README.md). cjs-140.0/doc/Internship-Getting-Started.md0000664000175000017500000001357015167114161017447 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-140.0/doc/Lang.md0000664000175000017500000000611415167114161013176 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-140.0/doc/Logging.md0000664000175000017500000000570715167114161013712 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-140.0/doc/Mainloop.md0000664000175000017500000001356215167114161014100 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-140.0/doc/Mapping.md0000664000175000017500000002524715167114161013720 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-140.0/doc/Modules.md0000664000175000017500000000154315167114161013726 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-140.0/doc/Overrides.md0000664000175000017500000007634115167114161014270 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, 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-140.0/doc/Package/0000775000175000017500000000000015167114161013324 5ustar fabiofabiocjs-140.0/doc/Package/Specification.md0000664000175000017500000001775215167114161016442 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-140.0/doc/Profiling.md0000664000175000017500000000106415167114161014245 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-140.0/doc/README.md0000664000175000017500000000752615167114161013262 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 Core Apps * [Characters](https://gitlab.gnome.org/GNOME/gnome-characters) * [Extensions](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/HEAD/subprojects/extensions-app) * [Maps](https://gitlab.gnome.org/GNOME/gnome-maps) * [Weather](https://gitlab.gnome.org/GNOME/gnome-weather) GNOME Circle Apps * [Biblioteca](https://github.com/workbenchdev/Biblioteca) * [Commit](https://github.com/sonnyp/commit/) * [Decibels](https://gitlab.gnome.org/GNOME/Incubator/decibels) (TypeScript) * [Forge Sparks](https://github.com/rafaelmardojai/forge-sparks) * [Junction](https://github.com/sonnyp/Junction) * [Polari](https://gitlab.gnome.org/GNOME/polari) * [Tangram](https://github.com/sonnyp/Tangram) * [Workbench](https://github.com/sonnyp/Workbench) ### 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-140.0/doc/Signals.md0000664000175000017500000000456415167114161013724 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-140.0/doc/SpiderMonkey_Memory.md0000664000175000017500000001536615167114161016267 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-140.0/doc/Style_Guide.md0000664000175000017500000001007215167114161014530 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-140.0/doc/System.md0000664000175000017500000001422315167114161013601 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-140.0/doc/Testing.md0000664000175000017500000000143015167114161013726 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-140.0/doc/Timers.md0000664000175000017500000000362715167114161013566 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-140.0/doc/Understanding-SpiderMonkey-code.md0000664000175000017500000000515215167114161020442 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 will fail assertions in debug builds. - 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-140.0/doc/cairo.md0000664000175000017500000001172415167114161013415 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-140.0/eslint.config.js0000777000175000017500000000000015167114161020557 2tools/eslint.config.jsustar fabiofabiocjs-140.0/examples/0000775000175000017500000000000015167114161013042 5ustar fabiofabiocjs-140.0/examples/README.md0000664000175000017500000000012015167114161014312 0ustar fabiofabioIn order to run those example scripts, do: ```sh gjs -m script-filename.js ``` cjs-140.0/examples/calc.js0000664000175000017500000000706215167114161014307 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-140.0/examples/dbus-client.js0000664000175000017500000001132715167114161015615 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.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.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_unwatch_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.gnome.gjs.Test', '/org/gnome/gjs/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-140.0/examples/dbus-service.js0000664000175000017500000000664215167114161016003 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/gnome/gjs/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.gnome.gjs.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-140.0/examples/gettext.js0000664000175000017500000000135715167114161015072 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-140.0/examples/gio-cat.js0000664000175000017500000000133315167114161014723 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-140.0/examples/glistmodel.js0000664000175000017500000000731015167114161015544 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-140.0/examples/gtk-application.js0000664000175000017500000000721715167114161016475 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.gnome.gjs.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: '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-140.0/examples/gtk3-template.js0000664000175000017500000000311015167114161016054 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-140.0/examples/gtk3-template.ui0000664000175000017500000000316215167114161016064 0ustar fabiofabio cjs-140.0/examples/gtk3.js0000664000175000017500000000501515167114161014251 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-140.0/examples/gtk4-frame-clock.js0000664000175000017500000000520715167114161016436 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-140.0/examples/gtk4-interface.js0000664000175000017500000000152615167114161016213 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Sonny Piers import Gio from 'gi://Gio'; import Gtk from 'gi://Gtk?version=4.0'; import System from 'system'; Gtk.init(); const file = Gio.File.new_for_uri(import.meta.url).resolve_relative_path('../gtk4-interface.ui'); const app = new Gtk.Application({ application_id: 'hello.world', }); function onclicked(button) { console.log('Hello', button.label); button.get_root().close(); } app.connect('activate', () => { const builder = new Gtk.Builder({ filename: file.get_path(), callbacks: {onclicked}, objects: {app}, }); const {window, button} = builder.get_objects(); button.label = 'Naturaleza es maravillosa'; window.present(); }); app.run([System.programInvocationName].concat(ARGV)); cjs-140.0/examples/gtk4-interface.ui0000664000175000017500000000135415167114161016213 0ustar fabiofabio app 400 400 cjs-140.0/examples/gtk4-template.js0000664000175000017500000000335015167114161016063 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_uri(import.meta.url); const templateFile = file.get_parent().resolve_relative_path('gtk4-template.ui'); const [, template] = templateFile.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-140.0/examples/gtk4-template.ui0000664000175000017500000000316215167114161016065 0ustar fabiofabio cjs-140.0/examples/gtk4.js0000664000175000017500000000357315167114161014261 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-140.0/examples/http-client.js0000664000175000017500000000332515167114161015636 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-140.0/examples/http-server.js0000664000175000017500000000262315167114161015666 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-140.0/examples/test.jpg0000664000175000017500000010705715167114161014535 0ustar fabiofabioÿØÿá(ExifMM*‡iÿíˆPhotoshop 3.08BIMkZ%GæIhttps://flickr.com/e/15q%2Flj%2B1Yoctl4ojKlDbYKTvTnar%2BgqihS4gI1hIHfs%3DÿàJFIFÿâ XICC_PROFILE HLinomntrRGB XYZ Î 1acspMSFTIEC sRGBöÖÓ-HP cprtP3desc„lwtptðbkptrXYZgXYZ,bXYZ@dmndTpdmddĈvuedL†viewÔ$lumiømeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ óQÌXYZ XYZ o¢8õXYZ b™·…ÚXYZ $ „¶Ï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 VPWçmeassig 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ƒ¤Åå'Ij‹­Îð4Vx›½à&Il²ÖúAe‰®Ò÷@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/‘/Ç/þ050l0¤0Û11J1‚1º1ò2*2c2›2Ô3 3F33¸3ñ4+4e4ž4Ø55M5‡5Â5ý676r6®6é7$7`7œ7×88P8Œ8È99B99¼9ù:6:t:²:ï;-;k;ª;è<' >`> >à?!?a?¢?â@#@d@¦@çA)AjA¬AîB0BrBµB÷C:C}CÀDDGDŠDÎEEUEšEÞF"FgF«FðG5G{GÀHHKH‘H×IIcI©IðJ7J}JÄK KSKšKâL*LrLºMMJM“MÜN%NnN·OOIO“OÝP'PqP»QQPQ›QæR1R|RÇSS_SªSöTBTTÛU(UuUÂVV\V©V÷WDW’WàX/X}XËYYiY¸ZZVZ¦Zõ[E[•[å\5\†\Ö]']x]É^^l^½__a_³``W`ª`üaOa¢aõbIbœbðcCc—cëd@d”dée=e’eçf=f’fèg=g“géh?h–hìiCišiñjHjŸj÷kOk§kÿlWl¯mm`m¹nnknÄooxoÑp+p†pàq:q•qðrKr¦ss]s¸ttptÌu(u…uáv>v›vøwVw³xxnxÌy*y‰yçzFz¥{{c{Â|!||á}A}¡~~b~Â#„å€G€¨ kÍ‚0‚’‚ôƒWƒº„„€„ã…G…«††r†×‡;‡ŸˆˆiˆÎ‰3‰™‰þŠdŠÊ‹0‹–‹üŒcŒÊ1˜ÿŽfŽÎ6žnÖ‘?‘¨’’z’ã“M“¶” ”Š”ô•_•É–4–Ÿ— —u—à˜L˜¸™$™™üšhšÕ›B›¯œœ‰œ÷dÒž@ž®ŸŸ‹Ÿú i Ø¡G¡¶¢&¢–££v£æ¤V¤Ç¥8¥©¦¦‹¦ý§n§à¨R¨Ä©7©©ªª««u«é¬\¬Ð­D­¸®-®¡¯¯‹°°u°ê±`±Ö²K²Â³8³®´%´œµµŠ¶¶y¶ð·h·à¸Y¸Ñ¹J¹Âº;ºµ».»§¼!¼›½½¾ ¾„¾ÿ¿z¿õÀpÀìÁgÁãÂ_ÂÛÃXÃÔÄQÄÎÅKÅÈÆFÆÃÇAÇ¿È=ȼÉ:ɹÊ8Ê·Ë6˶Ì5̵Í5͵Î6ζÏ7ϸÐ9кÑ<ѾÒ?ÒÁÓDÓÆÔIÔËÕNÕÑÖUÖØ×\×àØdØèÙlÙñÚvÚûÛ€ÜÜŠÝÝ–ÞÞ¢ß)߯à6à½áDáÌâSâÛãcãëäsäü儿 æ–çç©è2è¼éFéÐê[êåëpëûì†ííœî(î´ï@ïÌðXðåñrñÿòŒóó§ô4ôÂõPõÞömöû÷Šøø¨ù8ùÇúWúçûwüü˜ý)ýºþKþÜÿmÿÿÿî!Adobed@ÿÛC     ÿÛC   ÿÀ ÿÄ ÿÄJ!1AQ"aq2‘#B¡± R’$Cbr‚ÁÑð3áñ%DS45TUcsÂÿÄÿÄ7!1AQ"a2q‘±ðB¡ÁÑá#Rñ3rSÿÚ ?ùâ°sŠÈädŽqŠS‘ÐmŠCä‚gíF)²K8Æ)ñ‰&—¾+TbAˆŸâ«´BÖP>•šh¾x"‘µ€ÀÛ `#ñãëTexˆàÕBQA>ô¦oÅÂŒzSBá…JzT¦Ñ0«Š1³Ì s[a,ŒM–9À¦ä#R¦MW$’Þ®™2j4Á«>QV‘‚)L5$e~uD@gùSQlŽGš ²cŒ¹¥„e×ÄQâ‰L^h4.>t ‘"˜™¬[Ϩ¦²ÒÌçÖ—.Z4^‡šæPnã4‡4|‹].r‘òÇzZ°lbΡt*u `ûZ¡†¹5EôÓг€¥AcÁϵø<Ë놋Re ¼š£Ø¦jñìÏ¥'ifÊN­?–MEäBÉ~AïLÚ#p‘©ëGjæaÔ½¨;˜“©ž9©´›˜ƒ©Ÿz› ¼;FœÝ]€OxŸ']éÝ5LiÆMeš:5H¼Xé!|#ô¬í²/¨ô !fTÆH¯!›àó´k’9­2gŽì !ïJy`%ÀPG­ÂG\ÝnÞµB#Þ剭*82WÀpkv(„v9ÊÕQ\¥Éõ4·ÇwœsKp \ç‰DÑŸ­"Aˆ`{Vv¹ØŽÆ­Nã (4Ö‚ bâLšã¤cÉfrÇå[#Ád!1škGvägLmÓ#µY—âÀô¦G0V€cÈ©Œ°;x¦" Œbƒ.:¼ñTFä«&A¥Cš¶A^_=ª­•kˆÍT¨ì0ù‡šº!)g¤™øH¦'‚M#Fh~=¹úÕ$ÈtŽýÕÜNÃZÉ"ÈéñéÝH¯O û^câòXl-´xظ>Þõ²jesªµ`OÒ¶FL|Ns{!±÷ Ù®%C[…ˆn)-–kƒŸëP±fàЋ0ÍrVî!`M=Ø#4@$çÞ¡ Ñžh€šé†)x¹>µVz£­Vêˆ`N;V9rÎ\;NÒÁYßs§ÄWVZ%½œ„*Ñ$»> hÚyæß>⧦L MrœÕã I7qOKeÏ4Ì—5Š™!¬ $6AX?JE *~UVBÙ‰5šh„­¿q“Íc™¶§5ŸÈ'rE>(°4oñÓ¤‚p g ¸.9 ž0ë¶Ÿ’ ^i¡AG½.A6y ™4Ñ`d r`eeÉ«€Ä;¹Î*W­"ü>õFÂhœQÁ2fÒÕRÉ›DÇ"åUrFhŒ aö+²Œf‰RߤÂnA⬙1’Ó¦C»áG «w f°Ó6ÍÆÀÜüé2^A’ù£;CÚŀÆE<–+IšHðS¿"¯ɦ$Sh4^øÉ­išbŽk1UwSÜU\D­`ßô¥¶=¢“«Ø'ULÉ8d­]é ÅhR1¸‘²iÇ'оP¼¶ò«dm,ó£ ;S4S×5|A5l„À3C!4E@‰ÆjÙ¥^h6À†¨ØcLš „>Õ;Vi° 6ŠÊÈ-±Á©€àYƒuDð,6sŽh9ç€$ ŽÆ’ä^V}(n ×iÐAwl8ô­+Ž$€š-&6SJ|&÷ÿÒ†pY–ªÊÌíOÊ›¸‚ŹdPÎHkx;„K)ïG°S·j[ µ^sTÉ‚ U2•mÀÚ!-6·ŽA´–±íÈÁÅQŒÈÖ¤ÙŒÿ:ªCw2Qº{‚rsMBeÉGÎê˜9(Ó(ÅY<iã'µ[(†¼“Š™’˜ô«d"Yp*&!h䫉‘A°ò*™ äiÍÈH[FqÅf› Y ƒ¶éñbªß‘-kh‚GëYg´¼*¤6*&…T$Ö™gçN€{ö«ä‡PдµXãÆiÑä –$eT*•~+JˆÁÔº¡ŒÒ¤Æ=Ëߊj‡’™äîGK‹tRcýjêã“ÔzuÙÈ@*Ûø³ÎÞ/Ý6‘ir¹ÀU$°Ð‘åýT}æS»»Èù¿{ë°4·‘X0Ú€òj»I»"|åoZÀr…V2ÃÃ4mÔÐÈvµ°&ŽàmBM Æh³Š6¡&دj¶A· ?·dŸ±³9íG$ÚÊ"çÞœqÇâBǹ<%`Ò—¸‚M¯$ *Aš µhÈ€’SÐD‚3Dñ(ïéK“òK êGñQ²V±e› SGÇjFHeºݨJ\$Õ«ìQÞ°O’d-¯O¥%@9šè79§Æ dt÷ÅjŒx,Û‡ÉíZ`°U‘²Ç“ïZÓ*7äš¾B#aRx£ApPã4©G$ Žûæ“é>ÐÞ´‰CÉíçÅf’kdveã=ͰdŽ™=kT^Ižbš‚2çozvЈó0jlÉ,·JÃçÚ–ã‚í ÒU$Úxæ–LO@ŠGØJü#”øe’éÏÚ¤ #>£5¾ =Ê÷àôg†÷ku%@ÈíZ’Âc–tµéµEåCÈô’#µm0AnàJ(¹ä?´Fœÿuº*§œœÒììk­ðx¯QóWÈ#“X˲9wš¡0<¾õFˤ<‚¨Æ$>€žj»†$8\–Á„0êKýè Rs@‚€'½B`ÑCíš²aÁµO•Lƒ4ŒŽ+k<ðu´|ö¤M%‰AÚ’–H H «c5"†T\Žš,íZbø ÁB™’ ŠÕpõoZ«D ·|¸©®K@Àâ±Èƒ²>=*‰d™ àÿ*¬—%ãáEb}È u+(#4Ø,„ŽóØÖ¢ˆc1>µ|È<‹šb*#Ê-Ž*ù¿»úTRäƒolqœU÷i¢8«g$pÃÞ¬ˆ?i#Ž3U’D'lg ŒšçÛ•h5î7) w|d(ùšÓ€ãË•ø‡##žôø¢p13Iôòôô燷/L±’}±O…l îžô¶nžlyæ·B;E¬äôKéKm ¸ãÚ®Øä°Y^Ølâ†KÍ¢˜Ÿ#ÒŠ,5øáÓ‹}k0ÙÜìk©7Ö|7v’B#ÈÉô®{àÞ£’±?@É?ý)M‡Ò—£Þ?à¥ne•c'¦dðÕ[,«3ö ¯ðÕ.¡É¦ÑØz®Kl“LaéE08µ‹Ú˜˜§¡•R0@Àöª…!ÁlqÛš‹m–çÔPl*'.‚,ãŠèIžX‘†?,Vi<€îLdzмÒãkbžâ@‘6å¥`†y[û÷©œÁgÍOP€ó[l'Õ,€eÍ\p *\„6NE"K iI4¼`O‚>T¹G$%#¸;sXœy+‘‰ßÌ4ȬsˆÖ´¸,$“Æ*ȆrÔH=gçUlƒû¹ªd‡ |ûø‰â ƒ§tyùKH0‘Ǹˆsƒó®tº„\¶Ñøã>??ðuªé¶I)Zö'ïßò;«ý—t° ˆ°É©²ü1Éaˆ‹{pÙ¬wõ•§šŒ¶¶ügŸÐÛ_Hõ"Ú“ü¸ýO0x³ö^ñÁ褻Öôc6¬Tjv-æÁò-ŽS?1]=TÓêøƒÃög?S ¿J³5•î¹_ëñ9La8ï=¹ï]VÎk:÷€¿gÞ£ñç]»ÓtYmôôµ·Éw~®#äáUp9$çéŠáu£^‹bks“ÂHÝ£ÑKZå¶I(¬åž­ð[û5%¼–[¿µ¨@Š\G§éîYîäϰ¬PÔݯXÓIA}y‚íÁÐŽ‚½;Χæú.ß‹îz³Eû*øMÒ̳ZôV”ÓÆó%·G¤…qnË%<{¶lŒâßýuÆ9ú’W_ðo 5›X–û£tKô6EæYLjǰâ³Y²˜9T³õM¯ÌÐ’¶IM/Å#õרÁÏ"ÿEÑæé‹Ð‡ib_©NTþ”4½V-íŒäŸÕe »§Eòà—ÝÁÀµŸì¸Õ›UOØ=sc6šÜù—öÄH>_Á®õ=JSï׆Ÿ2Μ¢¾Yþ ‚•Ô_aÏú+Pe¶Ñ¡ê;(¿ Λ2å—ßcsòæ¶×®®oM?Í~fYèí‡Ùå~ü? <9Ô¬e[}SJºÓ®°RîŒçóÖ©FŘ¼˜e‰,œéŽ„ŠÚÊrkV+ŒHÐRÙÀ¢[n E‚€¨X9ÝBÔ!_Ö%X ƒ"ŽAךx½G\g4¹v5×Ã8Æ£Ñ* Ÿ/¿Ê³µ“¡`¨ê=™?»¥!¬ R+÷»îÿ•QÄfâç£@Î~”¼Ê"çéLgàªmrGMÓXÏÁüª®,=ÈÛžž p´R*ȹ´)WHV›D>‹P ü<Ð ý”Gj¥ 9‡ðÐÀQÇã‰TsNrg’$ÊŠED›!ss¹Ž9­q† ‡&šÈqId·aº³ÈxsÅ$€·*f›1Z´dƒÉVÄŒJm}"÷¥¶勚£dykZ^Ü”b ôvÈ>µ~Á¨*d‚ÕTfÃ51’¹û }š§ñ¨¢ë>¡Óºnɳd&À[™ü[Ou_CÛ5æºÓºKKKãùÚð½¿'¦éZx×U«ÿÏÕûþ>§Ó(ö鶪-â%Up±Ž@ôªïZJ³TsÇàŽ‹Né½ìf Öà nP¼²üAG¥e£CE©[ts)x/f¦È=•¾+ýØÒ®¬ÞÚ]>­Ÿ†Že ­õ½z:zn–0ÛÖUšËܲæWì< ðïKÔdÔmz3@‚òB Lº|[‰½8­ëOJ\˜·6ò£ÏÜ‹/ìm:(ŒP[Ã`~xÂåY5}=Ñq}¾†ª¬¶¶†ôý&ÓL?» —ÎwÃ¥Ñi´_ýiüÞæ‹µê%à} ·\¸V>™5©×Jù’~ÂT¬|ÜØÃ2r §áaÛ5†Ý56G•Çgõ4BÙÅýJþ½c.™g5Õ¢’#BJ§Å‘í\ n‘éàíÓ¬a?¯àu4×+f«·ÉDµë­RÊÞÞkν‚Øœ´Š`{ã½yHju5W*š¹é'¡¢ÉIBäØ`ñz>TpÏßâeäÕìë·Á*êMcȵÑ3óI¦*O Ôˆ†[q0oã’.)•k+’_ØTú$[‘d¶·µžÜ\Ûã1ú¥}“¥u(õTÒÃò|û]£zIíðIDG]³–hÞ¬Dò(f}]BŸŠdŠö©¬¡âÍ H¨j2¥É;ª¬tJæ£e©ÂŠSF…&V/´Urp¼ý*­ R ®úy‰â2*[Cˆ{®šçŸÒ–ÑtÈk¾——œBJ§òÈ™úVvÞ1ÿË#Ûáª;b‚ª‘¦ð®ô®JòÕ}h–ôX,ÞÞ!ÁCþZž¬CéHi¼0»;Hü¨;bL$ÎÂ5ùS¢²x²2ê~1š×ŒœšÐø£ÉFÀ xÅg“ övóU!£séš›i¤/EG25Ï¥È?|BªÙáŒmɬò|t¢ŽÝê¼äƒ-&ÓLQàÁæœc½1C7#Þ¬àL#qžõ]…‡EÀ5]„'Ö†Ò…û)}•µuë}SW¬ú .n–_.K–QÌqz÷<·¥pµýF4Káé’õ?Eþ}‘ÜÐté^½kWý«úv}UèýGè^—´Ñô•†ÏN²‰a·F䪀 õ&¼õ:ªi®mX²ÞrûþÛ= ꔥ¢øãTº•Ƨ+5­»‹xÎÇœðµÉ·WvºNZx5ZáËÁ¶4BˆâÉ|Ïœ(ïâ´Æñ¹öà=«×ÃUUÝß_Å•³8ì=¤n­÷»ˆÃÇ'çŠmzé]S”¥„órӪ刬™dAŒFÁ¤rÞæ«¦–`¡,É>ïܖŧ¹päXÈÃØrÖ¶©UF·Ý~ù3µ9',÷}Zc"˜¼¼ó(ø~gJÅ.¡L%=ënÜý®ßàzÓNI(¼çÛ¹ýa%Ý¢I£Qð¼ ߎxôÅråüC Ü¥()%æ/û<×M”’Iá¿soÕÒ>CB¯Ø«>8÷£.»§²xŒ¢Ÿ³~qïØ‹§ÙÊm}ÁÖz¥èeFÉžÿ:êéõšmBj-f=×¹’Ú-©¦û?#7·«,mÇ+Å*W+±(üÑc#¼7†ˆèz{M”Èïo÷n~Åd§I¥›”ç–ý3Õ_£< _ôÅ®BËs€Æ):®NRqá—«[kYÏ(!:Z[[U‚ÕFCde¿­z~‘JÐÇÑKå÷8ºû¥ú¸Dý3|–çd±;ãðƒŠô~´sƒ‘éȤêS\ÛÜ´§]IN„ª¥)¦1MÝ÷Cô¥<¡ªcÜxú¡Y¤Øõ$+û“1åÒ•ÈÅ$uÑpsû±SîD\·þX?,Ue’ÊHù{>ày¯G\Ož‘—9<Ö®Á6¨*dƒñ.*Œ‘ÂØ uÇFe¸æ™’ÁêÏ)€u dì*ªIÔgk ŒUŸb1VVù Äóm&Ÿ’½ÎãZTH s1ÔˆGµËLÚ·$w¡´˜K¯UÄ8:ÿÙs»¼fѺOSº¸´±$¸™ícÞ̱€ÅøCvÝéšæë­zz·G»x^{šô´«­Q—nÿ‘ö+Aè’ÓaÒº{MƒGÒìã oooDAosüëçÒé:F¦Ëc˜öyòÿ?'²†¶ªëŒ1íìf®[ÜÛîò£‡Ædíÿ¯åzoP©ÆOj“\æK·ßß êê´óRÆZû‰w&£p^ Ê’~ÓÜWEêì×[š²”¾†ON4Ç| «ÙFš!-$Ø#Ë+{˜A&ºš›¡Ó£ JRÿLjå¬òßÓõ\]ͤ—¿qú­»ÎcFóqæÉÛß5ZzÚë­åöËíïŸÃé‚ÓÓØ£¹ñôDÏÝ ‘ÝLÎ28àSõñù×{᪔“ÔØÚú|©ýÿOÄæz“Iúk,:8í-àHíÈwŒϹ®¤!§ªµ |cŸÏ–e“¶rr³RÔy<%›ƒæ·ÃÌW#¨is\çSM¿üŸüTYó%/èAAÒñèñÆÚ}«`,‚+]»ùÈʃƒ‚IÓ>ÕÆ]>í4+•0Šk üŸ+Œ´þÿx:rÖüCjé¶¼|ßßÇÜWõÍ©DêláÓn-îde»Èér‹¸`ª·ÂNÞã?NÜñõ=;W59%ì|¾Ò\öÃã·tu4ÚÜMÉ8¯•a8¾<µÏ~Ì* šÖõ­ZIâ·Ûñ#ãŸCÜ ÿZMjT\ôÛ¤«Ç+¾Ñ÷K?"ç‹!êa9~ünv³º8_%ŽâüŸnÔ›ú®®vº~>GÎ'ÇÓ¸Ÿ††ôí]Ë6›£^\•’Y ¨9‹M}GÓõWbˤ“i}“ƒ~¦šó¬ãÜ—“HYØ«’ c Zô2ÐÆÇ¶o±ÌZ—RîmÙ!#|}{šk«g-‹SÏdSr‚µg ¬¢»¹ä®ë0ú•ÚÊŒÍ:›1ÚÉ$çÊ‹¥ä„~aî kðk"¥ ˆôÅŒá—*К|£;Êî–h=dÔ¶ˆW°ªà²d%å¢ œUF¦EÍŸJ.˜Üj¹Æ)n#T‡<”lñJhná lƒ°ª4YHdÆ«Î)n#Sp¸>›ž;_2îæf”±’8cc±xãw¹ÿ¿Jõô×J–ªosm¨¯²¸ã?ë„`—Q„§¶˜¬vm÷qÞÕŒ²Bq…Žu9_Jó•W­”TÔœ|%$øÇ¶#¦Ý9k ýQ5ÔÓ\NÈ“É,$bv„ã¸'ŽÕÄš‹nžÈÊR~éGê›íÁÐŒ!Å6’ŽEÛu¬fÇËóæIÏa,jÁ=óÎr3šÓG^ŠÓú[¤¦ûeE¥ïç9]Ñ[:l•›°±ômgÛð5kâ:›-µÔí`:ºŒ6çâÈàý+m=jýTUw¼G†±ÃXóžÏî}ÅÙÓ?öV²ÿ5É`Óï¬îm¥–Úï&4eV*7ãüL3Ï9æ»ú{(”%:¬ÃI¬µÏ¾_>ýŸ?^M°²2Qœ{¿Ãî_à¯uÝu»h`¹Äžk”·• \g;†xÎ2¿Ê¼®¯WÔÚ’o_+ǾVWßÇ_O RÜ£ÆqÝgîý>›ÓºR©Ó縸“6é²Y§–Ü~˜®†—¦¾úV÷5ó,ç»±Wë!>.KéÇŸ¼±tÿW—‘,­–ê{ƒ¸˜çÓËÆx€=ýzŸ­·O5í|ðò¶ñõ翃•©ÒBY¶ÌE{¯%¯H]u€–æî3‘Ÿ$F×½zŽ›W[öjìŠÏò¤¸üN&¦Z%òÕ÷ä2Ù®f¹’;›c•í(zkÐW^cbüN|œ#‡”ªüHÃ=ê(]„›L¡àÝå̦g]E”Ö±2ÕFɼĀ›¨ïðÚ­›[¬åC3.C>2,z×Zý]¶J˜Wµ>"ñžÇÔê--0‚²RÎ9kèD]ꚪj€ÝJƒã „©ý;W›ŸQëzmO§\7É.p¸áé´VÕºo ö÷'4Y…ü#Íø&Ú…íüëÞtΡfª¨»’RÆ^?ÙçµzhÓ'é¾Ê ³.sƒ]ܦŽ~«ØÉÍQŒDCÛå©ûMU–HÀ1ëJli²*¢Øä 䣀›4¦†¦11ÀÈ¥´]< ,Š )ĺ‘’Ä ñKqÁtÀ$ˆIÒdð^'Ä£ÛÀ®ÖãÆ )íVÉŸáÅÈA̘'šf-fªà†3ØsSóÞƒY!!k>ÓœÖiÇ$ ‡ìpi8 ™$ ê(¥’ <Û—“LH&[é÷wMˆávü©ŽQv20o²'ôÞ‚Õoˆ¹÷’zº¡ä× Óì‹E‚šÐ]ÁŽ}1YQ‚ìoK±÷,ZwÙöiß cïH}SØÛ“î[´Ÿ³ø€gýÚÏ.¡)áÓ!å²ÓÁXíÐ géIø©Kɧá#„m}4Û}à t˜­ÞK{ÙYᕟ#òÀü«Òôû•µõ<çP¯Ó°±x§Õ­Òößxf-hd #! ¨'ƒôÏzóýSQ*^è<Åþ¥Ž¡í|3›Çö‚·´_-nÈ\îQ!÷¯2ú¥õaG²}Zÿ‡•Ÿ3òö’‚K§xodˆYAôÁÈÕu ížøÉ®r¿ô6¿áõ\1$™éHõ +IÄÞmÃC囆Ôq_PÐé«Û 7™á<¾ë(ùŽªÉÆRŽ1µùéÒݼҭͺÁ»*‚ÙfðߟM Ú«­²:š¶F-¥—–Òþn8ÃñÏÞ.øÕE×,·Ëú}?ªt’fã™bŸiÊö&¼Ïñ'Mr•vW=©¼~gk¥êÒŒ£5œšCéÒuuwqmfM± †Ç8#× ]*í oQlç\"ß 9LJÝHk!z^œ”ž9ðE5Αyòàin¦—UsÎ3îþµÆ½7Wˆ¨î¶igjágëïÿ¦lSæXŠ÷`« Ç¡}…cKyCåî>yÐJôÖ_ÙŽ^>åçï(õÎRÆy~JkÜM,&K#rÏ5ьۄ2‚  }+ÊÒ¥¬®n¬¼ËÆxúüƹ%n8YÏ`c¯eÐ!º›¨´Õ]*Ù>9Ø”`GɃ?ÈsNjùÚé¿M˜¥gžÉ§ûì…Fš¥*-ÄŸâ°YºÄ÷Q‚; fÒåÆTEÊòè0$ç·zïtiÏM|%[xåcÎ?¾~§¨ie*å¾?à½õF¿q ýÒãKÒ†£©ÝL"6èÙ”©Rw`1Üû×ÐuúFeš:7Ù7†¼ã åýǘÒé–£t.žØEg>§\ÞhÒ[x¬#ÒÒç";„œI`¹ÚÇŒzöã½tôºíL#­AËÙå~| —O…ÊOG=û{¬aýëÜ^µ×¶ö>QFE'%ÐäÆs‘OÔëcSŽSò3KÒç|ežðW‡ŒP¬Š-¤ŠÙX2Íñõúwâ¹–uŠ£°Ò÷þæ§Ñn\·—ìMhý[e­¬ÒýëÏÜ0©?ÅùûR)¶½lfå,§á}<þ&khžÅ%¿ô}Z}6Rßxy!Œä€Ýó¯7Tuz F×cpgÿë²UîtZªúþÎþÃóëÇI¸6ë:™Ý†áêÀúzõZ}Lª²t¾{ûçÉÏ–›Õ‚šì¿¡=g3ËLŒ;šôUR›Ýƒd±òŽž{ÖÜcƒ05ÚåsA—Enþô[?=©màzå‹QŽnƪË$mŸqâ–Ñs{¸ª`ºb^=↠&<*LX÷)¥´1·²zâ©‚Ù5 ŒG4¹"ÉŠ‘3Ífš|KÎáÅu;3Ædfb€8¦G÷°)Ñ6M8˜N*¬°¬àq@¨¨òOŸ¥FLÖZUÝÖp9ϸ¬³²1îÆF¹Ë²,ºw@êW¤,¨?*Ã=]qòj†ŠÙø-úO‚×WDÎ}ë›gRŒ8GN®‘9}¢í£x«´É ý+gS“ìÎÅ]"ît ÁKX6–„~•†zÙϳ:ÐU×LðÚÊËE•f•“›5*«‰f²éÝ:ÕGÂ¤Šº‹aÌQ%­„=£\Õ¶2Ži‰@ÙŠ|`&SÈþ£_kó¼m£]L«¸ª``|ÉíLrxÜÄÊGyélü'èÛe½•Rþî]ònÈÉÇÈ]ZuõihÝ'Ë|}N-šyêîÛØãÞ)x—kÔ:Eô³^Çil‡c[–ÆðG_¥y®}BJØgn{}=ÏuÓ´ K,IsîpÔð—XëH#½Òµ­-åWlÁœ–Î3ðúzóÚ»RЬoÏ¥–±Uò¶vo²ïv=7Ö·þ¿¨¾«¥mK+3m²(äu9‰c¸£îî5tÊ4ê÷dÖ\{“Çkµ…MOåŸwý¿Ïäzä9Šá¦\“½{7 OÔ]Ùó ÙŽÇÙ¬¢WÝ‹5hµ6°° ­Ú ëTb 4L\}qŠæõMuuE㘼ÿcNŽïFm{¬6òÍf  YdQ¹O$qÈ­»«\r×%Ôgß„GÇÓ–ú|¬ËF¿Ê¸´tM–×m5¤ßïð7Ï]m±Q”»Σh´Ï4Œ„oÄd·þØÍbÖÕV‘NÜ>{ã—ù´²•ø± >‰©a$QHö •+"¤ó“Y*ÑÂÈ5RÂãéûúšÞ¦UÍ9òyí¡àß_ø¹©ôïJtž¬êºF•k6±ª_¢~îWl$pFY€š@«#l#pãšìÓGÃæQŽécýþð3NéÕÊ1ÔZªƒ}ÿ¶?»à›û!ø]{<Û6‹&4tkYæ¿·òïçrFc™ÙD cºð1´Xôú[µ9I(¥ì±—ìüžÏ«ßÓ:^šÓüòk†Þî=׌þüÚ.€êÍ3ªíõfKÞV+§Ê]V 0ŠìPlà÷íó­éöGR¯µ®=¿CÏË«h®Ò½>6øûKÏ–¹äíâ–aÒ°i]«jWL²Û”#tCÍŸLTâÉö4®µ©­T«O2ý>¢:ŠøêÍb+9ÏŸ¡RѼ@µ*l-åžg”·Y?„Àçú×&:È*=5Ýžª}:Vj}ia%ãîò §tõýååÍÍêÉDЪGøQ\ÿ‡våÏ…ãØèÛ:”crüÿ¯¡=g|º-Ü·Zmã@ÊT:·Ã½ÉÆÕîN{ Òj”´z‡-;~3žÙöÿÑÌ»§GSZ¨å>&ý§¬l>ýk6±{ÓWQJð§ÝmÃÏvP¾ )P `‚2;שŠZ¼Yã”c‡Eø\+»>Ùãò8ÿ…~(kýsÖ½?mÕGqbÚØ²{»‰7y;Ø-Œq’öÍ^z*îš‚–3äô^‡Àh'©ôrâ›Û÷Kz+ /:GLûέ>ªê1¾eÛŒ~gúסÑh壃ƒ›—Þ|O¨ëá®·Ô…Jq>ÚlƒÓ5ÑÁÉÜ 6Ÿ)lÍU¦]I¡Ðn¥FÛ ¥J,ÑÇ܆ÓôÛˆy‘º·c‘A&]µîJˆQš&Ô`â”ÐÑYªà²•7*Œ`±m=»Õ.˜,°+ŽE)—L ­V6â“&1!.ZÍ9"ˆ€Øæº’õÊ»ªÆ޽=sûGDéß*^å\[zÃ}ŽÝ]å#HðvÖÍFè”|ñ\‰ëç6uá ª ±jÓ:#O±#*¼|©.ùÌj¢¸öE†(4ÛñJQ”ØÖãÂÆ·b˜T(úV¥¦fwjAPjÉ*|Ÿ•:<”vðJXhz¶¹ V7$ú¢~½«}zK,û1ÉŽÍUpûR.Ú7€ÝYªmi ŽÑO¬¯’?!]*úMòïÁ̳ªÓÜ—­ì¿9ÚoµFù¬þuЇEóÈçÙÕäþÄK¶“ötéû<’K–üÙ Ïå]tÝ<£¨ŸœkZfŸáäÑäXAÍ0í*+Áu/ø™S^š tŸŸo8ú®•TµŠnÙ<#‰xí×éw»¡U–ØÈR"½ÕÌÕk#ÔT6-©vüOcÓzk£2|äòUõ]÷PÀ°E–@2ŸÉî¾}éÚ}<4Üø:Ö9&’\{ÃÿôúQ-uË•¹hr-bLyÀžH` ‘Œ±'Ÿ•o‡PŽÆ}’)ªÓJì8¼{ä´øö®Ñ5n¯ŸH×¶éÖ·ònƒRžP°¬Ý„nO °là|ÓºU–FÙGPø“ÏÝôû?Ö4;ªSÓó·ºþç®#½†ú%¼ÑJ«ÆQƒÔWÐ2”r|áÁ©að’FH4"°òQ¼ŽK( [ É«·ÁT¹À$}Gc÷¥µóÔܲ±XÁåÀäàzàwÇjƵ5¹8gŸcSÓX£¿—«~ÒÝ ¤ul=='SZU®†5’D‰Õw’ERˆp;3ž;×>zØ·•Ùš:m²_gº ×ú›NÔ5+hõ|ÆQ9Xˆ`cõ$ûsYõÓK#è¢Ê ÚÓñ Óz²Î\ˆ2Q2ßí!Gú|èPÜ\³ÛÁKh—ç6¹ûvxeÑEu¡júב{Üò%»Éõ ¹TüDsŽø"ºÕ_òç,é“”°šÏ±ÛzSÄí¯´ÛMkF¼·Ô,%­åœ¡ÖN1´°õeO#➬O”Ž}šk)nþ !x«£ô®1ÔLwé^´ö µá+ñDðrxÁ湚½mtAçÏs¥ é·êm^Ÿç>ßSÁº¿OµÆ¹0H%´{ì–“LnD ³á‹ÎnIR ò=­|ÞË7ØÙõšÔ¶a¼ý}þ¡>ø†|=×áéþ£–ÚÞà"¼7Æ&h/ ,@nUÁXÄpyÐô¤¶ÛRù_ôýøfxÆÙgOg-vÇŸß”;ãßÚ&óHÓ®¬zf;»3Ò/æA‡ ø|½Ç9íñ8ÏÖºúm6ïµØéz/ORkæá~g>Ô:ï¨#ÓäýA¨]ês|sÏ4€Éµ³È`8ôÅOB>£mit¬.ÄTZO-· x mÎ0©ŒlûðZ½bðÍÚ˜ÊPXYR­_.#ÙÃ=Üï"3¶ÂÙ@~,ùäWFSIí38ÎÊa(åá¬ýWž¬eïˆÝo¦ê2ƽG£Ûà ä+)ºmÂK¸þ,†>Œ=ëÐhõ+Sü®ù?>tIt}cÚ¿ëž\_ê¿Ðíb·ž<ÞÕ>•%­ã~ê*e´ØºÒ¡¦éûY{Æ¿¥ &Ú#î:>ÝòTm?*£®,b¶KÉqÑ’û¹?")n¯f:7µÝw=7{?p=©.™!Êø²&êÎhŽ&_ž)2Œ—thŒâû0@RXø€Ê{ÖY{@Ò6ešÁ¢,ø“ei5ÇÄÍùWZÉF=Ùã%.ȳi~êz³."`ʹÖk«¬éSÓ®·ÁwѼšM­$Džù"¹Võ”¸Lô4t6þÑ~м †¥â˜®=ÝZsìÎý=&ªûCðÏNÓcãSëÚ¹sÖÙ3¡5uð‘e·Ñlíñ²ùR7N}Ç,D7 ¨Qô¦Æ¦QØ!Ý›¹À­¤K°c€qÞ´F¬Š”Çì|0ê¾´`šn)FãÍ”ZîitSŸÙ‰ÆÔë!ò:Cý‰u£ÇPjÆ0y0Û.?RkÑ×Ó¿óg³¨¿äG zGìíÒ+yv <«þ²¿t!£¢¾ÑÉ‚z»¬ï#¢Yè6V« º ‚®+bIpŒ·Ü5bUUò¢TÊ…MGáïP(Š¿é»=Jà\ߨ¸ÙÈV.+“«ézMtãfª n=³àÛN®ê"ãSÆO&xÅÒ‘EÔo" lî·ÊŒ‡%O¯–ëts§VáGmµôúf躯[H¥/µ'õ8݇G˧ÞçÈÙ´»ƒ¿ =ùõú×Bä§FeÝíÚßÔç÷Ô´Kêk#‡y6‚ãºî=Ž@ÈÍlÒV¤°ü ›RO%‹¥z¥5Í7W†å‚j:”Kk§Û6%Ñ8ó@'øAÛ“Áìzu×[ÌRî n’Mz•G —/¹{/©é¤ãû7xGg Yj­qw “^Ü^qšáÈ,ÀUç…Éä×n i «‹>{¡ÑÿÍë\§Sé·öº4^ªÒä²Ã•:Þ–Â{4àŸÞ¦w©çfÿ§¥:½O:Vþt[ ›×³îŸ¶{~‡o×|H±¾[Ñu+;ñ3++ÙÌ· "•'õ4^«Ó‡vy½Mœœ±i/~0žé}{â¯YG©\ØõLN«ov.%xÕ¯G*¬¼ã³ü=ËßØ×ªuþ‹Ö“Ošê-—ÑͶ‰,ùÍÊ„“2(8‰Š·å¹Èøhên=k&û´Õk+Åœ5†Úïîu~¦ë{Þ¤ÖÖ}^Ú(¥³2cq€sž® üþUá®×ÎöçjÃGr­ZHl¥÷"úCK¼êMNK«Û—ºh2±y²åFNHÉ#úÖ-2öçýûZêqÎ9ö¬Õèúvµ¡n:ž‹|lo.–0E¸qøwg ûÀ¾„ ÷¯gÓ6;r|5ýWíœÎ´îÒQôØÜŸ/Ù?£úàá]0uî¶»iõ^{¸bÎÆ–mû$<¶@ÏÓŠô6l¡m‚äÉÑ©Öu);õ6ü±úù:^“wo¨Ù]ÚÛJÒSÊ•æQ–É9ñ?!\« ã-Ò=õZº­­Â¦øã/Éb²C¯Imj¥d†"f…»gü^˜õ¬³ŠOƒ¯§ÅÐåä½ø}¡tN¥e¬—ѯ/ ‚c rY]2NÌ@ËÄ¥¶ä’0¦I®„+£ºÅÏõ¦»*„~iyiöüH>–ñ ¨xK×ñøŸáö‘Ô‘BÖÿ{Œ‰#a€$RUöû®àqò¯]¦»â)¸ÆOÊýg¦¾“¯·Då»kïô|¯ÇŸ©o­X9¨*Ê„2¡ ¨CEw1-Œ2çr)JÉØˆ¾é +¼þì)>£ŠTª„»¡±ºqìÊÆ¥áË®ZÚSônk$ô‰ý–m†±¯´Š¾¥Ówöï·,¾ëÍ`³O4» õ˳>nh>ÙØ¬fELLW‹·[dÙê)éÔÕà½é?§iª6ƤŠä[9˹ٮ¸Ep‹%¤QÈD ¿!X¶I¼šw$ƒBG 刧ƖÌòµ K}‘Î>Bš´ìOª…}ú-¿ˆnö§B–ŠÊÄû Ëp9$ö®…u£4¤[zÃs¯e i ‚Ç<ÜÊ0¸ù{×WO¡³PþUǹËÔk«£»Ë=Ñ~tÿMˆÞéýàäÉ0Ï?!é^›OÓ©«—Ë<Æ£©]w á{G°°±R1è+±’Â92““Ë&—n>Õb¦ñš„2¡ À¨C[jB.2}I¨B­ÖZôºD°Â ˜n•ŸU w®fºùR’_Í”u´Xêw7Þ8gê [ÝjÛjù‹&KHxÚ=³^ØI^’ç'¿Óê£]2Ï µF—¿tšG·w.ÑËñmç%Aö'ži{9q_ešªê>´—¨¹)½aÓ3AÓ3ºÌ,Õ˜Í!1îVplexÎ+¥UN¸'ÇwGª„®Æ3àætF­¤O£I¤¤vòÜ\¤ö’K8|DWq.1’§<‘ÉÚ}Æuqº'¯§S\ëœgÊK?äì~0éVÚÿGZIl>èª"’E#ËuÁÜ=Ç'¿¶+=žª”l]½Ï1Ñ¥éj,©<çã.—Eš’ãÀáÝwâ/‘4V@n 1¾Xs‡8É‚1ßô§f¦ˆ¼c¿Õ†¨Feo§º–ææÓS{[xäku_4,ß,q]§VÄ£î£'ÉSûEõ|ÚƒvºtÚ•íºÈ¥rÍ+;gŒc ëÏλ¹5'Â<ïU¡Ê"åµþÁ|_îþ«kiÔQCìûc-’DUC0RXœðsµ¾e›é]½n65%øÒU-¸]ß“¶ôŸDÝxq=ïß#š4wÙ̪Ä÷Áý!ÜXÜ ̾õÛ«CdäÏXÓÀΙ×-©q‡úÕÞ’(ªÔ¹ý κøˆ8ùÖ `¡ØÝTœ‘ÞüðSûÐbÖ5”+¥©ÌP ÇÜÿ³ýk¯ Ðz«Õ³ìþ§+¨kÕ_õWßÉé»K}6Ù!·"‰ÕDWªQQXG’”œžd.GÉÈ5ƒBV¦ p©q\àz*M•´×Õð%|Å]Ik¬71Î2Œ¯Ü•e@ Ï4p.é(ùÐmEe–I¾Ç4êKѪj¦cñ$YXÐú|ëÆku‘ºî;G„zr¦¬{÷*÷¶!.!ó"w‰ËwÁ®.¢-N;“ÃÉѹOkåqó¡h™K)þ•¢ÊØÌ¾®×¹ך:{iIR¾[påô®µÚ›ÝìÓ䲆$·½”‚>œ®1úü©¶%·tM ©FQä¬ëÚmµ·Ÿ«XN¾M½Ôv¢xÎÄw;¯¡‚¼zVðÔÁ BsõWrK©à–úß]ê‘#ÓÕá6j¬Hb23î=JÓ5œÏêu\¤âš~?@Ÿ ¼×µ(ãÉ´Ó³ yö¨ïQáT¹rÍCÓf@Zbqè*ÑÔ8ö¨O¹yÑšœì%N=À­°ÕÙd–š| Û€ÓìWà›LåË`tB<$]ú/¥¢Õ5Í7MQq2£eî5)OS|k÷en’ÓÓ+=‘ìëkXtÛ8- A1 DU྆¢ ”cÙTp šòˆ÷‘ûiÞÞ@Ë‘ô5dðXmµÖûäþ´ÜðP›]hd'ñjC·Um‘:Ž©-ðÁìxǵpú„í±(dz7éá¼¾äÕŸ™~›U#’+Î]§Q¾*†uë·5¶ÐÕΓÍÊl”ùуó¦YTnšP—Í‘µÁe®"iZ´Nïf,¥@>o›>•…U¬‡ö㟿ïì¢\ã9þƒZ•Åük Û$ŠÄ2ü,¿3ïV–·QS„-‡Ïlª¶åvððç „æÌpÁŽ 0¸ïÞf¥R•-üÏõ5n•¯„IØéåš¾±°—OÒ§:¶¢Ï&`HÈcäWk.$yö¬ÖÇ)z­Þ“X^;wüîp'½vbóS̆TÀ ;mÏ94ÈAm9wÏæiYj¦Ô@'IÅ»LlmŒßqžß*.9®åÝBÏÄ1­jÌu•„ÙJ=“¨;!¶+Ÿ©ûW:Uâ[Ù®¹ÇnØðt>“·º“£|ø¤k/Ù£žkéŽËˆ÷a`ÁÉSý{S¬®ëvÊè½D¶¥–—\¯~NÏ{©ŽŠèè^[\ÝCñì„üÿªœûaüéÖ鬄#é÷<•{57Käxýäá÷WuV‰ _ê—JÝÇu*;Üß@¬­Žc‚ ïíé^oS¢“‹]Æß­ÔJ·Tc„½žG:þþö;;·Ó'¸ŽY~(ÔdÄK ‘Æ2+§Ó«nuÁeÇžÆJë’I¾³g¡jÓ›Cz¶z¬XØv¹&A˜‡©ã“^ûM¦”¡‰p׃sÓk£Z·gÈÿ™ã}Hû™î^Æþãï®e9ÛBwPyÝ´w½gŸ¥“ãuI>ñ~W$'UtM¬M½¬ÖhG)m &<(ÜþÀ\ŠL¬ôâÔÐͦ^¦ÖP&‚á´;ý#î1Úi÷[\ÈçqD<ü>®ÄŸÊ¥w»ªÄ{ª½dp¢ßn×ÒaÕšwOé}1aam Ì1ǯ“ 24x ¤ó€Äó]xÎ\V»ý?©Óè=Ij®›kÎ_VðoìõyÓLºÆ½ek&Wx¥+,pïøqÀÏæÕÔÒhUSõ&¼Gø‡øª½n—átr}×=–Ô»~/ô=÷5p«°|µ¿, }snSŠ d9m*U©‚dIµ–.ãó¢ئ’?â5¤ËÃŒŠ¶ÊJPøº€É•$š>jÜÊCíAšøŽæûcqço1¬—̘OaïLŠRx*Þ;ƒ›û«¶'cùVÈUó#4¦äøŠäÆ@?¥hU¹sNI›ò­šÑnàKž9cžøÁ¤iÞ4tÎ;”šæëîêÌ02°^~g󮯋Lè²7>Éÿ£‘¬ÔFèJ•ݯӓݾ_Þ-•Ó“Œ×³’™5„’ºI>/0Æfað–åqô¬º…o.Mn}›íù †ßol ¾•Ø©áWhÉõç’*—×^¦nϸÆ_õÀꬕkìÓǦĮêÆ(Ï2·fQŽ@öæ»¶Åɧ…åù_à\c+%…ÝøÖìmï­–e…® ñ˜{œöþF†¦¨X•‘ŽìvÁÔÑ_:å±Ëï“‹ø½áÌZ—K\]Jä˜d1òÐà¦@ç9çÒ˜£Š÷K†ÿ¡ïúGPkPªOå_×ëøO½þçùºÜi|˜-LA¿yÊ«³Üž}ÎkMr”óè©k1M|ó–óãÛê ×vpëÝ0öK®›•ª3ډΌ8¥YT›ü ÕE®mF¿~ÑÅz£4ûHm^Öy aö½Õ¬k$dcØaƒgò>âŒmiòpõú;h{¤š!4û£»e¹IQQp†A¹Gà ðqÏjläœ~SÎ-êY™zè+ë-ùo.!K²dm‹"åS¤{ÐŽäö]¦½|e4ûëÿœ²¥Üa¤I ¿„{(YdRI¼B‡J®¨½  +n¹¿Ô.­7º{gßžC·¦êtdäøð6z +®[xÝßî:òu¥£ébûV_Ûw—‹‡³2iìj½‘XÌÖYóëtZm\ýVÈÇÎ9lV£ÕšuôRé¶ ¤j–ëºüj繌ó€§qX?ëÓ½Õ-¾øòc§¦ÙC”,’œú”N®ñW\¹K)­lç‡U´Pn®Å®Ù[“•y%O¶k¡ëe)CÇs·£é´Sº6Ë5Ë²Ï î^è¨Ûx‘{ŽKL‰‘)–…½ÔÿÞk*~S=úu2Y㕼èý#ã}²ÛÌÚÌE圻Ã÷†ÌYQ„ 2†ÁÏ$f—:¡?šÅÉáúŸAVN*®ïŽÿïOÐot~ºÐâÕ¬cÕ“Éà²2œ1¹^ßÏë¨Í0Ê_)à5ÚKú}Þ”ÞW¿ßÛñúîÕ›¯[46«#Û²° Áâ¼zZML\Z}½În§OëÑ(ÊXLôgLõ Þ¤^KÐ+r¨oξ•Ñú­ÚÔå©J9ìœët•Ñ…SÉj ÷½YLJqP‚ƒƒÞ¡ òÖNã" ŸN» }(âËÉRÀñžÔr]Ì„`â‰Qøõ)Qvîæ†KZJn! G4‡H8öúÐ,4È_»þJ*ù»­‘,çs¾&Óíì›ÕÀû×âÁæ\vr.O(\v¢ /Û¶+\bäfoMÓêç{§úSý||¨W¥žXĺj[ÈTœ®)»ñÉM©ðF¯AèÓê)}÷Uûäl$Y‡ ¬AЂ«=U›v¦Qi¡»8=á‰uV–¶÷2*jp³FN7ÿ¶>Gú׳éÚèêkQ“ù—òxzy¹E|¬è7zz^&ä;[éÞ»98©à€¸†kI6º‘Î3éJkò-cü+¯ð7éD†Ä¯ ¶On*vÞá–T*KžÕJjÖpM{¶1H1µÉîiÅ9 ­´‚4í>âX\˜Êìm¬à©¡ä M¹€Ë)$})Qbã Ân/(†“F‘\•ă¶Ú¼ÕÝ[üÓŽ³ŒH`’‘IøN3Δ´ÖÕŠç”UL½T¹XÆ=ÍÚYï{<ÙãïFy´þ”Ž9#x¯£}JH$ÆÅäùCær ϧý®¾¸Ù¨Žß8>¿ü5Ñ\ã=o¼^Üþ§Õ/u>¤Õîtà±Â‚g—tùÃF?Ƕ+¡$ìùQõ=5thª¹ËÂ\{‚KâD–ZCX‹xüðÅ€ • H±Œ6ùÿ [ê¹pú®ª4ëwClgºU ô8öª8·À›j¢R’yx.½?Òÿßû‘e%Õ­½„‰¶Kèi\c†SôïTŽ–Sšyìx>¥¤„kr‚yöñ/à´ëH­u%žeݱÃ&Ï|Ø×Cdkó–zá™N˜¸Æ /98õõèŠF;·`\ÙA)n|ž§QªÚÞsÒuø´½Û¬cï™ó1Î~µÓRPŽÖZ:y[%c| KÖ·ó$`ϱ£ÝÜŸ•b•ª\zzpˆÈú¶üHUnX©pØnEQ|æYF†ñƒ¡jZ®¡Ô?qK ¾G™“Øc¿Î‚»Ó[àóëOUV¹<œ[]ê[q¯-¤S™¶vQÆrZÓ³þ vuªtÚ•¤ÝÏôÿÙ+-Ý›@‹m!•ŠÅ‡(}@úÕ—²=wÆä]º[¬'ðûUéë«gÌ`ŒN²è>X8ÏÊ…XÚý°eÕôøu ,¦ÎÏ·Ó™ío z‹MêM#öå‚;EtäÆÓÞ(Hô*A:ùÆ£E-¦mrÞo¾ø>Õ4öi-økÙöí÷‡D¶¼ÔåËWÈν/MÑêõ3ŒÛi?SuU&Ž¥k—hÇ%Tkêia`ò-åäx"ŸJ!4Ð#zT ÛA³±¨A*Ç8ïP†§eòŽä,qP„ …w½½*åA‰8ýêšÐ¦b7öܧúÐ`]Ã繆Üì("®zš48‰7|û 8!ó®k@ª]¥ÜÇý«ãÐqKƒë’O<ˆ¶¶1ȪÀžG´¤±È–ÞBî ‰r®‰“éò«FI§à¤²˜l“K€¬Ý†~´¯•r™ðe³4ò$c9ã>ÕIÏÙŒŒ}Рëæã¹Î‡­H¾3 µž˜úŽçGÕãM¸ky¡åd_ocîµhÓnõ=X¼$&õNK9;ç†ÿh=?Z1XêÒ¥Ž xRÇ /û¤ÿC^×IÔãoËgÜñÚΗ*þj¹Gf†öÛP„d¬Šk¼š’àóò‹‹Ã5û0F?ÑßáôFôúU\A¸bD–/Ä„PÆ“ëoæ(pϸ_(1>…jÉ•ÀuÅ’ÜD¤*@8ÝëUœ‘hÉ¡“u41ˆÙÉ^ß—¶k>ùC†3j|˜$ÞÜŒŒw¦/™ådö¡´™hƒ~!Ÿ­UÇÜ9#nt+y‰e&#àÖtTÙœÇòàÑ ç ²Øß[/îÙ'íŒæY º þ©'÷ÿ“LuoæXÔ.åÙå¼er=‡¿=«‘¬ø„¶Êãó5Õ³í&× ${b@Σ*Äq•ZsR[kYkò"M<É•zŽ^—èmc\Á7•dï ÈL`O´í#ձǿֲÂvIÅÉïŒñãw·èvz^š:½ez|5™,ýÞO:¾¹,óÜIq#Ýê×ïºi$>òGÄ~yçõ¯EŒ.ÔúJk¦QXŒWp'Pkù«)‘ÓRBaiP6ŒËdÜVSå›tÕGkãå|þ º<š6—a©™Ñ¦‹,l¹À÷ªzN¼M¾àvú“•k²¾»m3[Y¦»i$E 6û‘Å9Çl³&av)Ãká[®¦YdŽ]AÇ—*ã |$¢ðÎv¯Lý/ú{–N¹²é {_µoÞ\5¥»3Åœ): BRÊðS@µð¡ÆI-ϹG¿²èË;+ F=)\y„ìfÎM/)bGMhµ6](N\¯¡Lê«ûi5K‰-@…ù “š_‰Õª2ª¥>Q žT33IJ Þ†•á<ù3N|÷4C*¼Î#ô¡·k2îÚÞK—kw.~"/÷‰ávDÎoúf•)F 9#ªè¨=¾%m5Ž£Ôd ›yC,˜ÎÚà§ xgÉ­—«)z‹œ÷'-uIoµ)¥ÚˆÓÈdÙ©'<j̫ڔWƒÖôÝD÷¨&tù$ÓJÉ RJ§%^@¬çZ2ÚY=ývJqMð{oì{Aáµâ]Y‹T³½1ÂÐä+«(cÉîAÎOΧÀרÿ²Å–~}þ6³Ðê Ó—Ú?¡ëîŸÔ ’Þ+b»%Udž¯QTÓI!²-6Ù: S…‹n¡HØqs°ðh6AØŸï1o¼;ÑDÖ-•Dm€[%ˆ¢€FG fƒs±Ç`‹nÖöég\ ®HΕ-ÇñsîÜÑÈF¥‚^áÉ>ÊL“'ÏíñŒU”…O`=þµñèBXÏd}vS[±ä6ÚßÉPòÍœóª;{c°}1Çxd}±°2ñœð¸ÿ­[ÔÇ2@ÙžÞô¹ãñœŒ¢³8xÇØ†»êŽõlaŽI&'÷“À¸ôÍmª´áêHË;žÈŽM¨Í(KmZA”LŽ?Ú4½’µí]†oPYc7¬q”ÜÎ~ñ²+r‚QÚ»!Ov_rÕ½Q“hÓâc…W<]=:‹9š™Ê+ކû`õ7‡’™Î³¦FpmîœïQþËÿÈ×ráÍláY(MbØþ>OVxYöÑèž»Á%ÿìA† ­ñÚsònƵÇX“Å«¡’Z-ÜÔóúþGÓ:¦ÏR…)’DnC)߯k1g:UÊÑ!þ‹sè9õl!|¡ûH–ÚPèCãѪ`™ fK‰?xÉ»²Š4ʱ`})S­`º“ÈZ€ _­f‹”8Àç‰,ŠG±ö5¡I1M`Sh´®“Ep*`™’5|Jí‚*®9-œM£Âá¼½Ñꇬhjµ>0þœ!|ãç'œ>Öºõ„9¡ÖçO»y.æF_‚FB¡ý ÏÕÌ«BôÒÌžïcëÁ0®ùÛ¨|Ia/¦{žL¸¶=]zolÖÕí£gÉSübÝÇëš«\÷7’é ê÷¿w“ùpª æ±Ù¸´Ù†Ü7·2ñ1­îµ /.(c½tfm×o¾Øþ.õ¶•¶8ðxþ¬¡;#¯˜¥ôÉd¸P³žûŸj-Χ”eº_™Ò§Dýikir.6¦Ù"ž% ÿà3K—-#Ù¹ª¢Ü¸ú£èwÙK¢ïzGÁ½ÊíqqxÏ~!Y‚4“FG„~µÙ¦¶ ¢~_þ+ÖÃYÕlœTqûã¹Ùm­ØL d0?¥h„^O)&‹|wX;Öã òɻ֡o sr¨5F;´ÒaFIôª®CØ•¶‡îöûXüMÞ¬7S›Íp£„^ÀzÑ@·¼[BYPêOz±0KÙÞ­Ú’w&«‚5Üq÷lü¨»Ö–0pUG»U\’ Mö>}[ÁqrìÊ<¹88ʯÈ{ŸŸjù¦å7¶?eX¦·_vIÇjÑDKÊ„<–ïÿ}ë$R4¿CD²¸C iå²ÍæT3±Í mÚ¾eÏ߸a^îÂ&Öc-+`Ÿ/¨ç >gÿZÍ[ÿA³Ä{Ý•·Ž8ãfu?ˆŸðæklwglëõö¥™~ÿØm¸hÞ7•OÄñBüùü_Ð`{×Wÿª?3 [帨õQ¥¤ùm•<ŸÖ©å÷$ÞÅú‡«~õy!“%€;€^®ÍZiÊ+ÝLTžJ”²µÊ³‚{óÀ×AÅׄsÔ•™dmÛïÚ#˜þØ«Ç+í ›ËùKDøý×>ÊŸ²5Ë“jŸê.It?.i±ŽÞbðþ…]Î\Me}OCôö]ZÈ-úŸI!T…7§ ûœVèYt<™\tö?üOHtÛ;¡:¸¤që1[ÎÃ"9ÎÓNŽ®?̰*Z7ü'eѼIÒõxÕ­o Oc€Ö¨Ûvf9S8÷E† ~Þp0Êi¹BœZ KÛyGðšL (p%¼Ž?:®ÄÏÈà°>?Z‘'O µM„Ê6s ü9úUv²dià‘sð˜aÎFŠ69 ?* æ_¶_Lë·kÒšÞ•,¬xo®1¸[+m*û}rT©‡SaIVþÖéèô^þiaÅ{ãº<ÒuÞ–°ŸËžÂkËÙ'%îg›ã•¶ç'åò¬.Q>Ò–ªè;#4¢—d¸G4Ö¯íÖÎQo Iî_ŽH9Í!¯—“¯Lš3ìVåh#g; r3´ò>T`Ÿ‘ŽÍByQ"õk˜âF|)á4Ìc„qµ¨Csà®ÇªO¥ÜKqzíÆ;ý(úiðyGª³Mk¾÷§u˜µ=ÞL¬ã²ƒÈúÒež=×Në«R–þÝka¡HÙÈEÙUpi`ïËYTSyîG>¬Ñ®Ž¨£ŽÉœ»uk@O|À–ß¹‰ÎM3áéßžì*ÛPHÙd”,Œÿ=¾t¶ž6“ÔŽÞå†=z+}?r2Ãˈ9õõ5ŠUóÉ˺ìwd(m&æåD±,½#r§¿¹ÏçL{Òã±]:Œå‰r¨èèÓAwk$Ÿwq”Vþœd|³Wƒ”—Ìt´öÆc<3ìýáuÇ‹ÝyßÇy“2]ß[.Ð&â1–8ïÜÖª ¤Îñ[ÿŽÓ·SNO²¯à})Ð. Ñ#¶ŠÕ<«{xÖâC€ªü€®µrÃÊ?;Þ¥sr“åòYtíCïîî)½jŒ·rsç œð‡<šw"‚D»8±ÿdf‰·Þ¦á#(=Üâ¡Æ–ìs,À}9¡€Ã6Ÿ…†Ä{Ñ ©/bå³P€7ºŒ¥I}}ènH)6EM¬XÛYZ£µ"ê©H‰Ô~æ¼lTsóð—×÷ǹêäÞ>Q›–7Ñ ÌѯŽyùsÛéò§)Æ1Ý.Ž«þÈ^Ù7…ûú Ìv°c²9àÛöÉÇåúã׌N.Éþø^ïêÍ;¶¬/ý‚ÌŸpkš\ìª|EØþ?ˆãž{qN_'Ë϶ä/írû~ÿh‰Û,²‰c’Ý‘›{+!Ä®ò íú|òk£[…pË|ýÿ¾=¿3$·Mý½nãÈbHŸâ8`Éç$ãœýê®È·î[kHåZÕøžà»lD+ 3Ï<ŸžkBm¬#,šO';Ö&YïLqF6àŽÄ1ì+·B’†é3‰{Œ§¶(º…BÎ 5]ÙˆtMx'-ºÂÞ@>59ùÓÓë’ð½Am(üKVÊ+µ®¡m à¯ëS‚¼û¹‚ÊþŠdI#nêà*8E£9Aî‹Ã+z§…Ý­àÞh:mÆÓdµBGÓŠ«„_tu)êÚú?úî’üYUÔ¾ËÞjÁ¼þ‘ÒŽ}V¿Ò¨ê­÷Š:uÿuŠ¾Î¢E_RûxM~„vâ‡?ý‰äOèÕWMo¼Mþ.êë½¹üø+zö{xW~8´¿‡ÿë¿“ÔÐô*ÿÄ2þ-ê3âÆŸàAêÙ³á½ãŽëW·+ÛeîGèEHÑZðQÿjäÖèÅþü‘°fgEÙJdµê r&''3#gõZ.ŠšìZ¿âkê{£ŸÇü‰¹þÍnšxÔÇÔúÀ—9.Þ[þirÓÖΤu)üÕ§ù€]fž“/àêÝMsÿñGÏòª|%oÉ£ÿ›ÚÖ+ó`¿ü3ì÷/Xjö6ñš? _¹_þigÿËú±kýšp9êýD7ÊñCá!îÀÿ,|ú_ÕŽ7öfé!›¬5S°a@Š!åQi*ÁÿÛ)nu/͇ÛfJÁ «u.²Ò)ÎðcËmƒ«ê^ÆšŠþÍKóeŠ/ìüé7„Eu®ë3Æ"ò‚™P`“ü=è-HSþ2Õçtkõÿ'ZðïìõÓþh‰¥éÜ¥º¹‘Œ’åÏv'Õ¾´ò=¬ë:mž¥©d½[ôÅ•¶2Ìß6jr®åKQ9VÑYØ6ä7¾i‹jì!¹K¸Sk äŒýhî@QlN©Š>Ì¢ªìE½6?Zƃ™GëTw%亦OÁyâ ¼yÌÃüÔ‰j »±ñÒÉø+ú‡ŠövàæáçYg¯®>MQÐÍø)úçÚu‚±{Ø×êâ°OªGÁ¶5ù9‡Sý®tM7r¾¥o`â²½uö}ˆšã¡ªiœ»_ûhYJÌ–óI!9o½+f®ÞüKKSï“™kjmST™‘UàmÁyÃwõïÛµ2=;w3–@õ°‡‰Rºñ‚ûR2ŪÜ*Ã+áÆÏŽ%‚¸9 ýAÁÍiŽŽ¸/‘ z¹ÉâO7Këý?P†Kk¶¶G <·_†9>.äžCc·|àƒïW•onåÜ´nNX}M4âeh×k”áÂ>áŸDÏõ¯•%Ÿwô¿ÙëðÓú„\HmãIÚ Þ‰ 6¾N;úïõ5•ÉY5µçÛêþƒpã~ãï{I’iL‰#âMË!%@þ"sÀ8_ž}jêè× óûîÿ·æ Žo•ûöÿ?‘‹÷éL³[^Ak(„6ç ŽÃ¶½Op§¶M6˜*±eŸ–?‰I·<Â#À›wXäŠóÈ]¢H×jð;'ÇoŸlcŒÓg>w7÷ð-,,$RºŽO4²Æ.#ÞÃ1¡xíŸZ¢kßúýN_Ô8ÉTŸs)9Çþ•ÐÓ×á´sï³ÊLªÜ[Ike<ï †S” ÍÂÛô®¦Tä Ÿ©Îqqƒ–9!âÓ7M¾ä2 r\=¾D·õ­’· îbyyŸïÿlB¬òÝÈVG‘s…ü°O•E,ö&dåÛ¹Í:µ›UÖ<«HÄ’1£)ì2~Jïó¯E§§ZÉçuõ,{B!·.ÿwòXGm’IÛ%²8qÁõÆO¥ZSQYŽxtlç~@,ÿëCÕDu´÷FŠCܧ=Ï·sLÜšµ¦9»gè>uW%Ø´cät@ì$y¡Ÿ!kœXÝßèÎÏc2ª“—ðѹù¯üÆ [r'+±tÑzâȰûè»ÐçýE¤ŒÑgæ+ùƒKj2î0¶QòÑдn³êˆ"è½MûNßÓlò¥ü<%ØrÔÍwä´é¿h~ºÑˆ¼ä/ªò)n‰G³µ—Ú‰mÓ>Ø=GjUe˜1ÜZV-i Íïé¤}µ/×xCüÁ«z·$GO"ߦýµ` ­Ýj-UËÀ’—Ù–K/¶^ adüêß5Þ%^‚´‰ëOµ¿ONn¶Ÿ™¢µþè¯üw³D½·Ú¦æÀûò­[þB>QOøéx$aûFtäݵÿZ²×ÖÊÿÇX?&1¨Åþj?_¹_øû=‚ÓÇ »j1šÆ×Øu|iѶ¡ùª-m~àø=…ãF‹ÿîšÆ×îO³ØÑñŸFÿõñš«ñÕ{‡àlöÞ5hÀøø¿ÍCãª÷ÀYì0þ8è‹ßP‹üÔP¯Ü+§Ùì /Zgÿ˜Eþj[ê5—ÿްo´6‚Ÿý|æª>£ë§M‘—_im?þ¹ÐÒßR„1tÙ·¿j ÿ¦)üéO©?ré¾ì®êkÝ,í¸þ*S×ÚûDbÐAwe_RûeéÈÉ3ùÕ«Rû!‹KD{²¡¬}µaE;ý/v®eöéaÝ”[í£;0µ†yIí´Qøkå̤Uߦ‡e’Ÿ¬ý©z²î7q²Î~9äÆ(­~Ô²Uë"¾Ì1÷œ×^ûFu.¡¹mõ.ñ¼ecåêkd:ukí%Ô§ü¥'SëTï5k‰œœà6Ð>X«\4ÔÃìÄÇ=]òûR+úÓÏWFv‰òw§¿ó­0ŠOjòeœÜ–[& 4 ,. n¸bG ÖfÚxf¤¸Ê 7äà¹ÚëØ¨ÀÇåEqØ.^áVڜцóI_ÇÏýÿßjŽÅ£,÷$`¾› ¤´§ŽèÐ¥.Ì÷ô1¿sq¡ÜØ‘ÛêqÍ|²Y”°¹ÇïÑVÏŸß"$S!yS ÂÆŒF}‡éÞŒ³?þžAÞÓö„¯A) ;³’¿%ǰöõ§i©Sùä¸_×ý ¶Ç•>Cs Qs´ù§½N~¼ñëAZ¦òòÓ–˘Ë*8õ\Ëè8àúö<Ò¥¼´Â°¸(ýKh N©ïOÀ§Í àóœúž1ùS`¾mϰ™¼ðŽz4^ ‰ã‚3vϰô­ó¹lÛù1ª¾mÒ õ-0è!òàbŠ©¸€ç¸íÏ~OåN®oŒ¾AS‚o·EöŽe¸–Ú)AD²†-¹±ØÉŸÏ5ª6(EM÷}Œ²©Éí]‘®ƒ¤èWsÌQøÔr° ócÀüëMö[ßþŒ×ÿ×T²Q­tŸÙqžX÷ßNºûÏáˆeàŸs]÷féa>?|œ^Èå®Xež€Ö–K¿¼üs0ÎYÍ%ß™eTb8b&°T;#S–;A9?ÄjÊMòÊ8$ð†o´Øò±Ä¹ˆO8•!7œ°YZì€å³EýÙ, ñÏ¿­3sî+j\½ºªn$‚ÔÔÛÒHv+pøCœcŸÖƒ–9-åàÜÀFÅU˜AE<®@øx[qo(šÚymå_ã„”oÔSSbšö-'^õ.žÁî-F䋨²GËpÁ¡)ኑ7‹z}ÓcUé÷ÞKVäpjÙÏrêx$m:¡õfø5§È}'VÿJŽ1ö ³êLÚèV×ø:n´“Žà$ÊÔ·ŽV?ÒtÞ±;eYF}E/Òö½÷k-bßñ[«}*ž‹«‰//â›ÿÂj¾Ž «¾¦&¹:˜&Cò&¨éúW?qÁÔ²®~+…üÍSÑOÁ]û™ýì•{\Ü/üF‡ ½ƒñ÷½iprÝÊ`ú±¡è/`üD½Í7ZÝÆ£pûƇ¡à/q³Ö÷x?üÂãüƧï`­L½Æ›®/1ÿæÿxÐøxûâeî0ýkt[›Û“ÿ©ðñöÄË=ƬnŸ8¸¹oø[áã슗¸Óõ=ãÿÃÄjzÄKÜaµÛù;$‡êÆ£¾¼˜ËÝj3n}̓ԛx¯H&IÖ1ëVÄWdSt½Èû«Ý>Ó"ëT@¯“ú ²ŒŸdQÊ+í2.^ªÒa8¶´ží»+´~¦™éÏËÀ¯V ð²GÜõ^£sðÀØ©ÿ ïoÔñüªÛ »ò-Û'Û‚.e’ë÷×3I<ÃÒߥ1<<$)æ\¶%† Û’sžx«g bå9U89"„{‘ˆ‰VE1>B6sÅÍÍbØ%}ÓÿNÔ»û+ú­“m BrB±S†Ü8¬»²jqq`Û6Žy²—\yà”²·bádb¹#kçaØçôþ”‰KÊ4B³>„Í ,¨¸ò@ÊŒ~¬kæñŽy\C}ñäRJ|ȕԕÉÚŠ0@ÿ>†„"¬žß“ÛÅù+êíÉ-Œþu¥í“Pð„¬ÇŸ,ÃyhIƒ‘é‘ô«e<ðŠ<ölBíÞ[îïJ“Ë,š‰«ÙEs*s· l¾µe (å.>ð)G<÷)Úý¬aÊ»TH’*Õ.EÍ”ûøL°«Ê¸AX‹'Åócÿ*èÂ8|wòd“Êä‡M2ݰv 0XàûSÜåžP­±Ç«´Ô¼½²³U,L.'9ãv>—µmÑMÅJÏ~ƒY9F Ç,¤J$½ê) ž+V0?ãݛ޻I(ÕËîq²åkúoˆíÙˆÚÇ;T >¿•gQËÁ¥¾ "Ú›w˜Ê0ª}ªòNoh¸â p™÷‰™×wÞ¿øïÍ»V< Î÷•Ü>!š@… ÝOZÎܒɪ*-àÓY§™#2+‘èF>•7^ôÅ7žX—8X"ï"Ýpˆ#êå›ëó­PmE¼™gŒãrFÔ*"€{â®›H«KÀHTƒO‘Ù]¶öïJËsK=‡¥Ã-w#æŠã  Ã{S”¤ÙÆ([{qÖ‰p}i©Éò*QŠã¿³,Ó÷‘«#cø9üªÛ¥Ø®È÷Aöš†§§:‹]^úÜwøf$~††çìLc„É8zÿ«­¹MgÏQéq¶jÊx#Ýî=ÿŒIÃ4VW8ïû²¿Òš™MíÛxÓ¨ýþƒm õÛ!^å£cöÿÆx[‰zuÁõÙ 5\}KúŸA¦ñJ|z-Êý š¶Ò¾¡¯üTÑs¦Ý¯ü"†Ðú¨fOt&íesþASkdõb7ÿ‰:!ÏúÏ䂪ါPËø™¤ó³M¹?ðŠ;`z¨¼L²ÏÁ¤N~¬OOê[è4þ&p|­ïICÓ^àõ¾€ïâ&£(ýÞnŸïM\|°ú²}$½m®Lä+Aû±gúÔÙVy—W×.-¨Ì¨Œʂؼ6? 2ÛI3~þâi³ÿÜrjû±Ùiù3î‰nÊqèqUÜä°+23ì)_A¹µ’\|@7#Š9MpÛ‹.À{Þª¤¡lAÆ Ç¥3>JmÀãÛf,‡¶jª\—qàn(wºªòxBÒ˼²}>úÖý~ßœzúR¡5dePùE×(Ú‹¢éËs rí ò1\ek‹q;е4¤9Š&´eR ®J’9úQw¸Éd‹N¥ » K¸ŒE&Óø ÇÖ«eê/]šà÷eÄè©Á8ÿ¯ëoåG°RÇ,v U|ÍòÉËvùVˆéöGåb¹|¡ø"%øœ¯zdjXÀ©Xó‘9šEÄ¡cQØ%8ãj@Iý s‡ÜæD+Ûð÷¥¨pàíR8Äm1çµ #)?¡"â¾òme·›8c'pïòª5â!ϹY½ÒŽ­2¼«ð¯`!O‡ý+RÅW§ÒÖÕ£cñZ›rË|QIpqÞ»š>ž’ÞÂÛ3j·¬~{WÖ½“7æoìÄàjš«äÚ`Zvˆº&™¶Xÿ}#eßÔÖ©OÖŸƒt· '’Êi¬0Í#GŽMÓ²QÀÇofºÙgjfª*‹[š&á°µ·µÞ°““‘¹}+œå±¥y.ÝÛü!ç zÖ>¢•6o]™³§?V½º,KÓͱ‚HT“íÚ°ú댣¡è>pÉ ?CšÁ•ü¤Ï|ãñRçtlXÈØU*ùÁëKXKÊdü'ðŠÁmäß)ä‘¶³ó˜ó…õ SS~Â[HtŠ(ü¨Ø9â“dœV`³Ë#§-0#¯Åߥqx$˜=Í«4"8äT?JÑ—ÚòûG¥·ž­#©nÀ⯈ÙÊìS.µ[cw“U{šÉ·<“ÄrBÁ¦™¤-ˆ‡eÖ´ÛZK 6È²é½æîì®wsÜŸjçF™ÝbŒ .Ø× Ò<éÒúyêΠ½êK•,”€7ð ¯WntõFˆ~'Ÿª1¾É_?À“×´·¹.P€v*“†2ŠÛùÁ½<-m̬K1Æ*¶jç‚CO¶9enöØÄç¶[€1]K'>qÃ.}=¦M<6ö¨xã8®¦q‹sgsO I("øzU®/1\ŠPMûCpÄÝ*.®c›àNø µNm"Þ‚“û„ë]>¥3v> Q¢÷üÀ¶¤û½W§dmÊŸ‡ŽMu)Ô%Ë9¶Pß`3¤%cåJ/<ŠUº¸¶ÇW§qK"#éÉ%½–WP@9íTz„¢’.©ç Qè.·SÊÑy"´;ÓJ)Šô0Ûhˆ} I3¹„rO$VÏ_ 2:2ó€K>œøäv‡#8w§Y¨ÂQL]z~[h0ô¢‹\´ÞÕŸâ~nï†XåwÝ?kåYòÍm…óqÊ3KOù^”F¤!Þ0=)S·ÈŸ†ÜIíR6¶+;ÕeäÑ*Æïúi•v·*uzŒ¼Š³O…‚-ús.¥w“ô­+Qß&_‡Ï`{ P4È_S¡‘§Kð°#ØVQ½&g¢É)`’G|Vk.Ik¥°{ý âÛ ŒÓj½KwiÚù„ %¤ažØ«»Ré6‚ÓàmôÇjÉ+Ôy6CO½‹@x¤hŠàÜSer’LZÓ¸¼GÓm"8 “jÌïÃFˆé³àršf(qÏb1Bz…ÈV˜E×OIhå=g‘F«}:e®Š×Váx$jM–í–GB­ÑÀõ¿O—Jü¹ªNý¯%£G Ót¹®8#›-Qy]M¬0«NÌŒ‡|©r¿Œ¡‘£ÀW÷`å =†Ú§ÄyEå§KƒL˜·g†a¥jÿŸý ¢nºqã¶ðÀrM2©KiYPÔwô呾vB •ç·µSPö,—Ó­ü ZéiÒ>"E1ø,¯°ÆcFv|V‰ÃÌJFµ§Õ©®Ò:Æ£Òo\ÀFã'C^V½N^Éw=,ªXÜ„C¡ »W‰ÝŽΛë:æ›+³tpŽÕ=ËC"€7|…wö.ìå9¾È”¶ó?0pv¬ó¶5¬¢ñƒ›äq"<»0Éô®t¦ìf¼m\ŽÏ [ ±÷­*j+ÜÆÚd­2+w à[[…³`ûS¦”V¤÷rGjãŒÆ™Ëœ)ôÔ—"çfDÞËog¦¯Á–A–??jl㻄-Kžeñ£¬¦êKÃ¥[’`g9÷ÕÒi£_ÎÎN®ùOäC]7iû2(íaX­âM„g’~•¦pƒyªœ”p™.¯PùfÕ¤ŸœòMRj¼v$ù•§•Y ®ÕÇô¬*ˆ7œš¯+7<×—iøBœâµJQ®F9e—¾†ÐåŠI&tlc‚Ey}|Ô±z$VYrˆ£9VúWPÜΪ– lËûô C{ [’ok,“K(„Ô®[Ì}¨r3Á­ÕÖ°²fœÚ"6<·qFÞ§Ÿa[T`ðeO2ä´¶’¶¶ #1$Œ ã¹¹KKn÷Ckj\Å©Ëæe;5‡“¦3må½èÖ÷O³±^{FXHÛñí],óÁ“Àv›¡»À05Šë±#Mu­¡i¬¸œjZ³<¢ÛJ½îž³Báã\îö®¥sq|34àœyDÅ”vÑåW(+<ŽL²PHjXmÙX€9ô¢·ä/h ÆŸÀÇÐÓ£d—qr‚hÁ #Dcw~ÕG¨`U Iô(ã,.1MW7Ù•u/b:]÷~¾¢´Æù?"ö¶Ð#PÛb#jLïo»/’]„KÒq\ÙËMÅ»TZ©BjAøxÎ-`ˆ·é†³*tíÚ·ËR¬Y‹0-?¦ñ$Xìt(JEˆð>•ʲחÉÓª´ ©è¥Ò°LVŠ/nu1O(7MÑ#ß´§ ;â³ÝkÆFÕZ΢ÐcŠB…2sǯYµ–ƺR`ú×N,’à Cðà)Ô_„Л©O ÔCp£ËÀo•ZW7åj,>šX.Ê„8“ךZ‡(ýÃÕI0ë^˜:mÖý¹Gô¤KP­ŽÑ±§cÉ’é"+Àþ^žxâŒ&Üvä’ŠÎI9ôU/ªœ})µ¬Å—qÎcC„IJíÚØïŠeÉ=¢¬„ZÈ,:|7y/Üö5«2„·!)&°ÅXh£I¸ ±e[ÔŠkŸ¬¹`PUö×ýûW§ÖêÏŸlC‚aCKw£n×Ù•º¯VwEãÃ[¸º£¦#ŠP ÑŒ>b¸:ú¾PÜ{3©¦³}I²cö vm±´­Î|±Ý»»(B¾d¿³^¢V9÷8ûT{I8“áQ…ŽÇ—ÑX$ ´" <„ïÆ}è´äÊvyû5¾¸mY3ÎYxxÎî~tÈÔç,””ÔVæL>ãÉ=«ª£ˆ˜w6Ê?ŠH4m à„ƒs0Ú¸ôÏ­2ª”˜«lã¡tW‡\h³Þ\úêã•Ü9:èÊ[QGs%4Þ»Ðco3ð1áEQµ5Átœ_$ºZY"nh”1ùVWLÛ4«"¸#µíÖ²<³q€*ž†[ªšÂ¥ô”w£<<±É¬‰¥”tÖÛ/öúVÖQİíàgŠò¶Ç2r;Õ¤¸›J‰]"òûü»ÖiE¨¹ ‹NAòÚÅmjWËôö®b„¤òÙ©Év(÷ÐÂÓÊLXü«¯T%…É‚Ù,ƒé\W—§1á½ñO»t#ÃZNE¦÷I‚£…†EqwI¶Î‚KwZu™XâTªêV%¨Ç"îôëS ÄÊ1ŠÊiåj/†Ý-a;qéÚ˜õ6ÄŠ¨4?MC mQŒö¬¯Q9<±žšŠàÿ¦Ö8\¯r;Õá{rÀ|Æé)ZFfc·>£½v–®=’2ºv}Òwsgë[iÕ×¶c¶‰I|¤v›ÑzÅ®£ûägˆöÉ⺯W¥”säçü>¡K‚ß§t„…Ë:m¯7ªÕÃv ÎÕTË0|(RBpM`zœšUCw½(Xçmê}‚ê"®zPçNsZã©*„wÞ/€ýè;“§„.Û¦]ˆ#4%¨XÀc[ÎAz‡¥æ-Õyô¦èõ0YLŠ\N“ÓÒ*Ä6÷ª_rËÁZ¡…€­S¤|Ôv+†Ç|RéÕícgNå-+A˜B™ÁÆqZ.¶,L Ó&.zy¡ž6Øsô¬0·)£D£žGäмøÀsŒÐ»XrˆÙ4FøNTûVµbòÄ8$"”!úâ³Â|´7nPkèÒOl­´ñJÞ£<K(pèàRT’>USà®8à ·ÐÞXÈœzb—)¤ò¸ÁšŸNµÖžÉ‰~UzlÛ<”œx+Ö=:ñ¶T÷"ºî̘±‚ulTº«®Híô¥¥ÆPsîXl4Xî­ž tr)SšSO¸Î0s^š‚~ƒñk'ÊYÜ7öÍnÔVµze?æBê—¥c^Úo4¯<$éÎEr꫌3[³ —žàH6]yñÁ‰ M»IïUÙä9Š5U'о0œˆw }«G¹WØÀT<äzVøEÈÉ&1|Žàó]:â’1ÊYdv¡{÷HdσLPtçÄ>¨3J¿è±°À5¢/jÀ‰.Nåc¢ÁmD¨ (·aSs—QHYÐRóýRãéN®<‹œ°UåèØg›h‡#å[œpŒ‰òg÷ (Ž÷€ûÖ+ åÁ²H’Òzs|Ã÷L¨;q\V™5ƒ£EødôšBF¹e#çìÒø:Q¸ŽÀ\j$…8ŠUºW·crÈf¡§‰—j©ãŠåKOµà× 2Vot‰eäóÍi®ì…M¦ù3JÐÒ)LЏü©wÆXäi<„\X n71á{V(ÖÙ«rÀZGÞo·©ÀZ¼âã<_iž[Ø©\^8#k"mtÝÓ+(â±ÚßcD}ɧ҃/p;Ö.Q|]i›\d*©´ò>+$[èèÒœŽ{VˆMã%lö44dY2¨>Fž­~D8¢NßMPèùõ¥Ù&û2É »³ «J\…°G´òÆpI4ÈÅÉË d2nH9÷§zXìWp¨t­ÿ½ á³c§Ä®[Þƒ›ð ]¤Ã#ûRœÛEÒÀTú™0A4ªæá,x’³ÒÞp1ÆxÀ­òžø˜ñ†u§™HOéYãÃÈìå[é À#é[$÷!=‰Yô9CÚ±çk¹@ÇMÕÆ*ñyä£:aóŠ•;G5¡>2J3Ì„FA÷¤·‡’þ-ô£´§Å¥]áòW;GLÛ•e=ª¸l-YØ,S`)Çj·¦äŠîAòiú¦¯]O<‹•‹ݵC‡#šéƶdsCW}1¬ŠF9¶W»Šv"JÓL0D>À­ œ”v”_úUl$‰b=+EPU·ٔݸºôUßí½ÞE\çN­“f­ù\›ÜkCYaðm_š¼VE¶ líMi@R““TŠA`’;*ð}ë¥ZäÃ7ÈÀc“ÏsZüü•þ»•“OES…#$ 1åòNxM G¥³ª€ÇÔSb&]Ë”r¹Üw%„I÷…¿Ý­´÷1ÚdŠY2'šÞbA÷1©„ü"—äq#¤Â‚Ü|"³Ø²:¶Æ5¤U·l+Ÿ8¬› Ù§Džs|"³Ê)¡êO!Íoü#µq­ŠÏc£ðDkF@;j‘Š,Û2ÒþíøjÍrM–‹dUÜjðzJŠÊ–'J‰v“´dš]ÑI-j‘©í¡­£3ȋԹ6¥¸Ò›Á,€míY¦– 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-140.0/examples/webkit.js0000664000175000017500000000066615167114161014675 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-140.0/examples/websocket-client.js0000664000175000017500000000267715167114161016656 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-140.0/gi/0000775000175000017500000000000015167114161011623 5ustar fabiofabiocjs-140.0/gi/arg-cache.cpp0000664000175000017500000042047215167114161014152 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 // for mem_fn #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 #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg-types-inl.h" #include "gi/arg.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/info.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/struct.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" // for GjsTypecheckNoThrow #include "cjs/auto.h" #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" using mozilla::Maybe, mozilla::Nothing, mozilla::Some; enum ExpectedType : uint8_t { 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_t(v1) | std::underlying_type_t(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 explicit BasicType(GITypeTag tag) : m_tag(tag) { g_assert(GI_TYPE_TAG_IS_BASIC(tag)); } GITypeTag m_tag : 5; }; struct HasTypeInfo { explicit HasTypeInfo(const GI::TypeInfo& info) { GI::detail::Pointer::to_stack(GI::detail::Pointer::get_from(info), &m_type_info); } [[nodiscard]] Maybe return_tag() const { return Some(ReturnTag{m_type_info}); } GI::StackTypeInfo 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) const; [[nodiscard]] 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) const { gjs_arg_unset(&state->out_cvalue(m_arg_pos)); // 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) const { 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 ExplicitArray { uint8_t m_length_pos; GIDirection m_length_direction : 2; ExplicitArray(int pos, GIDirection direction) : m_length_pos(pos), m_length_direction(direction) { g_assert(pos >= 0 && pos <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); } }; struct BasicCArray { constexpr explicit BasicCArray(GITypeTag element_tag) : m_element_tag(element_tag) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); } GITypeTag m_element_tag; }; struct ZeroTerminatedArray { constexpr explicit ZeroTerminatedArray(const GI::TypeInfo&) {} static bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentFlags flags, JS::HandleValue value) { return gjs_value_to_basic_array_gi_argument( cx, value, element_tag, GI_ARRAY_TYPE_C, arg, arg_name, GJS_ARGUMENT_ARGUMENT, flags); } static bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, JS::MutableHandleValue value) { return gjs_array_from_basic_zero_terminated_array( cx, value, element_tag, gjs_arg_get(arg)); } static void release_container(GIArgument* arg) { g_clear_pointer(&gjs_arg_member(arg), g_free); } static void release_contents(GIArgument* arg) { char** array = gjs_arg_get(arg); for (size_t ix = 0; array[ix]; ix++) g_free(array[ix]); } [[nodiscard]] static Maybe return_tag() { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct GArrayContainer { constexpr explicit GArrayContainer(const GI::TypeInfo&) {} static bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentFlags flags, JS::HandleValue value) { return gjs_value_to_basic_array_gi_argument( cx, value, element_tag, GI_ARRAY_TYPE_ARRAY, arg, arg_name, GJS_ARGUMENT_ARGUMENT, flags); } static bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, JS::MutableHandleValue value_out) { return gjs_value_from_basic_garray_gi_argument(cx, value_out, element_tag, arg); } static void release_container(GIArgument* arg) { g_clear_pointer(&gjs_arg_member(arg), g_array_unref); } static void release_contents(GIArgument* arg) { GArray* array = gjs_arg_get(arg); for (size_t ix = 0; ix < array->len; ix++) g_free(g_array_index(array, char*, ix)); } [[nodiscard]] static Maybe return_tag() { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct GPtrArrayContainer { constexpr explicit GPtrArrayContainer(const GI::TypeInfo&) {} static bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentFlags flags, JS::HandleValue value) { return gjs_value_to_basic_array_gi_argument( cx, value, element_tag, GI_ARRAY_TYPE_PTR_ARRAY, arg, arg_name, GJS_ARGUMENT_ARGUMENT, flags); } static bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, JS::MutableHandleValue value_out) { return gjs_value_from_basic_gptrarray_gi_argument(cx, value_out, element_tag, arg); } static void release_container(GIArgument* arg) { g_clear_pointer(&gjs_arg_member(arg), g_ptr_array_unref); } static void release_contents(GIArgument* arg) { GPtrArray* array = gjs_arg_get(arg); g_ptr_array_foreach( array, [](void* ptr, void*) { g_free(ptr); }, nullptr); } [[nodiscard]] static Maybe return_tag() { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct FixedSizeArray { explicit FixedSizeArray(const GI::TypeInfo& type_info) { size_t fixed_size = type_info.array_fixed_size().value(); g_assert( fixed_size <= UINT32_MAX && "4294967295 fixed array elements ought to be enough for anybody"); m_fixed_size = fixed_size; } uint32_t m_fixed_size = -1; static bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentFlags flags, JS::HandleValue value) { return gjs_value_to_basic_array_gi_argument( cx, value, element_tag, GI_ARRAY_TYPE_C, arg, arg_name, GJS_ARGUMENT_ARGUMENT, flags); } bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, JS::MutableHandleValue value) const { return gjs_value_from_basic_fixed_size_array_gi_argument( cx, value, element_tag, m_fixed_size, arg); } static void release_container(GIArgument* arg) { g_clear_pointer(&gjs_arg_member(arg), g_free); } void release_contents(GIArgument* arg) const { char** array = gjs_arg_get(arg); for (size_t ix = 0; ix < m_fixed_size; ix++) g_free(array[ix]); } [[nodiscard]] static Maybe return_tag() { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct GListContainer { explicit GListContainer(const GI::TypeInfo& type_info) : m_double_link(type_info.tag() == GI_TYPE_TAG_GLIST) {} bool m_double_link : 1; bool in(JSContext* cx, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentFlags, JS::HandleValue value) const { if (m_double_link) { return gjs_value_to_basic_glist_gi_argument( cx, value, element_tag, arg, arg_name, GJS_ARGUMENT_ARGUMENT); } return gjs_value_to_basic_gslist_gi_argument( cx, value, element_tag, arg, arg_name, GJS_ARGUMENT_ARGUMENT); } bool out(JSContext* cx, GITypeTag element_tag, GIArgument* arg, JS::MutableHandleValue value) const { if (m_double_link) return gjs_array_from_basic_glist_gi_argument(cx, value, element_tag, arg); return gjs_array_from_basic_gslist_gi_argument(cx, value, element_tag, arg); } void release_container(GIArgument* arg) const { if (m_double_link) g_clear_pointer(&gjs_arg_member(arg), g_list_free); else g_clear_pointer(&gjs_arg_member(arg), g_slist_free); } void release_contents(GIArgument* arg) const { GFunc free_gfunc = [](void* data, void*) { g_free(data); }; if (m_double_link) { GList* list = gjs_arg_get(arg); g_list_foreach(list, free_gfunc, nullptr); } else { GSList* list = gjs_arg_get(arg); g_slist_foreach(list, free_gfunc, nullptr); } } [[nodiscard]] Maybe return_tag() const { return Some(ReturnTag{container_tag()}); } [[nodiscard]] constexpr GITypeTag container_tag() const { return m_double_link ? GI_TYPE_TAG_GLIST : GI_TYPE_TAG_GSLIST; } }; struct GHashContainer { explicit GHashContainer(const GI::TypeInfo& type_info) : m_value_tag(type_info.value_type().tag()) {} [[nodiscard]] constexpr GITypeTag value_tag() const { return m_value_tag; } // Key type is managed by the basic container GITypeTag m_value_tag; }; template struct HasIntrospectionInfo { constexpr explicit HasIntrospectionInfo(const GI::UnownedInfo& info) : m_info(info) {} GI::OwnedInfo m_info; }; // boxed / union / GObject struct GTypedType { explicit GTypedType(GType gtype) : m_gtype(gtype) {} [[nodiscard]] constexpr GType gtype() const { return m_gtype; } protected: GType m_gtype; }; struct RegisteredType : GTypedType { RegisteredType(GType gtype, bool is_enum_or_flags) : GTypedType(gtype), m_is_enum_or_flags(is_enum_or_flags) {} explicit RegisteredType(const GI::RegisteredTypeInfo& info) : GTypedType(info.gtype()), m_is_enum_or_flags(info.is_enum_or_flags()) { g_assert(m_gtype != G_TYPE_NONE && "Use RegisteredInterface for this type"); } [[nodiscard]] Maybe return_tag() const { return Some(ReturnTag{GI_TYPE_TAG_INTERFACE, m_is_enum_or_flags, true}); } bool m_is_enum_or_flags : 1; }; template struct RegisteredInterface : HasIntrospectionInfo, GTypedType { explicit RegisteredInterface(const GI::UnownedInfo& info) : HasIntrospectionInfo(info), GTypedType(info.gtype()) {} [[nodiscard]] Maybe return_tag() const { return Some(ReturnTag{GI_TYPE_TAG_INTERFACE, HasIntrospectionInfo::m_info.type(), true}); } }; struct Callback : Nullable, HasIntrospectionInfo { explicit Callback(const GI::CallbackInfo& info, Maybe closure_pos, Maybe destroy_pos, GIScopeType scope) : HasIntrospectionInfo(info), m_closure_pos(closure_pos.valueOr(Argument::ABSENT)), m_destroy_pos(destroy_pos.valueOr(Argument::ABSENT)), m_scope(scope) { g_assert(destroy_pos.valueOr(0) <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); g_assert(closure_pos.valueOr(0) <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); } [[nodiscard]] constexpr bool has_callback_destroy() const { return m_destroy_pos != Argument::ABSENT; } [[nodiscard]] constexpr bool has_callback_closure() const { return m_closure_pos != Argument::ABSENT; } uint8_t m_closure_pos; uint8_t m_destroy_pos; GIScopeType m_scope : 3; }; struct Enum { explicit Enum(const GI::EnumInfo&); bool m_unsigned : 1; uint32_t m_min = 0; uint32_t m_max = 0; }; struct Flags { explicit Flags(const GI::FlagsInfo&); unsigned m_mask = 0; }; struct CallerAllocates { explicit CallerAllocates(size_t size) : m_allocates_size(size) {} size_t m_allocates_size; }; // 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 static bool skip() { return true; } }; struct Fallback : Transferable, HasTypeInfo { using HasTypeInfo::HasTypeInfo; }; struct FallbackIn : SkipAll, Fallback, Nullable { using Fallback::Fallback; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; [[nodiscard]] GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; struct FallbackInOut : SkipAll, Positioned, Fallback { using Fallback::Fallback; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct FallbackOut : FallbackInOut { using FallbackInOut::FallbackInOut; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct FallbackReturn : FallbackOut { using FallbackOut::FallbackOut; // No in! bool in(JSContext* cx, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override { return invalid(cx, G_STRFUNC); } [[nodiscard]] Maybe return_tag() const override { return HasTypeInfo::return_tag(); } }; 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); } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{MarshallingInfo::gi_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 BasicTypeReturn : SkipAll, BasicType { using BasicType::BasicType; 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_value_from_basic_gi_argument(cx, value, m_tag, arg); } bool release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { gjs_gi_argument_release_basic(GI_TRANSFER_NOTHING, m_tag, Argument::flags(), out_arg); return true; } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{m_tag}); } }; struct BasicTypeOut : BasicTypeReturn, Positioned { using BasicTypeReturn::BasicTypeReturn; bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } }; struct BasicTypeInOut : BasicTypeOut { using BasicTypeOut::BasicTypeOut; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if (!gjs_value_to_basic_gi_argument(cx, value, m_tag, arg, arg_name(), GJS_ARGUMENT_ARGUMENT, flags())) return false; return set_inout_parameter(state, arg); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg [[maybe_unused]]) override { GIArgument* original_out_arg = &(state->inout_original_cvalue(m_arg_pos)); gjs_gi_argument_release_basic(GI_TRANSFER_NOTHING, m_tag, flags(), original_out_arg); return true; } }; struct BasicTypeTransferableReturn : BasicTypeReturn, Transferable { using BasicTypeReturn::BasicTypeReturn; bool release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { gjs_gi_argument_release_basic(m_transfer, m_tag, Argument::flags(), out_arg); return true; } }; struct BasicTypeTransferableOut : BasicTypeTransferableReturn, Positioned { using BasicTypeTransferableReturn::BasicTypeTransferableReturn; bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } }; struct BasicTypeTransferableInOut : BasicTypeInOut, Transferable { using BasicTypeInOut::BasicTypeInOut; bool release(JSContext* cx, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) override { if (!BasicTypeInOut::release(cx, state, in_arg, out_arg)) return false; if (m_transfer != GI_TRANSFER_NOTHING) gjs_gi_argument_release_basic(m_transfer, m_tag, flags(), out_arg); return true; } }; struct ErrorIn : SkipAll, Transferable, Nullable { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_gerror_gi_argument(cx, value, m_transfer, arg, m_arg_name, GJS_ARGUMENT_ARGUMENT, flags()); } [[nodiscard]] GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; // The tag is normally used in containers for the element type, but for explicit // arrays we use it for the length argument type. Specific implementations can // override this struct BasicTypeContainerReturn : BasicTypeTransferableReturn, Nullable { explicit BasicTypeContainerReturn(GITypeTag element_tag) : BasicTypeTransferableReturn(element_tag) {} explicit BasicTypeContainerReturn(const GI::TypeInfo& type_info) : BasicTypeContainerReturn(type_info.element_type().tag()) {} [[nodiscard]] GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } [[nodiscard]] Maybe return_tag() const override { // in Return subclasses, this must be overridden with the container tag g_return_val_if_reached(Nothing{}); } constexpr GITypeTag element_tag() { return m_tag; } }; struct BasicTypeContainerOut : BasicTypeContainerReturn, Positioned { using BasicTypeContainerReturn::BasicTypeContainerReturn; }; struct BasicTypeContainerIn : BasicTypeContainerReturn { using BasicTypeContainerReturn::BasicTypeContainerReturn; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } }; struct BasicTypeContainerInOut : BasicTypeContainerOut { using BasicTypeContainerOut::BasicTypeContainerOut; }; template struct BasicTypeContainer : Marshaller, Container { explicit BasicTypeContainer(const GI::TypeInfo& type_info) : Marshaller(type_info), Container(type_info) {} bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if constexpr (std::is_same_v) { return Container::in(cx, Marshaller::element_tag(), arg, Marshaller::arg_name(), Marshaller::flags(), value); } if constexpr (std::is_same_v) return Marshaller::invalid(cx, G_STRFUNC); if constexpr (std::is_same_v) return Marshaller::set_out_parameter(state, arg); if constexpr (std::is_same_v) { return Container::in(cx, Marshaller::element_tag(), arg, Marshaller::arg_name(), Marshaller::flags(), value) && Marshaller::set_inout_parameter(state, arg); } g_return_val_if_reached(false); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { if constexpr (std::is_same_v) return Marshaller::skip(); if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { bool success = Container::out(cx, Marshaller::element_tag(), arg, value); GITransfer transfer = Marshaller::m_transfer; GITypeTag element_tag = Marshaller::element_tag(); if (!success && transfer == GI_TRANSFER_EVERYTHING) { if (Gjs::basic_type_needs_release(element_tag)) Container::release_contents(arg); if (transfer != GI_TRANSFER_CONTAINER) Container::release_container(arg); } return success; } g_return_val_if_reached(false); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg) override { GITransfer transfer = Marshaller::m_transfer; GITypeTag element_tag = Marshaller::element_tag(); if constexpr (std::is_same_v) { if (!state->call_completed()) transfer = GI_TRANSFER_NOTHING; if (!gjs_arg_get(in_arg) || transfer == GI_TRANSFER_EVERYTHING) return true; if (Gjs::basic_type_needs_release(element_tag)) Container::release_contents(in_arg); if (transfer != GI_TRANSFER_CONTAINER) Container::release_container(in_arg); return true; } if constexpr (std::is_same_v) { if (!state->call_completed()) transfer = GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &(state->inout_original_cvalue(Marshaller::m_arg_pos)); void* original_out_ptr = gjs_arg_get(original_out_arg); if (original_out_ptr && original_out_ptr != gjs_arg_get(out_arg) && transfer != GI_TRANSFER_EVERYTHING) { if (Gjs::basic_type_needs_release(element_tag)) Container::release_contents(original_out_arg); if (transfer != GI_TRANSFER_CONTAINER) Container::release_container(original_out_arg); } } if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { if (!gjs_arg_get(out_arg) || transfer == GI_TRANSFER_NOTHING) return true; if (Gjs::basic_type_needs_release(element_tag) && transfer != GI_TRANSFER_CONTAINER) Container::release_contents(out_arg); Container::release_container(out_arg); return true; } g_return_val_if_reached(false); } [[nodiscard]] Maybe return_tag() const override { return Container::return_tag(); } }; using BasicGListReturn = BasicTypeContainer; using BasicGListIn = BasicTypeContainer; using BasicGListOut = BasicTypeContainer; using BasicGListInOut = BasicTypeContainer; using BasicCZeroTerminatedArrayReturn = BasicTypeContainer; using BasicCZeroTerminatedArrayIn = BasicTypeContainer; using BasicCZeroTerminatedArrayOut = BasicTypeContainer; using BasicCZeroTerminatedArrayInOut = BasicTypeContainer; using BasicCFixedSizeArrayReturn = BasicTypeContainer; using BasicCFixedSizeArrayIn = BasicTypeContainer; using BasicCFixedSizeArrayOut = BasicTypeContainer; using BasicCFixedSizeArrayInOut = BasicTypeContainer; using BasicGArrayReturn = BasicTypeContainer; using BasicGArrayIn = BasicTypeContainer; using BasicGArrayOut = BasicTypeContainer; using BasicGArrayInOut = BasicTypeContainer; using BasicGPtrArrayReturn = BasicTypeContainer; using BasicGPtrArrayIn = BasicTypeContainer; using BasicGPtrArrayOut = BasicTypeContainer; using BasicGPtrArrayInOut = BasicTypeContainer; struct BasicGHashReturn : BasicTypeTransferableReturn, GHashContainer, Nullable { explicit BasicGHashReturn(const GI::TypeInfo& type_info) : BasicTypeTransferableReturn(type_info.key_type().tag()), GHashContainer(type_info) { g_assert(GI_TYPE_TAG_IS_BASIC(m_value_tag)); } bool out(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::MutableHandleValue value) override { return gjs_value_from_basic_ghash(cx, value, m_tag, m_value_tag, gjs_arg_get(arg)); } bool release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { if (m_transfer == GI_TRANSFER_NOTHING) return true; gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Releasing GIArgument ghash out param or return value"); gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, out_arg); return true; } [[nodiscard]] GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{GI_TYPE_TAG_GHASH}); } }; struct BasicGHashIn : BasicGHashReturn { using BasicGHashReturn::BasicGHashReturn; bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_basic_ghash_gi_argument( cx, value, m_tag, m_value_tag, m_transfer, arg, m_arg_name, GJS_ARGUMENT_ARGUMENT, flags()); } bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) override { // GI_TRANSFER_EVERYTHING: we don't own the argument anymore. // GI_TRANSFER_CONTAINER: See FIXME in gjs_array_to_g_list(); currently // an error and we won't get here. if (!state->call_completed() || m_transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument ghash in param"); gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, in_arg); return true; } }; struct BasicGHashOut : BasicGHashReturn, Positioned { using BasicGHashReturn::BasicGHashReturn; bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } }; struct BasicGHashInOut : BasicGHashOut { using BasicGHashOut::BasicGHashOut; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if (!gjs_value_to_basic_ghash_gi_argument( cx, value, m_tag, m_value_tag, m_transfer, arg, m_arg_name, GJS_ARGUMENT_ARGUMENT, flags())) return false; return set_inout_parameter(state, arg); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { GIArgument* original_out_arg = &(state->inout_original_cvalue(m_arg_pos)); gjs_gi_argument_release_basic_ghash(GI_TRANSFER_NOTHING, m_tag, m_value_tag, original_out_arg); if (m_transfer != GI_TRANSFER_NOTHING) gjs_gi_argument_release_basic_ghash(m_transfer, m_tag, m_value_tag, out_arg); return true; } }; struct ByteArrayReturn : SkipAll, Transferable { 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_value_from_byte_array_gi_argument(cx, value, arg); } bool release(JSContext*, GjsFunctionCallState*, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { if (m_transfer != GI_TRANSFER_NOTHING) gjs_gi_argument_release_byte_array(out_arg); return true; } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct ByteArrayIn : SkipAll, Transferable { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_byte_array_gi_argument(cx, value, arg, arg_name(), flags()); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, GIArgument* out_arg [[maybe_unused]]) override { if (!state->call_completed() || m_transfer != GI_TRANSFER_NOTHING) return true; gjs_gi_argument_release_byte_array(in_arg); return true; } }; struct ByteArrayOut : ByteArrayReturn, Positioned { using ByteArrayReturn::ByteArrayReturn; bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); } }; struct ByteArrayInOut : ByteArrayOut { using ByteArrayOut::ByteArrayOut; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_byte_array_gi_argument(cx, value, arg, m_arg_name, flags()) && set_inout_parameter(state, arg); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { GIArgument* original_out_arg = &(state->inout_original_cvalue(m_arg_pos)); if (m_transfer != GI_TRANSFER_EVERYTHING) gjs_gi_argument_release_byte_array(original_out_arg); if (m_transfer != GI_TRANSFER_NOTHING) gjs_gi_argument_release_byte_array(out_arg); return true; } }; struct ExplicitArrayBase : BasicTypeContainerReturn, ExplicitArray { ExplicitArrayBase(unsigned length_pos, GITypeTag length_tag, GIDirection length_direction) : BasicTypeContainerReturn(length_tag), ExplicitArray(length_pos, length_direction) {} }; struct CArrayIn : ExplicitArrayBase, HasTypeInfo { CArrayIn(const GI::TypeInfo& type_info, unsigned length_pos, GITypeTag length_tag, GIDirection length_direction) : ExplicitArrayBase(length_pos, length_tag, length_direction), HasTypeInfo(type_info) {} 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; }; // Positioned must come before HasTypeInfo for struct packing reasons, otherwise // this could inherit from CArrayIn struct CArrayInOut : ExplicitArrayBase, Positioned, HasTypeInfo { CArrayInOut(const GI::TypeInfo& type_info, unsigned length_pos, GITypeTag length_tag, GIDirection length_direction) : ExplicitArrayBase(length_pos, length_tag, length_direction), HasTypeInfo(type_info) {} bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct CArrayOut : CArrayInOut { using CArrayInOut::CArrayInOut; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) 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 set_out_parameter(state, arg); }; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; using ArrayLengthOut = SimpleOut; struct NotIntrospectable : SkipAll { explicit NotIntrospectable(NotIntrospectableReason reason) : m_reason(reason) {} NotIntrospectableReason m_reason; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; }; struct NullableIn : SkipAll, Nullable { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue) override { return handle_nullable(cx, arg, m_arg_name); } [[nodiscard]] 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(); } [[nodiscard]] Maybe as_instance() const override { return Some(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. [[nodiscard]] 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; [[nodiscard]] GType gtype() const override { return RegisteredType::gtype(); } }; template struct RegisteredInterfaceIn : Instance, RegisteredInterface, Transferable { using RegisteredInterface::RegisteredInterface; [[nodiscard]] 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 { using RegisteredInterfaceIn::RegisteredInterfaceIn; bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_interface_gi_argument( cx, value, m_info, m_transfer, arg, m_arg_name, GJS_ARGUMENT_ARGUMENT, flags()); } }; struct GdkAtomIn : NullableIn { bool in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) override { return gjs_value_to_gdk_atom_gi_argument(cx, value, arg, m_arg_name, GJS_ARGUMENT_ARGUMENT); } }; struct BoxedInTransferNone : RegisteredIn { using RegisteredIn::RegisteredIn; bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; [[nodiscard]] virtual Maybe info() const { return {}; } }; 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(const GI::StructInfo& info) : BoxedIn(info.gtype(), /* is_enum_or_flags = */ false), HasIntrospectionInfo(info) {} // This is a smart argument, no release needed }; 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 { bool success = Gjs::c_value_to_js_checked(cx, gjs_arg_get(arg), value); // do not leak since release is only for in args if constexpr (TRANSFER == GI_TRANSFER_EVERYTHING) { if (!success) { g_clear_pointer(&gjs_arg_member(arg), g_free); } } return success; } 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); } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{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; }; struct BasicExplicitCArrayOut : ExplicitArrayBase, BasicCArray, Positioned { explicit BasicExplicitCArrayOut(GITypeTag element_tag, unsigned length_pos, GITypeTag length_tag, GIDirection length_direction) : ExplicitArrayBase(length_pos, length_tag, length_direction), BasicCArray(element_tag) {} bool in(JSContext*, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue) override { return set_out_parameter(state, arg); }; bool out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::MutableHandleValue value) override { GIArgument* length_arg = &(state->out_cvalue(m_length_pos)); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); return gjs_value_from_basic_explicit_array(cx, value, m_element_tag, arg, length); } bool release(JSContext*, GjsFunctionCallState* state, [[maybe_unused]] GIArgument* in_arg, GIArgument* out_arg) override { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); gjs_gi_argument_release_basic_out_array(m_transfer, m_element_tag, length, out_arg); return true; } [[nodiscard]] Maybe return_tag() const override { return Some(ReturnTag{GI_TYPE_TAG_ARRAY}); } }; struct BasicExplicitCArrayIn : BasicExplicitCArrayOut { using BasicExplicitCArrayOut::BasicExplicitCArrayOut; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { void* data; size_t length; if (!gjs_array_to_basic_explicit_array( cx, value, m_element_tag, m_arg_name, GJS_ARGUMENT_ARGUMENT, 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; } bool out(JSContext*, GjsFunctionCallState*, GIArgument*, JS::MutableHandleValue) override { return skip(); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg, [[maybe_unused]] GIArgument* out_arg) override { 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; gjs_gi_argument_release_basic_in_array(transfer, m_element_tag, length, in_arg); return true; } }; struct BasicExplicitCArrayInOut : BasicExplicitCArrayIn { using BasicExplicitCArrayIn::BasicExplicitCArrayIn; bool in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) override { if (!BasicExplicitCArrayIn::in(cx, state, arg, value)) return false; 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(m_length_pos)); gjs_arg_unset(&state->out_cvalue(m_length_pos)); gjs_arg_unset(&state->inout_original_cvalue(m_length_pos)); gjs_arg_unset(&state->out_cvalue(m_arg_pos)); gjs_arg_unset(&state->inout_original_cvalue(m_arg_pos)); return true; } state->out_cvalue(m_length_pos) = state->inout_original_cvalue(m_length_pos) = state->in_cvalue(m_length_pos); gjs_arg_set(&state->in_cvalue(m_length_pos), &state->out_cvalue(m_length_pos)); return set_inout_parameter(state, arg); } bool out(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::MutableHandleValue value) override { GIArgument* length_arg = &(state->out_cvalue(m_length_pos)); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); return gjs_value_from_basic_explicit_array(cx, value, m_element_tag, arg, length); } bool release(JSContext*, GjsFunctionCallState* state, GIArgument* in_arg [[maybe_unused]], GIArgument* out_arg) override { GIArgument* length_arg = &state->out_cvalue(m_length_pos); size_t length = gjs_gi_argument_get_array_length(m_tag, length_arg); GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg)) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; gjs_gi_argument_release_basic_in_array(transfer, m_element_tag, length, original_out_arg); } gjs_gi_argument_release_basic_out_array(m_transfer, m_element_tag, length, out_arg); return true; } }; struct CallerAllocatesOut : FallbackOut, CallerAllocates { CallerAllocatesOut(const GI::TypeInfo& type_info, size_t size) : FallbackOut(type_info), CallerAllocates(size) {} bool in(JSContext*, GjsFunctionCallState*, GIArgument*, JS::HandleValue) override; bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; [[nodiscard]] GjsArgumentFlags flags() const override { return FallbackOut::flags() | GjsArgumentFlags::CALLER_ALLOCATES; } }; struct BoxedCallerAllocatesOut : CallerAllocatesOut, GTypedType { BoxedCallerAllocatesOut(const GI::TypeInfo& type_info, size_t size, GType gtype) : CallerAllocatesOut(type_info, size), GTypedType(gtype) {} bool release(JSContext*, GjsFunctionCallState*, GIArgument*, GIArgument*) override; }; struct ZeroTerminatedArrayInOut : FallbackInOut { using FallbackInOut::FallbackInOut; 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 : FallbackIn { using FallbackIn::FallbackIn; 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); } [[nodiscard]] GjsArgumentFlags flags() const override { return Argument::flags() | Nullable::flags(); } }; struct FixedSizeArrayIn : FallbackIn { using FallbackIn::FallbackIn; 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; size_t size = m_type_info.array_fixed_size().value(); return gjs_gi_argument_release_in_array(cx, transfer, m_type_info, size, in_arg); } }; struct FixedSizeArrayInOut : FallbackInOut { using FallbackInOut::FallbackInOut; 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); size_t size = m_type_info.array_fixed_size().value(); 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); } }; static const char* reason_strings[] = { "callback out-argument", // CALLBACK_OUT "DestroyNotify argument with no callback", // DESTROY_NOTIFY_NO_CALLBACK "DestroyNotify argument with no user data", // DESTROY_NOTIFY_NO_USER_DATA "type not supported for (transfer container)", // INTERFACE_TRANSFER_CONTAINER "type not supported for (out caller-allocates)", // OUT_CALLER_ALLOCATES_NON_STRUCT "boxed type with transfer not registered as a GType", // UNREGISTERED_BOXED_WITH_TRANSFER "union type not registered as a GType", // UNREGISTERED_UNION "type not supported by introspection", // UNSUPPORTED_TYPE }; static_assert(G_N_ELEMENTS(reason_strings) == LAST_REASON); GJS_JSAPI_RETURN_CONVENTION bool NotIntrospectable::in(JSContext* cx, GjsFunctionCallState* state, GIArgument*, JS::HandleValue) { gjs_throw(cx, "Function %s() cannot be called: argument '%s' is not " "introspectable because it has a %s", state->display_name().get(), m_arg_name, reason_strings[m_reason]); return false; } GJS_JSAPI_RETURN_CONVENTION bool FallbackIn::in(JSContext* cx, GjsFunctionCallState*, GIArgument* arg, JS::HandleValue value) { return gjs_value_to_gi_argument(cx, value, m_type_info, GJS_ARGUMENT_ARGUMENT, m_transfer, arg, flags(), m_arg_name); } GJS_JSAPI_RETURN_CONVENTION bool FallbackInOut::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { return gjs_value_to_gi_argument(cx, value, m_type_info, GJS_ARGUMENT_ARGUMENT, m_transfer, arg, flags(), m_arg_name) && set_inout_parameter(state, arg); } GJS_JSAPI_RETURN_CONVENTION bool CArrayIn::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { void* data; size_t length; if (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 CArrayInOut::in(JSContext* cx, GjsFunctionCallState* state, GIArgument* arg, JS::HandleValue value) { if (m_length_direction != GI_DIRECTION_INOUT) { gjs_throw(cx, "Using different length argument direction for array %s is " "not supported for inout arrays", m_arg_name); return false; } void* data; size_t length; 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); 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 { 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* callback; if (value.isNull() && m_nullable) { callback = 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; } callback = trampoline->get_func_ptr(); 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() && (m_scope == GI_SCOPE_TYPE_FOREVER || m_scope == GI_SCOPE_TYPE_NOTIFIED); if (trampoline && keep_forever) { trampoline->mark_forever(); } gjs_arg_set(arg, callback); return true; } GJS_JSAPI_RETURN_CONVENTION bool FallbackOut::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)", Gjs::AutoChar{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) const { 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) { AutoChar 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, js_chars_to_glib(std::move(str)).release()); 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; uint64_t bits = static_cast(number); if ((bits & m_mask) != bits) { gjs_throw(cx, "0x%" PRIx64 " is not a valid value for flags argument %s", bits, 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, StructBase::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); } if (info() && !StructBase::typecheck(cx, object, info().ref())) { gjs_arg_unset(arg); return false; } return StructBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN, m_transfer, gtype); } // 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 StructBase::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, 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 FallbackInOut::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 CArrayInOut::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, arg, length, m_transfer); } GJS_JSAPI_RETURN_CONVENTION bool FallbackIn::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 FallbackOut::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 FallbackInOut::release(JSContext* cx, GjsFunctionCallState* state, GIArgument*, GIArgument* out_arg) { GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; GIArgument* original_out_arg = &state->inout_original_cvalue(m_arg_pos); // Assume that inout transfer means that in and out transfer are the same. // See https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/192 if (gjs_arg_get(original_out_arg) != gjs_arg_get(out_arg) && !gjs_gi_argument_release_in_arg(cx, transfer, m_type_info, original_out_arg)) return false; return gjs_gi_argument_release(cx, transfer, m_type_info, out_arg); } GJS_JSAPI_RETURN_CONVENTION bool CArrayOut::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 CArrayIn::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 CArrayInOut::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); GITransfer transfer = state->call_completed() ? m_transfer : GI_TRANSFER_NOTHING; 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)) { if (!gjs_gi_argument_release_in_array(cx, transfer, m_type_info, length, original_out_arg)) return false; } 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) { 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 || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v> || std::is_same_v> || std::is_same_v> || std::is_same_v> || std::is_same_v || std::is_same_v) return 24; if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) return 40; if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) return 48; if constexpr (std::is_same_v) return 56; if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) return 176; // FIXME if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) return 184; // FIXME if constexpr (std::is_same_v) return 192; // FIXME else return 32; } #endif template void Argument::init_common(const Init& init, T* arg) { #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 if constexpr (ArgKind == Arg::Kind::INSTANCE) { g_assert(init.index == Argument::ABSENT && "index was ignored in INSTANCE parameter"); g_assert(init.name == nullptr && "name was ignored in INSTANCE parameter"); arg->set_instance_parameter(); } else if constexpr (ArgKind == Arg::Kind::RETURN_VALUE) { g_assert(init.index == Argument::ABSENT && "index was ignored in RETURN_VALUE parameter"); g_assert(init.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(init.index); arg->m_arg_name = init.name; } arg->m_skip_in = (init.flags & GjsArgumentFlags::SKIP_IN); arg->m_skip_out = (init.flags & GjsArgumentFlags::SKIP_OUT); if constexpr (std::is_base_of_v) arg->m_nullable = (init.flags & GjsArgumentFlags::MAY_BE_NULL); if constexpr (std::is_base_of_v) arg->m_transfer = init.transfer; } bool ArgsCache::initialize(JSContext* cx, const GI::CallableInfo& callable) { if (m_args) { gjs_throw(cx, "Arguments cache already initialized!"); return false; } GI::StackTypeInfo type_info; callable.load_return_type(&type_info); m_has_return = type_info.tag() != GI_TYPE_TAG_VOID || type_info.is_pointer(); m_is_method = callable.is_method(); int size = callable.n_args(); 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 void ArgsCache::set_argument(T* arg, const Argument::Init& init) { Argument::init_common(init, arg); arg_get(init.index) = arg; } template constexpr void ArgsCache::set_return(T* arg, GITransfer transfer, GjsArgumentFlags flags) { set_argument( arg, Argument::Init{nullptr, Argument::ABSENT, transfer, flags}); } template constexpr void ArgsCache::set_instance(T* arg, GITransfer transfer, GjsArgumentFlags flags) { set_argument( arg, Argument::Init{nullptr, Argument::ABSENT, transfer, flags}); } Maybe ArgsCache::instance_type() const { return instance() .andThen(std::mem_fn(&Argument::as_instance)) .map(std::mem_fn(&Arg::Instance::gtype)); } Maybe ArgsCache::return_tag() const { return return_value().andThen(std::mem_fn(&Argument::return_tag)); } void ArgsCache::set_skip_all(uint8_t index, const char* name) { set_argument(new Arg::SkipAll(), Argument::Init{name, index, GI_TRANSFER_NOTHING, GjsArgumentFlags::SKIP_ALL}); } void ArgsCache::init_out_array_length_argument(const GI::ArgInfo& length_arg, GjsArgumentFlags flags, unsigned length_pos) { // Even if we skip the length argument most of time, we need to do some // basic initialization here. g_assert(length_pos <= Argument::MAX_ARGS && "too many arguments"); uint8_t validated_length_pos = length_pos; set_argument(new Arg::ArrayLengthOut(), Argument::Init{length_arg.name(), validated_length_pos, GI_TRANSFER_NOTHING, flags | GjsArgumentFlags::SKIP_ALL}); } void ArgsCache::set_array_argument( const GI::CallableInfo& callable, uint8_t gi_index, const GI::TypeInfo& type_info, GIDirection direction, const GI::ArgInfo& arg, GjsArgumentFlags flags, unsigned length_pos) { g_assert(type_info.array_type() == GI_ARRAY_TYPE_C); GI::AutoTypeInfo element_type{type_info.element_type()}; GI::StackArgInfo length_arg; callable.load_arg(length_pos, &length_arg); GI::StackTypeInfo length_type; length_arg.load_type(&length_type); GITypeTag length_tag = length_type.tag(); GIDirection length_direction = length_arg.direction(); Argument::Init common_args{arg.name(), gi_index, arg.ownership_transfer(), flags}; if (direction == GI_DIRECTION_IN) { if (element_type.is_basic()) { set_argument( new Arg::BasicExplicitCArrayIn(element_type.tag(), length_pos, length_tag, length_direction), common_args); } else { set_argument(new Arg::CArrayIn(type_info, length_pos, length_tag, length_direction), common_args); } set_skip_all(length_pos, length_arg.name()); } else if (direction == GI_DIRECTION_INOUT) { if (element_type.is_basic()) { set_argument(new Arg::BasicExplicitCArrayInOut( element_type.tag(), length_pos, length_tag, length_direction), common_args); } else { set_argument(new Arg::CArrayInOut(type_info, length_pos, length_tag, length_direction), common_args); } set_skip_all(length_pos, length_arg.name()); } else { if (element_type.is_basic()) { set_argument( new Arg::BasicExplicitCArrayOut(element_type.tag(), length_pos, length_tag, length_direction), common_args); } else { set_argument(new Arg::CArrayOut(type_info, length_pos, length_tag, length_direction), common_args); } } if (direction == GI_DIRECTION_OUT) init_out_array_length_argument(length_arg, flags, length_pos); } void ArgsCache::set_array_return(const GI::CallableInfo& callable, const GI::TypeInfo& type_info, GjsArgumentFlags flags, unsigned length_pos) { g_assert(type_info.array_type() == GI_ARRAY_TYPE_C); GI::AutoTypeInfo element_type{type_info.element_type()}; GI::StackArgInfo length_arg; callable.load_arg(length_pos, &length_arg); GI::StackTypeInfo length_type; length_arg.load_type(&length_type); GITypeTag length_tag = length_type.tag(); GIDirection length_direction = length_arg.direction(); GITransfer transfer = callable.caller_owns(); if (element_type.is_basic()) { set_return( new Arg::BasicExplicitCArrayOut(element_type.tag(), length_pos, length_tag, length_direction), transfer, GjsArgumentFlags::NONE); } else { set_return(new Arg::CArrayOut(type_info, length_pos, length_tag, length_direction), transfer, GjsArgumentFlags::NONE); } init_out_array_length_argument(length_arg, flags, length_pos); } void ArgsCache::build_return(const GI::CallableInfo& callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); if (!m_has_return) { *inc_counter_out = false; return; } GI::StackTypeInfo type_info; callable.load_return_type(&type_info); GITransfer transfer = callable.caller_owns(); GITypeTag tag = type_info.tag(); *inc_counter_out = true; GjsArgumentFlags flags = GjsArgumentFlags::SKIP_IN; if (callable.may_return_null()) flags |= GjsArgumentFlags::MAY_BE_NULL; switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_return(new Arg::BooleanReturn(), transfer, flags); return; case GI_TYPE_TAG_INT8: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT16: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT32: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT8: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT16: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT32: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_INT64: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UINT64: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_FLOAT: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_DOUBLE: set_return(new Arg::NumericReturn(), transfer, flags); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { set_return(new Arg::StringReturn(), transfer, flags); return; } else { set_return(new Arg::StringReturn(), transfer, flags); return; } case GI_TYPE_TAG_ARRAY: { Maybe length_pos = type_info.array_length_index(); if (length_pos) { set_array_return(callable, type_info, flags, *length_pos); return; } GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { set_return(new Arg::ByteArrayReturn(), transfer, flags); return; } if (type_info.element_type().is_basic()) { if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { set_return( new Arg::BasicCZeroTerminatedArrayReturn(type_info), transfer, flags); return; } if (type_info.array_fixed_size()) { set_return( new Arg::BasicCFixedSizeArrayReturn(type_info), transfer, flags); return; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { set_return(new Arg::BasicGArrayReturn(type_info), transfer, flags); return; } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { set_return(new Arg::BasicGPtrArrayReturn(type_info), transfer, flags); return; } } [[fallthrough]]; } default: break; } if (type_info.is_basic()) { if (transfer == GI_TRANSFER_NOTHING) { set_return(new Arg::BasicTypeReturn(tag), transfer, flags); } else { set_return(new Arg::BasicTypeTransferableReturn(tag), transfer, flags); } return; } if (tag == GI_TYPE_TAG_GLIST || tag == GI_TYPE_TAG_GSLIST) { if (type_info.element_type().is_basic()) { set_return(new Arg::BasicGListReturn(type_info), transfer, flags); return; } } else if (tag == GI_TYPE_TAG_GHASH) { if (type_info.key_type().is_basic() && type_info.value_type().is_basic()) { set_return(new Arg::BasicGHashReturn(type_info), transfer, flags); return; } } // in() is ignored for the return value, but skip_in is not (it is used // in the failure release path) set_return(new Arg::FallbackReturn(type_info), transfer, flags); } namespace Arg { Enum::Enum(const GI::EnumInfo& info) { int64_t min = std::numeric_limits::max(); int64_t max = std::numeric_limits::min(); for (GI::AutoValueInfo value_info : info.values()) { int64_t value = value_info.value(); max = std::max(value, max); min = std::min(value, min); } // 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(const GI::FlagsInfo& info) { uint64_t mask = 0; for (GI::AutoValueInfo value_info : info.values()) { // 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_info.value()); } m_mask = mask; } } // namespace Arg template void ArgsCache::build_interface_in_arg(const Argument::Init& base_args, const GI::BaseInfo& 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(base_args.transfer == GI_TRANSFER_CONTAINER)) { set_argument( new Arg::NotIntrospectable(INTERFACE_TRANSFER_CONTAINER), base_args); return; } if (auto flags_info = interface_info.as()) { set_argument(new Arg::FlagsIn(*flags_info), base_args); return; } if (auto enum_info = interface_info.as()) { set_argument(new Arg::EnumIn(*enum_info), base_args); return; } auto struct_info = interface_info.as(); if (struct_info && struct_info->is_foreign()) { if constexpr (ArgKind == Arg::Kind::INSTANCE) set_argument( new Arg::ForeignStructInstanceIn(*struct_info), base_args); else set_argument(new Arg::ForeignStructIn(*struct_info), base_args); return; } if (auto reg_info = interface_info.as()) { if (reg_info->is_gdk_atom()) { if constexpr (ArgKind != Arg::Kind::INSTANCE) { set_argument(new Arg::GdkAtomIn(), base_args); return; } } GType gtype = reg_info->gtype(); if (struct_info && gtype == G_TYPE_NONE && !struct_info->is_gtype_struct()) { if constexpr (ArgKind != Arg::Kind::INSTANCE) { // This covers cases such as GTypeInstance set_argument(new Arg::FallbackInterfaceIn(*reg_info), base_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(new Arg::BoxedIn(*reg_info), base_args); else if (base_args.transfer == GI_TRANSFER_NOTHING) set_argument(new Arg::GValueInTransferNone(*reg_info), base_args); else set_argument(new Arg::GValueIn(*reg_info), base_args); return; } if (gtype == G_TYPE_CLOSURE) { if (base_args.transfer == GI_TRANSFER_NOTHING && ArgKind != Arg::Kind::INSTANCE) set_argument( new Arg::GClosureInTransferNone(*reg_info), base_args); else set_argument(new Arg::GClosureIn(*reg_info), base_args); return; } if (gtype == G_TYPE_BYTES) { if (base_args.transfer == GI_TRANSFER_NOTHING && ArgKind != Arg::Kind::INSTANCE) set_argument(new Arg::GBytesInTransferNone(*reg_info), base_args); else set_argument(new Arg::GBytesIn(*reg_info), base_args); return; } if (g_type_is_a(gtype, G_TYPE_OBJECT)) { set_argument(new Arg::ObjectIn(*reg_info), base_args); return; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { if constexpr (ArgKind != Arg::Kind::INSTANCE) { set_argument(new Arg::FallbackInterfaceIn(*reg_info), base_args); return; } } if (interface_info.is_union()) { if (gtype == G_TYPE_NONE) { // Can't handle unions without a GType set_argument( new Arg::NotIntrospectable(UNREGISTERED_UNION), base_args); return; } set_argument(new Arg::UnionIn(*reg_info), base_args); return; } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { set_argument(new Arg::FundamentalIn(*reg_info), base_args); return; } if (g_type_is_a(gtype, G_TYPE_INTERFACE)) { set_argument(new Arg::InterfaceIn(*reg_info), base_args); return; } // generic boxed type if (gtype == G_TYPE_NONE) { if (base_args.transfer != GI_TRANSFER_NOTHING) { // Can't transfer ownership of a structure type not // registered as a boxed set_argument(new Arg::NotIntrospectable( UNREGISTERED_BOXED_WITH_TRANSFER), base_args); return; } set_argument(new Arg::UnregisteredBoxedIn(*struct_info), base_args); return; } set_argument(new Arg::BoxedIn(*reg_info), base_args); return; } // Don't know how to handle this interface type (should not happen // in practice, for typelibs emitted by g-ir-compiler) set_argument(new Arg::NotIntrospectable(UNSUPPORTED_TYPE), base_args); } void ArgsCache::build_normal_in_arg(uint8_t gi_index, const GI::TypeInfo& type_info, const GI::ArgInfo& 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 = arg.name(); GITransfer transfer = arg.ownership_transfer(); Argument::Init common_args{name, gi_index, transfer, flags}; switch (type_info.tag()) { case GI_TYPE_TAG_VOID: set_argument(new Arg::NullIn(), common_args); break; case GI_TYPE_TAG_BOOLEAN: set_argument(new Arg::BooleanIn(), common_args); break; case GI_TYPE_TAG_INT8: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT16: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT32: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT8: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT16: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT32: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_INT64: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UINT64: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_FLOAT: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument(new Arg::NumericIn(), common_args); return; case GI_TYPE_TAG_UNICHAR: set_argument(new Arg::UnicharIn(), common_args); break; case GI_TYPE_TAG_GTYPE: set_argument(new Arg::GTypeIn(), common_args); break; case GI_TYPE_TAG_FILENAME: if (transfer == GI_TRANSFER_NOTHING) set_argument(new Arg::FilenameInTransferNone(), common_args); else set_argument(new Arg::FilenameIn(), common_args); break; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) set_argument(new Arg::StringInTransferNone(), common_args); else set_argument(new Arg::StringIn(), common_args); break; case GI_TYPE_TAG_INTERFACE: { build_interface_in_arg(common_args, type_info.interface()); return; } case GI_TYPE_TAG_ERROR: set_argument(new Arg::ErrorIn(), common_args); return; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: if (type_info.element_type().is_basic()) { set_argument(new Arg::BasicGListIn(type_info), common_args); return; } // Fall back to the generic marshaller set_argument(new Arg::FallbackIn(type_info), common_args); return; case GI_TYPE_TAG_GHASH: if (type_info.key_type().is_basic() && type_info.value_type().is_basic()) { set_argument(new Arg::BasicGHashIn(type_info), common_args); return; } // Fall back to the generic marshaller set_argument(new Arg::FallbackIn(type_info), common_args); return; case GI_TYPE_TAG_ARRAY: { GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { set_argument(new Arg::ByteArrayIn(), common_args); return; } if (type_info.element_type().is_basic()) { if (array_type == GI_ARRAY_TYPE_ARRAY) { set_argument(new Arg::BasicGArrayIn(type_info), common_args); return; } if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { set_argument(new Arg::BasicGPtrArrayIn(type_info), common_args); return; } if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { set_argument( new Arg::BasicCZeroTerminatedArrayIn(type_info), common_args); return; } if (type_info.array_fixed_size()) { set_argument(new Arg::BasicCFixedSizeArrayIn(type_info), common_args); return; } } } if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { set_argument(new Arg::ZeroTerminatedArrayIn(type_info), common_args); return; } if (type_info.array_fixed_size()) { set_argument(new Arg::FixedSizeArrayIn(type_info), common_args); return; } } [[fallthrough]]; } default: // FIXME: Falling back to the generic marshaller set_argument(new Arg::FallbackIn(type_info), common_args); } } void ArgsCache::build_normal_out_arg(uint8_t gi_index, const GI::TypeInfo& type_info, const GI::ArgInfo& arg, GjsArgumentFlags flags) { GITransfer transfer = arg.ownership_transfer(); Argument::Init common_args{arg.name(), gi_index, transfer, flags}; GITypeTag tag = type_info.tag(); switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_argument(new Arg::BooleanOut(), common_args); break; case GI_TYPE_TAG_INT8: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT16: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT32: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT8: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT16: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT32: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_INT64: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UINT64: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_FLOAT: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument(new Arg::NumericOut(), common_args); return; case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { set_argument(new Arg::StringOut(), common_args); } else { set_argument(new Arg::StringOut(), common_args); } return; default: { } } if (type_info.is_basic()) { if (transfer == GI_TRANSFER_NOTHING) { set_argument(new Arg::BasicTypeOut(tag), common_args); } else { set_argument(new Arg::BasicTypeTransferableOut(tag), common_args); } return; } if (tag == GI_TYPE_TAG_ARRAY) { GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { set_argument(new Arg::ByteArrayOut(), common_args); return; } if (type_info.element_type().is_basic()) { if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { set_argument( new Arg::BasicCZeroTerminatedArrayOut(type_info), common_args); return; } if (type_info.array_fixed_size()) { set_argument(new Arg::BasicCFixedSizeArrayOut(type_info), common_args); return; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { set_argument(new Arg::BasicGArrayOut(type_info), common_args); return; } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { set_argument(new Arg::BasicGPtrArrayOut(type_info), common_args); return; } } } if (tag == GI_TYPE_TAG_GLIST || tag == GI_TYPE_TAG_GSLIST) { if (type_info.element_type().is_basic()) { set_argument(new Arg::BasicGListOut(type_info), common_args); return; } } else if (tag == GI_TYPE_TAG_GHASH) { if (type_info.key_type().is_basic() && type_info.value_type().is_basic()) { set_argument(new Arg::BasicGHashOut(type_info), common_args); return; } } else if (tag == GI_TYPE_TAG_ARRAY) { GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { set_argument(new Arg::ByteArrayOut(), common_args); return; } if (type_info.element_type().is_basic()) { if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { set_argument( new Arg::BasicCZeroTerminatedArrayOut(type_info), common_args); return; } if (type_info.array_fixed_size()) { set_argument(new Arg::BasicCFixedSizeArrayOut(type_info), common_args); return; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { set_argument(new Arg::BasicGArrayOut(type_info), common_args); return; } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { set_argument(new Arg::BasicGPtrArrayOut(type_info), common_args); return; } } } set_argument(new Arg::FallbackOut(type_info), common_args); } void ArgsCache::build_normal_inout_arg(uint8_t gi_index, const GI::TypeInfo& type_info, const GI::ArgInfo& arg, GjsArgumentFlags flags) { GITransfer transfer = arg.ownership_transfer(); Argument::Init common_args{arg.name(), gi_index, transfer, flags}; GITypeTag tag = type_info.tag(); switch (tag) { case GI_TYPE_TAG_BOOLEAN: set_argument(new Arg::BooleanInOut(), common_args); break; case GI_TYPE_TAG_INT8: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT16: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT32: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT8: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT16: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT32: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_INT64: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_UINT64: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_FLOAT: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_DOUBLE: set_argument(new Arg::NumericInOut(), common_args); return; case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: if (type_info.element_type().is_basic()) { set_argument(new Arg::BasicGListInOut(type_info), common_args); return; } set_argument(new Arg::FallbackInOut(type_info), common_args); return; case GI_TYPE_TAG_GHASH: if (type_info.key_type().is_basic() && type_info.value_type().is_basic()) { set_argument(new Arg::BasicGHashInOut(type_info), common_args); return; } set_argument(new Arg::FallbackInOut(type_info), common_args); return; case GI_TYPE_TAG_ARRAY: { GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) { set_argument(new Arg::ByteArrayInOut(), common_args); return; } if (array_type == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { if (type_info.element_type().is_basic()) { set_argument( new Arg::BasicCZeroTerminatedArrayInOut(type_info), common_args); return; } set_argument(new Arg::ZeroTerminatedArrayInOut(type_info), common_args); return; } if (type_info.array_fixed_size()) { if (type_info.element_type().is_basic()) { set_argument( new Arg::BasicCFixedSizeArrayInOut(type_info), common_args); return; } set_argument(new Arg::FixedSizeArrayInOut(type_info), common_args); return; } } else if (array_type == GI_ARRAY_TYPE_ARRAY) { set_argument(new Arg::BasicGArrayInOut(type_info), common_args); return; } else if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { set_argument(new Arg::BasicGPtrArrayInOut(type_info), common_args); return; } set_argument(new Arg::FallbackInOut(type_info), common_args); return; } default: { } } if (type_info.is_basic()) { if (transfer == GI_TRANSFER_NOTHING) { set_argument(new Arg::BasicTypeInOut(tag), common_args); } else { set_argument(new Arg::BasicTypeTransferableInOut(tag), common_args); } return; } set_argument(new Arg::FallbackInOut(type_info), common_args); } void ArgsCache::build_instance(const GI::CallableInfo& callable) { if (!m_is_method) return; Maybe interface_info = callable.container(); g_assert(interface_info && "callable must be a contained type"); GITransfer transfer = callable.instance_ownership_transfer(); // 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 auto struct_info = interface_info->as(); if (struct_info && struct_info->is_gtype_struct()) { set_instance(new Arg::GTypeStructInstanceIn(), transfer); return; } if (auto reg_info = interface_info->as()) { GType gtype = reg_info->gtype(); if (g_type_is_a(gtype, G_TYPE_PARAM)) { set_instance(new Arg::ParamInstanceIn(), transfer); return; } } build_interface_in_arg( Argument::Init{nullptr, Argument::ABSENT, transfer, GjsArgumentFlags::NONE}, *interface_info); } 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, const GI::ArgInfo& arg, const GI::CallableInfo& callable, bool* inc_counter_out) { g_assert(inc_counter_out && "forgot out parameter"); GI::StackTypeInfo type_info; arg.load_type(&type_info); GjsArgumentFlags flags = GjsArgumentFlags::NONE; if (arg.may_be_null()) flags |= GjsArgumentFlags::MAY_BE_NULL; if ((direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) && arg.is_optional()) flags |= GjsArgumentFlags::MAY_BE_NULL; if (arg.caller_allocates()) 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; Argument::Init common_args{arg.name(), gi_index, arg.ownership_transfer(), flags}; GITypeTag type_tag = type_info.tag(); if (direction == GI_DIRECTION_OUT && (flags & GjsArgumentFlags::CALLER_ALLOCATES)) { size_t size = 0; if (type_tag == GI_TYPE_TAG_ARRAY) { switch (type_info.array_type()) { case GI_ARRAY_TYPE_C: { Maybe n_elements = type_info.array_fixed_size(); if (!n_elements || *n_elements == 0) break; GI::AutoTypeInfo element_type{type_info.element_type()}; size = gjs_type_get_element_size(element_type.tag(), element_type); size *= *n_elements; break; } default: break; } } else if (!type_tag_is_scalar(type_tag) && !type_info.is_pointer()) { // 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( new Arg::NotIntrospectable(OUT_CALLER_ALLOCATES_NON_STRUCT), common_args); return; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface_info{type_info.interface()}; // FIXME: What if it is not a registered type GType gtype = interface_info.as()->gtype(); if (g_type_is_a(gtype, G_TYPE_BOXED)) { set_argument( new Arg::BoxedCallerAllocatesOut(type_info, size, gtype), common_args); return; } } set_argument(new Arg::CallerAllocatesOut(type_info, size), common_args); return; } if (type_tag == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface_info{type_info.interface()}; if (auto callback_info = interface_info.as()) { if (direction != GI_DIRECTION_IN) { // Can't do callbacks for out or inout set_argument(new Arg::NotIntrospectable(CALLBACK_OUT), common_args); 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( new Arg::NotIntrospectable(DESTROY_NOTIFY_NO_CALLBACK), common_args); *inc_counter_out = false; } else { Maybe destroy_pos = arg.destroy_index(); Maybe closure_pos = arg.closure_index(); g_assert(destroy_pos.valueOr(0) <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); g_assert(closure_pos.valueOr(0) <= Argument::MAX_ARGS && "No more than 253 arguments allowed"); if (destroy_pos) set_skip_all(*destroy_pos); if (closure_pos) set_skip_all(*closure_pos); if (destroy_pos && !closure_pos) { set_argument( new Arg::NotIntrospectable(DESTROY_NOTIFY_NO_USER_DATA), common_args); return; } set_argument(new Arg::CallbackIn(*callback_info, closure_pos, destroy_pos, arg.scope()), common_args); } return; } } if (type_tag == GI_TYPE_TAG_ARRAY && type_info.array_type() == GI_ARRAY_TYPE_C) { Maybe length_pos = type_info.array_length_index(); if (length_pos) { 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; } } 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); } } // namespace Gjs cjs-140.0/gi/arg-cache.h0000664000175000017500000002334015167114161013610 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 #pragma once #include #include #include #include #include #include #include #include "gi/arg.h" #include "gi/info.h" #include "cjs/auto.h" #include "cjs/enum-utils.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 { struct Instance; enum class Kind : uint8_t { NORMAL, INSTANCE, RETURN_VALUE, }; class ReturnTag { GITypeTag m_tag : 5; bool m_is_enum_or_flags_interface : 1; bool m_is_pointer : 1; public: constexpr explicit ReturnTag(GITypeTag tag) : m_tag(tag), m_is_enum_or_flags_interface(false), m_is_pointer(false) {} constexpr explicit ReturnTag(GITypeTag tag, bool is_enum_or_flags_interface, bool is_pointer) : m_tag(tag), m_is_enum_or_flags_interface(is_enum_or_flags_interface), m_is_pointer(is_pointer) {} explicit ReturnTag(const GI::TypeInfo& type_info) : m_tag(type_info.tag()), m_is_pointer(type_info.is_pointer()) { if (m_tag == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface_info{type_info.interface()}; m_is_enum_or_flags_interface = interface_info.is_enum_or_flags(); } else { m_is_enum_or_flags_interface = false; } } [[nodiscard]] constexpr GITypeTag tag() const { return m_tag; } [[nodiscard]] constexpr bool is_enum_or_flags_interface() const { return m_tag == GI_TYPE_TAG_INTERFACE && m_is_enum_or_flags_interface; } [[nodiscard]] constexpr GType interface_gtype() const { return is_enum_or_flags_interface() ? GI_TYPE_ENUM_INFO : G_TYPE_NONE; } [[nodiscard]] constexpr bool is_pointer() const { return m_is_pointer; } }; } // namespace Arg // When creating an Argument, pass it directly to ArgsCache::set_argument() or // one of the similar methods, which will call init_common() on it and store it // in the appropriate place in the arguments cache. struct Argument { // Convenience struct to prevent long argument lists to make() and the // functions that call it struct Init { const char* name; uint8_t index; GITransfer transfer : 2; GjsArgumentFlags flags : 6; }; virtual ~Argument() = default; GJS_JSAPI_RETURN_CONVENTION virtual bool in(JSContext*, GjsFunctionCallState*, GIArgument* in_argument, JS::HandleValue); GJS_JSAPI_RETURN_CONVENTION virtual bool out(JSContext*, GjsFunctionCallState*, GIArgument* out_argument, JS::MutableHandleValue); GJS_JSAPI_RETURN_CONVENTION virtual bool release(JSContext*, GjsFunctionCallState*, GIArgument* in_argument, GIArgument* out_argument); [[nodiscard]] 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(); [[nodiscard]] constexpr const char* arg_name() const { return m_arg_name; } [[nodiscard]] constexpr bool skip_in() const { return m_skip_in; } [[nodiscard]] constexpr bool skip_out() const { return m_skip_out; } protected: constexpr Argument() : m_skip_in(false), m_skip_out(false) {} [[nodiscard]] virtual mozilla::Maybe return_tag() const { return {}; } [[nodiscard]] virtual mozilla::Maybe as_instance() const { return {}; } constexpr void set_instance_parameter() { m_arg_name = "instance parameter"; m_skip_out = true; } constexpr void set_return_value() { m_arg_name = "return value"; } static bool invalid(JSContext*, const char* func = nullptr); const char* m_arg_name = nullptr; bool m_skip_in : 1; bool m_skip_out : 1; private: friend struct ArgsCache; template static void init_common(const Init&, T* arg); }; using ArgumentPtr = AutoCppPointer; // 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*, const GI::CallableInfo&); // 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, const GI::ArgInfo&, const GI::CallableInfo&, bool* inc_counter_out); void build_return(const GI::CallableInfo&, bool* inc_counter_out); void build_instance(const GI::CallableInfo&); [[nodiscard]] mozilla::Maybe instance_type() const; [[nodiscard]] mozilla::Maybe return_tag() const; private: void build_normal_in_arg(uint8_t gi_index, const GI::TypeInfo&, const GI::ArgInfo&, GjsArgumentFlags); void build_normal_out_arg(uint8_t gi_index, const GI::TypeInfo&, const GI::ArgInfo&, GjsArgumentFlags); void build_normal_inout_arg(uint8_t gi_index, const GI::TypeInfo&, const GI::ArgInfo&, GjsArgumentFlags); // GITypeInfo is not available for instance parameters (see // https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/334) but // for other parameters, this function additionally takes a GITypeInfo. template void build_interface_in_arg(const Argument::Init&, const GI::BaseInfo& interface_info); template constexpr void set_argument(T* arg, const Argument::Init&); void set_array_argument(const GI::CallableInfo&, uint8_t gi_index, const GI::TypeInfo&, GIDirection, const GI::ArgInfo&, GjsArgumentFlags, unsigned length_pos); void set_array_return(const GI::CallableInfo&, const GI::TypeInfo&, GjsArgumentFlags, unsigned length_pos); void init_out_array_length_argument(const GI::ArgInfo&, GjsArgumentFlags, unsigned length_pos); template constexpr void set_return(T* arg, GITransfer, GjsArgumentFlags); template constexpr void set_instance( T* arg, GITransfer, GjsArgumentFlags flags = GjsArgumentFlags::NONE); void set_skip_all(uint8_t index, const char* name = nullptr); template [[nodiscard]] 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 [[nodiscard]] constexpr ArgumentPtr& arg_get(uint8_t index = Argument::MAX_ARGS) const { return m_args[arg_index(index)]; } public: [[nodiscard]] constexpr Argument* argument(uint8_t index) const { return arg_get(index).get(); } [[nodiscard]] constexpr mozilla::Maybe instance() const { if (!m_is_method) return {}; return mozilla::Some(arg_get().get()); } [[nodiscard]] constexpr mozilla::Maybe return_value() const { if (!m_has_return) return {}; return mozilla::Some(arg_get().get()); } private: AutoCppPointer m_args; bool m_is_method : 1; bool m_has_return : 1; }; } // namespace Gjs cjs-140.0/gi/arg-inl.h0000664000175000017500000002465615167114161013342 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 memset #include // for nullptr_t #include #include // for to_string #include #include #include // for GType #include // IWYU pragma: no_forward_declare _GBytes // IWYU pragma: no_forward_declare _GHashTable // IWYU pragma: no_forward_declare _GVariant #include // for Handle #include // for HandleValue #include "gi/arg-types-inl.h" #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 decltype(auto) gjs_arg_member(GIArgument* arg) { return (arg->*member); } template [[nodiscard]] constexpr decltype(auto) gjs_arg_member(GIArgument* arg) { if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_boolean>(arg); if constexpr (std::is_same_v) { // GType is defined differently on 32-bit vs. 64-bit architectures. // Don't replace gsize and gulong with size_t and unsigned long, since // GLib may define them differently on some platforms. 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 (std::is_same_v) return gjs_arg_member<&GIArgument::v_long>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_ulong>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_int>(arg); if constexpr (std::is_same_v) return gjs_arg_member<&GIArgument::v_uint>(arg); 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>>; return reinterpret_cast( gjs_arg_member<&GIArgument::v_pointer>(arg)); } } template >>> constexpr void gjs_arg_set(GIArgument* arg, Gjs::Tag::RealT v) { if constexpr (std::is_same_v || std::is_same_v) v = !!v; gjs_arg_member(arg) = v; } // Specialization for types where TAG and RealT are the same type, to allow // inferring template parameter template , T> && std::is_arithmetic_v>> constexpr void gjs_arg_set(GIArgument* arg, T v) { gjs_arg_set(arg, v); } // Specialization for non-function pointers, so that you don't have to repeat // the pointer type explicitly for type deduction, and that takes care of // GIArgument not having constness template >> constexpr void gjs_arg_set(GIArgument* arg, T* v) { using NonconstPtrT = std::add_pointer_t>; gjs_arg_member(arg) = const_cast(v); } // Overload for nullptr since it's not handled by TAG* constexpr void gjs_arg_set(GIArgument* arg, std::nullptr_t) { gjs_arg_member(arg) = nullptr; } // Store function pointers as void*. It is a requirement of GLib that your // compiler can do this template constexpr void gjs_arg_set(GIArgument* arg, ReturnT (*v)(Args...)) { gjs_arg_member(arg) = reinterpret_cast(v); } // Specifying an integer-type tag and passing a void pointer, extracts a stuffed // integer out of the pointer; otherwise just store the pointer in v_pointer template constexpr void gjs_arg_set(GIArgument* arg, void* v) { using T = Gjs::Tag::RealT; if constexpr (std::is_integral_v) gjs_arg_set(arg, gjs_pointer_to_int(v)); else gjs_arg_member(arg) = v; } template [[nodiscard]] constexpr Gjs::Tag::RealT gjs_arg_get(GIArgument* arg) { if constexpr (std::is_same_v || std::is_same_v) return Gjs::Tag::RealT(!!gjs_arg_member(arg)); return gjs_arg_member(arg); } template [[nodiscard]] constexpr void* gjs_arg_get_as_pointer(GIArgument* arg) { return gjs_int_to_pointer(gjs_arg_get(arg)); } constexpr void gjs_arg_unset(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_set(arg, 0) for some type T. memset(arg, 0, sizeof(GIArgument)); } template [[nodiscard]] constexpr Gjs::Tag::RealT 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]] 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) { using BigT = Gjs::Tag::RealT; 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, 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::Tag::JSValuePackT val{}; using T = Gjs::Tag::RealT; using HolderTag = Gjs::Tag::JSValuePackTag; 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(); } } namespace Gjs { [[nodiscard]] static inline bool basic_type_needs_release(GITypeTag tag) { g_assert(GI_TYPE_TAG_IS_BASIC(tag)); return tag == GI_TYPE_TAG_FILENAME || tag == GI_TYPE_TAG_UTF8; } } // namespace Gjs cjs-140.0/gi/arg-types-inl.h0000664000175000017500000002102515167114161014467 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 #include #include // for GValue #include // for gboolean 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; }; template constexpr bool comparable_types() { return std::is_arithmetic_v == std::is_arithmetic_v && std::is_integral_v == std::is_integral_v && std::is_signed_v == std::is_signed_v; } template constexpr bool type_fits() { if constexpr (comparable_types()) { return (std::numeric_limits::max() <= std::numeric_limits::max() && std::numeric_limits::lowest() >= std::numeric_limits::lowest()); } return false; } // These tags are used to disambiguate types such as gboolean and GType which // are in fact typedefs of other generic types. Using the tag instead of the // type directly allows performing proper specialization. See also arg-inl.h. namespace Tag { struct GBoolean {}; struct GType {}; struct Long {}; struct UnsignedLong {}; struct Enum {}; struct UnsignedEnum {}; } // namespace Tag template struct MarshallingInfo {}; template <> struct MarshallingInfo { using real_type = bool; using containing_tag = bool; using jsvalue_pack_type = int32_t; static constexpr const char* name = "bool"; }; template <> struct MarshallingInfo { using real_type = int8_t; using containing_tag = int32_t; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT8; static constexpr const char* name = "int8"; }; template <> struct MarshallingInfo { using real_type = uint8_t; using containing_tag = uint32_t; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT8; static constexpr const char* name = "uint8"; }; template <> struct MarshallingInfo { using real_type = int16_t; using containing_tag = int32_t; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT16; static constexpr const char* name = "int16"; }; template <> struct MarshallingInfo { using real_type = uint16_t; using containing_tag = uint32_t; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT16; static constexpr const char* name = "uint16"; }; template <> struct MarshallingInfo { using real_type = int32_t; using containing_tag = int32_t; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT32; static constexpr const char* name = "int32"; }; template <> struct MarshallingInfo { using real_type = uint32_t; using containing_tag = uint32_t; using jsvalue_pack_type = double; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT32; static constexpr const char* name = "uint32"; }; template <> struct MarshallingInfo { using real_type = char32_t; using containing_tag = char32_t; using jsvalue_pack_type = double; static constexpr const char* name = "char32"; }; template <> struct MarshallingInfo { using real_type = int64_t; using containing_tag = int64_t; using jsvalue_pack_type = TypeWrapper; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_INT64; static constexpr const char* name = "int64"; }; template <> struct MarshallingInfo { using real_type = uint64_t; using containing_tag = uint64_t; using jsvalue_pack_type = TypeWrapper; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_UINT64; static constexpr const char* name = "uint64"; }; template <> struct MarshallingInfo { using real_type = float; using containing_tag = double; using jsvalue_pack_type = double; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_FLOAT; static constexpr const char* name = "float"; }; template <> struct MarshallingInfo { using real_type = double; using containing_tag = double; using jsvalue_pack_type = double; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_DOUBLE; static constexpr const char* name = "double"; }; template struct MarshallingInfo { using real_type = T*; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_VOID; static constexpr const char* name = "pointer"; }; template <> struct MarshallingInfo { using real_type = GType; using containing_tag = Tag::GType; using jsvalue_pack_type = TypeWrapper; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_GTYPE; static constexpr const char* name = "GType"; }; template <> struct MarshallingInfo { using real_type = gboolean; using containing_tag = Tag::GBoolean; using jsvalue_pack_type = int32_t; static constexpr GITypeTag gi_tag = GI_TYPE_TAG_BOOLEAN; static constexpr const char* name = "boolean"; }; template <> struct MarshallingInfo { using real_type = GValue*; static constexpr const char* name = "GValue"; }; template <> struct MarshallingInfo { using real_type = GValue; using containing_tag = GValue; static constexpr const char* name = "flat GValue"; }; template <> struct MarshallingInfo { using real_type = char*; using containing_tag = char*; using jsvalue_pack_type = char*; static constexpr const char* name = "string"; }; template <> struct MarshallingInfo { using real_type = const char*; static constexpr const char* name = "constant string"; }; template <> struct MarshallingInfo { static constexpr bool is_32 = type_fits(); using real_type = long; using containing_tag = std::conditional_t; using jsvalue_pack_type = std::conditional_t>; static constexpr const char* name = "long"; }; template <> struct MarshallingInfo { static constexpr bool is_32 = type_fits(); using real_type = unsigned long; using containing_tag = std::conditional_t; using jsvalue_pack_type = std::conditional_t>; static constexpr const char* name = "unsigned long"; }; template <> struct MarshallingInfo { using real_type = int; using containing_tag = int32_t; using jsvalue_pack_type = int32_t; }; template <> struct MarshallingInfo { using real_type = unsigned; using containing_tag = uint32_t; using jsvalue_pack_type = double; }; template <> struct MarshallingInfo { using real_type = void; }; namespace Tag { template using RealT = typename MarshallingInfo::real_type; // There are two ways you can unpack a C value from a JSValue. // The containing type is the most appropriate C type that can contain the // unpacked value. Implicit conversion may be performed and the value may need // to be checked to make sure it is in range. // The JSValue pack type, on the other hand, is the C type that is exactly // equivalent to how JSValue stores the value, so no implicit conversion is // performed unless the JSValue contains a pointer to a GC-thing, like BigInt. template using JSValueContainingT = RealT::containing_tag>; template using JSValueContainingTag = typename MarshallingInfo::containing_tag; template using JSValuePackT = typename MarshallingInfo::jsvalue_pack_type; template using JSValuePackTag = std::conditional_t< std::is_same_v, TypeWrapper>, int64_t, std::conditional_t, TypeWrapper>, uint64_t, JSValuePackT>>; } // namespace Tag template constexpr const char* static_type_name() { return MarshallingInfo::name; } } // namespace Gjs cjs-140.0/gi/arg.cpp0000664000175000017500000050127015167114161013105 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 // for none_of #include // for mem_fn #include #include // for move #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 #include #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/info.h" #include "gi/interface.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/struct.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/byteArray.h" #include "cjs/context-private.h" #include "cjs/enum-utils.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" using mozilla::Maybe, mozilla::Nothing, mozilla::Some; // This file contains marshalling functions for GIArgument. It is divided into // three sections, with some helper functions coming first. // 1. "To" marshallers - JS::Value or other JS data structure to GIArgument or // pointer, used for in arguments // 2. "From" marshallers - GIArgument or pointer to JS::Value or other JS data // structure, used for out arguments and return values // 3. "Release" marshallers - used when cleaning up GIArguments after a C // function call GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal(JSContext*, GITransfer, const GI::TypeInfo&, GITypeTag, GjsArgumentType, GjsArgumentFlags, GIArgument*); static void throw_invalid_argument(JSContext*, JS::HandleValue, const GI::TypeInfo&, const char*, GjsArgumentType); bool gjs_flags_value_is_valid(JSContext* cx, GType gtype, int64_t value) { // Do proper value check for flags with GTypes if (gtype != G_TYPE_NONE) { Gjs::AutoTypeClass gflags_class{gtype}; // check all bits are valid bits for the flag and is a 32 bit flag if ((static_cast(value) & gflags_class->mask) != value) { // Not a uint32_t 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, const GI::EnumInfo& info, int64_t value) { GI::EnumInfo::ValuesIterator values = info.values(); if (std::none_of(values.begin(), values.end(), [value](const GI::AutoValueInfo& info) { return info.value() == value; })) { gjs_throw(cx, "%" PRId64 " is not a valid value for enumeration %s", value, info.name()); return false; } return true; } /* Check if an argument of the given type 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(const GI::TypeInfo& type_info, GITypeTag tag) { switch (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: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (auto reg_info = interface_info.as()) { GType gtype = reg_info->gtype(); if (g_type_is_a(gtype, G_TYPE_CLOSURE) || g_type_is_a(gtype, G_TYPE_VALUE)) return true; } 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 type needs to be released if we obtained it * from an out argument (or the return value), and we're transferring ownership. */ [[nodiscard]] static bool type_needs_out_release(const GI::TypeInfo& type_info, GITypeTag tag) { switch (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: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (interface_info.is_object()) return true; if (interface_info.is_struct() || interface_info.is_union()) return type_info.is_pointer(); return false; } default: return false; } } ///// "TO" MARSHALLERS ///////////////////////////////////////////////////////// // These marshaller functions are responsible for converting JS values to the // required GIArgument type, for the in parameters of a C function call. template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_g_list(JSContext* cx, JS::HandleValue value, const GI::TypeInfo& 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; } GI::AutoTypeInfo element_type{type_info.element_type()}; GITypeTag element_tag = element_type.tag(); g_assert(!GI_TYPE_TAG_IS_BASIC(element_tag) && "use basic_array_to_linked_list() instead"); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release(element_type, element_tag)) { /* 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, element_type, GJS_ARGUMENT_LIST_ELEMENT, transfer, &elem_arg)) { return false; } void* hash_pointer = element_type.hash_pointer_from_argument(&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(nullptr, nullptr); } template GJS_JSAPI_RETURN_CONVENTION static bool hashtable_int_key(JSContext* cx, JS::HandleValue value, void** pointer_out) { using IntType = Gjs::Tag::RealT; static_assert(std::is_integral_v, "Need an integer"); bool out_of_range = false; Gjs::Tag::JSValueContainingT 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) { 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", gi_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)); return true; case GI_TYPE_TAG_UNICHAR: if (value.isInt32()) { *pointer_out = gjs_int_to_pointer(value.toInt32()); return true; } uint32_t ch; if (!gjs_unichar_from_string(cx, value, &ch)) return false; *pointer_out = gjs_int_to_pointer(ch); return true; case GI_TYPE_TAG_INT8: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_INT16: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_INT32: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_UINT8: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_UINT16: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_UINT32: return hashtable_int_key(cx, value, pointer_out); case GI_TYPE_TAG_FILENAME: { Gjs::AutoChar 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(); return true; } 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()); return true; } 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, // as well as refactoring this function to take GITypeInfo* and // splitting out the marshalling for basic types into a different // function. 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: break; default: g_assert_not_reached(); break; } gjs_throw(cx, "Type %s not supported for hash table keys", gi_type_tag_to_string(type_tag)); return false; } template [[nodiscard]] static Gjs::Tag::RealT* heap_value_new_from_arg(GIArgument* val_arg) { auto* heap_val = g_new(Gjs::Tag::RealT, 1); *heap_val = gjs_arg_get(val_arg); return heap_val; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_object_to_g_hash(JSContext* cx, JS::HandleObject props, const GI::TypeInfo& type_info, GITransfer transfer, GHashTable** hash_p) { g_assert(props && "Property bag cannot be null"); GI::AutoTypeInfo key_type{type_info.key_type()}; GI::AutoTypeInfo value_type{type_info.value_type()}; GITypeTag key_tag = key_type.tag(); GITypeTag val_type = value_type.tag(); g_assert( (!GI_TYPE_TAG_IS_BASIC(key_tag) || !GI_TYPE_TAG_IS_BASIC(val_type)) && "use gjs_value_to_basic_ghash_gi_argument() instead"); if (transfer == GI_TRANSFER_CONTAINER) { if (type_needs_release(key_type, key_tag) || type_needs_release(value_type, val_type)) { /* 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::Rooted ids{cx, cx}; if (!JS_Enumerate(cx, props, &ids)) return false; Gjs::AutoPointer result{ create_hash_table_for_key_type(key_tag)}; JS::RootedValue key_js{cx}, val_js{cx}; JS::RootedId cur_id{cx}; for (size_t id_ix = 0, id_len = ids.length(); id_ix < id_len; ++id_ix) { cur_id = ids[id_ix]; void* key_ptr; GIArgument val_arg = { 0 }; if (!JS_IdToValue(cx, cur_id, &key_js) || // Type check key type. !value_to_ghashtable_key(cx, key_js, key_tag, &key_ptr) || !JS_GetPropertyById(cx, props, cur_id, &val_js) || // Type check and convert value to a C type !gjs_value_to_gi_argument(cx, val_js, value_type, GJS_ARGUMENT_HASH_ELEMENT, transfer, &val_arg, GjsArgumentFlags::MAY_BE_NULL)) return false; // Use heap-allocated values for types that don't fit in a pointer void* val_ptr; 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 = value_type.hash_pointer_from_argument(&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, JS::HandleValue value, Gjs::Tag::RealT* out) { if constexpr (Gjs::type_has_js_getter()) return Gjs::js_value_to_c(cx, value, out); Gjs::Tag::JSValueContainingT 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) { using RealT = Gjs::Tag::RealT; JS::RootedObject array(cx, array_value.toObjectOrNull()); JS::RootedValue elem(cx); // Add one so we're always zero terminated Gjs::SmartPointer 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 %zu", 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_to_strv(JSContext* cx, JS::Value array_value, unsigned length, void** arr_p) { return gjs_array_to_auto_array(cx, array_value, length, arr_p); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_string_to_intarray(JSContext* cx, JS::HandleString str, GITypeTag element_type, void** arr_p, size_t* length) { switch (element_type) { case GI_TYPE_TAG_INT8: case GI_TYPE_TAG_UINT8: { JS::UniqueChars result; if (!gjs_string_to_utf8_n(cx, str, &result, length)) return false; *arr_p = Gjs::js_chars_to_glib(std::move(result)).release(); return true; } case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: { char16_t* result16; if (!gjs_string_get_char16_data(cx, str, &result16, length)) return false; *arr_p = result16; return true; } case GI_TYPE_TAG_UNICHAR: { gunichar* result_ucs4; if (!gjs_string_to_ucs4(cx, str, &result_ucs4, length)) return false; *arr_p = result_ucs4; return true; } default: // can't convert a string to this type gjs_throw(cx, "Cannot convert string to array of '%s'", gi_type_tag_to_string(element_type)); return false; } } GJS_JSAPI_RETURN_CONVENTION static bool array_to_basic_c_array(JSContext* cx, JS::HandleValue v_array, size_t length, GITypeTag element_tag, void** array_out) { // Always one extra element, to cater for null terminated arrays Gjs::AutoPointer array{array_allocate(length + 1)}; JS::RootedObject array_obj{cx, &v_array.toObject()}; JS::RootedValue elem{cx}; for (size_t ix = 0; ix < length; ix++) { GIArgument arg; gjs_arg_unset(&arg); elem.setUndefined(); if (!JS_GetElement(cx, array_obj, ix, &elem)) { gjs_throw(cx, "Missing array element %zu", ix); return false; } if (!gjs_value_to_basic_gi_argument(cx, elem, element_tag, &arg, nullptr, GJS_ARGUMENT_ARRAY_ELEMENT, GjsArgumentFlags::NONE)) { gjs_throw(cx, "Invalid element in array"); return false; } array[ix] = gjs_arg_get(&arg); } *array_out = array.release(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_ptrarray(JSContext* cx, JS::Value array_value, unsigned length, GITransfer transfer, const GI::TypeInfo& param_info, void** arr_p) { JS::RootedObject array_obj{cx, array_value.toObjectOrNull()}; JS::RootedValue elem{cx}; // Always one extra element, to cater for null terminated arrays Gjs::AutoPointer array{array_allocate(length + 1)}; for (unsigned i = 0; i < length; i++) { GIArgument arg; gjs_arg_unset(&arg); elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array_obj, i, &elem)) { gjs_throw(cx, "Missing array element %u", i); return false; } if (!gjs_value_to_gi_argument(cx, elem, param_info, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) { gjs_throw(cx, "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, const GI::TypeInfo& param_info, size_t param_size, void** arr_p) { g_assert((param_size > 0) && "Only flat arrays of elements of known size are supported"); Gjs::AutoPointer 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; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_basic_array(JSContext* cx, JS::HandleValue v_array, size_t length, GITypeTag element_storage_type, void** array_out) { g_assert(GI_TYPE_TAG_IS_BASIC(element_storage_type)); switch (element_storage_type) { case GI_TYPE_TAG_UTF8: return gjs_array_to_strv(cx, v_array, length, array_out); case GI_TYPE_TAG_FILENAME: return array_to_basic_c_array(cx, v_array, length, element_storage_type, array_out); case GI_TYPE_TAG_BOOLEAN: return gjs_array_to_auto_array( cx, v_array, length, array_out); case GI_TYPE_TAG_UNICHAR: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_UINT8: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_INT8: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_UINT16: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_INT16: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_UINT32: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_INT32: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_INT64: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_UINT64: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_FLOAT: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_DOUBLE: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_GTYPE: return gjs_array_to_auto_array(cx, v_array, length, array_out); case GI_TYPE_TAG_VOID: gjs_throw(cx, "Unhandled array element type %d", element_storage_type); return false; default: g_assert_not_reached(); } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_to_array(JSContext* cx, JS::HandleValue array_value, size_t length, GITransfer transfer, const GI::TypeInfo& param_info, void** arr_p) { GITypeTag element_type = param_info.storage_type(); if (GI_TYPE_TAG_IS_BASIC(element_type)) { return gjs_array_to_basic_array(cx, array_value, length, element_type, arr_p); } switch (element_type) { case GI_TYPE_TAG_INTERFACE: if (!param_info.is_pointer()) { GI::AutoBaseInfo interface_info{param_info.interface()}; if (auto reg_info = interface_info.as(); reg_info && reg_info->is_g_value()) { // 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(cx, array_value, length, arr_p); } size_t element_size = gjs_type_get_element_size(param_info.tag(), param_info); if (element_size) { return gjs_array_to_flat_array(cx, 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: return gjs_array_to_ptrarray(cx, array_value, length, transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer, param_info, arr_p); default: // Basic types already handled in gjs_array_to_basic_array() gjs_throw(cx, "Unhandled array element type %d", element_type); return false; } } static inline size_t basic_type_element_size(GITypeTag element_tag) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); switch (element_tag) { 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_UTF8: case GI_TYPE_TAG_FILENAME: return sizeof(char*); default: g_return_val_if_reached(0); } } static inline void set_arg_from_carray_element(GIArgument* arg, GITypeTag element_tag, void* value) { switch (element_tag) { case GI_TYPE_TAG_BOOLEAN: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_INT8: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_UINT8: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_INT16: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_UINT16: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_INT32: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_UINT32: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_INT64: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_UINT64: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_FLOAT: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_DOUBLE: gjs_arg_set(arg, *static_cast(value)); break; case GI_TYPE_TAG_INTERFACE: gjs_arg_set(arg, value); break; case GI_TYPE_TAG_VOID: 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: case GI_TYPE_TAG_UNICHAR: // non interface tag support handled elsewhere g_assert_not_reached(); } } size_t gjs_type_get_element_size(GITypeTag element_tag, const GI::TypeInfo& type_info) { if (type_info.is_pointer() && element_tag != GI_TYPE_TAG_ARRAY) return sizeof(void*); switch (element_tag) { 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_INTERFACE: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (interface_info.is_enum_or_flags()) return sizeof(unsigned); if (auto struct_info = interface_info.as()) return struct_info->size(); if (auto union_info = interface_info.as()) return union_info->size(); return 0; } case GI_TYPE_TAG_GHASH: return sizeof(void*); case GI_TYPE_TAG_ARRAY: if (type_info.array_type() == GI_ARRAY_TYPE_C) { if (!type_info.array_length_index()) return sizeof(void*); GI::AutoTypeInfo element_type{type_info.element_type()}; return gjs_type_get_element_size(element_type.tag(), element_type); } return sizeof(void*); case GI_TYPE_TAG_VOID: break; default: return basic_type_element_size(element_tag); } g_return_val_if_reached(0); } static GArray* garray_new_for_storage_type(unsigned length, GITypeTag storage_type, const GI::TypeInfo& 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); } static GArray* garray_new_for_basic_type(unsigned length, GITypeTag tag) { size_t element_size = basic_type_element_size(tag); 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(); } } static void throw_invalid_argument(JSContext* cx, JS::HandleValue value, const GI::TypeInfo& arginfo, const char* arg_name, GjsArgumentType arg_type) { Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "Expected type %s for %s but got type '%s'", arginfo.display_string(), display_name.get(), JS::InformalValueTypeName(value)); } GJS_JSAPI_RETURN_CONVENTION static bool throw_invalid_argument_tag(JSContext* cx, JS::HandleValue value, GITypeTag type_tag, const char* arg_name, GjsArgumentType arg_type) { Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "Expected type %s for %s but got type '%s'", gi_type_tag_to_string(type_tag), display_name.get(), JS::InformalValueTypeName(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool throw_invalid_interface_argument(JSContext* cx, JS::HandleValue value, const GI::BaseInfo& interface_info, const char* arg_name, GjsArgumentType arg_type) { Gjs::AutoChar display_name{gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "Expected type %s for %s but got type '%s'", interface_info.type_string(), display_name.get(), JS::InformalValueTypeName(value)); return false; } bool gjs_array_to_basic_explicit_array( JSContext* cx, JS::HandleValue value, GITypeTag element_tag, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags, void** contents_out, size_t* length_out) { g_assert(contents_out && length_out && "forgot out parameter"); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to C array", arg_name, gjs_debug_value(value).c_str()); if ((value.isNull() && !(flags & GjsArgumentFlags::MAY_BE_NULL)) || (!value.isString() && !value.isObjectOrNull())) { return throw_invalid_argument_tag(cx, value, element_tag, arg_name, arg_type); } if (value.isNull()) { *contents_out = nullptr; *length_out = 0; return true; } if (value.isString()) { // Allow strings as int8/uint8/int16/uint16 arrays JS::RootedString str{cx, value.toString()}; return gjs_string_to_intarray(cx, str, element_tag, contents_out, length_out); } JS::RootedObject array_obj{cx, &value.toObject()}; if (JS_IsUint8Array(array_obj) && (element_tag == GI_TYPE_TAG_INT8 || element_tag == GI_TYPE_TAG_UINT8)) { GBytes* bytes = gjs_byte_array_get_bytes(array_obj); *contents_out = g_bytes_unref_to_data(bytes, length_out); return true; } const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); bool found_length; if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) return false; if (found_length) { uint32_t length; if (!gjs_object_require_converted_property(cx, array_obj, nullptr, atoms.length(), &length)) { return false; } // For basic types, type tag == storage type if (!gjs_array_to_basic_array(cx, value, length, element_tag, contents_out)) return false; *length_out = length; return true; } return throw_invalid_argument_tag(cx, value, element_tag, arg_name, arg_type); } bool gjs_array_to_explicit_array(JSContext* cx, JS::HandleValue value, const GI::TypeInfo& type_info, const char* arg_name, GjsArgumentType arg_type, GITransfer transfer, GjsArgumentFlags flags, void** contents, size_t* length_p) { GI::AutoTypeInfo element_type{type_info.element_type()}; GITypeTag element_tag = element_type.tag(); if (GI_TYPE_TAG_IS_BASIC(element_tag)) { return gjs_array_to_basic_explicit_array(cx, value, element_tag, arg_name, arg_type, flags, contents, length_p); } 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); if ((value.isNull() && !(flags & GjsArgumentFlags::MAY_BE_NULL)) || (!value.isString() && !value.isObjectOrNull())) { throw_invalid_argument(cx, value, element_type, arg_name, arg_type); return false; } if (value.isNull()) { *contents = nullptr; *length_p = 0; return true; } if (value.isString()) { // Allow strings as int8/uint8/int16/uint16 arrays JS::RootedString str{cx, value.toString()}; return gjs_string_to_intarray(cx, str, element_tag, contents, length_p); } JS::RootedObject array_obj{cx, &value.toObject()}; if (JS_IsUint8Array(array_obj) && (element_tag == GI_TYPE_TAG_INT8 || element_tag == 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(cx); bool found_length; if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) return false; if (found_length) { uint32_t length; if (!gjs_object_require_converted_property(cx, array_obj, nullptr, atoms.length(), &length) || !gjs_array_to_array(cx, value, length, transfer, element_type, contents)) return false; *length_p = length; return true; } throw_invalid_argument(cx, value, element_type, arg_name, arg_type); return false; } static void intern_gdk_atom(const char* name, GIArgument* ret) { GI::Repository repo; GI::AutoFunctionInfo atom_intern_fun{ repo.find_by_name("Gdk", "atom_intern").value()}; std::vector atom_intern_args{2}; gjs_arg_set(atom_intern_args.data(), name); gjs_arg_set(atom_intern_args.data() + 1, false); mozilla::Unused << atom_intern_fun.invoke(atom_intern_args, {}, ret); } static bool value_to_gdk_atom_gi_argument_internal(JSContext* cx, JS::HandleValue value, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type) { if (!value.isNull() && !value.isString()) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "Expected type String or null for %s but got type '%s'", display_name.get(), JS::InformalValueTypeName(value)); return false; } 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; } GJS_JSAPI_RETURN_CONVENTION bool value_to_interface_gi_argument_internal( JSContext* cx, JS::HandleValue value, const GI::BaseInfo& interface_info, GITransfer transfer, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { auto reg_type = interface_info.as(); if (reg_type && reg_type->is_gdk_atom()) { return value_to_gdk_atom_gi_argument_internal(cx, value, arg, arg_name, arg_type); } GType gtype = reg_type.map(std::mem_fn(&GI::RegisteredTypeInfo::gtype)) .valueOr(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) { return gjs_value_to_g_value_no_copy(cx, value, gjs_arg_get(arg)); } 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; } if (interface_info.is_enum_or_flags() == value.isObjectOrNull()) { // for enum/flags, object/null are invalid. for everything else, // everything except object/null is invalid. return throw_invalid_interface_argument(cx, value, interface_info, arg_name, arg_type); } if (value.isNull()) { gjs_arg_set(arg, nullptr); return true; } if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); auto struct_info = interface_info.as(); if (struct_info && struct_info->is_gtype_struct()) { GType actual_gtype; if (!gjs_gtype_get_actual_gtype(cx, obj, &actual_gtype)) return false; if (actual_gtype == G_TYPE_NONE) { return throw_invalid_interface_argument( cx, value, interface_info, arg_name, arg_type); } // 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 (struct_info && gtype == G_TYPE_NONE && !struct_info->is_foreign()) { 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 pointer 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 (struct_info && !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)) { if (!StructBase::typecheck(cx, obj, interface_info)) { gjs_arg_unset(arg); return false; } return StructBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } } if (interface_info.is_union()) { if (!UnionBase::typecheck(cx, obj, interface_info)) { gjs_arg_unset(arg); return false; } return UnionBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } 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); } 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; } if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { if (StructBase::typecheck(cx, obj, interface_info, GjsTypecheckNoThrow{}) && StructBase::typecheck(cx, obj, gtype, GjsTypecheckNoThrow{})) { return StructBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } 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 %s", g_type_name(gtype), interface_info.type_string()); return false; } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { return FundamentalBase::transfer_to_gi_argument( cx, obj, arg, GI_DIRECTION_IN, transfer, gtype); } 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, 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(), interface_info.name()); gjs_throw(cx, "Unexpected unregistered type unpacking GIArgument from " "Object"); return false; } if (value.isNumber()) { if (auto enum_info = interface_info.as()) { int64_t value_int64; if (!JS::ToInt64(cx, value, &value_int64)) return false; if (interface_info.is_flags()) { if (!gjs_flags_value_is_valid(cx, gtype, value_int64)) return false; } else { if (!gjs_enum_value_is_valid(cx, enum_info.value(), value_int64)) return false; } gjs_arg_set(arg, enum_info->enum_to_int(value_int64)); return true; } 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)); return throw_invalid_interface_argument(cx, value, interface_info, arg_name, arg_type); } template GJS_JSAPI_RETURN_CONVENTION inline static bool gjs_arg_set_from_js_value(JSContext* cx, 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) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "value %s is out of range for %s (type %s)", gjs_debug_value(value).c_str(), display_name.get(), Gjs::static_type_name()); } return false; } gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "%s set to value %s (type %s)", Gjs::AutoChar{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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, arg_type)}; gjs_throw(cx, "%s (type %s) may not be null", display_name.get(), gi_type_tag_to_string(type_tag)); return false; } return true; } bool gjs_value_to_basic_gi_argument(JSContext* cx, JS::HandleValue value, GITypeTag type_tag, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { g_assert(GI_TYPE_TAG_IS_BASIC(type_tag) && "use gjs_value_to_gi_argument() for non-basic types"); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, gjs_debug_value(value).c_str(), gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: // don't know how to handle non-pointer VOID return throw_invalid_argument_tag(cx, value, type_tag, arg_name, arg_type); case GI_TYPE_TAG_INT8: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT8: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT16: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT16: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT32: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT32: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_INT64: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UINT64: return gjs_arg_set_from_js_value(cx, 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(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_DOUBLE: return gjs_arg_set_from_js_value(cx, value, arg, arg_name, arg_type); case GI_TYPE_TAG_UNICHAR: if (value.isString()) { return gjs_unichar_from_string(cx, value, &gjs_arg_member(arg)); } return throw_invalid_argument_tag(cx, value, type_tag, arg_name, arg_type); case GI_TYPE_TAG_GTYPE: if (value.isObjectOrNull()) { GType gtype; JS::RootedObject obj{cx, value.toObjectOrNull()}; if (!gjs_gtype_get_actual_gtype(cx, obj, >ype)) return false; if (gtype == G_TYPE_INVALID) return false; gjs_arg_set(arg, gtype); return true; } return throw_invalid_argument_tag(cx, value, type_tag, arg_name, arg_type); case GI_TYPE_TAG_FILENAME: if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isString()) { Gjs::AutoChar filename_str; if (!gjs_string_to_filename(cx, value, &filename_str)) return false; gjs_arg_set(arg, filename_str.release()); } else { return throw_invalid_argument_tag(cx, value, type_tag, arg_name, arg_type); } return check_nullable_argument(cx, 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{cx, value.toString()}; JS::UniqueChars utf8_str{JS_EncodeStringToUTF8(cx, str)}; if (!utf8_str) return false; gjs_arg_set( arg, Gjs::js_chars_to_glib(std::move(utf8_str)).release()); } else { return throw_invalid_argument_tag(cx, value, type_tag, arg_name, arg_type); } return check_nullable_argument(cx, arg_name, arg_type, type_tag, flags, arg); default: g_return_val_if_reached(false); // non-basic type } } bool gjs_value_to_gerror_gi_argument(JSContext* cx, JS::HandleValue value, GITransfer transfer, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type error", arg_name, gjs_debug_value(value).c_str()); if (value.isNull()) { gjs_arg_set(arg, nullptr); } else if (value.isObject()) { JS::RootedObject obj(cx, &value.toObject()); if (!ErrorBase::transfer_to_gi_argument(cx, obj, arg, GI_DIRECTION_IN, transfer)) return false; } else { return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_ERROR, arg_name, arg_type); } return check_nullable_argument(cx, arg_name, arg_type, GI_TYPE_TAG_ERROR, flags, arg); } bool gjs_value_to_gdk_atom_gi_argument(JSContext* cx, JS::HandleValue value, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type) { gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type interface", arg_name, gjs_debug_value(value).c_str()); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "gtype of INTERFACE is GdkAtom"); return value_to_gdk_atom_gi_argument_internal(cx, value, arg, arg_name, arg_type); } // Convert a JS value to GIArgument, specifically for arguments with type tag // GI_TYPE_TAG_INTERFACE. bool gjs_value_to_interface_gi_argument(JSContext* cx, JS::HandleValue value, const GI::BaseInfo& interface_info, GITransfer transfer, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { if (auto reg_info = interface_info.as(); reg_info && reg_info->is_gdk_atom()) { return gjs_value_to_gdk_atom_gi_argument(cx, value, arg, arg_name, arg_type); } gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type interface", arg_name, gjs_debug_value(value).c_str()); if (auto struct_info = interface_info.as(); struct_info && struct_info->is_foreign()) { return gjs_struct_foreign_convert_to_gi_argument( cx, value, *struct_info, arg_name, arg_type, transfer, flags, arg); } if (!value_to_interface_gi_argument_internal(cx, value, interface_info, transfer, arg, arg_name, arg_type, flags)) return false; if (interface_info.is_enum_or_flags()) return true; return check_nullable_argument(cx, arg_name, arg_type, GI_TYPE_TAG_INTERFACE, flags, arg); } template GJS_JSAPI_RETURN_CONVENTION static bool basic_array_to_linked_list(JSContext* cx, JS::HandleValue value, GITypeTag element_tag, const char* arg_name, GjsArgumentType arg_type, T** list_p) { static_assert(std::is_same_v || std::is_same_v); g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && "use gjs_array_to_g_list() for lists containing non-basic types"); constexpr GITypeTag list_tag = std::is_same_v ? GI_TYPE_TAG_GLIST : GI_TYPE_TAG_GSLIST; // While a list can be NULL in C, that means empty array in JavaScript, it // doesn't mean null in JavaScript. if (!value.isObject()) return false; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject array_obj(cx, &value.toObject()); bool found_length; if (!JS_HasPropertyById(cx, array_obj, atoms.length(), &found_length)) return false; if (!found_length) { return throw_invalid_argument_tag(cx, value, list_tag, arg_name, arg_type); } uint32_t length; if (!gjs_object_require_converted_property(cx, array_obj, nullptr, atoms.length(), &length)) { return throw_invalid_argument_tag(cx, value, list_tag, arg_name, arg_type); } JS::RootedObject array{cx, value.toObjectOrNull()}; JS::RootedValue elem{cx}; T* list = nullptr; for (size_t i = 0; i < length; ++i) { GIArgument elem_arg = {0}; elem = JS::UndefinedValue(); if (!JS_GetElement(cx, array, i, &elem)) { gjs_throw(cx, "Missing array element %zu", i); return false; } if (!gjs_value_to_basic_gi_argument(cx, elem, element_tag, &elem_arg, arg_name, GJS_ARGUMENT_LIST_ELEMENT, GjsArgumentFlags::NONE)) { return false; } void* hash_pointer = gi_type_tag_hash_pointer_from_argument(element_tag, &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; } bool gjs_value_to_basic_glist_gi_argument(JSContext* cx, JS::HandleValue value, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && "use gjs_array_to_g_list() for lists containing non-basic types"); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type glist", arg_name, gjs_debug_value(value).c_str()); return basic_array_to_linked_list(cx, value, element_tag, arg_name, arg_type, &gjs_arg_member(arg)); } bool gjs_value_to_basic_gslist_gi_argument(JSContext* cx, JS::HandleValue value, GITypeTag element_tag, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && "use gjs_array_to_g_list() for lists containing non-basic types"); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type gslist", arg_name, gjs_debug_value(value).c_str()); return basic_array_to_linked_list(cx, value, element_tag, arg_name, arg_type, &gjs_arg_member(arg)); } bool gjs_value_to_basic_ghash_gi_argument( JSContext* cx, JS::HandleValue value, GITypeTag key_tag, GITypeTag value_tag, GITransfer transfer, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { g_assert(GI_TYPE_TAG_IS_BASIC(key_tag) && "use gjs_object_to_g_hash() for hashes with non-basic key types"); g_assert( GI_TYPE_TAG_IS_BASIC(value_tag) && "use gjs_object_to_g_hash() for hashes with non-basic value types"); gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type ghash", arg_name, gjs_debug_value(value).c_str()); if (value.isNull()) { if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) { return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_GHASH, arg_name, arg_type); } gjs_arg_set(arg, nullptr); return true; } if (!value.isObject()) { return throw_invalid_argument_tag(cx, value, GI_TYPE_TAG_GHASH, arg_name, arg_type); } if (transfer == GI_TRANSFER_CONTAINER && (Gjs::basic_type_needs_release(key_tag) || Gjs::basic_type_needs_release(value_tag))) { // See comment in gjs_value_to_g_hash() gjs_throw(cx, "Container transfer for in parameters not supported"); return false; } JS::RootedObject props{cx, &value.toObject()}; JS::Rooted ids{cx, cx}; if (!JS_Enumerate(cx, props, &ids)) return false; Gjs::AutoPointer result{ create_hash_table_for_key_type(key_tag)}; JS::RootedValue v_key{cx}, v_val{cx}; JS::RootedId cur_id{cx}; for (size_t id_ix = 0, id_len = ids.length(); id_ix < id_len; ++id_ix) { cur_id = ids[id_ix]; void* key_ptr; void* val_ptr; GIArgument val_arg = {0}; if (!JS_IdToValue(cx, cur_id, &v_key) || // Type check key type. !value_to_ghashtable_key(cx, v_key, key_tag, &key_ptr) || !JS_GetPropertyById(cx, props, cur_id, &v_val) || // Type check and convert value to a C type !gjs_value_to_basic_gi_argument(cx, v_val, value_tag, &val_arg, nullptr, GJS_ARGUMENT_HASH_ELEMENT, GjsArgumentFlags::MAY_BE_NULL)) return false; // Use heap-allocated values for types that don't fit in a pointer if (value_tag == GI_TYPE_TAG_INT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (value_tag == GI_TYPE_TAG_UINT64) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (value_tag == GI_TYPE_TAG_FLOAT) { val_ptr = heap_value_new_from_arg(&val_arg); } else if (value_tag == GI_TYPE_TAG_DOUBLE) { val_ptr = heap_value_new_from_arg(&val_arg); } else { // Other types are simply stuffed inside the pointer val_ptr = gi_type_tag_hash_pointer_from_argument(value_tag, &val_arg); } g_hash_table_insert(result, key_ptr, val_ptr); } gjs_arg_set(arg, result.release()); return true; } bool gjs_value_to_basic_array_gi_argument(JSContext* cx, JS::HandleValue value, GITypeTag element_tag, GIArrayType array_type, GIArgument* arg, const char* arg_name, GjsArgumentType arg_type, GjsArgumentFlags flags) { Gjs::AutoPointer data; size_t length; if (!gjs_array_to_basic_explicit_array(cx, value, element_tag, arg_name, arg_type, flags, data.out(), &length)) { return false; } switch (array_type) { case GI_ARRAY_TYPE_C: gjs_arg_set(arg, data.release()); return true; case GI_ARRAY_TYPE_ARRAY: { GArray* array = garray_new_for_basic_type(length, element_tag); if (data) g_array_append_vals(array, data, length); gjs_arg_set(arg, array); return true; } case GI_ARRAY_TYPE_BYTE_ARRAY: return gjs_value_to_byte_array_gi_argument(cx, value, arg, arg_name, flags); case 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); return true; } } g_assert_not_reached(); } bool gjs_value_to_byte_array_gi_argument(JSContext* cx, JS::HandleValue value, GIArgument* arg, const char* arg_name, GjsArgumentFlags flags) { // 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)) { gjs_arg_set(arg, gjs_byte_array_get_byte_array(bytearray_obj)); return true; } } Gjs::AutoPointer data; size_t length; if (!gjs_array_to_basic_explicit_array(cx, value, GI_TYPE_TAG_UINT8, arg_name, GJS_ARGUMENT_ARGUMENT, flags, data.out(), &length)) { return false; } 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); return true; } bool gjs_value_to_gi_argument(JSContext* cx, JS::HandleValue value, const GI::TypeInfo& type_info, GjsArgumentType arg_type, GITransfer transfer, GIArgument* arg, GjsArgumentFlags flags /* = NONE */, const char* arg_name /* = nullptr */) { GITypeTag type_tag = type_info.tag(); if (type_info.is_basic()) { return gjs_value_to_basic_gi_argument(cx, value, type_tag, arg, arg_name, arg_type, flags); } if (type_tag == GI_TYPE_TAG_ERROR) { return gjs_value_to_gerror_gi_argument(cx, value, transfer, arg, arg_name, arg_type, flags); } if (type_tag == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface_info{type_info.interface()}; return gjs_value_to_interface_gi_argument(cx, value, interface_info, transfer, arg, arg_name, arg_type, flags); } if (type_tag == GI_TYPE_TAG_GLIST || type_tag == GI_TYPE_TAG_GSLIST) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { if (type_tag == GI_TYPE_TAG_GLIST) return gjs_value_to_basic_glist_gi_argument( cx, value, element_type.tag(), arg, arg_name, arg_type); return gjs_value_to_basic_gslist_gi_argument( cx, value, element_type.tag(), arg, arg_name, arg_type); } // else, fall through to generic marshaller } else if (type_tag == GI_TYPE_TAG_GHASH) { GI::AutoTypeInfo key_type{type_info.key_type()}; GI::AutoTypeInfo value_type{type_info.value_type()}; if (key_type.is_basic() && value_type.is_basic()) { return gjs_value_to_basic_ghash_gi_argument( cx, value, key_type.tag(), value_type.tag(), transfer, arg, arg_name, arg_type, flags); } // else, fall through to generic marshaller } else if (type_tag == GI_TYPE_TAG_ARRAY) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { return gjs_value_to_basic_array_gi_argument( cx, value, element_type.tag(), type_info.array_type(), arg, arg_name, arg_type, flags); } // else, fall through to generic marshaller } gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Converting argument '%s' JS value %s to GIArgument type %s", arg_name, gjs_debug_value(value).c_str(), gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: g_assert(type_info.is_pointer() && "non-pointers should be handled by " "gjs_value_to_basic_gi_argument()"); // void pointer; cannot marshal. Pass null to C if argument is nullable. gjs_arg_unset(arg); return check_nullable_argument(cx, arg_name, arg_type, type_tag, flags, arg); case GI_TYPE_TAG_GLIST: return gjs_array_to_g_list(cx, value, type_info, transfer, arg_name, arg_type, &gjs_arg_member(arg)); case GI_TYPE_TAG_GSLIST: return gjs_array_to_g_list(cx, 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(cx, value, type_info, arg_name, arg_type); return false; } return true; } if (!value.isObject()) { throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } GHashTable* ghash; JS::RootedObject props{cx, &value.toObject()}; if (!gjs_object_to_g_hash(cx, props, type_info, transfer, &ghash)) return false; gjs_arg_set(arg, ghash); return true; } case GI_TYPE_TAG_ARRAY: { Gjs::AutoPointer data; size_t length; GIArrayType array_type = type_info.array_type(); if (!gjs_array_to_explicit_array(cx, value, type_info, arg_name, arg_type, transfer, flags, data.out(), &length)) { return false; } GI::AutoTypeInfo element_type{type_info.element_type()}; switch (array_type) { case GI_ARRAY_TYPE_C: gjs_arg_set(arg, data.release()); return true; case GI_ARRAY_TYPE_ARRAY: { GArray* array = garray_new_for_storage_type( length, element_type.storage_type(), element_type); if (data) g_array_append_vals(array, data, length); gjs_arg_set(arg, array); return true; } case 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); return true; } case GI_ARRAY_TYPE_BYTE_ARRAY: // handled in gjs_value_to_basic_array_gi_argument() { } } g_assert_not_reached(); } default: // basic types handled in gjs_value_to_basic_gi_argument(), ERROR // handled in gjs_value_to_gerror_gi_argument(), and INTERFACE handled // in gjs_value_to_interface_gi_argument() g_warning("Unhandled type %s for JavaScript to GIArgument conversion", gi_type_tag_to_string(type_tag)); throw_invalid_argument(cx, value, type_info, arg_name, arg_type); return false; } } bool gjs_value_to_callback_out_arg(JSContext* cx, JS::HandleValue value, const GI::ArgInfo& arg_info, GIArgument* arg) { g_assert((arg_info.direction() == GI_DIRECTION_OUT || arg_info.direction() == GI_DIRECTION_INOUT) && "gjs_value_to_callback_out_arg does not handle in arguments."); GjsArgumentFlags flags = GjsArgumentFlags::NONE; GI::StackTypeInfo type_info; arg_info.load_type(&type_info); // If the argument is optional and we're passed nullptr, // ignore the GJS value. if (arg_info.is_optional() && !arg) return true; // Otherwise, throw an error to prevent a segfault. if (!arg) { gjs_throw(cx, "Return value %s is not optional but was passed NULL", arg_info.name()); return false; } if (arg_info.may_be_null()) flags |= GjsArgumentFlags::MAY_BE_NULL; if (arg_info.caller_allocates()) flags |= GjsArgumentFlags::CALLER_ALLOCATES; return gjs_value_to_gi_argument( cx, value, type_info, (arg_info.is_return_value() ? GJS_ARGUMENT_RETURN_VALUE : GJS_ARGUMENT_ARGUMENT), arg_info.ownership_transfer(), arg, flags, arg_info.name()); } ///// "FROM" MARSHALLERS /////////////////////////////////////////////////////// // These marshaller functions are responsible for converting C values returned // from a C function call, usually stored in a GIArgument, back to JS values. bool gjs_value_from_basic_gi_argument(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag type_tag, GIArgument* arg) { g_assert(GI_TYPE_TAG_IS_BASIC(type_tag) && "use gjs_value_from_gi_argument() for non-basic types"); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument %s to JS::Value", gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: // Pointers are handled in gjs_value_from_gi_argument(), and would // set null instead value_out.setUndefined(); return true; case GI_TYPE_TAG_BOOLEAN: value_out.setBoolean(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_INT32: value_out.setInt32(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_UINT32: value_out.setNumber(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_INT64: value_out.setNumber(gjs_arg_get_maybe_rounded(arg)); return true; case GI_TYPE_TAG_UINT64: value_out.setNumber(gjs_arg_get_maybe_rounded(arg)); return true; case GI_TYPE_TAG_UINT16: value_out.setInt32(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_INT16: value_out.setInt32(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_UINT8: value_out.setInt32(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_INT8: value_out.setInt32(gjs_arg_get(arg)); return true; case GI_TYPE_TAG_FLOAT: value_out.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); return true; case GI_TYPE_TAG_DOUBLE: value_out.setNumber(JS::CanonicalizeNaN(gjs_arg_get(arg))); return true; case GI_TYPE_TAG_GTYPE: { GType gtype = gjs_arg_get(arg); if (gtype == 0) { value_out.setNull(); return true; } JSObject* obj = gjs_gtype_create_gtype_wrapper(cx, gtype); if (!obj) return false; value_out.setObject(*obj); return true; } case GI_TYPE_TAG_UNICHAR: { char32_t value = gjs_arg_get(arg); // Preserve the bidirectional mapping between 0 and "" if (value == 0) { value_out.set(JS_GetEmptyStringValue(cx)); return true; } if (!g_unichar_validate(value)) { gjs_throw(cx, "Invalid unicode codepoint U+%" PRIXLEAST32, value); return false; } char utf8[7]; int bytes = g_unichar_to_utf8(value, utf8); return gjs_string_from_utf8_n(cx, utf8, bytes, value_out); } case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: { const char* str = gjs_arg_get(arg); if (!str) { value_out.setNull(); return true; } if (type_tag == GI_TYPE_TAG_FILENAME) return gjs_string_from_filename(cx, str, -1, value_out); if (!g_utf8_validate(str, -1, nullptr)) { gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "String from C value is invalid UTF-8 and " "cannot be safely stored"); return false; } return gjs_string_from_utf8(cx, str, value_out); } default: // this function handles only basic types g_return_val_if_reached(false); } } bool gjs_array_from_strv(JSContext* cx, JS::MutableHandleValue value_out, const char** strv) { // 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 // value_out.setNull(), but clients would need to always check for both an // empty array and null if that was the case. JS::RootedValueVector elems{cx}; for (size_t i = 0; strv && strv[i]; i++) { if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_string_from_utf8(cx, strv[i], elems[i])) return false; } JSObject* obj = JS::NewArrayObject(cx, elems); if (!obj) return false; value_out.setObject(*obj); return true; } template GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_g_list(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& type_info, GITransfer transfer, T* list) { static_assert(std::is_same_v || std::is_same_v); JS::RootedValueVector elems(cx); GI::AutoTypeInfo element_type{type_info.element_type()}; GIArgument arg; for (size_t i = 0; list; list = list->next, ++i) { element_type.argument_from_hash_pointer(list->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_gi_argument(cx, elems[i], element_type, 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 fill_vector_from_basic_c_array(JSContext* cx, JS::MutableHandleValueVector elems, GITypeTag element_tag, GIArgument* arg, void* array, size_t length) { using T = Gjs::Tag::RealT; for (size_t i = 0; i < length; i++) { gjs_arg_set(arg, *(static_cast(array) + i)); if (!gjs_value_from_basic_gi_argument(cx, elems[i], element_tag, arg)) return false; } return true; } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) const GI::TypeInfo& element_type, 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], element_type, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, arg)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_basic_c_array_internal( JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, size_t length, void* contents) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag)); // Special case array(uint8) if (element_tag == GI_TYPE_TAG_UINT8) { JSObject* u8array = gjs_byte_array_from_data_copy(cx, length, contents); if (!u8array) return false; value_out.setObject(*u8array); return true; } // Special case array(unichar) to be a string in JS if (element_tag == GI_TYPE_TAG_UNICHAR) { return gjs_string_from_ucs4(cx, static_cast(contents), length, value_out); } // a null array pointer takes precedence over whatever `length` says if (!contents) { JSObject* array = JS::NewArrayObject(cx, 0); if (!array) return false; value_out.setObject(*array); return true; } JS::RootedValueVector elems{cx}; if (!elems.resize(length)) { JS_ReportOutOfMemory(cx); return false; } GIArgument arg; switch (element_tag) { // 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_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT8: if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_basic_c_array( cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: if (!fill_vector_from_basic_c_array(cx, &elems, element_tag, &arg, contents, length)) return false; break; case GI_TYPE_TAG_VOID: gjs_throw(cx, "Unknown Array element-type %d", element_tag); return false; default: g_assert_not_reached(); } JSObject* array = JS::NewArrayObject(cx, elems); if (!array) return false; value_out.setObject(*array); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_carray_internal(JSContext* cx, JS::MutableHandleValue value_p, GIArrayType array_type, const GI::TypeInfo& element_type, GITransfer transfer, size_t length, void* array) { GITypeTag element_tag = element_type.tag(); if (GI_TYPE_TAG_IS_BASIC(element_tag)) { return gjs_array_from_basic_c_array_internal(cx, value_p, element_tag, length, array); } // a null array pointer takes precedence over whatever `length` says if (!array) { JSObject* jsarray = JS::NewArrayObject(cx, 0); if (!jsarray) return false; value_p.setObject(*jsarray); return true; } JS::RootedValueVector elems{cx}; if (!elems.resize(length)) { JS_ReportOutOfMemory(cx); return false; } GIArgument arg; switch (element_tag) { case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{element_type.interface()}; GITypeTag storage_element_type = element_type.storage_type(); if (array_type != GI_ARRAY_TYPE_PTR_ARRAY && (interface_info.is_struct() || interface_info.is_union() || interface_info.is_enum_or_flags()) && !element_type.is_pointer()) { size_t element_size; if (auto union_info = interface_info.as()) { element_size = union_info->size(); } else if (auto struct_info = interface_info.as()) { element_size = struct_info->size(); } else { GITypeTag storage = interface_info.as()->storage_type(); element_size = basic_type_element_size(storage); } for (size_t i = 0; i < length; i++) { auto* value = static_cast(array) + (element_size * i); // use the storage tag instead of element tag to handle // enums and flags set_arg_from_carray_element(&arg, storage_element_type, value); if (!gjs_value_from_gi_argument(cx, elems[i], element_type, GJS_ARGUMENT_ARRAY_ELEMENT, transfer, &arg)) return false; } break; } } [[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: if (!fill_vector_from_carray(cx, elems, element_type, &arg, array, length, transfer)) return false; break; default: // Basic types handled above gjs_throw(cx, "Unknown Array element-type %s", element_type.display_string()); return false; } JSObject* obj = JS::NewArrayObject(cx, elems); if (!obj) return false; value_p.setObject(*obj); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_fixed_size_array(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& type_info, GITransfer transfer, void* array) { Maybe length = type_info.array_fixed_size(); g_assert(length); return gjs_array_from_carray_internal(cx, value_p, type_info.array_type(), type_info.element_type(), transfer, *length, array); } bool gjs_value_from_explicit_array( JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& type_info, GIArgument* arg, size_t length, GITransfer transfer /* = GI_TRANSFER_EVERYTHING */) { return gjs_array_from_carray_internal(cx, value_p, type_info.array_type(), type_info.element_type(), transfer, length, gjs_arg_get(arg)); } bool gjs_value_from_basic_explicit_array(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, GIArgument* arg, size_t length) { return gjs_array_from_basic_c_array_internal( cx, value_out, element_tag, length, gjs_arg_get(arg)); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_boxed_array(JSContext* cx, JS::MutableHandleValue value_p, GIArrayType array_type, const GI::TypeInfo& element_type, GITransfer transfer, GIArgument* arg) { if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } GArray* array; GPtrArray* ptr_array; void* data = nullptr; size_t length = 0; 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(cx, value_p, array_type, element_type, transfer, length, data); } GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& element_type, 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 = Gjs::gvalue_get(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 = Gjs::gvalue_get(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, element_type, transfer, length, data); } template GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_basic_zero_terminated_c_array( JSContext* cx, JS::MutableHandleValueVector elems, GITypeTag element_tag, GIArgument* arg, void* c_array) { using T = Gjs::Tag::RealT; T* array = static_cast(c_array); for (size_t ix = 0; array[ix]; ix++) { gjs_arg_set(arg, array[ix]); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_basic_gi_argument(cx, elems[ix], element_tag, arg)) return false; } return true; } GJS_JSAPI_RETURN_CONVENTION static bool fill_vector_from_zero_terminated_pointer_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) const GI::TypeInfo& param_info, GIArgument* arg, void* c_array, GITransfer transfer = GI_TRANSFER_EVERYTHING) { void** array = static_cast(c_array); for (size_t i = 0;; i++) { if (!array[i]) break; gjs_arg_set(arg, array[i]); 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 fill_vector_from_zero_terminated_non_pointer_carray( JSContext* cx, JS::RootedValueVector& elems, // NOLINT(runtime/references) const GI::TypeInfo& param_info, GIArgument* arg, size_t element_size, void* c_array, GITransfer transfer = GI_TRANSFER_EVERYTHING) { uint8_t* element_start = reinterpret_cast(c_array); for (size_t i = 0;; i++) { if (*element_start == 0 && memcmp(element_start, element_start + 1, element_size - 1) == 0) break; gjs_arg_set(arg, element_start); element_start += element_size; 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; } bool gjs_array_from_basic_zero_terminated_array( JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, void* c_array) { g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && "Use gjs_array_from_zero_terminated_c_array for non-basic types"); if (!c_array) { // OK, but no conversion to do value_out.setNull(); return true; } // Special case array(uint8_t) if (element_tag == GI_TYPE_TAG_UINT8) { size_t length = strlen(static_cast(c_array)); JSObject* byte_array = gjs_byte_array_from_data_copy(cx, length, c_array); if (!byte_array) return false; value_out.setObject(*byte_array); return true; } // Special case array(gunichar) to JS string if (element_tag == GI_TYPE_TAG_UNICHAR) { return gjs_string_from_ucs4(cx, static_cast(c_array), -1, value_out); } JS::RootedValueVector elems{cx}; GIArgument arg; switch (element_tag) { case GI_TYPE_TAG_INT8: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT16: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT16: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT32: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT32: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_UINT64: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_INT64: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_FLOAT: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_DOUBLE: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; case GI_TYPE_TAG_GTYPE: case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: if (!fill_vector_from_basic_zero_terminated_c_array( cx, &elems, element_tag, &arg, c_array)) return false; break; // Boolean zero-terminated array makes no sense, because false is also // zero case GI_TYPE_TAG_BOOLEAN: gjs_throw(cx, "Boolean zero-terminated array not supported"); return false; case GI_TYPE_TAG_VOID: gjs_throw(cx, "Unknown element-type 'void'"); return false; default: // UINT8 and UNICHAR are special cases handled above g_assert_not_reached(); } JSObject* array = JS::NewArrayObject(cx, elems); if (!array) return false; value_out.setObject(*array); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_array_from_zero_terminated_c_array( JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& element_type, GITransfer transfer, void* c_array) { GITypeTag element_tag = element_type.tag(); if (element_type.is_basic()) { return gjs_array_from_basic_zero_terminated_array(cx, value_p, element_tag, c_array); } JS::RootedValueVector elems{cx}; GIArgument arg; switch (element_tag) { case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{element_type.interface()}; auto reg_info = interface_info.as(); bool element_is_pointer = element_type.is_pointer(); bool is_struct = reg_info->is_struct(); if (!element_is_pointer && is_struct) { auto struct_info = interface_info.as(); size_t element_size = struct_info->size(); if (!fill_vector_from_zero_terminated_non_pointer_carray( cx, elems, element_type, &arg, element_size, c_array)) return false; break; } [[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: if (!fill_vector_from_zero_terminated_pointer_carray( cx, elems, element_type, &arg, c_array, transfer)) return false; break; default: // Handled in gjs_array_from_basic_zero_terminated_c_array() gjs_throw(cx, "Unknown element-type %s", element_type.display_string()); return false; } JSObject* obj = JS::NewArrayObject(cx, elems); if (!obj) return false; value_p.setObject(*obj); return true; } bool gjs_object_from_g_hash(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& key_type, const GI::TypeInfo& val_type, GITransfer transfer, GHashTable* hash) { GHashTableIter iter; g_assert((!GI_TYPE_TAG_IS_BASIC(key_type.tag()) || !GI_TYPE_TAG_IS_BASIC(val_type.tag())) && "use gjs_value_from_basic_ghash() instead"); // a NULL hash table becomes a null JS value if (hash == nullptr) { value_p.setNull(); return true; } JS::RootedObject obj{cx, JS_NewPlainObject(cx)}; if (!obj) return false; value_p.setObject(*obj); JS::RootedValue keyjs{cx}, valjs{cx}; 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)) { key_type.argument_from_hash_pointer(key_pointer, &keyarg); if (!gjs_value_from_gi_argument(cx, &keyjs, key_type, GJS_ARGUMENT_HASH_ELEMENT, transfer, &keyarg)) return false; JS::RootedId key{cx}; if (!JS_ValueToId(cx, keyjs, &key)) return false; val_type.argument_from_hash_pointer(val_pointer, &valarg); if (!gjs_value_from_gi_argument(cx, &valjs, val_type, GJS_ARGUMENT_HASH_ELEMENT, transfer, &valarg) || !JS_DefinePropertyById(cx, obj, key, valjs, JSPROP_ENUMERATE)) return false; } return true; } bool gjs_value_from_basic_fixed_size_array_gi_argument( JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, size_t fixed_size, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument fixed array of %s to JS::Value", gi_type_tag_to_string(element_tag)); void* c_array = gjs_arg_get(arg); if (!c_array) { // OK, but no conversion to do value_out.setNull(); return true; } return gjs_array_from_basic_c_array_internal(cx, value_out, element_tag, fixed_size, c_array); } bool gjs_value_from_byte_array_gi_argument(JSContext* cx, JS::MutableHandleValue value_out, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument byte array to JS::Value"); auto* byte_array = gjs_arg_get(arg); if (!byte_array) { value_out.setNull(); return true; } JSObject* u8array = gjs_byte_array_from_byte_array(cx, byte_array); if (!u8array) return false; value_out.setObject(*u8array); return true; } bool gjs_value_from_basic_garray_gi_argument(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument GArray of %s to JS::Value", gi_type_tag_to_string(element_tag)); auto* garray = gjs_arg_get(arg); if (!garray) { value_out.setNull(); return true; } return gjs_array_from_basic_c_array_internal(cx, value_out, element_tag, garray->len, garray->data); } bool gjs_value_from_basic_gptrarray_gi_argument( JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument GPtrArray of %s to JS::Value", gi_type_tag_to_string(element_tag)); auto* ptr_array = gjs_arg_get(arg); if (!ptr_array) { value_out.setNull(); return true; } return gjs_array_from_basic_c_array_internal( cx, value_out, element_tag, ptr_array->len, ptr_array->pdata); } template GJS_JSAPI_RETURN_CONVENTION static bool array_from_basic_linked_list(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, T* list) { static_assert(std::is_same_v || std::is_same_v); g_assert( GI_TYPE_TAG_IS_BASIC(element_tag) && "use gjs_array_from_g_list() for lists containing non-basic types"); GIArgument arg; JS::RootedValueVector elems{cx}; for (size_t i = 0; list; list = list->next, ++i) { // for basic types, type tag == storage type gi_type_tag_argument_from_hash_pointer(element_tag, list->data, &arg); if (!elems.growBy(1)) { JS_ReportOutOfMemory(cx); return false; } if (!gjs_value_from_basic_gi_argument(cx, elems[i], element_tag, &arg)) return false; } JS::RootedObject obj{cx, JS::NewArrayObject(cx, elems)}; if (!obj) return false; value_out.setObject(*obj); return true; } bool gjs_array_from_basic_glist_gi_argument(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GArgument glist to JS::Value"); return array_from_basic_linked_list(cx, value_out, element_tag, gjs_arg_get(arg)); } bool gjs_array_from_basic_gslist_gi_argument(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GArgument gslist to JS::Value"); return array_from_basic_linked_list(cx, value_out, element_tag, gjs_arg_get(arg)); } bool gjs_value_from_basic_ghash(JSContext* cx, JS::MutableHandleValue value_out, GITypeTag key_tag, GITypeTag value_tag, GHashTable* hash) { g_assert( GI_TYPE_TAG_IS_BASIC(key_tag) && "use gjs_object_from_g_hash() for hashes with non-basic key types"); g_assert( GI_TYPE_TAG_IS_BASIC(value_tag) && "use gjs_object_from_g_hash() for hashes with non-basic value types"); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument ghash to JS::Value"); // a NULL hash table becomes a null JS value if (!hash) { value_out.setNull(); return true; } JS::RootedObject obj{cx, JS_NewPlainObject(cx)}; if (!obj) return false; JS::RootedValue v_key{cx}, v_val{cx}; JS::RootedId key{cx}; GIArgument key_arg, value_arg; GHashTableIter iter; void* key_pointer; void* val_pointer; g_hash_table_iter_init(&iter, hash); while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) { gi_type_tag_argument_from_hash_pointer(key_tag, key_pointer, &key_arg); gi_type_tag_argument_from_hash_pointer(value_tag, val_pointer, &value_arg); if (!gjs_value_from_basic_gi_argument(cx, &v_key, key_tag, &key_arg) || !JS_ValueToId(cx, v_key, &key) || !gjs_value_from_basic_gi_argument(cx, &v_val, value_tag, &value_arg) || !JS_DefinePropertyById(cx, obj, key, v_val, JSPROP_ENUMERATE)) return false; } value_out.setObject(*obj); return true; } bool gjs_value_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& type_info, GjsArgumentType argument_type, GITransfer transfer, GIArgument* arg) { GITypeTag type_tag = type_info.tag(); if (type_info.is_basic()) return gjs_value_from_basic_gi_argument(cx, value_p, type_tag, arg); if (type_tag == GI_TYPE_TAG_GLIST) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { return gjs_array_from_basic_glist_gi_argument( cx, value_p, element_type.tag(), arg); } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_GSLIST) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { return gjs_array_from_basic_gslist_gi_argument( cx, value_p, element_type.tag(), arg); } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_GHASH) { GI::AutoTypeInfo key_type{type_info.key_type()}; GI::AutoTypeInfo value_type{type_info.value_type()}; if (key_type.is_basic() && value_type.is_basic()) { return gjs_value_from_basic_ghash(cx, value_p, key_type.tag(), value_type.tag(), gjs_arg_get(arg)); } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_ARRAY) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { switch (type_info.array_type()) { case GI_ARRAY_TYPE_C: { if (type_info.is_zero_terminated()) { return gjs_array_from_basic_zero_terminated_array( cx, value_p, element_type.tag(), gjs_arg_get(arg)); } Maybe fixed_size = type_info.array_fixed_size(); g_assert(fixed_size && "arrays with length param handled in " "gjs_value_from_basic_explicit_array()"); return gjs_value_from_basic_fixed_size_array_gi_argument( cx, value_p, element_type.tag(), *fixed_size, arg); } case GI_ARRAY_TYPE_BYTE_ARRAY: return gjs_value_from_byte_array_gi_argument(cx, value_p, arg); case GI_ARRAY_TYPE_ARRAY: return gjs_value_from_basic_garray_gi_argument( cx, value_p, element_type.tag(), arg); case GI_ARRAY_TYPE_PTR_ARRAY: return gjs_value_from_basic_gptrarray_gi_argument( cx, value_p, element_type.tag(), arg); } } // else fall through to generic marshaller } gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Converting GIArgument %s to JS::Value", gi_type_tag_to_string(type_tag)); switch (type_tag) { case GI_TYPE_TAG_VOID: g_assert(type_info.is_pointer() && "non-pointers should be handled by " "gjs_value_from_basic_gi_argument()"); // If the argument is a pointer, convert to null to match our // in handling. value_p.setNull(); return true; 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(cx, ptr); if (!obj) return false; value_p.setObject(*obj); return true; } case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (interface_info.is_unresolved()) { gjs_throw(cx, "Unable to resolve arg type '%s'", interface_info.name()); return false; } // Enum/Flags are aren't pointer types, unlike the other interface // subtypes if (auto enum_info = interface_info.as()) { int64_t value_int64 = enum_info->enum_from_int(gjs_arg_get(arg)); if (interface_info.is_flags()) { GType gtype = enum_info->gtype(); if (gtype != G_TYPE_NONE) { // Check to make sure 32 bit flag if (static_cast(value_int64) != value_int64) { gjs_throw(cx, "0x%" PRIx64 " is not a valid value for flags %s", value_int64, g_type_name(gtype)); return false; } // Pass only valid values Gjs::AutoTypeClass gflags_class{gtype}; value_int64 &= gflags_class->mask; } } else { if (!gjs_enum_value_is_valid(cx, enum_info.value(), value_int64)) return false; } value_p.setNumber(static_cast(value_int64)); return true; } if (auto struct_info = interface_info.as(); struct_info && struct_info->is_foreign()) { return gjs_struct_foreign_convert_from_gi_argument( cx, value_p, struct_info.value(), arg); } // Everything else is a pointer type, NULL is the easy case if (!gjs_arg_get(arg)) { value_p.setNull(); return true; } if (auto struct_info = interface_info.as(); struct_info && struct_info->is_gtype_struct()) { // 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(cx, gtype, value_p); } return gjs_lookup_object_constructor(cx, gtype, value_p); } GType gtype = interface_info.as()->gtype(); 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(cx, value_p, gjs_arg_get(arg)); } if (g_type_is_a(gtype, G_TYPE_ERROR)) { JSObject* obj = ErrorInstance::object_for_c_ptr( cx, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } if (auto struct_info = interface_info.as()) { if (struct_info->is_gdk_atom()) { GI::AutoFunctionInfo atom_name_fun{ struct_info->method("name").value()}; GIArgument atom_name_ret; Gjs::GErrorResult<> result = atom_name_fun.invoke({{*arg}}, {}, &atom_name_ret); if (result.isErr()) { gjs_throw(cx, "Failed to call gdk_atom_name(): %s", result.inspectErr()->message); return false; } Gjs::AutoChar name{gjs_arg_get(&atom_name_ret)}; if (g_strcmp0("NONE", name) == 0) { value_p.setNull(); return true; } return gjs_string_from_utf8(cx, name, value_p); } 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; } } JSObject* obj; if (transfer == GI_TRANSFER_EVERYTHING) obj = StructInstance::new_for_c_struct( cx, struct_info.value(), gjs_arg_get(arg)); else obj = StructInstance::new_for_c_struct( cx, struct_info.value(), gjs_arg_get(arg), Boxed::NoCopy{}); if (!obj) return false; value_p.setObject(*obj); return true; } if (auto union_info = interface_info.as()) { JSObject* obj = UnionInstance::new_for_c_union( cx, union_info.value(), 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( cx, 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(cx, "Type %s registered for unexpected interface_type %s", g_type_name(gtype), interface_info.type_string()); return false; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { JSObject* obj = gjs_param_from_g_param( cx, G_PARAM_SPEC(gjs_arg_get(arg))); if (!obj) return false; value_p.setObject(*obj); return true; } if (gtype == G_TYPE_NONE) { gjs_throw(cx, "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( cx, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } gjs_throw(cx, "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 (type_info.array_type() == GI_ARRAY_TYPE_C) { if (type_info.is_zero_terminated()) { return gjs_array_from_zero_terminated_c_array( cx, value_p, type_info.element_type(), transfer, gjs_arg_get(arg)); } // arrays with length are handled outside of this function g_assert(!type_info.array_length_index() && "Use gjs_value_from_explicit_array() for arrays with " "length param"); return gjs_array_from_fixed_size_array( cx, value_p, type_info, transfer, gjs_arg_get(arg)); } if (type_info.array_type() == GI_ARRAY_TYPE_BYTE_ARRAY) { auto* byte_array = gjs_arg_get(arg); JSObject* array = gjs_byte_array_from_byte_array(cx, byte_array); if (!array) { gjs_throw(cx, "Couldn't convert GByteArray to a Uint8Array"); return false; } value_p.setObject(*array); return true; } // this assumes the array type is GArray or GPtrArray return gjs_array_from_boxed_array(cx, value_p, type_info.array_type(), type_info.element_type(), transfer, arg); case GI_TYPE_TAG_GLIST: return gjs_array_from_g_list(cx, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GSLIST: return gjs_array_from_g_list(cx, value_p, type_info, transfer, gjs_arg_get(arg)); case GI_TYPE_TAG_GHASH: return gjs_object_from_g_hash(cx, value_p, type_info.key_type(), type_info.value_type(), transfer, gjs_arg_get(arg)); default: // basic types handled in gjs_value_from_basic_gi_argument() g_warning("Unhandled type %s converting GIArgument to JavaScript", gi_type_tag_to_string(type_tag)); return false; } } ///// RELEASE MARSHALLERS ////////////////////////////////////////////////////// // These marshaller function are responsible for releasing the values stored in // GIArgument after a C function call succeeds or fails. template GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_g_list(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, GjsArgumentFlags flags, GIArgument* arg) { static_assert(std::is_same_v || std::is_same_v); Gjs::SmartPointer list{gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER) return true; GIArgument elem; GI::AutoTypeInfo element_type{type_info.element_type()}; for (T* l = list; l; l = l->next) { gjs_arg_set(&elem, l->data); if (!gjs_g_arg_release_internal( cx, transfer, element_type, element_type.tag(), GJS_ARGUMENT_LIST_ELEMENT, flags, &elem)) return false; } return true; } enum class ArrayReleaseType : uint8_t { EXPLICIT_LENGTH, ZERO_TERMINATED, }; template static inline void release_basic_array_internal(GITypeTag element_tag, Maybe length, void** array) { if (!Gjs::basic_type_needs_release(element_tag)) return; for (size_t ix = 0;; ix++) { if constexpr (release_type == ArrayReleaseType::ZERO_TERMINATED) { if (!array[ix]) break; } if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { if (ix == *length) break; } g_free(array[ix]); } } template static inline bool gjs_gi_argument_release_array_internal( JSContext* cx, GITransfer element_transfer, GjsArgumentFlags flags, const GI::TypeInfo& element_type, Maybe length, GIArgument* arg) { Gjs::AutoPointer arg_array{ gjs_arg_steal(arg)}; if (!arg_array) return true; if (element_transfer != GI_TRANSFER_EVERYTHING) return true; if (element_type.is_basic()) { release_basic_array_internal(element_type.tag(), length, arg_array.as()); return true; } if constexpr (release_type == ArrayReleaseType::EXPLICIT_LENGTH) { if (*length == 0) return true; } if (flags & GjsArgumentFlags::ARG_IN && !type_needs_release(element_type, element_type.tag())) return true; if (flags & GjsArgumentFlags::ARG_OUT && !type_needs_out_release(element_type, element_type.tag())) return true; GITypeTag type_tag = element_type.tag(); size_t element_size = gjs_type_get_element_size(type_tag, element_type); if G_UNLIKELY (element_size == 0) return true; bool is_pointer = element_type.is_pointer(); 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, element_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; } /* 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); } static void release_basic_type_internal(GITypeTag type_tag, GIArgument* arg) { if (is_string_type(type_tag)) g_clear_pointer(&gjs_arg_member(arg), g_free); } template static void basic_linked_list_release(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { static_assert(std::is_same_v || std::is_same_v); g_assert(GI_TYPE_TAG_IS_BASIC(element_tag) && "use gjs_g_arg_release_g_list() for lists with non-basic types"); Gjs::SmartPointer list = gjs_arg_steal(arg); if (transfer == GI_TRANSFER_CONTAINER) return; GIArgument elem; for (T* l = list; l; l = l->next) { gjs_arg_set(&elem, l->data); release_basic_type_internal(element_tag, &elem); } } void gjs_gi_argument_release_basic_glist(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument GList"); basic_linked_list_release(transfer, element_tag, arg); } void gjs_gi_argument_release_basic_gslist(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument GSList"); basic_linked_list_release(transfer, element_tag, arg); } void gjs_gi_argument_release_basic_ghash(GITransfer transfer, GITypeTag key_tag, GITypeTag value_tag, GIArgument* arg) { g_assert(GI_TYPE_TAG_IS_BASIC(key_tag) && GI_TYPE_TAG_IS_BASIC(value_tag)); if (!gjs_arg_get(arg)) return; Gjs::AutoPointer hash_table{ gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER) { g_hash_table_remove_all(hash_table); } else { GHashTableIter iter; g_hash_table_iter_init(&iter, hash_table); void *key, *val; while (g_hash_table_iter_next(&iter, &key, &val)) { GIArgument key_arg, val_arg; gjs_arg_set(&key_arg, key); gjs_arg_set(&val_arg, val); release_basic_type_internal(key_tag, &key_arg); switch (value_tag) { 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: release_basic_type_internal(value_tag, &val_arg); } g_hash_table_iter_steal(&iter); } } } void gjs_gi_argument_release_basic_c_array(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { if (!gjs_arg_get(arg)) return; if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) g_clear_pointer(&gjs_arg_member(arg), g_strfreev); else g_clear_pointer(&gjs_arg_member(arg), g_free); } void gjs_gi_argument_release_basic_c_array(GITransfer transfer, GITypeTag element_tag, size_t length, GIArgument* arg) { if (!gjs_arg_get(arg)) return; Gjs::AutoPointer array{gjs_arg_steal(arg)}; if (!is_string_type(element_tag) || transfer == GI_TRANSFER_CONTAINER) return; for (size_t ix = 0; ix < length; ix++) g_free(array[ix]); } void gjs_gi_argument_release_basic_garray(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { if (!gjs_arg_get(arg)) return; Gjs::AutoPointer array{ gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) return; for (size_t ix = 0; ix < array->len; ix++) g_free(g_array_index(array, char*, ix)); } void gjs_gi_argument_release_byte_array(GIArgument* arg) { if (!gjs_arg_get(arg)) return; g_clear_pointer(&gjs_arg_member(arg), g_byte_array_unref); } void gjs_gi_argument_release_basic_gptrarray(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { if (!gjs_arg_get(arg)) return; Gjs::AutoPointer array{ gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) return; g_ptr_array_foreach( array, [](void* ptr, void*) { g_free(ptr); }, nullptr); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_g_arg_release_internal( JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, GITypeTag type_tag, [[maybe_unused]] GjsArgumentType argument_type, GjsArgumentFlags flags, GIArgument* arg) { g_assert(transfer != GI_TRANSFER_NOTHING || flags != GjsArgumentFlags::NONE); if (type_info.is_basic()) { release_basic_type_internal(type_tag, arg); return true; } if (type_tag == GI_TYPE_TAG_GLIST) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { basic_linked_list_release(transfer, element_type.tag(), arg); return true; } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_GSLIST) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { basic_linked_list_release(transfer, element_type.tag(), arg); return true; } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_GHASH) { GI::AutoTypeInfo key_type{type_info.key_type()}; GI::AutoTypeInfo value_type{type_info.value_type()}; if (key_type.is_basic() && value_type.is_basic()) { gjs_gi_argument_release_basic_ghash(transfer, key_type.tag(), value_type.tag(), arg); return true; } // else fall through to generic marshaller } if (type_tag == GI_TYPE_TAG_ARRAY) { GI::AutoTypeInfo element_type{type_info.element_type()}; if (element_type.is_basic()) { switch (type_info.array_type()) { case GI_ARRAY_TYPE_C: gjs_gi_argument_release_basic_c_array( transfer, element_type.tag(), arg); return true; case GI_ARRAY_TYPE_ARRAY: gjs_gi_argument_release_basic_garray( transfer, element_type.tag(), arg); return true; case GI_ARRAY_TYPE_BYTE_ARRAY: gjs_gi_argument_release_byte_array(arg); return true; case GI_ARRAY_TYPE_PTR_ARRAY: gjs_gi_argument_release_basic_gptrarray( transfer, element_type.tag(), arg); return true; default: g_assert_not_reached(); } } // else fall through to generic marshaller } switch (type_tag) { case GI_TYPE_TAG_VOID: g_assert(type_info.is_pointer() && "non-pointer should be handled by " "release_basic_type_internal()"); return true; case GI_TYPE_TAG_ERROR: if (!is_transfer_in_nothing(transfer, flags)) g_clear_error(&gjs_arg_member(arg)); return true; case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (auto struct_info = interface_info.as(); struct_info && struct_info->is_foreign()) return gjs_struct_foreign_release_gi_argument( cx, transfer, struct_info.value(), arg); if (interface_info.is_enum_or_flags()) return true; // enum and flags // Anything else is a pointer if (!gjs_arg_get(arg)) return true; GType gtype = interface_info.as()->gtype(); 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)); return true; } 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); return true; } if (g_type_is_a(gtype, G_TYPE_CLOSURE)) { g_clear_pointer(&gjs_arg_member(arg), g_closure_unref); return true; } if (g_type_is_a(gtype, G_TYPE_VALUE)) { // G_TYPE_VALUE is a G_TYPE_BOXED, but we special case it if (type_info.is_pointer()) g_boxed_free(gtype, gjs_arg_steal(arg)); else g_clear_pointer(&gjs_arg_member(arg), g_value_unset); return true; } if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (!is_transfer_in_nothing(transfer, flags)) g_boxed_free(gtype, gjs_arg_steal(arg)); return true; } 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); return true; } if (gtype == G_TYPE_NONE) { if (!is_transfer_in_nothing(transfer, flags)) { gjs_throw(cx, "Don't know how to release GIArgument: not an " "object or boxed type"); return false; } return true; } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { if (!is_transfer_in_nothing(transfer, flags)) { auto* priv = FundamentalPrototype::for_gtype(cx, gtype); priv->call_unref_function(gjs_arg_steal(arg)); } return true; } gjs_throw(cx, "Unhandled GType %s releasing GIArgument", g_type_name(gtype)); return false; } case GI_TYPE_TAG_ARRAY: { if (!gjs_arg_get(arg)) return true; // OK GIArrayType array_type = type_info.array_type(); if (array_type == GI_ARRAY_TYPE_C) { GI::AutoTypeInfo element_type{type_info.element_type()}; switch (element_type.tag()) { case GI_TYPE_TAG_INTERFACE: if (!element_type.is_pointer()) { GI::AutoBaseInfo interface_info{ element_type.interface()}; if (interface_info.is_struct() || interface_info.is_union()) { g_clear_pointer(&gjs_arg_member(arg), g_free); return true; } } [[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 (type_info.is_zero_terminated()) { return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>( cx, element_transfer, flags | GjsArgumentFlags::ARG_OUT, element_type, {}, arg); } return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>( cx, element_transfer, flags | GjsArgumentFlags::ARG_OUT, element_type, type_info.array_fixed_size(), arg); } default: // basic types handled above gjs_throw( cx, "Releasing a C array with explicit length, that was " "nested inside another container. This is not " "supported (and will leak)"); return false; } } if (array_type == GI_ARRAY_TYPE_ARRAY) { GI::AutoTypeInfo element_type = type_info.element_type(); GITypeTag element_tag = element_type.tag(); switch (element_tag) { 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: { Gjs::AutoPointer array{ gjs_arg_steal(arg)}; if (transfer != GI_TRANSFER_CONTAINER && type_needs_out_release(element_type, element_tag)) { for (unsigned i = 0; i < array->len; i++) { GIArgument arg_iter; gjs_arg_set(&arg_iter, g_array_index(array, void*, i)); if (!gjs_g_arg_release_internal( cx, transfer, element_type, element_tag, GJS_ARGUMENT_ARRAY_ELEMENT, flags, &arg_iter)) return false; } } return true; } default: // basic types handled above gjs_throw( cx, "Don't know how to release GArray element-type %d", element_tag); return false; } } if (array_type == GI_ARRAY_TYPE_PTR_ARRAY) { GI::AutoTypeInfo element_type{type_info.element_type()}; Gjs::AutoPointer array{ gjs_arg_steal(arg)}; if (transfer != GI_TRANSFER_CONTAINER) { for (unsigned 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(cx, transfer, element_type, &arg_iter, flags)) return false; } } return true; } // GI_ARRAY_TYPE_BYTEARRAY handled above; other values unknown g_return_val_if_reached(false); } case GI_TYPE_TAG_GLIST: return gjs_g_arg_release_g_list(cx, transfer, type_info, flags, arg); case GI_TYPE_TAG_GSLIST: return gjs_g_arg_release_g_list(cx, transfer, type_info, flags, arg); case GI_TYPE_TAG_GHASH: { if (gjs_arg_get(arg) == nullptr) return true; // nothing to do Gjs::AutoPointer hash_table{gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER) { g_hash_table_remove_all(hash_table); return true; } GI::AutoTypeInfo key_type{type_info.key_type()}; GI::AutoTypeInfo val_type{type_info.value_type()}; GITypeTag key_tag = key_type.tag(), val_tag = val_type.tag(); g_assert((!GI_TYPE_TAG_IS_BASIC(key_tag) || !GI_TYPE_TAG_IS_BASIC(val_tag)) && "use basic_ghash_release() instead"); GHashTableIter iter; g_hash_table_iter_init(&iter, hash_table); void *key, *val; bool failed = false; while (g_hash_table_iter_next(&iter, &key, &val)) { GIArgument key_arg, val_arg; gjs_arg_set(&key_arg, key); gjs_arg_set(&val_arg, val); if (!gjs_g_arg_release_internal(cx, transfer, key_type, key_tag, GJS_ARGUMENT_HASH_ELEMENT, flags, &key_arg)) failed = true; switch (val_tag) { 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( cx, transfer, val_type, val_tag, GJS_ARGUMENT_HASH_ELEMENT, flags, &val_arg)) failed = true; } g_hash_table_iter_steal(&iter); } return !failed; } default: // basic types should have been handled in release_basic_type_internal() g_warning("Unhandled type %s releasing GIArgument", gi_type_tag_to_string(type_tag)); return false; } } bool gjs_gi_argument_release(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, GIArgument* arg, GjsArgumentFlags flags /* = NONE */) { if (transfer == GI_TRANSFER_NOTHING && !is_transfer_in_nothing(transfer, flags)) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s out param or return value", type_info.type_string()); return gjs_g_arg_release_internal(cx, transfer, type_info, type_info.tag(), GJS_ARGUMENT_ARGUMENT, flags, arg); } void gjs_gi_argument_release_basic(GITransfer transfer, GITypeTag type_tag, GjsArgumentFlags flags, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING && !is_transfer_in_nothing(transfer, flags)) return; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s out param or return value", gi_type_tag_to_string(type_tag)); release_basic_type_internal(type_tag, arg); } bool gjs_gi_argument_release_in_arg(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, 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 tag = type_info.tag(); gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument %s in param", type_info.type_string()); if (!type_needs_release(type_info, tag)) return true; return gjs_g_arg_release_internal(cx, transfer, type_info, tag, GJS_ARGUMENT_ARGUMENT, GjsArgumentFlags::ARG_IN, arg); } void gjs_gi_argument_release_basic_in_array(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument basic C array in param"); if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) g_clear_pointer(&gjs_arg_member(arg), g_strfreev); else g_clear_pointer(&gjs_arg_member(arg), g_free); } void gjs_gi_argument_release_basic_in_array(GITransfer transfer, GITypeTag element_tag, size_t length, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument basic C array in param"); Gjs::AutoPointer array{gjs_arg_steal(arg)}; if (!is_string_type(element_tag)) return; for (size_t ix = 0; ix < length; ix++) g_free(array[ix]); } bool gjs_gi_argument_release_in_array(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, size_t length, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>( cx, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, type_info.element_type(), Some(length), arg); } bool gjs_gi_argument_release_in_array(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) return true; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array in param"); return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>(cx, GI_TRANSFER_EVERYTHING, GjsArgumentFlags::ARG_IN, type_info.element_type(), {}, arg); } void gjs_gi_argument_release_basic_out_array(GITransfer transfer, GITypeTag element_tag, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array out param"); if (is_string_type(element_tag) && transfer != GI_TRANSFER_CONTAINER) g_clear_pointer(&gjs_arg_member(arg), g_strfreev); else g_clear_pointer(&gjs_arg_member(arg), g_free); } void gjs_gi_argument_release_basic_out_array(GITransfer transfer, GITypeTag element_tag, size_t length, GIArgument* arg) { if (transfer == GI_TRANSFER_NOTHING) return; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Releasing GIArgument array out param"); Gjs::AutoPointer array{gjs_arg_steal(arg)}; if (transfer == GI_TRANSFER_CONTAINER || !is_string_type(element_tag)) return; for (size_t ix = 0; ix < length; ix++) g_free(array[ix]); } bool gjs_gi_argument_release_out_array(JSContext* cx, GITransfer transfer, const GI::TypeInfo& type_info, size_t 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; return gjs_gi_argument_release_array_internal< ArrayReleaseType::EXPLICIT_LENGTH>( cx, element_transfer, GjsArgumentFlags::ARG_OUT, type_info.element_type(), Some(length), arg); } bool gjs_gi_argument_release_out_array(JSContext* cx, GITransfer transfer, const GI::TypeInfo& 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; return gjs_gi_argument_release_array_internal< ArrayReleaseType::ZERO_TERMINATED>(cx, element_transfer, GjsArgumentFlags::ARG_OUT, type_info.element_type(), {}, arg); } cjs-140.0/gi/arg.h0000664000175000017500000003023315167114161012546 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 #pragma once #include #include // for size_t #include #include #include #include // for GHashTable #include #include #include "gi/info.h" #include "cjs/macros.h" // Different roles for a GIArgument; currently used only in exception and debug // messages. enum GjsArgumentType : uint8_t { GJS_ARGUMENT_ARGUMENT, GJS_ARGUMENT_RETURN_VALUE, GJS_ARGUMENT_FIELD, GJS_ARGUMENT_LIST_ELEMENT, GJS_ARGUMENT_HASH_ELEMENT, GJS_ARGUMENT_ARRAY_ELEMENT }; enum class [[clang::flag_enum]] 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&, GjsArgumentFlags const&); [[nodiscard]] char* gjs_argument_display_name(const char* arg_name, GjsArgumentType); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_callback_out_arg(JSContext*, JS::HandleValue, const GI::ArgInfo&, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_explicit_array(JSContext*, JS::HandleValue, const GI::TypeInfo&, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, void** contents, size_t* length_p); size_t gjs_type_get_element_size(GITypeTag element_tag, const GI::TypeInfo&); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_gi_argument(JSContext*, JS::HandleValue, const GI::TypeInfo&, GjsArgumentType, GITransfer, GIArgument*, GjsArgumentFlags = GjsArgumentFlags::NONE, const char* arg_name = nullptr); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_basic_gi_argument(JSContext*, JS::HandleValue, GITypeTag, GIArgument*, const char* arg_name, GjsArgumentType, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_gerror_gi_argument(JSContext*, JS::HandleValue, GITransfer, GIArgument*, const char* arg_name, GjsArgumentType, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_basic_glist_gi_argument(JSContext*, JS::HandleValue, GITypeTag element_tag, GIArgument*, const char* arg_name, GjsArgumentType); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_basic_gslist_gi_argument(JSContext*, JS::HandleValue, GITypeTag element_tag, GIArgument*, const char* arg_name, GjsArgumentType); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_basic_ghash_gi_argument(JSContext*, JS::HandleValue, GITypeTag key_tag, GITypeTag value_tag, GITransfer, GIArgument*, const char* arg_name, GjsArgumentType, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_basic_array_gi_argument(JSContext*, JS::HandleValue, GITypeTag element_tag, GIArrayType, GIArgument*, const char* arg_name, GjsArgumentType, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_byte_array_gi_argument(JSContext*, JS::HandleValue, GIArgument*, const char* arg_name, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_basic_explicit_array(JSContext*, JS::HandleValue, GITypeTag element_tag, const char* arg_name, GjsArgumentType, GjsArgumentFlags, void** contents_out, size_t* length_out); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_gdk_atom_gi_argument(JSContext*, JS::HandleValue, GIArgument*, const char* arg_name, GjsArgumentType); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_interface_gi_argument(JSContext*, JS::HandleValue, const GI::BaseInfo& interface_info, GITransfer, GIArgument*, const char* arg_name, GjsArgumentType, GjsArgumentFlags); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_basic_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_gi_argument(JSContext*, JS::MutableHandleValue, const GI::TypeInfo&, GjsArgumentType, GITransfer, GIArgument*); GJS_JSAPI_RETURN_CONVENTION inline bool gjs_value_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& 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_basic_ghash(JSContext*, JS::MutableHandleValue, GITypeTag key_tag, GITypeTag value_tag, GHashTable*); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_basic_glist_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_basic_gslist_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_basic_zero_terminated_array(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, void* c_array); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_basic_fixed_size_array_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, size_t fixed_size, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_basic_explicit_array(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, GIArgument*, size_t length); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_explicit_array(JSContext*, JS::MutableHandleValue, const GI::TypeInfo&, GIArgument*, size_t length, GITransfer = GI_TRANSFER_EVERYTHING); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_byte_array_gi_argument(JSContext*, JS::MutableHandleValue, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_basic_garray_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_basic_gptrarray_gi_argument(JSContext*, JS::MutableHandleValue, GITypeTag element_tag, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release(JSContext*, GITransfer, const GI::TypeInfo&, GIArgument*, GjsArgumentFlags = GjsArgumentFlags::NONE); void gjs_gi_argument_release_basic(GITransfer, GITypeTag, GjsArgumentFlags, GIArgument*); void gjs_gi_argument_release_basic_glist(GITransfer, GITypeTag element_tag, GIArgument*); void gjs_gi_argument_release_basic_gslist(GITransfer, GITypeTag element_tag, GIArgument*); void gjs_gi_argument_release_basic_ghash(GITransfer, GITypeTag key_tag, GITypeTag value_tag, GIArgument*); void gjs_gi_argument_release_basic_garray(GITransfer transfer, GITypeTag element_tag, GIArgument* arg); void gjs_gi_argument_release_basic_gptrarray(GITransfer transfer, GITypeTag element_tag, GIArgument* arg); void gjs_gi_argument_release_basic_c_array(GITransfer, GITypeTag element_tag, GIArgument*); void gjs_gi_argument_release_basic_c_array(GITransfer, GITypeTag element_tag, size_t length, GIArgument*); void gjs_gi_argument_release_basic_in_array(GITransfer, GITypeTag element_tag, GIArgument*); void gjs_gi_argument_release_basic_in_array(GITransfer, GITypeTag element_tag, size_t length, GIArgument*); void gjs_gi_argument_release_basic_out_array(GITransfer, GITypeTag element_tag, GIArgument*); void gjs_gi_argument_release_basic_out_array(GITransfer, GITypeTag element_tag, size_t length, GIArgument*); void gjs_gi_argument_release_byte_array(GIArgument* arg); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, const GI::TypeInfo&, size_t length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_out_array(JSContext*, GITransfer, const GI::TypeInfo&, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, const GI::TypeInfo&, size_t length, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_array(JSContext*, GITransfer, const GI::TypeInfo&, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_gi_argument_release_in_arg(JSContext*, GITransfer, const GI::TypeInfo&, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_flags_value_is_valid(JSContext*, GType, int64_t value); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_strv(JSContext*, JS::MutableHandleValue, const char** strv); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_to_strv(JSContext*, JS::Value array_value, unsigned length, void** arr_p); GJS_JSAPI_RETURN_CONVENTION bool gjs_array_from_g_value_array(JSContext*, JS::MutableHandleValue, const GI::TypeInfo& element_type, GITransfer, const GValue*); GJS_JSAPI_RETURN_CONVENTION bool gjs_object_from_g_hash(JSContext*, JS::MutableHandleValue, const GI::TypeInfo& key_type, const GI::TypeInfo& val_type, GITransfer, GHashTable*); cjs-140.0/gi/boxed.cpp0000664000175000017500000011735715167114161013446 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: 2022 Marco Trevisan #include #include #include // for memcpy, size_t, strcmp #include // for one_of, any_of #include #include #include // for move, forward #include #include #include #include // for JS_EncodeStringToUTF8 #include // for ESClass #include // for JS_ReportOutOfMemory #include #include // for GCHashMap #include // for MutableWrappedPtrOperations #include #include // for SetReservedSlot #include // for JS_DefineFunction, JS_Enumerate #include #include #include // for UniqueChars #include #include #include // for IdVector #include #include #include #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/boxed.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/info.h" #include "gi/repo.h" #include "gi/struct.h" #include "gi/union.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util.h" #include "util/log.h" using mozilla::Maybe, mozilla::Some; template [[nodiscard]] static bool struct_is_simple(const GI::UnownedInfo&); template [[nodiscard]] static bool simple_struct_has_pointers(const GI::UnownedInfo&); template BoxedInstance::BoxedInstance(Prototype* prototype, JS::HandleObject obj) : BaseClass(prototype, obj), m_allocated_directly(false), m_owning_ptr(false) {} // See GIWrapperBase::resolve(). template 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 Maybe method_info{info().method(prop_name.get())}; if (!method_info) { *resolved = false; return true; } method_info->log_usage(); if (method_info->is_method()) { gjs_debug(GJS_DEBUG_GBOXED, "Defining method %s in prototype for %s", method_info->name(), format_name().c_str()); // 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(). template bool BoxedPrototype::new_enumerate_impl( JSContext* cx, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable [[maybe_unused]]) { for (const GI::AutoFunctionInfo& meth_info : info().methods()) { if (meth_info.is_method()) { jsid id = gjs_intern_string_to_id(cx, meth_info.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. */ template Base* BoxedBase::get_copy_source( JSContext* cx, JS::Value value) const { if (!value.isObject()) return nullptr; JS::RootedObject object{cx, &value.toObject()}; Base* source_priv = Base::for_js(cx, object); if (!source_priv || 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.) */ template void BoxedInstance::allocate_directly() { g_assert(get_prototype()->can_allocate_directly()); own_ptr(g_malloc0(info().size())); 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. template std::unique_ptr BoxedPrototype::create_field_map( JSContext* cx, const BoxedInfo& info) { auto result = std::make_unique(); typename BoxedInfo::FieldsIterator fields = info.fields(); if (!result->reserve(fields.size())) { JS_ReportOutOfMemory(cx); return nullptr; } for (GI::AutoFieldInfo field_info : fields) { // 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 const std::string& field_name = find_unique_js_field_name(info, field_info.name()); JSString* atom = JS_AtomizeAndPinStringN(cx, field_name.c_str(), field_name.length()); 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. */ template 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. */ template Maybe BoxedPrototype::lookup_field(JSContext* cx, JSString* prop_name) { if (!ensure_field_map(cx)) return {}; JS::RootedString rooted_name(cx, prop_name); JS::UniqueChars encoded_prop_name = JS_EncodeStringToUTF8(cx, rooted_name); const std::string field_name{ find_unique_js_field_name(info(), encoded_prop_name.get())}; if (field_name != encoded_prop_name.get()) { prop_name = JS_AtomizeAndPinStringN(cx, field_name.c_str(), field_name.length()); } 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 {}; } return Some(entry->value()); } /* 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. */ template bool BoxedInstance::init_from_props( JSContext* cx, JS::Value props_value) { if (!props_value.isObject()) { gjs_throw(cx, "argument should be a hash with fields to set"); return false; } JS::RootedObject props{cx, &props_value.toObject()}; JS::Rooted ids{cx, cx}; if (!JS_Enumerate(cx, props, &ids)) { gjs_throw(cx, "Failed to enumerate fields hash"); return false; } JS::RootedValue value{cx}; for (size_t ix = 0, length = ids.length(); ix < length; ix++) { if (!ids[ix].isString()) { gjs_throw(cx, "Fields hash contained a non-string field"); return false; } Maybe field_info = get_prototype()->lookup_field(cx, 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( cx, props, "property list", JS::HandleId::fromMarkedLocation(ids[ix].address()), &value)) return false; if (!field_setter_impl(cx, *field_info, value)) return false; } return true; } template bool BoxedInstance::invoke_static_method( JSContext* cx, JS::HandleObject obj, JS::HandleId method_name, const JS::CallArgs& args) { GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); JS::RootedObject js_constructor{cx}; if (!gjs_object_require_property( cx, obj, nullptr, gjs->atoms().constructor(), &js_constructor)) return false; JS::RootedValue method{cx}; if (!gjs_object_require_property(cx, js_constructor, nullptr, method_name, &method)) return false; return gjs->call_function(nullptr, method, args, args.rval()); } /** * BoxedInstance::copy_boxed: * * Allocate a new boxed pointer using g_boxed_copy(), either from a raw boxed * pointer or another BoxedInstance. */ template void BoxedInstance::copy_boxed(void* boxed_ptr) { own_ptr(g_boxed_copy(gtype(), boxed_ptr)); debug_lifecycle("Boxed pointer created with g_boxed_copy()"); } template void BoxedInstance::copy_boxed(Instance* source) { copy_boxed(source->ptr()); } /** * BoxedInstance::copy_memory: * * Allocate a new boxed pointer by copying the contents of another boxed pointer * or another BoxedInstance. */ template void BoxedInstance::copy_memory(void* boxed_ptr) { allocate_directly(); memcpy(m_ptr, boxed_ptr, info().size()); } template void BoxedInstance::copy_memory(Instance* source) { copy_memory(source->ptr()); } // See GIWrapperBase::constructor(). template bool BoxedInstance::constructor_impl( JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args) { // Short-circuit copy-construction in the case where we can use copy_boxed() // or copy_memory() Base* source_priv; if (args.length() == 1 && (source_priv = get_copy_source(cx, args[0]))) { if (!source_priv->check_is_instance(cx, "construct boxed object")) return false; if (g_type_is_a(gtype(), G_TYPE_BOXED)) { copy_boxed(source_priv->to_instance()); return true; } if (get_prototype()->can_allocate_directly()) { copy_memory(source_priv->to_instance()); return true; } } Prototype* 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 m_zero_args_constructor and // m_default_constructor are always Nothing 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 (Maybe zero_args_info{ proto->zero_args_constructor_info()}; zero_args_info) { GIArgument rval_arg; Gjs::GErrorResult<> result = zero_args_info->invoke({}, {}, &rval_arg); if (result.isErr()) { gjs_throw(cx, "Failed to invoke boxed constructor: %s", result.inspectErr()->message); return false; } own_ptr(gjs_arg_steal(&rval_arg)); debug_lifecycle("Boxed pointer created from zero-args constructor"); } else if (Maybe default_ctor_info{ proto->default_constructor_info()}; proto->can_allocate_directly_without_pointers() || (!default_ctor_info && proto->can_allocate_directly())) { // has_default_constructor() takes priority over can_allocate_directly() // for historical compatibility reasons allocate_directly(); } else if (default_ctor_info) { js::ESClass es_class = js::ESClass::Other; if (proto->can_allocate_directly() && args.length() == 1 && args[0].isObject()) { JS::RootedObject arg0{cx, &args[0].toObject()}; if (!JS::GetBuiltinClass(cx, arg0, &es_class)) return false; } if (es_class == js::ESClass::Object) { // If one argument is passed and it's a plain object, assume we are // constructing from a property bag. Introspected constructors // should not take a property bag as argument. allocate_directly(); } else { // 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. JS::RootedId ctor_name{ cx, gjs_intern_string_to_id(cx, default_ctor_info->name())}; if (ctor_name.isVoid() || !invoke_static_method(cx, obj, ctor_name, args)) 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 { gjs_throw(cx, "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 property bag if (args.length() == 0) return true; if (args.length() > 1) { gjs_throw(cx, "Constructor with multiple arguments not supported for %s", name()); return false; } return init_from_props(cx, args[0]); } template BoxedInstance::~BoxedInstance() { if (!m_owning_ptr) return; if (m_allocated_directly) { g_free(m_ptr.release()); return; } if (g_type_is_a(gtype(), G_TYPE_BOXED)) { g_boxed_free(gtype(), m_ptr.release()); return; } // This check is not easily moveable to ~StructInstance() because of the // g_assert_not_reached() below. So we do it inline here with if constexpr. if constexpr (Base::TAG == GI::InfoTag::STRUCT) { if (g_type_is_a(gtype(), G_TYPE_VARIANT)) { g_variant_unref(static_cast(m_ptr.release())); return; } } g_assert_not_reached(); } template void BoxedInstance::trace_impl(JSTracer* trc) { m_nested_objects.trace(trc); } /** * 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. */ template Maybe BoxedBase::get_field_info( JSContext* cx, uint32_t id) const { Maybe field_info = info().fields()[id]; if (!field_info) gjs_throw(cx, "No field %d on boxed type %s", id, name()); return field_info; } // Helper function to work around -Wunsupported-friend, where it is not possible // for BoxedInstance to be a friend of a BoxedInstance method and // vice versa. This could be static for g++, but not for clang++. template void adopt_nested_ptr(OtherInstance* priv, void* data) { priv->share_ptr(data); priv->debug_lifecycle( "Boxed pointer created, pointing inside memory owned by parent"); } /** * 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. */ template template bool BoxedInstance::get_nested_interface_object( JSContext* cx, JSObject* parent_obj, const GI::FieldInfo& field_info, const GI::UnownedInfo& struct_info, JS::MutableHandleValue value) const { if (!struct_is_simple(struct_info)) { gjs_throw(cx, "Reading field %s.%s is not supported", format_name().c_str(), field_info.name()); return false; } // If we have already set the field from a JS object which we have stashed // because it owns pointers, return that JS object instead of creating one. auto entry = m_nested_objects.lookup(field_info.name()); if (entry.found()) { value.setObject(*entry->value().get()); return true; } JS::RootedObject obj{ cx, gjs_new_object_with_generic_prototype(cx, struct_info)}; if (!obj) return false; FieldInstance* priv = FieldInstance::new_for_js_object(cx, obj); // A structure nested inside a parent object; doesn't have an independent // allocation adopt_nested_ptr(priv, raw_ptr() + field_info.offset()); /* 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. */ template bool BoxedBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv); if (!priv->check_is_instance(cx, "get a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); Maybe field_info{priv->get_field_info(cx, field_ix)}; if (!field_info) return false; return priv->to_instance()->field_getter_impl(cx, obj, field_info.ref(), args.rval()); } // See BoxedBase::field_getter(). template bool BoxedInstance::field_getter_impl( JSContext* cx, JSObject* obj, const GI::FieldInfo& field_info, JS::MutableHandleValue rval) const { GI::AutoTypeInfo type_info{field_info.type_info()}; if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface{type_info.interface()}; if (auto union_info = interface.as()) { return get_nested_interface_object( cx, obj, field_info, union_info.value(), rval); } if (auto struct_info = interface.as()) { return get_nested_interface_object( cx, obj, field_info, struct_info.value(), rval); } } GIArgument arg; if (field_info.read(m_ptr, &arg).isErr()) { gjs_throw(cx, "Reading field %s.%s is not supported", format_name().c_str(), field_info.name()); return false; } if (type_info.tag() == GI_TYPE_TAG_ARRAY && type_info.array_length_index()) { unsigned length_field_ix = type_info.array_length_index().value(); Maybe length_field_info{ get_field_info(cx, length_field_ix)}; if (!length_field_info) { gjs_throw(cx, "Reading field %s.%s is not supported", format_name().c_str(), field_info.name()); return false; } GIArgument length_arg; if (length_field_info->read(m_ptr, &length_arg).isErr()) { gjs_throw(cx, "Reading field %s.%s is not supported", format_name().c_str(), length_field_info->name()); return false; } size_t length = gjs_gi_argument_get_array_length( length_field_info->type_info().tag(), &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. * * If the copied memory contains pointers, they are owned by the nested boxed * type's JS object, so the nested JS object will be prevented from being * garbage collected while the parent JS object is active. */ template template bool BoxedInstance::set_nested_interface_object( JSContext* cx, const GI::FieldInfo& field_info, const GI::UnownedInfo& boxed_info, JS::HandleValue value) { if (!struct_is_simple(boxed_info)) { gjs_throw(cx, "Writing field %s.%s is not supported", format_name().c_str(), field_info.name()); return false; } /* If we can't directly copy from the source object we need to construct a * new temporary object. */ FieldBase* source_priv = nullptr; JS::RootedObject source_object{cx}; bool field_has_same_info = false; if constexpr (std::is_same_v) { field_has_same_info = info() == boxed_info; if (field_has_same_info) { source_object = &value.toObject(); source_priv = FieldBase::for_js(cx, source_object); } } if (!source_priv && !field_has_same_info) { source_object = &value.toObject(); source_priv = FieldBase::for_js(cx, source_object); if (source_priv && source_priv->info() != boxed_info) { std::string source_name{source_priv->format_name()}; gjs_throw(cx, "Impossible to associate a %s to a %s.%s field", source_name.c_str(), name(), field_info.name()); return false; } } if (!source_priv) { JS::RootedObject proto{cx, gjs_lookup_generic_prototype(cx, boxed_info)}; if (!proto) return false; JS::RootedValueArray<1> args{cx}; args[0].set(value); source_object = gjs_construct_object_dynamic(cx, proto, args); if (!source_object || !FieldBase::for_js_typecheck(cx, source_object, &source_priv)) return false; } if (!source_priv->check_is_instance(cx, "copy")) return false; // Any pointers in the copied memory are still owned by the source struct. // So we need to tie the lifetime of the source JS object to this one. if (simple_struct_has_pointers(boxed_info) && !m_nested_objects.put(field_info.name(), source_object)) { JS_ReportOutOfMemory(cx); return false; } memcpy(raw_ptr() + field_info.offset(), source_priv->to_instance()->ptr(), source_priv->info().size()); return true; } // See BoxedBase::field_setter(). template bool BoxedInstance::field_setter_impl( JSContext* cx, const GI::FieldInfo& field_info, JS::HandleValue value) { GI::AutoTypeInfo type_info{field_info.type_info()}; if (!type_info.is_pointer() && type_info.tag() == GI_TYPE_TAG_INTERFACE) { GI::AutoBaseInfo interface_info{type_info.interface()}; if (auto union_info = interface_info.as()) { return set_nested_interface_object( cx, field_info, union_info.value(), value); } if (auto struct_info = interface_info.as()) { return set_nested_interface_object( cx, field_info, struct_info.value(), value); } } GIArgument arg; if (!gjs_value_to_gi_argument( cx, value, type_info, GJS_ARGUMENT_FIELD, GI_TRANSFER_NOTHING, &arg, GjsArgumentFlags::MAY_BE_NULL, field_info.name())) return false; auto cleanup = mozilla::MakeScopeExit([cx, &arg, &type_info]() { JS::AutoSaveExceptionState saved_exc{cx}; if (!gjs_gi_argument_release(cx, GI_TRANSFER_NOTHING, type_info, &arg, GjsArgumentFlags::ARG_IN)) gjs_log_exception(cx); saved_exc.restore(); }); if (field_info.write(m_ptr, &arg).isErr()) { gjs_throw(cx, "Writing field %s.%s is not supported", format_name().c_str(), field_info.name()); return false; } return true; } /** * 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. */ template bool BoxedBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, Base, priv); if (!priv->check_is_instance(cx, "set a field")) return false; uint32_t field_ix = gjs_dynamic_property_private_slot(&args.callee()) .toPrivateUint32(); Maybe 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; } template std::string BoxedPrototype::find_unique_js_field_name( const BoxedInfo& info, std::string const& c_field_name) { // Give priority to methods that have names equal to fields std::string property_name{c_field_name}; Maybe method_info; while ((method_info = info.method(property_name.c_str()))) property_name.insert(0, "_"); return property_name; } /** * 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. */ template bool BoxedPrototype::define_boxed_class_fields( JSContext* cx, JS::HandleObject proto) { uint32_t count = 0; // 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 (const GI::AutoFieldInfo& field : info().fields()) { const std::string property_name = find_unique_js_field_name(info(), field.name()); JS::RootedValue private_id{cx, JS::PrivateUint32Value(count++)}; JS::RootedId id{cx, gjs_intern_string_to_id(cx, property_name.c_str())}; gjs_debug_marshal(GJS_DEBUG_GBOXED, "Defining field %s%s in prototype for %s", field.name(), property_name != field.name() ? (" (as " + property_name + ")").c_str() : "", format_name().c_str()); if (!gjs_define_property_dynamic(cx, proto, property_name.c_str(), id, "boxed_field", &Base::field_getter, private_id, &Base::field_setter, private_id, GJS_MODULE_PROP_FLAGS)) return false; } return true; } // Overrides GIWrapperPrototype::trace_impl(). template void BoxedPrototype::trace_impl(JSTracer* trc) { if (m_field_map) m_field_map->trace(trc); } [[nodiscard]] static bool type_can_be_allocated_directly(const GI::TypeInfo& type_info) { if (type_info.is_pointer()) { if (type_info.tag() == GI_TYPE_TAG_ARRAY && type_info.array_type() == GI_ARRAY_TYPE_C) return type_can_be_allocated_directly(type_info.element_type()); return true; } if (type_info.tag() != GI_TYPE_TAG_INTERFACE) return true; GI::AutoBaseInfo interface_info{type_info.interface()}; if (auto struct_info = interface_info.as()) return struct_is_simple(struct_info.value()); if (auto union_info = interface_info.as()) return struct_is_simple(union_info.value()); if (interface_info.is_enum_or_flags()) return true; return false; } [[nodiscard]] static bool direct_allocation_has_pointers(const GI::TypeInfo& type_info) { if (type_info.is_pointer()) { if (type_info.tag() == GI_TYPE_TAG_ARRAY && type_info.array_type() == GI_ARRAY_TYPE_C) { return direct_allocation_has_pointers(type_info.element_type()); } return type_info.tag() != GI_TYPE_TAG_VOID; } if (type_info.tag() != GI_TYPE_TAG_INTERFACE) return false; GI::AutoBaseInfo interface{type_info.interface()}; if (auto struct_info = interface.as()) return simple_struct_has_pointers(struct_info.value()); if (auto union_info = interface.as()) return simple_struct_has_pointers(union_info.value()); 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. */ template [[nodiscard]] bool struct_is_simple(const GI::UnownedInfo& info) { typename GI::UnownedInfo::FieldsIterator iter = info.fields(); // If it's opaque, it's not simple if (iter.size() == 0) return false; return std::all_of( iter.begin(), iter.end(), [](const GI::AutoFieldInfo& field_info) { return type_can_be_allocated_directly(field_info.type_info()); }); } template [[nodiscard]] static bool simple_struct_has_pointers(const GI::UnownedInfo& info) { g_assert(struct_is_simple(info) && "Don't call simple_struct_has_pointers() on a non-simple struct"); typename GI::UnownedInfo::FieldsIterator fields = info.fields(); return std::any_of( fields.begin(), fields.end(), [](const GI::AutoFieldInfo& field) { return direct_allocation_has_pointers(field.type_info()); }); } template BoxedPrototype::BoxedPrototype(const BoxedInfo& info, GType gtype) : BaseClass(info, gtype), 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); } if (gtype == G_TYPE_NONE) return; ConstructorIndex i = 0; Maybe first_constructor; /* 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. */ for (const GI::AutoFunctionInfo& func_info : info.methods()) { if (func_info.is_constructor()) { if (!first_constructor) first_constructor = Some(i); if (!m_zero_args_constructor && func_info.n_args() == 0) m_zero_args_constructor = Some(i); if (!m_default_constructor && strcmp(func_info.name(), "new") == 0) m_default_constructor = Some(i); } i++; } if (!m_default_constructor && m_zero_args_constructor) m_default_constructor = m_zero_args_constructor; if (!m_default_constructor && first_constructor) m_default_constructor = first_constructor; } /** * BoxedPrototype::define_class_impl: * @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. */ template bool BoxedPrototype::define_class_impl( JSContext* cx, JS::HandleObject in_object, const BoxedInfo& info, JS::MutableHandleObject prototype) { JS::RootedObject unused_constructor{cx}; BoxedPrototype* priv = BoxedPrototype::create_class( cx, in_object, info, info.gtype(), &unused_constructor, prototype); return priv && priv->define_boxed_class_fields(cx, prototype); } /* 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 template JSObject* BoxedInstance::new_for_c_struct_impl( JSContext* cx, const BoxedInfo& info, void* gboxed, Args... args) { if (gboxed == nullptr) return nullptr; gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s %p with JSObject", info.name(), 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; return obj; } /** * 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. */ template bool BoxedInstance::init_from_c_struct( JSContext*, void* gboxed, Boxed::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; } template 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; } 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; } 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; } template class BoxedBase; template class BoxedPrototype; template class BoxedInstance; template JSObject* StructInstance::new_for_c_struct_impl<>( JSContext*, const GI::StructInfo&, void*); template JSObject* StructInstance::new_for_c_struct_impl( JSContext*, const GI::StructInfo&, void*, Boxed::NoCopy); template class BoxedBase; template class BoxedPrototype; template class BoxedInstance; template JSObject* UnionInstance::new_for_c_struct_impl<>(JSContext*, const GI::UnionInfo&, void*); cjs-140.0/gi/boxed.h0000664000175000017500000002307715167114161013106 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: 2022 Marco Trevisan // SPDX-FileCopyrightText: 2025 Philip Chimento #pragma once #include #include // for size_t #include #include // for unique_ptr #include #include #include #include #include // for GCHashMap #include #include // for DefaultHasher #include #include #include #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class JSTracer; namespace JS { class CallArgs; } namespace Boxed { struct NoCopy {}; using FieldMap = JS::GCHashMap, GI::AutoFieldInfo, js::DefaultHasher, js::SystemAllocPolicy>; } // namespace Boxed /* 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. */ template class BoxedBase : public GIWrapperBase { using BaseClass = GIWrapperBase; protected: using BaseClass::BaseClass; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GBOXED; // JS property accessors GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext*, unsigned, JS::Value*); // Helper methods that work on either instances or prototypes GJS_JSAPI_RETURN_CONVENTION mozilla::Maybe get_field_info(JSContext*, uint32_t id) const; public: [[nodiscard]] Base* get_copy_source(JSContext*, JS::Value) const; using BaseClass::info; using BaseClass::name; }; template class BoxedPrototype : public GIWrapperPrototype, GI::UnownedInfo> { using BoxedInfo = GI::UnownedInfo; using BaseClass = GIWrapperPrototype, BoxedInfo>; friend class GIWrapperBase; using ConstructorIndex = unsigned; mozilla::Maybe m_zero_args_constructor; mozilla::Maybe m_default_constructor; std::unique_ptr m_field_map; bool m_can_allocate_directly_without_pointers : 1; bool m_can_allocate_directly : 1; protected: explicit BoxedPrototype(const BoxedInfo&, GType); // Accessors public: GJS_JSAPI_RETURN_CONVENTION mozilla::Maybe lookup_field(JSContext*, JSString* prop_name); [[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]] mozilla::Maybe zero_args_constructor_info() const { return m_zero_args_constructor.map( [this](ConstructorIndex ix) { return *info().methods()[ix]; }); } [[nodiscard]] mozilla::Maybe default_constructor_info() const { return m_default_constructor.map( [this](ConstructorIndex ix) { return *info().methods()[ix]; }); } using BaseClass::format_name; using BaseClass::gtype; using BaseClass::info; using BaseClass::name; // JSClass operations private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext*, JS::HandleObject, 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*, const BoxedInfo&); GJS_JSAPI_RETURN_CONVENTION bool ensure_field_map(JSContext*); GJS_JSAPI_RETURN_CONVENTION bool define_boxed_class_fields(JSContext*, JS::HandleObject proto); protected: GJS_JSAPI_RETURN_CONVENTION static bool define_class_impl(JSContext*, JS::HandleObject in_object, const BoxedInfo&, JS::MutableHandleObject prototype); static std::string find_unique_js_field_name(const BoxedInfo&, const std::string& field_name); }; template class BoxedInstance : public GIWrapperInstance { using BaseClass = GIWrapperInstance; friend class GIWrapperBase; friend class BoxedBase; // for field_getter, etc template friend void adopt_nested_ptr(OtherInstance*, void*); using BoxedInfo = GI::UnownedInfo; // Reserved slots static const size_t PARENT_OBJECT = 1; protected: using NestedObjectsMap = JS::GCHashMap, js::DefaultHasher, js::SystemAllocPolicy>; NestedObjectsMap m_nested_objects; 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(Prototype*, JS::HandleObject); ~BoxedInstance(); // 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 copy_boxed(void* boxed_ptr); void copy_boxed(Instance* source); void copy_memory(void* boxed_ptr); void copy_memory(Instance* source); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool invoke_static_method(JSContext*, JS::HandleObject, JS::HandleId method_name, const JS::CallArgs&); GJS_JSAPI_RETURN_CONVENTION bool init_from_props(JSContext*, JS::Value props_value); template GJS_JSAPI_RETURN_CONVENTION bool get_nested_interface_object(JSContext*, JSObject* parent_obj, const GI::FieldInfo&, const GI::UnownedInfo&, JS::MutableHandleValue) const; template GJS_JSAPI_RETURN_CONVENTION bool set_nested_interface_object(JSContext*, const GI::FieldInfo&, const GI::UnownedInfo&, JS::HandleValue); GJS_JSAPI_RETURN_CONVENTION static void* 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 %s type not registered as " "boxed", Base::DEBUG_TAG); return nullptr; } // JSClass operations void trace_impl(JSTracer* trc); // JS property accessors GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext*, JSObject*, const GI::FieldInfo&, JS::MutableHandleValue rval) const; GJS_JSAPI_RETURN_CONVENTION bool field_setter_impl(JSContext*, const GI::FieldInfo&, JS::HandleValue); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext*, JS::HandleObject, const JS::CallArgs&); // Public API for initializing BoxedInstance JS object from C struct private: GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext*, void* gboxed); GJS_JSAPI_RETURN_CONVENTION bool init_from_c_struct(JSContext*, void* gboxed, Boxed::NoCopy); protected: template GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct_impl(JSContext*, const BoxedInfo&, void* gboxed, Args...); using BaseClass::debug_lifecycle; using BaseClass::get_copy_source; using BaseClass::get_field_info; using BaseClass::get_prototype; using BaseClass::m_ptr; using BaseClass::raw_ptr; public: using BaseClass::format_name; using BaseClass::gtype; using BaseClass::info; using BaseClass::name; }; namespace JS { template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS cjs-140.0/gi/closure.cpp0000664000175000017500000001560515167114161014012 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.cpp 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())}; if (!JS::Call(m_cx, this_obj, v_callable, args, retval)) { 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); } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx); gjs->schedule_gc_if_needed(); return true; } } // namespace Gjs cjs-140.0/gi/closure.h0000664000175000017500000001055015167114161013451 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 #pragma once #include #include #include #include #include #include "gi/utils-inl.h" #include "cjs/auto.h" #include "cjs/jsapi-util-root.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 = Gjs::AutoPointer; [[nodiscard]] constexpr static Closure* for_gclosure(GClosure* gclosure) { return 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, /* root = */ false, 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 [[nodiscard]] JSObject* callable() const { return m_callable.get(); } [[nodiscard]] constexpr JSContext* cx() 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* return_value, unsigned n_param_values, const GValue* param_values, void* invocation_hint, void* marshal_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 cjs-140.0/gi/cwrapper.cpp0000664000175000017500000000155015167114161014153 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-140.0/gi/cwrapper.h0000664000175000017500000005502515167114161013626 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 string methods #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; /* NOLINT(bugprone-macro-parentheses) */ \ if (!type::for_js_typecheck(cx, thisobj, &(priv), &(args))) \ return false; GJS_JSAPI_RETURN_CONVENTION bool gjs_wrapper_define_gtype_prop(JSContext*, JS::HandleObject constructor, 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 Wrapped* wrapped_ptr GJS_USED_VERBOSE_LIFECYCLE, const JSObject* 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 JSObject* 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 JSObject* 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 ubsan bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71962 // 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-140.0/gi/enumeration.cpp0000664000175000017500000000746015167114161014664 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 #include #include #include // for JS_NewPlainObject #include "gi/cwrapper.h" #include "gi/enumeration.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/auto.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* cx, JS::HandleObject in_object, const GI::ValueInfo& info) { const char* value_name = info.name(); int64_t value_val = info.value(); /* 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. */ Gjs::AutoChar fixed_name{g_ascii_strup(value_name, -1)}; for (size_t i = 0; fixed_name[i]; ++i) { char c = fixed_name[i]; if (!(g_ascii_isupper(c) || g_ascii_isdigit(c))) 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(cx, in_object, fixed_name, static_cast(value_val), GJS_MODULE_PROP_FLAGS)) { gjs_throw(cx, "Unable to define enumeration value %s %" PRId64 " (no memory most likely)", fixed_name.get(), value_val); return false; } return true; } bool gjs_define_enum_values(JSContext* cx, JS::HandleObject in_object, const GI::EnumInfo& info) { /* Fill in enum values first, so we don't define the enum itself until we're * sure we can finish successfully. */ auto iter = info.values(); return std::all_of(iter.begin(), iter.end(), [cx, in_object](const GI::AutoValueInfo& value_info) { return gjs_define_enum_value(cx, in_object, value_info); }); } bool gjs_define_enumeration(JSContext* cx, JS::HandleObject in_object, const GI::EnumInfo& info) { /* 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. */ const char* enum_name = info.name(); JS::RootedObject enum_obj{cx, JS_NewPlainObject(cx)}; if (!enum_obj) { gjs_throw(cx, "Could not create enumeration %s.%s", info.ns(), enum_name); return false; } GType gtype = info.gtype(); if (!gjs_define_enum_values(cx, enum_obj, info) || !gjs_define_static_methods(cx, enum_obj, gtype, info) || !gjs_wrapper_define_gtype_prop(cx, enum_obj, gtype)) return false; gjs_debug(GJS_DEBUG_GENUM, "Defining %s.%s as %p", info.ns(), enum_name, enum_obj.get()); if (!JS_DefineProperty(cx, in_object, enum_name, enum_obj, GJS_MODULE_PROP_FLAGS)) { gjs_throw( cx, "Unable to define enumeration property (no memory most likely)"); return false; } return true; } cjs-140.0/gi/enumeration.h0000664000175000017500000000106415167114161014323 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 #pragma once #include #include #include "gi/info.h" #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enum_values(JSContext*, JS::HandleObject in_object, const GI::EnumInfo&); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_enumeration(JSContext*, JS::HandleObject in_object, const GI::EnumInfo&); cjs-140.0/gi/foreign.cpp0000664000175000017500000001023015167114161013754 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 // for pair #include #include #include #include #include "gi/foreign.h" #include "gi/info.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" enum LoadedStatus : uint8_t { NotLoaded, Loaded }; static std::unordered_map foreign_modules{ {"cairo", NotLoaded}}; using StructID = std::pair; struct StructIDHash { [[nodiscard]] size_t operator()(const 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, const GI::StructInfo& info) { StructID key{info.ns(), info.name()}; auto entry = foreign_structs_table.find(key); if (entry == foreign_structs_table.end()) { if (gjs_foreign_load_foreign_module(cx, info.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* cx, JS::Value value, const GI::StructInfo& info, const char* arg_name, GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(cx, info); if (!foreign) return false; if (!foreign->to_func(cx, value, arg_name, argument_type, transfer, flags, arg)) return false; return true; } bool gjs_struct_foreign_convert_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, const GI::StructInfo& info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(cx, info); if (!foreign) return false; if (!foreign->from_func(cx, value_p, arg)) return false; return true; } bool gjs_struct_foreign_release_gi_argument(JSContext* cx, GITransfer transfer, const GI::StructInfo& info, GIArgument* arg) { GjsForeignInfo* foreign = gjs_struct_foreign_lookup(cx, info); if (!foreign) return false; if (!foreign->release_func) return true; if (!foreign->release_func(cx, transfer, arg)) return false; return true; } cjs-140.0/gi/foreign.h0000664000175000017500000000417515167114161013434 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 #pragma once #include #include #include #include #include "gi/arg.h" #include "gi/info.h" #include "cjs/macros.h" using GjsArgOverrideToGIArgumentFunc = bool (*)(JSContext*, JS::Value, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); using GjsArgOverrideFromGIArgumentFunc = bool (*)(JSContext*, JS::MutableHandleValue, GIArgument*); using GjsArgOverrideReleaseGIArgumentFunc = bool (*)(JSContext*, GITransfer, GIArgument*); struct GjsForeignInfo { GjsArgOverrideToGIArgumentFunc to_func; GjsArgOverrideFromGIArgumentFunc from_func; GjsArgOverrideReleaseGIArgumentFunc release_func; }; void gjs_struct_foreign_register(const char* gi_namespace, const char* type_name, GjsForeignInfo*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_to_gi_argument(JSContext*, JS::Value, const GI::StructInfo&, const char* arg_name, GjsArgumentType, GITransfer, GjsArgumentFlags, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_convert_from_gi_argument(JSContext*, JS::MutableHandleValue, const GI::StructInfo&, GIArgument*); GJS_JSAPI_RETURN_CONVENTION bool gjs_struct_foreign_release_gi_argument(JSContext*, GITransfer, const GI::StructInfo&, GIArgument*); cjs-140.0/gi/function.cpp0000664000175000017500000014243115167114161014161 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 // for unique_ptr #include #include #include #include // for move #include #ifndef G_DISABLE_ASSERT # include // for numeric_limits #endif #include #include #include #include #include #include #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 #include #include #ifndef G_DISABLE_ASSERT # include // for IsCallable #endif #include "gi/arg-cache.h" #include "gi/arg-inl.h" #include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/gerror.h" #include "gi/info.h" #include "gi/object.h" #include "gi/utils-inl.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/gerror-result.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" using mozilla::Maybe, mozilla::Some; // We use uint8_t for arguments; functions can't have more than this. #define GJS_ARG_INDEX_INVALID UINT8_MAX namespace Gjs { class Function : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_function; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GFUNCTION; GI::AutoCallableInfo m_info; ArgsCache m_arguments; uint8_t m_js_in_argc = 0; uint8_t m_js_out_argc = 0; GIFunctionInvoker m_invoker{}; explicit Function(const GI::CallableInfo& info) : m_info(info) { 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*, GType, const GI::CallableInfo&); [[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, const GI::FunctionInfo& 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) { using T = Gjs::Tag::RealT; 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(const GI::TypeInfo& ret_type, void* result, GIArgument* return_value) { // Be consistent with gjs_value_to_gi_argument() switch (ret_type.tag()) { 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: if (ret_type.interface().is_enum_or_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.\n" << "The 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 get the * return value back. */ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) { GI::StackTypeInfo ret_type; // Fill in the result with some hopefully neutral value m_info.load_return_type(&ret_type); if (ret_type.tag() != GI_TYPE_TAG_VOID) { GIArgument argument = {}; gjs_arg_unset(&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* cx = this->cx(); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); 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{cx, callable()}; unsigned n_args = m_info.n_args(); auto cleanup = mozilla::MakeScopeExit([this, gjs]() { if (this->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", this); gjs->async_closure_enqueue_for_gc(this); } gjs->schedule_gc_if_needed(); }); JS::RootedObject this_object{cx}; unsigned 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(cx, 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(cx); return; } } /* "this" is not included in the GI signature, but is in the C (and FFI) * signature */ c_args_offset = 1; } JS::RootedValue rval{cx}; if (!callback_closure_inner(cx, this_object, gobj, &rval, args, ret_type, n_args, c_args_offset, result)) { if (!JS_IsExceptionPending(cx)) { // "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 (!m_info.can_throw_gerror()) { gjs_log_exception_uncaught(cx); 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(cx); g_propagate_error(gerror, local_error); } } inline GIArgument* get_argument_for_arg_info(const GI::ArgInfo& arg_info, GIArgument** args, int index) { if (!arg_info.caller_allocates()) return *reinterpret_cast(args[index]); return args[index]; } bool GjsCallbackTrampoline::callback_closure_inner( JSContext* cx, JS::HandleObject this_object, GObject* gobject, JS::MutableHandleValue rval, GIArgument** args, const GI::TypeInfo& ret_type, unsigned n_args, unsigned c_args_offset, void* result) { unsigned n_outargs = 0; JS::RootedValueVector jsargs{cx}; if (!jsargs.reserve(n_args)) g_error("Unable to reserve space for vector"); GITypeTag ret_tag = ret_type.tag(); bool ret_type_is_void = ret_tag == GI_TYPE_TAG_VOID; bool in_args_to_cleanup = false; for (unsigned i = 0, n_jsargs = 0; i < n_args; i++) { GI::StackArgInfo arg_info; GI::StackTypeInfo type_info; m_info.load_arg(i, &arg_info); arg_info.load_type(&type_info); // Skip void* arguments if (type_info.tag() == GI_TYPE_TAG_VOID) continue; if (arg_info.direction() == GI_DIRECTION_OUT) { n_outargs++; continue; } if (arg_info.direction() == GI_DIRECTION_INOUT) n_outargs++; if (arg_info.ownership_transfer() != GI_TRANSFER_NOTHING) in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER; GjsParamType param_type = m_param_types[i]; switch (param_type) { case PARAM_SKIPPED: continue; case PARAM_ARRAY: { // In initialize(), we already don't store PARAM_ARRAY for non- // fixed-size arrays unsigned array_length_pos = type_info.array_length_index().value(); GI::StackArgInfo array_length_arg; GI::StackTypeInfo arg_type_info; m_info.load_arg(array_length_pos, &array_length_arg); array_length_arg.load_type(&arg_type_info); size_t length = gjs_gi_argument_get_array_length( arg_type_info.tag(), args[array_length_pos + c_args_offset]); if (!jsargs.growBy(1)) g_error("Unable to grow vector"); if (!gjs_value_from_explicit_array( cx, jsargs[n_jsargs++], type_info, args[i + c_args_offset], length, arg_info.ownership_transfer())) return false; break; } case PARAM_NORMAL: { if (!jsargs.growBy(1)) g_error("Unable to grow vector"); GIArgument* arg = args[i + c_args_offset]; if (arg_info.direction() == GI_DIRECTION_INOUT && !arg_info.caller_allocates()) arg = *reinterpret_cast(arg); if (!gjs_value_from_gi_argument(cx, 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 = m_info.caller_owns(); // non-void return value, no out args. Should be a single return value. if (!gjs_value_to_gi_argument( cx, rval, ret_type, GJS_ARGUMENT_RETURN_VALUE, transfer, &argument, GjsArgumentFlags::MAY_BE_NULL, "callback")) 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 arg. Should be a single return value. for (unsigned i = 0; i < n_args; i++) { GI::StackArgInfo arg_info; m_info.load_arg(i, &arg_info); if (arg_info.direction() == GI_DIRECTION_IN) continue; if (!gjs_value_to_callback_out_arg( cx, rval, arg_info, get_argument_for_arg_info(arg_info, args, i + c_args_offset))) return false; break; } } else { bool is_array; if (!JS::IsArrayObject(cx, rval, &is_array)) return false; if (!is_array) { gjs_throw(cx, "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{cx}; JS::RootedObject out_array{cx, rval.toObjectOrNull()}; size_t 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 = m_info.caller_owns(); if (!JS_GetElement(cx, out_array, elem_idx, &elem)) return false; if (!gjs_value_to_gi_argument( cx, elem, ret_type, GJS_ARGUMENT_RETURN_VALUE, transfer, &argument, GjsArgumentFlags::MAY_BE_NULL, "callback")) 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 { Gjs::AutoChar 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 (unsigned i = 0; i < n_args; i++) { GI::StackArgInfo arg_info; m_info.load_arg(i, &arg_info); if (arg_info.direction() == GI_DIRECTION_IN) continue; if (!JS_GetElement(cx, out_array, elem_idx, &elem)) return false; if (!gjs_value_to_callback_out_arg( cx, 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 (unsigned i = 0; i < n_args; i++) { GI::StackArgInfo arg_info; m_info.load_arg(i, &arg_info); GITransfer transfer = arg_info.ownership_transfer(); if (transfer == GI_TRANSFER_NOTHING) continue; if (arg_info.direction() != GI_DIRECTION_IN) continue; GIArgument* arg = args[i + c_args_offset]; if (m_scope == GI_SCOPE_TYPE_CALL) { GI::StackTypeInfo type_info; arg_info.load_type(&type_info); if (!gjs_gi_argument_release(cx, transfer, type_info, arg)) return false; continue; } struct InvalidateData { GI::StackArgInfo arg_info; GIArgument arg; }; auto* data = new InvalidateData({std::move(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 = data->arg_info.ownership_transfer(); GI::StackTypeInfo type_info; data->arg_info.load_type(&type_info); if (!gjs_gi_argument_release(self->cx(), transfer, type_info, &data->arg)) { gjs_throw(self->cx(), "Impossible to release closure argument '%s'", data->arg_info.name()); } }); } return true; } GjsCallbackTrampoline* GjsCallbackTrampoline::create( JSContext* cx, JS::HandleObject callable, const GI::CallableInfo& 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( // optional? JSContext* cx, JS::HandleObject callable, const GI::CallableInfo& 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, callable_info.name()), m_info(callable_info), m_param_types(std::make_unique(callable_info.n_args())), m_scope(scope), m_is_vfunc(is_vfunc) { add_finalize_notifier(); } GjsCallbackTrampoline::~GjsCallbackTrampoline() { if (m_closure) m_info.destroy_closure(m_closure); } void GjsCallbackTrampoline::mark_forever() { s_forever_closure_list.emplace_back(this, Gjs::TakeOwnership{}); } 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), Gjs::TakeOwnership{}}; trampoline.as()->callback_closure(args, result); }; return m_info.create_closure(&m_cif, callback, this); } bool GjsCallbackTrampoline::initialize() { g_assert(is_valid()); g_assert(!m_closure); /* Analyze param types and directions, similarly to * init_cached_function_data */ unsigned n_param_types = m_info.n_args(); for (unsigned i = 0; i < n_param_types; i++) { GI::StackArgInfo arg_info; GI::StackTypeInfo type_info; if (m_param_types[i] == PARAM_SKIPPED) continue; m_info.load_arg(i, &arg_info); arg_info.load_type(&type_info); GIDirection direction = arg_info.direction(); GITypeTag type_tag = type_info.tag(); if (direction != GI_DIRECTION_IN) { // INOUT and OUT arguments are handled differently. continue; } if (type_tag == GI_TYPE_TAG_INTERFACE) { if (type_info.interface().is_callback()) { gjs_throw(cx(), "%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 (type_info.array_type() == GI_ARRAY_TYPE_C) { Maybe array_length_pos = type_info.array_length_index(); if (!array_length_pos) continue; if (*array_length_pos < n_param_types) { GI::StackArgInfo length_arg_info; m_info.load_arg(*array_length_pos, &length_arg_info); if (length_arg_info.direction() != direction) { gjs_throw(cx(), "%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 = m_info.is_method(); std::string retval = is_method ? "method" : "function"; retval += ' '; retval += m_info.ns(); retval += '.'; if (is_method) { retval += m_info.container()->name(); retval += '.'; } retval += m_info.name(); return retval; } namespace Gjs { static void* get_return_ffi_pointer_from_gi_argument( Maybe return_tag, GIFFIReturnValue* return_value) { if (!return_tag) return nullptr; if (return_tag->is_pointer()) return &gjs_arg_member(return_value); switch (return_tag->tag()) { case GI_TYPE_TAG_VOID: g_assert_not_reached(); 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_tag->is_enum_or_flags_interface()) return &gjs_arg_member(return_value); [[fallthrough]]; } 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* cx, 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"); GIFFIReturnValue return_value; unsigned ffi_argc = m_invoker.cif.nargs; GjsFunctionCallState state{cx, m_info}; if (state.gi_argc > Argument::MAX_ARGS) { gjs_throw(cx, "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(cx, "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) { JS::CallArgs::reportMoreArgsNeeded(cx, 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{cx, this_obj}; if (!args.isConstructing() && !args.computeThis(cx, &obj)) return false; std::string dynamicString("(unknown)"); if (state.is_method) { GIArgument* in_value = state.instance(); JS::RootedValue in_js_value{cx, JS::ObjectValue(*obj)}; if (!m_arguments.instance().value()->in(cx, &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 Maybe gtype = m_arguments.instance_type(); if (gtype) { 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(cx, obj); dynamicString = GJS_PROFILER_DYNAMIC_STRING(cx, o->format_name()); } } } std::string full_name{ GJS_PROFILER_DYNAMIC_STRING(cx, dynamicString + "." + format_name())}; AutoProfilerLabel label{cx, "", full_name}; 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) { GI::StackArgInfo arg_info; m_info.load_arg(gi_arg_pos, &arg_info); gjs_throw(cx, "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(), arg_info.name()); state.failed = true; break; } JS::RootedValue js_in_arg{cx}; if (js_arg_pos < args.length()) js_in_arg = args[js_arg_pos]; if (!gjs_arg->in(cx, &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(cx, 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); Maybe return_tag = m_arguments.return_tag(); // return_value_p will point inside the return GIFFIReturnValue union if the // C function has a non-void return type void* return_value_p = get_return_ffi_pointer_from_gi_argument(return_tag, &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_tag) { gi_type_tag_extract_ffi_return_value( return_tag->tag(), return_tag->interface_gtype(), &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++) { Maybe 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 = Some(m_arguments.argument(gi_arg_pos)); } gjs_debug_marshal( GJS_DEBUG_GFUNCTION, "Marshalling argument '%s' out, %d/%d GI args", gjs_arg.map(std::mem_fn(&Argument::arg_name)).valueOr(""), gi_arg_pos, state.gi_argc); JS::RootedValue js_out_arg{cx}; if (!r_value) { if (!gjs_arg && gi_arg_pos >= 0) { GI::StackArgInfo arg_info; m_info.load_arg(gi_arg_pos, &arg_info); gjs_throw( cx, "Error invoking %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.", format_name().c_str(), arg_info.name()); state.failed = true; break; } if (gjs_arg && !(*gjs_arg)->out(cx, &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(cx); 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(cx, 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++) { Maybe 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 = Some(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()); return !state->failed; } bool Function::call(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject callee{cx, &args.callee()}; Function* priv; if (!Function::for_js_typecheck(cx, callee, &priv, &args)) return false; gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p", callee.get(), priv); g_assert(priv); return priv->invoke(cx, args); } Function::~Function() { gi_function_invoker_clear(&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 (auto func_info = priv->m_info.as()) return gjs_string_from_utf8(cx, func_info->symbol(), rec.rval()); return gjs_string_from_utf8(cx, priv->format_name().c_str(), rec.rval()); } bool Function::to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, rec, this_obj, Function, priv); return priv->to_string_impl(cx, rec.rval()); } bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) { int n_args = m_info.n_args(); std::string arg_names; for (int i = 0, n_jsargs = 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(); } AutoChar descr; if (auto func_info = m_info.as()) { descr = g_strdup_printf( "%s(%s) {\n\t/* wrapper for native symbol %s() */\n}", format_name().c_str(), arg_names.c_str(), func_info->symbol()); } 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* cx, GType gtype /* = G_TYPE_NONE */) { if (auto func_info = m_info.as()) { GErrorResult<> result = func_info->prep_invoker(&m_invoker); if (result.isErr()) return gjs_throw_gerror(cx, result.unwrapErr()); } else if (auto vfunc_info = m_info.as()) { Gjs::GErrorResult result = vfunc_info->address(gtype); if (!result.isOk()) { if (result.inspectErr()->code != GI_INVOKE_ERROR_SYMBOL_NOT_FOUND) return gjs_throw_gerror(cx, result.unwrapErr()); gjs_throw(cx, "Virtual function not implemented: %s", result.inspectErr()->message); return false; } GErrorResult<> result2 = m_info.init_function_invoker(result.unwrap(), &m_invoker); if (result2.isErr()) return gjs_throw_gerror(cx, result2.unwrapErr()); } uint8_t n_args = m_info.n_args(); if (!m_arguments.initialize(cx, 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 (uint8_t i = 0; i < n_args; i++) { Argument* gjs_arg = m_arguments.argument(i); GI::StackArgInfo arg_info; if (gjs_arg && (gjs_arg->skip_in() || gjs_arg->skip_out())) { continue; } m_info.load_arg(i, &arg_info); GIDirection direction = arg_info.direction(); 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* cx, GType gtype, const GI::CallableInfo& info) { JS::RootedObject proto{cx, Function::create_prototype(cx)}; if (!proto) return nullptr; JS::RootedObject function{ cx, JS_NewObjectWithGivenProto(cx, &Function::klass, proto)}; if (!function) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function"); return nullptr; } auto* priv = new Function(info); Function::init_private(function, priv); debug_lifecycle(priv, function, "Constructor"); if (!priv->init(cx, gtype)) return nullptr; return function; } } // namespace Gjs GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_function(JSContext* cx, JS::HandleObject in_object, GType gtype, const GI::CallableInfo& info) { using std::string_literals::operator""s; std::string name; JS::RootedObject function{cx, Gjs::Function::create(cx, gtype, info)}; if (!function) return nullptr; if (info.is_function()) { name = info.name(); } else if (info.is_vfunc()) { name = "vfunc_"s + info.name(); } else { g_assert_not_reached(); } if (!JS_DefineProperty(cx, in_object, name.c_str(), function, GJS_MODULE_PROP_FLAGS)) { gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function"); function = nullptr; } return function; } bool gjs_invoke_constructor_from_c(JSContext* cx, const GI::FunctionInfo& info, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { return Gjs::Function::invoke_constructor_uncached(cx, info, obj, args, rvalue); } cjs-140.0/gi/function.h0000664000175000017500000001272515167114161013630 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 #pragma once #include #include #include // for unique_ptr #include #include #include #include #include #include #include #include #include #include #include #include "gi/closure.h" #include "gi/info.h" #include "cjs/auto.h" #include "cjs/gerror-result.h" #include "cjs/macros.h" namespace JS { class CallArgs; } enum GjsParamType : uint8_t { PARAM_NORMAL, PARAM_SKIPPED, PARAM_ARRAY, PARAM_CALLBACK, PARAM_UNKNOWN, }; struct GjsCallbackTrampoline : public Gjs::Closure { GJS_JSAPI_RETURN_CONVENTION static GjsCallbackTrampoline* create(JSContext*, JS::HandleObject callable, const GI::CallableInfo&, GIScopeType, bool has_scope_object, bool is_vfunc); ~GjsCallbackTrampoline(); [[nodiscard]] void* get_func_ptr() const { return m_info.closure_native_address(m_closure); } [[nodiscard]] 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*, JS::HandleObject callable, const GI::CallableInfo&, GIScopeType, 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, const GI::TypeInfo& ret_type, unsigned n_args, unsigned 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; GI::AutoCallableInfo 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 { Gjs::AutoCppPointer m_in_cvalues; Gjs::AutoCppPointer m_out_cvalues; Gjs::AutoCppPointer m_inout_original_cvalues; public: std::unordered_set ignore_release; JS::RootedObject instance_object; JS::RootedVector return_values; Gjs::AutoError local_error; const GI::CallableInfo 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, const GI::CallableInfo& callable) : instance_object(cx), return_values(cx), info(callable), gi_argc(callable.n_args()), failed(false), can_throw_gerror(callable.can_throw_gerror()), is_method(callable.is_method()) { 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() const { return !failed && !did_throw_gerror(); } constexpr unsigned last_processed_index() const { return first_arg_offset() + processed_c_args; } [[nodiscard]] Gjs::AutoChar display_name() { mozilla::Maybe container = info.container(); if (container) { return g_strdup_printf("%s.%s.%s", container->ns(), container->name(), info.name()); } return g_strdup_printf("%s.%s", info.ns(), info.name()); } }; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_define_function(JSContext*, JS::HandleObject in_object, GType, const GI::CallableInfo&); GJS_JSAPI_RETURN_CONVENTION bool gjs_invoke_constructor_from_c(JSContext*, const GI::FunctionInfo&, JS::HandleObject this_obj, const JS::CallArgs&, GIArgument* rvalue); cjs-140.0/gi/fundamental.cpp0000664000175000017500000003725315167114161014637 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 // for string methods #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/info.h" #include "gi/repo.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-root.h" // for WeakPtr methods #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" namespace JS { class CallArgs; } using mozilla::Maybe, mozilla::Some; 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 Maybe find_fundamental_constructor( const GI::ObjectInfo& info) { for (GI::AutoFunctionInfo func_info : info.methods()) { if (func_info.is_constructor()) return Some(func_info); } return {}; } bool FundamentalPrototype::resolve_interface(JSContext* cx, JS::HandleObject obj, bool* resolved, const char* name) { unsigned n_interfaces; Gjs::AutoFree interfaces{g_type_interfaces(gtype(), &n_interfaces)}; GI::Repository repo; for (unsigned i = 0; i < n_interfaces; i++) { Maybe iface_info{ repo.find_by_gtype(interfaces[i])}; if (!iface_info) continue; Maybe method_info{iface_info->method(name)}; if (method_info && method_info->is_method()) { if (gjs_define_function(cx, obj, gtype(), method_info.ref())) { *resolved = true; } else { return false; } } } return true; } // 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 Maybe method_info{info().method(prop_name.get())}; if (method_info) { method_info->log_usage(); if (method_info->is_method()) { // we do not define deprecated methods in the prototype if (method_info->is_deprecated()) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Ignoring definition of deprecated method %s in " "prototype %s", method_info->name(), format_name().c_str()); *resolved = false; return true; } gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Defining method %s in prototype for %s", method_info->name(), format_name().c_str()); 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* cx, JS::HandleObject obj, const JS::CallArgs& args, GIArgument* rvalue) { Maybe constructor_info = get_prototype()->constructor_info(); if (!constructor_info) { gjs_throw(cx, "Couldn't find a constructor for type %s", format_name().c_str()); return false; } return gjs_invoke_constructor_from_c(cx, *constructor_info, obj, args, rvalue); } // See GIWrapperBase::constructor(). bool FundamentalInstance::constructor_impl(JSContext* cx, JS::HandleObject object, const JS::CallArgs& args) { GIArgument ret_value; if (!invoke_constructor(cx, object, args, &ret_value) || !associate_js_instance(cx, object, gjs_arg_get(&ret_value))) return false; Maybe constructor_info = get_prototype()->constructor_info(); g_assert(constructor_info); GI::StackTypeInfo return_info; constructor_info->load_return_type(&return_info); return gjs_gi_argument_release(cx, constructor_info->caller_owns(), return_info, &ret_value); } FundamentalInstance::~FundamentalInstance() { if (m_ptr) { unref(); m_ptr = nullptr; } GJS_DEC_COUNTER(fundamental_instance); } FundamentalPrototype::FundamentalPrototype(const GI::ObjectInfo& info, GType gtype) : GIWrapperPrototype(info, gtype), m_ref_function(info.ref_function_pointer()), m_unref_function(info.unref_function_pointer()), m_get_value_function(info.get_value_function_pointer()), m_set_value_function(info.set_value_function_pointer()), m_constructor_info(find_fundamental_constructor(info)) { GJS_INC_COUNTER(fundamental_prototype); } FundamentalPrototype::~FundamentalPrototype() { GJS_DEC_COUNTER(fundamental_prototype); } 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, }; // FIXME: assume info is non-null on main? Is it possible to have hidden // fundamental types? GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_fundamental_prototype(JSContext* cx, const GI::ObjectInfo& info) { JS::RootedObject in_object{cx, gjs_lookup_namespace_object(cx, info)}; const char* constructor_name = info.name(); if (G_UNLIKELY (!in_object)) return nullptr; bool found; if (!JS_HasProperty(cx, in_object, constructor_name, &found)) return nullptr; JS::RootedValue value(cx); if (found && !JS_GetProperty(cx, in_object, constructor_name, &value)) return nullptr; JS::RootedObject constructor{cx}; 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(cx, in_object, info, &constructor)) return nullptr; } else { if (G_UNLIKELY(!value.isObject())) { gjs_throw(cx, "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(cx); JS::RootedObject prototype{cx}; if (!gjs_object_require_property(cx, constructor, "constructor object", atoms.prototype(), &prototype)) return nullptr; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_fundamental_prototype_from_gtype(JSContext* cx, GType gtype) { Maybe info; GI::Repository repo; // 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 types. while (gtype != G_TYPE_INVALID && !(info = repo.find_by_gtype(gtype))) gtype = g_type_parent(gtype); return gjs_lookup_fundamental_prototype(cx, info.ref()); } // 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() const { if (m_constructor_info) return m_constructor_info->n_args(); 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, const GI::ObjectInfo& info, JS::MutableHandleObject constructor) { JS::RootedObject prototype(cx); FundamentalPrototype* priv = FundamentalPrototype::create_class( cx, in_object, info, info.gtype(), constructor, &prototype); if (!priv) return false; if (info.fields().size() > 0) { gjs_debug(GJS_DEBUG_GFUNDAMENTAL, "Fundamental type '%s' apparently has accessible fields. GJS " "has no support for this yet, ignoring these.", priv->format_name().c_str()); } 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* cx, void* gfundamental) { if (!gfundamental) { gjs_throw(cx, "Cannot get JSObject for null fundamental pointer"); return nullptr; } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); 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{cx, gjs_lookup_fundamental_prototype_from_gtype( cx, G_TYPE_FROM_INSTANCE(gfundamental))}; if (!proto) return nullptr; JS::RootedObject object{ cx, JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto)}; if (!object) return nullptr; auto* priv = FundamentalInstance::new_for_js_object(cx, object); if (!priv->associate_js_instance(cx, 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; } 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-140.0/gi/fundamental.h0000664000175000017500000001340515167114161014275 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 #pragma once #include #include #include #include #include #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/wrapperutils.h" #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*, JS::HandleObject, 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; mozilla::Maybe m_constructor_info; explicit FundamentalPrototype(const GI::ObjectInfo&, GType); ~FundamentalPrototype(); public: GJS_JSAPI_RETURN_CONVENTION static FundamentalPrototype* for_gtype(JSContext*, GType); // Accessors [[nodiscard]] mozilla::Maybe 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*, JS::MutableHandleObject proto) const; [[nodiscard]] unsigned constructor_nargs() const; GJS_JSAPI_RETURN_CONVENTION bool resolve_interface(JSContext*, JS::HandleObject, bool* resolved, const char* name); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool* resolved); // Public API public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext*, JS::HandleObject in_object, const GI::ObjectInfo&, JS::MutableHandleObject constructor); }; class FundamentalInstance : public GIWrapperInstance { friend class FundamentalBase; // for set_value() friend class GIWrapperInstance; friend class GIWrapperBase; explicit FundamentalInstance(FundamentalPrototype*, JS::HandleObject); ~FundamentalInstance(); // Helper methods GJS_JSAPI_RETURN_CONVENTION bool invoke_constructor(JSContext*, JS::HandleObject, const JS::CallArgs&, GIArgument* rvalue); void ref() { get_prototype()->call_ref_function(m_ptr); } void unref() { 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*, JSObject*, void* gfundamental); // JS constructor GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext*, JS::HandleObject, const JS::CallArgs&); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext*, void* gfundamental); GJS_JSAPI_RETURN_CONVENTION static bool object_for_gvalue(JSContext*, const GValue*, GType, JS::MutableHandleObject); static void* copy_ptr(JSContext*, GType, void* gfundamental); }; cjs-140.0/gi/gerror.cpp0000664000175000017500000004455615167114161013645 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 string methods #include #include #include #include #include #include #include #include #include // for JSPROP_ENUMERATE #include // for JS_PSG #include #include #include // for BuildStackString, CaptureCurrentStack #include #include // for UniqueChars #include #include #include // for InformalValueTypeName, JS_GetClassObject #include // for JSProtoKey, JSProto_Error, JSProt... #include #include "gi/arg-inl.h" #include "gi/enumeration.h" #include "gi/gerror.h" #include "gi/info.h" #include "gi/repo.h" #include "gi/struct.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/error-types.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" using mozilla::Maybe; ErrorPrototype::ErrorPrototype(const GI::EnumInfo& info, GType gtype) : GIWrapperPrototype(info, gtype), m_domain(g_quark_from_string(info.error_domain())) { GJS_INC_COUNTER(gerror_prototype); } ErrorPrototype::~ErrorPrototype() { GJS_DEC_COUNTER(gerror_prototype); } ErrorInstance::ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj) : GIWrapperInstance(prototype, obj) { GJS_INC_COUNTER(gerror_instance); } ErrorInstance::~ErrorInstance() { GJS_DEC_COUNTER(gerror_instance); } /** * ErrorBase::domain: * * Fetches ErrorPrototype::domain() for instances as well as prototypes. */ GQuark ErrorBase::domain() const { return get_prototype()->domain(); } // See GIWrapperBase::constructor(). bool ErrorInstance::constructor_impl(JSContext* cx, JS::HandleObject object, const JS::CallArgs& args) { if (args.length() != 1 || !args[0].isObject()) { gjs_throw(cx, "Invalid parameters passed to GError constructor, expected " "one object"); return false; } JS::RootedObject params_obj{cx, &args[0].toObject()}; JS::UniqueChars message; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, params_obj, "GError constructor", atoms.message(), &message)) return false; int32_t code; if (!gjs_object_require_property(cx, 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(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, self); Gjs::AutoChar descr; // An error created via `new GLib.Error` will have a Struct* private // pointer, not an Error*, so we can't call regular to_string() on it. if (StructBase::typecheck(cx, self, G_TYPE_ERROR, GjsTypecheckNoThrow{})) { auto* gerror = StructBase::to_c_ptr(cx, 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(cx, descr, rec.rval()); } ErrorBase* priv; if (!for_js_typecheck(cx, 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(priv->format_name().c_str()); } else { descr = g_strdup_printf("%s: %s", priv->format_name().c_str(), priv->to_instance()->message()); } return gjs_string_from_utf8(cx, descr, rec.rval()); } // JSNative implementation of `valueOf()`. bool ErrorBase::value_of(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, self); JS::RootedObject prototype{cx}; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, self, "constructor", atoms.prototype(), &prototype)) { // This error message will be more informative JS_ClearPendingException(cx); gjs_throw(cx, "GLib.Error.valueOf() called on something that is not a " "constructor"); return false; } ErrorBase* priv; if (!for_js_typecheck(cx, prototype, &priv, &rec)) return false; rec.rval().setInt32(priv->domain()); return true; } 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 | JSCLASS_IS_DOMJSCLASS, // needed for Error.isError() &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}; // Overrides GIWrapperPrototype::get_parent_proto(). bool ErrorPrototype::get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) { GI::Repository repo{}; repo.require("GLib", "2.0").unwrap(); GI::AutoBaseInfo glib_error_info{ repo.find_by_name("GLib", "Error").value()}; proto.set(gjs_lookup_generic_prototype(cx, glib_error_info)); return !!proto; } bool ErrorPrototype::define_class(JSContext* cx, JS::HandleObject in_object, const GI::EnumInfo& info) { JS::RootedObject prototype{cx}, constructor{cx}; if (!ErrorPrototype::create_class(cx, 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(cx); return JS_DefineFunctionById(cx, prototype, atoms.to_string(), &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS) && gjs_define_enum_values(cx, constructor, info); } [[nodiscard]] static Maybe find_error_domain_info( const GI::Repository& repo, GQuark domain) { // first an attempt without loading extra libraries Maybe info = repo.find_by_error_domain(domain); if (info) return info; // load standard stuff repo.require("GLib", "2.0").unwrap(); repo.require("GObject", "2.0").unwrap(); repo.require("Gio", "2.0").unwrap(); info = repo.find_by_error_domain(domain); if (info) return info; // last attempt: load GIRepository (for invoke errors, rarely needed) repo.require("GIRepository", "3.0").unwrap(); return repo.find_by_error_domain(domain); } // define properties that JS Error exposes, 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* cx, GError* gerror) { if (!gerror) return nullptr; if (gerror->domain == GJS_JS_ERROR) return gjs_error_from_js_gerror(cx, gerror); GI::Repository repo; Maybe info = find_error_domain_info(repo, gerror->domain); if (!info) { // We don't have error domain metadata. Marshal the error as a plain // GError GI::AutoStructInfo glib_boxed{ repo.find_by_name("GLib", "Error").value()}; return StructInstance::new_for_c_struct(cx, glib_boxed, gerror); } gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s with JSObject", info->name()); JS::RootedObject obj{cx, gjs_new_object_with_generic_prototype(cx, *info)}; if (!obj) return nullptr; ErrorInstance* priv = ErrorInstance::new_for_js_object(cx, 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 (StructBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{})) return StructBase::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 (StructBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{})) return true; return GIWrapperBase::typecheck(cx, obj, G_TYPE_ERROR); } bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj, GjsTypecheckNoThrow no_throw) { if (StructBase::typecheck(cx, obj, G_TYPE_ERROR, no_throw)) return true; return GIWrapperBase::typecheck(cx, obj, 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; Gjs::AutoTypeClass 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. 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, Gjs::AutoError 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-140.0/gi/gerror.h0000664000175000017500000001214215167114161013274 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 #pragma once #include #include #include #include #include #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/gerror-result.h" #include "cjs/macros.h" #include "util/log.h" class ErrorPrototype; class ErrorInstance; struct JSFunctionSpec; struct JSPropertySpec; 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; public: // public in order to implement Error.isError() static const struct JSClass klass; protected: static JSPropertySpec proto_properties[]; static JSFunctionSpec static_methods[]; // Accessors public: [[nodiscard]] GQuark domain() const; // Property getters protected: GJS_JSAPI_RETURN_CONVENTION static bool get_domain(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool get_message(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool get_code(JSContext*, unsigned, JS::Value*); // JS methods GJS_JSAPI_RETURN_CONVENTION static bool value_of(JSContext*, unsigned, JS::Value*); public: GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext*, unsigned, JS::Value*); // Helper methods GJS_JSAPI_RETURN_CONVENTION static GError* to_c_ptr(JSContext*, JS::HandleObject); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext*, JS::HandleObject, GIArgument*, GIDirection transfer_direction, GITransfer transfer_ownership); GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext*, JS::HandleObject); [[nodiscard]] static bool typecheck(JSContext*, JS::HandleObject, GjsTypecheckNoThrow); }; class ErrorPrototype : public GIWrapperPrototype { friend class GIWrapperPrototype; friend class GIWrapperBase; GQuark m_domain; explicit ErrorPrototype(const GI::EnumInfo&, GType); ~ErrorPrototype(); GJS_JSAPI_RETURN_CONVENTION static bool get_parent_proto(JSContext*, JS::MutableHandleObject proto); public: [[nodiscard]] GQuark domain() const { return m_domain; } GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext*, JS::HandleObject in_object, const GI::EnumInfo&); }; class ErrorInstance : public GIWrapperInstance { friend class GIWrapperInstance; friend class GIWrapperBase; explicit ErrorInstance(ErrorPrototype*, JS::HandleObject); ~ErrorInstance(); 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() const { return m_ptr->message; } [[nodiscard]] int code() const { return m_ptr->code; } // JS constructor private: GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext*, JS::HandleObject, const JS::CallArgs&); // Public API public: GJS_JSAPI_RETURN_CONVENTION static JSObject* object_for_c_ptr(JSContext*, GError*); }; GJS_JSAPI_RETURN_CONVENTION GError* gjs_gerror_make_from_thrown_value(JSContext*); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_error_properties(JSContext*, JS::HandleObject); bool gjs_throw_gerror(JSContext*, Gjs::AutoError const&); cjs-140.0/gi/gjs_gi_probes.d0000664000175000017500000000037015167114161014604 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-140.0/gi/gjs_gi_trace.h0000664000175000017500000000100415167114161014407 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 #pragma once #include #ifdef HAVE_DTRACE // include the generated probes header and put markers in code #include "gjs_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 cjs-140.0/gi/gobject.cpp0000664000175000017500000002667515167114161013764 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/auto.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; Gjs::AutoChar underscore_name{gjs_hyphen_to_underscore(pspec->name)}; if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) { unsigned flags = GJS_MODULE_PROP_FLAGS | JSPROP_READONLY; Gjs::AutoChar 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 || !priv->wrapper()) { 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 || !priv->wrapper()) { 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); Gjs::AutoChar 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 (Gjs::AutoParam& 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 (Gjs::AutoParam& 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-140.0/gi/gobject.h0000664000175000017500000000110715167114161013410 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 #pragma once #include #include #include #include "cjs/auto.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, AutoParamArray* params); bool pop_class_init_properties(GType, AutoParamArray* params_out); cjs-140.0/gi/gtype.cpp0000664000175000017500000001572115167114161013465 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/auto.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util-root.h" // for WeakPtr methods #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 GjsGlobalSlot 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; Gjs::AutoChar strval{ g_strdup_printf("[object GType for '%s']", g_type_name(gtype))}; return gjs_string_from_utf8(cx, strval, rec.rval()); } static constexpr JSPropertySpec proto_props[] = { JS_PSG("name", >ypeObj::get_name, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryGType", JSPROP_READONLY), JS_PS_END}; // clang-format off 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* cx, GType gtype) { return GTypeObj::create(cx, gtype); } bool gjs_gtype_get_actual_gtype(JSContext* cx, JS::HandleObject object, GType* gtype_out) { return GTypeObj::actual_gtype(cx, object, gtype_out); } cjs-140.0/gi/gtype.h0000664000175000017500000000101115167114161013115 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. #pragma once #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_gtype_create_gtype_wrapper(JSContext*, GType); GJS_JSAPI_RETURN_CONVENTION bool gjs_gtype_get_actual_gtype(JSContext*, JS::HandleObject, GType* gtype_out); cjs-140.0/gi/info.h0000664000175000017500000016705415167114161012744 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Philip Chimento #pragma once #include #include // for INT_MAX #include #include #include // for nullptr_t #include #include // for pair, make_pair, move #if GJS_VERBOSE_ENABLE_GI_USAGE # include # include #endif #include #include #include #include #include #include // for IgnoreGCPolicy #include #include #include #include "cjs/auto.h" #include "cjs/gerror-result.h" #include "util/log.h" // This file is a C++ wrapper for libgirepository that attempts to be more // null-safe and type-safe. // Each introspection info type has the methods of the C API's GIFooInfo, but // indicates whether the return value is owned by the caller (GI::AutoFooInfo) // or unowned (GI::FooInfo), and uses Maybe to indicate when it is nullable. // There are also GI::StackArgInfo and GI::StackTypeInfo for use with the // CallableInfo.load_arg(), CallableInfo.load_return_type(), and // ArgInfo.load_type() methods, for performance. // COMPAT: We use Mozilla's Maybe, Result, and Span types because they are more // complete than the C++ standard library types. // std::optional does not have transform(), and_then(), etc., until C++23. // std::expected does not appear until C++23. // std::span does not appear until C++20. // Note, only the methods actually needed in GJS are wrapped here. So if one is // missing, that's not for any particular reason unless noted otherwise; it just // was never needed yet. using BoolResult = mozilla::Result; namespace GI { enum class InfoTag : uint8_t { ARG, BASE, CALLABLE, CALLBACK, CONSTANT, ENUM, FIELD, FLAGS, FUNCTION, INTERFACE, OBJECT, PROPERTY, REGISTERED_TYPE, SIGNAL, STRUCT, TYPE, UNION, VALUE, VFUNC, }; namespace detail { template struct InfoTraits {}; template <> struct InfoTraits { using CStruct = GIArgInfo; }; template <> struct InfoTraits { using CStruct = GIBaseInfo; }; template <> struct InfoTraits { using CStruct = GICallableInfo; }; template <> struct InfoTraits { using CStruct = GICallbackInfo; }; template <> struct InfoTraits { using CStruct = GIConstantInfo; }; template <> struct InfoTraits { using CStruct = GIEnumInfo; }; template <> struct InfoTraits { using CStruct = GIFieldInfo; }; template <> struct InfoTraits { using CStruct = GIFlagsInfo; }; template <> struct InfoTraits { using CStruct = GIFunctionInfo; }; template <> struct InfoTraits { using CStruct = GIInterfaceInfo; }; template <> struct InfoTraits { using CStruct = GIObjectInfo; }; template <> struct InfoTraits { using CStruct = GIPropertyInfo; }; template <> struct InfoTraits { using CStruct = GIRegisteredTypeInfo; }; template <> struct InfoTraits { using CStruct = GISignalInfo; }; template <> struct InfoTraits { using CStruct = GIStructInfo; }; template <> struct InfoTraits { using CStruct = GITypeInfo; }; template <> struct InfoTraits { using CStruct = GIUnionInfo; }; template <> struct InfoTraits { using CStruct = GIValueInfo; }; template <> struct InfoTraits { using CStruct = GIVFuncInfo; }; using GTypeFunc = GType (*)(); static constexpr const GTypeFunc gtype_funcs[] = { gi_arg_info_get_type, gi_base_info_get_type, gi_callable_info_get_type, gi_callback_info_get_type, gi_constant_info_get_type, gi_enum_info_get_type, gi_field_info_get_type, gi_flags_info_get_type, gi_function_info_get_type, gi_interface_info_get_type, gi_object_info_get_type, gi_property_info_get_type, gi_registered_type_info_get_type, gi_signal_info_get_type, gi_struct_info_get_type, gi_type_info_get_type, gi_union_info_get_type, gi_value_info_get_type, gi_vfunc_info_get_type, }; constexpr GTypeFunc gtype_func(InfoTag tag) { return gtype_funcs[size_t(tag)]; } } // namespace detail template class InfoOperations {}; class StackArgInfo; class StackTypeInfo; template class OwnedInfo; template class UnownedInfo; namespace detail { // We want the underlying pointer to be inaccessible. However, the three storage // classes sometimes have to interact with each others' pointers. It's easier to // put all of those operations into detail::Pointer and have the classes be // friends of it, than it is to expose all the pointer operations via friend // declarations individually. struct Pointer { template using CStruct = typename InfoTraits::CStruct; template [[nodiscard]] static constexpr typename detail::InfoTraits::CStruct* cast(GIBaseInfo* ptr) { // (the following is a GI_TAG_INFO() cast but written out) return reinterpret_cast::CStruct*>( g_type_check_instance_cast(reinterpret_cast(ptr), gtype_func(TAG)())); } template static constexpr CStruct* get_from(const OwnedInfo& owned) { return const_cast*>(owned.m_info); } template static constexpr CStruct* get_from(const UnownedInfo& unowned) { return const_cast*>(unowned.m_info); } // Defined out-of-line because they are not templates and so StackArgInfo // and StackTypeInfo need to be complete types. static constexpr GIArgInfo* get_from(const StackArgInfo& stack); static constexpr GITypeInfo* get_from(const StackTypeInfo& stack); template static constexpr OwnedInfo to_owned(CStruct* ptr) { return OwnedInfo{ptr}; } template static constexpr UnownedInfo to_unowned(CStruct* ptr) { return UnownedInfo{ptr}; } // Same, defined out of line so StackTypeInfo is not incomplete. static void to_stack(GITypeInfo* ptr, StackTypeInfo* stack); template static constexpr mozilla::Maybe> nullable( CStruct* ptr) { return ptr ? mozilla::Some(OwnedInfo{ptr}) : mozilla::Nothing{}; } template static constexpr mozilla::Maybe> nullable_unowned( CStruct* ptr) { return ptr ? mozilla::Some(UnownedInfo{ptr}) : mozilla::Nothing{}; } template [[nodiscard]] static constexpr bool typecheck(GIBaseInfo* ptr) { return G_TYPE_CHECK_INSTANCE_TYPE(ptr, gtype_func(TAG)()); } }; } // namespace detail ///// UNOWNED INTROSPECTION INFO /////////////////////////////////////////////// template class UnownedInfo : public InfoOperations, TAG> { friend struct detail::Pointer; using CStruct = typename detail::InfoTraits::CStruct; CStruct* m_info; explicit UnownedInfo(CStruct* info) : m_info(info) { validate(); } [[nodiscard]] CStruct* ptr() const { return m_info; } void validate() const { static_assert(sizeof(CStruct*) == sizeof(UnownedInfo), "UnownedInfo should be byte-compatible with T*"); #ifndef G_DISABLE_CAST_CHECKS g_assert(m_info && "Info pointer cannot be null"); g_assert(detail::Pointer::typecheck(GI_BASE_INFO(m_info)) && "Info type must match"); #endif // G_DISABLE_CAST_CHECKS } public: UnownedInfo() = delete; UnownedInfo(std::nullptr_t) = delete; // NOLINT(runtime/explicit) // https://github.com/cpplint/cpplint/issues/386 // No need to delete move constructor; declaring a copy constructor prevents // it from being generated. // Copying is cheap, UnownedInfo just consists of a pointer. constexpr UnownedInfo(const UnownedInfo& other) : m_info(other.m_info) {} // False positive when the class is a template class. In the case of self- // assignment m_info and other.m_info are already the same pointer. // See https://github.com/llvm/llvm-project/issues/53313 // NOLINTNEXTLINE(bugprone-unhandled-self-assignment) UnownedInfo& operator=(const UnownedInfo& other) { m_info = other.m_info; return *this; } // Caller must take care that the lifetime of UnownedInfo does not exceed // the lifetime of the StackInfo. Do not store the UnownedInfo, or try to // take ownership. UnownedInfo(const StackArgInfo& other) // NOLINT(runtime/explicit) : UnownedInfo(detail::Pointer::get_from(other)) { static_assert(TAG == InfoTag::ARG); } UnownedInfo(const StackTypeInfo& other) // NOLINT(runtime/explicit) : UnownedInfo(detail::Pointer::get_from(other)) { static_assert(TAG == InfoTag::TYPE); } // Caller must take care that the lifetime of UnownedInfo does not exceed // the lifetime of the originating OwnedInfo. That means, if you store it, // only store it as an OwnedInfo, adding another reference. UnownedInfo(const OwnedInfo& other) // NOLINT(runtime/explicit) : UnownedInfo(detail::Pointer::get_from(other)) {} }; using ArgInfo = UnownedInfo; using BaseInfo = UnownedInfo; using CallableInfo = UnownedInfo; using CallbackInfo = UnownedInfo; using ConstantInfo = UnownedInfo; using EnumInfo = UnownedInfo; using FieldInfo = UnownedInfo; using FlagsInfo = UnownedInfo; using FunctionInfo = UnownedInfo; using InterfaceInfo = UnownedInfo; using ObjectInfo = UnownedInfo; using RegisteredTypeInfo = UnownedInfo; using StructInfo = UnownedInfo; using TypeInfo = UnownedInfo; using UnionInfo = UnownedInfo; using ValueInfo = UnownedInfo; using VFuncInfo = UnownedInfo; ///// OWNED INTROSPECTION INFO ///////////////////////////////////////////////// template class OwnedInfo : public InfoOperations, TAG> { friend struct detail::Pointer; using CStruct = typename detail::InfoTraits::CStruct; CStruct* m_info; explicit OwnedInfo(CStruct* info) : m_info(info) { static_assert(sizeof(CStruct*) == sizeof(OwnedInfo), "OwnedInfo should be byte-compatible with T*"); #ifndef G_DISABLE_CAST_CHECKS g_assert(m_info && "Info pointer cannot be null"); g_assert(detail::Pointer::typecheck(GI_BASE_INFO(m_info)) && "Info type must match"); #endif // G_DISABLE_CAST_CHECKS } [[nodiscard]] CStruct* ptr() const { return m_info; } public: OwnedInfo() = delete; OwnedInfo(std::nullptr_t) = delete; // NOLINT(runtime/explicit) // https://github.com/cpplint/cpplint/issues/386 // Copy OwnedInfo from another OwnedInfo. Explicit because it takes a // reference. explicit OwnedInfo(const OwnedInfo& other) : OwnedInfo(other.m_info) { gi_base_info_ref(m_info); } // Move another OwnedInfo into this one OwnedInfo(OwnedInfo&& other) : OwnedInfo(other.m_info) { other.m_info = nullptr; } OwnedInfo& operator=(const OwnedInfo& other) { if (this == &other) return *this; m_info = other.m_info; gi_base_info_ref(m_info); return *this; } OwnedInfo& operator=(OwnedInfo&& other) { std::swap(m_info, other.m_info); return *this; } ~OwnedInfo() { g_clear_pointer(&m_info, gi_base_info_unref); } // Copy OwnedInfo from UnownedInfo, which also comes down to just taking a // reference. Explicit because it takes a reference. However, make sure the // UnownedInfo is not borrowed from a StackInfo! explicit OwnedInfo(const UnownedInfo& other) : OwnedInfo(detail::Pointer::get_from(other)) { gi_base_info_ref(m_info); } // Do not try to take ownership of a StackInfo. // (cpplint false positive: https://github.com/cpplint/cpplint/issues/386) OwnedInfo(const StackArgInfo& other) = delete; // NOLINT(runtime/explicit) OwnedInfo(const StackTypeInfo& other) = delete; // NOLINT(runtime/explicit) }; using AutoArgInfo = OwnedInfo; using AutoBaseInfo = OwnedInfo; using AutoCallableInfo = OwnedInfo; using AutoCallbackInfo = OwnedInfo; using AutoEnumInfo = OwnedInfo; using AutoFieldInfo = OwnedInfo; using AutoFunctionInfo = OwnedInfo; using AutoInterfaceInfo = OwnedInfo; using AutoObjectInfo = OwnedInfo; using AutoPropertyInfo = OwnedInfo; using AutoRegisteredTypeInfo = OwnedInfo; using AutoSignalInfo = OwnedInfo; using AutoStructInfo = OwnedInfo; using AutoTypeInfo = OwnedInfo; using AutoUnionInfo = OwnedInfo; using AutoValueInfo = OwnedInfo; using AutoVFuncInfo = OwnedInfo; // The various specializations of InfoOperations are used to ensure that the // OwnedInfo and UnownedInfo specializations for a particular GIFooInfo type // (and the stack-allocated class, if applicable) have the same methods. So, for // example, AutoTypeInfo, TypeInfo, and StackTypeInfo all inherit from // InfoOperations. template class InfoOperations { protected: [[nodiscard]] GIBaseInfo* ptr() const { return GI_BASE_INFO( detail::Pointer::get_from(*static_cast(this))); } // Helper for adapting GLib-style error reporting into GErrorResult [[nodiscard]] static Gjs::GErrorResult<> bool_gerror(bool ok, GError* error) { if (!ok) return mozilla::Err(error); return mozilla::Ok{}; } // Helper for adapting C-style success/failure result into mozilla::Result. // Used when there is no GError out parameter. [[nodiscard]] static BoolResult bool_to_result(bool ok) { if (!ok) return Err(mozilla::Nothing{}); return mozilla::Ok{}; } public: template bool operator==(const OwnedInfo& other) const { return gi_base_info_equal( ptr(), GI_BASE_INFO(detail::Pointer::get_from(other))); } template bool operator==(const UnownedInfo& other) const { return gi_base_info_equal( ptr(), GI_BASE_INFO(detail::Pointer::get_from(other))); } template bool operator!=(const OwnedInfo& other) const { return !(*this == other); } template bool operator!=(const UnownedInfo& other) const { return !(*this == other); } template [[nodiscard]] mozilla::Maybe> container() const { return detail::Pointer::nullable_unowned( detail::Pointer::cast(gi_base_info_get_container(ptr()))); } [[nodiscard]] bool is_deprecated() const { return gi_base_info_is_deprecated(ptr()); } [[nodiscard]] const char* name() const { return gi_base_info_get_name(ptr()); } [[nodiscard]] const char* ns() const { return gi_base_info_get_namespace(ptr()); } [[nodiscard]] const char* type_string() const { return g_type_name_from_instance( reinterpret_cast(ptr())); } // Type-checking methods [[nodiscard]] bool is_callback() const { return GI_IS_CALLBACK_INFO(ptr()); } [[nodiscard]] bool is_enum_or_flags() const { return GI_IS_ENUM_INFO(ptr()); } [[nodiscard]] bool is_flags() const { return GI_IS_FLAGS_INFO(ptr()); } [[nodiscard]] bool is_function() const { return GI_IS_FUNCTION_INFO(ptr()); } [[nodiscard]] bool is_interface() const { return GI_IS_INTERFACE_INFO(ptr()); } [[nodiscard]] bool is_object() const { return GI_IS_OBJECT_INFO(ptr()); } [[nodiscard]] bool is_registered_type() const { return GI_IS_REGISTERED_TYPE_INFO(ptr()); } [[nodiscard]] bool is_struct() const { return GI_IS_STRUCT_INFO(ptr()); } [[nodiscard]] bool is_union() const { return GI_IS_UNION_INFO(ptr()); } [[nodiscard]] bool is_unresolved() const { // We don't have a wrapper for GIUnresolvedInfo because it has no // methods, but you can check whether a BaseInfo is one. return GI_IS_UNRESOLVED_INFO(ptr()); } [[nodiscard]] bool is_vfunc() const { return GI_IS_VFUNC_INFO(ptr()); } // Don't enumerate types which GJS doesn't define on namespaces. // See gjs_define_info(). [[nodiscard]] bool is_enumerable() const { return GI_IS_REGISTERED_TYPE_INFO(ptr()) || GI_IS_FUNCTION_INFO(ptr()) || GI_IS_CONSTANT_INFO(ptr()); } // Having this casting function be a template is slightly inconsistent with // all the is_X() type-checking methods above. But if we were to make // separate as_X() methods, C++ can't easily deal with all the forward decls // of UnownedInfo instantiating the template. template [[nodiscard]] mozilla::Maybe> as() const { if (!detail::Pointer::typecheck(ptr())) return {}; auto* checked_ptr = detail::Pointer::cast(ptr()); return mozilla::Some(detail::Pointer::to_unowned(checked_ptr)); } void log_usage() const { #if GJS_VERBOSE_ENABLE_GI_USAGE mozilla::Maybe parent = container(); gjs_debug_gi_usage( "{ GIInfoType %s, \"%s\", \"%s\", \"%s\" }", type_string(), ns(), parent.map(std::mem_fn(&GI::BaseInfo::name)).valueOr(""), name()); #endif // GJS_VERBOSE_ENABLE_GI_USAGE } }; template using BaseInfoOperations = InfoOperations; // The following InfoIterator class is a C++ iterator implementation that's used // to implement the C iteration pattern: // // unsigned n_bars = gi_foo_info_get_n_bars(info); // for (unsigned ix = 0; ix < n_bars; ix++) { // GIBarInfo* bar = gi_foo_info_get_bar(info, ix); // do_stuff(bar); // gi_base_info_unref(bar); // } // // as a more idiomatic C++ pattern: // // for (AutoBarInfo bar : info.bars()) // do_stuff(bar); template using NInfosFunc = unsigned (*)(T); template using GetInfoFunc = typename detail::InfoTraits::CStruct* (*)(T, unsigned); template get_n_infos, GetInfoFunc get_info> class InfoIterator { T m_obj; int m_ix; InfoIterator(T obj, int ix) : m_obj(obj), m_ix(ix) {} public: using iterator_category = std::forward_iterator_tag; using difference_type = int; using value_type = OwnedInfo; using pointer = value_type*; using reference = value_type&; explicit InfoIterator(T info) : InfoIterator(info, 0) {} OwnedInfo operator*() const { return detail::Pointer::to_owned(get_info(m_obj, m_ix)); } InfoIterator& operator++() { m_ix++; return *this; } InfoIterator operator++(int) { InfoIterator tmp = *this; m_ix++; return tmp; } bool operator==(const InfoIterator& other) const { return m_obj == other.m_obj && m_ix == other.m_ix; } bool operator!=(const InfoIterator& other) const { return m_obj != other.m_obj || m_ix != other.m_ix; } [[nodiscard]] mozilla::Maybe> operator[](size_t ix) const { return detail::Pointer::nullable(get_info(m_obj, ix)); } [[nodiscard]] InfoIterator begin() const { return InfoIterator{m_obj, 0}; } [[nodiscard]] InfoIterator end() const { int n_fields = get_n_infos(m_obj); return InfoIterator{m_obj, n_fields}; } [[nodiscard]] size_t size() const { return get_n_infos(m_obj); } }; // These are used to delete the type-checking and casting methods from // InfoOperations specializations for subtypes of GIBaseInfo, as appropriate. // So, for example, if you have AutoCallableInfo, you still want to be able to // check is_callback, is_function, and is_vfunc, but not is_boxed etc. #define DELETE_CALLABLE_TYPECHECK_METHODS \ bool is_callback() const = delete; \ bool is_function() const = delete; \ bool is_vfunc() const = delete; #define DELETE_REGISTERED_TYPE_TYPECHECK_METHODS \ bool is_boxed() const = delete; \ bool is_enum_or_flags() const = delete; \ bool is_flags() const = delete; \ bool is_interface() const = delete; \ bool is_object() const = delete; \ bool is_struct() const = delete; \ bool is_union() const = delete; #define DELETE_SUPERCLASS_TYPECHECK_METHODS \ bool is_registered_type() const = delete; \ bool is_unresolved() const = delete; #define DELETE_CAST_METHOD \ template \ mozilla::Maybe> as() const = delete; #define DELETE_ALL_TYPECHECK_METHODS \ DELETE_SUPERCLASS_TYPECHECK_METHODS \ DELETE_CALLABLE_TYPECHECK_METHODS \ DELETE_REGISTERED_TYPE_TYPECHECK_METHODS \ DELETE_CAST_METHOD // Needs to come first, because InfoOperations and InfoOperations // instantiate the template by having methods with GI::StackTypeInfo* parameters template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GITypeInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } // Private, because we don't use this directly. Use the more semantic // versions below (element_type() for GSLIST, GLIST, and ARRAY type tags; // key_type() and value_type() for GHASH.) [[nodiscard]] AutoTypeInfo param_type(int n) const { return detail::Pointer::to_owned( gi_type_info_get_param_type(ptr(), n)); } public: [[nodiscard]] mozilla::Maybe array_length_index() const { unsigned out; if (!gi_type_info_get_array_length_index(ptr(), &out)) return {}; return mozilla::Some(out); } [[nodiscard]] mozilla::Maybe array_fixed_size() const { size_t out; if (!gi_type_info_get_array_fixed_size(ptr(), &out)) return {}; return mozilla::Some(out); } [[nodiscard]] GIArrayType array_type() const { return gi_type_info_get_array_type(ptr()); } void argument_from_hash_pointer(void* hash_pointer, GIArgument* arg) const { gi_type_info_argument_from_hash_pointer(ptr(), hash_pointer, arg); } [[nodiscard]] void* hash_pointer_from_argument(GIArgument* arg) const { return gi_type_info_hash_pointer_from_argument(ptr(), arg); } // Unlike the libgirepository API, this doesn't return null. Only call it on // TypeInfo with GI_TYPE_TAG_INTERFACE tag. [[nodiscard]] AutoBaseInfo interface() const { g_assert(tag() == GI_TYPE_TAG_INTERFACE); return detail::Pointer::to_owned( gi_type_info_get_interface(ptr())); } [[nodiscard]] bool is_pointer() const { return gi_type_info_is_pointer(ptr()); } [[nodiscard]] bool is_zero_terminated() const { return gi_type_info_is_zero_terminated(ptr()); } [[nodiscard]] GITypeTag storage_type() const { return gi_type_info_get_storage_type(ptr()); } [[nodiscard]] GITypeTag tag() const { return gi_type_info_get_tag(ptr()); } void extract_ffi_return_value(GIFFIReturnValue* ffi_value, GIArgument* arg) const { gi_type_info_extract_ffi_return_value(ptr(), ffi_value, arg); } // Methods not present in GIRepository [[nodiscard]] bool can_be_allocated_directly() const; [[nodiscard]] bool direct_allocation_has_pointers() const; [[nodiscard]] const char* display_string() const { GITypeTag type_tag = tag(); if (type_tag == GI_TYPE_TAG_INTERFACE) return interface().type_string(); return gi_type_tag_to_string(type_tag); } [[nodiscard]] bool is_string_type() const { GITypeTag t = tag(); return t == GI_TYPE_TAG_FILENAME || t == GI_TYPE_TAG_UTF8; } [[nodiscard]] bool is_basic() const { GITypeTag t = tag(); if (t == GI_TYPE_TAG_VOID && is_pointer()) return false; // void* is not a basic type return GI_TYPE_TAG_IS_BASIC(t); } // More semantic versions of param_type(), that are only intended to be // called on TypeInfos where the result is known not to be null [[nodiscard]] AutoTypeInfo element_type() const { g_assert(tag() == GI_TYPE_TAG_ARRAY || tag() == GI_TYPE_TAG_GLIST || tag() == GI_TYPE_TAG_GSLIST); return param_type(0); } [[nodiscard]] AutoTypeInfo key_type() const { g_assert(tag() == GI_TYPE_TAG_GHASH); return param_type(0); } [[nodiscard]] AutoTypeInfo value_type() const { g_assert(tag() == GI_TYPE_TAG_GHASH); return param_type(1); } }; // Needs to come after InfoOperations but before InfoOperations // since this class instantiates the GI::StackTypeInfo template, but // InfoOperations instantiates this one. template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIArgInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: [[nodiscard]] bool caller_allocates() const { return gi_arg_info_is_caller_allocates(ptr()); } [[nodiscard]] mozilla::Maybe closure_index() const { unsigned out; if (!gi_arg_info_get_closure_index(ptr(), &out)) return {}; return mozilla::Some(out); } [[nodiscard]] mozilla::Maybe destroy_index() const { unsigned out; if (!gi_arg_info_get_destroy_index(ptr(), &out)) return {}; return mozilla::Some(out); } [[nodiscard]] GIDirection direction() const { return gi_arg_info_get_direction(ptr()); } void load_type(StackTypeInfo* type) const { gi_arg_info_load_type_info(ptr(), detail::Pointer::get_from(*type)); } [[nodiscard]] bool is_optional() const { return gi_arg_info_is_optional(ptr()); } [[nodiscard]] bool is_return_value() const { return gi_arg_info_is_return_value(ptr()); } [[nodiscard]] bool may_be_null() const { return gi_arg_info_may_be_null(ptr()); } [[nodiscard]] GITransfer ownership_transfer() const { return gi_arg_info_get_ownership_transfer(ptr()); } [[nodiscard]] GIScopeType scope() const { return gi_arg_info_get_scope(ptr()); } }; template class InfoOperations : public BaseInfoOperations { DELETE_SUPERCLASS_TYPECHECK_METHODS; DELETE_REGISTERED_TYPE_TYPECHECK_METHODS; [[nodiscard]] GICallableInfo* ptr() const { return GI_CALLABLE_INFO( detail::Pointer::get_from(*static_cast(this))); } public: using ArgsIterator = InfoIterator; [[nodiscard]] ArgsIterator args() const { return ArgsIterator{ptr()}; } [[nodiscard]] AutoArgInfo arg(unsigned n) const { g_assert(n < n_args()); return detail::Pointer::to_owned( gi_callable_info_get_arg(ptr(), n)); } [[nodiscard]] unsigned n_args() const { return gi_callable_info_get_n_args(ptr()); } [[nodiscard]] GITransfer caller_owns() const { return gi_callable_info_get_caller_owns(ptr()); } [[nodiscard]] bool can_throw_gerror() const { return gi_callable_info_can_throw_gerror(ptr()); } [[nodiscard]] void* closure_native_address(ffi_closure* closure) const { return gi_callable_info_get_closure_native_address(ptr(), closure); } [[nodiscard]] ffi_closure* create_closure(ffi_cif* cif, GIFFIClosureCallback callback, void* user_data) const { return gi_callable_info_create_closure(ptr(), cif, callback, user_data); } void destroy_closure(ffi_closure* closure) const { gi_callable_info_destroy_closure(ptr(), closure); } [[nodiscard]] Gjs::GErrorResult<> init_function_invoker( void* address, GIFunctionInvoker* invoker) const { GError* error = nullptr; return this->bool_gerror(gi_function_invoker_new_for_address( address, ptr(), invoker, &error), error); } [[nodiscard]] GITransfer instance_ownership_transfer() const { return gi_callable_info_get_instance_ownership_transfer(ptr()); } [[nodiscard]] bool is_method() const { return gi_callable_info_is_method(ptr()); } void load_arg(unsigned n, StackArgInfo* arg) const { g_assert(n < n_args()); gi_callable_info_load_arg(ptr(), n, detail::Pointer::get_from(*arg)); } void load_return_type(StackTypeInfo* type) const { gi_callable_info_load_return_type(ptr(), detail::Pointer::get_from(*type)); } [[nodiscard]] bool may_return_null() const { return gi_callable_info_may_return_null(ptr()); } [[nodiscard]] bool skip_return() const { return gi_callable_info_skip_return(ptr()); } // Methods not in GIRepository void log_usage() { #if GJS_VERBOSE_ENABLE_GI_USAGE std::ostringstream out; # 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") out << ".details = { .func = { .retval_transfer = GI_TRANSFER_" << TRANSFER_STRING(caller_owns()) << ", .n_args = " << n_args() << ", .args = { "; ArgsIterator iter = args(); std::for_each(iter.begin(), iter.end(), [&out](AutoArgInfo arg_info) { out << "{ GI_DIRECTION_" << DIRECTION_STRING(arg_info.direction()) << ", GI_TRANSFER_" << TRANSFER_STRING(arg_info.ownership_transfer()) << " }, "; }); out.seekp(-2, std::ios_base::end); // Erase trailing comma # undef DIRECTION_STRING # undef TRANSFER_STRING out << " } } }"; std::string details{out.str()}; using Base = BaseInfoOperations; mozilla::Maybe parent = Base::container(); gjs_debug_gi_usage( "{ GIInfoType %s, \"%s\", \"%s\", \"%s\", %s }", Base::type_string(), Base::ns(), parent.map(std::mem_fn(&GI::BaseInfo::name)).valueOr(""), Base::name(), details.c_str()); #endif // GJS_VERBOSE_ENABLE_GI_USAGE } }; template using CallableInfoOperations = InfoOperations; template class InfoOperations : public BaseInfoOperations { DELETE_SUPERCLASS_TYPECHECK_METHODS; DELETE_CALLABLE_TYPECHECK_METHODS; [[nodiscard]] GIRegisteredTypeInfo* ptr() const { return GI_REGISTERED_TYPE_INFO( detail::Pointer::get_from(*static_cast(this))); } public: [[nodiscard]] GType gtype() const { return gi_registered_type_info_get_g_type(ptr()); } // Methods not in GIRepository [[nodiscard]] bool is_gdk_atom() const { return strcmp("Atom", this->name()) == 0 && strcmp("Gdk", this->ns()) == 0; } [[nodiscard]] bool is_g_value() const { return g_type_is_a(gtype(), G_TYPE_VALUE); } operator BaseInfo() const { return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); } }; template using RegisteredTypeInfoOperations = InfoOperations; template class InfoOperations : public CallableInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GICallbackInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: operator BaseInfo() const { return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); } operator CallableInfo() const { return detail::Pointer::to_unowned( GI_CALLABLE_INFO(ptr())); } }; template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIConstantInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: void free_value(GIArgument* arg) const { gi_constant_info_free_value(ptr(), arg); } int load_value(GIArgument* arg) const { return gi_constant_info_get_value(ptr(), arg); } [[nodiscard]] AutoTypeInfo type_info() const { return detail::Pointer::to_owned( gi_constant_info_get_type_info(ptr())); } }; // Must come before any use of MethodsIterator template class InfoOperations : public CallableInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIFunctionInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } [[nodiscard]] GIFunctionInfoFlags flags() const { return gi_function_info_get_flags(ptr()); } public: [[nodiscard]] Gjs::GErrorResult<> invoke(const mozilla::Span& in_args, const mozilla::Span& out_args, GIArgument* return_value) const { g_assert(in_args.size() <= INT_MAX); g_assert(out_args.size() <= INT_MAX); GError* error = nullptr; return this->bool_gerror( gi_function_info_invoke(ptr(), in_args.data(), in_args.size(), out_args.data(), out_args.size(), return_value, &error), error); } [[nodiscard]] Gjs::GErrorResult<> prep_invoker(GIFunctionInvoker* invoker) const { GError* error = nullptr; return this->bool_gerror( gi_function_info_prep_invoker(ptr(), invoker, &error), error); } [[nodiscard]] const char* symbol() const { return gi_function_info_get_symbol(ptr()); } // Has to be defined later because there's a chicken-and-egg loop between // AutoPropertyInfo and AutoFunctionInfo [[nodiscard]] mozilla::Maybe property() const; // Methods not in GIRepository [[nodiscard]] bool is_method() const { return flags() & GI_FUNCTION_IS_METHOD; } [[nodiscard]] bool is_constructor() const { return flags() & GI_FUNCTION_IS_CONSTRUCTOR; } operator CallableInfo() const { return detail::Pointer::to_unowned( GI_CALLABLE_INFO(ptr())); } }; template class InfoOperations : public RegisteredTypeInfoOperations { DELETE_REGISTERED_TYPE_TYPECHECK_METHODS; [[nodiscard]] GIEnumInfo* ptr() const { return GI_ENUM_INFO( detail::Pointer::get_from(*static_cast(this))); } public: using ValuesIterator = InfoIterator; [[nodiscard]] ValuesIterator values() const { return ValuesIterator{ptr()}; } using MethodsIterator = InfoIterator; [[nodiscard]] MethodsIterator methods() const { return MethodsIterator{ptr()}; } [[nodiscard]] mozilla::Maybe method(const char* name) const { return detail::Pointer::nullable( gi_enum_info_find_method(ptr(), name)); } [[nodiscard]] const char* error_domain() const { return gi_enum_info_get_error_domain(ptr()); } [[nodiscard]] GITypeTag storage_type() const { return gi_enum_info_get_storage_type(ptr()); } // Methods not in GIRepository [[nodiscard]] bool uses_signed_type() const { switch (storage_type()) { 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 - gi_function_info_invoke() and // gi_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 // gi_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 enum_from_int(int int_value) const { if (uses_signed_type()) return int64_t{int_value}; return int64_t{static_cast(int_value)}; } // Here for symmetry, but result is the same for the two cases [[nodiscard]] int enum_to_int(int64_t value) const { return static_cast(value); } }; template using EnumInfoOperations = InfoOperations; template class InfoOperations : public EnumInfoOperations { DELETE_ALL_TYPECHECK_METHODS; }; template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIFieldInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } // Use the various is_FLAG() methods instead. [[nodiscard]] GIFieldInfoFlags flags() const { return gi_field_info_get_flags(ptr()); } public: [[nodiscard]] size_t offset() const { return gi_field_info_get_offset(ptr()); } [[nodiscard]] BoolResult read(void* blob, GIArgument* value_out) const { return this->bool_to_result( gi_field_info_get_field(ptr(), blob, value_out)); } [[nodiscard]] AutoTypeInfo type_info() const { return detail::Pointer::to_owned( gi_field_info_get_type_info(ptr())); } [[nodiscard]] BoolResult write(void* blob, const GIArgument* value) const { return this->bool_to_result( gi_field_info_set_field(ptr(), blob, value)); } // Methods not in GIRepository [[nodiscard]] bool is_readable() const { return flags() & GI_FIELD_IS_READABLE; } [[nodiscard]] bool is_writable() const { return flags() & GI_FIELD_IS_WRITABLE; } }; template class InfoOperations : public CallableInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GISignalInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } }; template class InfoOperations : public RegisteredTypeInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIStructInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: using FieldsIterator = InfoIterator; [[nodiscard]] FieldsIterator fields() const { return FieldsIterator{ptr()}; } using MethodsIterator = InfoIterator; [[nodiscard]] MethodsIterator methods() const { return MethodsIterator{ptr()}; } [[nodiscard]] mozilla::Maybe method(const char* name) const { return detail::Pointer::nullable( gi_struct_info_find_method(ptr(), name)); } [[nodiscard]] bool is_foreign() const { return gi_struct_info_is_foreign(ptr()); } [[nodiscard]] bool is_gtype_struct() const { return gi_struct_info_is_gtype_struct(ptr()); } [[nodiscard]] size_t size() const { return gi_struct_info_get_size(ptr()); } operator BaseInfo() const { return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); } }; template class InfoOperations : public RegisteredTypeInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIUnionInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: using FieldsIterator = InfoIterator; [[nodiscard]] FieldsIterator fields() const { return FieldsIterator{ptr()}; } using MethodsIterator = InfoIterator; [[nodiscard]] MethodsIterator methods() const { return MethodsIterator{ptr()}; } [[nodiscard]] mozilla::Maybe method(const char* name) const { return detail::Pointer::nullable( gi_union_info_find_method(ptr(), name)); } [[nodiscard]] size_t size() const { return gi_union_info_get_size(ptr()); } }; template class InfoOperations : public CallableInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIVFuncInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: [[nodiscard]] Gjs::GErrorResult address(GType implementor_gtype) const { Gjs::AutoError error; // Cannot use GError*, distinguish from void* void* address = gi_vfunc_info_get_address(ptr(), implementor_gtype, error.out()); if (!address) return mozilla::Err(std::move(error)); return address; } [[nodiscard]] operator CallableInfo() const { return detail::Pointer::to_unowned( GI_CALLABLE_INFO(ptr())); } }; template class InfoOperations : public RegisteredTypeInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIInterfaceInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: using MethodsIterator = InfoIterator; [[nodiscard]] MethodsIterator methods() const { return MethodsIterator{ptr()}; } [[nodiscard]] mozilla::Maybe method(const char* name) const { return detail::Pointer::nullable( gi_interface_info_find_method(ptr(), name)); } using PropertiesIterator = InfoIterator; [[nodiscard]] PropertiesIterator properties() const { return PropertiesIterator{ptr()}; } [[nodiscard]] mozilla::Maybe iface_struct() const { return detail::Pointer::nullable( gi_interface_info_get_iface_struct(ptr())); } [[nodiscard]] mozilla::Maybe signal(const char* name) const { return detail::Pointer::nullable( gi_interface_info_find_signal(ptr(), name)); } [[nodiscard]] mozilla::Maybe vfunc(const char* name) const { return detail::Pointer::nullable( gi_interface_info_find_vfunc(ptr(), name)); } operator RegisteredTypeInfo() const { return detail::Pointer::to_unowned( GI_REGISTERED_TYPE_INFO(ptr())); } }; template class InfoOperations : public RegisteredTypeInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIObjectInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: using FieldsIterator = InfoIterator; [[nodiscard]] FieldsIterator fields() const { return FieldsIterator{ptr()}; } using InterfacesIterator = InfoIterator; [[nodiscard]] InterfacesIterator interfaces() const { return InterfacesIterator{ptr()}; } using MethodsIterator = InfoIterator; [[nodiscard]] MethodsIterator methods() const { return MethodsIterator{ptr()}; } [[nodiscard]] mozilla::Maybe method(const char* name) const { return detail::Pointer::nullable( gi_object_info_find_method(ptr(), name)); } using PropertiesIterator = InfoIterator; [[nodiscard]] PropertiesIterator properties() const { return PropertiesIterator{ptr()}; } [[nodiscard]] mozilla::Maybe class_struct() const { return detail::Pointer::nullable( gi_object_info_get_class_struct(ptr())); } [[nodiscard]] mozilla::Maybe> find_method_using_interfaces(const char* name) const { GIBaseInfo* declarer_ptr = nullptr; GIFunctionInfo* method_ptr = gi_object_info_find_method_using_interfaces(ptr(), name, &declarer_ptr); if (!method_ptr) { g_assert(!declarer_ptr); return {}; } AutoFunctionInfo method{ detail::Pointer::to_owned(method_ptr)}; AutoRegisteredTypeInfo declarer{ detail::Pointer::to_owned( GI_REGISTERED_TYPE_INFO(declarer_ptr))}; g_assert(declarer.is_object() || declarer.is_interface()); return mozilla::Some(std::make_pair(method, declarer)); } [[nodiscard]] mozilla::Maybe> find_vfunc_using_interfaces(const char* name) const { GIBaseInfo* declarer_ptr = nullptr; GIVFuncInfo* vfunc_ptr = gi_object_info_find_vfunc_using_interfaces( ptr(), name, &declarer_ptr); if (!vfunc_ptr) { g_assert(!declarer_ptr); return {}; } AutoVFuncInfo vfunc{ detail::Pointer::to_owned(vfunc_ptr)}; AutoRegisteredTypeInfo declarer{ detail::Pointer::to_owned( GI_REGISTERED_TYPE_INFO(declarer_ptr))}; g_assert(declarer.is_object() || declarer.is_interface()); return mozilla::Some(std::make_pair(vfunc, declarer)); } [[nodiscard]] GIObjectInfoGetValueFunction get_value_function_pointer() const { return gi_object_info_get_get_value_function_pointer(ptr()); } [[nodiscard]] mozilla::Maybe parent() const { return detail::Pointer::nullable( gi_object_info_get_parent(ptr())); } [[nodiscard]] GIObjectInfoRefFunction ref_function_pointer() const { return gi_object_info_get_ref_function_pointer(ptr()); } [[nodiscard]] GIObjectInfoSetValueFunction set_value_function_pointer() const { return gi_object_info_get_set_value_function_pointer(ptr()); } [[nodiscard]] mozilla::Maybe signal(const char* name) const { return detail::Pointer::nullable( gi_object_info_find_signal(ptr(), name)); } [[nodiscard]] GIObjectInfoUnrefFunction unref_function_pointer() const { return gi_object_info_get_unref_function_pointer(ptr()); } [[nodiscard]] mozilla::Maybe vfunc(const char* name) const { return detail::Pointer::nullable( gi_object_info_find_vfunc(ptr(), name)); } [[nodiscard]] operator BaseInfo() const { return detail::Pointer::to_unowned(GI_BASE_INFO(ptr())); } }; template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIPropertyInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } [[nodiscard]] GParamFlags flags() const { return gi_property_info_get_flags(ptr()); } public: [[nodiscard]] mozilla::Maybe getter() const { return detail::Pointer::nullable( gi_property_info_get_getter(ptr())); } [[nodiscard]] mozilla::Maybe setter() const { return detail::Pointer::nullable( gi_property_info_get_setter(ptr())); } [[nodiscard]] AutoTypeInfo type_info() const { return detail::Pointer::to_owned( gi_property_info_get_type_info(ptr())); } // Methods not in GIRepository [[nodiscard]] bool has_deprecated_param_flag() const { // Note, different from is_deprecated(). It's possible that the property // has the deprecated GParamSpec flag, but is not marked deprecated in // the GIR doc comment. return flags() & G_PARAM_DEPRECATED; } }; // Out-of-line definition to avoid chicken-and-egg loop between AutoFunctionInfo // and AutoPropertyInfo template inline mozilla::Maybe InfoOperations::property() const { return detail::Pointer::nullable( gi_function_info_get_property(ptr())); } template class InfoOperations : public BaseInfoOperations { DELETE_ALL_TYPECHECK_METHODS; [[nodiscard]] GIValueInfo* ptr() const { return detail::Pointer::get_from(*static_cast(this)); } public: [[nodiscard]] int64_t value() const { return gi_value_info_get_value(ptr()); } }; // In order to avoid having to create an OwnedInfo or UnownedInfo from a pointer // anywhere except in these wrappers, we also wrap GIRepository. // (ArgCache::HasTypeInfo is the one exception.) class Repository { Gjs::AutoUnref m_ptr = gi_repository_dup_default(); // Helper object for iterating the introspection info objects of a // namespace. Unlike the other introspection info iterators, this requires // two parameters, the GIRepository* and the namespace string, so we need // this helper object to adapt InfoIterator. struct IterableNamespace { GIRepository* repo; const char* ns; static unsigned get_n_infos(const IterableNamespace obj) { return gi_repository_get_n_infos(obj.repo, obj.ns); } static GIBaseInfo* get_info(const IterableNamespace obj, unsigned ix) { return gi_repository_get_info(obj.repo, obj.ns, ix); } bool operator==(const IterableNamespace& other) const { return repo == other.repo && strcmp(ns, other.ns) == 0; } bool operator!=(const IterableNamespace& other) const { return !(*this == other); } }; public: using Iterator = InfoIterator; [[nodiscard]] Iterator infos(const char* ns) const { return Iterator{{m_ptr, ns}}; } [[nodiscard]] Gjs::AutoStrv enumerate_versions(const char* ns, size_t* n_versions) const { return gi_repository_enumerate_versions(m_ptr, ns, n_versions); } [[nodiscard]] mozilla::Maybe find_by_error_domain(GQuark domain) const { return detail::Pointer::nullable( gi_repository_find_by_error_domain(m_ptr, domain)); } template [[nodiscard]] mozilla::Maybe> find_by_gtype(GType gtype) const { return detail::Pointer::nullable(detail::Pointer::cast( gi_repository_find_by_gtype(m_ptr, gtype))); } template [[nodiscard]] mozilla::Maybe> find_by_name(const char* ns, const char* name) const { return detail::Pointer::nullable(detail::Pointer::cast( gi_repository_find_by_name(m_ptr, ns, name))); } [[nodiscard]] const char* get_version(const char* ns) const { return gi_repository_get_version(m_ptr, ns); } [[nodiscard]] bool is_registered(const char* ns, const char* version) const { return gi_repository_is_registered(m_ptr, ns, version); } [[nodiscard]] mozilla::Span object_get_gtype_interfaces( GType gtype) const { InterfaceInfo* interfaces; size_t n_interfaces; gi_repository_get_object_gtype_interfaces( m_ptr, gtype, &n_interfaces, reinterpret_cast(&interfaces)); return {interfaces, n_interfaces}; } void prepend_search_path(const char* path) { gi_repository_prepend_search_path(m_ptr, path); } [[nodiscard]] Gjs::GErrorResult require( const char* ns, const char* version, GIRepositoryLoadFlags flags = {}) const { GError* error = nullptr; GITypelib* typelib = gi_repository_require(m_ptr, ns, version, flags, &error); if (!typelib) return mozilla::Err(error); return typelib; } }; ///// STACK-ALLOCATED INTROSPECTION INFO /////////////////////////////////////// // Introspection info allocated directly on the stack. This is used only in a // few cases, for performance reasons. In C, the stack-allocated struct is // filled in by a function such as gi_arg_info_load_type_info(). // Needs to appear at the end, due to FIXME. class StackArgInfo : public InfoOperations { friend struct detail::Pointer; GIArgInfo m_info = {}; [[nodiscard]] constexpr GIArgInfo* ptr() const { return detail::Pointer::get_from(*this); } public: constexpr StackArgInfo() = default; ~StackArgInfo() { gi_base_info_clear(&m_info); } // Moving is okay, we copy the contents of the GIArgInfo struct and reset // the existing one StackArgInfo(StackArgInfo&& other) : m_info(other.m_info) { gi_base_info_clear(&other.m_info); } StackArgInfo& operator=(StackArgInfo&& other) { m_info = other.m_info; gi_base_info_clear(&other.m_info); return *this; } // Prefer moving to copying StackArgInfo(const StackArgInfo&) = delete; StackArgInfo& operator=(const StackArgInfo&) = delete; }; class StackTypeInfo : public InfoOperations { friend struct detail::Pointer; GITypeInfo m_info = {}; [[nodiscard]] constexpr GITypeInfo* ptr() const { return detail::Pointer::get_from(*this); } public: constexpr StackTypeInfo() = default; ~StackTypeInfo() { gi_base_info_clear(&m_info); } // Moving is okay, we copy the contents of the GITypeInfo struct and reset // the existing one StackTypeInfo(StackTypeInfo&& other) : m_info(other.m_info) { gi_base_info_clear(&other.m_info); } StackTypeInfo& operator=(StackTypeInfo&& other) { m_info = other.m_info; gi_base_info_clear(&other.m_info); return *this; } // Prefer moving to copying StackTypeInfo(const StackTypeInfo&) = delete; StackTypeInfo& operator=(const StackTypeInfo&) = delete; }; namespace detail { constexpr GIArgInfo* Pointer::get_from(const StackArgInfo& stack) { return const_cast(&stack.m_info); } constexpr GITypeInfo* Pointer::get_from(const StackTypeInfo& stack) { return const_cast(&stack.m_info); } inline void Pointer::to_stack(GITypeInfo* ptr, StackTypeInfo* stack) { stack->m_info = *ptr; // Hacky: Reproduce gi_info_init() and mark the copied GITypeInfo as // stack-allocated. Unfortunately, GI_TYPE_TYPE_INFO makes this function // unable to be constexpr. GIBaseInfoStack* stack_ptr = &stack->m_info.parent; stack_ptr->parent_instance.g_class = static_cast(g_type_class_ref(GI_TYPE_TYPE_INFO)); stack_ptr->dummy0 = 0x7fff'ffff; } } // namespace detail static_assert(sizeof(StackArgInfo) == sizeof(GIArgInfo), "StackArgInfo should be byte-compatible with GIArgInfo"); static_assert(sizeof(StackTypeInfo) == sizeof(GITypeInfo), "StackTypeInfo should be byte-compatible with GITypeInfo"); } // namespace GI // For use of GI::OwnedInfo in GC hash maps namespace JS { template struct GCPolicy> : public IgnoreGCPolicy> {}; } // namespace JS cjs-140.0/gi/interface.cpp0000664000175000017500000001310315167114161014265 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 // for JS_ReportOutOfMemory #include // for MutableHandleIdVector #include // for PropertyKey, jsid #include // for JSFunctionSpec, JS_FS_END #include #include // for UniqueChars #include "gi/function.h" #include "gi/info.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" using mozilla::Maybe, mozilla::Nothing; InterfacePrototype::InterfacePrototype( const Maybe& info, GType gtype) : GIWrapperPrototype(info, gtype), m_vtable( static_cast(g_type_default_interface_ref(gtype))) { GJS_INC_COUNTER(interface); } InterfacePrototype::~InterfacePrototype() { 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; GI::InterfaceInfo::MethodsIterator methods = info()->methods(); int n_methods = methods.size(); if (!properties.reserve(properties.length() + n_methods)) { JS_ReportOutOfMemory(cx); return false; } for (GI::AutoFunctionInfo meth_info : methods) { if (meth_info.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* cx, 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(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } Maybe method_info{m_info->method(prop_name.get())}; if (method_info && method_info->is_method()) { if (!gjs_define_function(cx, obj, m_gtype, method_info.ref())) return false; *resolved = true; } 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, m_gtype, GjsTypecheckNoThrow{}); args.rval().setBoolean(isinstance); return true; } 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}; // clang-format off 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* cx, GType gtype, JS::MutableHandleValue value_p) { GI::Repository repo; Maybe interface_info{repo.find_by_gtype(gtype)}; if (!interface_info) { gjs_throw(cx, "Cannot expose non introspectable interface %s", g_type_name(gtype)); return false; } JSObject* constructor = gjs_lookup_generic_constructor(cx, interface_info.ref()); if (G_UNLIKELY(!constructor)) return false; value_p.setObject(*constructor); return true; } cjs-140.0/gi/interface.h0000664000175000017500000001056515167114161013743 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. #pragma once #include #include #include #include #include #include #include #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "util/log.h" class InterfacePrototype; class InterfaceInstance; struct JSFunctionSpec; /* 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*, unsigned, JS::Value*); }; class InterfacePrototype : public GIWrapperPrototype, mozilla::Maybe> { friend class GIWrapperPrototype, mozilla::Maybe>; friend class GIWrapperBase; friend class InterfaceBase; // for has_instance_impl // the GTypeInterface vtable wrapped by this JS object GTypeInterface* m_vtable; explicit InterfacePrototype(const mozilla::Maybe&, GType); ~InterfacePrototype(); // JSClass operations GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext*, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable); // JS methods GJS_JSAPI_RETURN_CONVENTION bool has_instance_impl(JSContext*, const JS::CallArgs&); }; 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() { g_assert_not_reached(); } }; GJS_JSAPI_RETURN_CONVENTION bool gjs_lookup_interface_constructor(JSContext*, GType, JS::MutableHandleValue); cjs-140.0/gi/js-value-inl.h0000664000175000017500000003470515167114161014313 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 // for move #include #include #include #include // for JS_EncodeStringToUTF8 #include #include // for JSExnType #include #include #include // for UniqueChars #include // for CanonicalizeNaN #include "gi/arg-types-inl.h" #include "gi/gtype.h" #include "gi/value.h" #include "cjs/auto.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" namespace Gjs { // There are two ways you can unpack a C value from a JSValue. // ContainingType means storing the unpacked value in the most appropriate C // type that can contain it. Implicit conversion may be performed and the value // may need to be checked to make sure it is in range. // PackType, on the other hand, means storing it in the C type that is exactly // equivalent to how JSValue stores it, so no implicit conversion is performed // unless the JSValue contains a pointer to a GC-thing, like BigInt. enum HolderMode : uint8_t { ContainingType, PackType }; template constexpr bool type_has_js_getter() { if constexpr (MODE == HolderMode::PackType) { return std::is_same_v, Tag::JSValuePackT>; } else { return std::is_same_v, Tag::JSValueContainingT>; } } // Avoid implicit conversions template bool js_value_to_c(JSContext*, JS::HandleValue, UnpackT*) = delete; template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext* cx, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, JS::HandleValue value, double* out) { return JS::ToNumber(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext* cx, JS::HandleValue value, double* out) { return JS::ToNumber(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext* cx, JS::HandleValue value, double* out) { return JS::ToNumber(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*, JS::HandleValue value, gboolean* out) { *out = !!JS::ToBoolean(value); return true; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext* cx, 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, 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, JS::HandleValue value, char** out) { if (value.isNull()) { *out = nullptr; return true; } if (!value.isString()) return false; JS::RootedString str{cx, value.toString()}; JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str); *out = js_chars_to_glib(std::move(utf8)).release(); return true; } template [[nodiscard]] constexpr BigT max_safe_big_number() { return (BigT(1) << std::numeric_limits::digits) - 1; } template [[nodiscard]] constexpr BigT min_safe_big_number() { if constexpr (std::is_signed_v) return -(max_safe_big_number()); return std::numeric_limits::lowest(); } template , TAG>>, typename U> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(JSContext* cx, JS::HandleValue value, U* out, bool* out_of_range) { using T = Tag::RealT; 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; } } template , T>>> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(JSContext* cx, JS::HandleValue value, T* out, bool* out_of_range) { return js_value_to_c_checked(cx, value, out, out_of_range); } template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(JSContext* cx, JS::HandleValue value, TypeWrapper* out, bool* out_of_range) { static_assert(std::is_integral_v); if constexpr (std::is_same_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; } // Handle the cases resulting from TypeWrapper and // TypeWrapper not being convertible on macOS if constexpr (!std::is_same_v && std::is_same_v && std::is_same_v) { return js_value_to_c_checked(cx, value, out, out_of_range); } if constexpr (!std::is_same_v && std::is_same_v && std::is_same_v) { return js_value_to_c_checked(cx, value, out, out_of_range); } } template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js(JSContext* cx [[maybe_unused]], Tag::RealT value, JS::MutableHandleValue js_value_p) { using T = Tag::RealT; if constexpr (std::is_same_v || std::is_same_v) { 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"); } } // Specialization for types where TAG and RealT are the same type, to allow // inferring template parameter template , T>>> GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js(JSContext* cx, T value, JS::MutableHandleValue js_value_p) { return c_value_to_js(cx, value, js_value_p); } template GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked(JSContext* cx [[maybe_unused]], Tag::RealT value, JS::MutableHandleValue js_value_p) { using T = Tag::RealT; 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()); } } if constexpr (std::is_same_v) { if (value && !g_utf8_validate(value, -1, nullptr)) { gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "String from C value is invalid UTF-8 and cannot " "be safely stored"); return false; } } return c_value_to_js(cx, value, js_value_p); } // Specialization for types where TAG and RealT are the same type, to allow // inferring template parameter template , T>>> GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked(JSContext* cx, T value, JS::MutableHandleValue js_value_p) { return c_value_to_js_checked(cx, value, js_value_p); } } // namespace Gjs cjs-140.0/gi/ns.cpp0000664000175000017500000001715415167114161012757 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 #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/ns.h" #include "gi/repo.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "util/log.h" using mozilla::Maybe; // helper function void platform_specific_warning_glib(JSContext* cx, const char* prefix, const char* platform, const char* resolved_name) { if (!g_str_has_prefix(resolved_name, prefix)) return; const char* base_name = resolved_name + strlen(prefix); Gjs::AutoChar old_name{g_strdup_printf("GLib.%s", resolved_name)}; Gjs::AutoChar new_name{g_strdup_printf("GLib%s.%s", platform, base_name)}; gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId::PlatformSpecificTypelib, {old_name.get(), new_name.get()}); } class Ns : private Gjs::AutoChar, public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr GjsGlobalSlot PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_ns; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GNAMESPACE; explicit Ns(const char* ns_name) : Gjs::AutoChar(const_cast(ns_name), Gjs::TakeOwnership{}) { GJS_INC_COUNTER(ns); #if !GLIB_CHECK_VERSION(2, 87, 3) m_is_glib = strcmp(ns_name, "GLib") == 0; #endif } ~Ns() { GJS_DEC_COUNTER(ns); } #if !GLIB_CHECK_VERSION(2, 87, 3) bool m_is_glib : 1; #endif // 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 } Maybe info{ GI::Repository{}.find_by_name(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'", info->type_string(), info->name(), info->ns()); #if !GLIB_CHECK_VERSION(2, 87, 3) if (m_is_glib) { platform_specific_warning_glib(cx, "Unix", "Unix", name.get()); platform_specific_warning_glib(cx, "unix_", "Unix", name.get()); platform_specific_warning_glib(cx, "Win32", "Win32", name.get()); platform_specific_warning_glib(cx, "win32_", "Win32", name.get()); } #endif bool defined; if (!gjs_define_info(cx, obj, info.ref(), &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]]) { GI::Repository::Iterator infos{GI::Repository{}.infos(get())}; if (!properties.reserve(properties.length() + infos.size())) { JS_ReportOutOfMemory(cx); return false; } for (GI::AutoBaseInfo info : infos) { if (!info.is_enumerable()) continue; jsid id = gjs_intern_string_to_id(cx, info.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 = GI::Repository{}.get_version(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, }; 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}; 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* cx, const char* ns_name) { return Ns::create(cx, ns_name); } cjs-140.0/gi/ns.h0000664000175000017500000000053015167114161012412 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 #pragma once #include #include "cjs/macros.h" class JSObject; struct JSContext; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_create_ns(JSContext*, const char* ns_name); cjs-140.0/gi/object.cpp0000664000175000017500000043450615167114161013611 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 #include // for mem_fn #include #include // for make_unique, unique_ptr #include #include // for tie #include #include #include // for move, pair #include #include #include #include #include #include // for IsCallable, JS_CallFunctionValue #include #include #include #include #include // for JS_ReportOutOfMemory #include // for JS_ClearPendingException #include // for JS_AddWeakPointerCompartmentCallback #include // for MutableWrappedPtrOperations #include #include // for AddAssociatedMemory, RemoveAssoci... #include #include #include // for JSPROP_PERMANENT, JSPROP_READONLY #include // for JS_FN, JSFunctionSpec, JSPropertySpec #include #include #include #include // for UniqueChars #include #include #include #include // for JS_GetFunctionObject, IdVector #include // for JS_GetObjectFunction, GetFunctionNativeReserved #include #include #include #include #include #include "gi/arg-inl.h" #include "gi/arg-types-inl.h" #include "gi/arg.h" #include "gi/closure.h" #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/gjs_gi_trace.h" #include "gi/info.h" #include "gi/js-value-inl.h" // for Relaxed, c_value_to_js_checked #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/auto.h" #include "cjs/context-private.h" #include "cjs/deprecation.h" #include "cjs/gerror-result.h" #include "cjs/jsapi-class.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/mem-private.h" #include "cjs/profiler-private.h" #include "util/log.h" class JSTracer; using mozilla::Err, mozilla::Maybe, mozilla::Nothing, mozilla::Ok, mozilla::Result, mozilla::Some; /* 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 uintptr_t DISPOSED_OBJECT = std::numeric_limits::max(); GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_object_prototype_from_info( JSContext*, Maybe, 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) // clang-format on G_DEFINE_QUARK(gjs::disposed, ObjectBase::disposed) [[nodiscard]] static G_DEFINE_QUARK(gjs::private, gjs_object_priv); bool ObjectBase::is_custom_js_class() { return !!g_type_get_qdata(gtype(), ObjectBase::custom_type_quark()); } void ObjectInstance::link() { auto [_, done] = s_wrapped_gobject_list.insert(this); g_assert(done); mozilla::Unused << done; } void ObjectInstance::unlink() { s_wrapped_gobject_list.erase(this); } const void* ObjectBase::jsobj_addr() const { if (is_prototype()) return nullptr; return to_instance()->m_wrapper.debug_addr(); } bool ObjectInstance::check_gobject_disposed_or_finalized( const char* for_what) const { if (!m_gobj_disposed) return true; g_critical( "Object %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", format_name().c_str(), 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() { 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() { g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this); } void ObjectInstance::set_object_qdata() { 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() { GQuark 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, Gjs::AutoTypeClass 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; Gjs::AutoChar 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; } template 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 full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + pspec->name + "\"]")}; AutoProfilerLabel label{cx, "property getter", full_name}; 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()); } template 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) { gjs_warn_deprecated_once_per_callsite(cx, DeprecatedGObjectProperty, {format_name(), param->name}); } 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); if constexpr (!std::is_same_v) { if (Gjs::c_value_to_js_checked(cx, Gjs::gvalue_get(&gvalue), rval)) return true; gjs_throw(cx, "Can't convert value %s got from %s::%s property", Gjs::gvalue_to_string(&gvalue).c_str(), format_name().c_str(), param->name); return false; } else { return gjs_value_from_g_value(cx, rval, &gvalue); } } bool ObjectBase::prop_getter_write_only(JSContext*, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().setUndefined(); return true; } class ObjectPropertyInfoCaller { public: GI::AutoFunctionInfo func_info; void* native_address = nullptr; explicit ObjectPropertyInfoCaller(const GI::FunctionInfo& info) : func_info(info) {} Gjs::GErrorResult<> init() { GIFunctionInvoker invoker; MOZ_TRY(func_info.prep_invoker(&invoker)); native_address = invoker.native_address; gi_function_invoker_clear(&invoker); return Ok{}; } }; bool ObjectBase::prop_getter_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedObject pspec_obj{ cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; auto* info_caller = JS::ObjectGetStashedPointer(cx, pspec_obj); const GI::AutoFunctionInfo& func_info = info_caller->func_info; GI::AutoPropertyInfo property_info{func_info.property().value()}; std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + property_info.name() + "\"]")}; AutoProfilerLabel label{cx, "property getter", full_name}; priv->debug_jsprop("Property getter", property_info.name(), obj); // Ignore silently; note that this is different from what we do for // boxed types, for historical reasons if (priv->is_prototype()) return true; return priv->to_instance()->prop_getter_impl(cx, info_caller, args); } template [[nodiscard]] static bool simple_getter_caller(GObject* obj, void* native_address, GIArgument* out_arg) { using T = Gjs::Tag::RealT; using FuncType = T (*)(GObject*); FuncType func = reinterpret_cast(native_address); gjs_arg_set(out_arg, func(obj)); return true; } [[nodiscard]] static bool simple_getters_caller(const GI::TypeInfo& type_info, GObject* obj, void* native_address, GIArgument* out_arg) { switch (type_info.tag()) { case GI_TYPE_TAG_VOID: if (type_info.is_pointer()) return simple_getter_caller(obj, native_address, out_arg); return false; case GI_TYPE_TAG_BOOLEAN: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_INT8: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_UINT8: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_INT16: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_UINT16: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_INT32: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_UINT32: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_INT64: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_UINT64: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_FLOAT: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_DOUBLE: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_GTYPE: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_UNICHAR: return simple_getter_caller(obj, native_address, out_arg); case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (interface_info.is_enum_or_flags()) { return simple_getter_caller( obj, native_address, out_arg); } return simple_getter_caller(obj, native_address, out_arg); } 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: return simple_getter_caller(obj, native_address, out_arg); } return false; } bool ObjectInstance::prop_getter_impl(JSContext* cx, ObjectPropertyInfoCaller* info_caller, JS::CallArgs const& args) { if (!check_gobject_finalized("get any property from")) { args.rval().setUndefined(); return true; } const GI::AutoFunctionInfo& getter = info_caller->func_info; GI::AutoPropertyInfo property_info{getter.property().value()}; if (property_info.has_deprecated_param_flag() || property_info.is_deprecated() || getter.is_deprecated()) { gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {format_name(), property_info.name()}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", property_info.name()); GIArgument ret; std::array gi_args; gjs_arg_set(gi_args.data(), m_ptr.get()); GI::StackTypeInfo type_info; getter.load_return_type(&type_info); if (!simple_getters_caller(type_info, m_ptr, info_caller->native_address, &ret)) { const std::string& class_name = format_name(); gjs_throw(cx, "Wrong type for %s::%s getter", class_name.c_str(), property_info.name()); return false; } GITransfer transfer = getter.caller_owns(); if (!gjs_value_from_gi_argument(cx, args.rval(), type_info, GJS_ARGUMENT_RETURN_VALUE, transfer, &ret)) { // Unlikely to happen, but we fallback to gvalue mode, just in case JS_ClearPendingException(cx); Gjs::AutoTypeClass klass{gtype()}; GParamSpec* pspec = g_object_class_find_property(klass, property_info.name()); if (!pspec) { const std::string& class_name = format_name(); gjs_throw(cx, "Error converting value got from %s::%s getter", class_name.c_str(), property_info.name()); return false; } return prop_getter_impl(cx, pspec, args[0]); } return gjs_gi_argument_release(cx, transfer, type_info, &ret, GjsArgumentFlags::ARG_OUT); } class ObjectPropertyPspecCaller { public: GParamSpec* pspec; void* native_address = nullptr; explicit ObjectPropertyPspecCaller(GParamSpec* param) : pspec(param) {} Gjs::GErrorResult<> init(const GI::FunctionInfo& info) { GIFunctionInvoker invoker; MOZ_TRY(info.prep_invoker(&invoker)); native_address = invoker.native_address; gi_function_invoker_clear(&invoker); return Ok{}; } }; template bool ObjectBase::prop_getter_simple_type_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedObject pspec_obj( cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()); auto* caller = JS::ObjectGetStashedPointer(cx, pspec_obj); std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + caller->pspec->name + "\"]")}; AutoProfilerLabel label{cx, "property getter", full_name}; priv->debug_jsprop("Property getter", gjs_intern_string_to_id(cx, caller->pspec->name), obj); // Ignore silently; note that this is different from what we do for // boxed types, for historical reasons if (priv->is_prototype()) return true; return priv->to_instance()->prop_getter_impl(cx, caller, args); } template bool ObjectInstance::prop_getter_impl(JSContext* cx, ObjectPropertyPspecCaller* pspec_caller, JS::CallArgs const& args) { if (!check_gobject_finalized("get any property from")) { args.rval().setUndefined(); return true; } if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) { gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {format_name(), pspec_caller->pspec->name}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Accessing GObject property %s", pspec_caller->pspec->name); using T = Gjs::Tag::RealT; using FuncType = T (*)(GObject*); FuncType func = reinterpret_cast(pspec_caller->native_address); T retval = func(m_ptr); if (!Gjs::c_value_to_js_checked(cx, retval, args.rval())) return false; if constexpr (TRANSFER != GI_TRANSFER_NOTHING) { static_assert(std::is_same_v, "Unexpected type to release"); g_free(retval); } return true; } [[nodiscard]] static Maybe lookup_field_info(const GI::ObjectInfo& info, const char* name) { for (GI::AutoFieldInfo retval : info.fields()) { if (strcmp(name, retval.name()) == 0) return Some(retval); } return {}; } bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedObject field_info_obj{ cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; auto const& field_info = *JS::ObjectGetStashedPointer(cx, field_info_obj); std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + field_info.name() + "\"]")}; AutoProfilerLabel label{cx, "field getter", full_name}; priv->debug_jsprop("Field getter", field_info.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, field_info, args.rval()); } bool ObjectInstance::field_getter_impl(JSContext* cx, GI::AutoFieldInfo const& field, JS::MutableHandleValue rval) { if (!check_gobject_finalized("get any property from")) return true; GIArgument arg = { 0 }; gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Overriding %s with GObject field", field.name()); GI::AutoTypeInfo type{field.type_info()}; switch (type.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", field.name(), type.display_string()); return false; default: break; } if (field.read(m_ptr, &arg).isErr()) { gjs_throw(cx, "Error getting field %s from object", field.name()); 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. */ template 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 full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + pspec->name + "\"]")}; AutoProfilerLabel label{cx, "property setter", full_name}; 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]); } template 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_DEPRECATED) { gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {format_name(), 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)); using T = Gjs::Tag::RealT; if constexpr (std::is_same_v) { if (!gjs_value_to_g_value(cx, value, &gvalue)) return false; } else if constexpr (std::is_arithmetic_v< // NOLINT(readability/braces) T> && !Gjs::type_has_js_getter()) { bool out_of_range = false; Gjs::Tag::JSValuePackT val{}; using HolderTag = Gjs::Tag::JSValuePackTag; if (!Gjs::js_value_to_c_checked(cx, value, &val, &out_of_range)) { gjs_throw(cx, "Can't convert value %s to set %s::%s property", gjs_debug_value(value).c_str(), format_name().c_str(), param_spec->name); return false; } if (out_of_range) { gjs_throw(cx, "value %s is out of range for %s (type %s)", std::to_string(val).c_str(), param_spec->name, Gjs::static_type_name()); return false; } Gjs::gvalue_set(&gvalue, val); } else { T native_value; if (!Gjs::js_value_to_c(cx, value, &native_value)) { gjs_throw(cx, "Can't convert %s value to set %s::%s property", gjs_debug_value(value).c_str(), format_name().c_str(), param_spec->name); return false; } if constexpr (std::is_pointer_v) { Gjs::gvalue_take(&gvalue, g_steal_pointer(&native_value)); } else { Gjs::gvalue_set(&gvalue, native_value); } } g_object_set_property(m_ptr, param_spec->name, &gvalue); return true; } bool ObjectBase::prop_setter_read_only(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()); // Prevent setting the property even in JS return gjs_wrapper_throw_readonly_field(cx, priv->to_instance()->gtype(), pspec->name); } bool ObjectBase::prop_setter_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedObject func_obj{ cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; auto* info_caller = JS::ObjectGetStashedPointer(cx, func_obj); const GI::AutoFunctionInfo& func_info = info_caller->func_info; GI::AutoPropertyInfo property_info{func_info.property().value()}; std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + property_info.name() + "\"]")}; AutoProfilerLabel label{cx, "property setter", full_name}; priv->debug_jsprop("Property setter", property_info.name(), obj); // Ignore silently; note that this is different from what we do for // boxed types, for historical reasons if (priv->is_prototype()) return true; return priv->to_instance()->prop_setter_impl(cx, info_caller, args); } template [[nodiscard]] static bool simple_setter_caller(GIArgument* arg, GObject* obj, void* native_address) { using FuncType = void (*)(GObject*, Gjs::Tag::RealT); FuncType func = reinterpret_cast(native_address); func(obj, gjs_arg_get(arg)); return true; } [[nodiscard]] static bool simple_setters_caller(const GI::TypeInfo& type_info, GIArgument* arg, GObject* obj, void* native_address) { switch (type_info.tag()) { case GI_TYPE_TAG_VOID: if (type_info.is_pointer()) return simple_setter_caller(arg, obj, native_address); return false; case GI_TYPE_TAG_BOOLEAN: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_INT8: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_UINT8: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_INT16: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_UINT16: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_INT32: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_UINT32: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_INT64: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_UINT64: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_FLOAT: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_DOUBLE: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_GTYPE: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_UNICHAR: return simple_setter_caller(arg, obj, native_address); case GI_TYPE_TAG_INTERFACE: { GI::AutoBaseInfo interface_info{type_info.interface()}; if (interface_info.is_enum_or_flags()) { return simple_setter_caller(arg, obj, native_address); } return simple_setter_caller(arg, obj, native_address); } 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: return simple_setter_caller(arg, obj, native_address); } return false; } bool ObjectInstance::prop_setter_impl(JSContext* cx, ObjectPropertyInfoCaller* info_caller, JS::CallArgs const& args) { if (!check_gobject_finalized("set any property on")) return true; const GI::AutoFunctionInfo& setter = info_caller->func_info; GI::AutoPropertyInfo property_info{setter.property().value()}; if (property_info.has_deprecated_param_flag() || property_info.is_deprecated() || setter.is_deprecated()) { gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {format_name(), property_info.name()}); } gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s", property_info.name()); GI::StackArgInfo arg_info; setter.load_arg(0, &arg_info); GI::StackTypeInfo type_info; arg_info.load_type(&type_info); GITransfer transfer = arg_info.ownership_transfer(); JS::RootedValue value{cx, args[0]}; GIArgument arg; if (!gjs_value_to_gi_argument(cx, value, type_info, GJS_ARGUMENT_ARGUMENT, transfer, &arg, GjsArgumentFlags::ARG_IN, property_info.name())) { // Unlikely to happen, but we fallback to gvalue mode, just in case JS_ClearPendingException(cx); Gjs::AutoTypeClass klass{gtype()}; GParamSpec* pspec = g_object_class_find_property(klass, property_info.name()); if (!pspec) { const std::string& class_name = format_name(); gjs_throw(cx, "Error converting value to call %s::%s setter", class_name.c_str(), property_info.name()); return false; } return prop_setter_impl(cx, pspec, value); } if (!simple_setters_caller(type_info, &arg, m_ptr, info_caller->native_address)) { const std::string& class_name = format_name(); gjs_throw(cx, "Wrong type for %s::%s setter", class_name.c_str(), property_info.name()); return false; } return gjs_gi_argument_release_in_arg(cx, transfer, type_info, &arg); } template bool ObjectBase::prop_setter_simple_type_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv); JS::RootedObject pspec_obj( cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()); auto* caller = JS::ObjectGetStashedPointer(cx, pspec_obj); std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[" + caller->pspec->name + "]")}; AutoProfilerLabel label{cx, "property setter", full_name}; priv->debug_jsprop("Property setter", caller->pspec->name, obj); // Ignore silently; note that this is different from what we do for // boxed types, for historical reasons if (priv->is_prototype()) return true; return priv->to_instance()->prop_setter_impl(cx, caller, args); } template bool ObjectInstance::prop_setter_impl(JSContext* cx, ObjectPropertyPspecCaller* pspec_caller, JS::CallArgs const& args) { if (!check_gobject_finalized("set any property on")) return true; gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop via setter %s", pspec_caller->pspec->name); if (pspec_caller->pspec->flags & G_PARAM_DEPRECATED) { gjs_warn_deprecated_once_per_callsite( cx, DeprecatedGObjectProperty, {format_name(), pspec_caller->pspec->name}); } using T = Gjs::Tag::RealT; using FuncType = void (*)(GObject*, T); FuncType func = reinterpret_cast(pspec_caller->native_address); if constexpr (std::is_arithmetic_v && !Gjs::type_has_js_getter()) { bool out_of_range = false; Gjs::Tag::JSValuePackT native_value{}; using HolderTag = Gjs::Tag::JSValuePackTag; if (!Gjs::js_value_to_c_checked( cx, args[0], &native_value, &out_of_range)) return false; if (out_of_range) { gjs_throw(cx, "value %s is out of range for %s (type %s)", std::to_string(native_value).c_str(), pspec_caller->pspec->name, Gjs::static_type_name()); return false; } func(m_ptr, native_value); } else { T native_value; if (!Gjs::js_value_to_c(cx, args[0], &native_value)) return false; func(m_ptr, native_value); if constexpr (TRANSFER == GI_TRANSFER_NOTHING && std::is_pointer_v) { static_assert(std::is_same_v, "Unexpected type to release"); g_free(native_value); } } 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::RootedObject field_info_obj{ cx, &gjs_dynamic_property_private_slot(&args.callee()).toObject()}; auto const& field_info = *JS::ObjectGetStashedPointer(cx, field_info_obj); std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, priv->format_name() + "[\"" + field_info.name() + "\"]")}; AutoProfilerLabel label{cx, "field setter", full_name}; priv->debug_jsprop("Field setter", field_info.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://web.archive.org/web/20210512234746/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, field_info); } bool ObjectInstance::field_setter_not_impl(JSContext* cx, GI::AutoFieldInfo const& field) { if (!check_gobject_finalized("set GObject field on")) return true; /* 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 (field.is_writable()) { g_message( "Field %s of a GObject is writable, but setting it is not " "implemented", field.name()); return true; } return gjs_wrapper_throw_readonly_field(cx, gtype(), field.name()); } bool ObjectPrototype::is_vfunc_unchanged(const GI::VFuncInfo& info) const { GType ptype = g_type_parent(m_gtype); Gjs::GErrorResult addr1 = info.address(m_gtype); if (addr1.isErr()) return false; Gjs::GErrorResult addr2 = info.address(ptype); if (addr2.isErr()) return false; return addr1.unwrap() == addr2.unwrap(); } [[nodiscard]] static Maybe find_vfunc_on_parents( const GI::ObjectInfo& 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 */ Maybe parent{Some(info)}; /* 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. */ Maybe vfunc = parent->find_vfunc_using_interfaces(name).map( [](auto&& pair) { return std::move(pair.first); }); while (!vfunc && parent) { parent = parent->parent(); if (parent) vfunc = parent->vfunc(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 Gjs::AutoChar& 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 Maybe get_ginterface_property_by_name( const GI::InterfaceInfo& info, const char* name) { for (GI::AutoPropertyInfo prop_info : info.properties()) { if (strcmp(name, prop_info.name()) == 0) return Some(std::move(prop_info)); } return {}; } [[nodiscard]] static Maybe get_gobject_property_info( const GI::ObjectInfo& info, const char* name) { for (GI::AutoPropertyInfo prop_info : info.properties()) { if (strcmp(name, prop_info.name()) == 0) return Some(std::move(prop_info)); } for (GI::AutoInterfaceInfo iface_info : info.interfaces()) { if (Maybe prop_info = get_ginterface_property_by_name(iface_info, name)) return prop_info; } return {}; } [[nodiscard]] static JSNative get_getter_for_type(const GI::TypeInfo& type_info, GITransfer transfer) { switch (type_info.tag()) { case GI_TYPE_TAG_BOOLEAN: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_INT8: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_UINT8: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_INT16: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_UINT16: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_INT32: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_UINT32: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_INT64: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_UINT64: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_FLOAT: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_DOUBLE: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_GTYPE: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_UNICHAR: return ObjectBase::prop_getter_simple_type_func; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { return ObjectBase::prop_getter_simple_type_func< const char*, GI_TRANSFER_NOTHING>; } else { return ObjectBase::prop_getter_simple_type_func< char*, GI_TRANSFER_EVERYTHING>; } default: return nullptr; } } [[nodiscard]] static JSNative get_setter_for_type(const GI::TypeInfo& type_info, GITransfer transfer) { switch (type_info.tag()) { case GI_TYPE_TAG_BOOLEAN: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_INT8: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_UINT8: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_INT16: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_UINT16: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_INT32: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_UINT32: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_INT64: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_UINT64: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_FLOAT: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_DOUBLE: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_GTYPE: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_UNICHAR: return ObjectBase::prop_setter_simple_type_func; case GI_TYPE_TAG_FILENAME: case GI_TYPE_TAG_UTF8: if (transfer == GI_TRANSFER_NOTHING) { return ObjectBase::prop_setter_simple_type_func< char*, GI_TRANSFER_NOTHING>; } else { return ObjectBase::prop_setter_simple_type_func< char*, GI_TRANSFER_EVERYTHING>; } default: return nullptr; } } // Wrap a call to JS::NewObjectWithStashedPointer() while ensuring the pointer // is properly deleted if the call fails. template GJS_JSAPI_RETURN_CONVENTION static inline JSObject* new_object_with_stashed_pointer(JSContext* cx, const Ts&... args) { std::unique_ptr data = std::make_unique(args...); JSObject* obj = JS::NewObjectWithStashedPointer( cx, data.get(), [](T* data) { delete data; }); if (obj) data.release(); return obj; } GJS_JSAPI_RETURN_CONVENTION static JSNative create_getter_invoker(JSContext* cx, GParamSpec* pspec, const GI::FunctionInfo& getter, const GI::TypeInfo& type, JS::MutableHandleValue wrapper_out) { JS::RootedObject wrapper{cx}; GITransfer transfer = getter.caller_owns(); JSNative js_getter = get_getter_for_type(type, transfer); Gjs::GErrorResult<> init_result{Ok{}}; if (js_getter) { wrapper = new_object_with_stashed_pointer( cx, pspec); if (!wrapper) return nullptr; auto* caller = JS::ObjectGetStashedPointer(cx, wrapper); init_result = caller->init(getter); } else { wrapper = new_object_with_stashed_pointer( cx, getter); if (!wrapper) return nullptr; js_getter = &ObjectBase::prop_getter_func; auto* caller = JS::ObjectGetStashedPointer(cx, wrapper); init_result = caller->init(); } if (init_result.isErr()) { gjs_throw(cx, "Impossible to create invoker for %s: %s", getter.name(), init_result.inspectErr()->message); return nullptr; } wrapper_out.setObject(*wrapper); return js_getter; } // We cannot use g_base_info_equal because the GITypeInfo of properties is // not marked as a pointer in GIR files, while it is marked as a pointer in the // return type of the associated getter, or the argument type of the associated // setter. Also, there isn't a GParamSpec for integers of specific widths, there // is only int and long, whereas the corresponding getter may return a specific // width of integer. [[nodiscard]] static bool type_info_compatible(const GI::TypeInfo& func_type, const GI::TypeInfo& prop_type) { GITypeTag tag = prop_type.tag(); GITypeTag func_tag = func_type.tag(); if (GI_TYPE_TAG_IS_BASIC(tag)) { if (func_type.is_pointer() != prop_type.is_pointer()) return false; } switch (tag) { case GI_TYPE_TAG_VOID: // g_param_spec_param case GI_TYPE_TAG_BOOLEAN: // g_param_spec_boolean case GI_TYPE_TAG_INT8: // g_param_spec_char case GI_TYPE_TAG_DOUBLE: // g_param_spec_double case GI_TYPE_TAG_FLOAT: // g_param_spec_float case GI_TYPE_TAG_GTYPE: // g_param_spec_gtype case GI_TYPE_TAG_UINT8: // g_param_spec_uchar case GI_TYPE_TAG_UNICHAR: // g_param_spec_unichar case GI_TYPE_TAG_ERROR: // would be g_param_spec_boxed? return func_tag == tag; case GI_TYPE_TAG_INT32: case GI_TYPE_TAG_INT64: // g_param_spec_int, g_param_spec_long, or g_param_spec_int64 return func_tag == GI_TYPE_TAG_INT8 || func_tag == GI_TYPE_TAG_INT16 || func_tag == GI_TYPE_TAG_INT32 || func_tag == GI_TYPE_TAG_INT64; case GI_TYPE_TAG_UINT32: case GI_TYPE_TAG_UINT64: // g_param_spec_uint, g_param_spec_ulong, or g_param_spec_uint64 return func_tag == GI_TYPE_TAG_UINT8 || func_tag == GI_TYPE_TAG_UINT16 || func_tag == GI_TYPE_TAG_UINT32 || func_tag == GI_TYPE_TAG_UINT64; case GI_TYPE_TAG_UTF8: // g_param_spec_string return func_tag == tag || func_tag == GI_TYPE_TAG_FILENAME; case GI_TYPE_TAG_INT16: case GI_TYPE_TAG_UINT16: case GI_TYPE_TAG_FILENAME: g_return_val_if_reached(false); // never occurs as GParamSpec type // everything else case GI_TYPE_TAG_GLIST: case GI_TYPE_TAG_GSLIST: return func_tag == tag && func_type.element_type() == prop_type.element_type(); case GI_TYPE_TAG_ARRAY: return func_tag == tag && func_type.element_type() == prop_type.element_type() && func_type.is_zero_terminated() == prop_type.is_zero_terminated() && func_type.array_fixed_size() == prop_type.array_fixed_size() && func_type.array_type() == prop_type.array_type(); case GI_TYPE_TAG_GHASH: return func_tag == tag && func_type.key_type() == prop_type.key_type() && func_type.value_type() == prop_type.value_type(); case GI_TYPE_TAG_INTERFACE: return func_tag == tag && func_type.interface() == prop_type.interface(); } g_return_val_if_reached(false); } GJS_JSAPI_RETURN_CONVENTION static JSNative get_getter_for_property( JSContext* cx, GParamSpec* pspec, Maybe property_info, JS::MutableHandleValue priv_out) { if (!(pspec->flags & G_PARAM_READABLE)) { priv_out.setUndefined(); return &ObjectBase::prop_getter_write_only; } if (property_info) { Maybe prop_getter{property_info->getter()}; if (prop_getter && prop_getter->is_method() && prop_getter->n_args() == 0 && !prop_getter->skip_return()) { GI::StackTypeInfo return_type; prop_getter->load_return_type(&return_type); GI::AutoTypeInfo prop_type{property_info->type_info()}; if (G_LIKELY(type_info_compatible(return_type, prop_type))) { return create_getter_invoker(cx, pspec, *prop_getter, return_type, priv_out); } Maybe container = prop_getter->container(); g_warning( "Type %s of property %s.%s::%s does not match return type %s " "of getter %s. Falling back to slow path", prop_type.type_string(), container->ns(), container->name(), property_info->name(), return_type.type_string(), prop_getter->name()); // fall back to GValue below } } priv_out.setPrivate(pspec); switch (pspec->value_type) { case G_TYPE_BOOLEAN: return &ObjectBase::prop_getter; case G_TYPE_INT: return &ObjectBase::prop_getter; case G_TYPE_UINT: return &ObjectBase::prop_getter; case G_TYPE_CHAR: return &ObjectBase::prop_getter; case G_TYPE_UCHAR: return &ObjectBase::prop_getter; case G_TYPE_INT64: return &ObjectBase::prop_getter; case G_TYPE_UINT64: return &ObjectBase::prop_getter; case G_TYPE_FLOAT: return &ObjectBase::prop_getter; case G_TYPE_DOUBLE: return &ObjectBase::prop_getter; case G_TYPE_STRING: return &ObjectBase::prop_getter; case G_TYPE_LONG: return &ObjectBase::prop_getter; case G_TYPE_ULONG: return &ObjectBase::prop_getter; default: return &ObjectBase::prop_getter<>; } } GJS_JSAPI_RETURN_CONVENTION static JSNative create_setter_invoker(JSContext* cx, GParamSpec* pspec, const GI::FunctionInfo& setter, const GI::ArgInfo& value_arg, const GI::TypeInfo& type, JS::MutableHandleValue wrapper_out) { JS::RootedObject wrapper{cx}; GITransfer transfer = value_arg.ownership_transfer(); JSNative js_setter = get_setter_for_type(type, transfer); Gjs::GErrorResult<> init_result{Ok{}}; if (js_setter) { wrapper = new_object_with_stashed_pointer( cx, pspec); if (!wrapper) return nullptr; auto* caller = JS::ObjectGetStashedPointer(cx, wrapper); init_result = caller->init(setter); } else { wrapper = new_object_with_stashed_pointer( cx, setter); if (!wrapper) return nullptr; js_setter = &ObjectBase::prop_setter_func; auto* caller = JS::ObjectGetStashedPointer(cx, wrapper); init_result = caller->init(); } if (init_result.isErr()) { gjs_throw(cx, "Impossible to create invoker for %s: %s", setter.name(), init_result.inspectErr()->message); return nullptr; } wrapper_out.setObject(*wrapper); return js_setter; } GJS_JSAPI_RETURN_CONVENTION static JSNative get_setter_for_property( JSContext* cx, GParamSpec* pspec, Maybe property_info, JS::MutableHandleValue priv_out) { if (!(pspec->flags & G_PARAM_WRITABLE)) { priv_out.setPrivate(pspec); return &ObjectBase::prop_setter_read_only; } if (property_info) { Maybe prop_setter{property_info->setter()}; if (prop_setter && prop_setter->is_method() && prop_setter->n_args() == 1) { GI::StackArgInfo value_arg; prop_setter->load_arg(0, &value_arg); GI::StackTypeInfo type_info; value_arg.load_type(&type_info); GI::AutoTypeInfo prop_type{property_info->type_info()}; if (G_LIKELY(type_info_compatible(type_info, prop_type))) { return create_setter_invoker(cx, pspec, *prop_setter, value_arg, type_info, priv_out); } Maybe container = prop_setter->container(); g_warning( "Type %s of property %s.%s::%s does not match type %s of first " "argument of setter %s. Falling back to slow path", prop_type.type_string(), container->ns(), container->name(), property_info->name(), type_info.type_string(), prop_setter->name()); // fall back to GValue below } } priv_out.setPrivate(pspec); switch (pspec->value_type) { case G_TYPE_BOOLEAN: return &ObjectBase::prop_setter; case G_TYPE_INT: return &ObjectBase::prop_setter; case G_TYPE_UINT: return &ObjectBase::prop_setter; case G_TYPE_CHAR: return &ObjectBase::prop_setter; case G_TYPE_UCHAR: return &ObjectBase::prop_setter; case G_TYPE_INT64: return &ObjectBase::prop_setter; case G_TYPE_UINT64: return &ObjectBase::prop_setter; case G_TYPE_FLOAT: return &ObjectBase::prop_setter; case G_TYPE_DOUBLE: return &ObjectBase::prop_setter; case G_TYPE_STRING: return &ObjectBase::prop_setter; case G_TYPE_LONG: return &ObjectBase::prop_setter; case G_TYPE_ULONG: return &ObjectBase::prop_setter; default: return &ObjectBase::prop_setter<>; } } bool ObjectPrototype::lazy_define_gobject_property( JSContext* cx, JS::HandleObject obj, JS::HandleId id, GParamSpec* pspec, bool* resolved, const char* name, const Maybe& property_info) { JS::RootedId canonical_id{cx}; JS::Rooted canonical_desc{cx}; // Make property configurable so that interface properties can be // overridden by GObject.ParamSpec.override in the class that // implements them unsigned flags = GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT; if (!g_str_equal(pspec->name, name)) { canonical_id = gjs_intern_string_to_id(cx, pspec->name); JS::Rooted> desc{cx}; if (!JS_GetOwnPropertyDescriptorById(cx, obj, canonical_id, &desc)) return false; if (desc.isSome()) { debug_jsprop("Defining alias GObject property", id, obj); canonical_desc = *desc; if (!JS_DefinePropertyById(cx, obj, id, canonical_desc)) return false; *resolved = true; return true; } } debug_jsprop("Defining lazy GObject property", id, obj); if (!(pspec->flags & (G_PARAM_WRITABLE | G_PARAM_READABLE))) { if (!JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue, flags)) return false; if (!canonical_id.isVoid() && !JS_DefinePropertyById(cx, obj, canonical_id, JS::UndefinedHandleValue, flags)) return false; *resolved = true; return true; } // 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 getter_priv{cx}; JSNative js_getter = get_getter_for_property(cx, pspec, property_info, &getter_priv); if (!js_getter) return false; JS::RootedValue setter_priv{cx}; JSNative js_setter = get_setter_for_property(cx, pspec, property_info, &setter_priv); if (!js_setter) return false; if (!gjs_define_property_dynamic(cx, obj, name, id, "gobject_prop", js_getter, getter_priv, js_setter, setter_priv, flags)) return false; if G_UNLIKELY (!canonical_id.isVoid()) { debug_jsprop("Defining alias GObject property", canonical_id, obj); if (!JS_DefinePropertyById(cx, obj, canonical_id, canonical_desc)) 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, const GI::InterfaceInfo& iface_info, JS::HandleId identifier, JS::HandleObject class_prototype, bool* found) { JS::RootedObject interface_prototype( cx, gjs_lookup_object_prototype_from_info(cx, Some(iface_info), 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) { Gjs::AutoChar 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); } } mozilla::Span interfaces = GI::Repository{}.object_get_gtype_interfaces(m_gtype); // 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()) { Gjs::AutoTypeClass 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 (const GI::InterfaceInfo& iface_info : interfaces) { Maybe method_info{iface_info.method(name)}; if (method_info && method_info->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.ref())) return false; *resolved = true; return true; } if (!resolve_on_interface_prototype(cx, iface_info, id, obj, resolved)) return false; if (*resolved) return true; } *resolved = false; return true; } [[nodiscard]] static Maybe find_gobject_property_info( const GI::ObjectInfo& info, const char* name) { // Optimization: GObject property names must start with a letter if (!g_ascii_isalpha(name[0])) return {}; Gjs::AutoChar canonical_name{gjs_hyphen_from_camel(name)}; canonicalize_key(canonical_name); return get_gobject_property_info(info, canonical_name); } // 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* cx, 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(cx, id, &prop_name)) return false; if (!prop_name) { *resolved = false; return true; // not resolved, but no error } if (!uncached_resolve(cx, obj, id, prop_name.get(), resolved)) return false; if (!*resolved && !m_unresolvable_cache.putNew(id)) { JS_ReportOutOfMemory(cx); return false; } return true; } bool ObjectPrototype::uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, const char* name, bool* resolved) { 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; } // 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(cx, 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; Maybe vfunc{find_vfunc_on_parents( m_info.ref(), 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.ref())) { *resolved = false; return true; } if (!gjs_define_function(cx, obj, m_gtype, vfunc.ref())) return false; *resolved = true; return true; } /* If the vfunc wasn't found, fall through, back to normal method * resolution. */ } if (Maybe property_info = find_gobject_property_info(m_info.ref(), name)) { Gjs::AutoTypeClass gobj_class{m_gtype}; if (GParamSpec* pspec = g_object_class_find_property(gobj_class, property_info->name())) return lazy_define_gobject_property(cx, obj, id, pspec, resolved, name, property_info); } Maybe field_info{lookup_field_info(m_info.ref(), name)}; if (field_info) { debug_jsprop("Defining lazy GObject field", id, obj); unsigned flags = GJS_MODULE_PROP_FLAGS; if (!field_info->is_writable()) flags |= JSPROP_READONLY; JS::RootedObject rooted_field{ cx, new_object_with_stashed_pointer( cx, field_info.extract())}; JS::RootedValue private_value{cx, JS::ObjectValue(*rooted_field)}; if (!gjs_define_property_dynamic( cx, obj, name, id, "gobject_field", &ObjectBase::field_getter, private_value, &ObjectBase::field_setter, private_value, 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) */ auto result = m_info->find_method_using_interfaces(name); // 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 (!result) return resolve_no_info(cx, obj, id, resolved, name, ConsiderOnlyMethods); GI::AutoFunctionInfo method_info{result->first}; GI::AutoRegisteredTypeInfo implementor_info{result->second}; method_info.log_usage(); if (method_info.is_method()) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for %s (%s)", method_info.name(), type_name(), format_name().c_str()); if (auto iface_info = implementor_info.as()) { bool found = false; if (!resolve_on_interface_prototype(cx, iface_info.value(), id, obj, &found)) return false; // If the method was not found fallback to defining the function // from type info... if (!found && !gjs_define_function(cx, obj, m_gtype, method_info)) return false; } else if (!gjs_define_function(cx, 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; Gjs::AutoFree interfaces{g_type_interfaces(gtype(), &n_interfaces)}; GI::Repository repo; for (unsigned k = 0; k < n_interfaces; k++) { Maybe iface_info{ repo.find_by_gtype(interfaces[k])}; if (!iface_info) continue; GI::InterfaceInfo::MethodsIterator meth_iter = iface_info->methods(); GI::InterfaceInfo::PropertiesIterator props_iter = iface_info->properties(); if (!properties.reserve(properties.length() + meth_iter.size() + props_iter.size())) { JS_ReportOutOfMemory(cx); return false; } // Methods for (GI::AutoFunctionInfo meth_info : meth_iter) { if (meth_info.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 (GI::AutoPropertyInfo prop_info : props_iter) { Gjs::AutoChar 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); } } if (info()) { GI::ObjectInfo::MethodsIterator meth_iter = info()->methods(); GI::ObjectInfo::PropertiesIterator props_iter = info()->properties(); if (!properties.reserve(properties.length() + meth_iter.size() + props_iter.size())) { JS_ReportOutOfMemory(cx); return false; } // Methods for (GI::AutoFunctionInfo meth_info : meth_iter) { if (meth_info.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 (GI::AutoPropertyInfo prop_info : props_iter) { Gjs::AutoChar 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 (props is a property bag) bool ObjectPrototype::props_to_g_parameters( JSContext* cx, Gjs::AutoTypeClass const& object_class, JS::HandleObject props, std::vector* names, AutoGValueVector* values) { JS::RootedId prop_id{cx}; JS::RootedValue value{cx}; JS::Rooted ids{cx, cx}; std::unordered_set visited_params; if (!JS_Enumerate(cx, props, &ids)) { gjs_throw(cx, "Failed to create property iterator for object props hash"); return false; } values->reserve(ids.length()); for (size_t 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( cx, m_gtype, gjs_debug_id(prop_id).c_str()); JS::RootedString js_prop_name{cx, prop_id.toString()}; GParamSpec* param_spec = find_param_spec_from_id(cx, 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(cx, props, prop_id, &value)) return false; if (value.isUndefined()) { gjs_throw(cx, "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(cx, 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(cx, 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() { GQuark 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() { GQuark 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() { 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) { for (auto link = s_wrapped_gobject_list.begin(), last = s_wrapped_gobject_list.end(); link != last;) { if (predicate(*link)) { action(*link); link = s_wrapped_gobject_list.erase(link); continue; } ++link; } } /** * 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() { if (wrapper_is_rooted()) { debug_lifecycle("Was rooted, but unrooting due to GjsContext dispose"); discard_wrapper(); } } void ObjectInstance::toggle_down() { 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() { if (G_UNLIKELY(!m_ptr || m_gobj_disposed || m_gobj_finalized)) { gjs_debug_lifecycle( GJS_DEBUG_GOBJECT, "Avoid toggling up a wrapper for a %s object: %p (%s)", m_ptr ? (m_gobj_finalized ? "finalized" : "disposed") : "released", m_ptr ? m_ptr.as() : this, g_type_name(gtype())); 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"); JSContext* 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 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. */ bool 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 transitioned 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() { 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 GI::Repository repo; GI::AutoObjectInfo surface_info{ repo.find_by_gtype(gdksurface_type) .value()}; GI::AutoFunctionInfo destroy_func{ surface_info.method("destroy").value()}; GIArgument destroy_args; gjs_arg_set(&destroy_args, ptr); GIArgument unused_return; auto result = destroy_func.invoke({{destroy_args}}, {}, &unused_return); if (result.isErr()) g_critical("Error destroying GdkSurface %p: %s", ptr, result.inspectErr()->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() { ToggleQueue::get_default()->handle_all_toggles(toggle_handler); } void gjs_object_shutdown_toggle_queue() { 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() { /* 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(const Maybe& 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)); } 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* cx, 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(cx); 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); } template static void invalidate_closure_collection(T* 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 Gjs::AutoGClosure closure{*it, Gjs::TakeOwnership{}}; 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() { 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) { // First, 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* cx, const JS::CallArgs& args, JS::HandleObject object) { g_assert(gtype() != G_TYPE_NONE); if (args.length() > 1 && !JS::WarnUTF8(cx, "Too many arguments to the constructor of %s: expected " "1, got %u", name(), args.length())) return false; std::vector names; AutoGValueVector values; if (args.length() > 0 && !args[0].isUndefined()) { if (!args[0].isObject()) { gjs_throw(cx, "Argument to the constructor of %s should be a plain JS " "object with properties to set", name()); return false; } JS::RootedObject props{cx, &args[0].toObject()}; if (ObjectBase::for_js(cx, props)) { gjs_throw(cx, "Argument to the constructor of %s should be a plain JS " "object with properties to set", name()); return false; } Gjs::AutoTypeClass object_class{gtype()}; if (!m_proto->props_to_g_parameters(cx, object_class, props, &names, &values)) return false; } if (G_TYPE_IS_ABSTRACT(gtype())) { gjs_throw(cx, "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(cx); if (!gjs->object_init_list().append(object)) { JS_ReportOutOfMemory(cx); 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(cx); 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(cx, 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* cx, JS::HandleObject object, const JS::CallArgs& args) { JS::RootedValue initer{cx}; GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); const JS::MutableHandleValue& new_target = args.newTarget(); bool has_gtype; g_assert(new_target.isObject() && "new.target needs to be an object"); JS::RootedObject rooted_target{cx, &new_target.toObject()}; if (!JS_HasOwnPropertyById(cx, rooted_target, gjs->atoms().gtype(), &has_gtype)) return false; if (!has_gtype) { gjs_throw(cx, "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(cx, object, "GObject instance", gjs->atoms().init(), &initer) && gjs->call_function(object, initer, args, args.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_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", format_name().c_str()); } 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_collection(&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* cx, Maybe info, GType gtype) { g_return_val_if_fail(!info || info->is_object() || info->is_interface(), nullptr); JS::RootedObject in_object{cx}; const char* constructor_name; if (info) { in_object = gjs_lookup_namespace_object(cx, info.value()); constructor_name = info->name(); } else { in_object = gjs_lookup_private_namespace(cx); constructor_name = g_type_name(gtype); } if (G_UNLIKELY (!in_object)) return nullptr; bool found; if (!JS_HasProperty(cx, in_object, constructor_name, &found)) return nullptr; JS::RootedValue value{cx}; if (found && !JS_GetProperty(cx, in_object, constructor_name, &value)) return nullptr; JS::RootedObject constructor{cx}; 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{cx}; if (!ObjectPrototype::define_class(cx, in_object, Nothing{}, gtype, nullptr, 0, &constructor, &ignored)) return nullptr; } else { if (G_UNLIKELY (!value.isObject())) return nullptr; constructor = &value.toObject(); } g_assert(constructor); return constructor; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_object_prototype_from_info( JSContext* cx, Maybe info, GType gtype) { g_return_val_if_fail(!info || info->is_object() || info->is_interface(), nullptr); JS::RootedObject constructor{ cx, gjs_lookup_object_constructor_from_info(cx, info, gtype)}; if (G_UNLIKELY(!constructor)) return nullptr; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject prototype{cx}; if (!gjs_object_require_property(cx, constructor, "constructor object", atoms.prototype(), &prototype)) return nullptr; return prototype; } GJS_JSAPI_RETURN_CONVENTION static JSObject* gjs_lookup_object_prototype(JSContext* cx, GType gtype) { GI::Repository repo; return gjs_lookup_object_prototype_from_info(cx, repo.find_by_gtype(gtype), gtype); } 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_collection(&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); } static constexpr const char* connect_func_name(bool after, bool object) { if (object) return "connect_object"; if (after) return "connect_after"; return "connect"; } bool ObjectInstance::connect_impl(JSContext* cx, const JS::CallArgs& args, bool after, bool object) { GQuark signal_detail; const char* func_name = connect_func_name(after, object); 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{cx}; JS::RootedObject associate_obj{cx}; GConnectFlags flags; if (object) { if (!gjs_parse_call_args(cx, 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(cx, "Unsupported connect flag G_CONNECT_SWAPPED"); return false; } after = flags & G_CONNECT_AFTER; } else { if (!gjs_parse_call_args(cx, func_name, args, "so", "signal name", &signal_name, "callback", &callback)) return false; } std::string dynamic_string{GJS_PROFILER_DYNAMIC_STRING( cx, format_name() + '.' + func_name + "('" + signal_name.get() + "')")}; AutoProfilerLabel label{cx, "", dynamic_string}; if (!JS::IsCallable(callback)) { gjs_throw(cx, "second arg must be a callback"); return false; } unsigned signal_id; if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, true)) { gjs_throw(cx, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } GClosure* closure = Gjs::Closure::create_for_signal( cx, callback, "signal callback", signal_id); if (closure == nullptr) return false; if (associate_obj.get() != nullptr) { ObjectInstance* obj = ObjectInstance::for_js(cx, associate_obj); if (!obj) return false; if (!obj->associate_closure(cx, closure)) return false; } else if (!associate_closure(cx, closure)) { return false; } unsigned long 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* cx, const JS::CallArgs& args) { GQuark signal_detail; GSignalQuery signal_query; gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this, args.length()); if (!check_gobject_finalized("emit any signal on")) { args.rval().setUndefined(); return true; } JS::UniqueChars signal_name; if (!gjs_parse_call_args(cx, "emit", args, "!s", "signal name", &signal_name)) return false; std::string full_name{GJS_PROFILER_DYNAMIC_STRING( cx, format_name() + " emit('" + signal_name.get() + "')")}; AutoProfilerLabel label{cx, "", full_name}; unsigned signal_id; if (!g_signal_parse_name(signal_name.get(), gtype(), &signal_id, &signal_detail, false)) { gjs_throw(cx, "No signal '%s' on object '%s'", signal_name.get(), type_name()); return false; } g_signal_query(signal_id, &signal_query); if ((args.length() - 1) != signal_query.n_params) { gjs_throw(cx, "Signal '%s' on %s requires %d args got %d", signal_name.get(), type_name(), signal_query.n_params, args.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 (unsigned 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(cx, args[i + 1], &value)) return false; } else { if (!gjs_value_to_g_value(cx, args[i + 1], &value)) return false; } if (!ObjectBase::info()) continue; Maybe signal_info = ObjectBase::info()->signal(signal_query.signal_name); if (!signal_info) continue; GI::AutoArgInfo arg_info{signal_info->arg(i)}; if (arg_info.ownership_transfer() != 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); args.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(cx, args.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() 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* cx, unsigned argc, JS::Value* vp) { GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, argv, obj, ObjectBase, priv); if (!priv->check_is_instance(cx, "initialize")) return false; std::string full_name{ GJS_PROFILER_DYNAMIC_STRING(cx, priv->format_name() + "._init")}; AutoProfilerLabel label{cx, "", full_name}; return priv->to_instance()->init_impl(cx, argv, obj); } const struct JSClassOps ObjectBase::class_ops = { &ObjectBase::add_property, nullptr, // deleteProperty nullptr, // enumerate &ObjectBase::new_enumerate, &ObjectBase::resolve, nullptr, // mayResolve &ObjectBase::finalize, nullptr, // call nullptr, // construct &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}; // 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* cx, JS::HandleObject in_object, const Maybe& info, GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) { ObjectPrototype* priv = ObjectPrototype::create_class( cx, in_object, info, gtype, constructor, prototype); if (!priv) return false; priv->set_interfaces(interface_gtypes, n_interface_gtypes); JS::RootedObject parent_constructor{cx}; if (!priv->get_parent_constructor(cx, &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(cx, 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(cx); return JS_DefineFunctionById(cx, prototype, atoms.hook_up_vfunc(), &ObjectBase::hook_up_vfunc, 3, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(cx, prototype, atoms.signal_find(), &ObjectBase::signal_find, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( cx, prototype, atoms.signals_block(), &ObjectBase::signals_action<&g_signal_handlers_block_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById( cx, prototype, atoms.signals_unblock(), &ObjectBase::signals_action<&g_signal_handlers_unblock_matched>, 1, GJS_MODULE_PROP_FLAGS) && JS_DefineFunctionById(cx, 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; auto* 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) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (!ObjectBase::typecheck(cx, obj, 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; } // Returns pair of implementor_vtable pointer, maybe field info GJS_JSAPI_RETURN_CONVENTION static Maybe>> find_vfunc_info( JSContext* cx, GType implementor_gtype, const GI::VFuncInfo& vfunc_info, const char* vfunc_name) { Maybe struct_info; void* implementor_vtable_ret = nullptr; const GI::RegisteredTypeInfo ancestor_info = vfunc_info.container().value(); GType ancestor_gtype = ancestor_info.gtype(); Gjs::AutoTypeClass implementor_class{implementor_gtype}; if (auto iface_info = ancestor_info.as()) { auto* implementor_iface_class = static_cast( g_type_interface_peek(implementor_class, ancestor_gtype)); if (implementor_iface_class == nullptr) { gjs_throw(cx, "Couldn't find GType of implementor of interface %s.", g_type_name(ancestor_gtype)); return Nothing{}; } implementor_vtable_ret = implementor_iface_class; struct_info = iface_info->iface_struct(); } else { struct_info = ancestor_info.as()->class_struct(); implementor_vtable_ret = implementor_class; } for (GI::AutoFieldInfo field_info : struct_info->fields()) { if (strcmp(field_info.name(), vfunc_name) != 0) continue; GI::AutoTypeInfo type_info{field_info.type_info()}; if (type_info.tag() != 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 Some(std::make_pair(implementor_vtable_ret, Nothing{})); } return Some(std::make_pair(implementor_vtable_ret, Some(std::move(field_info)))); } return Some(std::make_pair(implementor_vtable_ret, Nothing{})); } 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); bool is_static = false; if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so|b", "name", &name, "function", &callable, "is_static", &is_static)) return false; args.rval().setUndefined(); // find the first class that actually has repository information GI::Repository repo; Maybe info = m_info; GType info_gtype = m_gtype; while (!info && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); info = repo.find_by_gtype(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); Maybe vfunc{info->vfunc(name.get())}; // Search the parent type chain while (!vfunc && info_gtype != G_TYPE_OBJECT) { info_gtype = g_type_parent(info_gtype); info = repo.find_by_gtype(info_gtype); if (info) vfunc = info->vfunc(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) { Maybe interface{ repo.find_by_gtype(interface_gtype)}; // Private and dynamic interfaces (defined in JS) do not have type // info. if (interface) { vfunc = interface->vfunc(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/gjs/-/issues/89 if (!vfunc) { unsigned n_interfaces; Gjs::AutoPointer interface_list{ g_type_interfaces(m_gtype, &n_interfaces)}; for (unsigned i = 0; i < n_interfaces; i++) { Maybe interface{ repo.find_by_gtype(interface_list[i])}; if (!interface) continue; Maybe parent_vfunc{interface->vfunc(name.get())}; if (parent_vfunc) { Gjs::AutoChar 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; } if (vfunc->is_method() != !is_static) { gjs_throw(cx, "Invalid %s definition of %s virtual function %s", is_static ? "static" : "non-static", is_static ? "non-static" : "static", name.get()); return false; } auto result = find_vfunc_info(cx, m_gtype, vfunc.ref(), name.get()); if (!result) return false; void* implementor_vtable = result->first; Maybe field_info = result->second; if (field_info) { int offset = field_info->offset(); void** vfunc_slot_ptr = static_cast(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; } auto* trampoline = GjsCallbackTrampoline::create( cx, callable, vfunc.ref(), GI_SCOPE_TYPE_NOTIFIED, true, !is_static); 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.insert(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); }); *vfunc_slot_ptr = trampoline->get_func_ptr(); } return true; } void ObjectPrototype::vfunc_invalidated_notify(void* data, GClosure* closure) { // This callback should *only* touch m_vfuncs auto* priv = static_cast(data); priv->m_vfuncs.erase(closure); } bool gjs_lookup_object_constructor(JSContext* cx, GType gtype, JS::MutableHandleValue value_p) { GI::Repository repo; JSObject* constructor = gjs_lookup_object_constructor_from_info( cx, repo.find_by_gtype(gtype), gtype); if (G_UNLIKELY(constructor == nullptr)) 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-140.0/gi/object.h0000664000175000017500000004573115167114161013254 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 #pragma once #include #include // for size_t #include // for uint32_t #include #include #include #include #include #include #include #include // for GCHashMap #include // for DefaultHasher #include #include #include #include // for HashGeneric, HashNumber #include // for MOZ_LIKELY #include #include "gi/info.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/auto.h" #include "cjs/jsapi-util-root.h" #include "cjs/jsapi-util.h" // for gjs_throw #include "cjs/macros.h" #include "util/log.h" class GjsAtoms; struct JSFunctionSpec; struct JSPropertySpec; class JSTracer; namespace JS { class CallArgs; } namespace Gjs::Test { struct ObjectInstance; } class ObjectInstance; class ObjectPrototype; class ObjectPropertyInfoCaller; class ObjectPropertyPspecCaller; /** * 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 = unsigned(void*, GSignalMatchType, unsigned, GQuark, GClosure*, void*, void*); 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*, JS::HandleObject) = delete; GJS_JSAPI_RETURN_CONVENTION static bool to_c_ptr(JSContext*, JS::HandleObject, GObject** ptr); GJS_JSAPI_RETURN_CONVENTION static bool transfer_to_gi_argument(JSContext*, JS::HandleObject, GIArgument*, GIDirection transfer_direction, GITransfer transfer_ownership, GType expected_gtype); 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]] static bool id_is_never_lazy(jsid name, const GjsAtoms&); [[nodiscard]] bool is_custom_js_class(); public: // Overrides GIWrapperBase::typecheck(). We only override the overload that // throws, so that we can throw our own more informative error. template GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject obj, T expected) { if (GIWrapperBase::typecheck(cx, obj, expected)) 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; } template [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj, T expected, GjsTypecheckNoThrow no_throw) { return GIWrapperBase::typecheck(cx, obj, expected, no_throw); } // JSClass operations static bool add_property(JSContext*, JS::HandleObject, JS::HandleId, JS::HandleValue); // JS property getters/setters template GJS_JSAPI_RETURN_CONVENTION static bool prop_getter(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool prop_getter_write_only(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool prop_getter_func(JSContext*, unsigned, JS::Value*); template GJS_JSAPI_RETURN_CONVENTION static bool prop_getter_simple_type_func(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool field_getter(JSContext*, unsigned, JS::Value*); template GJS_JSAPI_RETURN_CONVENTION static bool prop_setter(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool prop_setter_read_only(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool prop_setter_func(JSContext*, unsigned, JS::Value*); template GJS_JSAPI_RETURN_CONVENTION static bool prop_setter_simple_type_func(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool field_setter(JSContext*, unsigned, JS::Value*); // JS methods GJS_JSAPI_RETURN_CONVENTION static bool connect(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool connect_after(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool connect_object(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool emit(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool signal_find(JSContext*, unsigned, JS::Value*); template GJS_JSAPI_RETURN_CONVENTION static bool signals_action(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool init_gobject(JSContext*, unsigned, JS::Value*); GJS_JSAPI_RETURN_CONVENTION static bool hook_up_vfunc(JSContext*, unsigned, JS::Value*); // 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 { using Lookup = jsid; 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, mozilla::Maybe> { friend class GIWrapperPrototype, mozilla::Maybe>; friend class GIWrapperBase; using NegativeLookupCache = JS::GCHashSet, IdHasher, js::SystemAllocPolicy>; NegativeLookupCache m_unresolvable_cache; // a list of vfunc GClosures installed on this prototype, used when tracing std::unordered_set m_vfuncs; // a list of interface types explicitly associated with this prototype, // by gjs_add_interface std::vector m_interface_gtypes; ObjectPrototype(const mozilla::Maybe&, GType); ~ObjectPrototype(); public: [[nodiscard]] static ObjectPrototype* for_gtype(GType); // Helper methods private: GJS_JSAPI_RETURN_CONVENTION bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) const; GJS_JSAPI_RETURN_CONVENTION bool get_parent_constructor(JSContext*, JS::MutableHandleObject constructor) const; [[nodiscard]] bool is_vfunc_unchanged(const GI::VFuncInfo&) const; static void vfunc_invalidated_notify(void* data, GClosure*); GJS_JSAPI_RETURN_CONVENTION bool lazy_define_gobject_property( JSContext*, JS::HandleObject, JS::HandleId, GParamSpec*, bool* resolved, const char* name, const mozilla::Maybe& = {}); enum ResolveWhat : uint8_t { ConsiderOnlyMethods, ConsiderMethodsAndProperties }; GJS_JSAPI_RETURN_CONVENTION bool resolve_no_info(JSContext*, JS::HandleObject, JS::HandleId, bool* resolved, const char* name, ResolveWhat); GJS_JSAPI_RETURN_CONVENTION bool uncached_resolve(JSContext*, JS::HandleObject, JS::HandleId, const char* name, bool* resolved); public: void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes); void set_type_qdata(); GJS_JSAPI_RETURN_CONVENTION GParamSpec* find_param_spec_from_id(JSContext*, Gjs::AutoTypeClass const&, JS::HandleString key); GJS_JSAPI_RETURN_CONVENTION bool props_to_g_parameters(JSContext*, Gjs::AutoTypeClass const&, JS::HandleObject props, std::vector* names, AutoGValueVector* values); GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext*, JS::HandleObject in_object, const mozilla::Maybe&, GType, GType* interface_gtypes, uint32_t n_interface_gtypes, JS::MutableHandleObject constructor, JS::MutableHandleObject prototype); void ref_vfuncs() { for (GClosure* closure : m_vfuncs) g_closure_ref(closure); } void unref_vfuncs() { for (GClosure* closure : m_vfuncs) g_closure_unref(closure); } // JSClass operations private: GJS_JSAPI_RETURN_CONVENTION bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool* resolved); GJS_JSAPI_RETURN_CONVENTION bool new_enumerate_impl(JSContext*, JS::HandleObject, JS::MutableHandleIdVector properties, bool only_enumerable); void trace_impl(JSTracer*); // JS methods public: GJS_JSAPI_RETURN_CONVENTION bool hook_up_vfunc_impl(JSContext*, const JS::CallArgs&); }; 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*, JS::HandleObject); ~ObjectInstance(); GJS_JSAPI_RETURN_CONVENTION static ObjectInstance* new_for_gobject(JSContext*, GObject*); // Extra method to get an existing ObjectInstance from qdata public: [[nodiscard]] static ObjectInstance* for_gobject(GObject*); // 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() { 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 associate_js_gobject(JSContext*, JS::HandleObject, GObject*); void disassociate_js_gobject(); void handle_context_dispose(); [[nodiscard]] bool weak_pointer_was_finalized(JSTracer*); static void ensure_weak_pointer_callback(JSContext*); static void update_heap_wrapper_weak_pointers(JSTracer*, JS::Compartment*, void* data); public: void toggle_down(); void toggle_up(); GJS_JSAPI_RETURN_CONVENTION static JSObject* wrapper_from_gobject(JSContext*, GObject*); GJS_JSAPI_RETURN_CONVENTION static bool set_value_from_gobject(JSContext*, GObject*, JS::MutableHandleValue); // Methods to manipulate the list of closures private: void invalidate_closures(); static void closure_invalidated_notify(void* data, GClosure*); public: GJS_JSAPI_RETURN_CONVENTION bool associate_closure(JSContext*, GClosure*); // Helper methods private: void set_object_qdata(); void unset_object_qdata(); void track_gobject_finalization(); void ignore_gobject_finalization(); void check_js_object_finalized(); 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*, 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*, JS::HandleObject wrapper, GObject*); static void associate_string(GObject*, char* str); // Methods to manipulate the linked list of instances private: static std::unordered_set s_wrapped_gobject_list; void link(); void unlink(); [[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&, const Action&); public: static void prepare_shutdown(); // JSClass operations private: GJS_JSAPI_RETURN_CONVENTION bool add_property_impl(JSContext*, JS::HandleObject, JS::HandleId, JS::HandleValue); void finalize_impl(JS::GCContext*, JSObject*); void trace_impl(JSTracer*); // JS property getters/setters template GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl(JSContext*, GParamSpec*, JS::MutableHandleValue rval); GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl(JSContext*, ObjectPropertyInfoCaller*, JS::CallArgs const&); template GJS_JSAPI_RETURN_CONVENTION bool prop_getter_impl(JSContext*, ObjectPropertyPspecCaller*, JS::CallArgs const&); GJS_JSAPI_RETURN_CONVENTION bool field_getter_impl(JSContext*, GI::AutoFieldInfo const&, JS::MutableHandleValue rval); template GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext*, GParamSpec*, JS::HandleValue); GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext*, ObjectPropertyInfoCaller*, JS::CallArgs const&); template GJS_JSAPI_RETURN_CONVENTION bool prop_setter_impl(JSContext*, ObjectPropertyPspecCaller*, JS::CallArgs const&); GJS_JSAPI_RETURN_CONVENTION bool field_setter_not_impl(JSContext*, GI::AutoFieldInfo const&); // JS constructor GJS_JSAPI_RETURN_CONVENTION static bool constructor_impl(JSContext*, JS::HandleObject, const JS::CallArgs&); // JS methods GJS_JSAPI_RETURN_CONVENTION bool connect_impl(JSContext*, const JS::CallArgs&, bool after, bool object = false); GJS_JSAPI_RETURN_CONVENTION bool emit_impl(JSContext*, const JS::CallArgs&); GJS_JSAPI_RETURN_CONVENTION bool signal_find_impl(JSContext*, const JS::CallArgs&); template GJS_JSAPI_RETURN_CONVENTION bool signals_action_impl(JSContext*, const JS::CallArgs&); GJS_JSAPI_RETURN_CONVENTION bool init_impl(JSContext*, const JS::CallArgs&, JS::HandleObject); [[nodiscard]] const char* to_string_kind() const; // Overrides GIWrapperInstance::typecheck_impl() template GJS_JSAPI_RETURN_CONVENTION bool typecheck_impl(T expected) const { g_assert(m_gobj_disposed || !m_ptr || g_type_is_a(gtype(), G_OBJECT_TYPE(m_ptr.as()))); return GIWrapperInstance::typecheck_impl(expected); } // Notification callbacks void gobj_dispose_notify(); static void wrapped_gobj_dispose_notify(void* data, GObject*); static void wrapped_gobj_toggle_notify(void* instance, GObject*, 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*, GType, JS::MutableHandleValue); void gjs_object_clear_toggles(); void gjs_object_shutdown_toggle_queue(); cjs-140.0/gi/param.cpp0000664000175000017500000002116315167114161013432 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 // 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 #include "gi/cwrapper.h" #include "gi/function.h" #include "gi/info.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/wrapperutils.h" #include "cjs/atoms.h" #include "cjs/auto.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" using mozilla::Maybe; extern struct JSClass gjs_param_class; // Reserved slots static const size_t POINTER = 0; struct Param : Gjs::AutoParam { explicit Param(GParamSpec* param) : Gjs::AutoParam(param, Gjs::TakeOwnership{}) {} }; [[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* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) { if (!param_value(cx, obj)) { // instance, not prototype *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 } GI::Repository repo; GI::AutoObjectInfo info{ repo.find_by_gtype(G_TYPE_PARAM).value()}; Maybe method_info{info.method(name.get())}; if (!method_info) { *resolved = false; return true; } method_info->log_usage(); if (method_info->is_method()) { gjs_debug(GJS_DEBUG_GOBJECT, "Defining method %s in prototype for GObject.ParamSpec", method_info->name()); if (!gjs_define_function(cx, obj, G_TYPE_PARAM, method_info.ref())) 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* cx) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject in_object{ cx, gjs_lookup_namespace_object_by_name(cx, atoms.gobject())}; if (G_UNLIKELY (!in_object)) return nullptr; JS::RootedValue value{cx}; if (!JS_GetPropertyById(cx, in_object, atoms.param_spec(), &value) || G_UNLIKELY(!value.isObject())) return nullptr; JS::RootedObject constructor{cx, &value.toObject()}; g_assert(constructor); if (!JS_GetPropertyById(cx, constructor, atoms.prototype(), &value) || G_UNLIKELY(!value.isObjectOrNull())) return nullptr; return value.toObjectOrNull(); } bool gjs_define_param_class(JSContext* cx, JS::HandleObject in_object) { JS::RootedObject prototype{cx}, constructor{cx}; if (!gjs_init_class_dynamic( cx, 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(cx, constructor, G_TYPE_PARAM)) return false; GI::Repository repo; GI::AutoObjectInfo info{ repo.find_by_gtype(G_TYPE_PARAM).value()}; if (!gjs_define_static_methods(cx, 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* cx, GParamSpec* gparam) { 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{cx, gjs_lookup_param_prototype(cx)}; if (!proto) return nullptr; JSObject* obj = JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto); if (!obj) return nullptr; 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* cx, JS::HandleObject obj) { if (!obj) return nullptr; return param_value(cx, obj); } bool gjs_typecheck_param(JSContext* cx, JS::HandleObject object, GType expected_type, bool throw_error) { if (!gjs_typecheck_instance(cx, object, &gjs_param_class, throw_error)) return false; GParamSpec* param = param_value(cx, object); if (!param) { if (throw_error) { gjs_throw_custom(cx, 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) return true; if (!g_type_is_a(G_TYPE_FROM_INSTANCE(param), expected_type)) { if (throw_error) { gjs_throw_custom(cx, 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 false; } return true; } cjs-140.0/gi/param.h0000664000175000017500000000126315167114161013076 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 #pragma once #include #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_param_class(JSContext*, JS::HandleObject in_object); GJS_JSAPI_RETURN_CONVENTION GParamSpec* gjs_g_param_from_param(JSContext*, JS::HandleObject); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_param_from_g_param(JSContext*, GParamSpec*); [[nodiscard]] bool gjs_typecheck_param(JSContext*, JS::HandleObject, GType expected_type, bool throw_error); cjs-140.0/gi/private.cpp0000664000175000017500000005417615167114161014016 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 #include #include #include #include #include // for UniqueChars #include #include #include // for JS_NewPlainObject #include #ifndef G_DISABLE_ASSERT # include // for IsCallable #endif #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/auto.h" #include "cjs/context-private.h" #include "cjs/jsapi-util-args.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" using mozilla::Nothing; /* 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 { Gjs::AutoTypeClass 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; } Gjs::AutoParam new_pspec{g_param_spec_override(name.get(), pspec)}; g_param_spec_set_qdata(new_pspec, ObjectBase::custom_property_quark(), GINT_TO_POINTER(1)); JSObject* obj = gjs_param_from_g_param(cx, new_pspec); if (!obj) return false; args.rval().setObject(*obj); 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; Gjs::AutoPointer 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, Nothing{}, 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, Nothing{}, 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; Gjs::AutoPointer 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 args = 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", args, "osioo", "parent", &parent, "name", &name, "flags", &type_flags, "interfaces", &interfaces, "properties", &properties)) return false; GType instance_type; Gjs::AutoPointer 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, Nothing{}, instance_type, iface_types, n_interfaces, &constructor, &prototype)) return false; auto* priv = ObjectPrototype::for_js(cx, prototype); priv->set_type_qdata(); args.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 args = 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", args, "oosioo", "class", &klass, "parent", &parent, "name", &name, "flags", &type_flags, "interfaces", &interfaces, "properties", &properties)) return false; GType instance_type; uint32_t n_interfaces; Gjs::AutoPointer 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, Nothing{}, 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, args.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; Gjs::AutoPointer 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* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject func_obj{cx}; JS::RootedObject target_obj{cx}; Gjs::AutoGValue value(G_TYPE_CLOSURE); if (!gjs_parse_call_args(cx, "associateClosure", args, "oo", "object", &target_obj, "func", &func_obj)) return false; g_assert(JS::IsCallable(func_obj) && "associateClosure's function must be callable"); ObjectInstance* obj = ObjectInstance::for_js(cx, target_obj); if (!obj) return false; Gjs::Closure::Ptr closure = Gjs::Closure::create_marshaled(cx, func_obj, "wrapped", false); if (!obj->associate_closure(cx, closure)) return false; g_value_set_boxed(&value, closure); return gjs_value_from_g_value(cx, args.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-140.0/gi/private.h0000664000175000017500000000054415167114161013451 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 #pragma once #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_private_gi_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/gi/repo.cpp0000664000175000017500000004544015167114161013303 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 #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 #include #include #include "gi/arg.h" #include "gi/enumeration.h" #include "gi/function.h" #include "gi/fundamental.h" #include "gi/gerror.h" #include "gi/info.h" #include "gi/interface.h" #include "gi/ns.h" #include "gi/object.h" #include "gi/param.h" #include "gi/repo.h" #include "gi/struct.h" #include "gi/union.h" #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/gerror-result.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "cjs/module.h" #include "util/log.h" using mozilla::Maybe; 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* cx, JS::HandleObject repo_obj, JS::HandleId ns_id, JS::UniqueChars* version) { JS::RootedObject versions{cx}; bool found; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!gjs_object_require_property(cx, repo_obj, "GI repository object", atoms.versions(), &versions)) return false; if (!JS_AlreadyHasOwnPropertyById(cx, versions, ns_id, &found)) return false; if (!found) return true; return gjs_object_require_property(cx, versions, nullptr, ns_id, version); } GJS_JSAPI_RETURN_CONVENTION static bool resolve_namespace_object(JSContext* cx, JS::HandleObject repo_obj, JS::HandleId ns_id) { JS::UniqueChars version; if (!get_version_for_ns(cx, repo_obj, ns_id, &version)) return false; JS::UniqueChars ns_name; if (!gjs_get_string_id(cx, ns_id, &ns_name)) return false; if (!ns_name) { gjs_throw(cx, "Requiring invalid namespace on imports.gi"); return false; } GI::Repository repo; size_t nversions; mozilla::Unused << repo.enumerate_versions(ns_name.get(), &nversions); if (nversions > 1 && !version && !repo.is_registered(ns_name.get(), nullptr) && !JS::WarnUTF8(cx, "Requiring %s but it has %zu versions available; use " "imports.gi.versions to pick one", ns_name.get(), nversions)) return false; // 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 (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 Gjs::AutoChar platform_specific{ g_strconcat(ns_name.get(), platform, nullptr)}; auto required = repo.require(platform_specific, version.get()); if (!required.isOk()) { gjs_throw(cx, "Failed to require %s %s: %s", platform_specific.get(), version.get(), required.inspectErr()->message); return false; } } #endif // (defined(G_OS_UNIX) || defined(G_OS_WIN32)) auto required = repo.require(ns_name.get(), version.get()); if (!required.isOk()) { gjs_throw(cx, "Requiring %s, version %s: %s", ns_name.get(), version ? version.get() : "none", required.inspectErr()->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{cx, gjs_create_ns(cx, ns_name.get())}; JS::RootedValue override{cx}; if (!lookup_override_function(cx, ns_id, &override) || // Define the property early, to avoid reentrancy issues if the override // module looks for namespaces that import this !JS_DefinePropertyById(cx, repo_obj, ns_id, gi_namespace, GJS_MODULE_PROP_FLAGS)) return false; JS::RootedValue result{cx}; if (!override.isUndefined() && !JS_CallFunctionValue(cx, /* this_obj = */ gi_namespace, override, 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(cx); 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* 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; } 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(cx, 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* cx) { JS::RootedObject repo{cx, JS_NewObject(cx, &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(cx); JS::RootedObject versions{cx, JS_NewPlainObject(cx)}; if (!JS_DefinePropertyById(cx, 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{cx, JS_NewStringCopyZ(cx, "2.0")}; if (!JS_DefinePropertyById(cx, versions, atoms.glib(), two_point_oh, JSPROP_PERMANENT) || !JS_DefinePropertyById(cx, versions, atoms.gobject(), two_point_oh, JSPROP_PERMANENT) || !JS_DefinePropertyById(cx, versions, atoms.gio(), two_point_oh, JSPROP_PERMANENT)) return nullptr; #ifdef G_OS_UNIX if (!JS_DefineProperty(cx, versions, "GLibUnix", two_point_oh, JSPROP_PERMANENT) || !JS_DefineProperty(cx, versions, "GioUnix", two_point_oh, JSPROP_PERMANENT)) return nullptr; #elif defined(G_OS_WIN32) if (!JS_DefineProperty(cx, versions, "GLibWin32", two_point_oh, JSPROP_PERMANENT) || !JS_DefineProperty(cx, versions, "GioWin32", two_point_oh, JSPROP_PERMANENT)) return nullptr; #endif // G_OS_UNIX/G_OS_WIN32 JS::RootedObject private_ns{cx, JS_NewPlainObject(cx)}; if (!JS_DefinePropertyById(cx, 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, const GI::ConstantInfo info, JS::MutableHandleValue value) { GIArgument garg; info.load_value(&garg); auto guard = mozilla::MakeScopeExit([&info, &garg]() { info.free_value(&garg); }); return gjs_value_from_gi_argument(cx, value, info.type_info(), &garg, true); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_define_constant(JSContext* cx, JS::HandleObject in_object, const GI::ConstantInfo& info) { JS::RootedValue value{cx}; if (!gjs_value_from_constant_info(cx, info, &value)) return false; return JS_DefineProperty(cx, in_object, info.name(), value, GJS_MODULE_PROP_FLAGS); } bool gjs_define_info(JSContext* cx, JS::HandleObject in_object, const GI::BaseInfo& info, bool* defined) { info.log_usage(); *defined = true; if (auto func_info = info.as()) return gjs_define_function(cx, in_object, 0, func_info.value()); if (auto object_info = info.as()) { GType gtype = object_info->gtype(); if (g_type_is_a(gtype, G_TYPE_PARAM)) return gjs_define_param_class(cx, in_object); if (g_type_is_a(gtype, G_TYPE_OBJECT)) { JS::RootedObject ignored1{cx}, ignored2{cx}; return ObjectPrototype::define_class(cx, in_object, object_info, gtype, nullptr, 0, &ignored1, &ignored2); } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { JS::RootedObject ignored{cx}; return FundamentalPrototype::define_class( cx, in_object, object_info.value(), &ignored); } gjs_throw(cx, "Unsupported type %s, deriving from fundamental %s", g_type_name(gtype), g_type_name(g_type_fundamental(gtype))); return false; } auto struct_info = info.as(); // We don't want GType structures in the namespace, we expose their fields // as vfuncs and their methods as static methods if (struct_info && struct_info->is_gtype_struct()) { *defined = false; return true; } if (struct_info) return StructPrototype::define_class(cx, in_object, struct_info.value()); if (auto union_info = info.as()) return UnionPrototype::define_class(cx, in_object, union_info.value()); if (auto enum_info = info.as()) { if (!info.is_flags() && enum_info->error_domain()) { // define as GError subclass return ErrorPrototype::define_class(cx, in_object, enum_info.value()); } return gjs_define_enumeration(cx, in_object, enum_info.value()); } if (auto constant_info = info.as()) return gjs_define_constant(cx, in_object, constant_info.value()); if (auto interface_info = info.as()) { JS::RootedObject ignored1{cx}, ignored2{cx}; return InterfacePrototype::create_class(cx, in_object, interface_info, interface_info->gtype(), &ignored1, &ignored2); } gjs_throw(cx, "API of type %s not implemented, cannot define %s.%s", info.type_string(), info.ns(), info.name()); return false; } // Get the "unknown namespace", which should be used for unnamespaced types JSObject* gjs_lookup_private_namespace(JSContext* cx) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return gjs_lookup_namespace_object_by_name(cx, atoms.private_ns_marker()); } // Get the namespace object that the GIBaseInfo should be inside JSObject* gjs_lookup_namespace_object(JSContext* cx, const GI::BaseInfo& info) { const char* ns = info.ns(); if (ns == nullptr) { gjs_throw(cx, "%s '%s' does not have a namespace", info.type_string(), info.name()); return nullptr; } JS::RootedId ns_name{cx, gjs_intern_string_to_id(cx, ns)}; if (ns_name.isVoid()) return nullptr; return gjs_lookup_namespace_object_by_name(cx, 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); auto cleanup = mozilla::MakeScopeExit([&saved_exc]() { saved_exc.restore(); }); bool eq; return JS_GetPropertyById(cx, exc, atoms.name(), &exc_name) && JS_StringEqualsLiteral(cx, exc_name.toString(), "ImportError", &eq) && eq; } 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)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); 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 nullptr; 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); } char* gjs_hyphen_from_camel(const char* camel_name) { // four hyphens should be reasonable guess GString* s = g_string_sized_new(strlen(camel_name) + 4 + 1); for (const char* 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* cx, const GI::BaseInfo& info) { JS::RootedObject in_object{cx, gjs_lookup_namespace_object(cx, info)}; const char* constructor_name = info.name(); if (G_UNLIKELY (!in_object)) return nullptr; JS::RootedValue value{cx}; if (!JS_GetProperty(cx, in_object, constructor_name, &value)) return nullptr; if (G_UNLIKELY(!value.isObject())) { gjs_throw(cx, "Constructor of %s.%s was the wrong type, expected an object", info.ns(), constructor_name); return nullptr; } return &value.toObject(); } JSObject* gjs_lookup_generic_prototype(JSContext* cx, const GI::BaseInfo& info) { JS::RootedObject constructor{cx, gjs_lookup_generic_constructor(cx, info)}; if (G_UNLIKELY(!constructor)) return nullptr; const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue value{cx}; if (!JS_GetPropertyById(cx, constructor, atoms.prototype(), &value)) return nullptr; if (G_UNLIKELY(!value.isObject())) { gjs_throw(cx, "Prototype of %s.%s was the wrong type, expected an object", info.ns(), info.name()); return nullptr; } return &value.toObject(); } JSObject* gjs_new_object_with_generic_prototype(JSContext* cx, const GI::BaseInfo& info) { JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info)); if (!proto) return nullptr; return JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto); } cjs-140.0/gi/repo.h0000664000175000017500000000231615167114161012743 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 #pragma once #include #include #include "gi/info.h" #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_repo(JSContext*, JS::MutableHandleObject repo); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_private_namespace(JSContext*); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_namespace_object(JSContext*, const GI::BaseInfo&); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_namespace_object_by_name(JSContext*, JS::HandleId name); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_generic_constructor(JSContext*, const GI::BaseInfo&); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_lookup_generic_prototype(JSContext*, const GI::BaseInfo&); GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_new_object_with_generic_prototype(JSContext*, const GI::BaseInfo&); GJS_JSAPI_RETURN_CONVENTION bool gjs_define_info(JSContext*, JS::HandleObject in_object, const GI::BaseInfo&, bool* defined); [[nodiscard]] char* gjs_hyphen_from_camel(const char* camel_name); cjs-140.0/gi/struct.cpp0000664000175000017500000001154115167114161013655 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento #include #include #include #include // for JS_DefineFunction #include #include #include "gi/boxed.h" #include "gi/gerror.h" #include "gi/struct.h" #include "cjs/atoms.h" #include "cjs/context-private.h" #include "cjs/jsapi-util.h" #include "cjs/mem-private.h" // clang-format off const struct JSClassOps StructBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &StructBase::BoxedBase::new_enumerate, &StructBase::BoxedBase::resolve, nullptr, // mayResolve &StructBase::BoxedBase::finalize, nullptr, // call nullptr, // construct &StructBase::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 StructBase::klass = { "GObject_Struct", JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE, &StructBase::class_ops }; // clang-format on StructPrototype::StructPrototype(const GI::StructInfo& info, GType gtype) : BoxedPrototype(info, gtype) { GJS_INC_COUNTER(boxed_prototype); } StructPrototype::~StructPrototype() { GJS_DEC_COUNTER(boxed_prototype); } bool StructPrototype::define_class(JSContext* cx, JS::HandleObject in_object, const GI::StructInfo& info) { JS::RootedObject prototype{cx}; if (!BoxedPrototype::define_class_impl(cx, in_object, info, &prototype)) return false; if (info.gtype() == G_TYPE_ERROR && !JS_DefineFunction(cx, prototype, "toString", &ErrorBase::to_string, 0, GJS_MODULE_PROP_FLAGS)) return false; return true; } StructInstance::StructInstance(StructPrototype* prototype, JS::HandleObject obj) : BoxedInstance(prototype, obj) { GJS_INC_COUNTER(boxed_instance); } StructInstance::~StructInstance() { if (m_owning_ptr && m_allocated_directly && gtype() == G_TYPE_VALUE) g_value_unset(m_ptr.template as()); GJS_DEC_COUNTER(boxed_instance); } bool StructInstance::constructor_impl(JSContext* cx, JS::HandleObject obj, const JS::CallArgs& args) { if (gtype() == G_TYPE_VARIANT) { // Short-circuit construction for GVariants by calling into the JS // packing function const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); if (!invoke_static_method(cx, 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; } if (!BoxedInstance::constructor_impl(cx, obj, args)) return false; // Define the expected Error properties if (gtype() == G_TYPE_ERROR) { JS::RootedObject gerror(cx, &args.rval().toObject()); if (!gjs_define_error_properties(cx, gerror)) return false; } return true; } static bool define_extra_error_properties(JSContext* cx, JS::HandleObject obj) { StructBase* priv = StructBase::for_js(cx, obj); if (priv->gtype() != G_TYPE_ERROR) return true; return gjs_define_error_properties(cx, obj); } /** * StructInstance::new_for_c_struct: * * Creates a new StructInstance 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* StructInstance::new_for_c_struct(JSContext* cx, const GI::StructInfo& info, void* gboxed) { JS::RootedObject obj{cx, new_for_c_struct_impl(cx, info, gboxed)}; if (!obj || !define_extra_error_properties(cx, obj)) return nullptr; return obj; } JSObject* StructInstance::new_for_c_struct(JSContext* cx, const GI::StructInfo& info, void* gboxed, Boxed::NoCopy no_copy) { JS::RootedObject obj{cx, new_for_c_struct_impl(cx, info, gboxed, no_copy)}; if (!obj || !define_extra_error_properties(cx, obj)) return nullptr; return obj; } cjs-140.0/gi/struct.h0000664000175000017500000000536215167114161013326 0ustar fabiofabio/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento #pragma once #include #include #include #include #include "gi/boxed.h" #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/macros.h" namespace JS { class CallArgs; } struct JSClassOps; class StructPrototype; class StructInstance; class StructBase : public BoxedBase { friend class CWrapperPointerOps; friend class GIWrapperBase; friend class BoxedBase; friend class BoxedPrototype; friend class BoxedInstance; protected: using BoxedBase::BoxedBase; static constexpr const char* DEBUG_TAG = "boxed"; // for historical reasons static const JSClassOps class_ops; static const JSClass klass; public: static constexpr const GI::InfoTag TAG = GI::InfoTag::STRUCT; }; class StructPrototype : public BoxedPrototype { friend class GIWrapperPrototype; StructPrototype(const GI::StructInfo&, GType); ~StructPrototype(); public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext* cx, JS::HandleObject in_object, const GI::StructInfo&); }; class StructInstance : public BoxedInstance { friend class GIWrapperBase; friend class GIWrapperInstance; StructInstance(StructPrototype*, JS::HandleObject); ~StructInstance(); GJS_JSAPI_RETURN_CONVENTION bool constructor_impl(JSContext*, JS::HandleObject, const JS::CallArgs&); GJS_JSAPI_RETURN_CONVENTION static void* copy_ptr(JSContext* cx, GType gtype, void* ptr) { if (g_type_is_a(gtype, G_TYPE_VARIANT)) return g_variant_ref(static_cast(ptr)); return BoxedInstance::copy_ptr(cx, gtype, ptr); } public: GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext*, const GI::StructInfo&, void* gboxed); GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct(JSContext*, const GI::StructInfo&, void* gboxed, Boxed::NoCopy); }; cjs-140.0/gi/toggle.cpp0000664000175000017500000001520315167114161013611 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)); g_assert(self->m_toggle_handler && "must have been enqueued with handler"); 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; const Item& 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() { debug("shutdown", nullptr); g_assert(q.empty() && "Queue should have been emptied before shutting down"); m_shutdown = true; } void ToggleQueue::enqueue(ObjectInstance* obj, ToggleQueue::Direction direction, 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, since if we're here, * the JSObject wrapper already has 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 objects 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(m_toggle_handler == handler && "Should always enqueue with the same handler"); return; } m_toggle_handler = handler; m_idle_id = g_idle_add_full(G_PRIORITY_HIGH, idle_handle_toggle, this, idle_destroy_notify); } cjs-140.0/gi/toggle.h0000664000175000017500000000673615167114161013271 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 #pragma once #include #include #include #include #include #include // for pair #include // for gboolean class ObjectInstance; namespace Gjs::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 : uint8_t { DOWN, UP }; using Handler = void (*)(ObjectInstance*, Direction); private: friend Gjs::Test::ToggleQueue; struct Item { Item() = default; 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*, Direction); [[nodiscard]] std::deque::const_iterator find_operation_locked( const ObjectInstance*, 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*) const; // Cancels pending toggles and returns whether any were queued. std::pair cancel(ObjectInstance*); /* 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); void handle_all_toggles(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(); // Queues a toggle to be processed in idle time. void enqueue(ObjectInstance*, Direction, Handler); [[nodiscard]] static Locked get_default() { return Locked(&get_default_unlocked()); } }; cjs-140.0/gi/union.cpp0000664000175000017500000000410315167114161013455 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: 2022 Marco Trevisan #include #include #include #include #include "gi/boxed.h" #include "gi/union.h" #include "cjs/mem-private.h" UnionPrototype::UnionPrototype(const GI::UnionInfo& info, GType gtype) : BoxedPrototype(info, gtype) { GJS_INC_COUNTER(union_prototype); } UnionPrototype::~UnionPrototype() { GJS_DEC_COUNTER(union_prototype); } UnionInstance::UnionInstance(UnionPrototype* prototype, JS::HandleObject obj) : BoxedInstance(prototype, obj) { GJS_INC_COUNTER(union_instance); } UnionInstance::~UnionInstance() { GJS_DEC_COUNTER(union_instance); } const struct JSClassOps UnionBase::class_ops = { nullptr, // addProperty nullptr, // deleteProperty nullptr, // enumerate &UnionBase::BoxedBase::new_enumerate, &UnionBase::BoxedBase::resolve, nullptr, // mayResolve &UnionBase::BoxedBase::finalize, nullptr, // call nullptr, // construct &UnionBase::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 UnionBase::klass = { "GObject_Union", JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE, &UnionBase::class_ops}; bool UnionPrototype::define_class(JSContext* cx, JS::HandleObject in_object, const GI::UnionInfo& info) { JS::RootedObject unused{cx}; return BoxedPrototype::define_class_impl(cx, in_object, info, &unused); } JSObject* UnionInstance::new_for_c_union(JSContext* cx, const GI::UnionInfo& info, void* gboxed) { return new_for_c_struct_impl(cx, info, gboxed); } cjs-140.0/gi/union.h0000664000175000017500000000376515167114161013137 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 #pragma once #include #include #include #include "gi/boxed.h" #include "gi/cwrapper.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/macros.h" struct JSClass; struct JSClassOps; class UnionPrototype; class UnionInstance; class UnionBase : public BoxedBase { friend class CWrapperPointerOps; friend class GIWrapperBase; friend class BoxedBase; friend class BoxedPrototype; friend class BoxedInstance; protected: using BoxedBase::BoxedBase; static constexpr const char* DEBUG_TAG = "union"; static const JSClassOps class_ops; static const JSClass klass; public: static constexpr const GI::InfoTag TAG = GI::InfoTag::UNION; }; class UnionPrototype : public BoxedPrototype { friend class GIWrapperPrototype; explicit UnionPrototype(const GI::UnionInfo&, GType); ~UnionPrototype(); public: GJS_JSAPI_RETURN_CONVENTION static bool define_class(JSContext*, JS::HandleObject in_object, const GI::UnionInfo&); }; class UnionInstance : public BoxedInstance { friend class GIWrapperInstance; explicit UnionInstance(UnionPrototype*, JS::HandleObject); ~UnionInstance(); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_union(JSContext*, const GI::UnionInfo&, void* gboxed); }; cjs-140.0/gi/utils-inl.h0000664000175000017500000000334715167114161013723 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 #include #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-140.0/gi/value.cpp0000664000175000017500000013134415167114161013451 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 INT_MAX #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 #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/info.h" #include "gi/js-value-inl.h" #include "gi/object.h" #include "gi/param.h" #include "gi/struct.h" #include "gi/union.h" #include "gi/value.h" #include "gi/wrapperutils.h" #include "cjs/auto.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" using mozilla::Maybe, mozilla::Nothing, mozilla::Some; GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal( JSContext*, JS::MutableHandleValue, const GValue*, bool = false, bool = false, Maybe> = {}); 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(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 known GIArgument conversion for %s", G_VALUE_TYPE_NAME(value)); return false; } GJS_JSAPI_RETURN_CONVENTION static bool maybe_release_signal_value(JSContext* cx, const GI::ArgInfo& arg_info, const GI::TypeInfo& 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, &arg, GjsArgumentFlags::ARG_OUT)) { 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 Nothing 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 Maybe get_signal_info_if_available( const GI::Repository& repo, GSignalQuery* signal_query) { if (!signal_query->itype) return {}; Maybe info{ repo.find_by_gtype(signal_query->itype)}; if (!info) return {}; if (auto object_info = info->as()) return object_info->signal(signal_query->signal_name); if (auto interface_info = info->as()) return interface_info->signal(signal_query->signal_name); return {}; } /* * 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* cx, JS::MutableHandleValue value_p, const GI::TypeInfo& array_type_info, const GValue* array_value, Maybe> array_length_info, const GValue* array_length_value, bool no_copy, bool is_introspected_signal) { JS::RootedValue array_length{cx}; // NOLINTBEGIN(bugprone-assert-side-effect,bugprone-reserved-identifier) // G_VALUE_HOLDS* macros mess up these two checks g_assert(G_VALUE_HOLDS_POINTER(array_value)); g_assert(G_VALUE_HOLDS_INT(array_length_value)); // NOLINTEND(bugprone-assert-side-effect,bugprone-reserved-identifier) if (!gjs_value_from_g_value_internal(cx, &array_length, array_length_value, no_copy, is_introspected_signal, std::move(array_length_info))) return false; GIArgument array_arg; gjs_arg_set(&array_arg, Gjs::gvalue_get(array_value)); return gjs_value_from_explicit_array( cx, value_p, array_type_info, &array_arg, array_length.toInt32(), no_copy ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING); } // 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) { 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; } GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx); if (G_UNLIKELY(!gjs->is_owner_thread()) || JS::RuntimeHeapIsCollecting()) { auto* hint = static_cast(invocation_hint); std::ostringstream message; if (!gjs->is_owner_thread()) { message << "Attempting to call back into JSAPI on a different " "thread. This is most likely caused by an API not " "intended to be used in JS. Because it would crash the " "application, it has been blocked."; } else { 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."; message << "\n" << gjs_dumpstack_string(); } if (hint) { g_signal_query(hint->signal_id, &signal_query); void* 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 << "."; } g_critical("%s", message.str().c_str()); return; } JSAutoRealm ar{m_cx, callable()}; if (marshal_data) { // we are used for a signal handler unsigned 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; // FIXME don't use StackInfo here? Maybe> info; bool skip = false; // Convenience methods, unsafe if there is no introspection info GI::StackArgInfo& arg_info() { return info->first; } GI::StackTypeInfo& type_info() { return info->second; } }; std::vector args_details(n_param_values); bool needs_cleanup = false; GI::Repository repo; Maybe signal_info{ get_signal_info_if_available(repo, &signal_query)}; if (signal_info) { // Start at argument 1, skip the instance parameter for (unsigned i = 1; i < n_param_values; ++i) { ArgumentDetails& arg_details = args_details[i]; arg_details.info.emplace(); signal_info->load_arg(i - 1, &arg_details.arg_info()); arg_details.info->first.load_type(&arg_details.type_info()); Maybe array_len_pos = arg_details.type_info().array_length_index(); if (array_len_pos) { args_details[*array_len_pos + 1].skip = true; arg_details.array_len_index_for = *array_len_pos + 1; } if (!needs_cleanup && arg_details.arg_info().ownership_transfer() != GI_TRANSFER_NOTHING) needs_cleanup = true; } } JS::RootedValueVector argv{m_cx}; // May end up being less if (!argv.reserve(n_param_values)) g_error("Unable to reserve space"); JS::RootedValue argv_to_append{m_cx}; bool is_introspected_signal = !!signal_info; for (unsigned i = 0; i < n_param_values; ++i) { const GValue* gval = ¶m_values[i]; ArgumentDetails& arg_details = args_details[i]; bool res; if (arg_details.skip) continue; bool 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( // FIXME: what if no type_info m_cx, &argv_to_append, arg_details.type_info(), gval, array_len_details.info, array_len_gval, no_copy, is_introspected_signal); } else { res = gjs_value_from_g_value_internal( m_cx, &argv_to_append, gval, no_copy, is_introspected_signal, arg_details.info); } if (!res) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert arg %d in order to invoke closure", i); gjs_log_exception(m_cx); return; } argv.infallibleAppend(argv_to_append); } JS::RootedValue rval{m_cx}; if (!invoke(nullptr, argv, &rval)) { if (JS_IsExceptionPending(m_cx)) { gjs_log_exception_uncaught(m_cx); } 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 (unsigned i = 0; i < n_param_values; ++i) { ArgumentDetails& arg_details = args_details[i]; if (!arg_details.info) continue; GITransfer transfer = arg_details.arg_info().ownership_transfer(); if (transfer == GI_TRANSFER_NOTHING) continue; if (!maybe_release_signal_value(m_cx, arg_details.arg_info(), arg_details.type_info(), ¶m_values[i], transfer)) { gjs_log_exception(m_cx); 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 != nullptr) { 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(m_cx, rval, return_value)) { gjs_debug(GJS_DEBUG_GCLOSURE, "Unable to convert return value when invoking closure"); gjs_log_exception(m_cx); return; } } } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_guess_g_type(JSContext* cx, 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{cx, &value.toObject()}; return gjs_gtype_get_actual_gtype(cx, 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* cx, JS::HandleValue value, GValue* gvalue, bool no_copy) { bool out_of_range = false; GType gtype = G_VALUE_TYPE(gvalue); if (value.isObject()) { JS::RootedObject obj{cx, &value.toObject()}; GType boxed_gtype; if (!gjs_gtype_get_actual_gtype(cx, 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( cx, "Cannot convert GObject.Value object without copying."); return false; } GValue* source = StructBase::to_c_ptr(cx, 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(cx, "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(cx, "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(cx, value, >ype)) return false; if (gtype == G_TYPE_INVALID) { gjs_throw(cx, "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()) { Gjs::gvalue_set(gvalue, nullptr); return true; } if (value.isString()) { JS::RootedString str{cx, value.toString()}; JS::UniqueChars utf8_string{JS_EncodeStringToUTF8(cx, str)}; if (!utf8_string) return false; Gjs::gvalue_set(gvalue, utf8_string.get()); return true; } return throw_expect_type(cx, value, "string"); } if (gtype == G_TYPE_CHAR) { int32_t i; if (Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range) && !out_of_range) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "char", 0, out_of_range); } if (gtype == G_TYPE_UCHAR) { uint32_t i; if (Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range) && !out_of_range) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "unsigned char", 0, out_of_range); } if (gtype == G_TYPE_INT) { int32_t i; if (Gjs::js_value_to_c(cx, value, &i)) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "integer"); } if (gtype == G_TYPE_INT64) { int64_t i; if (Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range) && !out_of_range) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "64-bit integer", 0, out_of_range); } if (gtype == G_TYPE_DOUBLE) { double d; if (Gjs::js_value_to_c(cx, value, &d)) { Gjs::gvalue_set(gvalue, d); return true; } return throw_expect_type(cx, value, "double"); } if (gtype == G_TYPE_FLOAT) { double d; if (Gjs::js_value_to_c_checked(cx, value, &d, &out_of_range) && !out_of_range) { Gjs::gvalue_set(gvalue, d); return true; } return throw_expect_type(cx, value, "float", 0, out_of_range); } if (gtype == G_TYPE_UINT) { uint32_t i; if (Gjs::js_value_to_c(cx, value, &i)) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "unsigned integer"); } if (gtype == G_TYPE_UINT64) { uint64_t i; if (Gjs::js_value_to_c_checked(cx, value, &i, &out_of_range) && !out_of_range) { Gjs::gvalue_set(gvalue, i); return true; } return throw_expect_type(cx, value, "unsigned 64-bit integer", 0, out_of_range); } if (gtype == G_TYPE_BOOLEAN) { // JS::ToBoolean() can't fail Gjs::gvalue_set(gvalue, JS::ToBoolean(value)); return true; } if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { GObject* gobj = nullptr; if (value.isNull()) { // nothing to do } else if (value.isObject()) { JS::RootedObject obj{cx, &value.toObject()}; if (!ObjectBase::typecheck(cx, obj, gtype) || !ObjectBase::to_c_ptr(cx, obj, &gobj)) return false; if (!gobj) return true; // treat disposed object as if value.isNull() } else { return throw_expect_type(cx, value, "object", gtype); } Gjs::gvalue_set(gvalue, gobj); return true; } if (gtype == G_TYPE_STRV) { if (value.isNull()) return true; bool is_array; if (!JS::IsArrayObject(cx, value, &is_array)) return false; if (!is_array) return throw_expect_type(cx, value, "strv"); JS::RootedObject array_obj{cx, &value.toObject()}; uint32_t length; if (!JS::GetArrayLength(cx, array_obj, &length)) return throw_expect_type(cx, value, "strv"); void* result; if (!gjs_array_to_strv(cx, value, length, &result)) return false; g_value_take_boxed(gvalue, result); return true; } if (g_type_is_a(gtype, G_TYPE_BOXED)) { if (value.isNull()) return true; void* gboxed = nullptr; // 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{cx, &value.toObject()}; GType guessed_gtype; if (!gjs_value_guess_g_type(cx, value, &guessed_gtype)) return false; if (guessed_gtype == G_TYPE_VALUE) { gboxed = StructBase::to_c_ptr(cx, obj); g_value_set_boxed(gvalue, gboxed); return true; } } Gjs::AutoGValue nested_gvalue; if (!gjs_value_to_g_value(cx, value, &nested_gvalue)) return false; g_value_set_boxed(gvalue, &nested_gvalue); return true; } if (value.isObject()) { JS::RootedObject obj{cx, &value.toObject()}; if (gtype == ObjectBox::gtype()) { g_value_set_boxed(gvalue, ObjectBox::boxed(cx, obj).get()); return true; } if (gtype == G_TYPE_ARRAY || gtype == G_TYPE_PTR_ARRAY || gtype == G_TYPE_HASH_TABLE) { gjs_throw(cx, "Converting %s to %s is not supported", JS::InformalValueTypeName(value), g_type_name(gtype)); return false; } if (gtype == G_TYPE_ERROR) { // special case GError gboxed = ErrorBase::to_c_ptr(cx, obj); if (!gboxed) return false; } else if (gtype == G_TYPE_BYTE_ARRAY) { // special case GByteArray if (JS_IsUint8Array(obj)) { g_value_take_boxed(gvalue, gjs_byte_array_get_byte_array(obj)); return true; } } else { GI::Repository repo; Maybe registered{ repo.find_by_gtype(gtype)}; // We don't necessarily have the typelib loaded when we first // see the structure... if (registered) { if (auto struct_info = registered->as(); struct_info && struct_info->is_foreign()) { GIArgument arg; if (!gjs_struct_foreign_convert_to_gi_argument( cx, value, struct_info.value(), 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 necessarily possible, // if e.g. we see the GType without loading the typelib. if (!gboxed) { if (UnionBase::typecheck(cx, obj, gtype, GjsTypecheckNoThrow{})) { gboxed = UnionBase::to_c_ptr(cx, obj); } else { if (!StructBase::typecheck(cx, obj, gtype)) return false; gboxed = StructBase::to_c_ptr(cx, obj); } if (!gboxed) return false; } } } else { return throw_expect_type(cx, value, "boxed type", gtype); } if (no_copy) g_value_set_static_boxed(gvalue, gboxed); else g_value_set_boxed(gvalue, gboxed); return true; } if (gtype == G_TYPE_VARIANT) { GVariant* variant = nullptr; if (value.isNull()) { // nothing to do } else if (value.isObject()) { JS::RootedObject obj{cx, &value.toObject()}; if (!StructBase::typecheck(cx, obj, G_TYPE_VARIANT)) return false; variant = StructBase::to_c_ptr(cx, obj); if (!variant) return false; } else { return throw_expect_type(cx, value, "boxed type", gtype); } Gjs::gvalue_set(gvalue, variant); return true; } if (g_type_is_a(gtype, G_TYPE_ENUM)) { int64_t value_int64; if (Gjs::js_value_to_c(cx, value, &value_int64)) { Gjs::AutoTypeClass enum_class{gtype}; // See arg.c:_gjs_enum_to_int() GEnumValue* v = g_enum_get_value(enum_class, static_cast(value_int64)); if (v == nullptr) { gjs_throw(cx, "%d is not a valid value for enumeration %s", value.toInt32(), g_type_name(gtype)); return false; } g_value_set_enum(gvalue, v->value); return true; } return throw_expect_type(cx, value, "enum", gtype); } if (g_type_is_a(gtype, G_TYPE_FLAGS)) { int64_t value_int64; if (Gjs::js_value_to_c(cx, value, &value_int64)) { if (!gjs_flags_value_is_valid(cx, gtype, value_int64)) return false; // See arg.c:_gjs_enum_to_int() g_value_set_flags(gvalue, static_cast(value_int64)); return true; } return throw_expect_type(cx, value, "flags", gtype); } if (g_type_is_a(gtype, G_TYPE_PARAM)) { GParamSpec* gparam = nullptr; if (value.isNull()) { // nothing to do } else if (value.isObject()) { JS::RootedObject obj{cx, &value.toObject()}; if (!gjs_typecheck_param(cx, obj, gtype, true)) return false; gparam = gjs_g_param_from_param(cx, obj); } else { return throw_expect_type(cx, value, "param type", gtype); } g_value_set_param(gvalue, gparam); return true; } if (gtype == G_TYPE_GTYPE) { GType type; if (!value.isObject()) return throw_expect_type(cx, value, "GType object"); JS::RootedObject obj{cx, &value.toObject()}; if (!gjs_gtype_get_actual_gtype(cx, obj, &type)) return false; Gjs::gvalue_set(gvalue, type); return true; } if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (value.isNull()) return true; // Nothing to do gjs_throw(cx, "Cannot convert non-null JS value to G_POINTER"); return false; } 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. */ int32_t i; if (Gjs::js_value_to_c(cx, value, &i)) { GValue int_value = { 0, }; g_value_init(&int_value, G_TYPE_INT); Gjs::gvalue_set(&int_value, i); g_value_transform(&int_value, gvalue); return true; } return throw_expect_type(cx, value, "integer"); } 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(cx, value, "object", gtype); JS::RootedObject fundamental_object{cx, &value.toObject()}; return FundamentalBase::to_gvalue(cx, fundamental_object, gvalue); } 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(cx, "Don't know how to convert JavaScript object to GType %s", g_type_name(gtype)); return false; } bool gjs_value_to_g_value(JSContext* cx, JS::HandleValue value, GValue* gvalue) { return gjs_value_to_g_value_internal(cx, value, gvalue, false); } bool gjs_value_to_g_value_no_copy(JSContext* cx, JS::HandleValue value, GValue* gvalue) { return gjs_value_to_g_value_internal(cx, value, gvalue, true); } [[nodiscard]] static JS::Value convert_int_to_enum(const GI::Repository& repo, GType gtype, int64_t v) { double v_double; if (v > 0 && v < INT_MAX) { // Optimize the unambiguous case v_double = v; } else { // Need to distinguish between negative integers and unsigned integers Maybe info{ repo.find_by_gtype(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 = v; } else { v_double = info->enum_from_int(v); } } return JS::NumberValue(v_double); } GJS_JSAPI_RETURN_CONVENTION static bool gjs_value_from_g_value_internal( JSContext* cx, JS::MutableHandleValue value_p, const GValue* gvalue, bool no_copy, bool is_introspected_signal, Maybe> introspection_info) { 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; } switch (gtype) { case G_TYPE_CHAR: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_UCHAR: return Gjs::c_value_to_js( cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_INT: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_UINT: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_LONG: return Gjs::c_value_to_js( cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_ULONG: return Gjs::c_value_to_js( cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_INT64: return Gjs::c_value_to_js_checked( cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_UINT64: return Gjs::c_value_to_js_checked( cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_DOUBLE: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_FLOAT: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_BOOLEAN: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); case G_TYPE_STRING: return Gjs::c_value_to_js(cx, Gjs::gvalue_get(gvalue), value_p); default: { } } if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) { return ObjectInstance::set_value_from_gobject( cx, Gjs::gvalue_get(gvalue), value_p); } if (gtype == G_TYPE_STRV) { if (!gjs_array_from_strv(cx, value_p, Gjs::gvalue_get(gvalue))) { gjs_throw(cx, "Failed to convert strv to array"); return false; } return true; } if (gtype == G_TYPE_BYTE_ARRAY) { auto* byte_array = static_cast(g_value_get_boxed(gvalue)); JSObject* array = gjs_byte_array_from_byte_array(cx, byte_array); if (!array) { gjs_throw(cx, "Couldn't convert GByteArray to a Uint8Array"); return false; } value_p.setObject(*array); return true; } if (gtype == G_TYPE_ARRAY || gtype == G_TYPE_PTR_ARRAY) { if (!is_introspected_signal || !introspection_info) { gjs_throw(cx, "Can't convert untyped array to JS value"); return false; } const GI::ArgInfo arg_info = introspection_info->first; const GI::TypeInfo type_info = introspection_info->second; if (!gjs_array_from_g_value_array(cx, value_p, type_info.element_type(), arg_info.ownership_transfer(), gvalue)) { gjs_throw(cx, "Failed to convert array"); return false; } return true; } if (gtype == G_TYPE_HASH_TABLE) { if (!introspection_info) { gjs_throw(cx, "Failed to get GValue from Hash Table without signal " "information"); return false; } const GI::ArgInfo arg_info = introspection_info->first; const GI::TypeInfo type_info = introspection_info->second; GI::AutoTypeInfo key_info{type_info.key_type()}; GI::AutoTypeInfo value_info{type_info.value_type()}; GITypeTag key_tag = key_info.tag(); GITypeTag val_tag = value_info.tag(); auto* ghash = Gjs::gvalue_get(gvalue); if (GI_TYPE_TAG_IS_BASIC(key_tag) && GI_TYPE_TAG_IS_BASIC(val_tag)) { if (!gjs_value_from_basic_ghash(cx, value_p, key_tag, val_tag, ghash)) return false; } else if (!gjs_object_from_g_hash(cx, value_p, key_info, value_info, arg_info.ownership_transfer(), ghash)) { gjs_throw(cx, "Failed to convert Hash Table"); return false; } return true; } if (g_type_is_a(gtype, G_TYPE_BOXED) || gtype == G_TYPE_VARIANT) { if (g_type_is_a(gtype, ObjectBox::gtype())) { JSObject* obj = ObjectBox::object_for_c_ptr( cx, Gjs::gvalue_get(gvalue)); if (!obj) return false; value_p.setObject(*obj); return true; } // special case GError if (gtype == G_TYPE_ERROR) { JSObject* obj = ErrorInstance::object_for_c_ptr( cx, Gjs::gvalue_get(gvalue)); if (!obj) return false; value_p.setObject(*obj); return true; } // special case GValue if (gtype == G_TYPE_VALUE) { return gjs_value_from_g_value(cx, value_p, Gjs::gvalue_get(gvalue)); } /* The only way to differentiate unions and structs is from their g-i * info as both are GBoxed */ GI::Repository repo; Maybe info{repo.find_by_gtype(gtype)}; if (!info) { gjs_throw(cx, "No introspection information found for %s", g_type_name(gtype)); return false; } JSObject* obj; void* gboxed = Gjs::gvalue_get(gvalue); if (auto struct_info = info->as()) { if (struct_info->is_foreign()) { GIArgument arg; gjs_arg_set(&arg, gboxed); return gjs_struct_foreign_convert_from_gi_argument( cx, value_p, struct_info.value(), &arg); } if (no_copy) { obj = StructInstance::new_for_c_struct(cx, struct_info.value(), gboxed, Boxed::NoCopy{}); } else { obj = StructInstance::new_for_c_struct(cx, struct_info.value(), gboxed); } } else if (auto union_info = info->as()) { obj = UnionInstance::new_for_c_union(cx, union_info.value(), gboxed); } else { gjs_throw(cx, "Unexpected introspection type %s for %s", info->type_string(), g_type_name(gtype)); return false; } if (!obj) return false; value_p.setObject(*obj); return true; } if (g_type_is_a(gtype, G_TYPE_ENUM)) { GI::Repository repo; value_p.set(convert_int_to_enum( repo, gtype, Gjs::gvalue_get(gvalue))); return true; } if (g_type_is_a(gtype, G_TYPE_PARAM)) { GParamSpec* gparam = Gjs::gvalue_get(gvalue); JSObject* obj = gjs_param_from_g_param(cx, gparam); if (!obj) return false; value_p.setObject(*obj); return true; } if (is_introspected_signal && g_type_is_a(gtype, G_TYPE_POINTER)) { if (!introspection_info) { gjs_throw(cx, "Unknown signal."); return false; } const GI::TypeInfo type_info = introspection_info->second; g_assert(!type_info.array_length_index() && "Check gjs_value_from_array_and_length_values() before " "calling gjs_value_from_g_value_internal()"); GIArgument arg; gjs_arg_set(&arg, Gjs::gvalue_get(gvalue)); return gjs_value_from_gi_argument(cx, value_p, type_info, &arg, true); } if (gtype == G_TYPE_GTYPE) { GType gvalue_gtype = Gjs::gvalue_get(gvalue); if (gvalue_gtype == 0) { value_p.setNull(); return true; } JS::RootedObject obj{cx, gjs_gtype_create_gtype_wrapper(cx, gvalue_gtype)}; if (!obj) return false; value_p.setObject(*obj); return true; } if (g_type_is_a(gtype, G_TYPE_POINTER)) { if (Gjs::gvalue_get(gvalue) != nullptr) { gjs_throw(cx, "Can't convert non-null pointer to JS value"); return false; } return true; } if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) { GValue double_value = { 0, }; g_value_init(&double_value, G_TYPE_DOUBLE); g_value_transform(gvalue, &double_value); return Gjs::c_value_to_js(cx, Gjs::gvalue_get(&double_value), value_p); } if (g_value_type_transformable(gtype, G_TYPE_INT)) { GValue int_value = { 0, }; g_value_init(&int_value, G_TYPE_INT); g_value_transform(gvalue, &int_value); return Gjs::c_value_to_js(cx, Gjs::gvalue_get(&int_value), value_p); } if (G_TYPE_IS_INSTANTIATABLE(gtype)) { // The gtype is none of the above, it should be a custom fundamental // type. JS::RootedObject obj{cx}; if (!FundamentalInstance::object_for_gvalue(cx, gvalue, gtype, &obj)) return false; value_p.setObject(*obj); return true; } gjs_throw(cx, "Don't know how to convert GType %s to JavaScript object", g_type_name(gtype)); return false; } bool gjs_value_from_g_value(JSContext* cx, JS::MutableHandleValue value_p, const GValue* gvalue) { return gjs_value_from_g_value_internal(cx, value_p, gvalue, false); } cjs-140.0/gi/value.h0000664000175000017500000002201115167114161013104 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 #pragma once #include #include #include // for nullptr_t #include // for ostringstream #include // for string #include #include // for move, swap #include // for vector #include #include // for FALSE, g_clear_pointer, g_free, g_variant_... // IWYU pragma: no_forward_declare _GHashTable #include #include "gi/arg-types-inl.h" #include "gi/utils-inl.h" #include "cjs/auto.h" #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: *this = 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); } }; /* This is based on what GMarshalling does, it is an unsupported API but GJS can * be considered a GLib implementation for JS, so it is fine to do this, but we * need to be in sync with gmarshal.c in GLib. * https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gmarshal.c */ template constexpr Tag::RealT gvalue_get(const GValue* gvalue) { if constexpr (std::is_same_v || std::is_same_v) return gvalue->data[0].v_int != FALSE; else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) return gvalue->data[0].v_int; else if constexpr (std::is_same_v || std::is_same_v) return gvalue->data[0].v_uint; else if constexpr (std::is_same_v || std::is_same_v) return gvalue->data[0].v_long; else if constexpr (std::is_same_v || std::is_same_v) return gvalue->data[0].v_ulong; else if constexpr (std::is_same_v) return gvalue->data[0].v_int64; else if constexpr (std::is_same_v) return gvalue->data[0].v_uint64; else if constexpr (std::is_same_v) return gvalue->data[0].v_float; else if constexpr (std::is_same_v) return gvalue->data[0].v_double; else if constexpr (std::is_same_v) return gjs_pointer_to_int(gvalue->data[0].v_pointer); else if constexpr (!std::is_pointer_v) static_assert(std::is_pointer_v, "Scalar type not properly handled"); else return static_cast(gvalue->data[0].v_pointer); } template void gvalue_set(GValue* gvalue, Tag::RealT value) { if constexpr (std::is_same_v) gvalue->data[0].v_int = value != FALSE; else if constexpr (std::is_same_v) gvalue->data[0].v_int = value != false; else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) gvalue->data[0].v_int = value; else if constexpr (std::is_same_v || std::is_same_v) gvalue->data[0].v_uint = value; else if constexpr (std::is_same_v || std::is_same_v) gvalue->data[0].v_long = value; else if constexpr (std::is_same_v || std::is_same_v) gvalue->data[0].v_ulong = value; else if constexpr (std::is_same_v) gvalue->data[0].v_int64 = value; else if constexpr (std::is_same_v) gvalue->data[0].v_uint64 = value; else if constexpr (std::is_same_v) gvalue->data[0].v_float = value; else if constexpr (std::is_same_v) gvalue->data[0].v_double = value; else if constexpr (std::is_same_v) gvalue->data[0].v_pointer = gjs_int_to_pointer(value); else static_assert(!std::is_scalar_v>, "Scalar type not properly handled"); } // Specialization for types where TAG and RealT are the same type, to allow // inferring template parameter template , T>>> inline void gvalue_set(GValue* gvalue, T value) { gvalue_set(gvalue, value); } template void gvalue_set(GValue* gvalue, T* value) = delete; template <> inline void gvalue_set(GValue* gvalue, char* value) { g_clear_pointer(&gvalue->data[0].v_pointer, g_free); gvalue->data[0].v_pointer = g_strdup(value); } template <> inline void gvalue_set(GValue* gvalue, GObject* value) { // NOLINTNEXTLINE(bugprone-sizeof-expression) g_set_object(&gvalue->data[0].v_pointer, value); } template <> inline void gvalue_set(GValue* gvalue, GVariant* value) { g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), g_variant_unref); gvalue->data[0].v_pointer = value ? g_variant_ref(value) : nullptr; } template void gvalue_set(GValue* gvalue, std::nullptr_t) { if constexpr (std::is_same_v) { g_clear_pointer(&gvalue->data[0].v_pointer, g_free); } else if constexpr (std::is_same_v) { // NOLINTNEXTLINE(bugprone-sizeof-expression) g_set_object(&gvalue->data[0].v_pointer, nullptr); } else if constexpr (std::is_same_v) { g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), g_variant_unref); gvalue->data[0].v_pointer = nullptr; } else { static_assert(!std::is_pointer_v, "Not a known pointer type"); } } template void gvalue_take(GValue* gvalue, Tag::RealT value) { using T = Tag::RealT; if constexpr (!std::is_pointer_v) { return gvalue_set(gvalue, value); } if constexpr (std::is_same_v) { g_clear_pointer(&gvalue->data[0].v_pointer, g_free); gvalue->data[0].v_pointer = g_steal_pointer(&value); } else if constexpr (std::is_same_v) { g_clear_object(&gvalue->data[0].v_pointer); gvalue->data[0].v_pointer = g_steal_pointer(&value); } else if constexpr (std::is_same_v) { g_clear_pointer(reinterpret_cast(&gvalue->data[0].v_pointer), g_variant_unref); gvalue->data[0].v_pointer = value; } else { static_assert(!std::is_pointer_v, "Not a known pointer type"); } } template std::string gvalue_to_string(GValue* gvalue) { using std::string_literals::operator""s; std::string str{"GValue of type "s + G_VALUE_TYPE_NAME(gvalue) + ": "}; if constexpr (std::is_same_v) { str += "\""s + Gjs::gvalue_get(gvalue) + '"'; } else if constexpr (std::is_same_v) { AutoChar variant{g_variant_print(Gjs::gvalue_get(gvalue), true)}; str += "<"s + variant.get() + '>'; } else if constexpr (std::is_arithmetic_v) { str += std::to_string(Gjs::gvalue_get(gvalue)); } else { std::ostringstream out; out << Gjs::gvalue_get(gvalue); str += out.str(); } return str; } } // namespace Gjs using AutoGValueVector = std::vector; GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value(JSContext*, JS::HandleValue, GValue*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_to_g_value_no_copy(JSContext*, JS::HandleValue, GValue*); GJS_JSAPI_RETURN_CONVENTION bool gjs_value_from_g_value(JSContext*, JS::MutableHandleValue, const GValue*); cjs-140.0/gi/wrapperutils.cpp0000664000175000017500000000753715167114161015104 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 #include "gi/function.h" #include "gi/info.h" #include "gi/wrapperutils.h" #include "cjs/jsapi-util.h" using mozilla::Maybe; /* 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* cx, JSObject* this_obj, const char* objtype, const Maybe& 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:" << info->ns() << "." << info->name(); } else { out << " GType:" << g_type_name(gtype); } out << " jsobj@" << this_obj; if (native_address) out << " native@" << native_address; out << ']'; return gjs_string_from_utf8(cx, 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; } template bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, GType gtype, const GI::UnownedInfo& info) { for (const GI::AutoFunctionInfo& meth_info : info.methods()) { // 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 (!meth_info.is_method()) { if (!gjs_define_function(cx, constructor, gtype, meth_info)) return false; } } // Also define class/interface methods if there is a gtype struct Maybe type_struct; if constexpr (TAG == GI::InfoTag::OBJECT) type_struct = info.class_struct(); else if constexpr (TAG == GI::InfoTag::INTERFACE) type_struct = info.iface_struct(); if (!type_struct) return true; auto iter = type_struct->methods(); return std::all_of( iter.begin(), iter.end(), [cx, constructor, gtype](const GI::AutoFunctionInfo& meth_info) { return gjs_define_function(cx, constructor, gtype, meth_info); }); } // All possible instantiations are needed template bool gjs_define_static_methods( JSContext*, JS::HandleObject constructor, GType, const GI::EnumInfo&); template bool gjs_define_static_methods( JSContext*, JS::HandleObject constructor, GType, const GI::InterfaceInfo&); template bool gjs_define_static_methods( JSContext*, JS::HandleObject constructor, GType, const GI::ObjectInfo&); template bool gjs_define_static_methods( JSContext*, JS::HandleObject constructor, GType, const GI::StructInfo&); template bool gjs_define_static_methods( JSContext*, JS::HandleObject constructor, GType, const GI::UnionInfo&); cjs-140.0/gi/wrapperutils.h0000664000175000017500000013214015167114161014536 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 #pragma once #include #include #include // for operator new #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 #include "gi/arg-inl.h" #include "gi/cwrapper.h" #include "gi/info.h" #include "cjs/atoms.h" #include "cjs/auto.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*, JSObject* this_obj, const char* objtype, const mozilla::Maybe&, GType, const void* native_address, JS::MutableHandleValue ret); // Needed because some of the templates don't have Maybe as their info() type GJS_JSAPI_RETURN_CONVENTION static inline bool gjs_wrapper_to_string_func(JSContext* cx, JSObject* this_obj, const char* objtype, const GI::BaseInfo& info, GType gtype, const void* native_address, JS::MutableHandleValue ret) { return gjs_wrapper_to_string_func( cx, this_obj, objtype, mozilla::Some(info), gtype, native_address, ret); } bool gjs_wrapper_throw_nonexistent_field(JSContext*, GType, const char* field_name); bool gjs_wrapper_throw_readonly_field(JSContext*, GType, const char* field_name); namespace MemoryUse { constexpr JS::MemoryUse GObjectInstanceStruct = JS::MemoryUse::Embedding1; } struct GjsTypecheckNoThrow {}; // Some types of introspected wrapper permit creating a new type from JS (e.g., // objects, interfaces.) These JS-created types do not have introspection info // and so their GIWrapperPrototype::info() methods return Maybe. // Others do not permit creating a new type from JS (e.g., enums, boxeds.) These // have GIWrapperPrototype::info() methods that return const FooInfo directly. // Sometimes we need to have different code for the two cases. template struct is_maybe : std::false_type {}; template struct is_maybe> : std::true_type {}; /** * gjs_define_static_methods: * * Defines all static methods from @info on @constructor. Also includes class * methods for GI::ObjectInfo, and interface methods for GI::InterfaceInfo. */ template GJS_JSAPI_RETURN_CONVENTION bool gjs_define_static_methods(JSContext*, JS::HandleObject constructor, GType, const GI::UnownedInfo&); template GJS_JSAPI_RETURN_CONVENTION inline bool gjs_define_static_methods(JSContext* cx, JS::HandleObject constructor, GType gtype, const GI::OwnedInfo& info) { return gjs_define_static_methods(cx, constructor, gtype, GI::UnownedInfo{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]] auto 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 { if constexpr (Prototype::may_not_have_info) { const auto i = info(); return i ? i->ns() : ""; } else { return info().ns(); } } [[nodiscard]] const char* name() const { if constexpr (Prototype::may_not_have_info) { const auto i = info(); return i ? i->name() : type_name(); } else { return info().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", this, Base::DEBUG_TAG, ptr_addr(), format_name().c_str(), 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", this, Base::DEBUG_TAG, ptr_addr(), obj, format_name().c_str(), 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'", this, Base::DEBUG_TAG, ptr_addr(), obj, format_name().c_str(), 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 full_name{ GJS_PROFILER_DYNAMIC_STRING(cx, priv->format_name())}; AutoProfilerLabel label{cx, "constructor", full_name}; 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.prototype; only on instances", for_what, format_name().c_str()); 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) { g_assert(transfer_direction != GI_DIRECTION_INOUT && "transfer_to_gi_argument() must choose between in or out"); if (expected_gtype != G_TYPE_NONE && !Base::typecheck(cx, obj, 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, const GI::BaseInfo& expected_info) { 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(expected_info)) return true; gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object is of type %s - cannot convert to %s.%s", priv->format_name().c_str(), expected_info.ns(), expected_info.name()); return false; } GJS_JSAPI_RETURN_CONVENTION static bool typecheck(JSContext* cx, JS::HandleObject object, 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(expected_gtype)) return true; gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr, "Object is of type %s - cannot convert to %s", priv->format_name().c_str(), g_type_name(expected_gtype)); return false; } template [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject object, const T& expected, GjsTypecheckNoThrow) { Base* priv = Base::for_js(cx, object); if (!priv || priv->is_prototype()) return false; return priv->to_instance()->typecheck_impl(expected); } // 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 = Gjs::AutoPointer; 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. OwnedInfo m_info; GType m_gtype; explicit GIWrapperPrototype(const UnownedInfo& info, GType gtype) : Base(), m_info(info), 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 static bool get_parent_proto(JSContext*, JS::MutableHandleObject proto) { 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; if constexpr (may_not_have_info) gi_namespace = Base::info() ? Base::ns() : "unknown"; else gi_namespace = Base::ns(); 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. */ GJS_JSAPI_RETURN_CONVENTION bool define_static_methods(JSContext* cx, JS::HandleObject constructor) { if constexpr (may_not_have_info) { if (!info()) return true; // no introspection means no methods to define return gjs_define_static_methods(cx, constructor, m_gtype, info().value()); } else { return gjs_define_static_methods(cx, constructor, m_gtype, m_info); } } GJS_JSAPI_RETURN_CONVENTION static Prototype* create_prototype(const UnownedInfo& 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, const UnownedInfo& 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, const UnownedInfo& 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; Gjs::AutoChar 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 static constexpr bool may_not_have_info = is_maybe::value; [[nodiscard]] UnownedInfo 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() { g_atomic_rc_box_acquire(this); return static_cast(this); } void release() { 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; namespace Gjs { template <> struct SmartPointer : AutoPointer { using AutoPointer::AutoPointer; }; } // namespace Gjs /** * 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: Gjs::SmartPointer 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() { 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(const GI::BaseInfo& expected_info) const { if constexpr (Prototype::may_not_have_info) { if (Base::info()) return Base::info().ref() == expected_info; } else { return Base::info() == expected_info; } return true; } [[nodiscard]] bool typecheck_impl(GType expected_gtype) const { g_assert(expected_gtype != G_TYPE_NONE && "should not call typecheck_impl() without a real GType"); return g_type_is_a(Base::gtype(), expected_gtype); } }; cjs-140.0/gjs.doap0000664000175000017500000000266615167114161012666 0ustar fabiofabio gjs gjs GNOME JavaScript bindings GNOME JavaScript bindings C++ Philip Chimento ptomato pchimento cjs-140.0/installed-tests/0000775000175000017500000000000015167114161014343 5ustar fabiofabiocjs-140.0/installed-tests/debugger-test.sh0000775000175000017500000000251615167114161017447 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento stringContain() { case $2 in *$1* ) return 0;; *) return 1;; esac ;} 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" if stringContain "module" "$JS_SCRIPT" ; then # module specifiers are canonicalized into absolute paths THE_DIFF=$("$gjs" -d -m "$JS_SCRIPT" < "$DEBUGGER_SCRIPT" | sed \ -e "s#file://$(realpath "$1")#$(basename "$1")#g" \ -e "s/file:\/\/.*sourcemap-number-module.js/sourcemap-number-module.js/g" \ -e "s/0x[0-9a-f]\{4,16\}/0xADDR/g" \ -e "s/[0-9][0-9.]* ms/XXXX ms/g" \ | diff -u "$EXPECTED_OUTPUT" -) else 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" \ -e "s/[0-9][0-9.]* ms/XXXX ms/g" \ | diff -u "$EXPECTED_OUTPUT" -) fi EXITCODE=$? if test -n "$THE_DIFF"; then echo "not ok 1 - $1" echo "$THE_DIFF" | while read -r 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-140.0/installed-tests/debugger.test.in0000664000175000017500000000037115167114161017436 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-140.0/installed-tests/debugger/0000775000175000017500000000000015167114161016127 5ustar fabiofabiocjs-140.0/installed-tests/debugger/backtrace.debugger0000664000175000017500000000041415167114161021553 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-140.0/installed-tests/debugger/backtrace.debugger.js0000664000175000017500000000062415167114161022171 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-140.0/installed-tests/debugger/backtrace.debugger.output0000664000175000017500000000307215167114161023115 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-140.0/installed-tests/debugger/breakpoint.debugger0000664000175000017500000000024015167114161021767 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-140.0/installed-tests/debugger/breakpoint.debugger.js0000664000175000017500000000033215167114161022404 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-140.0/installed-tests/debugger/breakpoint.debugger.output0000664000175000017500000000106215167114161023331 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-140.0/installed-tests/debugger/continue.debugger0000664000175000017500000000021715167114161021461 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento continue cont c cjs-140.0/installed-tests/debugger/continue.debugger.js0000664000175000017500000000022515167114161022073 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento debugger; debugger; cjs-140.0/installed-tests/debugger/continue.debugger.output0000664000175000017500000000052315167114161023020 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-140.0/installed-tests/debugger/delete.debugger0000664000175000017500000000034115167114161021075 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-140.0/installed-tests/debugger/delete.debugger.js0000664000175000017500000000027515167114161021516 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-140.0/installed-tests/debugger/delete.debugger.output0000664000175000017500000000126315167114161022440 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-140.0/installed-tests/debugger/detach.debugger0000664000175000017500000000020615167114161021063 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento detach cjs-140.0/installed-tests/debugger/detach.debugger.js0000664000175000017500000000021615167114161021477 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('hi'); cjs-140.0/installed-tests/debugger/detach.debugger.output0000664000175000017500000000032315167114161022422 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-140.0/installed-tests/debugger/down-up.debugger0000664000175000017500000000024515167114161021227 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-140.0/installed-tests/debugger/down-up.debugger.js0000664000175000017500000000036715167114161021647 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-140.0/installed-tests/debugger/down-up.debugger.output0000664000175000017500000000151415167114161022566 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-140.0/installed-tests/debugger/finish.debugger0000664000175000017500000000022015167114161021107 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c finish c fin c cjs-140.0/installed-tests/debugger/finish.debugger.js0000664000175000017500000000054515167114161021534 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-140.0/installed-tests/debugger/finish.debugger.output0000664000175000017500000000116115167114161022453 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-140.0/installed-tests/debugger/frame.debugger0000664000175000017500000000021715167114161020727 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento c frame 2 f 1 c cjs-140.0/installed-tests/debugger/frame.debugger.js0000664000175000017500000000030115167114161021334 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-140.0/installed-tests/debugger/frame.debugger.output0000664000175000017500000000057315167114161022273 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-140.0/installed-tests/debugger/keys.debugger0000664000175000017500000000031315167114161020605 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 keys b c cjs-140.0/installed-tests/debugger/keys.debugger.js0000664000175000017500000000223015167114161021220 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', }; class Parent { #privateField; constructor() { this.#privateField = 1; } } class Child extends Parent { #subPrivateField; meaningOfLife = 42; constructor() { super(); this.#subPrivateField = 2; } } class PrivateTest extends Child { #child; childVisible; #customToStringChild; #circular1; #circular2; #selfRef; #date; #privateFunc; constructor() { super(); this.#child = new Child(); this.childVisible = new Child(); this.#customToStringChild = new Child(); this.#customToStringChild.toString = () => 'Custom child!'; this.#circular2 = {}; this.#circular1 = {n: this.#circular2}; this.#circular2.n = this.#circular1; this.#selfRef = this; this.#date = new Date('2025-01-07T00:53:42.417Z'); this.#privateFunc = () => 1; } } const b = new PrivateTest(); debugger; void (a, b); cjs-140.0/installed-tests/debugger/keys.debugger.output0000664000175000017500000000135615167114161022154 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:49: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> keys b "meaningOfLife", "childVisible", #privateField, #subPrivateField, #child, #customToStringChild, #circular1, #circular2, #selfRef, #date, #privateFunc db> c Program exited with code 0 cjs-140.0/installed-tests/debugger/lastvalues.debugger0000664000175000017500000000026115167114161022017 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-140.0/installed-tests/debugger/lastvalues.debugger.js0000664000175000017500000000031615167114161022433 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-140.0/installed-tests/debugger/lastvalues.debugger.output0000664000175000017500000000067215167114161023364 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-140.0/installed-tests/debugger/list.debugger0000664000175000017500000000031615167114161020610 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-140.0/installed-tests/debugger/list.debugger.js0000664000175000017500000000047015167114161021224 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-140.0/installed-tests/debugger/list.debugger.output0000664000175000017500000000332115167114161022146 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(); 12 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(); 12 db> list 12 7 return undefined; 8 else 9 return a / b; 10 } 11 divide(); *12 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-140.0/installed-tests/debugger/next.debugger0000664000175000017500000000022415167114161020611 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-140.0/installed-tests/debugger/next.debugger.js0000664000175000017500000000036515167114161021232 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-140.0/installed-tests/debugger/next.debugger.output0000664000175000017500000000120015167114161022143 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-140.0/installed-tests/debugger/noModule.js.map0000664000175000017500000000053515167114161021026 0ustar fabiofabio{"version":3,"file":"noModule.js","sourceRoot":"","sources":["noModule.ts"],"names":[],"mappings":"AAIA,IAAI,CAAC,GAAgB,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC;AAC/B,IAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC","sourcesContent":["interface FancyNumber {\n n: number;\n}\n\nlet a: FancyNumber = {n: null};\nconst b = a.n.toString(42);\n\n"]}cjs-140.0/installed-tests/debugger/noModule.js.map.license0000664000175000017500000000015615167114161022446 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/number.js.map0000664000175000017500000000060715167114161020534 0ustar fabiofabio{"version":3,"file":"number.js","sourceRoot":"","sources":["number.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,IAAM,YAAY,GAAG,UAAC,GAAuB;IAChD,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAA","sourcesContent":["interface SuperFancyNumber {\n n: number;\n}\n\nexport const get2ndNumber = (num: SuperFancyNumber[]) => {\n return num[1].n.toFixed(1);\n}"]}cjs-140.0/installed-tests/debugger/number.js.map.license0000664000175000017500000000015615167114161022154 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/numberWork.js.map0000664000175000017500000000052315167114161021374 0ustar fabiofabio{"version":3,"file":"numberWork.js","sourceRoot":"","sources":["numberWork.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,EAAE,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC","sourcesContent":["import { get2ndNumber } from \"./number.js\";\n\nget2ndNumber([{n:1}, {n: null}]);"]}cjs-140.0/installed-tests/debugger/numberWork.js.map.license0000664000175000017500000000015615167114161023017 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/print.debugger0000664000175000017500000000041015167114161020764 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 p p p q c cjs-140.0/installed-tests/debugger/print.debugger.js0000664000175000017500000000342215167114161021405 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', with: null, and: undefined}; 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'}; class Parent { #privateField; constructor() { this.#privateField = 1; } } class Child extends Parent { #subPrivateField; meaningOfLife = 42; constructor() { super(); this.#subPrivateField = 2; } } class PrivateTest extends Child { #child; childVisible; #customToStringChild; #circular1; #circular2; #selfRef; #date; #privateFunc; constructor() { super(); this.#child = new Child(); this.childVisible = new Child(); this.#customToStringChild = new Child(); this.#customToStringChild.toString = () => 'Custom child!'; this.#circular2 = {}; this.#circular1 = {n: this.#circular2}; this.#circular2.n = this.#circular1; this.#selfRef = this; this.#date = new Date('2025-01-07T00:53:42.417Z'); this.#privateFunc = () => 1; } } const p = new PrivateTest(); class PrivateNullishToString { #test; toString = null; constructor() { this.#test = 1; } } const q = new PrivateNullishToString(); debugger; void (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q); cjs-140.0/installed-tests/debugger/print.debugger.output0000664000175000017500000000331515167114161022332 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:69: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", with: null, and: undefined } 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> p p $20 = [object Object] { meaningOfLife: 42, childVisible: { meaningOfLife: 42, #privateField: 1, #subPrivateField: 2 }, #privateField: 1, #subPrivateField: 2, #child: { meaningOfLife: 42, #privateField: 1, #subPrivateField: 2 }, #customToStringChild: Custom child!, #circular1: { n: { n: [Circular] } }, #circular2: [Circular], #selfRef: [Circular], #date: 2025-01-07T00:53:42.417Z, #privateFunc: [ Function: ] } db> p q $21 = [object Object] { toString: null, #test: 1 } db> c Program exited with code 0 cjs-140.0/installed-tests/debugger/quit.debugger0000664000175000017500000000020115167114161020610 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Philip Chimento q cjs-140.0/installed-tests/debugger/quit.debugger.js0000664000175000017500000000021615167114161021231 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento print('hi'); cjs-140.0/installed-tests/debugger/quit.debugger.output0000664000175000017500000000031315167114161022153 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-140.0/installed-tests/debugger/return.debugger0000664000175000017500000000033315167114161021153 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-140.0/installed-tests/debugger/return.debugger.js0000664000175000017500000000043515167114161021571 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-140.0/installed-tests/debugger/return.debugger.output0000664000175000017500000000156015167114161022515 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-140.0/installed-tests/debugger/set.debugger0000664000175000017500000000062315167114161020431 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-140.0/installed-tests/debugger/set.debugger.js0000664000175000017500000000024115167114161021040 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2018 Philip Chimento const a = {}; debugger; void a; cjs-140.0/installed-tests/debugger/set.debugger.output0000664000175000017500000000145515167114161021774 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-140.0/installed-tests/debugger/sourcemap-dynamic-module.debugger0000664000175000017500000000023315167114161024536 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2025 Philip Chimento c s s set colors false list cjs-140.0/installed-tests/debugger/sourcemap-dynamic-module.debugger.js0000664000175000017500000000036715167114161025161 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento const {get2ndNumber} = await import('./sourcemap-number-module.js'); debugger; get2ndNumber([{ n: 1 }, { n: null }]); cjs-140.0/installed-tests/debugger/sourcemap-dynamic-module.debugger.output0000664000175000017500000000203715167114161026101 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2025 Philip Chimento db> c Promise 2 started from @sourcemap-dynamic-module.debugger.js:1:1 Promise 3 started from @sourcemap-dynamic-module.debugger.js:3:24 Promise 4 fulfilled after XXXX ms Promise 3 fulfilled after XXXX ms with [object Object] [Object: null prototype] { get2ndNumber: [ Function: get2ndNumber ], [Symbol.toStringTag]: "Module" } Debugger statement, module code at sourcemap-dynamic-module.debugger.js:4:1 db> s module code at sourcemap-dynamic-module.debugger.js:4:1 db> s module code at sourcemap-dynamic-module.debugger.js:5:1 entered frame: ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 db> set colors false db> list 1 interface SuperFancyNumber { 2 n: number; 3 } 4 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { *6 return num[1].n.toFixed(1); 7 } db> [quit due to end of input] Program exited with code 0 cjs-140.0/installed-tests/debugger/sourcemap-inlined-module.debugger0000664000175000017500000000024415167114161024536 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Gary Li set colors false list bt frame c list up list bt cjs-140.0/installed-tests/debugger/sourcemap-inlined-module.debugger.js0000664000175000017500000000113215167114161025146 0ustar fabiofabioimport { get2ndNumber } from "./sourcemap-number-module.js"; get2ndNumber([{ n: 1 }, { n: null }]); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVtYmVyV29yay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm51bWJlcldvcmsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUUzQyxZQUFZLENBQUMsQ0FBQyxFQUFDLENBQUMsRUFBQyxDQUFDLEVBQUMsRUFBRSxFQUFDLENBQUMsRUFBRSxJQUFJLEVBQUMsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBnZXQybmROdW1iZXIgfSBmcm9tIFwiLi9udW1iZXIuanNcIjtcblxuZ2V0Mm5kTnVtYmVyKFt7bjoxfSwge246IG51bGx9XSk7Il19cjs-140.0/installed-tests/debugger/sourcemap-inlined-module.debugger.js.license0000664000175000017500000000015615167114161026574 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/sourcemap-inlined-module.debugger.output0000664000175000017500000000276315167114161026105 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2024 Gary Li db> set colors false db> list 1 interface SuperFancyNumber { 2 n: number; 3 } 4 *5 export const get2ndNumber = (num: SuperFancyNumber[]) => { 6 return num[1].n.toFixed(1); 7 } db> bt #0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 db> frame #0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 1 export var get2ndNumber = function (num) { db> c Unwinding due to exception. (Type 'c' to continue unwinding.) #0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 2 return num[1].n.toFixed(1); Exception value is: $1 = [object TypeError] TypeError: can't access property "toFixed", num[1].n is null db> list 1 interface SuperFancyNumber { 2 n: number; 3 } 4 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { *6 return num[1].n.toFixed(1); 7 } db> up #1 module code at sourcemap-inlined-module.debugger.js:2:13 -> numberWork.ts:3:14 2 get2ndNumber([{ n: 1 }, { n: null }]); db> list 1 import { get2ndNumber } from "./number.js"; 2 *3 get2ndNumber([{n:1}, {n: null}]); db> bt #0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 #1 module code at sourcemap-inlined-module.debugger.js:2:13 -> numberWork.ts:3:14 db> [quit due to end of input] Program exited with code 0 cjs-140.0/installed-tests/debugger/sourcemap-inlined.debugger0000664000175000017500000000024415167114161023253 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Gary Li set colors false list bt frame c list up list bt cjs-140.0/installed-tests/debugger/sourcemap-inlined.debugger.js0000664000175000017500000000106515167114161023670 0ustar fabiofabiovar a = { n: null }; var b = a.n.toString(42); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9Nb2R1bGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub01vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxJQUFJLENBQUMsR0FBZ0IsRUFBQyxDQUFDLEVBQUUsSUFBSSxFQUFDLENBQUM7QUFDL0IsSUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbnRlcmZhY2UgRmFuY3lOdW1iZXIge1xuICAgIG46IG51bWJlcjtcbn1cblxubGV0IGE6IEZhbmN5TnVtYmVyID0ge246IG51bGx9O1xuY29uc3QgYiA9IGEubi50b1N0cmluZyg0Mik7XG5cbiJdfQ==cjs-140.0/installed-tests/debugger/sourcemap-inlined.debugger.js.license0000664000175000017500000000015615167114161025311 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/sourcemap-inlined.debugger.output0000664000175000017500000000240115167114161024607 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2024 Gary Li db> set colors false db> list 1 interface FancyNumber { 2 n: number; 3 } 4 *5 let a: FancyNumber = {n: null}; 6 const b = a.n.toString(42); 7 8 db> bt #0 toplevel at sourcemap-inlined.debugger.js:1:1 -> noModule.ts:5:1 db> frame #0 toplevel at sourcemap-inlined.debugger.js:1:1 -> noModule.ts:5:1 1 var a = { n: null }; db> c Unwinding due to exception. (Type 'c' to continue unwinding.) #0 toplevel at sourcemap-inlined.debugger.js:2:9 -> noModule.ts:6:12 2 var b = a.n.toString(42); Exception value is: $1 = [object TypeError] TypeError: can't access property "toString", a.n is null db> list 1 interface FancyNumber { 2 n: number; 3 } 4 5 let a: FancyNumber = {n: null}; *6 const b = a.n.toString(42); 7 8 db> up Initial frame selected; you cannot go up. db> list 1 interface FancyNumber { 2 n: number; 3 } 4 5 let a: FancyNumber = {n: null}; *6 const b = a.n.toString(42); 7 8 db> bt #0 toplevel at sourcemap-inlined.debugger.js:2:9 -> noModule.ts:6:12 db> [quit due to end of input] Program exited with code 0 cjs-140.0/installed-tests/debugger/sourcemap-number-module.js0000664000175000017500000000016015167114161023231 0ustar fabiofabioexport var get2ndNumber = function (num) { return num[1].n.toFixed(1); }; //# sourceMappingURL=number.js.mapcjs-140.0/installed-tests/debugger/sourcemap-number-module.js.license0000664000175000017500000000015615167114161024657 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/sourcemap-separate-module.debugger0000664000175000017500000000024415167114161024720 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Gary Li set colors false list bt frame c list up list bt cjs-140.0/installed-tests/debugger/sourcemap-separate-module.debugger.js0000664000175000017500000000021215167114161025326 0ustar fabiofabioimport { get2ndNumber } from "./sourcemap-number-module.js"; get2ndNumber([{ n: 1 }, { n: null }]); //# sourceMappingURL=numberWork.js.mapcjs-140.0/installed-tests/debugger/sourcemap-separate-module.debugger.js.license0000664000175000017500000000015615167114161026756 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/sourcemap-separate-module.debugger.output0000664000175000017500000000276515167114161026271 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2024 Gary Li db> set colors false db> list 1 interface SuperFancyNumber { 2 n: number; 3 } 4 *5 export const get2ndNumber = (num: SuperFancyNumber[]) => { 6 return num[1].n.toFixed(1); 7 } db> bt #0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 db> frame #0 module code at sourcemap-number-module.js:1:27 -> number.ts:5:29 1 export var get2ndNumber = function (num) { db> c Unwinding due to exception. (Type 'c' to continue unwinding.) #0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 2 return num[1].n.toFixed(1); Exception value is: $1 = [object TypeError] TypeError: can't access property "toFixed", num[1].n is null db> list 1 interface SuperFancyNumber { 2 n: number; 3 } 4 5 export const get2ndNumber = (num: SuperFancyNumber[]) => { *6 return num[1].n.toFixed(1); 7 } db> up #1 module code at sourcemap-separate-module.debugger.js:2:13 -> numberWork.ts:3:14 2 get2ndNumber([{ n: 1 }, { n: null }]); db> list 1 import { get2ndNumber } from "./number.js"; 2 *3 get2ndNumber([{n:1}, {n: null}]); db> bt #0 ([object Array]) at sourcemap-number-module.js:2:5 -> number.ts:6:5 #1 module code at sourcemap-separate-module.debugger.js:2:13 -> numberWork.ts:3:14 db> [quit due to end of input] Program exited with code 0 cjs-140.0/installed-tests/debugger/sourcemap-separate.debugger0000664000175000017500000000024415167114161023435 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2024 Gary Li set colors false list bt frame c list up list bt cjs-140.0/installed-tests/debugger/sourcemap-separate.debugger.js0000664000175000017500000000012315167114161024044 0ustar fabiofabiovar a = { n: null }; var b = a.n.toString(42); //# sourceMappingURL=noModule.js.mapcjs-140.0/installed-tests/debugger/sourcemap-separate.debugger.js.license0000664000175000017500000000015615167114161025473 0ustar fabiofabioSPDX-License-Identifier: MIT OR LGPL-2.0-or-later SPDX-FileCopyrightText: 2024 Gary Li cjs-140.0/installed-tests/debugger/sourcemap-separate.debugger.output0000664000175000017500000000240515167114161024775 0ustar fabiofabioGJS debugger. Type "help" for help db> # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later db> # SPDX-FileCopyrightText: 2024 Gary Li db> set colors false db> list 1 interface FancyNumber { 2 n: number; 3 } 4 *5 let a: FancyNumber = {n: null}; 6 const b = a.n.toString(42); 7 8 db> bt #0 toplevel at sourcemap-separate.debugger.js:1:1 -> noModule.ts:5:1 db> frame #0 toplevel at sourcemap-separate.debugger.js:1:1 -> noModule.ts:5:1 1 var a = { n: null }; db> c Unwinding due to exception. (Type 'c' to continue unwinding.) #0 toplevel at sourcemap-separate.debugger.js:2:9 -> noModule.ts:6:12 2 var b = a.n.toString(42); Exception value is: $1 = [object TypeError] TypeError: can't access property "toString", a.n is null db> list 1 interface FancyNumber { 2 n: number; 3 } 4 5 let a: FancyNumber = {n: null}; *6 const b = a.n.toString(42); 7 8 db> up Initial frame selected; you cannot go up. db> list 1 interface FancyNumber { 2 n: number; 3 } 4 5 let a: FancyNumber = {n: null}; *6 const b = a.n.toString(42); 7 8 db> bt #0 toplevel at sourcemap-separate.debugger.js:2:9 -> noModule.ts:6:12 db> [quit due to end of input] Program exited with code 0 cjs-140.0/installed-tests/debugger/step.debugger0000664000175000017500000000022715167114161020611 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-140.0/installed-tests/debugger/step.debugger.js0000664000175000017500000000034715167114161021227 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-140.0/installed-tests/debugger/step.debugger.output0000664000175000017500000000153415167114161022152 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-140.0/installed-tests/debugger/throw-ignored.debugger0000664000175000017500000000017615167114161022431 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2021 Florian Müllner c q cjs-140.0/installed-tests/debugger/throw-ignored.debugger.js0000664000175000017500000000043215167114161023037 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-140.0/installed-tests/debugger/throw-ignored.debugger.output0000664000175000017500000000072415167114161023767 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-140.0/installed-tests/debugger/throw.debugger0000664000175000017500000000034215167114161020777 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-140.0/installed-tests/debugger/throw.debugger.js0000664000175000017500000000035415167114161021415 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-140.0/installed-tests/debugger/throw.debugger.output0000664000175000017500000000207315167114161022341 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-140.0/installed-tests/debugger/until.debugger0000664000175000017500000000022415167114161020766 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-140.0/installed-tests/debugger/until.debugger.js0000664000175000017500000000032615167114161021404 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-140.0/installed-tests/debugger/until.debugger.output0000664000175000017500000000071215167114161022327 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-140.0/installed-tests/extra/0000775000175000017500000000000015167114161015466 5ustar fabiofabiocjs-140.0/installed-tests/extra/gjs.supp0000664000175000017500000000553615167114161017173 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_* fun:_ZN7mozilla9TimeStamp20ComputeProcessUptimeEv fun:_ZN7mozilla9TimeStamp15ProcessCreation* fun:_ZN2JS6detail25InitWithFailureDiagnosticEb* ... fun:_ZN7GjsInitC* } # 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 } { gtk-static-display-settings Memcheck:Leak match-leak-kinds: possible ... fun:g_object_bind_property_full fun:g_object_bind_property fun:settings_init_style fun:gtk_settings_create_for_display } { gdk-static-x11-screen Memcheck:Leak match-leak-kinds: definite ... fun:epoxy_glx_version fun:gdk_x11_screen_init_gl } # 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 } { pango-static-default-language Memcheck:Leak match-leak-kinds: possible fun:calloc fun:g_malloc0 fun:pango_language_from_string fun:pango_language_get_default } # Oddly, Valgrind does not detect that sysprof_malloc0 produces initialized # memory { sysprof-malloc0 Memcheck:Param write(buf) fun:write fun:sysprof_capture_writer_flush_data fun:sysprof_capture_writer_flush fun:gjs_profiler_stop } # 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-show-text 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 } { cairo-static-data-text-extents 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_text_extents } cjs-140.0/installed-tests/extra/lsan.supp0000664000175000017500000000136715167114161017343 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 # Data created as part of opening X display leak:glx_screen_init leak:eglCreateContext leak:eglInitialize cjs-140.0/installed-tests/js/0000775000175000017500000000000015167114161014757 5ustar fabiofabiocjs-140.0/installed-tests/js/builder-nontemplate.ui0000664000175000017500000000075515167114161021277 0ustar fabiofabio cjs-140.0/installed-tests/js/complex3.ui0000664000175000017500000000242215167114161017050 0ustar fabiofabio cjs-140.0/installed-tests/js/complex4.ui0000664000175000017500000000213215167114161017047 0ustar fabiofabio cjs-140.0/installed-tests/js/jasmine.js0000664000175000017500000076243215167114161016761 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-140.0/installed-tests/js/jsunit.gresources.xml0000664000175000017500000000373015167114161021200 0ustar fabiofabio builder-nontemplate.ui 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/networkURI.js modules/overrides/GIMarshallingTests.js modules/say.js modules/scaryURI.js modules/sideEffect.js modules/sideEffect2.js modules/sideEffect3.js modules/sideEffect4.js modules/sloppy.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-140.0/installed-tests/js/libgjstesttools/0000775000175000017500000000000015167114161020212 5ustar fabiofabiocjs-140.0/installed-tests/js/libgjstesttools/gjs-test-tools.cpp0000664000175000017500000002550415167114161023622 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 #ifdef G_OS_UNIX # include # include // for FD_CLOEXEC # include # include // for close, write # include // for g_unix_open_pipe #endif #include "cjs/auto.h" 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)) { GObject* 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) { GThread* thread = g_thread_try_new("ref_object", g_object_ref, object, error); if (thread) g_thread_join(thread); } static void* emit_test_signal_other_thread_func(void* data) { g_signal_emit_by_name(data, "test"); return nullptr; } void gjs_test_tools_emit_test_signal_other_thread(GObject* object, GError** error) { GThread* thread = g_thread_try_new("emit_signal_object", emit_test_signal_other_thread_func, object, error); if (thread) g_thread_join(thread); } enum [[clang::flag_enum]] RefType : uint8_t { REF = 1 << 0, UNREF = 1 << 1, }; struct RefThreadData { GObject* object; RefType ref_type; int delay; }; 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) { Gjs::AutoPointer 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; 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) { GThread* thread = g_thread_try_new("unref_object", ref_thread_func, ref_thread_data_new(object, -1, UNREF), error); if (thread) g_thread_join(thread); } /** * 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) { GThread* thread = g_thread_try_new( "run_dispose", [](void* object) -> void* { g_object_run_dispose(G_OBJECT(object)); return nullptr; }, object, error); g_thread_join(thread); } /** * 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) { GThread* thread = g_thread_try_new( "weak_get", [](void*) -> void* { return gjs_test_tools_get_weak(); }, nullptr, error); if (!thread) return nullptr; return static_cast(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 void 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; } #endif // G_OS_UNIX /** * gjs_open_bytes: * @bytes: bytes to send to the pipe * @error: Return location for a #GError, or nullptr * * 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) { g_return_val_if_fail(bytes, -1); g_return_val_if_fail(error == nullptr || *error == nullptr, -1); #ifdef G_OS_UNIX int pipefd[2]; if (!g_unix_open_pipe(pipefd, FD_CLOEXEC, error)) return -1; size_t count; const void* buf = g_bytes_get_data(bytes, &count); ssize_t bytes_written = write(pipefd[1], buf, count); if (bytes_written < 0) { throw_errno_prefix(error, "write"); return -1; } if (static_cast(bytes_written) != count) g_warning("%s: %zu bytes sent, only %zd bytes written", __func__, count, bytes_written); int 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-140.0/installed-tests/js/libgjstesttools/gjs-test-tools.h0000664000175000017500000000560215167114161023264 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 void gjs_test_tools_emit_test_signal_other_thread(GObject* object, 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(void); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_steal_saved(void); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_peek_saved(void); GJS_TEST_TOOL_EXTERN int gjs_test_tools_get_saved_ref_count(void); GJS_TEST_TOOL_EXTERN void gjs_test_tools_clear_saved(void); GJS_TEST_TOOL_EXTERN void gjs_test_tools_save_weak(GObject* object); GJS_TEST_TOOL_EXTERN GObject* gjs_test_tools_get_weak(void); 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(void); G_END_DECLS cjs-140.0/installed-tests/js/libgjstesttools/meson.build0000664000175000017500000000240415167114161022354 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: libgjs_dep, cpp_args: libgjs_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: 'GjsTestTools', 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-140.0/installed-tests/js/matchers.js0000664000175000017500000000406515167114161017130 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-140.0/installed-tests/js/meson.build0000664000175000017500000001205715167114161017126 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: libgjs_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', 'Async', 'AsyncMainloop', 'Cairo', 'Console', 'Encoding', 'ESModules', 'Exceptions', 'Fundamental', 'Gettext', 'GIMarshalling', 'Gio', 'GLib', 'GLibLogWriter', 'Global', 'GObject', 'GObjectClass', 'GObjectInterface', 'GObjectValue', 'GTypeClass', 'Introspection', 'Namespace', 'ParamSpec', 'Print', 'Promise', 'Regress', 'System', 'Timers', 'Utility', 'WarnLib', 'WeakRef', ] if not get_option('skip_gtk_tests') jasmine_tests += 'GObjectDestructionAccess' if have_gtk3 jasmine_tests += 'Gtk3' endif if have_gtk4 jasmine_tests += 'Gtk4' endif endif installed_js_tests_dir = installed_tests_execdir / 'js' gschemas_compiled = gnome.compile_schemas( depend_files: 'org.cinnamon.CjsTest.gschema.xml') tests_dependencies = [ gir_deps, gschemas_compiled, gjs_private_typelib, gjstest_tools_typelib, gi_tests.get_variable('gimarshallingtests_typelib'), gi_tests.get_variable('regress_typelib'), gi_tests.get_variable('regress_unix_typelib'), gi_tests.get_variable('warnlib_typelib'), gi_tests.get_variable('utility_typelib'), ] foreach test : jasmine_tests test_file = files('test@0@.js'.format(test)) test(test, minijasmine, args: [test_file, '-m'], 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 is separate, because it can be skipped, and # during build should be run using dbus-run-session bus_config = files('../../test/test-bus.conf') dbus_test_file = files('testGDBus.js') if not get_option('skip_dbus_tests') test('GDBus', dbus_run_session, args: [ '--config-file', bus_config, '--', minijasmine, dbus_test_file, '-m' ], env: tests_environment, protocol: 'tap', suite: 'dbus', depends: tests_dependencies) endif dbus_test_description_subst = { 'name': 'testGDBus.js', 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file( configuration: dbus_test_description_subst, input: '../minijasmine.test.in', output: 'testGDBus.test', install: get_option('installed_tests'), install_dir: installed_tests_metadir) if get_option('installed_tests') install_data(dbus_test_file, install_dir: installed_js_tests_dir) endif # tests that require the legacy importer are also separate because they need to # invoke minijasmine without the -m flag legacy_importer_tests = [ 'Format', 'Importer', 'Importer2', 'Lang', 'LegacyByteArray', 'LegacyCairo', 'LegacyClass', 'LegacyGObject', 'Mainloop', 'Overrides', 'Package', 'Signals', 'Tweener', ] if not get_option('skip_gtk_tests') legacy_importer_tests += 'LegacyGtk' endif foreach test : legacy_importer_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') 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-legacy-importer.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-140.0/installed-tests/js/minijasmine-executor.js0000664000175000017500000000322415167114161021455 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-140.0/installed-tests/js/minijasmine.js0000664000175000017500000001072415167114161017624 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/gjs/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 // disable Jasmine timeouts and just let the Meson timeout take effect. const gcZeal = GLib.getenv('JS_GC_ZEAL'); const valgrind = GLib.getenv('VALGRIND'); if (valgrind || gcZeal) jasmine.DEFAULT_TIMEOUT_INTERVAL = 2 ** 31 - 1; /** * 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-140.0/installed-tests/js/modules/0000775000175000017500000000000015167114161016427 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/alwaysThrows.js0000664000175000017500000000025415167114161021475 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-140.0/installed-tests/js/modules/badOverrides/0000775000175000017500000000000015167114161021040 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/badOverrides/GIMarshallingTests.js0000664000175000017500000000031115167114161025075 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-140.0/installed-tests/js/modules/badOverrides/Gio.js0000664000175000017500000000030015167114161022105 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-140.0/installed-tests/js/modules/badOverrides/Regress.js0000664000175000017500000000032715167114161023012 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-140.0/installed-tests/js/modules/badOverrides/WarnLib.js0000664000175000017500000000031115167114161022727 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-140.0/installed-tests/js/modules/badOverrides2/0000775000175000017500000000000015167114161021122 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/badOverrides2/GIMarshallingTests.js0000664000175000017500000000036215167114161025165 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-140.0/installed-tests/js/modules/badOverrides2/Gio.js0000664000175000017500000000027615167114161022203 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-140.0/installed-tests/js/modules/badOverrides2/Regress.js0000664000175000017500000000027315167114161023074 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-140.0/installed-tests/js/modules/badOverrides2/WarnLib.js0000664000175000017500000000025715167114161023022 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-140.0/installed-tests/js/modules/data.txt0000664000175000017500000000001215167114161020072 0ustar fabiofabiotest data cjs-140.0/installed-tests/js/modules/dynamic.js0000664000175000017500000000033215167114161020407 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-140.0/installed-tests/js/modules/encodings.json0000664000175000017500000002316315167114161021300 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-140.0/installed-tests/js/modules/exports.js0000664000175000017500000000061515167114161020473 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-140.0/installed-tests/js/modules/foobar.js0000664000175000017500000000062415167114161020237 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-140.0/installed-tests/js/modules/greet.js0000664000175000017500000000055615167114161020101 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-140.0/installed-tests/js/modules/importmeta.js0000664000175000017500000000046715167114161021155 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-140.0/installed-tests/js/modules/lexicalScope.js0000664000175000017500000000172415167114161021404 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-140.0/installed-tests/js/modules/modunicode.js0000664000175000017500000000027015167114161021112 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-140.0/installed-tests/js/modules/mutualImport/0000775000175000017500000000000015167114161021131 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/mutualImport/a.js0000664000175000017500000000053215167114161021707 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-140.0/installed-tests/js/modules/mutualImport/b.js0000664000175000017500000000032115167114161021704 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-140.0/installed-tests/js/modules/networkURI.js0000664000175000017500000000041315167114161021034 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento export {foo} from 'https://gitlab.gnome.org/GNOME/gjs/-/raw/ce4411f5d9b6fc00ab8d949890037bd351634d5f/installed-tests/js/modules/say.js'; cjs-140.0/installed-tests/js/modules/overrides/0000775000175000017500000000000015167114161020431 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/overrides/GIMarshallingTests.js0000664000175000017500000000174115167114161024476 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-140.0/installed-tests/js/modules/say.js0000664000175000017500000000036615167114161017566 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-140.0/installed-tests/js/modules/scaryURI.js0000664000175000017500000000025215167114161020465 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento export {foo} from 'scary:///module.js'; cjs-140.0/installed-tests/js/modules/sideEffect.js0000664000175000017500000000027015167114161021025 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento globalThis.leakyState ??= 0; globalThis.leakyState++; cjs-140.0/installed-tests/js/modules/sideEffect2.js0000664000175000017500000000027015167114161021107 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2023 Philip Chimento globalThis.leakyState ??= 0; globalThis.leakyState++; cjs-140.0/installed-tests/js/modules/sideEffect3.js0000664000175000017500000000026615167114161021115 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Gary Li globalThis.queryLeakyState ??= 0; globalThis.queryLeakyState++; cjs-140.0/installed-tests/js/modules/sideEffect4.js0000664000175000017500000000033015167114161021106 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-140.0/installed-tests/js/modules/sloppy.js0000664000175000017500000000074215167114161020316 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2025 Philip Chimento /* exported setPropertyInSloppyMode */ // ES Module code is always strict mode. In order to test something occurring in // sloppy mode from within a module, you need to import (via the legacy // importer) a function that was defined in a non-module context. function setPropertyInSloppyMode(object, property, value) { object[property] = value; } cjs-140.0/installed-tests/js/modules/subA/0000775000175000017500000000000015167114161017321 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/subA/subB/0000775000175000017500000000000015167114161020214 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/subA/subB/__init__.js0000664000175000017500000000064115167114161022312 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-140.0/installed-tests/js/modules/subA/subB/baz.js0000664000175000017500000000000015167114161021314 0ustar fabiofabiocjs-140.0/installed-tests/js/modules/subA/subB/foobar.js0000664000175000017500000000033415167114161022022 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-140.0/installed-tests/js/modules/subBadInit/0000775000175000017500000000000015167114161020453 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/subBadInit/__init__.js0000664000175000017500000000021615167114161022547 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh this is gobbledygook cjs-140.0/installed-tests/js/modules/subErrorInit/0000775000175000017500000000000015167114161021056 5ustar fabiofabiocjs-140.0/installed-tests/js/modules/subErrorInit/__init__.js0000664000175000017500000000022515167114161023152 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh throw Error('a bad init!'); cjs-140.0/installed-tests/js/org.cinnamon.CjsTest.gschema.xml0000664000175000017500000000135015167114161023054 0ustar fabiofabio (-1, -1) false false 10 cjs-140.0/installed-tests/js/testAsync.js0000664000175000017500000000415315167114161017275 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-140.0/installed-tests/js/testAsyncMainloop.js0000664000175000017500000000141515167114161020772 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/gjs/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-140.0/installed-tests/js/testCairo.js0000664000175000017500000004216315167114161017260 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2010 litl, LLC // SPDX-FileCopyrightText: 2024 Philip Chimento import Cairo from 'cairo'; import Gdk from 'gi://Gdk?version=3.0'; import giCairo from 'gi://cairo'; import GIMarshallingTests from 'gi://GIMarshallingTests'; import GLib from 'gi://GLib'; import Gtk from 'gi://Gtk?version=3.0'; import Regress from 'gi://Regress'; 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 () { ['none', 'full'].forEach(transfer => { it(`can be marshalled as a transfer ${transfer} return value`, function () { const outCr = Regress[`test_cairo_context_${transfer}_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 a transfer ${transfer} in parameter`, function () { expect(() => Regress[`test_cairo_context_${transfer}_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 a transfer ${transfer} in parameter`, function () { expect(() => Regress[`test_cairo_surface_${transfer}_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); }); }); describe('for path', function () { it('can be marshalled as a return value', function () { const path = Regress.test_cairo_path_full_return(); expect(path).toBeInstanceOf(Cairo.Path); }); it('can be marshalled as an in parameter with transfer none', function () { const path = cr.copyPath(); Regress.test_cairo_path_none_in(path); }); it('can be marshalled as an in parameter with transfer full', function () { const path = cr.copyPath(); const outPath = Regress.test_cairo_path_full_in_full_return(path); expect(outPath).toBeInstanceOf(Cairo.Path); }); }); describe('for pattern', function () { ['none', 'full'].forEach(transfer => { it(`can be marshalled as a return value with transfer ${transfer}`, function () { const pattern = Regress[`test_cairo_pattern_${transfer}_return`](); expect(pattern).toBeInstanceOf(Cairo.Pattern); }); it(`can be marshalled as an in parameter with transfer ${transfer}`, function () { const pattern = Cairo.SolidPattern.createRGB(1, 2, 3); Regress[`test_cairo_pattern_${transfer}_in`](pattern); }); }); }); describe('for region', function () { it('can be marshalled as an in argument', function () { const region = new Cairo.Region(); Regress.test_cairo_region_full_in(region); }); }); xdescribe('for FontOptions', function () { ['none', 'full'].forEach(transfer => { it(`can be marshalled as a return value with transfer ${transfer}`, function () { const fontOptions = Regress[`test_cairo_font_options_${transfer}_return`](); expect(fontOptions).toBeInstanceOf(Cairo.FontOptions); }); it(`can be marshalled as an in parameter with transfer ${transfer}`, function () { const fontOptions = Cairo.SolidPattern.createRGB(1, 2, 3); Regress[`test_cairo_font_options_${transfer}_in`](fontOptions); }); }); }).pend('Cairo.FontOptions not implemented. Open an issue if you need this'); xdescribe('for FontFace', function () { it('can be marshalled as a return value with transfer full', function () { const fontFace = Regress.test_cairo_font_face_full_return(cr); expect(fontFace).toBeInstanceOf(Cairo.FontFace); }); }).pend('Cairo.FontFace not implemented. Open an issue if you need this'); xdescribe('for ScaledFont', function () { it('can be marshalled as a return value with transfer full', function () { const scaledFont = Regress.test_cairo_scaled_font_full_return(cr); expect(scaledFont).toBeInstanceOf(Cairo.ScaledFont); }); }).pend('Cairo.ScaledFont not implemented. Open an issue if you need this'); xdescribe('for matrix', function () { it('can be marshalled as a return value', function () { const matrix = Regress.test_cairo_matrix_none_return(); expect(matrix).toEqual(jasmine.objectContaining({ x0: 0, y0: 0, xx: 1, xy: 0, yy: 1, yx: 0, })); }); it('can be marshalled as an in parameter', function () { const matrix = new Cairo.Matrix(); matrix.initIdentity(); Regress.test_cairo_matrix_none_in(matrix); }); it('can be marshalled as a caller-allocates out parameter', function () { const matrix = Regress.test_cairo_matrix_out_caller_allocates(); expect(matrix).toEqual(jasmine.objectContaining({ x0: 0, y0: 0, xx: 1, xy: 0, yy: 1, yx: 0, })); }); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/662'); 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 () { it('has the same functionality as cairo ES module', 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-140.0/installed-tests/js/testConsole.js0000664000175000017500000003001515167114161017616 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(); }); afterAll(function () { GLib.log_set_writer_default(); }); 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-140.0/installed-tests/js/testESModules.js0000664000175000017500000002145415167114161020063 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/gjs/jsunit/modules/exports.js'; import {NamedExport, data} from 'resource:///org/gjs/jsunit/modules/exports.js'; import metaProperties from 'resource:///org/gjs/jsunit/modules/importmeta.js'; // These imports should all refer to the same module and import it only once import 'resource:///org/gjs/jsunit/modules/sideEffect.js'; import 'resource://org/gjs/jsunit/modules/sideEffect.js'; import 'resource:///org/gjs/jsunit/modules/../modules/sideEffect.js'; // Imports with query parameters should not fail and be imported uniquely import 'resource:///org/gjs/jsunit/modules/sideEffect3.js?foo=bar&maple=syrup'; // these should resolve to the same after being canonicalized import 'resource://org/gjs/jsunit/modules/./sideEffect3.js?etag=1'; import 'resource:///org/gjs/jsunit/modules/sideEffect3.js?etag=1'; import greeting1 from 'resource:///org/gjs/jsunit/modules/greet.js?greeting=Hello&name=Test%20Code'; import greeting2 from 'resource:///org/gjs/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'); }); it('rejects imports from a nonsense URI scheme', async function () { await expectAsync(import('resource:///org/gjs/jsunit/modules/scaryURI.js')) .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); }); it('rejects imports from a real but unsupported URI scheme', async function () { await expectAsync(import('resource:///org/gjs/jsunit/modules/networkURI.js')) .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); }); }); 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/gjs/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/gjs/jsunit/modules/sideEffect2.js'); await import('resource://org/gjs/jsunit/modules/sideEffect2.js'); await import('resource:///org/gjs/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/gjs/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/gjs/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/gjs/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/gjs/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/gjs/jsunit/modules/data.txt'); fail('should not be reached'); } catch (e) { expect(e.constructor).toBe(SyntaxError); expect(e.stack).not.toMatch('internal/'); } }); it('rejects imports from a nonsense URI scheme', async function () { await expectAsync(import('scary:///module.js')) .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); }); it('rejects imports from a real but unsupported URI scheme', async function () { await expectAsync(import('https://gitlab.gnome.org/GNOME/gjs/-/raw/ce4411f5d9b6fc00ab8d949890037bd351634d5f/installed-tests/js/modules/say.js')) .toBeRejectedWith(jasmine.objectContaining({name: 'ImportError'})); }); }); cjs-140.0/installed-tests/js/testEncoding.js0000664000175000017500000003630415167114161017751 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/gjs/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-140.0/installed-tests/js/testExceptions.js0000664000175000017500000002650715167114161020350 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Red Hat, Inc. // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. import GIMarshallingTests from 'gi://GIMarshallingTests'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; 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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'JS ERROR: Error: set*'); new Foo({prop: 'bar'}); GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', 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('Gjs', '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('Gjs', 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('Gjs', 'testExceptions.js', 0, 'testExceptionInPropertyGetterWithBinding'); }); }); describe('logError', function () { afterEach(function () { GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0, 'testGErrorMessages'); }); it('logs a warning for a GError', function () { GLib.test_expect_message('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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('Gjs', 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/); }); }); describe('Interoperation with Error.isError', function () { it('thrown GError', function () { let err; try { const file = Gio.file_new_for_path("\\/,.^!@&$_don't exist"); file.read(null); } catch (e) { err = e; } expect(Error.isError(err)).toBeTruthy(); }); xit('returned GError', function () { const err = GIMarshallingTests.gerror_return(); expect(Error.isError(err)).toBeTruthy(); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/700'); it('created GError', function () { const err = new Gio.IOErrorEnum({message: 'a message', code: 0}); expect(Error.isError(err)).toBeTruthy(); }); it('GError created with the GLib.Error constructor', function () { const err = new GLib.Error(Gio.IOErrorEnum, 0, 'a message'); expect(Error.isError(err)).toBeTruthy(); }); it('GError created with GLib.Error.new_literal', function () { const err = GLib.Error.new_literal( Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'message'); expect(Error.isError(err)).toBeTruthy(); }); it('not an error', function () { const err = new GIMarshallingTests.BoxedStruct(); expect(Error.isError(err)).toBeFalsy(); }); }); cjs-140.0/installed-tests/js/testFormat.js0000664000175000017500000000417315167114161017452 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-140.0/installed-tests/js/testFundamental.js0000664000175000017500000001363415167114161020462 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Lionel Landwerlin // SPDX-FileCopyrightText: 2021 Marco Trevisan import GObject from 'gi://GObject'; import Regress from 'gi://Regress'; 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-140.0/installed-tests/js/testGDBus.js0000664000175000017500000012213115167114161017161 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC import Gio from 'gi://Gio'; import GjsTestTools from 'gi://GjsTestTools'; import GLib from 'gi://GLib'; import GioUnix from 'gi://GioUnix'; /* 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/gnome/gjs/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!'); } synchronouslyAlwaysThrowExceptionAsync() { throw Error('Exception!'); } async asynchronouslyAlwaysThrowExceptionSync() { await this.asynchronouslyAlwaysThrowExceptionAsync(); } async asynchronouslyAlwaysThrowExceptionAsync() { await (() => new Promise(() => { throw Error('Async Exception!'); }))(); } returnThenThrowExceptionSyncAsync(_parameters, invocation) { invocation.return_value(new GLib.Variant('(s)', ['I\'m going to fail soon!'])); // These should be a no-op! invocation.return_value(new GLib.Variant('(s)', ['And returning again..!'])); invocation.return_error_literal(Gio.DBusError, Gio.DBusError.NOT_SUPPORTED, 'Calling multiple times should not be supported'); // And let's throw on JS side only. throw Error('Oh no!'); } async returnThenThrowExceptionAsync(_parameters, invocation) { invocation.return_value(new GLib.Variant('(s)', ['I\'m going to fail soon!'])); await (() => new Promise(() => { throw Error('Oh no!'); }))(); } 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; }); } async asynchronouslyEchoSync(someString, someInt) { const ret = await new Promise(resolve => { GLib.idle_add(GLib.PRIORITY_LOW, () => { resolve([someString, someInt]); return GLib.SOURCE_REMOVE; }); }); return ret; } // 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 = GjsTestTools.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 = GjsTestTools.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.gnome.gjs.Test', Gio.BusNameOwnerFlags.NONE, name => { log(`Acquired name ${name}`); loop.quit(); }, name => { log(`Lost name ${name}`); }); loop.run(); new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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 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('Gjs', 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: alwaysThrowException: *'); await expectAsync(proxy.alwaysThrowExceptionAsync({})).toBeRejected(); }); it('can handle an exception thrown by an async remote method that is implemented synchronously', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: asynchronouslyAlwaysThrowExceptionSync: *Async Exception!*'); proxy.asynchronouslyAlwaysThrowExceptionSyncRemote({}, function (_, e) { expect(e).not.toBeNull(); expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.DBUS_ERROR)).toBeTrue(); expect(e.message.includes('asynchronouslyAlwaysThrowExceptionSync')); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by an async method that is implemented synchronously with async/await', async function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: asynchronouslyAlwaysThrowExceptionSync: *Async Exception!*'); await expectAsync(proxy.asynchronouslyAlwaysThrowExceptionSyncAsync({})).toBeRejected(); }); it('can handle an exception thrown by a sync remote method that is implemented asynchronously', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: synchronouslyAlwaysThrowException: *'); proxy.synchronouslyAlwaysThrowExceptionRemote({}, function (result, excp) { expect(excp).not.toBeNull(); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by a sync method that is implemented asynchronously with async/await', async function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: synchronouslyAlwaysThrowException: *'); await expectAsync(proxy.synchronouslyAlwaysThrowExceptionAsync({})).toBeRejected(); }); it('can handle an exception thrown by an async remote method that is implemented asynchronously', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: asynchronouslyAlwaysThrowException: *Async Exception!*'); proxy.asynchronouslyAlwaysThrowExceptionRemote({}, function (_, e) { expect(e).not.toBeNull(); expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.DBUS_ERROR)).toBeTrue(); expect(e.message.includes('asynchronouslyAlwaysThrowException')); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by an async method that is implemented asynchronously with async/await', async function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: asynchronouslyAlwaysThrowException: *Async Exception!*'); await expectAsync(proxy.asynchronouslyAlwaysThrowExceptionAsync({})).toBeRejected(); }); it('can handle an exception thrown by a sync remote method that is implemented asynchronously if invocation is already consumed', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: [object *] (returnThenThrowExceptionSync) already returned*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: [object *] (returnThenThrowExceptionSync) already returned*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: returnThenThrowExceptionSync: *Oh no!*'); proxy.returnThenThrowExceptionSyncRemote(function ([result], e) { expect(e).toBeNull(); expect(result).toBe('I\'m going to fail soon!'); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by an sync method that is implemented asynchronously with async/await if invocation is already consumed', async function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: [object *] (returnThenThrowExceptionSync) already returned*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: [object *] (returnThenThrowExceptionSync) already returned*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: returnThenThrowExceptionSync: *Oh no!*'); const [result] = await proxy.returnThenThrowExceptionSyncAsync(); expect(result).toBe('I\'m going to fail soon!'); }); it('can handle an exception thrown by an async remote method that is implemented asynchronously if invocation is already consumed', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: returnThenThrowException: *Oh no!*'); proxy.returnThenThrowExceptionRemote(function ([result], e) { expect(e).toBeNull(); expect(result).toBe('I\'m going to fail soon!'); loop.quit(); }); loop.run(); }); it('can handle an exception thrown by an async method that is implemented asynchronously with async/await if invocation is already consumed', async function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: returnThenThrowException: *Oh no!*'); const [result] = await proxy.returnThenThrowExceptionAsync(); expect(result).toBe('I\'m going to fail soon!'); }); it('can still destructure the return value when an exception is thrown', function () { GLib.test_expect_message('Gjs', 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; GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Missing handler for DBus method 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; GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Missing handler for DBus method 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 () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: arrayOutBadSig: ' + 'TypeError: can\'t convert symbol to number*'); 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 () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'JS ERROR: Exception in method call: arrayOutBadSig: ' + 'TypeError: can\'t convert symbol to number*'); 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 call a remote async method that is implemented synchronously', function () { let someString = 'Hello world!'; let someInt = 42; proxy.asynchronouslyEchoSyncRemote(someString, someInt, function (result, excp) { expect(excp).toBeNull(); expect(result).toEqual([someString, someInt]); loop.quit(); }); loop.run(); }); it('can call an async/await async method that is implemented synchronously', async function () { const someString = 'Hello world!'; const someInt = 42; const results = await proxy.asynchronouslyEchoSyncAsync(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 = GjsTestTools.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 = GjsTestTools.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 = GjsTestTools.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 = GjsTestTools.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(); }); afterAll(function () { GLib.log_set_writer_default(); }); it('init failures are reported in sync mode', function () { const cancellable = new Gio.Cancellable(); cancellable.cancel(); expect(() => new ProxyClass(Gio.DBus.session, 'org.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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.gnome.gjs.Test', '/org/gnome/gjs/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-140.0/installed-tests/js/testGIMarshalling.js0000664000175000017500000034223115167114161020703 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, 2024 Philip Chimento // We use Gio and GLib to have some objects that we know exist import GIMarshallingTests from 'gi://GIMarshallingTests'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import System from 'system'; // 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 = { 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: { skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44', }, 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); } const retval = func(...args); if (is64bit) { GLib.test_assert_expected_messages_internal('Gjs', '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('off_t', function () { testSimpleMarshalling('off_t', 1234567890, 0, 0); }); function testUnixIntegerTypedefMarshalling(type, inValue, skipAny = {}) { describe(type, function () { const skip = GIMarshallingTests[`${type}_in`] ? false : 'Only supported on Unix'; testSimpleMarshalling(type, inValue, 0, 0, { returnv: {skip: skip || skipAny.skipReturn}, in: {skip: skip || skipAny.skipIn}, out: {skip: skip || skipAny.skipOut}, uninitOut: {skip: skip || skipAny.skipUninitOut}, inout: {skip: skip || skipAny.skipInOut}, }); }); } // https://gitlab.gnome.org/GNOME/gjs/-/issues/673 testUnixIntegerTypedefMarshalling('dev_t', 1234567890, {skipInOut: true}); testUnixIntegerTypedefMarshalling('gid_t', 65534); testUnixIntegerTypedefMarshalling('pid_t', 12345); testUnixIntegerTypedefMarshalling('socklen_t', 123); testUnixIntegerTypedefMarshalling('uid_t', 65534); 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]); testUninitializedOutParameter('array_fixed', null); 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('picks a reasonable default for struct array out param when uninitialized', function () { expect(GIMarshallingTests.array_fixed_out_struct_uninitialized()).toEqual([false, null]); }); 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}), ]); }); for (const marshal of ['return', 'out']) { it(`handles a ${marshal} array with odd alignment`, function () { const arr = GIMarshallingTests[`array_fixed_${marshal}_unaligned`](); expect(arr.length).toEqual(32); expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); GIMarshallingTests.cleanup_unaligned_buffer(); }); } }); 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 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(); }); for (const marshal of ['return', 'out']) { it(`handles a ${marshal} array with odd alignment`, function () { const arr = GIMarshallingTests[`array_${marshal}_unaligned`](); expect(arr.length).toEqual(32); expect(Array.prototype.slice.call(arr, 0, 4)).toEqual([1, 2, 3, 4]); GIMarshallingTests.cleanup_unaligned_buffer(); }); } it('supports optional inout array with length', function () { expect(GIMarshallingTests.length_array_utf8_optional_inout(['🅰', 'β', 'c', 'd'])) .toEqual(['a', 'b', '¢', '🔠']); expect(GIMarshallingTests.length_array_utf8_optional_inout([])).toEqual(['a', 'b']); expect(GIMarshallingTests.length_array_utf8_optional_inout(null)).toEqual([]); }); }); 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 sequential structs as a return value', function () { let structArray = GIMarshallingTests.array_zero_terminated_return_sequential_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']); }); }); }); for (const marshal of ['return', 'out']) { it(`handles a ${marshal} array with odd alignment`, function () { const arr = GIMarshallingTests[`array_zero_terminated_${marshal}_unaligned`](); expect(Array.from(arr)).toEqual([1, 2, 3, 4, 5, 6, 7]); GIMarshallingTests.cleanup_unaligned_buffer(); }); } }); describe('Exhaustive test of UTF-8 sequences', function () { ['length', 'fixed', 'zero_terminated'].forEach(arrayKind => ['none', 'container', 'full'].forEach(transfer => { const testFunction = returnMode => { const commonName = 'array_utf8'; const funcName = [arrayKind, commonName, transfer, returnMode].join('_'); return GIMarshallingTests[funcName]; }; ['out', 'return'].forEach(returnMode => it(`${arrayKind} ${returnMode} transfer ${transfer}`, function () { const func = testFunction(returnMode); expect(func()).toEqual(['a', 'b', '¢', '🔠']); })); it(`${arrayKind} in transfer ${transfer}`, function () { const func = testFunction('in'); if (transfer === 'container') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); expect(() => func(['🅰', 'β', 'c', 'd'])).not.toThrow(); }); it(`${arrayKind} inout transfer ${transfer}`, function () { const func = testFunction('inout'); if (transfer === 'container') pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/44'); expect(func(['🅰', 'β', 'c', 'd'])).toEqual(['a', 'b', '¢', '🔠']); }); })); }); 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 BigInt int64s as a transfer-none in value', function () { GIMarshallingTests.garray_uint64_none_in([0, BigIntLimits.int64.umax]); }); 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()).toThrow(); }); }); 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); testOutParameter('bytearray_full', refByteArray); testInoutParameter('bytearray_full', refByteArray, Uint8Array.from([104, 101, 108, 0, 0xFF])); 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('_'); return GIMarshallingTests[funcName]; }; ['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 int"); 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(warn64(true, 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(); }); it('separates float from double correctly', function () { // Passing a Number infers the type 'double'. To pass a float GValue, we // need to construct it manually. const floatValue = new GObject.Value(); floatValue.init(GObject.TYPE_FLOAT); floatValue.set_float(3.14); expect(() => GIMarshallingTests.gvalue_float(floatValue, 3.14)).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, })); }); testUninitializedOutParameter('boxed_struct', null); 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(); }); it('can be constructed empty', function () { const constructedUnion = new GIMarshallingTests.Union(); expect(constructedUnion.long_).toBe(0); }); it('can be constructed with properties', function () { const constructedUnion = new GIMarshallingTests.Union({long_: 55}); expect(constructedUnion.long_).toBe(55); }); it('cannot be constructed with unknown properties', function () { expect(() => new GIMarshallingTests.Union({invalidProperty: 55})).toThrowError( /No field.*invalidProperty.*Union.*/); }); xit('cannot be constructed with wrong-type properties', function () { expect(() => new GIMarshallingTests.Union({long_: 'long_'})).toThrow(); expect(() => new GIMarshallingTests.Union({long_: union})).toThrow(); }).pend('We implicitly convert wrong typed values'); it('marshals as a return value', function () { expect(union.long_).toBe(42); }); it('marshals as a settable property', function () { union.long_ = 5555; expect(union.long_).toBe(5555); }); 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(); }); it('marshals as the this-argument of a method when constructed', function () { expect(() => new GIMarshallingTests.Union({long_: 42}).inv()).not.toThrow(); expect(() => new GIMarshallingTests.Union({long_: 42}).method()).not.toThrow(); }); it('marshals unregistered union', function () { const u = new GIMarshallingTests.UnregisteredUnion(); expect(u.long_).toBe(0); expect(u.size).toBe(0); expect(u.str).toBe(null); u.long_ = 1; expect(u.long_).toBe(1); u.size = 2; expect(u.size).toBe(2); expect(() => (u.str = 'three')).toThrow(); expect(u.size).toBe(2); }); it('marshals unregistered initialized union', function () { expect(new GIMarshallingTests.UnregisteredUnion({long_: 123}).long_).toBe(123); expect(new GIMarshallingTests.UnregisteredUnion({size: 321}).size).toBe(321); }); xit('marshals unregistered initialized union with pointer', function () { expect(() => new GIMarshallingTests.UnregisteredUnion({str: '123'}).str).toBe('123'); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/109'); }); describe('Structured union', function () { it('cannot be constructed with empty default constructor', function () { expect(() => new GIMarshallingTests.StructuredUnion()).toThrow(); }); Object.entries(GIMarshallingTests.StructuredUnionType).forEach(([unionTypeName, unionType]) => { const memberType = unionTypeName.toLowerCase().replace( /(^[a-z]|[-_][a-z])/g, group => group.toUpperCase().replace('_', '')); function getMember(union) { let member = union[unionTypeName.toLowerCase()]; switch (union.type()) { case GIMarshallingTests.StructuredUnionType.SINGLE_UNION: member = member.parent; break; } return member; } it(`can be constructed with default constructor ${memberType}`, function () { const union = new GIMarshallingTests.StructuredUnion(unionType); expect(union.type()).toBe(unionType); expect(union._type).toBe(unionType); }); it(`cannot change a private member ${memberType}`, function () { const union = new GIMarshallingTests.StructuredUnion(unionType); expect(() => (union._type = GIMarshallingTests.StructuredUnionType.NONE)).toThrow(); }); it(`can be constructed and has valid member ${memberType}`, function () { const union = new GIMarshallingTests.StructuredUnion(unionType); const member = getMember(union); let invObj; switch (union.type()) { case GIMarshallingTests.StructuredUnionType.NONE: expect(member).toBeUndefined(); return; case GIMarshallingTests.StructuredUnionType.NESTED_STRUCT: invObj = member.parent.simple_struct; break; case GIMarshallingTests.StructuredUnionType.SINGLE_UNION: invObj = member.union_; break; } expect(member.type).toBe(union.type()); expect(() => (invObj ?? member.parent).inv()).not.toThrow(); }); it('cannot be constructed with private field', function () { expect(() => new GIMarshallingTests.StructuredUnion({ type: unionType, })).toThrow(); expect(() => new GIMarshallingTests.StructuredUnion({ _type: unionType, })).toThrow(); }); it(`can be constructed with member value ${memberType}`, function () { if (unionType === GIMarshallingTests.StructuredUnionType.NONE) return; const member = new GIMarshallingTests[`StructuredUnion${memberType}`](); if (unionType === GIMarshallingTests.StructuredUnionType.SINGLE_UNION) { expect(member.parent.type).toBe(GIMarshallingTests.StructuredUnionType.NONE); member.parent.type = unionType; } else { expect(member.type).toBe(GIMarshallingTests.StructuredUnionType.NONE); member.type = unionType; } const union = new GIMarshallingTests.StructuredUnion({ [unionTypeName.toLowerCase()]: member, }); expect(union.type()).toBe(unionType); expect(union._type).toBe(unionType); }); it(`can be constructed from constructed member ${memberType}`, function () { if (unionType === GIMarshallingTests.StructuredUnionType.NONE) return; const baseUnion = new GIMarshallingTests.StructuredUnion(unionType); const prop = unionTypeName.toLowerCase(); const member = baseUnion[prop]; const union = new GIMarshallingTests.StructuredUnion({[prop]: member}); expect(union.type()).toBe(baseUnion.type()); expect(union.type()).toBe(member.type ?? member.parent.type); expect(union._type).toBe(baseUnion._type); }); }); it('can be constructed from boxed struct property', function () { const member = new GIMarshallingTests.StructuredUnionBoxedStruct(); member.parent = GIMarshallingTests.boxed_struct_returnv(); const union = new GIMarshallingTests.StructuredUnion({ 'boxed_struct': member, }); System.gc(); // test that member.parent is traced expect(union.boxed_struct.parent.long_).toBe(42); expect(union.boxed_struct.parent.string_).toBe('hello'); expect(union.boxed_struct.parent.g_strv).toEqual(['0', '1', '2']); }); it('can be created with a default constructor', function () { const u = new GIMarshallingTests.StructuredUnion(GIMarshallingTests.StructuredUnionType.NONE); expect(u).toBeInstanceOf(GIMarshallingTests.StructuredUnion); }); it('can be created with NONE', function () { const t = GIMarshallingTests.StructuredUnionType.NONE; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); }); it('can be created with SIMPLE_STRUCT', function () { const t = GIMarshallingTests.StructuredUnionType.SIMPLE_STRUCT; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.simple_struct.parent.long_).toBe(6); expect(u.simple_struct.parent.int8).toBe(7); }); it('can be created with NESTED_STRUCT', function () { const t = GIMarshallingTests.StructuredUnionType.NESTED_STRUCT; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.nested_struct.parent.simple_struct.long_).toBe(6); expect(u.nested_struct.parent.simple_struct.int8).toBe(7); }); it('can be created with BOXED_STRUCT', function () { const t = GIMarshallingTests.StructuredUnionType.BOXED_STRUCT; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.boxed_struct.parent.long_).toBe(42); expect(u.boxed_struct.parent.string_).toBe('hello'); expect(u.boxed_struct.parent.g_strv).toEqual(['0', '1', '2']); }); it('can be created with BOXED_STRUCT_PTR', function () { const t = GIMarshallingTests.StructuredUnionType.BOXED_STRUCT_PTR; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.boxed_struct_ptr.parent.long_).toBe(42); expect(u.boxed_struct_ptr.parent.string_).toBe('hello'); expect(u.boxed_struct_ptr.parent.g_strv).toEqual(['0', '1', '2']); }); it('can be created with POINTER_STRUCT', function () { const t = GIMarshallingTests.StructuredUnionType.POINTER_STRUCT; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.pointer_struct.parent.long_).toBe(42); }); it('can be created with SINGLE_UNION', function () { const t = GIMarshallingTests.StructuredUnionType.SINGLE_UNION; const u = new GIMarshallingTests.StructuredUnion(t); expect(u.type()).toBe(t); expect(u.single_union.parent.union_.long_).toBe(42); }); }); 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(`picks a reasonable default when uninitialized as out parameter with transfer ${transfer}`, function () { expect(GIMarshallingTests.Object[`${transfer}_out_uninitialized`]()).toEqual([false, null]); }); 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 in argument through a method that indirectly calls the vfunc', function () { tester.int8_in(39); expect(tester.int).toEqual(39); }); it('marshals an out argument', function () { expect(tester.method_int8_out()).toEqual(40); }); it('marshals an out argument through a method that indirectly calls the vfunc', function () { expect(tester.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('Alloc,') || 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('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and one out parameter', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a return value and multiple out parameters', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an array out parameter', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum return value', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals an enum out parameter', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags return value', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); it('marshals a flags out parameter', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGIMarshalling.js', 0, 'testVFuncReturnWrongValue'); }); }); let StaticVFuncTester; try { StaticVFuncTester = GObject.registerClass( class StaticVFuncTesterClass extends VFuncTester { static vfunc_vfunc_static_name() { return 'Overridden name'; } static vfunc_vfunc_static_create_new(int) { return new StaticVFuncTester({int}); } static vfunc_vfunc_static_create_new_out(int) { return new StaticVFuncTester({int}); } }); } catch (e) { if (!`${e}`.includes('Could not find definition of virtual function')) throw e; } describe('Static virtual functions', function () { beforeEach(function () { if (!StaticVFuncTester) { if (GIMarshallingTests.Object.vfunc_static_name) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/543'); } }); it('has static_name', function () { expect(GIMarshallingTests.Object.vfunc_static_name()).toBe( 'GIMarshallingTestsObject'); expect(StaticVFuncTester.vfunc_static_name()).toBe( 'GIMarshallingTestsObject'); }); it('has static_typed_name', function () { expect(GIMarshallingTests.Object.vfunc_static_typed_name( GIMarshallingTests.Object.$gtype)).toBe('GIMarshallingTestsObject'); expect(StaticVFuncTester.vfunc_static_typed_name(StaticVFuncTester.$gtype)) .toBe('Overridden name'); }); ['', '_out'].forEach(suffix => it(`has static_create_new${suffix}`, function () { const baseObj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( GIMarshallingTests.Object.$gtype, 55); expect(baseObj).toBeInstanceOf(GIMarshallingTests.Object); expect(baseObj).not.toBeInstanceOf(StaticVFuncTester); expect(baseObj.int_).toBe(55); const middleObj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( VFuncTester.$gtype, 35); expect(middleObj).toBeInstanceOf(GIMarshallingTests.Object); expect(middleObj).not.toBeInstanceOf(VFuncTester); expect(middleObj.int_).toBe(35); const obj = GIMarshallingTests.Object[`vfunc_static_create_new${suffix}`]( StaticVFuncTester.$gtype, 85); expect(obj).toBeInstanceOf(GIMarshallingTests.Object); expect(obj).toBeInstanceOf(VFuncTester); expect(obj).toBeInstanceOf(StaticVFuncTester); expect(obj.int_).toBe(85); })); }); 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); }); it('has a vfunc default implementation that can be called', function () { const o = new GIMarshallingTests[klass]({int: 0}); o.vfunc_method_deep_hierarchy(44); expect(o.int).toBe(44); }); it('has a vfunc that can be overridden', function () { class Derived extends GIMarshallingTests[klass] { static [GObject.GTypeName] = `Derived${klass}`; static { GObject.registerClass(Derived); } vfunc_method_deep_hierarchy(param) { expect(param).toBe(45); this.int = 46; } } const o = new Derived({int: 0}); o.vfunc_method_deep_hierarchy(45); expect(o.int).toBe(46); }); }); }); }); 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', ]); }); testUninitializedOutParameter('gerror', null); 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('picks a reasonable default value when out parameter is uninitialized with transfer none', function () { expect(GIMarshallingTests.gerror_out_transfer_none_uninitialized()).toEqual([false, null, null]); }); it('marshals GError as a return value', function () { expect(GIMarshallingTests.gerror_return()).toEqual(jasmine.any(GLib.Error)); }); }); 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)); testUninitializedOutParameter('param_spec', null); }); describe('GObject properties', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.PropertiesObject(); }); function testPropertyGetSet(type, value1, value2) { 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 () { 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('_', '-'); const isBigInt = v => v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER); 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(warn64(isBigInt(value1), () => obj[snakeCase])).toEqual( Number(value1)); obj[snakeCase] = value2; expect(handler).toHaveBeenCalledTimes(2); expect(warn64(isBigInt(value2), () => 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); testPropertyGetSetBigInt('uint64', 0n, BigInt(Number.MAX_SAFE_INTEGER)); testPropertyGetSet('string', 'Gjs', '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 () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); obj.some_deprecated_int = 35; GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); expect(obj.some_deprecated_int).toBe(35); GLib.test_assert_expected_messages_internal('Gjs', '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(); expect(() => (overriding.someInt = 85)).toThrowError(TypeError); 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)); }); it('can be created from C constructor as well', function () { obj = GIMarshallingTests.PropertiesObject.new(); expect(obj).toBeInstanceOf(GIMarshallingTests.PropertiesObject); }); }); describe('GObject properties accessors', function () { let obj; beforeEach(function () { obj = new GIMarshallingTests.PropertiesAccessorsObject(); }); function testPropertyGetSet(type, value1, value2) { 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 () { obj[propertyName] = value1; expect(obj[propertyName]).toEqual(value1); obj[propertyName] = value2; expect(obj[propertyName]).toEqual(value2); }); }); } function testPropertyGetSetBigInt(type, value1, value2) { const isBigInt = v => v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER); it(`gets and sets a ${type} property with a bigint`, function () { obj[`some_${type}`] = value1; expect(warn64(isBigInt(value1), () => obj[`some_${type}`])).toEqual( Number(value1)); obj[`some_${type}`] = value2; expect(warn64(isBigInt(value2), () => obj[`some_${type}`])).toEqual( Number(value2)); }); } 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', 'Gjs', 'is cool!'); 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 () { obj.some_float = Math.E; expect(obj.some_float).toBeCloseTo(Math.E); obj.some_float = Math.PI; expect(obj.some_float).toBeCloseTo(Math.PI); }); it('gets and sets a double property', function () { obj.some_double = Math.E; expect(obj.some_double).toBeCloseTo(Math.E); obj.some_double = Math.PI; expect(obj.some_double).toBeCloseTo(Math.PI); }); testPropertyGetSet('strv', ['0', '1', '2'], []); testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(), new GIMarshallingTests.BoxedStruct({long_: 42})); // testPropertyGetSet('boxed_glist', [1, 2, 3], []); 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('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('👾')); 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 () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); obj.some_deprecated_int = 35; GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GObject property*.some-deprecated-int is deprecated*'); expect(obj.some_deprecated_int).toBe(35); GLib.test_assert_expected_messages_internal('Gjs', 'testGIMarshalling.js', 0, 'testAllowToSetGetDeprecatedProperties'); }); }); 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', 'full'].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})); }); xit('not-ref-counted boxed types with transfer full originating from C are properly handled', function () { const callbackFunc = jasmine.createSpy('callbackFunc'); const signalId = obj.connect('some-boxed-struct-full', callbackFunc); obj.emit_boxed_struct_full(); obj.disconnect(signalId); expect(callbackFunc).toHaveBeenCalledOnceWith(obj, new GIMarshallingTests.BoxedStruct({long_: 99, string_: 'a string', g_strv: ['foo', 'bar', 'baz']})); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/470'); it('can be created from C constructor as well', function () { obj = GIMarshallingTests.SignalsObject.new(); expect(obj).toBeInstanceOf(GIMarshallingTests.SignalsObject); }); }); // Adapted from pygobject describe('GError extra tests', function () { it('marshals GError instances through GValue', function () { const error = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); const error1 = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); GIMarshallingTests.compare_two_gerrors_in_gvalue(error, error1); }); it('can be nullable', function () { const error = GLib.Error.new_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, 'error'); expect(GIMarshallingTests.nullable_gerror(error)).toBeTruthy(); expect(GIMarshallingTests.nullable_gerror(null)).toBeFalsy(); }); }); // Adapted from pygobject describe('GHashTable extra tests', function () { it('marshals a hash table of enums as an in argument', function () { GIMarshallingTests.ghashtable_enum_none_in({ 1: GIMarshallingTests.ExtraEnum.VALUE1, 2: GIMarshallingTests.ExtraEnum.VALUE2, 3: GIMarshallingTests.ExtraEnum.VALUE3, }); }); it('marshals a hash table of enums as a return value', function () { expect(GIMarshallingTests.ghashtable_enum_none_return()).toEqual({ 1: GIMarshallingTests.ExtraEnum.VALUE1, 2: GIMarshallingTests.ExtraEnum.VALUE2, 3: GIMarshallingTests.ExtraEnum.VALUE3, }); }); }); // Adapted from pygobject describe('Filename tests', function () { let workdir; beforeAll(function (done) { Gio.File.new_tmp_dir_async(null, GLib.PRIORITY_DEFAULT, null, (self, result) => { workdir = Gio.File.new_tmp_dir_finish(result); done(); }); }); afterAll(function () { GLib.rmdir(workdir.get_path()); }); it('wrong types', function () { expect(() => GIMarshallingTests.filename_copy(23)).toThrowError(); expect(() => GIMarshallingTests.filename_copy([])).toThrowError(); }); it('nullability', function () { expect(GIMarshallingTests.filename_copy(null)).toBeNull(); expect(() => GIMarshallingTests.filename_exists(null)).toThrowError(); }); it('round-tripping', function () { expect(GIMarshallingTests.filename_copy('foo')).toBe('foo'); }); // We run the tests with Latin1 filename encoding, to catch mistakes it('various types of paths in GLib encoding', function () { const strPath = GIMarshallingTests.filename_copy('ä'); expect(strPath).toBe('ä'); expect(GIMarshallingTests.filename_to_glib_repr(strPath)) .toEqual(Uint8Array.of(0xe4)); }); it('various types of path existing', function () { const paths = ['foo-2', 'öäü-3']; for (const path of paths) { const file = workdir.get_child(path); const stream = file.create(Gio.FileCreateFlags.NONE, null); expect(GIMarshallingTests.filename_exists(file.get_path())).toBeTrue(); stream.close(null); file.delete(null); } }); }); // Adapted from pygobject describe('Array of enum extra tests', function () { it('marshals a C array of enum values as a return value', function () { expect(GIMarshallingTests.enum_array_return_type()).toEqual([0, 1, 42]); }); }); // Adapted from pygobject describe('Flags extra tests', function () { it('marshals a 32-high bit flags value as an in argument', function () { GIMarshallingTests.extra_flags_large_in(GIMarshallingTests.ExtraFlags.VALUE2); }); }); // Adapted from pygobject describe('UTF-8 strings invalid bytes tests', function () { it('handles invalid UTF-8 return values gracefully', function () { expect(() => GIMarshallingTests.extra_utf8_full_return_invalid()).toThrowError(TypeError); }); it('handles invalid UTF-8 out arguments gracefully', function () { expect(() => GIMarshallingTests.extra_utf8_full_out_invalid()).toThrowError(TypeError); }); }); cjs-140.0/installed-tests/js/testGLib.js0000664000175000017500000005133515167114161017041 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna // SPDX-FileCopyrightText: 2019, 2023 Philip Chimento import GLib from 'gi://GLib'; let GLibUnix; try { GLibUnix = (await import('gi://GLibUnix')).default; } catch {} const PLATFORM_DEPENDENT_NS_SPLIT = GLib.MAJOR_VERSION > 2 || (GLib.MAJOR_VERSION === 2 && (GLib.MINOR_VERSION > 87 || (GLib.MINOR_VERSION === 87 && GLib.MICRO_VERSION >= 3))); 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); }); }); describe('GLib source function overrides', function () { let loop, spy; beforeEach(function () { loop = new GLib.MainLoop(null, false); spy = jasmine.createSpy('sourceFunc'); }); it('GLib.idle_add_once', function () { GLib.idle_add_once(GLib.PRIORITY_DEFAULT, spy); GLib.idle_add(GLib.PRIORITY_LOW, () => loop.quit()); loop.run(); expect(spy).toHaveBeenCalledTimes(1); }); it('GLib.timeout_add_once', function () { GLib.timeout_add_once(GLib.PRIORITY_DEFAULT, 50, spy); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => loop.quit()); loop.run(); expect(spy).toHaveBeenCalledTimes(1); }); it('GLib.timeout_add_seconds_once', function () { GLib.timeout_add_seconds_once(GLib.PRIORITY_DEFAULT, 1, spy); GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => loop.quit()); loop.run(); expect(spy).toHaveBeenCalledTimes(1); }); }); describe('GLib string function overrides', function () { let numExpectedWarnings; function expectWarnings(count) { numExpectedWarnings = count; for (let c = 0; c < count; c++) { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { GLib.test_assert_expected_messages_internal('Gjs', '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 pointer. 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('GjsPrivate.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('GjsPrivate.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('GjsPrivate.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('GjsPrivate.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(); }); }); }); describe('GLibUnix functionality', function () { beforeEach(function () { if (!GLibUnix) pending('Not supported platform'); }); it('provides structs', function () { new GLibUnix.Pipe(); }); it('provides functions', function () { GLibUnix.fd_source_new(0, GLib.IOCondition.IN); }); it('provides enums', function () { expect(GLibUnix.PipeEnd.READ).toBe(0); expect(GLibUnix.PipeEnd.WRITE).toBe(1); }); }); describe('GLibUnix compatibility fallback', function () { beforeEach(function () { if (!GLibUnix) pending('Not supported platform'); }); function expectDeprecationWarning(testFunction, oldName = '*', newName = '*') { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, `*GLib.${oldName} has been moved to a separate platform-specific library. ` + `Please update your code to use GLibUnix.${newName} instead*`); const ret = testFunction(); GLib.test_assert_expected_messages_internal('Gjs', 'testGLib.js', 0, `GLib.${oldName} expected warns on GLib platform-specific fallback`); return ret; } it('provides structs', function () { expectDeprecationWarning(() => new GLib.UnixPipe()); }); it('provides functions', function () { expectDeprecationWarning(() => GLib.unix_fd_source_new(0, GLib.IOCondition.IN)); }); it('provides enum value UnixPipeEnd.READ', function () { expectDeprecationWarning(() => { expect(GLib.UnixPipeEnd.READ).toBe(0); }); }); it('provides enum value UnixPipeEnd.WRITE', function () { if (!PLATFORM_DEPENDENT_NS_SPLIT) pending('Platform-dependent namespace has not been split on this GLib version'); expectDeprecationWarning(() => { expect(GLib.UnixPipeEnd.WRITE).toBe(1); }); }); describe('provides platform-independent symbol', function () { const symbols = { 'UnixPipe': { newName: 'Pipe', needsNewerGLibVersion: true, }, 'UnixPipeEnd': { newName: 'PipeEnd', needsNewerGLibVersion: true, }, 'closefrom': { needsNewerGLibVersion: true, }, 'unix_error_quark': { newName: 'error_quark', }, 'unix_fd_add_full': { newName: 'fd_add_full', }, 'unix_fd_query_path': { newName: 'fd_query_path', needsNewerGLibVersion: true, }, 'unix_fd_source_new': { newName: 'fd_source_new', needsNewerGLibVersion: true, }, 'fdwalk_set_cloexec': { needsNewerGLibVersion: true, }, 'unix_get_passwd_entry': { newName: 'get_passwd_entry', }, 'unix_open_pipe': { newName: 'open_pipe', }, 'unix_set_fd_nonblocking': { newName: 'set_fd_nonblocking', }, 'unix_signal_add': { newName: 'signal_add', needsNewerGLibVersion: true, }, 'unix_signal_source_new': { newName: 'signal_source_new', }, }; Object.entries(symbols).forEach(([name, testData]) => { it(`GLib.${name}`, function () { if (testData.needsNewerGLibVersion && !PLATFORM_DEPENDENT_NS_SPLIT) pending('Platform-dependent namespace has not been split on this GLib version'); const newName = testData.newName ?? name; // We need to use a named function so that the warning system can // consider this a different call site for each symbol tested. const getterName = `${name}_getter`; const valueGetter = { [getterName]() { return GLib[name]; }, }[getterName]; const oldValue = expectDeprecationWarning(valueGetter, name, newName); expect(oldValue).toBeDefined(); expect(GLibUnix[newName]).toBeDefined(); if (PLATFORM_DEPENDENT_NS_SPLIT) expect(oldValue).toBe(GLibUnix[newName]); }); }); }); it('provides signal_add_full wrapper for signal_add', function () { if (!PLATFORM_DEPENDENT_NS_SPLIT) pending('Platform-dependent namespace has not been split on this GLib version'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*GLibUnix.signal_add_full has been renamed. ' + 'Please update your code to use GLibUnix.signal_add instead*'); const oldValue = GLibUnix.signal_add_full; GLib.test_assert_expected_messages_internal('Gjs', 'testGLib.js', 0, 'GLibUnix signal_add_full rename'); expect(oldValue).toBe(GLibUnix.signal_add); }); }); cjs-140.0/installed-tests/js/testGLibLogWriter.js0000664000175000017500000000674215167114161020702 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); }); afterAll(function () { GLib.log_set_writer_default(); }); 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, ]), }) ); }); it('only considers own properties of the field argument', function () { const proto = {BAD_PROP: 'a prototype property'}; const fields = Object.assign(Object.create(proto), { MESSAGE: 'own property', GJS_CUSTOM_FIELD: 'another own property', }); GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, fields); expect(writer_func).toHaveBeenCalled(); expect(writer_func).not.toHaveBeenCalledWith( GLib.LogLevelFlags.LEVEL_MESSAGE, jasmine.objectContaining({BAD_PROP: jasmine.anything()})); }); }); cjs-140.0/installed-tests/js/testGObject.js0000664000175000017500000001532615167114161017541 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. import GjsTestTools from 'gi://GjsTestTools'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import System from 'system'; 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('guards against signal emission on non-js thread', function () { handler.calls.reset(); GObject.signal_connect(o, 'test', handler); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Attempting to call back into JSAPI on a different thread.*The offending signal was test on Gjs_TestObj*'); GjsTestTools.emit_test_signal_other_thread(o); expect(handler).not.toHaveBeenCalled(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObject.js', 0, 'testSignalEmissionOtherThread'); }); }); 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-140.0/installed-tests/js/testGObjectClass.js0000664000175000017500000020570315167114161020527 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=3.0'; import System from 'system'; imports.searchPath.unshift('resource:///org/gjs/jsunit/modules'); const {setPropertyInSloppyMode} = imports.sloppy; // 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(func, ...args) { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); const ret = func(...args); const error = new Error(); GLib.test_assert_expected_messages_internal('Gjs', error.fileName, error.lineNumber, 'warn64'); return ret; } 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('Gjs', GLib.LogLevelFlags.LEVEL_MESSAGE, '*Too many arguments*'); new ObjectWithDefaultConstructor({}, 'this is ignored', 123); GLib.test_assert_expected_messages_internal('Gjs', '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 in sloppy mode', function () { setPropertyInSloppyMode(myInstance, 'construct', 'val'); expect(myInstance.construct).toEqual('default'); }); it('throws when setting CONSTRUCT_ONLY properties in strict mode', function () { expect(() => (myInstance.construct = 'val')).toThrow(); }); 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 = warn64(() => new PropInt64({int64})); expect(obj.int64).toEqual(Number(int64)); int64 = GLib.MININT64_BIGINT + 555n; obj = warn64(() => 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 = warn64(() => 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 = warn64(() => 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 = warn64(() => 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('Gjs', '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 in sloppy mode', function () { setPropertyInSloppyMode(myInstance, 'construct', 'val'); expect(myInstance.construct).toEqual('default'); }); it('does not allow changing CONSTRUCT_ONLY properties in strict mode', function () { expect(() => (myInstance.construct = 'val')).toThrow(); }); 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(); expect(() => GObject.registerClass({ Implements: [Gio.AsyncInitable], }, class FooStatic extends GObject.Object { static vfunc_not_existing() {} })).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); }); it('supports static methods', function () { if (!Gio.Icon.vfunc_from_tokens) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/543'); expect(() => GObject.registerClass({ Implements: [Gio.Icon], }, class extends GObject.Object { static vfunc_from_tokens() {} })).not.toThrow(); }); it('must be non-static for methods', function () { expect(() => GObject.registerClass({ Implements: [Gio.Icon], }, class extends GObject.Object { static vfunc_serialize() {} })).toThrowError(/.* static definition of non-static.*/); }); it('must be static for methods', function () { if (!Gio.Icon.vfunc_from_tokens) pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/543'); expect(() => GObject.registerClass({ Implements: [Gio.Icon], }, class extends GObject.Object { vfunc_from_tokens() {} })).toThrowError(/.*non-static definition of static.*/); }); }); 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('Gjs', 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('Gjs', '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 () { 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 () { 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': globalThis}, 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': globalThis}, 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': globalThis}, 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': globalThis}, 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => true); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 1); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => Math.PI); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*JSObject expected*'); myInstance.connect('get-object', () => 'string'); myInstance.emit('get-object', {}); GLib.test_assert_expected_messages_internal('Gjs', '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-140.0/installed-tests/js/testGObjectDestructionAccess.js0000664000175000017500000007056615167114161023116 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Canonical, Ltd. import Gio from 'gi://Gio'; import GjsTestTools from 'gi://GjsTestTools'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=3.0'; import System from 'system'; 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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.title).toBe('To be destroyed'); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); destroyedWindow.expandoProperty = 'Hello!'; GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.get_title()).toBe('To be destroyed'); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect('foo-signal', () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.connect_after('foo-signal', () => {})).toBe(0); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { GLib.test_expect_message('Gjs', 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('Gjs', '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.previouslySetExpandoProperty = 'Hello!'; destroyedWindow.destroy(); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* disposed *'); GjsTestTools.unref(destroyedWindow); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); afterEach(function () { destroyedWindow = null; GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'generates a warn on object garbage collection'); }); it('Get property', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.title).toBeUndefined(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertyGet'); }); it('Set property', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.title = 'I am dead'; expect(destroyedWindow.title).toBeUndefined(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectPropertySet'); }); it('Add expando property', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); destroyedWindow.expandoProperty = 'Hello!'; GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectExpandoPropertySet'); }); it('Access to unset expando property', function () { expect(destroyedWindow.expandoProperty).toBeUndefined(); }); it('Access previously set expando property', function () { expect(destroyedWindow.previouslySetExpandoProperty).toBe('Hello!'); }); it('Access to getter method', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodGet'); }); it('Access to setter method', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectMethodSet'); }); it('Proto function connect', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect('foo-signal', () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnect'); }); it('Proto function connect_after', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.connect_after('foo-signal', () => { })).toBe(0); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectConnectAfter'); }); it('Proto function emit', function () { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Window (0x* finalized *'); expect(destroyedWindow.emit('keys-changed')).toBeUndefined(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectEmit'); }); it('Proto function signals_disconnect', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsDisconnect'); }); it('Proto function signals_block', function () { GLib.test_expect_message('Gjs', 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('Gjs', 'testGObjectDestructionAccess.js', 0, 'testExceptionInDestroyedObjectSignalsBlock'); }); it('Proto function signals_unblock', function () { GLib.test_expect_message('Gjs', 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('Gjs', '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 () { GjsTestTools.init(); }); afterEach(function () { GjsTestTools.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', 'dummy-symbolic']); 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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was dispose()*'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectDestructionAccess.js', 0, 'calls dispose vfunc on explicit disposal only'); expect(callSpy).toHaveBeenCalledTimes(2); }); it('generates a warn on object garbage collection', function () { GjsTestTools.unref(Gio.File.new_for_path('/')); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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; GjsTestTools.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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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('/'); GjsTestTools.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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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('/'); GjsTestTools.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(GjsTestTools.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(GjsTestTools.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('/'); GjsTestTools.save_object(file); GjsTestTools.delayed_unref(file, 30); file.run_dispose(); let disposedFile = GjsTestTools.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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); disposedFile = null; System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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; GjsTestTools.ref(file); GjsTestTools.unref_other_thread(file); file.run_dispose(); }); it('ignores toggling queued toggles', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; GjsTestTools.ref_other_thread(file); GjsTestTools.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; GjsTestTools.ref(file); GjsTestTools.unref_other_thread(file); GjsTestTools.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; GjsTestTools.run_dispose_other_thread(file); file = null; System.gc(); }); }); describe('GObject with toggle references', function () { beforeAll(function () { GjsTestTools.init(); }); afterEach(function () { GjsTestTools.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); GjsTestTools.save_object_unreffed(file); GjsTestTools.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 = GjsTestTools.steal_saved(); if (file) { expect(System.addressOfGObject(file)).toBe(objectAddress); expect(file instanceof Gio.File).toBeTruthy(); GjsTestTools.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; GjsTestTools.save_object(file); GjsTestTools.ref(file); GjsTestTools.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 = GjsTestTools.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; GjsTestTools.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; GjsTestTools.save_weak(file); GjsTestTools.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; GjsTestTools.save_weak(file); GjsTestTools.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; GjsTestTools.save_weak(file); GjsTestTools.get_weak_other_thread(); // Ok, let's play more dirty now... GjsTestTools.ref(file); // toggle up GjsTestTools.unref(file); // toggle down GjsTestTools.ref(file); GjsTestTools.ref(file); GjsTestTools.unref(file); GjsTestTools.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; GjsTestTools.save_weak(file); GjsTestTools.get_weak_other_thread(); GjsTestTools.ref_other_thread(file); GjsTestTools.unref_other_thread(file); GjsTestTools.ref(file); GjsTestTools.unref(file); GjsTestTools.ref_other_thread(file); GjsTestTools.unref(file); GjsTestTools.ref(file); GjsTestTools.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(GjsTestTools.delayed_ref_unref_other_thread(file, 0)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 100000)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 200000)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 300000)); GjsTestTools.save_object(file); GjsTestTools.save_weak(file); file = null; System.gc(); threads.forEach(th => th.join()); GjsTestTools.clear_saved(); System.gc(); expect(GjsTestTools.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(GjsTestTools.delayed_ref_unref_other_thread(file, 0)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 100000)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 200000)); threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 300000)); GjsTestTools.save_object(file); GjsTestTools.save_weak(file); file.run_dispose(); file = null; System.gc(); threads.forEach(th => th.join()); GjsTestTools.clear_saved(); expect(GjsTestTools.get_weak()).toBeNull(); }); it('can be finalized while queued in toggle queue', function () { let file = Gio.File.new_for_path('/'); file.expandMeWithToggleRef = true; GjsTestTools.ref(file); GjsTestTools.unref_other_thread(file); GjsTestTools.unref_other_thread(file); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*Object 0x* has been finalized *'); file = null; System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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; GjsTestTools.save_weak(file); const ids = []; let threads = []; ids.push(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { threads = threads.slice(-50); try { threads.push(GjsTestTools.delayed_ref_unref_other_thread(file, 1)); } catch { // 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(GjsTestTools.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 GjsTestTools.save_object(file); file = null; System.gc(); threads.forEach(th => th.join()); expect(GjsTestTools.get_saved_ref_count()).toBeGreaterThan(0); GjsTestTools.clear_saved(); System.gc(); expect(GjsTestTools.get_weak()).toBeNull(); }).pend('Flaky, see https://gitlab.gnome.org/GNOME/gjs/-/issues/568'); }); cjs-140.0/installed-tests/js/testGObjectInterface.js0000664000175000017500000004133015167114161021354 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from '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('Gjs', '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-140.0/installed-tests/js/testGObjectValue.js0000664000175000017500000002054515167114161020535 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import GIMarshallingTests from 'gi://GIMarshallingTests'; import Regress from 'gi://Regress'; 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', 'enum']; const INSTANCED_TYPES = ['object', 'instance']; const ALL_TYPES = [...NUMERIC_TYPES, ...SPECIFIC_TYPES, ...INSTANCED_TYPES]; // Test that constructors can be used in place of GType arguments and corresponds to specified type const CONSTRUCTORS = [[String, 'string'], [Number, 'double'], [Boolean, 'boolean'], [Object, 'boxed'], [GIMarshallingTests.PropertiesObject, 'object'], [Regress.TestEnum, 'enum']]; describe('GObject value (GValue)', function () { let v, overrideV; 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 === 'enum') return Regress.TestEnum[`VALUE${(Math.random() * 5 | 0) + 1}`]; if (type === 'boxed' || type === 'boxed-struct') { return new GIMarshallingTests.BoxedStruct({ long_: getDefaultContentByType('long'), // string_: getDefaultContentByType('string'), not supported }); } if (type === 'object') { const wasCreatingObject = globalThis.creatingObject; globalThis.creatingObject = true; const props = ALL_TYPES.filter(e => (e !== 'object' || !wasCreatingObject) && e !== 'boxed' && e !== 'gtype' && e !== 'instance' && e !== 'param' && e !== 'schar' && e !== 'enum').concat([ 'boxed-struct', ]).reduce((ac, a) => ({ ...ac, [`some-${a}`]: getDefaultContentByType(a), }), {}); delete globalThis.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, ...CONSTRUCTORS].forEach(type => { let gtype; // for testing constructor/type tuples if (Array.isArray(type)) [gtype, type] = [type[0], type[1]]; else 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); overrideV = new GObject.Value(gtype, randomContent); }); it(`sets and gets ${type}`, function () { skipUnsupported(type); setContent(v, type, randomContent); expect(getContent(v, type)).toEqual(randomContent); expect(getContent(overrideV, 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); expect(GIMarshallingTests.gvalue_round_trip(overrideV)).toEqual(randomContent); expect(GIMarshallingTests.gvalue_copy(overrideV)).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); overrideV.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); overrideV.init_from_instance(instance); expect(getContent(overrideV, type)).toEqual(instance); }); }); afterEach(function () { v.unset(); overrideV?.unset(); }); }); cjs-140.0/installed-tests/js/testGTypeClass.js0000664000175000017500000000410515167114161020233 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 import Gio from 'gi://Gio'; import GObject from '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 { } expect(GObject.TYPE_STRING.name).toEqual('gchararray'); }); it('has an undeletable name', function () { try { delete GObject.TYPE_STRING.name; } catch { } 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-140.0/installed-tests/js/testGettext.js0000664000175000017500000000123715167114161017644 0ustar fabiofabio// -*- mode: js; indent-tabs-mode: nil -*- // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2015 Endless Mobile, Inc. import Gettext from '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-140.0/installed-tests/js/testGio.js0000664000175000017500000006357115167114161016747 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2017 Patrick Griffis // SPDX-FileCopyrightText: 2019 Philip Chimento import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; let GioUnix; try { GioUnix = (await import('gi://GioUnix')).default; } 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', parameter_type: new GLib.VariantType('s'), }, { name: 'baz', state: 'false', }, { name: 'qux', state: GLib.Variant.new_boolean(true), }, { name: 'quux', state: true, }, ]; 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').parameter_type.dup_string()).toEqual(entries[1].parameter_type.dup_string()); expect(app.lookup_action('baz').name).toEqual(entries[2].name); expect(app.lookup_action('baz').state.print(true)).toEqual(entries[2].state); expect(app.lookup_action('qux').name).toEqual(entries[3].name); expect(app.lookup_action('qux').state.print(true)).toEqual(entries[3].state.print(true)); expect(app.lookup_action('quux').name).toEqual(entries[4].name); expect(app.lookup_action('quux').state.get_boolean()).toEqual(entries[4].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 () { let keyFile; const desktopFileContent = `[Desktop Entry] Version=1.0 Type=Application Name=Some Application Exec=${GLib.find_program_in_path('sh')} `; beforeAll(function () { keyFile = new GLib.KeyFile(); keyFile.load_from_data(desktopFileContent, desktopFileContent.length, GLib.KeyFileFlags.NONE); }); beforeEach(function () { if (!GioUnix) pending('Not supported platform'); }); function expectDeprecationWarning(testFunction) { GLib.test_expect_message('Gjs', 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('Gjs', '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 () { 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 () { 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*not introspectable*'); } } function assertWarnings(testName) { for (let c = 0; c < numExpectedWarnings; c++) { GLib.test_assert_expected_messages_internal('Gjs', '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(['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)); }); }); describe('GioUnix compatibility fallback', function () { beforeEach(function () { if (!GioUnix) pending('Not supported platform'); }); function expectDeprecationWarning(testFunction, oldName, newName) { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, `*Gio.${oldName} has been moved to a separate platform-specific library. ` + `Please update your code to use GioUnix.${newName} instead*`); const ret = testFunction(); GLib.test_assert_expected_messages_internal('Gjs', 'testGio.js', 0, `Gio.${oldName} expected warns on Gio platform-specific fallback`); return ret; } describe('provides platform-independent symbol', function () { // Sadly we've to be repetitive here and not just do everything in a loop, // because we want each test to run from a different callsite, otherwise // no warning will be emitted after the first one. const symbols = { 'DESKTOP_APP_INFO_LOOKUP_EXTENSION_POINT_NAME': {}, 'DesktopAppInfo': {}, 'DesktopAppInfoLookup': {}, 'UnixFDMessage': { newName: 'FDMessage', }, 'UnixFDMessagePrivate': { newName: 'FDMessagePrivate', }, 'FileDescriptorBased': { newName: 'FileDescriptorBased', }, 'UnixInputStream': { newName: 'InputStream', }, 'UnixInputStreamPrivate': { newName: 'InputStreamPrivate', }, 'UnixMountEntry': { newName: 'MountEntry', }, 'UnixMountMonitor': { newName: 'MountMonitor', }, 'UnixMountPoint': { newName: 'MountPoint', }, 'UnixOutputStream': { newName: 'OutputStream', }, 'UnixOutputStreamPrivate': { newName: 'OutputStreamPrivate', }, 'unix_is_mount_path_system_internal': { newName: 'is_mount_path_system_internal', }, 'unix_is_system_device_path': { newName: 'is_system_device_path', }, 'unix_is_system_fs_type': { newName: 'is_system_fs_type', }, 'unix_mount_at': { newName: 'mount_at', }, 'unix_mount_compare': { newName: 'mount_compare', }, 'unix_mount_copy': { newName: 'mount_copy', }, 'unix_mount_entries_changed_since': { newName: 'mount_entries_changed_since', }, 'unix_mount_entries_get': { newName: 'mount_entries_get', }, 'unix_mount_entries_get_from_file': { newName: 'mount_entries_get_from_file', }, 'unix_mount_entry_at': { newName: 'mount_entry_at', }, 'unix_mount_entry_for': { newName: 'mount_entry_for', }, 'unix_mount_for': { newName: 'mount_for', }, 'unix_mount_free': { newName: 'mount_free', }, 'unix_mount_get_device_path': { newName: 'mount_get_device_path', }, 'unix_mount_get_fs_type': { newName: 'mount_get_fs_type', }, 'unix_mount_get_mount_path': { newName: 'mount_get_mount_path', }, 'unix_mount_get_options': { newName: 'mount_get_options', }, 'unix_mount_get_root_path': { newName: 'mount_get_root_path', }, 'unix_mount_guess_can_eject': { newName: 'mount_guess_can_eject', }, 'unix_mount_guess_icon': { newName: 'mount_guess_icon', }, 'unix_mount_guess_name': { newName: 'mount_guess_name', }, 'unix_mount_guess_should_display': { newName: 'mount_guess_should_display', }, 'unix_mount_guess_symbolic_icon': { newName: 'mount_guess_symbolic_icon', }, 'unix_mount_is_readonly': { newName: 'mount_is_readonly', }, 'unix_mount_is_system_internal': { newName: 'mount_is_system_internal', }, 'unix_mount_point_at': { newName: 'mount_point_at', }, 'unix_mount_points_changed_since': { newName: 'mount_points_changed_since', }, 'unix_mount_points_get': { newName: 'mount_points_get', }, 'unix_mount_points_get_from_file': { newName: 'mount_points_get_from_file', }, 'unix_mounts_changed_since': { newName: 'mounts_changed_since', }, 'unix_mounts_get': { newName: 'mounts_get', }, 'unix_mounts_get_from_file': { newName: 'mounts_get_from_file', }, }; Object.entries(symbols).forEach(([name, testData]) => { it(`Gio.${name}`, function () { const newName = testData.newName ?? name; // We need to use a named function so that the warning system can // consider this a different call site for each symbol tested. const getterName = `${name}_getter`; const valueGetter = { [getterName]() { return Gio[name]; }, }[getterName]; const oldValue = expectDeprecationWarning(valueGetter, name, newName); expect(oldValue).not.toBeUndefined(); expect(GioUnix[newName]).not.toBeUndefined(); expect(oldValue).toBe(GioUnix[newName]); }); }); }); }); cjs-140.0/installed-tests/js/testGlobal.js0000664000175000017500000000220015167114161017407 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-140.0/installed-tests/js/testGtk3.js0000664000175000017500000004356015167114161017035 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna import Gdk from 'gi://Gdk?version=3.0'; import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=3.0'; import System from '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/gjs/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('Gjs', 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('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, '*during garbage collection*offending callback was destroy()*'); label = null; System.gc(); GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, 'Object Gtk.Label (0x* disposed *'); expect(label.label).toBe('Hello'); GLib.test_assert_expected_messages_internal('Gjs', '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(); }); }); describe('Gdk Events', function () { beforeAll(function () { Gtk.init(null); }); it('can construct generic', function () { expect(() => new Gdk.Event()).toThrow(); const event = new Gdk.Event(Gdk.EventType.KEY_PRESS); expect(event.constructor.$gtype).toBe(GObject.type_from_name('GdkEvent')); expect(event.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.any.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.key.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.selection.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.any.constructor.name).toBe('Gdk_EventAny'); expect(event.expose.constructor.name).toBe('Gdk_EventExpose'); expect(event.visibility.constructor.name).toBe('Gdk_EventVisibility'); expect(event.motion.constructor.name).toBe('Gdk_EventMotion'); expect(event.button.constructor.name).toBe('Gdk_EventButton'); expect(event.touch.constructor.name).toBe('Gdk_EventTouch'); expect(event.scroll.constructor.name).toBe('Gdk_EventScroll'); expect(event.key.constructor.name).toBe('Gdk_EventKey'); expect(event.crossing.constructor.name).toBe('Gdk_EventCrossing'); expect(event.focus_change.constructor.name).toBe('Gdk_EventFocus'); expect(event.configure.constructor.name).toBe('Gdk_EventConfigure'); expect(event.property.constructor.name).toBe('Gdk_EventProperty'); expect(event.selection.constructor.name).toBe('Gdk_EventSelection'); expect(event.owner_change.constructor.name).toBe('Gdk_EventOwnerChange'); expect(event.proximity.constructor.name).toBe('Gdk_EventProximity'); expect(event.dnd.constructor.name).toBe('Gdk_EventDND'); expect(event.window_state.constructor.name).toBe('Gdk_EventWindowState'); expect(event.setting.constructor.name).toBe('Gdk_EventSetting'); expect(event.grab_broken.constructor.name).toBe('Gdk_EventGrabBroken'); expect(event.touchpad_swipe.constructor.name).toBe('Gdk_EventTouchpadSwipe'); expect(event.touchpad_pinch.constructor.name).toBe('Gdk_EventTouchpadPinch'); expect(event.pad_button.constructor.name).toBe('Gdk_EventPadButton'); expect(event.pad_axis.constructor.name).toBe('Gdk_EventPadAxis'); expect(event.pad_group_mode.constructor.name).toBe('Gdk_EventPadGroupMode'); expect(event.It$anInvalidField).toBeUndefined(); }); it('can set generic properties', function () { const event = new Gdk.Event(Gdk.EventType.MOTION_NOTIFY); expect(event.type).toBe(Gdk.EventType.MOTION_NOTIFY); expect(event.motion.x).toBe(0); expect(event.motion.y).toBe(0); expect(() => (event.motion.window = 20)).toThrow(); const win = new Gtk.OffscreenWindow(); win.realize(); event.motion.window = win.get_window(); expect(event.motion.window).toBe(win.get_window()); event.motion.window = null; expect(event.motion.window).toBeNull(); const key = new Gdk.EventKey(); event.key = key; expect(event.key).toEqual(key); expect(() => (event.key = new Gdk.EventMotion())).toThrowError(/Event.key/); }); it('can construct specific with property', function () { const event = new Gdk.EventMotion(); expect(event.type).toBe(0); expect(event.x).toBe(0); expect(event.y).toBe(0); expect(() => (event.window = 20)).toThrow(); const win = new Gtk.OffscreenWindow(); win.realize(); event.window = win.get_window(); expect(event.window).toBe(win.get_window()); event.window = null; expect(event.window).toBeNull(); }); it('can construct specific with property', function () { const win = new Gtk.OffscreenWindow(); win.realize(); const event = new Gdk.EventMotion({x: 3.1, y: 1.2, window: win.get_window()}); expect(event.x).toBe(3.1); expect(event.y).toBe(1.2); expect(event.window).toBe(win.get_window()); }); it('can construct generic with an empty property bag', function () { const e = new Gdk.Event({}); expect(e.type).toBe(0); expect(e.any).toEqual(jasmine.any(Gdk.EventAny)); expect(e.any.type).toBe(0); expect(e.any.window).toBeNull(); expect(e.any.send_event).toBe(0); }); it('can construct generic with sub-type property', function () { const win = new Gtk.OffscreenWindow(); win.realize(); const keyEvent = new Gdk.EventKey({ type: Gdk.EventType.KEY_PRESS, state: 25, send_event: 35, keyval: Gdk.KEY_Return, window: win.get_window(), }); const event = new Gdk.Event({key: keyEvent}); expect(event.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.any.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.any.send_event).toBe(35); expect(event.any.window).toBe(win.get_window()); expect(event.key.type).toBe(Gdk.EventType.KEY_PRESS); expect(event.key.state).toBe(25); expect(event.key.send_event).toBe(35); expect(event.key.keyval).toBe(Gdk.KEY_Return); expect(event.key.window).toBe(win.get_window()); expect(event.get_window()).toBe(win.get_window()); expect(event.get_keyval()).toEqual([true, Gdk.KEY_Return]); expect(() => new Gdk.Event({motion: new Gdk.EventKey({state: 25})})).toThrow(); }); }); cjs-140.0/installed-tests/js/testGtk4.js0000664000175000017500000007216315167114161017037 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2013 Giovanni Campagna import Gdk from 'gi://Gdk?version=4.0'; import Gio from 'gi://Gio'; import GjsTestTools from 'gi://GjsTestTools'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=4.0'; import System from 'system'; const PromiseInternal = imports._promiseNative; // 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/gjs/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(); }); }); } class LeakTestWidget extends Gtk.Button { buttonClicked() {} } GObject.registerClass({ Template: new TextEncoder().encode(` `), }, LeakTestWidget); describe('Gtk 4', function () { // This test file times out on CI under Valgrind if (GLib.getenv('CI_JOB_NAME') === 'valgrind') return; let writerFunc; beforeAll(function () { Gtk.init(); // Set up log writer for tests to override writerFunc = jasmine.createSpy('log writer', () => GLib.LogWriterOutput.UNHANDLED); writerFunc.and.callThrough(); GLib.log_set_writer_func(writerFunc); }); afterAll(function () { GLib.log_set_writer_default(); templateFile.delete(null); }); describe('overrides', function () { 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('Gtk.Builder typename', function () { const builder = new Gtk.Builder(); expect(builder.toString()).toContain('object instance wrapper GType:GtkJSBuilder'); }); describe('Non-template UI file', function () { let callbacks; beforeEach(function () { callbacks = { onButtonClicked() {}, onButtonClickedBound() {}, }; spyOn(callbacks, 'onButtonClicked'); spyOn(callbacks, 'onButtonClickedBound'); }); it('from resource', function () { const builder = new Gtk.Builder({ resource: '/org/gjs/jsunit/builder-nontemplate.ui', callbacks, }); const win = builder.get_object('win'); const button = builder.get_object('button'); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(callbacks); expect(callbacks.onButtonClickedBound).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClickedBound.calls.mostRecent().object).toBe(win); }); it('from filename', function () { const [ntFile, ntStream] = Gio.File.new_tmp(null); const output = ntStream.get_output_stream(); const input = Gio.resources_open_stream('/org/gjs/jsunit/builder-nontemplate.ui', Gio.ResourceLookupFlags.NONE); output.splice(input, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET, null); const builder = new Gtk.Builder({ filename: ntFile.get_path(), callbacks, }); const win = builder.get_object('win'); const button = builder.get_object('button'); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(callbacks); expect(callbacks.onButtonClickedBound).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClickedBound.calls.mostRecent().object).toBe(win); ntFile.delete(null); }); it('from string', function () { const data = Gio.resources_lookup_data('/org/gjs/jsunit/builder-nontemplate.ui', Gio.ResourceLookupFlags.NONE); const decoder = new TextDecoder('utf-8'); const string = decoder.decode(data); const builder = new Gtk.Builder({ data: string, callbacks, }); const win = builder.get_object('win'); const button = builder.get_object('button'); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(callbacks); expect(callbacks.onButtonClickedBound).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClickedBound.calls.mostRecent().object).toBe(win); }); it('from bytes', function () { const data = Gio.resources_lookup_data('/org/gjs/jsunit/builder-nontemplate.ui', Gio.ResourceLookupFlags.NONE); const builder = new Gtk.Builder({ data, callbacks, }); const win = builder.get_object('win'); const button = builder.get_object('button'); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(callbacks); expect(callbacks.onButtonClickedBound).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClickedBound.calls.mostRecent().object).toBe(win); }); it('lifetime of signal connection is not tied to builder scope', function () { let ref; const button = (function () { const builder = new Gtk.Builder({ resource: '/org/gjs/jsunit/builder-nontemplate.ui', callbacks, }); ref = new WeakRef(builder); return builder.get_object('button'); })(); expect(button).toEqual(jasmine.any(Gtk.Button)); PromiseInternal.drainMicrotaskQueue(); // let WeakRef lapse System.gc(); expect(ref.deref()).not.toBeDefined(); // The builder scope is now orphaned. If it was the signal // connection's associated GObject, the signal will be // disconnected in the next GC System.gc(); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(callbacks); }); it('lifetime of signal connection is tied to builder current object', function () { // Normally you wouldn't use currentObject like this - it'd be // a widget that contains the widgets you're building, not a // separate object that is discarded. let builder, ref; (function () { const currentObject = new GObject.Object(); builder = new Gtk.Builder({ resource: '/org/gjs/jsunit/builder-nontemplate.ui', callbacks, currentObject, }); ref = new WeakRef(currentObject); builder.currentObject = null; })(); const button = builder.get_object('button'); expect(button).toEqual(jasmine.any(Gtk.Button)); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalledOnceWith(button); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(ref.deref()); callbacks.onButtonClicked.calls.reset(); PromiseInternal.drainMicrotaskQueue(); // let WeakRef lapse System.gc(); expect(ref.deref()).not.toBeDefined(); button.emit('clicked'); expect(callbacks.onButtonClicked).not.toHaveBeenCalled(); }); it('signal connection is not leaked after widget goes away', function () { let ref; (function () { const disappearingCallbacks = { onButtonClicked() {}, onButtonClickedBound() {}, }; const builder = new Gtk.Builder({ resource: '/org/gjs/jsunit/builder-nontemplate.ui', callbacks: disappearingCallbacks, }); const win = builder.get_object('win'); win.destroy(); ref = new WeakRef(disappearingCallbacks); })(); expect(ref.deref()).toBeDefined(); PromiseInternal.drainMicrotaskQueue(); // let WeakRef lapse System.gc(); // widgets go away System.gc(); // GClosure goes away System.gc(); // callbacks object and handler can be collected expect(ref.deref()).not.toBeDefined(); }); }); describe('UI file with external objects', function () { const builderXML = ` label `; let callbacks, label, obj; beforeEach(function () { obj = new GObject.Object(); label = new Gtk.Label({label: 'Text'}); callbacks = { onButtonClicked() {}, }; spyOn(callbacks, 'onButtonClicked'); }); it('fails if external objects not provided', function () { expect(() => new Gtk.Builder({data: builderXML})).toThrow(); }); it('with objects provided at construct time', function () { const builder = new Gtk.Builder({ data: builderXML, callbacks, objects: {label, obj}, }); const button = builder.get_object('button'); expect(button.child).toBe(label); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalled(); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(obj); }); it('with objects provided using expose_object', function () { const builder = new Gtk.Builder({callbacks}); builder.expose_object('label', label); builder.expose_object('obj', obj); builder.add_from_string(builderXML, -1); const button = builder.get_object('button'); expect(button.child).toBe(label); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalled(); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(obj); }); it('with objects provided using exposeObjects', function () { const builder = new Gtk.Builder({callbacks}); builder.exposeObjects({label, obj}); builder.add_from_string(builderXML, -1); const button = builder.get_object('button'); expect(button.child).toBe(label); button.emit('clicked'); expect(callbacks.onButtonClicked).toHaveBeenCalled(); expect(callbacks.onButtonClicked.calls.mostRecent().object).toBe(obj); }); it('lets object be retrieved using get_object', function () { const builder = new Gtk.Builder({ data: builderXML, callbacks, objects: {label, obj}, }); expect(builder.get_object('label')).toBe(label); expect(builder.get_object('obj')).toBe(obj); }); it('lets object be retrieved using get_objects', function () { const builder = new Gtk.Builder({ data: builderXML, callbacks, objects: {label, obj}, }); const objects = builder.get_objects(); expect(objects).toEqual(jasmine.arrayContaining([label, obj])); expect(objects.label).toBe(label); expect(objects.obj).toBe(obj); }); }); describe('Gtk.Builder.get_objects override', function () { const objectsXML = ` `; let objects, window, box, label, zero, ten; beforeEach(function () { const builder = new Gtk.Builder({data: objectsXML}); window = builder.get_object('window'); box = builder.get_object('box'); label = builder.get_object('label'); zero = builder.get_object('0'); ten = builder.get_object('10'); objects = builder.get_objects(); }); it('works as a drop-in replacement for the original get_objects', function () { expect(objects).toEqual(jasmine.arrayContaining([window, box, label, zero, ten])); }); it('allows fetching objects by name', function () { expect(objects.window).toBe(window); expect(objects.box).toBe(box); expect(objects.label).toBe(label); }); it('does not override array index properties', function () { expect(objects[0]).not.toBe(zero); // OK because the array is not that long expect(objects[10]).toBe(ten); }); it('does not duplicate calls to get_object', function () { spyOn(Gtk.Builder.prototype, 'get_object').and.callThrough(); expect(objects.window).toBe(window); expect(objects.window).toBe(window); expect(Gtk.Builder.prototype.get_object).toHaveBeenCalledTimes(1); }); it('builder instance is kept alive by the proxy', function () { System.gc(); expect(objects.window).toBe(window); }); it('builder instance is not kept alive if no proxy is returned', function () { let ref; const retrievedWindow = (function () { const builder = new Gtk.Builder({data: objectsXML}); ref = new WeakRef(builder); return builder.get_object('window'); })(); expect(retrievedWindow).toEqual(jasmine.any(Gtk.Window)); PromiseInternal.drainMicrotaskQueue(); // let WeakRef lapse System.gc(); expect(ref.deref()).not.toBeDefined(); }); }); 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); const unused = 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); const unused = 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('regressions', function () { it('Gdk.Event fundamental type should not crash', function () { expect(() => new Gdk.Event()).toThrowError(/Couldn't find a constructor/); }); it('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); }); 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) GjsTestTools.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 = GjsTestTools.steal_saved(); expect(surface.is_destroyed()).toBeFalsy(); }); it('private type implementing two interfaces is introspected correctly', function () { const pages = new Gtk.Notebook().pages; // implements Gio.ListModel and Gtk.SelectionModel expect(pages.get_n_items()).toBe(0); expect(pages.get_selection().get_size()).toBe(0); }); it('callback with scope-notify transfer-full in parameter', function () { // https://gitlab.gnome.org/GNOME/gjs/-/issues/691 const model = new Gio.ListStore({itemType: Gtk.Label}); model.append(new Gtk.Label({label: 'test'})); const mapModel = new Gtk.MapListModel({model}); mapModel.set_map_func(item => Gtk.StringObject.new(item.label)); mapModel.get_item(0); }); }); describe('template signal', function () { 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-140.0/installed-tests/js/testImporter.js0000664000175000017500000002354215167114161020024 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/gjs/jsunit/modules/badOverrides. let oldSearchPath; beforeAll(function () { oldSearchPath = imports.overrides.searchPath.slice(); imports.overrides.searchPath = ['resource:///org/gjs/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/gjs/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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, "Some code accessed the property 'b' on the module " + "'lexicalScope'.*"); GLib.test_expect_message('Gjs', 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('Gjs', '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-140.0/installed-tests/js/testImporter2.js0000664000175000017500000000322615167114161020103 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/gjs/jsunit/modules/badOverrides2. let oldSearchPath; beforeAll(function () { oldSearchPath = imports.overrides.searchPath.slice(); imports.overrides.searchPath = ['resource:///org/gjs/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-140.0/installed-tests/js/testIntrospection.js0000664000175000017500000002640515167114161021064 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 import Gdk from 'gi://Gdk?version=3.0'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=3.0'; import System from 'system'; let GioUnix; try { GioUnix = (await import('gi://GioUnix')).default; } catch {} 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); void GLib.MININT64; void GLib.MAXINT64; void GLib.MAXUINT64; GLib.test_assert_expected_messages_internal('Gjs', '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('Gjs', 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(); settings.disconnect(handler); GLib.idle_add(GLib.PRIORITY_LOW, () => { GLib.test_assert_expected_messages_internal('Gjs', '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 of a struct', 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)); }); it('enumerates all properties of a union', function () { if (GLib.getenv('ENABLE_GTK') !== 'yes') { pending('GTK disabled'); return; } Gtk.init(null); const event = new Gdk.Event(Gdk.EventType.KEY_PRESS); const names = Object.getOwnPropertyNames(Object.getPrototypeOf(event)); const expectAtLeast = ['_get_angle', '_get_center', '_get_distance', 'any', 'button', 'configure', 'copy', 'crossing', 'dnd', 'expose', 'focus_change', 'free', 'get_axis', 'get_button', 'get_click_count', 'get_coords', 'get_device_tool', 'get_device', 'get_event_sequence', 'get_event_type', 'get_keycode', 'get_keyval', 'get_pointer_emulated', 'get_root_coords', 'get_scancode', 'get_screen', 'get_scroll_deltas', 'get_scroll_direction', 'get_seat', 'get_source_device', 'get_state', 'get_time', 'get_window', 'grab_broken', 'is_scroll_stop_event', 'key', 'motion', 'owner_change', 'pad_axis', 'pad_button', 'pad_group_mode', 'property', 'proximity', 'put', 'scroll', 'selection', 'set_device_tool', 'set_device', 'set_screen', 'set_source_device', 'setting', 'touch', 'touchpad_pinch', 'touchpad_swipe', 'triggers_context_menu', 'type', 'visibility', 'window_state']; 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 () { it('GioUnix objects are looked up in GioUnix, not Gio', function () { if (!GioUnix) { pending('GioUnix required for this test'); return; } GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*Gio.UnixMountMonitor*'); const monitor = Gio.UnixMountMonitor.get(); expect(monitor.toString()).toContain('GIName:GioUnix.MountMonitor'); GLib.test_assert_expected_messages_internal('Gjs', 'testIntrospection.js', 0, 'Expected deprecation message for Gio.Unix -> GioUnix'); }); it('GioUnix functions are looked up in GioUnix, not Gio', function () { if (!GioUnix) { pending('GioUnix required for this test'); return; } GLib.test_expect_message('Gjs', 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('Gjs', '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 (!GioUnix) { 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-140.0/installed-tests/js/testLang.js0000664000175000017500000000577415167114161017113 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-140.0/installed-tests/js/testLegacyByteArray.js0000664000175000017500000001707615167114161021257 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, GjsTestTools, 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 = GjsTestTools.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 = GjsTestTools.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('Gjs', 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('Gjs', '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-140.0/installed-tests/js/testLegacyCairo.js0000664000175000017500000000156615167114161020407 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Philip Chimento const Cairo = imports.cairo; const giCairo = imports.gi.cairo; describe('Cairo imported from legacy importer', 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 legacy GI importer', 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-140.0/installed-tests/js/testLegacyClass.js0000664000175000017500000005554015167114161020420 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 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-140.0/installed-tests/js/testLegacyGObject.js0000664000175000017500000007332615167114161020672 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('Gjs', '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('Gjs', '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-140.0/installed-tests/js/testLegacyGtk.js0000664000175000017500000001156115167114161020073 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/gjs/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-140.0/installed-tests/js/testMainloop.js0000664000175000017500000000633315167114161020000 0ustar fabiofabio// 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-140.0/installed-tests/js/testNamespace.js0000664000175000017500000000044415167114161020113 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2012 Red Hat, Inc. import Regress from 'gi://Regress'; describe('GI repository namespace', function () { it('supplies a name', function () { expect(Regress.__name__).toEqual('Regress'); }); }); cjs-140.0/installed-tests/js/testOverrides.js0000664000175000017500000000316315167114161020162 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2019 Philip Chimento // Load overrides for GIMarshallingTests. This is only possible using the legacy // importer imports.overrides.searchPath.unshift('resource:///org/gjs/jsunit/modules/overrides'); const GIMarshallingTests = imports.gi.GIMarshallingTests; 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); }); it('returns the overridden object from a C constructor', function () { const obj = GIMarshallingTests.OverridesObject.new(); expect(obj).toBeInstanceOf(GIMarshallingTests.OverridesObject); }); }); cjs-140.0/installed-tests/js/testPackage.js0000664000175000017500000000605115167114161017552 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-140.0/installed-tests/js/testParamSpec.js0000664000175000017500000000407315167114161020074 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Red Hat, Inc. import GObject from 'gi://GObject'; import Regress from 'gi://Regress'; 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-140.0/installed-tests/js/testPrint.js0000664000175000017500000001675715167114161017331 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Philip Chimento // SPDX-FileCopyrightText: 2022 Nasah Kuma import Gdk from 'gi://Gdk?version=3.0'; import GLib from 'gi://GLib'; 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] }'); }); it('null prototype object', function () { const obj = Object.create(null); obj.test = 1; expect(prettyPrint(obj)).toEqual('[Object: null prototype] { test: 1 }'); }); it('null prototype object with custom toString', function () { const obj = Object.create(null); obj.toString = () => 'Maple Syrup'; expect(prettyPrint(obj)).toEqual('Maple Syrup'); }); it('object with nullish toString', function () { expect(prettyPrint({toString: null})).toEqual('{ toString: null }'); }); 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] = GLib.locale_from_utf8('â…œ', -1); expect(prettyPrint(a)).toEqual('[226, 133, 156]'); }); }); cjs-140.0/installed-tests/js/testPromise.js0000664000175000017500000000617115167114161017640 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. import GLib from 'gi://GLib'; 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('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, 'Unhandled promise rejection.*'); loop.run(); GLib.test_assert_expected_messages_internal('Gjs', '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-140.0/installed-tests/js/testRegress.js0000664000175000017500000031655215167114161017643 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2008 Red Hat, Inc. // SPDX-FileCopyrightText: 2024 Philip Chimento // We use Gio to have some objects that we know exist import Regress from 'gi://Regress'; import Utility from 'gi://Utility'; import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; let RegressUnix; try { RegressUnix = (await import('gi://RegressUnix')).default; } catch {} function expectWarn64(callable) { GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING, '*cannot be safely stored*'); const ret = callable(); GLib.test_assert_expected_messages_internal('Gjs', '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 off_t', function () { expect(Regress.test_offt(0x7fff_ffff)).toBe(0x7fff_ffff); }); 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(['1', '2'])).toBeFalsy(); expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy(); expect(Regress.test_strv_in(['1', '5', '6'])).toBeFalsy(); expect(Regress.test_strv_in(['1', '2', '6'])).toBeFalsy(); expect(Regress.test_strv_in(['4', '5', null])).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); }); it('flag returned without private values below smallest flag value', function () { expect(Regress.TestDiscontinuousFlags.DISCONTINUOUS1).toEqual(512); expect(Regress.test_discontinuous_1_with_private_values()).toEqual(Regress.TestDiscontinuousFlags.DISCONTINUOUS1); }); it('flag returned without private values above highest flag value', function () { expect(Regress.TestDiscontinuousFlags.DISCONTINUOUS2).toEqual(536870912); expect(Regress.test_discontinuous_2_with_private_values()).toEqual(Regress.TestDiscontinuousFlags.DISCONTINUOUS2); }); 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; // Not possible, type tag is void: // 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/714'); }); // 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(); }); }); it('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(); }); }); 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'); t.set_string('string3'); expect(t.string).toBe('string3'); expect(t.get_string()).toBe('string3'); }); 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/715'); }); 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); }); it('DOES NOT skip over return value annotated with skip', function () { // This test will need to change as part of // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an // API break, so we have a regression test for the current // behaviour. const [bool, b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5); expect(bool).toBeTrue(); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); const retval = o.skip_return_val_no_out(1); expect(retval).toBeTrue(); expect(() => o.skip_return_val_no_out(0)).toThrow(); }); it('DOES NOT skip over parameters annotated with skip', function () { // This test will need to change as part of // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an // API break, so we have a regression test for the current // behaviour. expect(o.skip_param.length).toEqual(5); const [success, b, d, sum] = o.skip_param(1, 1.5, 2, 3, 4); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(3); expect(sum).toEqual(43); }); it('DOES NOT skip over out parameters annotated with skip', function () { // This test will need to change as part of // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an // API break, so we have a regression test for the current // behaviour. const [success, b, d, sum] = o.skip_out_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); }); it('skips over inout parameters annotated with skip', function () { // This test will need to change as part of // https://gitlab.gnome.org/GNOME/gjs/issues/59. Note this is an // API break, so we have a regression test for the current // behaviour. expect(o.skip_inout_param.length).toEqual(5); const [success, b, d, sum] = o.skip_inout_param(1, 2, 3, 4, 5); expect(success).toBeTruthy(); expect(b).toEqual(2); expect(d).toEqual(4); expect(sum).toEqual(54); }); 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('constructs an instance of a fundamental type with no get/set function', function () { const o = new Regress.TestFundamentalObjectNoGetSetFunc('plop'); expect(o.get_data()).toBe('plop'); }); }); 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(); callback.calls.reset(); o.instance_method_callback(null); expect(callback).not.toHaveBeenCalled(); }); it('async instance methods', function (done) { const o = new Regress.TestObj(); const prio = GLib.PRIORITY_DEFAULT; const cancel = new Gio.Cancellable(); expect(o.function_sync(prio)).toBeTrue(); o.function_async(prio, cancel, (obj, res) => { expect(obj).toBe(o); expect(o.function_finish(res)).toBeTrue(); done(); }); expect(o.function_thaw_async()).toBe(1); }); it('async instance method with extra callback', function (done) { const o = new Regress.TestObj(); const prio = GLib.PRIORITY_DEFAULT; const cancel = new Gio.Cancellable(); expect(o.function2_sync(prio)).toBeTrue(); const testCallback = jasmine.createSpy('testCallback'); o.function2(prio, cancel, testCallback, (obj, res) => { expect(obj).toBe(o); expect(o.function2_finish(res)).toEqual([true, true, null]); expect(testCallback).toHaveBeenCalledTimes(1); done(); }); expect(o.function_thaw_async()).toBe(1); }); it('static method taking a callback', function () { const callback = jasmine.createSpy('callback'); Regress.TestObj.static_method_callback(callback); expect(callback).toHaveBeenCalled(); callback.calls.reset(); Regress.TestObj.static_method_callback(null); expect(callback).not.toHaveBeenCalled(); }); it('async static methods', function (done) { const prio = GLib.PRIORITY_DEFAULT; const cancel = new Gio.Cancellable(); expect(Regress.test_function_sync(prio)).toBeTrue(); Regress.test_function_async(prio, cancel, (obj, res) => { expect(obj).toBeNull(); expect(Regress.test_function_finish(res)).toBeTrue(); done(); }); expect(Regress.test_function_thaw_async()).toBe(1); }); 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('async constructor', function (done) { const cancel = new Gio.Cancellable(); Regress.TestObj.new_async('plop', cancel, (obj, res) => { expect(obj).toBeNull(); expect(Regress.TestObj.new_finish(res)).toBeInstanceOf(Regress.TestObj); done(); }); expect(Regress.TestObj.constructor_thaw_async()).toBe(1); }); 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.not_introspectable_via_alias).not.toBeDefined(); 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(); }); }); }); // Inline functions are not introspectable because there is no symbol to load // from the library describe('Inline', function () { it('function', function () { expect(Regress.test_inline_function).not.toBeDefined(); }); it('method', function () { const o = new Regress.TestObj(); expect(o.inline_method).not.toBeDefined(); }); }); describe('Annotations object', function () { let o; beforeEach(function () { o = new Regress.AnnotationObject(); }); it('handles a deprecated string property', function () { expect(o.stringProperty).toBeNull(); o.stringProperty = 'foo'; }); xit('handles a callback property', function () { expect(o.functionProperty).toBeNull(); o.functionProperty = null; o.functionProperty = array => array.map(x => x + 1); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/83'); it('handles a property that was annotated weirdly', function () { expect(o.tabProperty).toBeNull(); o.tabProperty = '\t'; }); xit('handles a signal which takes a pointer argument but annotated as a string', function () { o.connect('string-signal', function (self, str) { expect(self).toBe(o); expect(str).toBe('foo'); }); o.emit('string-signal', 'foo'); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/57'); xit('handles a signal which takes a list of strings', function () { o.connect('list-signal', function (self, list) { expect(self).toBe(o); expect(list).toBe(['foo', 'bar']); }); o.emit('list-signal', ['foo', 'bar']); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/57'); it('handles a signal with undocumented argument', function () { o.connect('doc-empty-arg-parsing', function (self, arg) { expect(self).toBe(o); expect(arg).toBeNull(); }); o.emit('doc-empty-arg-parsing', null); }); it('handles a signal with arbitrary ignored attributes', function () { o.connect('attribute-signal', function (self, s1, s2) { expect(self).toBe(o); expect(s1).toBe('foo'); expect(s2).toBe('bar'); return s1 + s2; }); expect(o.emit('attribute-signal', 'foo', 'bar')).toBe('foobar'); }); xit('handles an in-argument passed by pointer', function () { expect(o.in(2)).toBe(2); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/652'); xit('handles an optional in-out argument', function () { expect(o.inout3()).toEqual([1, null]); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/53'); it('handles various in-out argument configurations also tested elsewhere', function () { expect(o.method()).toBe(1); expect(o.out()).toEqual([1, 2]); expect(o.inout(2)).toEqual([3, 3]); expect(o.inout2(2)).toEqual([3, 3]); // not sure why this exists expect(o.inout3(1)).toEqual([2, 1]); expect(o.calleeowns()).toEqual([1, null]); expect(o.calleesowns()).toEqual([1, null, null]); expect(o.get_strings()).toEqual(['bar', 'regress_annotation']); expect(o.get_hash()).toEqual({ one: o, two: o, }); o.with_voidp(null); expect(o.get_objects()).toEqual([o]); expect(o.create_object()).toBe(o); o.use_buffer(new Uint8Array(16)); o.compute_sum([1, 2, 3]); o.compute_sum_n([1, 2, 3]); o.compute_sum_nz([1, 2, 3]); o.parse_args(['--num', '5', '--no-florp']); expect(o.string_out()).toEqual([false, null]); o.foreach(() => {}); expect(o.set_data(Uint8Array.from([104, 105, 106, 107]))); expect(o.set_data2([104, 105, 106, 107])); expect(o.set_data3(Uint8Array.from([104, 105, 106, 107]))); expect(o.allow_none('foo')).toBeNull(); expect(o.notrans()).toBeNull(); expect(o.do_not_use()).toBeNull(); o.watch(() => {}); o.hidden_self(); Regress.annotation_init(['--num', '5', '--no-florp']); expect(Regress.annotation_return_array()).toEqual([]); expect(Regress.annotation_string_zero_terminated()).toBeNull(); expect(Regress.annotation_string_zero_terminated_out(['in', 'out'])).toEqual(['in', 'out']); Regress.annotation_versioned(); Regress.annotation_string_array_length(['foo', 'bar']); o.extra_annos(); Regress.annotation_custom_destroy(() => {}); Regress.annotation_custom_destroy_cleanup(); expect(Regress.annotation_get_source_file()).toBeNull(); Regress.annotation_set_source_file('résumé.txt'); expect(Regress.annotation_attribute_func(o, 'foo')).toBe(42); Regress.annotation_invalid_regress_annotation(42); expect(Regress.annotation_test_parsing_bug630862()).toBeNull(); Regress.annotation_space_after_comment_bug631690(); expect(Regress.annotation_return_filename()).toBe('a utf-8 filename'); expect(Regress.annotation_transfer_floating(o)).toBeNull(); }); xit('handles a GPtrArray of GValue', function () { Regress.annotation_ptr_array([]); Regress.annotation_ptr_array([1, 2]); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/272 (maybe)'); xit('handles a struct with a fixed length array field', function () { const s = new Regress.AnnotationStruct({ objects: Array(10).fill(o), }); expect(s.objects).toEqual([o, o, o, o, o, o, o, o, o, o]); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/525'); it('handles various header-only stuff', function () { const f = new Regress.AnnotationFields({ field1: 42, field4: 43, }); // writing the array field is not supported but should fail gracefully expect(() => (f.arr = Uint8Array.from([104, 105, 106, 107]))).toThrow(); expect(Regress.ANNOTATION_CALCULATED_DEFINE).toBe(100); expect(Regress.ANNOTATION_CALCULATED_LARGE_DIV).toBe(1000000); }); xit('handles an integer constant larger than 32 bits', function () { expect(Regress.ANNOTATION_CALCULATED_LARGE).toBe(10000000000); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/526'); }); describe('Abstract drawable object', function () { class MyDrawable extends Regress.TestInheritDrawable {} beforeAll(function () { GObject.registerClass(MyDrawable); }); let o; beforeEach(function () { o = new MyDrawable(); }); it('calls methods', function () { void o.do_foo(42); expect(o.get_origin()).toEqual([0, 0]); expect(o.get_size()).toEqual([42, 42]); void o.do_foo_maybe_throw(42); expect(() => o.do_foo_maybe_throw(43)).toThrow(); }); }); describe('Regress.Foo', function () { it('various stuff', function () { expect(Regress.foo_init()).toBe(0x1138); expect(Regress.foo_init_argv([])).toBe(0x1138); expect(Regress.foo_init_argv_address(['--num', '5', '--no-florp'])) .toEqual([0x1138, ['--num', '5', '--no-florp']]); expect(Regress.foo_not_a_constructor_new()).toBeNull(); const o = new Utility.Object(); const s = new Utility.Struct({ field: 42, bitfield1: 7, bitfield2: 3, }); void Regress.foo_method_external_references(o, Utility.EnumType.C, Utility.FlagType.B, s); void Regress.FooObject.a_global_method(o); expect(Regress.foo_private_function).not.toBeDefined(); }); it('Interface', function () { class Impl extends GObject.Object { static [GObject.interfaces] = [Regress.FooInterface]; static { GObject.registerClass(Impl); } do_regress_foo_called = false; vfunc_do_regress_foo(foo) { expect(foo).toBe(777); this.do_regress_foo_called = true; } } Impl.static_method(77); const o = new Impl(); o.do_regress_foo(777); expect(o.do_regress_foo_called).toBeTrue(); }); it('SubInterface', function () { class SubImpl extends GObject.Object { static [GObject.interfaces] = [Regress.FooInterface, Regress.FooSubInterface]; static { GObject.registerClass(SubImpl); } do_regress_foo_called = false; vfunc_do_regress_foo(foo) { expect(foo).toBe(777); this.do_regress_foo_called = true; } do_bar_called = false; vfunc_do_bar() { this.do_bar_called = true; } } SubImpl.static_method(77); const o = new SubImpl(); o.do_regress_foo(777); expect(o.do_regress_foo_called).toBeTrue(); o.do_bar(); expect(o.do_bar_called).toBeTrue(); const spy = jasmine.createSpy('callback'); o.connect('destroy-event', spy); o.emit('destroy-event'); expect(spy).toHaveBeenCalledOnceWith(o); }); xit('interface with vfunc that takes a callback', function () { class BazImpl extends GObject.Object { static [GObject.interfaces] = [Regress.FooInterface, Regress.FooSubInterface]; static { GObject.registerClass(BazImpl); } do_baz_called = false; vfunc_do_baz(callback) { callback(777); this.do_baz_called = true; } } const o = new BazImpl(); const spy = jasmine.createSpy('callback'); o.do_baz(spy); expect(spy).toHaveBeenCalledOnceWith(777); expect(o.do_baz_called).toBeTrue(); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/72'); it('Object static', function () { expect(Regress.FooObject.new()).toBeInstanceOf(Regress.FooObject); expect(Regress.FooObject.new_as_super()).toBeInstanceOf(Regress.FooObject); expect(Regress.FooObject.static_meth()).toBe(77); expect(Regress.FooObject.get_default()).toBeNull(); }); function testRegressFooObjectMethods(o) { expect(o.external_type()).toBeNull(); o.various(null, Regress.AnnotationObject); o.is_it_time_yet(new Date()); o.seek(0x7fff_ffff_ffff_ffffn); expect(o.get_name()).toBe('regress_foo'); expect(o.dup_name()).toBe('regress_foo'); o.handle_glyph(0x2212); expect(o.skipped_method).toBeUndefined(); expect(o.append_new_stack_layer(5)).toBeNull(); o.do_regress_foo(777); } it('Object instance', function () { const o = new Regress.FooObject(); testRegressFooObjectMethods(o); expect(o.virtual_method(77)).toBeFalse(); o.read(0xff, 10); }); it('Object properties', function () { const o = new Regress.FooObject({ string: 'string', }); expect(o.string).toBeNull(); expect(o.hidden).toBeUndefined(); }); it('Subobject instance', function () { class MySubobject extends Regress.FooSubobject { vfunc_virtual_method(first_param) { expect(first_param).toBe(77); return true; } read_fn_called = false; vfunc_read_fn() { this.read_fn_called = true; } } GObject.registerClass(MySubobject); const o = new MySubobject(); testRegressFooObjectMethods(o); expect(o.virtual_method(77)).toBeTrue(); o.read(0xff, 10); expect(o.read_fn_called).toBeFalse(); // C method does not call vfunc }); it('Buffer instance', function () { const o = new Regress.FooBuffer(); o.some_method(); }); it('OtherObject instance', function () { void new Regress.FooOtherObject(); }); it('EnumType', function () { expect(Regress.foo_enum_method(Regress.FooEnumType.BETA)).toBe(0); expect(Regress.FooEnumType.method(Regress.FooEnumType.ALPHA)).toBe(1); expect(Regress.foo_enum_type_method(Regress.FooEnumType.ALPHA)).toBe(1); expect(Regress.FooEnumType.returnv(1)).toBe(Regress.FooEnumType.DELTA); expect(Regress.foo_enum_type_returnv(1)).toBe(Regress.FooEnumType.DELTA); }); it('FlagsType', function () { expect(Regress.FooFlagsType.FIRST).toBe(1); expect(Regress.FooFlagsType.SECOND).toBe(2); expect(Regress.FooFlagsType.THIRD).toBe(4); expect(Regress.FOO_FLAGS_SECOND_AND_THIRD).toBe(6); }); it('EnumNoType', function () { expect(Regress.FooEnumNoType.UN).toBe(1); expect(Regress.FooEnumNoType.DEUX).toBe(2); expect(Regress.FooEnumNoType.TROIS).toBe(3); expect(Regress.FooEnumNoType.NEUF).toBe(9); }); it('FlagsNoType', function () { expect(Regress.FooFlagsNoType.ETT).toBe(1); expect(Regress.FooFlagsNoType.TVA).toBe(2); expect(Regress.FooFlagsNoType.FYRA).toBe(4); }); it('EnumFullname', function () { expect(Regress.FooEnumFullname.ONE).toBe(1); expect(Regress.FooEnumFullname.TWO).toBe(2); expect(Regress.FooEnumFullname.THREE).toBe(3); }); it('Address', function () { expect(Regress.FooAddressType.INVALID).toBe(0); expect(Regress.FooAddressType.IPV4).toBe(1); expect(Regress.FooAddressType.IPV6).toBe(2); }); it('Various boxed instances', function () { const o1 = new Regress.FooBoxed(); o1.method(); const o2 = new Regress.FooBRect({x: 1.5, y: -2.5}); expect(o2.x).toBe(1.5); expect(o2.y).toBe(-2.5); const o3 = Regress.FooBRect.new(-1.4, 2.6); expect(o3.x).toBe(-1.4); expect(o3.y).toBe(2.6); o2.add(o3); o3.add(o2); }); xit('Primitive type union fields', function () { const u = new Regress.FooBUnion(); expect(u.type).toBeDefined(); expect(u.v).toBeDefined(); u.type = 77; expect(u.type).toBe(77); u.v = 7.777; expect(u.v).toBe(7.777); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/770'); xit('Union field that is a pointer to a boxed type', function () { const s = Regress.FooBRect.new(-1.4, 2.6); const u = new Regress.FooBUnion(); expect(u.rect).toBeDefined(); u.rect = s; expect(u.rect).toBe(s); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/770'); it('Rectangle instance', function () { const o1 = new Regress.FooRectangle({x: 0, y: 0, width: 10, height: 10}); const o2 = new Regress.FooRectangle({x: 1, y: 1, width: 12, height: 12}); o1.add(o2); o2.add(o1); }); it('Various test functions', function () { Regress.foo_test_unsigned(0xffff_ffff); Regress.foo_test_string_array([]); Regress.foo_test_string_array_with_g([]); expect(Regress.foo_test_array()).toBeNull(); }); it('Error type', function () { expect(() => { throw new Regress.FooError({message: 'foo', code: Regress.FooError.BAD}); }).toThrow(jasmine.objectContaining({ message: 'foo', code: Regress.FooError.BAD, })); }); it('Foreign struct', function () { const s1 = new Regress.FooForeignStruct({regress_foo: 777}); expect(s1.regress_foo).toBe(777); }); xit('Foreign struct via introspected constructor', function () { const s2 = Regress.FooForeignStruct.new(); expect(s2.regress_foo).toBe(0); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/656'); }); describe('RegressUnix', function () { if (!RegressUnix) return; ['devt', 'gidt', 'pidt', 'socklent', 'uidt'].forEach(name => { it(`handles ${name}`, function () { expect(RegressUnix[`test_${name}`](12345)).toBe(12345); }); }); }); // Adapted from pygobject describe('Boxed type return extra tests', function () { it('Refcounted boxed type wrapper', function () { const wrapper = new Regress.TestBoxedCWrapper(); const copy = wrapper.copy(); // TestBoxedC uses refcounting, so the underlying native objects should // be the same function nativeAddress(boxed) { const match = /native@0x([0-9a-f]+)/.exec(boxed.toString()); return match[1]; } expect(nativeAddress(copy)).not.toBe(nativeAddress(wrapper)); expect(nativeAddress(copy.get())).toBe(nativeAddress(wrapper.get())); }); it('Array of boxed type transfer none out parameter', function () { expect(Regress.test_array_fixed_boxed_none_out()).toEqual([ jasmine.any(Regress.TestBoxedC), jasmine.any(Regress.TestBoxedC), ]); }); it('Boxed GValue out', function () { const int8 = Math.trunc(Math.random() * (GLib.MAXINT8 - GLib.MININT8) + GLib.MININT8); expect(Regress.test_gvalue_out_boxed(int8).some_int8).toBe(int8); }); ['none', 'full'].forEach(transfer => { it(`GList of boxed type transfer ${transfer} return value`, function () { expect(Regress[`test_glist_boxed_${transfer}_return`](2)).toEqual([ jasmine.any(Regress.TestBoxedC), jasmine.any(Regress.TestBoxedC), ]); }); }); }); // Adapted from pygobject describe('UTF-8 strings invalid bytes tests', function () { it('handles invalid UTF-8 return values gracefully', function () { expect(() => Regress.test_array_of_non_utf8_strings()).toThrowError(TypeError); }); }); // Adapted from pygobject describe('Fundamental extra tests', function () { it('array of fundamental objects in', function () { expect(Regress.test_array_of_fundamental_objects_in([ new Regress.TestFundamentalSubObject('data1'), new Regress.TestFundamentalSubObject('data2'), ])).toBeTrue(); }); it('array of fundamental objects out', function () { const objs = Regress.test_array_of_fundamental_objects_out(); expect(objs).toEqual([ jasmine.any(Regress.TestFundamentalObject), jasmine.any(Regress.TestFundamentalObject), ]); }); it('in argument', function () { const o = new Regress.TestFundamentalSubObject('data'); expect(Regress.test_fundamental_argument_in(o)).toBeTrue(); }); it('abstract type', function () { expect(() => new Regress.TestFundamentalObject()).toThrow(); }); it('out argument', function () { const o = new Regress.TestFundamentalSubObject('data'); const sameObject = Regress.test_fundamental_argument_out(o); expect(sameObject).toBe(o); }); }); // See testCairo.js for extra Cairo tests from regressextra.c // Adapted from pygobject describe('Class with action signals', function () { let o; beforeEach(function () { o = new Regress.TestAction(); }); xit('returns a new object', function () { const otherObj = o.emit('action'); expect(otherObj).toBeInstanceOf(Regress.TestAction); expect(otherObj).not.toBe(o); }).pend('https://gitlab.gnome.org/GNOME/gjs/-/issues/661'); it('returns null', function () { expect(o.emit('action2')).toBeNull(); }); }); describe('Bitmask fundamental type', function () { xit('can be created', function () { const bitmask = new Regress.Bitmask(2); console.log(bitmask); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection-tests/-/issues/7'); }); cjs-140.0/installed-tests/js/testSignals.js0000664000175000017500000001700215167114161017615 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('Gjs', 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('Gjs', 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-140.0/installed-tests/js/testSystem.js0000664000175000017500000000620515167114161017504 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. import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import System from 'system'; 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.version', function () { it('gives a plausible number', function () { expect(System.version).not.toBeLessThan(1); expect(System.version).toBeLessThan(5000000); }); }); 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-140.0/installed-tests/js/testTimers.js0000664000175000017500000002667415167114161017477 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-140.0/installed-tests/js/testTweener.js0000664000175000017500000002774315167114161017643 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-140.0/installed-tests/js/testUtility.js0000664000175000017500000000501515167114161017661 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2024 Philip Chimento import Utility from 'gi://Utility'; // A small library, part of gobject-introspection-tests, mainly used as a // dependency of other tests. describe('Utility callback', function () { it('recognizes callback correctly', function () { Utility.dir_foreach('/path/', () => {}); }); }); describe('Utility object', function () { it('recognizes callback correctly', function () { const o = new Utility.Object(); o.watch_dir('/path/', () => {}); }); }); describe('Utility unions/structs', function () { xit('TaggedValue', function () { const t = new Utility.TaggedValue(); t.tag = 0xff; expect(t.tag).toBe(0xff); expect(t.value.v_pointer).toBeNull(); t.value.v_integer = 0x7fff_ffff; expect(t.value.v_integer).toBe(0x7fff_ffff); t.value.v_double = Math.PI; expect(t.value.v_double).toBe(Math.PI); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/569'); xit('Byte', function () { const b = new Utility.Byte(); b.value = 0xcd; expect(b.parts.first_nibble).toBe(0xc); expect(b.parts.second_nibble).toBe(0xd); }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/569'); it('Buffer', function () { const b = new Utility.Buffer(); expect(b.length).toBe(0); expect(b.data).toBe(null); }); xit('Struct', function () { const s = new Utility.Struct(); s.field = 42; s.bitfield1 = 0xf; s.bitfield2 = 0x0; expect(s.field).toBe(42); expect(s.bitfield1).toBe(7); expect(s.bitfield2).toBe(0); expect(s.data).toBeInstanceOf(Uint8Array); }).pend('Bitfields not supported. Open an issue if you need this'); it('Union', function () { const u = new Utility.Union(); expect(u.pointer).toBeNull(); u.integer = 0x7fff_ffff; expect(u.integer).toBe(0x7fff_ffff); u.real = Math.PI; expect(u.real).toBe(Math.PI); }); }); describe('Utility enums/flags', function () { it('enum', function () { expect(Utility.EnumType).toEqual(jasmine.objectContaining({ A: 0, B: 1, C: 2, })); }); it('flags', function () { expect(Utility.FlagType).toEqual(jasmine.objectContaining({ A: 1, B: 2, C: 4, })); }); }); cjs-140.0/installed-tests/js/testWarnLib.js0000664000175000017500000000274515167114161017563 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 import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; import WarnLib from 'gi://WarnLib'; 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-140.0/installed-tests/js/testWeakRef.js0000664000175000017500000000473115167114161017546 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-140.0/installed-tests/js/testself.js0000664000175000017500000000360115167114161017146 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-140.0/installed-tests/meson.build0000664000175000017500000001045115167114161016506 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 = [ gjs_console, gjs_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', ] if not get_option('skip_gtk_tests') and have_gtk4 simple_tests += 'Gtk4Warnings' endif 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_command_tests = [ 'backtrace', 'breakpoint', 'continue', 'delete', 'detach', 'down-up', 'finish', 'frame', 'keys', 'lastvalues', 'list', 'next', 'print', 'quit', 'return', 'set', 'step', 'throw', '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_command_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 debugger_tests = [ 'sourcemap dynamic module', 'sourcemap separate module', 'sourcemap separate', 'sourcemap inlined', 'sourcemap inlined module', 'throw ignored', ] foreach test : debugger_tests filename = test.replace(' ', '-') test_file = files('debugger' / '@0@.debugger'.format(filename)) test(test, debugger_test_driver, args: test_file, env: tests_environment, protocol: 'tap', suite: 'Debugger', depends: tests_dependencies) test_description_subst = { 'name': '@0@.debugger'.format(filename), 'installed_tests_execdir': prefix / installed_tests_execdir, } configure_file(configuration: test_description_subst, input: 'debugger.test.in', output: '@0@.test'.format(filename), 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(filename), 'debugger' / '@0@.debugger.output'.format(filename), install_dir: installed_tests_execdir / 'debugger') endif endforeach if get_option('installed_tests') install_data('debugger' / 'sourcemap-number-module.js', install_dir: installed_tests_execdir / 'debugger') endif cjs-140.0/installed-tests/minijasmine-legacy-importer.test.in0000664000175000017500000000035615167114161023261 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-140.0/installed-tests/minijasmine.cpp0000664000175000017500000000714315167114161017357 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, ""); GIRepository* repo = gi_repository_dup_default(); if (g_getenv("GJS_USE_UNINSTALLED_FILES") != nullptr) { gi_repository_prepend_search_path(repo, g_getenv("TOP_BUILDDIR")); } else { gi_repository_prepend_search_path(repo, INSTTESTDIR); gi_repository_prepend_library_path(repo, INSTTESTDIR); } g_clear_object(&repo); 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/gjs/jsunit", nullptr}; if (coverage_prefix) gjs_coverage_enable(); GjsContext* gjs_context = gjs_context_new_with_search_path(const_cast(search_path)); GjsCoverage* coverage = nullptr; if (coverage_prefix) { const char* coverage_prefixes[2] = {coverage_prefix, nullptr}; if (!coverage_output_path) { bail_out(gjs_context, "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, gjs_context, output); g_object_unref(output); } GError* error = nullptr; bool success; uint8_t code; uint8_t u8_exitcode_ignored; if (!gjs_context_eval_module_file( gjs_context, "resource:///org/gjs/jsunit/minijasmine.js", &u8_exitcode_ignored, &error)) bail_out(gjs_context, error); bool eval_as_module = argc >= 3 && strcmp(argv[2], "-m") == 0; if (eval_as_module) { success = gjs_context_eval_module_file(gjs_context, argv[1], &u8_exitcode_ignored, &error); } else { int exitcode_ignored; success = gjs_context_eval_file(gjs_context, argv[1], &exitcode_ignored, &error); } if (!success) bail_out(gjs_context, error); success = gjs_context_eval_module_file( gjs_context, "resource:///org/gjs/jsunit/minijasmine-executor.js", &code, &error); if (!success) bail_out(gjs_context, error); if (coverage) { gjs_coverage_write_statistics(coverage); g_clear_object(&coverage); } gjs_memory_report("before destroying context", false); g_object_unref(gjs_context); 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-140.0/installed-tests/minijasmine.test.in0000664000175000017500000000036115167114161020154 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-140.0/installed-tests/script.test.in0000664000175000017500000000026215167114161017155 0ustar fabiofabio# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2013 Red Hat, Inc. [Test] Type=session Exec=sh @installed_tests_execdir@/scripts/@name@ Output=TAP cjs-140.0/installed-tests/scripts/0000775000175000017500000000000015167114161016032 5ustar fabiofabiocjs-140.0/installed-tests/scripts/common.sh0000775000175000017500000000205615167114161017664 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 # shellcheck disable=SC2034 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-140.0/installed-tests/scripts/testCommandLine.sh0000775000175000017500000003413515167114161021465 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 cat <int.js const Format = imports.format; const output = imports.format.vprintf('%Id', [60]); print(output .split('') .map(c => c.codePointAt(0).toString(16).padStart(4, '0')) .join(' ')); EOF # this script prints out all files in current directory # useful to test encodings and filename type marshalling cat <printFiles.js const {GLib, Gio} = imports.gi; const cd = Gio.File.new_for_path('.'); const iter = cd.enumerate_children("standard::name", null, null); let f; while (f = iter.next_file(null)) { f.get_name() } 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." # ensure unicode paths are supported mkdir Код touch Код/ðŸ.js $gjs -m Код/ðŸ.js report "Unicode pathed encoding should work for module run." rm -r Код # non UTF8 file names throws error bash -c "touch $'\xff'" G_FILENAME_ENCODING=utf8 $gjs -m printFiles.js 2>&1 | grep -q 'Gjs-CRITICAL.*Could not convert filename string to UTF-8 for string: \\377' report "Throws error if filename is not UTF8" bash -c "rm ''$'\377'" # 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 'Gjs-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 'Gjs-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 'Gjs-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 'Gjs-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 'Gjs-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 # https://gitlab.gnome.org/GNOME/gjs/-/issues/671 output=$(LC_ALL=C $gjs int.js) test "$output" = "0036 0030" report "%Id prints Latin digits in C locale $output" output=$(LC_ALL=en_CA $gjs int.js) test "$output" = "0036 0030" report "%Id prints Latin digits in en_CA locale $output" output=$(LC_ALL=fa_IR $gjs int.js) test "$output" = "06f6 06f0" report "%Id prints Persian digits in fa_IR locale $output" output=$(LC_ALL=ar_EG $gjs int.js) test "$output" = "0666 0660" report "%Id prints Arabic digits in ar_EG locale $output" rm -f exit.js help.js promise.js awaitcatch.js doublegi.js argv.js int.js \ signalexit.js promiseexit.js echo "1..$total" cjs-140.0/installed-tests/scripts/testCommandLineModules.sh0000775000175000017500000000423415167114161023013 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 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-140.0/installed-tests/scripts/testExamples.sh0000775000175000017500000000233115167114161021046 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2018 Claudio André DIR="$( cd "$( dirname "${0}" )" && pwd )" # shellcheck disable=SC1091 . "${DIR}"/common.sh # Run the examples # shellcheck disable=SC2154 # It's defined in common.sh $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 # shellcheck disable=SC2154 # It's defined in common.sh echo "1..$total" cjs-140.0/installed-tests/scripts/testGtk4Warnings.sh0000775000175000017500000000704015167114161021614 0ustar fabiofabio#!/bin/sh # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later # SPDX-FileCopyrightText: 2025 Gary Li 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 } skip () { total=$((total + 1)) echo "ok $total - $1 # SKIP $2" } cat <<'EOF' >gcWrapperWarning.js import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk?version=4.0'; Gtk.init(); const encoder = new TextEncoder(); const Window = GObject.registerClass({ GTypeName: 'Window', Template: encoder.encode(` `), Properties: { 'model': GObject.ParamSpec.object('model', '', '', GObject.ParamFlags.READWRITE, Gtk.StringList), }, }, class Window extends Gtk.Window { _init(props = {}) { super._init(props); this.child.factory = new Gtk.BuilderListItemFactory({bytes: new GLib.Bytes(encoder.encode(` `))}); } }); const Row = GObject.registerClass({ GTypeName: 'Row', Template: encoder.encode(` `), Properties: { 'string-object': GObject.ParamSpec.object('string-object', '', '', GObject.ParamFlags.READWRITE, Gtk.StringObject), }, }, class Row extends Gtk.Box { }); const loop = GLib.MainLoop.new(null, false); const win = new Window({model: Gtk.StringList.new(['test'])}); let weak = new WeakRef(win); win.connect('close-request', () => loop.quit()); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { weak.deref()?.close(); return false; }); win.present(); loop.run(); EOF if test "$CI_JOB_NAME" = "valgrind"; then skip "Issue 443 GObject wrapper disposed warning" "Valgrind takes too long on CI" else $gjs -m gcWrapperWarning.js 2>&1 | \ grep -q 'Wrapper for GObject.*was disposed, cannot set property string-object' report "Issue 443 GObject wrapper disposed warning" fi rm -f gcWrapperWarning.js echo "1..$total" cjs-140.0/installed-tests/scripts/testWarnings.sh0000775000175000017500000000172215167114161021063 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-140.0/js.gresource.xml0000664000175000017500000000470715167114161014367 0ustar fabiofabio modules/internal/internalLoader.js modules/internal/source-map/array-set.js modules/internal/source-map/base64-vlq.js modules/internal/source-map/base64.js modules/internal/source-map/binary-search.js modules/internal/source-map/extractUrl.js modules/internal/source-map/source-map-consumer.js modules/internal/source-map/util.js 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-140.0/libcjs.map0000664000175000017500000000025515167114161013173 0ustar fabiofabio/* SPDX-License-Identifier: MIT OR LGPL-2.0-or-later */ /* SPDX-FileCopyrightText: 2019 Philip Chimento */ { global: gjs_*; local: *; }; cjs-140.0/libcjs.symbols0000664000175000017500000000065715167114161014114 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-140.0/libgjs-private/0000775000175000017500000000000015167114161014146 5ustar fabiofabiocjs-140.0/libgjs-private/gjs-gdbus-wrapper.c0000664000175000017500000004257615167114161017673 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 #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 unsigned signals[SIGNAL_LAST]; struct _GjsDBusImplementation { GDBusInterfaceSkeleton parent; GDBusInterfaceVTable vtable; GDBusInterfaceInfo* ifaceinfo; // from char* to GVariant* GHashTable* outstanding_properties; unsigned idle_id; }; G_DEFINE_TYPE(GjsDBusImplementation, gjs_dbus_implementation, G_TYPE_DBUS_INTERFACE_SKELETON); static inline GVariant* gjs_gvariant_ref_sink0(void* value) { if (value) g_variant_ref_sink(value); return value; } static inline void gjs_gvariant_unref0(void* value) { if (value) g_variant_unref(value); } static bool gjs_dbus_implementation_check_interface(GjsDBusImplementation* self, GDBusConnection* connection, const char* object_path, const char* interface_name, GError** error) { 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; } const char* 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) != 0) { 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->ifaceinfo->name) != 0) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface %s on %s", interface_name, self->ifaceinfo->name); return false; } return true; } static bool 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->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(g_steal_pointer(&invocation), error); return; } if (!G_UNLIKELY(g_dbus_method_invocation_get_method_info(invocation)) || !g_dbus_interface_info_lookup_method(self->ifaceinfo, method_name)) { g_dbus_method_invocation_return_error( g_steal_pointer(&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, g_steal_pointer(&invocation), parameters); } 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); 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; GVariant* value; 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) { self->vtable.method_call = gjs_dbus_implementation_method_call; self->vtable.get_property = gjs_dbus_implementation_property_get; self->vtable.set_property = gjs_dbus_implementation_property_set; self->outstanding_properties = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, gjs_gvariant_unref0); } static void gjs_dbus_implementation_dispose(GObject* object) { GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(object); g_clear_handle_id(&self->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->ifaceinfo); g_hash_table_destroy(self->outstanding_properties); G_OBJECT_CLASS(gjs_dbus_implementation_parent_class)->finalize(object); } static void gjs_dbus_implementation_set_property(GObject* object, unsigned property_id, const GValue* value, GParamSpec* pspec) { GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(object); switch (property_id) { case PROP_G_INTERFACE_INFO: self->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->ifaceinfo; } static GDBusInterfaceVTable* gjs_dbus_implementation_get_vtable( GDBusInterfaceSkeleton* skeleton) { GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(skeleton); return &self->vtable; } static GVariant* gjs_dbus_implementation_get_properties( GDBusInterfaceSkeleton* skeleton) { GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(skeleton); GDBusInterfaceInfo* info = self->ifaceinfo; GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); for (GDBusPropertyInfo** 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->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; g_variant_builder_init(&changed_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init(&invalidated_props, G_VARIANT_TYPE_STRING_ARRAY); char* prop_name; GVariant* val; g_hash_table_iter_init(&iter, self->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->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->outstanding_properties); g_clear_handle_id(&self->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))); /** * GjsDBusImplementation::handle-method-call: * @self: * @method_name: * @invocation: (transfer full): * @parameters: */ 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_DBUS_METHOD_INVOCATION, G_TYPE_VARIANT /* parameters */); /** * GjsDBusImplementation::handle-property-get: * @self: * @property_name: * * Return: The property value */ 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 */); /** * GjsDBusImplementation::handle-property-set: * @self: * @property_name: * @value: */ 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(void* 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, char* property, GVariant* newvalue) { g_hash_table_replace(self->outstanding_properties, g_strdup(property), gjs_gvariant_ref_sink0(newvalue)); if (!self->idle_id) self->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, char* 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); gjs_gvariant_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->ifaceinfo->name, signal_name, parameters, NULL); g_object_unref(iter->data); } gjs_gvariant_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->outstanding_properties); g_clear_handle_id(&self->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->outstanding_properties); g_clear_handle_id(&self->idle_id, g_source_remove); } g_list_free_full(connections, g_object_unref); g_dbus_interface_skeleton_unexport_from_connection(skeleton, connection); } cjs-140.0/libgjs-private/gjs-gdbus-wrapper.h0000664000175000017500000000212115167114161017656 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 */ #pragma once #include #include #include #include "cjs/macros.h" G_BEGIN_DECLS GJS_EXPORT G_DECLARE_FINAL_TYPE(GjsDBusImplementation, gjs_dbus_implementation, GJS, DBUS_IMPLEMENTATION, GDBusInterfaceSkeleton); GJS_EXPORT void gjs_dbus_implementation_emit_property_changed(GjsDBusImplementation* self, char* property, GVariant* newvalue); GJS_EXPORT void gjs_dbus_implementation_emit_signal(GjsDBusImplementation* self, char* 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 cjs-140.0/libgjs-private/gjs-match-info.c0000664000175000017500000002506715167114161017132 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-140.0/libgjs-private/gjs-match-info.h0000664000175000017500000000607515167114161017135 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-140.0/libgjs-private/gjs-util.c0000664000175000017500000004373115167114161016060 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 errno */ #include /* for setlocale/duplocale/uselocale/newlocale/freelocale */ #include #include #include #include #include #include /* for bindtextdomain, bind_textdomain_codeset, ... */ #include "libgjs-private/gjs-util.h" #include "util/console.h" typedef struct { locale_t id; char* name; char* prior_name; } GjsLocale; #define UNSET_LOCALE_ID ((locale_t)0) static void gjs_clear_locale_id(locale_t* id) { if (id == NULL) return; if (*id == UNSET_LOCALE_ID) return; freelocale(*id); *id = UNSET_LOCALE_ID; } static locale_t gjs_steal_locale_id(locale_t* id) { locale_t stolen_id = *id; *id = UNSET_LOCALE_ID; return stolen_id; } static bool gjs_set_locale_id(locale_t* locale_id_pointer, locale_t new_locale_id) { if (*locale_id_pointer == new_locale_id) return false; if (*locale_id_pointer != UNSET_LOCALE_ID) freelocale(*locale_id_pointer); *locale_id_pointer = new_locale_id; return true; } static int gjs_locale_category_get_mask(GjsLocaleCategory category) { /* It's tempting to just return (1 << category) but the header file * says not to do that. */ switch (category) { case GJS_LOCALE_CATEGORY_ALL: return LC_ALL_MASK; case GJS_LOCALE_CATEGORY_COLLATE: return LC_COLLATE_MASK; case GJS_LOCALE_CATEGORY_CTYPE: return LC_CTYPE_MASK; case GJS_LOCALE_CATEGORY_MESSAGES: return LC_MESSAGES_MASK; case GJS_LOCALE_CATEGORY_MONETARY: return LC_MONETARY_MASK; case GJS_LOCALE_CATEGORY_NUMERIC: return LC_NUMERIC_MASK; case GJS_LOCALE_CATEGORY_TIME: return LC_TIME_MASK; default: break; } return 0; } static size_t get_number_of_locale_categories(void) { return __builtin_popcount(LC_ALL_MASK) + 1; } static void gjs_locales_free(GjsLocale** locales) { size_t number_of_categories = get_number_of_locale_categories(); for (size_t i = 0; i < number_of_categories; i++) { GjsLocale* locale = locales[i]; gjs_clear_locale_id(&locale->id); g_clear_pointer(&locale->name, g_free); g_clear_pointer(&locale->prior_name, g_free); } g_free(locales); } static GjsLocale* gjs_locales_new(void) { size_t number_of_categories = get_number_of_locale_categories(); return g_new0(GjsLocale, number_of_categories); } static GPrivate gjs_private_locale_key = G_PRIVATE_INIT((GDestroyNotify)gjs_locales_free); /** * gjs_set_thread_locale: * @category: * @locale: (allow-none): * * Returns: */ const char* gjs_set_thread_locale(GjsLocaleCategory category, const char* locale_name) { locale_t new_locale_id = UNSET_LOCALE_ID, old_locale_id = UNSET_LOCALE_ID; char* prior_name = NULL; bool success = false; GjsLocale* locales = g_private_get(&gjs_private_locale_key); if (locales == NULL) { locales = gjs_locales_new(); g_private_set(&gjs_private_locale_key, locales); } GjsLocale* locale = &locales[category]; if (locale_name == NULL) { if (locale->name != NULL) return locale->name; return setlocale(category, NULL); } old_locale_id = uselocale(UNSET_LOCALE_ID); if (old_locale_id == UNSET_LOCALE_ID) goto out; old_locale_id = duplocale(old_locale_id); if (old_locale_id == UNSET_LOCALE_ID) goto out; int category_mask = gjs_locale_category_get_mask(category); if (category_mask == 0) goto out; new_locale_id = newlocale(category_mask, locale_name, old_locale_id); if (new_locale_id == UNSET_LOCALE_ID) goto out; old_locale_id = UNSET_LOCALE_ID; /* was moved into new_locale_id */ prior_name = g_strdup(setlocale(category, NULL)); if (uselocale(new_locale_id) == UNSET_LOCALE_ID) goto out; g_set_str(&locale->prior_name, prior_name); gjs_set_locale_id(&locale->id, gjs_steal_locale_id(&new_locale_id)); g_set_str(&locale->name, setlocale(category, NULL)); success = true; out: g_clear_pointer(&prior_name, g_free); int errno_save = errno; gjs_clear_locale_id(&old_locale_id); gjs_clear_locale_id(&new_locale_id); errno = errno_save; if (!success) return NULL; return locale->prior_name; } 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); } 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); } #undef G_CLOSURE_NOTIFY static GParamSpec* gjs_gtk_container_class_find_child_property( GIObjectInfo* container_info, GObject* container, const char* property) { GIArgument ret; GIArgument find_child_property_args[2]; GIStructInfo* class_info = gi_object_info_get_class_struct(container_info); GIFunctionInfo* find_child_property_fun = gi_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; gi_function_info_invoke(find_child_property_fun, find_child_property_args, 2, NULL, 0, &ret, NULL); g_clear_pointer(&class_info, gi_base_info_unref); g_clear_pointer(&find_child_property_fun, gi_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) { GIFunctionInfo* child_set_property_fun = NULL; GValue value_arg = G_VALUE_INIT; GIArgument ret; GIArgument child_set_property_args[4]; GIRepository* repo = gi_repository_dup_default(); GIObjectInfo* container_info = GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "Container")); GParamSpec* 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 = GI_FUNCTION_INFO( gi_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; gi_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(&container_info, gi_base_info_unref); g_clear_pointer(&child_set_property_fun, gi_base_info_unref); g_clear_object(&repo); } /** * 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 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) { GIRepository* repo = gi_repository_dup_default(); GIObjectInfo* container_info = GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "CustomSorter")); GIFunctionInfo* custom_sorter_new_fun = GI_FUNCTION_INFO(gi_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; gi_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3, NULL, 0, &ret, NULL); g_clear_pointer(&container_info, gi_base_info_unref); g_clear_pointer(&custom_sorter_new_fun, gi_base_info_unref); g_clear_object(&repo); 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) { GIRepository* repo = gi_repository_dup_default(); GIObjectInfo* container_info = GI_OBJECT_INFO(gi_repository_find_by_name(repo, "Gtk", "CustomSorter")); GIFunctionInfo* set_sort_func_fun = GI_FUNCTION_INFO( gi_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; gi_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0, &unused_ret, NULL); g_clear_pointer(&container_info, gi_base_info_unref); g_clear_pointer(&set_sort_func_fun, gi_base_info_unref); g_clear_object(&repo); } static bool log_writer_cleared = false; 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 log writer function has been cleared with log_set_writer_default() // or the wrapper is called from a thread other than the one that set it, // return unhandled so the fallback logger is used. if (log_writer_cleared || 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); for (size_t 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(void) { if (log_writer_user_data_free) { log_writer_user_data_free(log_writer_user_data); } log_writer_user_data_free = NULL; log_writer_user_data = NULL; log_writer_thread = g_thread_self(); log_writer_cleared = true; } /** * 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-140.0/libgjs-private/gjs-util.h0000664000175000017500000001203015167114161016051 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 */ #pragma once #include #include #include #include #include "cjs/macros.h" G_BEGIN_DECLS /** * 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, void* user_data, GDestroyNotify user_data_free); GJS_EXPORT void gjs_log_set_writer_default(void); /* 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_set_thread_locale(GjsLocaleCategory category, const char* locale); GJS_EXPORT void gjs_textdomain(const char* domain); GJS_EXPORT void gjs_bindtextdomain(const char* domain, const char* location); /* 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); /** * 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); /* 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 cjs-140.0/meson.build0000664000175000017500000007524315167114161013401 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 : '140.0', license : ['MIT', 'LGPL2+'], meson_version : '>= 1.4', 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') gjsjsdir = 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 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.86.0' glib = dependency('glib-2.0', version: glib_required_version, required: false) gthread = dependency('gthread-2.0', version: glib_required_version, required: false) gobject = dependency('gobject-2.0', version: glib_required_version, required: false) gio = dependency('gio-2.0', version: glib_required_version, required: false) gi = dependency('girepository-2.0', version: glib_required_version, required: false) gir_deps = [] if not glib.found() glib_project = subproject('glib', required: true, default_options: ['introspection=enabled']) glib = glib_project.get_variable('libglib_dep') gthread = glib_project.get_variable('libgthread_dep') gobject = glib_project.get_variable('libgobject_dep') gio = glib_project.get_variable('libgio_dep') gi = glib_project.get_variable('libgirepository_dep') gir_deps = [ glib_project.get_variable('glib_gir'), glib_project.get_variable('gobject_gir'), glib_project.get_variable('gio_gir'), glib_project.get_variable('gio_platform_gir'), ] if host_machine.system() == 'windows' gir_deps += glib_project.get_variable('glib_win32_gir') else gir_deps += glib_project.get_variable('glib_unix_gir') endif endif ffi = dependency('libffi', fallback: ['libffi', 'ffi_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-140') 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() { 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() { 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() have_gtk3 = dependency('gtk+-3.0', required: false).found() gtk4_dep = dependency('gtk4', required: false) have_gtk4 = gtk4_dep.found() if (not have_gtk3 and not have_gtk4 and not get_option('skip_gtk_tests')) error('''You have neither GTK 3 nor 4 available. GTK is not required, but without at least one of these versions you'll be skipping a lot of tests. Configure with -Dskip_gtk_tests=true if that's intentional.''') endif ### 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() { 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 ### 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') 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/gjs_gi_probes.d') probes_objfile = probes_objfile_gen.process('gi/gjs_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 / gjsjsdir), '-DPKGLIBDIR="@0@"'.format(prefix / pkglibdir), ] gjs_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 libgjs_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/gjs_gi_trace.h', 'gi/gobject.cpp', 'gi/gobject.h', 'gi/gtype.cpp', 'gi/gtype.h', 'gi/info.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/struct.cpp', 'gi/struct.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/auto.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/gerror-result.h', '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', ] enum_files = gnome.mkenums_simple('gjs-enums', decorator: 'GJS_EXPORT', body_prefix: '#include ', header_prefix: '#include "cjs/macros.h"', sources: 'libgjs-private/gjs-util.h') # GjsPrivate introspection sources libgjs_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', ] + enum_files libgjs_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']) libgjs_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() libgjs_dependencies += cairo_xlib pkg_dependencies += cairo_xlib endif if build_readline gio_unix = dependency('gio-unix-2.0', version: glib_required_version, fallback: ['glib', 'libgiounix_dep']) libgjs_dependencies += [readline_deps, gio_unix] endif libgjs_cpp_args = ['-DGJS_COMPILATION'] + directory_defines # Check G-I and/or Meson on this one. libgjs_cpp_args += ['-DG_LOG_DOMAIN="Gjs"'] if host_machine.system() == 'windows' # We need these defines to build properly for all Windows builds libgjs_cpp_args += ['-DWIN32', '-DXP_WIN', '-DWIN32_LEAN_AND_MEAN'] endif # This dependency should provide everything that is needed to compile gjs except # the sources themselves, is used to compile both the static libraries and the # tests base_build_dep = declare_dependency( compile_args: libgjs_cpp_args, dependencies: libgjs_dependencies) internal_build_dep = declare_dependency( compile_args: (release_build ? ['-DG_DISABLE_ASSERT'] : []), dependencies: [ base_build_dep, build_profiler ? profiler_deps : [], ]) libgjs_jsapi = static_library(meson.project_name() + '-jsapi', libgjs_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. libgjs_internal = static_library('gjs-internal', libgjs_sources, probes_header, probes_objfile, cpp_pch: 'cjs/cjs_pch.hh', dependencies: internal_build_dep, link_with: libgjs_jsapi) link_args = [] symbol_map = files('libcjs.map') symbol_list = files('libcjs.symbols') # macOS linker link_args += cxx.get_supported_link_arguments([ '-Wl,--version-script,@0@'.format(symbol_map[0].full_path()), '-Wl,-exported_symbols_list,@0@'.format(symbol_list[0].full_path()), ]) libgjs = shared_library(meson.project_name(), sources: libgjs_private_sources, link_args: link_args, link_depends: [symbol_map, symbol_list], link_whole: [libgjs_internal, module_resource_lib], dependencies: base_build_dep, version: '0.0.0', soversion: '0', gnu_symbol_visibility: 'hidden', install: true) install_headers(gjs_public_headers, subdir: api_name / 'cjs') # Allow using libgjs as a subproject libgjs_dep = declare_dependency(link_with: [libgjs, libgjs_jsapi], dependencies: base_build_dep, include_directories: top_include) ### Build GjsPrivate introspection library ##################################### gjs_private_gir = gnome.generate_gir(libgjs, includes: ['GObject-2.0', 'Gio-2.0'], sources: libgjs_private_sources, namespace: 'GjsPrivate', 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') gjs_private_typelib = gjs_private_gir[1] ### Build gjs-console interpreter ############################################## gjs_console_srcs = ['cjs/console.cpp'] gjs_console = executable('cjs-console', gjs_console_srcs, dependencies: libgjs_dep, install: true) meson.add_install_script('build/symlink-gjs.py', bindir) ### Install data files ######################################################### install_data('installed-tests/extra/gjs.supp', install_dir: gjsjsdir / 'valgrind') install_data('installed-tests/extra/lsan.supp', install_dir: gjsjsdir / '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 ################################################### requires_private_names = ['gthread-2.0', 'girepository-2.0', 'libffi', 'cairo', 'cairo-gobject', 'mozjs-140'] if pkg_dependencies.contains(cairo_xlib) requires_private_names += 'cairo-xlib' endif pkg.generate(libgjs, name: api_name, description: 'JS bindings for GObjects', requires: ['glib-2.0', 'gobject-2.0', 'gio-2.0'], requires_private: requires_private_names, subdirs: api_name, variables: [ 'exec_prefix=${prefix}', 'datarootdir=${datadir}', 'cjs_console=${bindir}/cjs-console', 'mozjs_dep_name=@0@'.format(spidermonkey.name()), ]) ### Test environment ########################################################### tests_environment = environment() gi_tests_builddir = meson.project_build_root() / 'subprojects' / 'gobject-introspection-tests' glib_root_builddir = meson.project_build_root() / 'subprojects' / 'glib' glib_builddir = glib_root_builddir / 'glib' gobject_builddir = glib_root_builddir / 'gobject' gi_builddir = glib_root_builddir / 'girepository' gio_builddir = glib_root_builddir / 'gio' js_tests_builddir = meson.current_build_dir() / 'installed-tests' / 'js' libgjs_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, libgjs_test_tools_builddir, gi_builddir / 'introspection') tests_environment.prepend('LD_LIBRARY_PATH', meson.current_build_dir(), gi_tests_builddir, js_tests_builddir, libgjs_test_tools_builddir, glib_builddir, gobject_builddir, gio_builddir, gi_builddir) tests_environment.prepend('DYLD_LIBRARY_PATH', meson.current_build_dir(), gi_tests_builddir, js_tests_builddir, libgjs_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') lsan_suppressions = files('installed-tests/extra/lsan.supp') tests_environment.set('LSAN_OPTIONS', 'fast_unwind_on_malloc=0,exitcode=23,suppressions=@0@'.format( lsan_suppressions[0].full_path())) tests_environment.set('G_SLICE', 'always-malloc') tests_environment.set('NO_AT_BRIDGE', '1') tests_environment.set('GTK_A11Y', 'none') tests_environment.set('GSETTINGS_SCHEMA_DIR', js_tests_builddir) tests_environment.set('GSETTINGS_BACKEND', 'memory') tests_environment.set('G_DEBUG', 'fatal-warnings,fatal-criticals') # Vulkan renderer may not be compatible in all environments e.g CI, use opengl instead tests_environment.set('GSK_RENDERER', gtk4_dep.version().version_compare('>= 4.19.4') ? 'gl' : 'ngl') 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') gjs_suppressions = files('installed-tests/extra/gjs.supp') valgrind_args = [ '--suppressions=@0@'.format(glib_suppresssions), '--suppressions=@0@'.format(gjs_suppressions[0].full_path()), '--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: 120, env: valgrind_environment, exe_wrapper: ['valgrind'] + valgrind_args) zeal2_environment = environment() zeal2_environment.set('JS_GC_ZEAL', 'Alloc,10') add_test_setup('extra_gc', timeout_multiplier: 60, env: zeal2_environment) zeal4_environment = environment() zeal4_environment.set('JS_GC_ZEAL', 'VerifierPre') add_test_setup('pre_verify', timeout_multiplier: 60, env: zeal4_environment) zeal11_environment = environment() zeal11_environment.set('JS_GC_ZEAL', 'VerifierPost') add_test_setup('post_verify', timeout_multiplier: 5, 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.') elif not have_gtk3 warning('Not using GTK 3, not all tests will be run.') elif not have_gtk4 warning('Not using GTK 4, 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, 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]), 'SpiderMonkey': '@0@ (@1@, @2@ build)'.format(spidermonkey.version(), locations[2], nondebug_spidermonkey ? 'release' : 'debug'), }, section: 'Dependencies') if build_readline summary('Readline', '(@0@)'.format(locations[3]), section: 'Dependencies') endif if build_profiler summary('Sysprof', '@0@ (@1@)'.format(sysprof_capture.version(), locations[4]), 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-tag-release', command: ['build/maintainer-tag-release.sh', meson.project_version()]) cjs-140.0/meson_options.txt0000664000175000017500000000244715167114161014670 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-140.0/modules/0000775000175000017500000000000015167114161012674 5ustar fabiofabiocjs-140.0/modules/cairo-context.cpp0000664000175000017500000011317315167114161016165 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/auto.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* cx, unsigned argc, JS::Value* vp) { \ GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj) #define GJS_CAIRO_CONTEXT_DEFINE_FUNC_END \ return gjs_cairo_check_status(cx, cairo_status(cr), "context"); \ } #define GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(m) \ if (argc > 0) { \ gjs_throw(cx, "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 = static_cast(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) \ GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ cairo_bool_t 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(cx, #method, argv, "ff", #n1, &arg1, #n2, &arg2)) \ return false; \ cfunc(cr, &arg1, &arg2); \ if (cairo_status(cr) == CAIRO_STATUS_SUCCESS) { \ JS::RootedObject array{cx, JS::NewArrayObject(cx, 2)}; \ if (!array) \ return false; \ JS::RootedValue r{cx, JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(cx, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(cx, 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{cx, JS::NewArrayObject(cx, 2)}; \ if (!array) \ return false; \ JS::RootedValue r{cx, JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(cx, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(cx, 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{cx, JS::NewArrayObject(cx, 4)}; \ if (!array) \ return false; \ JS::RootedValue r{cx, JS::NumberValue(JS::CanonicalizeNaN(arg1))}; \ if (!JS_SetElement(cx, array, 0, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg2)); \ if (!JS_SetElement(cx, array, 1, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg3)); \ if (!JS_SetElement(cx, array, 2, r)) \ return false; \ r.setNumber(JS::CanonicalizeNaN(arg4)); \ if (!JS_SetElement(cx, 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) \ GJS_CAIRO_CONTEXT_CHECK_NO_ARGS(method) \ double 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(cx, #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(cx, #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; \ if (!gjs_parse_call_args(cx, #method, argv, fmt, #n1, &arg1, #n2, &arg2)) \ return false; \ cairo_bool_t 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(cx, #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(cx, #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(cx, #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(cx, #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* cx, const JS::CallArgs& args) { JS::RootedObject surface_wrapper{cx}; if (!gjs_parse_call_args(cx, "Context", args, "o", "surface", &surface_wrapper)) return nullptr; cairo_surface_t* surface = CairoSurface::for_js(cx, surface_wrapper); if (!surface) return nullptr; cairo_t* cr = cairo_create(surface); if (!gjs_cairo_check_status(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject path_wrapper{cx}; if (!gjs_parse_call_args(cx, "path", argv, "o", "path", &path_wrapper)) return false; cairo_path_t* path; if (!CairoPath::for_js_typecheck(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); if (!gjs_parse_call_args(cx, "", argv, "")) return false; cairo_path_t* path = cairo_copy_path(cr); JSObject* retval = CairoPath::take_c_ptr(cx, path); if (!retval) return false; argv.rval().setObject(*retval); return true; } GJS_JSAPI_RETURN_CONVENTION static bool copyPathFlat_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); if (!gjs_parse_call_args(cx, "", argv, "")) return false; cairo_path_t* path = cairo_copy_path_flat(cr); JSObject* retval = CairoPath::take_c_ptr(cx, path); if (!retval) return false; argv.rval().setObject(*retval); return true; } GJS_JSAPI_RETURN_CONVENTION static bool mask_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject pattern_wrapper{cx}; if (!gjs_parse_call_args(cx, "mask", argv, "o", "pattern", &pattern_wrapper)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, pattern_wrapper); if (!pattern) return false; cairo_mask(cr, pattern); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool maskSurface_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject surface_wrapper{cx}; double x, y; if (!gjs_parse_call_args(cx, "maskSurface", argv, "off", "surface", &surface_wrapper, "x", &x, "y", &y)) return false; cairo_surface_t* surface = CairoSurface::for_js(cx, surface_wrapper); if (!surface) return false; cairo_mask_surface(cr, surface, x, y); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setDash_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject dashes{cx}; double offset; bool is_array; if (!gjs_parse_call_args(cx, "setDash", argv, "of", "dashes", &dashes, "offset", &offset)) return false; if (!JS::IsArrayObject(cx, dashes, &is_array)) return false; if (!is_array) { gjs_throw(cx, "dashes must be an array"); return false; } uint32_t len; if (!JS::GetArrayLength(cx, dashes, &len)) { gjs_throw(cx, "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{cx}; for (uint32_t i = 0; i < len; ++i) { double b; elem.setUndefined(); if (!JS_GetElement(cx, dashes, i, &elem)) { return false; } if (elem.isUndefined()) continue; if (!JS::ToNumber(cx, elem, &b)) return false; if (b <= 0) { gjs_throw(cx, "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* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject pattern_wrapper{cx}; if (!gjs_parse_call_args(cx, "setSource", argv, "o", "pattern", &pattern_wrapper)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, pattern_wrapper); if (!pattern) return false; cairo_set_source(cr, pattern); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setSourceSurface_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::RootedObject surface_wrapper{cx}; double x, y; if (!gjs_parse_call_args(cx, "setSourceSurface", argv, "off", "surface", &surface_wrapper, "x", &x, "y", &y)) return false; cairo_surface_t* surface = CairoSurface::for_js(cx, surface_wrapper); if (!surface) return false; cairo_set_source_surface(cr, surface, x, y); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool showText_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::UniqueChars utf8; if (!gjs_parse_call_args(cx, "showText", argv, "s", "utf8", &utf8)) return false; cairo_show_text(cr, utf8.get()); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool selectFontFace_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, argv, obj); JS::UniqueChars family; cairo_font_slant_t slant; cairo_font_weight_t weight; if (!gjs_parse_call_args(cx, "selectFontFace", argv, "sii", "family", &family, "slant", &slant, "weight", &weight)) return false; cairo_select_font_face(cr, family.get(), slant, weight); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool popGroup_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "Context.popGroup() takes no arguments"); return false; } cairo_pattern_t* pattern = cairo_pop_group(cr); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; JSObject* pattern_wrapper = gjs_cairo_pattern_from_pattern(cx, pattern); cairo_pattern_destroy(pattern); if (!pattern_wrapper) { gjs_throw(cx, "failed to create pattern"); return false; } rec.rval().setObject(*pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getSource_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "Context.getSource() takes no arguments"); return false; } cairo_pattern_t* pattern = cairo_get_source(cr); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; // pattern belongs to the context, so keep the reference JSObject* pattern_wrapper = gjs_cairo_pattern_from_pattern(cx, pattern); if (!pattern_wrapper) { gjs_throw(cx, "failed to create pattern"); return false; } rec.rval().setObject(*pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getTarget_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "Context.getTarget() takes no arguments"); return false; } cairo_surface_t* surface = cairo_get_target(cr); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; // surface belongs to the context, so keep the reference JSObject* surface_wrapper = CairoSurface::from_c_ptr(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_CAIRO_CONTEXT_GET_PRIV_CR_CHECKED(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "Context.getGroupTarget() takes no arguments"); return false; } cairo_surface_t* surface = cairo_get_group_target(cr); if (!gjs_cairo_check_status(cx, cairo_status(cr), "context")) return false; // surface belongs to the context, so keep the reference JSObject* surface_wrapper = CairoSurface::from_c_ptr(cx, 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 GJS_JSAPI_RETURN_CONVENTION static bool context_to_gi_argument(JSContext* cx, 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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } JS::RootedObject obj{cx, &value.toObject()}; cairo_t* cr = CairoContext::for_js(cx, 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* cx, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoContext::from_c_ptr(cx, static_cast(arg->v_pointer)); if (!obj) { gjs_throw(cx, "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() { static GjsForeignInfo foreign_info = {context_to_gi_argument, context_from_gi_argument, context_release_argument}; gjs_struct_foreign_register("cairo", "Context", &foreign_info); } cjs-140.0/modules/cairo-gradient.cpp0000664000175000017500000000524515167114161016276 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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); double offset, red, green, blue; if (!gjs_parse_call_args(cx, "addColorStopRGB", argv, "ffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgb(pattern, offset, red, green, blue); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool addColorStopRGBA_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); double offset, red, green, blue, alpha; if (!gjs_parse_call_args(cx, "addColorStopRGBA", argv, "fffff", "offset", &offset, "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_pattern_add_color_stop_rgba(pattern, offset, red, green, blue, alpha); if (!gjs_cairo_check_status(cx, 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-140.0/modules/cairo-image-surface.cpp0000664000175000017500000001204715167114161017207 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/auto.h" #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* cx, const JS::CallArgs& args) { int format, width, height; // create_for_data optional parameter if (!gjs_parse_call_args(cx, "ImageSurface", args, "iii", "format", &format, "width", &width, "height", &height)) return nullptr; cairo_surface_t* surface = cairo_image_surface_create((cairo_format_t)format, width, height); if (!gjs_cairo_check_status(cx, 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* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); Gjs::AutoChar filename; if (!gjs_parse_call_args(cx, "createFromPNG", args, "F", "filename", &filename)) return false; cairo_surface_t* surface = cairo_image_surface_create_from_png(filename); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; JSObject* surface_wrapper = CairoImageSurface::from_c_ptr(cx, surface); if (!surface_wrapper) return false; cairo_surface_destroy(surface); args.rval().setObject(*surface_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getFormat_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "ImageSurface.getFormat() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_format_t format = cairo_image_surface_get_format(surface); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(format); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getWidth_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "ImageSurface.getWidth() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; int width = cairo_image_surface_get_width(surface); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(width); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getHeight_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "ImageSurface.getHeight() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; int height = cairo_image_surface_get_height(surface); if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface")) return false; rec.rval().setInt32(height); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getStride_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "ImageSurface.getStride() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; int stride = cairo_image_surface_get_stride(surface); if (!gjs_cairo_check_status(cx, 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-140.0/modules/cairo-linear-gradient.cpp0000664000175000017500000000273015167114161017542 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* cx, const JS::CallArgs& args) { double x0, y0, x1, y1; if (!gjs_parse_call_args(cx, "LinearGradient", args, "ffff", "x0", &x0, "y0", &y0, "x1", &x1, "y1", &y1)) return nullptr; cairo_pattern_t* pattern = cairo_pattern_create_linear(x0, y0, x1, y1); if (!gjs_cairo_check_status(cx, 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-140.0/modules/cairo-module.h0000664000175000017500000000054315167114161015427 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. #pragma once #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_js_define_cairo_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/modules/cairo-path.cpp0000664000175000017500000000750015167114161015431 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 GIArgument, GITransfer, ... #include // for JSPROP_READONLY #include #include #include #include #include // for JS_NewObjectWithGivenProto #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/auto.h" #include "cjs/enum-utils.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #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. */ 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); } GJS_JSAPI_RETURN_CONVENTION static bool path_to_gi_argument( JSContext* cx, 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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } if (!value.isObject()) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s is not a Cairo.Path", display_name.get()); return false; } JS::RootedObject path_wrapper{cx, &value.toObject()}; cairo_path_t* s = CairoPath::for_js(cx, path_wrapper); if (!s) return false; if (transfer == GI_TRANSFER_EVERYTHING) s = CairoPath::copy_ptr(s); gjs_arg_set(arg, s); return true; } GJS_JSAPI_RETURN_CONVENTION static bool path_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoPath::from_c_ptr(cx, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } static bool path_release_argument(JSContext*, GITransfer transfer, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) cairo_path_destroy(gjs_arg_get(arg)); return true; } void gjs_cairo_path_init() { static GjsForeignInfo foreign_info = { path_to_gi_argument, path_from_gi_argument, path_release_argument}; gjs_struct_foreign_register("cairo", "Path", &foreign_info); } // Adapted from PyGObject cairo code cairo_path_t* CairoPath::copy_ptr(cairo_path_t* path) { cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); cairo_t* cr = cairo_create(surface); cairo_append_path(cr, path); cairo_path_t* copy = cairo_copy_path(cr); cairo_destroy(cr); cairo_surface_destroy(surface); return copy; } cjs-140.0/modules/cairo-pattern.cpp0000664000175000017500000001426515167114161016160 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 GIArgument, GITransfer, ... #include #include #include #include // for GetClass #include // for JSPROP_READONLY #include #include #include #include #include "gi/arg-inl.h" #include "gi/arg.h" #include "gi/foreign.h" #include "cjs/auto.h" #include "cjs/enum-utils.h" #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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "Pattern.getType() takes no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_pattern_type_t type = cairo_pattern_get_type(pattern); if (!gjs_cairo_check_status(cx, 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: * @cx: 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* cx, cairo_pattern_t* pattern) { g_return_val_if_fail(cx, nullptr); g_return_val_if_fail(pattern, nullptr); switch (cairo_pattern_get_type(pattern)) { case CAIRO_PATTERN_TYPE_SOLID: return CairoSolidPattern::from_c_ptr(cx, pattern); case CAIRO_PATTERN_TYPE_SURFACE: return CairoSurfacePattern::from_c_ptr(cx, pattern); case CAIRO_PATTERN_TYPE_LINEAR: return CairoLinearGradient::from_c_ptr(cx, pattern); case CAIRO_PATTERN_TYPE_RADIAL: return CairoRadialGradient::from_c_ptr(cx, pattern); case CAIRO_PATTERN_TYPE_MESH: case CAIRO_PATTERN_TYPE_RASTER_SOURCE: default: gjs_throw(cx, "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); } GJS_JSAPI_RETURN_CONVENTION static bool pattern_to_gi_argument(JSContext* cx, 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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } if (!value.isObject()) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s is not a Cairo.Pattern", display_name.get()); return false; } JS::RootedObject pattern_wrapper{cx, &value.toObject()}; cairo_pattern_t* s = CairoPattern::for_js(cx, pattern_wrapper); if (!s) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_pattern_reference(s); gjs_arg_set(arg, s); return true; } GJS_JSAPI_RETURN_CONVENTION static bool pattern_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoPattern::from_c_ptr(cx, gjs_arg_get(arg)); if (!obj) return false; value_p.setObject(*obj); return true; } static bool pattern_release_argument(JSContext*, GITransfer transfer, GIArgument* arg) { if (transfer != GI_TRANSFER_NOTHING) cairo_pattern_destroy(gjs_arg_get(arg)); return true; } void gjs_cairo_pattern_init() { static GjsForeignInfo foreign_info = {pattern_to_gi_argument, pattern_from_gi_argument, pattern_release_argument}; gjs_struct_foreign_register("cairo", "Pattern", &foreign_info); } cjs-140.0/modules/cairo-pdf-surface.cpp0000664000175000017500000000401315167114161016670 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 # include "cjs/auto.h" # 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* cx, const JS::CallArgs& args) { Gjs::AutoChar filename; double width, height; if (!gjs_parse_call_args(cx, "PDFSurface", args, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; cairo_surface_t* surface = cairo_pdf_surface_create(filename, width, height); if (!gjs_cairo_check_status(cx, 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* cx, cairo_surface_t* surface) { gjs_throw(cx, "could not create PDF surface, recompile cairo and gjs with PDF " "support."); return nullptr; } #endif // CAIRO_HAS_PDF_SURFACE cjs-140.0/modules/cairo-private.h0000664000175000017500000005341315167114161015620 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 #pragma once #include #include // for CAIRO_HAS_PDF_SURFACE, CAIRO_HAS_PS_SURFACE #include #include #include #include #include #include // for JSProtoKey #include "gi/cwrapper.h" #include "cjs/global.h" #include "cjs/macros.h" #include "util/log.h" struct JSFunctionSpec; struct JSPropertySpec; namespace JS { class CallArgs; } GJS_JSAPI_RETURN_CONVENTION bool gjs_cairo_check_status(JSContext*, cairo_status_t, const char* name); class CairoRegion : public CWrapper { friend CWrapperPointerOps; friend CWrapper; 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*, const JS::CallArgs&); static void finalize_impl(JS::GCContext*, cairo_region_t*); 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}; public: CairoRegion() = delete; CairoRegion(CairoRegion&) = delete; CairoRegion(CairoRegion&&) = delete; }; void gjs_cairo_region_init(); class CairoContext : public CWrapper { friend CWrapperPointerOps; friend CWrapper; 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*, const JS::CallArgs&); static void finalize_impl(JS::GCContext*, cairo_t*); 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*, unsigned, JS::Value*); public: CairoContext() = delete; CairoContext(CairoContext&) = delete; CairoContext(CairoContext&&) = delete; }; void gjs_cairo_context_init(); void gjs_cairo_surface_init(); // path void gjs_cairo_path_init(); class CairoPath : public CWrapper { friend CWrapperPointerOps; friend CWrapper; 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*); 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: static cairo_path_t* copy_ptr(cairo_path_t*); GJS_JSAPI_RETURN_CONVENTION static JSObject* take_c_ptr(JSContext*, cairo_path_t*); CairoPath() = delete; CairoPath(CairoPath&) = delete; CairoPath(CairoPath&&) = delete; }; // surface class CairoSurface : public CWrapper { friend CWrapperPointerOps; friend CWrapper; friend class CairoImageSurface; // "inherits" from CairoSurface friend class CairoPSSurface; friend class CairoPDFSurface; friend class CairoSVGSurface; 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*); 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*, unsigned, JS::Value*); public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext*, cairo_surface_t*); GJS_JSAPI_RETURN_CONVENTION static cairo_surface_t* for_js(JSContext*, JS::HandleObject surface_wrapper); CairoSurface() = delete; CairoSurface(CairoSurface&) = delete; CairoSurface(CairoSurface&&) = delete; }; 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*, 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*, const JS::CallArgs&); }; #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*, 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*, const JS::CallArgs&); }; #else class CairoPSSurface { GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext*, cairo_surface_t*); }; #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*, 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*, const JS::CallArgs&); }; #else class CairoPDFSurface { public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext*, cairo_surface_t*); }; #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*, 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*, const JS::CallArgs&); }; #else class CairoSVGSurface { public: GJS_JSAPI_RETURN_CONVENTION static JSObject* from_c_ptr(JSContext*, cairo_surface_t*); }; #endif // CAIRO_HAS_SVG_SURFACE // pattern void gjs_cairo_pattern_init(); 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; 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*, unsigned, JS::Value*); protected: static void finalize_impl(JS::GCContext*, cairo_pattern_t*); public: static cairo_pattern_t* for_js(JSContext*, JS::HandleObject pattern_wrapper); CairoPattern() = delete; CairoPattern(CairoPattern&) = delete; CairoPattern(CairoPattern&&) = delete; }; GJS_JSAPI_RETURN_CONVENTION JSObject* gjs_cairo_pattern_from_pattern(JSContext*, cairo_pattern_t*); 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*, 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*, 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*, const JS::CallArgs&); 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*, 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*, const JS::CallArgs&); 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*, 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*, const JS::CallArgs&); 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*, 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*) {} }; cjs-140.0/modules/cairo-ps-surface.cpp0000664000175000017500000000434615167114161016552 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 # include "cjs/auto.h" # 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* cx, const JS::CallArgs& args) { Gjs::AutoChar filename; double width, height; if (!gjs_parse_call_args(cx, "PSSurface", args, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; cairo_surface_t* surface = cairo_ps_surface_create(filename, width, height); if (!gjs_cairo_check_status(cx, 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* cx, cairo_surface_t* surface) { gjs_throw(cx, "could not create PS surface, recompile cairo and gjs with PS " "support."); return nullptr; } #endif // CAIRO_HAS_PS_SURFACE cjs-140.0/modules/cairo-radial-gradient.cpp0000664000175000017500000000313615167114161017525 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* cx, const JS::CallArgs& args) { double cx0, cy0, radius0, cx1, cy1, radius1; if (!gjs_parse_call_args(cx, "RadialGradient", args, "ffffff", "cx0", &cx0, "cy0", &cy0, "radius0", &radius0, "cx1", &cx1, "cy1", &cy1, "radius1", &radius1)) return nullptr; cairo_pattern_t* pattern = cairo_pattern_create_radial(cx0, cy0, radius0, cx1, cy1, radius1); if (!gjs_cairo_check_status(cx, 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-140.0/modules/cairo-region.cpp0000664000175000017500000002267415167114161015771 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/auto.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* cx, JS::HandleObject obj, cairo_rectangle_int_t* rect); #define PRELUDE \ GJS_GET_THIS(cx, argc, vp, argv, obj); \ cairo_region_t* this_region; \ if (!CairoRegion::for_js_typecheck(cx, obj, &this_region, &argv)) \ return false; #define RETURN_STATUS \ return gjs_cairo_check_status(cx, cairo_region_status(this_region), \ "region"); #define REGION_DEFINE_REGION_FUNC(method) \ GJS_JSAPI_RETURN_CONVENTION \ static bool method##_func(JSContext* cx, unsigned argc, JS::Value* vp) { \ PRELUDE; \ JS::RootedObject other_obj{cx}; \ if (!gjs_parse_call_args(cx, #method, argv, "o", "other_region", \ &other_obj)) \ return false; \ \ cairo_region_t* other_region = CairoRegion::for_js(cx, 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* cx, unsigned argc, \ JS::Value* vp) { \ PRELUDE; \ JS::RootedObject rect_obj{cx}; \ if (!gjs_parse_call_args(cx, #method, argv, "o", "rect", &rect_obj)) \ return false; \ \ cairo_rectangle_int_t rect; \ if (!fill_rectangle(cx, 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* cx, JS::HandleObject obj, cairo_rectangle_int_t* rect) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedValue val{cx}; if (!JS_GetPropertyById(cx, obj, atoms.x(), &val)) return false; if (!JS::ToInt32(cx, val, &rect->x)) return false; if (!JS_GetPropertyById(cx, obj, atoms.y(), &val)) return false; if (!JS::ToInt32(cx, val, &rect->y)) return false; if (!JS_GetPropertyById(cx, obj, atoms.width(), &val)) return false; if (!JS::ToInt32(cx, val, &rect->width)) return false; if (!JS_GetPropertyById(cx, obj, atoms.height(), &val)) return false; if (!JS::ToInt32(cx, val, &rect->height)) return false; return true; } GJS_JSAPI_RETURN_CONVENTION static JSObject* make_rectangle(JSContext* cx, cairo_rectangle_int_t* rect) { const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); JS::RootedObject rect_obj{cx, JS_NewPlainObject(cx)}; if (!rect_obj) return nullptr; JS::RootedValue val{cx}; val = JS::Int32Value(rect->x); if (!JS_SetPropertyById(cx, rect_obj, atoms.x(), val)) return nullptr; val = JS::Int32Value(rect->y); if (!JS_SetPropertyById(cx, rect_obj, atoms.y(), val)) return nullptr; val = JS::Int32Value(rect->width); if (!JS_SetPropertyById(cx, rect_obj, atoms.width(), val)) return nullptr; val = JS::Int32Value(rect->height); if (!JS_SetPropertyById(cx, rect_obj, atoms.height(), val)) return nullptr; return rect_obj; } GJS_JSAPI_RETURN_CONVENTION static bool num_rectangles_func(JSContext* cx, unsigned argc, JS::Value* vp) { PRELUDE; if (!gjs_parse_call_args(cx, "num_rectangles", argv, "")) return false; int 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* cx, unsigned argc, JS::Value* vp) { PRELUDE; int i; cairo_rectangle_int_t rect; if (!gjs_parse_call_args(cx, "get_rectangle", argv, "i", "rect", &i)) return false; cairo_region_get_rectangle(this_region, i, &rect); JSObject* rect_obj = make_rectangle(cx, &rect); if (!rect_obj) return false; argv.rval().setObject(*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* cx, const JS::CallArgs& args) { if (!gjs_parse_call_args(cx, "Region", args, "")) return nullptr; return cairo_region_create(); } void CairoRegion::finalize_impl(JS::GCContext*, cairo_region_t* region) { if (!region) return; cairo_region_destroy(region); } GJS_JSAPI_RETURN_CONVENTION static bool region_to_gi_argument(JSContext* cx, 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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } JS::RootedObject obj{cx, &value.toObject()}; cairo_region_t* region; if (!CairoRegion::for_js_typecheck(cx, obj, ®ion)) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_region_reference(region); gjs_arg_set(arg, region); return true; } GJS_JSAPI_RETURN_CONVENTION static bool region_from_gi_argument(JSContext* cx, JS::MutableHandleValue value_p, GIArgument* arg) { JSObject* obj = CairoRegion::from_c_ptr(cx, 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() { static GjsForeignInfo foreign_info = {region_to_gi_argument, region_from_gi_argument, region_release_argument}; gjs_struct_foreign_register("cairo", "Region", &foreign_info); } cjs-140.0/modules/cairo-solid-pattern.cpp0000664000175000017500000000520115167114161017256 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* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); double red, green, blue; if (!gjs_parse_call_args(cx, "createRGB", args, "fff", "red", &red, "green", &green, "blue", &blue)) return false; cairo_pattern_t* pattern = cairo_pattern_create_rgb(red, green, blue); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; JSObject* pattern_wrapper = CairoSolidPattern::from_c_ptr(cx, pattern); if (!pattern_wrapper) return false; cairo_pattern_destroy(pattern); args.rval().setObject(*pattern_wrapper); return true; } GJS_JSAPI_RETURN_CONVENTION static bool createRGBA_func(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); double red, green, blue, alpha; if (!gjs_parse_call_args(cx, "createRGBA", args, "ffff", "red", &red, "green", &green, "blue", &blue, "alpha", &alpha)) return false; cairo_pattern_t* pattern = cairo_pattern_create_rgba(red, green, blue, alpha); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; JSObject* pattern_wrapper = CairoSolidPattern::from_c_ptr(cx, pattern); if (!pattern_wrapper) return false; cairo_pattern_destroy(pattern); args.rval().setObject(*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-140.0/modules/cairo-surface-pattern.cpp0000664000175000017500000001013615167114161017577 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* cx, const JS::CallArgs& args) { JS::RootedObject surface_wrapper{cx}; if (!gjs_parse_call_args(cx, "SurfacePattern", args, "o", "surface", &surface_wrapper)) return nullptr; cairo_surface_t* surface = CairoSurface::for_js(cx, surface_wrapper); if (!surface) return nullptr; cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface); if (!gjs_cairo_check_status(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); cairo_extend_t extend; if (!gjs_parse_call_args(cx, "setExtend", argv, "i", "extend", &extend)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_pattern_set_extend(pattern, extend); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getExtend_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "SurfacePattern.getExtend() requires no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_extend_t extend = cairo_pattern_get_extend(pattern); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; rec.rval().setInt32(extend); return true; } GJS_JSAPI_RETURN_CONVENTION static bool setFilter_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); cairo_filter_t filter; if (!gjs_parse_call_args(cx, "setFilter", argv, "i", "filter", &filter)) return false; cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_pattern_set_filter(pattern, filter); if (!gjs_cairo_check_status(cx, cairo_pattern_status(pattern), "pattern")) return false; argv.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool getFilter_func(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 0) { gjs_throw(cx, "SurfacePattern.getFilter() requires no arguments"); return false; } cairo_pattern_t* pattern = CairoPattern::for_js(cx, obj); if (!pattern) return false; cairo_filter_t filter = cairo_pattern_get_filter(pattern); if (!gjs_cairo_check_status(cx, 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-140.0/modules/cairo-surface.cpp0000664000175000017500000002613315167114161016130 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/auto.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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, argv, obj); Gjs::AutoChar filename; if (!gjs_parse_call_args(cx, "writeToPNG", argv, "F", "filename", &filename)) return false; cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_write_to_png(surface, filename); if (!gjs_cairo_check_status(cx, 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* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); if (argc > 1) { gjs_throw(cx, "Surface.getType() takes no arguments"); return false; } cairo_surface_t* surface = CairoSurface::for_js(cx, obj); if (!surface) return false; cairo_surface_type_t type = cairo_surface_get_type(surface); if (!gjs_cairo_check_status(cx, 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: * @cx: 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* cx, cairo_surface_t* surface) { g_return_val_if_fail(cx, 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(cx, surface); if (type == CAIRO_SURFACE_TYPE_PDF) return CairoPDFSurface::from_c_ptr(cx, surface); if (type == CAIRO_SURFACE_TYPE_PS) return CairoPSSurface::from_c_ptr(cx, surface); if (type == CAIRO_SURFACE_TYPE_SVG) return CairoSVGSurface::from_c_ptr(cx, surface); return CairoSurface::CWrapper::from_c_ptr(cx, 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); } GJS_JSAPI_RETURN_CONVENTION static bool surface_to_gi_argument(JSContext* cx, 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)) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s may not be null", display_name.get()); return false; } gjs_arg_unset(arg); return true; } if (!value.isObject()) { Gjs::AutoChar display_name{ gjs_argument_display_name(arg_name, argument_type)}; gjs_throw(cx, "%s is not a Cairo.Surface", display_name.get()); return false; } JS::RootedObject surface_wrapper{cx, &value.toObject()}; cairo_surface_t* s = CairoSurface::for_js(cx, surface_wrapper); if (!s) return false; if (transfer == GI_TRANSFER_EVERYTHING) cairo_surface_reference(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() { static GjsForeignInfo foreign_info = {surface_to_gi_argument, surface_from_gi_argument, surface_release_argument}; gjs_struct_foreign_register("cairo", "Surface", &foreign_info); } cjs-140.0/modules/cairo-svg-surface.cpp0000664000175000017500000000401515167114161016720 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 # include "cjs/auto.h" # 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* cx, const JS::CallArgs& args) { Gjs::AutoChar filename; double width, height; if (!gjs_parse_call_args(cx, "SVGSurface", args, "Fff", "filename", &filename, "width", &width, "height", &height)) return nullptr; cairo_surface_t* surface = cairo_svg_surface_create(filename, width, height); if (!gjs_cairo_check_status(cx, 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* cx, cairo_surface_t* surface) { gjs_throw(cx, "could not create SVG surface, recompile cairo and gjs with SVG " "support."); return nullptr; } #endif // CAIRO_HAS_SVG_SURFACE cjs-140.0/modules/cairo.cpp0000664000175000017500000000467515167114161014511 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" // IWYU pragma: associated #ifdef CAIRO_HAS_XLIB_SURFACE class XLibConstructor { public: XLibConstructor() { XInitThreads(); } }; static XLibConstructor constructor; #endif bool gjs_cairo_check_status(JSContext* cx, cairo_status_t status, const char* name) { if (status != CAIRO_STATUS_SUCCESS) { gjs_throw(cx, "cairo error on %s: \"%s\" (%d)", name, cairo_status_to_string(status), status); return false; } return true; } bool gjs_js_define_cairo_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); if (!CairoRegion::create_prototype(cx, module)) return false; gjs_cairo_region_init(); if (!CairoContext::create_prototype(cx, module)) return false; gjs_cairo_context_init(); if (!CairoSurface::create_prototype(cx, module)) return false; gjs_cairo_surface_init(); if (!CairoPattern::create_prototype(cx, module)) return false; gjs_cairo_pattern_init(); if (!CairoPath::create_prototype(cx, module)) return false; gjs_cairo_path_init(); return CairoImageSurface::create_prototype(cx, module) && #if CAIRO_HAS_PS_SURFACE CairoPSSurface::create_prototype(cx, module) && #endif #if CAIRO_HAS_PDF_SURFACE CairoPDFSurface::create_prototype(cx, module) && #endif #if CAIRO_HAS_SVG_SURFACE CairoSVGSurface::create_prototype(cx, module) && #endif CairoGradient::create_prototype(cx, module) && CairoLinearGradient::create_prototype(cx, module) && CairoRadialGradient::create_prototype(cx, module) && CairoSurfacePattern::create_prototype(cx, module) && CairoSolidPattern::create_prototype(cx, module); } cjs-140.0/modules/console.cpp0000664000175000017500000003016415167114161015046 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 #ifdef HAVE_READLINE_READLINE_H # include # include // IWYU pragma: keep #endif #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 #include "cjs/atoms.h" #include "cjs/auto.h" #include "cjs/context-private.h" #include "cjs/global.h" #include "cjs/jsapi-util.h" #include "cjs/macros.h" #include "modules/console.h" #include "util/console.h" namespace mozilla { union Utf8Unit; } using mozilla::Maybe, mozilla::Nothing, mozilla::Some; 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 { Gjs::AutoChar 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 #ifdef HAVE_READLINE_READLINE_H // Readline only has a global handler anyway, so may as well use global data static Maybe rl_async_line{}; static bool rl_async_done = true; static gboolean on_stdin_ready(GPollableInputStream* pollable, void*) { while (g_pollable_input_stream_is_readable(pollable)) rl_callback_read_char(); return G_SOURCE_CONTINUE; } static void on_readline_complete(char* line) { rl_async_line = line ? Some(line) : Nothing(); rl_async_done = true; // This needs to be called from the callback handler, otherwise an extra // prompt is displayed. rl_callback_handler_remove(); } #endif [[nodiscard]] static bool gjs_console_readline(std::string* bufp, const char* prompt, const char* repl_history_path [[maybe_unused]]) { #ifdef HAVE_READLINE_READLINE_H g_assert(rl_async_done && "should not attempt two parallel readline calls"); rl_callback_handler_install(prompt, on_readline_complete); rl_async_done = false; Gjs::AutoUnref stdin_stream{ g_unix_input_stream_new(fileno(rl_instream), /* close_fd = */ false)}; Gjs::AutoUnref stdin_source{g_pollable_input_stream_create_source( G_POLLABLE_INPUT_STREAM(stdin_stream.get()), nullptr)}; g_source_set_callback(stdin_source, G_SOURCE_FUNC(on_stdin_ready), nullptr, nullptr); Gjs::AutoPointer main_context{g_main_context_ref_thread_default()}; unsigned tag = g_source_attach(stdin_source, main_context); stdin_source.release(); while (!rl_async_done) { // Yield to other things while waiting for input while (g_main_context_pending(main_context)) g_main_context_iteration(main_context, /* may_block = */ false); } g_source_remove(tag); if (!rl_async_line) return false; *bufp = rl_async_line.extract(); if ((*bufp)[0] != '\0') { add_history(bufp->c_str()); gjs_console_write_repl_history(repl_history_path); } #else // !HAVE_READLINE_READLINE_H char line[256]; fprintf(stdout, "%s", prompt); fflush(stdout); if (!fgets(line, sizeof line, stdin)) return false; *bufp = 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* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); volatile bool eof, exit_warning; // accessed after setjmp() JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)}; volatile int lineno; // accessed after setjmp() volatile int startline; // accessed after setjmp() GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); #ifndef HAVE_READLINE_READLINE_H int rl_end = 0; // nonzero if using readline and any text is typed in #endif JS::SetWarningReporter(cx, 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; 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 std::string temp_buf; if (!gjs_console_readline(&temp_buf, startline == lineno ? "gjs> " : ".... ", gjs->repl_history_path())) { eof = true; break; } buffer += temp_buf; buffer += "\n"; lineno++; } while (!JS_Utf8BufferIsCompilableUnit(cx, global, buffer.c_str(), buffer.size())); bool ok; { AutoReportException are(cx); ok = gjs_console_eval_and_print(cx, global, buffer, startline); } exit_warning = false; 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 gjs-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"); args.rval().setUndefined(); return true; } bool gjs_define_console_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return JS_DefineFunctionById(cx, module, atoms.interact(), gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS); } cjs-140.0/modules/console.h0000664000175000017500000000054115167114161014507 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 #pragma once #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_console_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/modules/core/0000775000175000017500000000000015167114161013624 5ustar fabiofabiocjs-140.0/modules/core/_cairo.js0000664000175000017500000000357615167114161015431 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-140.0/modules/core/_common.js0000664000175000017500000001675115167114161015623 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, _createWrappersForPlatformSpecificNamespace, _defineDeprecatedWrapper */ // 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. const {warnDeprecatedOncePerCallsite, RENAMED, PLATFORM_SPECIFIC_TYPELIB} = imports._print; 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(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(thisObj, handlerName, swapped, connectObj); if (flags & GObject.ConnectFlags.AFTER) obj.connect_after(signalName, closure); else obj.connect(signalName, closure); }; } function _createWrappersForPlatformSpecificNamespace(namespace) { // Redefine namespace properties 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. let platformNamespace; let platformName; const namespaceName = namespace.__name__; try { platformName = 'Unix'; platformNamespace = imports.gi[`${namespaceName}${platformName}`]; } catch { try { platformName = 'Win32'; platformNamespace = imports.gi[`${namespaceName}${platformName}`]; } catch { return; } } const platformNameLower = platformName.toLowerCase(); Object.entries(Object.getOwnPropertyDescriptors(platformNamespace)).forEach(([prop, desc]) => { let genericProp = prop; const originalValue = platformNamespace[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}`; else if (originalValue instanceof Object && (!gtypeName || gtypeName === 'void') && (!originalValue.name || originalValue.name.startsWith( `${namespaceName}${platformName}_`))) genericProp = `${platformName}${prop}`; if (Object.hasOwn(namespace, genericProp)) { console.log(`${namespaceName} already contains property ${genericProp}`); namespace[genericProp] = originalValue; return; } _defineDeprecatedWrapperForDescriptor( namespace, platformNamespace, genericProp, prop, desc, PLATFORM_SPECIFIC_TYPELIB); }); } function _defineDeprecatedWrapperForDescriptor( namespace, newNamespace, oldName, newName, desc, deprecationReason = RENAMED) { const namespaceName = namespace.__name__; if (Object.hasOwn(namespace, oldName)) { console.log(`${namespaceName} already contains property ${oldName}`); return; } Object.defineProperty(namespace, oldName, { enumerable: true, configurable: false, get() { warnDeprecatedOncePerCallsite(deprecationReason, `${namespaceName}.${oldName}`, `${newNamespace.__name__}.${newName}`); return desc.get?.() ?? desc.value; }, }); } function _defineDeprecatedWrapper( namespace, newNamespace, oldName, newName, deprecationReason = RENAMED) { _defineDeprecatedWrapperForDescriptor( namespace, newNamespace, oldName, newName, Object.getOwnPropertyDescriptor(namespace, newName), deprecationReason); } cjs-140.0/modules/core/_format.js0000664000175000017500000000476315167114161015623 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 */ let numberFormatter = null; 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) { numberFormatter ??= new Intl.NumberFormat(); s = numberFormatter.format(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-140.0/modules/core/_gettext.js0000664000175000017500000000425615167114161016014 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 GjsPrivate = imports.gi.GjsPrivate; var LocaleCategory = GjsPrivate.LocaleCategory; function setlocale(category, locale) { return GjsPrivate.set_thread_locale(category, locale); } function textdomain(dom) { return GjsPrivate.textdomain(dom); } function bindtextdomain(dom, location) { return GjsPrivate.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-140.0/modules/core/_signals.js0000664000175000017500000001226115167114161015763 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-140.0/modules/core/overrides/0000775000175000017500000000000015167114161015626 5ustar fabiofabiocjs-140.0/modules/core/overrides/GLib.js0000664000175000017500000005461015167114161017007 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; const {_createWrappersForPlatformSpecificNamespace, _defineDeprecatedWrapper} = imports._common; 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 (const [key, field] of Object.entries(fields)) { 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)); }; // GjsPrivate 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.GjsPrivate; log_set_writer_func(...args); }; this.log_set_writer_default = function (...args) { const {log_set_writer_default} = imports.gi.GjsPrivate; log_set_writer_default(...args); }; this.log_set_writer_func = function (writer_func) { const {log_set_writer_func} = imports.gi.GjsPrivate; 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); }); } }; if (GLib.MAJOR_VERSION > 2 || (GLib.MAJOR_VERSION === 2 && (GLib.MINOR_VERSION > 87 || (GLib.MINOR_VERSION === 87 && GLib.MICRO_VERSION >= 3)))) _createWrappersForPlatformSpecificNamespace(GLib); try { // We cannot use a specific GLibUnix override file because that // would make the platform-independent wrapper creator to warn // about using a deprecated symbol. const {GLibUnix} = imports.gi; if (!Object.hasOwn(GLibUnix, 'signal_add_full')) { _defineDeprecatedWrapper( GLibUnix, GLibUnix, 'signal_add_full', 'signal_add'); } } catch {} 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); }; // Provide overrides for one-shot idle/timeout functions in GLib. // The original functions are not introspectable, as they don't have // "full" variants with a GDestroy parameter for the callback. this.idle_add_once = function (priority, callback) { const id = this.idle_add(priority, () => { callback(); return this.SOURCE_REMOVE; }); return id; }; this.timeout_add_once = function (priority, interval, callback) { const id = this.timeout_add(priority, interval, () => { callback(); return this.SOURCE_REMOVE; }); return id; }; this.timeout_add_seconds_once = function (priority, interval, callback) { const id = this.timeout_add_seconds(priority, interval, () => { callback(); return this.SOURCE_REMOVE; }); return id; }; // 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.GjsPrivate; const originalMatchInfoMethods = new Set(Object.keys(oldMatchInfo.prototype)); const overriddenMatchInfoMethods = new Set(Object.keys(MatchInfo.prototype)); const symmetricDifference = originalMatchInfoMethods.symmetricDifference(overriddenMatchInfoMethods); 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 GjsPrivate requires GLib. So this monkeypatches GLib.MatchInfo // with a Proxy that overwrites itself with the real GjsPrivate.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.GjsPrivate.regex_match(this, ...args); }; this.Regex.prototype.match_full = function (...args) { patchMatchInfo(GLib); return imports.gi.GjsPrivate.regex_match_full(this, ...args); }; this.Regex.prototype.match_all = function (...args) { patchMatchInfo(GLib); return imports.gi.GjsPrivate.regex_match_all(this, ...args); }; this.Regex.prototype.match_all_full = function (...args) { patchMatchInfo(GLib); return imports.gi.GjsPrivate.regex_match_all_full(this, ...args); }; } cjs-140.0/modules/core/overrides/GObject.js0000664000175000017500000011326515167114161017511 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 {GjsPrivate, 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) .filter(name => name.startsWith('vfunc_')) .forEach(name => { const prop = Object.getOwnPropertyDescriptor(klass, name); if (!(prop.value instanceof Function)) return; giPrototype[Gi.hook_up_vfunc_symbol](name.slice(6), klass[name], true); }); 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' && Object.prototype.isPrototypeOf.call(GObject.Interface.prototype, 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 GjsPrivate.param_spec_get_flags(this); }, }, 'value_type': { configurable: false, enumerable: false, get() { return GjsPrivate.param_spec_get_value_type(this); }, }, 'owner_type': { configurable: false, enumerable: false, get() { return GjsPrivate.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 GjsPrivate.g_object_bind_property_full(this, ...args); }; GObject.BindingGroup.prototype.bind_full = function (...args) { return GjsPrivate.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; const gValConstructor = GObject.Value; GObject.Value = function (...args) { const v = new gValConstructor(); if (args.length !== 2) return v; const type = args[0], val = args[1]; v.init(type); switch (v.g_type) { case GObject.TYPE_BOOLEAN: v.set_boolean(val); break; case GObject.TYPE_BOXED: v.set_boxed(val); break; case GObject.TYPE_CHAR: v.set_schar(val); break; case GObject.TYPE_DOUBLE: v.set_double(val); break; case GObject.TYPE_FLOAT: v.set_float(val); break; case GObject.TYPE_GTYPE: v.set_gtype(val); break; case GObject.TYPE_INT: v.set_int(val); break; case GObject.TYPE_INT64: v.set_int64(val); break; case GObject.TYPE_LONG: v.set_long(val); break; case GObject.TYPE_OBJECT: v.set_object(val); break; case GObject.TYPE_PARAM: v.set_param(val); break; case GObject.TYPE_STRING: v.set_string(val); break; case GObject.TYPE_UCHAR: v.set_uchar(val); break; case GObject.TYPE_UINT: v.set_uint(val); break; case GObject.TYPE_UINT64: v.set_uint64(val); break; case GObject.TYPE_ULONG: v.set_ulong(val); break; case GObject.TYPE_VARIANT: v.set_variant(val); break; // case TYPE_POINTER omitted default: if (GObject.type_is_a(v.g_type, GObject.TYPE_FLAGS)) v.set_flag(val); else if (GObject.type_is_a(v.g_type, GObject.TYPE_ENUM)) v.set_enum(val); else if (GObject.type_is_a(v.g_type, GObject.TYPE_BOXED)) v.set_boxed(val); else if (GObject.type_is_a(v.g_type, GObject.TYPE_OBJECT)) v.set_object(val); else throw new TypeError(`Invalid type argument ${type} to GObject.Value constructor!`); } return v; }; GObject.Value.prototype = gValConstructor.prototype; GObject.Value.prototype.constructor = GObject.Value; GObject.Value.$gtype = gValConstructor.$gtype; Object.entries(gValConstructor).forEach(([k, v]) => { GObject.Value[k] = v; }); } cjs-140.0/modules/core/overrides/Gio.js0000664000175000017500000010224415167114161016705 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna var GLib = imports.gi.GLib; var GjsPrivate = imports.gi.GjsPrivate; var Signals = imports.signals; const {_createWrappersForPlatformSpecificNamespace} = imports._common; 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 _handleDBusReply(invocation, ret) { if (ret === undefined) { // undefined (no return value) is the empty tuple ret = new GLib.Variant('()', []); } try { let outFdList = null; if (!(ret instanceof GLib.Variant)) { // attempt packing according to out signature const outArgs = invocation.get_method_info().out_args; const outSignature = _makeOutSignature(outArgs); if (outSignature.includes('h') && ret[ret.length - 1] instanceof Gio.UnixFDList) { outFdList = ret.pop(); } else if (outArgs.length === 1) { // if one arg, we don't require the handler wrapping it // into an Array ret = [ret]; } ret = new GLib.Variant(outSignature, ret); } invocation.return_value_with_unix_fd_list(ret, outFdList); } catch (e) { logError(e, `Exception in method call: ${invocation.get_method_name()}`); // 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'); } } function _handleDBusError(invocation, e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); return; } let {name} = e; if (!name.includes('.')) { // likely to be a normal JS error name = `org.gnome.gjs.JSError.${name}`; } logError(e, `Exception in method call: ${invocation.get_method_name()}`); invocation.return_dbus_error(name, e.message); } function _handleMethodCall(methodName, invocation, parameters) { // prefer a sync version if available const method = this[methodName]; if (method) { let retval; try { const args = parameters.deepUnpack(); args.push(invocation.get_message().get_unix_fd_list()); retval = method.apply(this, args); } catch (e) { _handleDBusError(invocation, e); return; } // eslint-disable-next-line no-unused-expressions retval?.then?.(r => _handleDBusReply(invocation, r))?.catch?.( e => _handleDBusError(invocation, e)) ?? _handleDBusReply(invocation, retval); return; } const asyncMethod = this[`${methodName}Async`]; if (asyncMethod) { function maybeHandleError(e) { if (_methodInvocations.has(invocation)) { logError(e, `Exception in method call: ${invocation.get_method_name()}`); return; } _handleDBusError(invocation, e); }; const fdList = invocation.get_message().get_unix_fd_list(); let ret; try { ret = asyncMethod.call(this, parameters.deepUnpack(), invocation, fdList); } catch (e) { maybeHandleError(e); return; } ret?.catch?.(maybeHandleError); } else { logError(new Error(), `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 GjsPrivate.DBusImplementation({g_interface_info: info}); impl.connect('handle-method-call', function (_, ...args) { return _handleMethodCall.apply(jsObj, args); }); 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)); } const _methodInvocations = new WeakMap(); function _init() { Gio = this; Gio.Application.prototype.runAsync = GLib.MainLoop.prototype.runAsync; _createWrappersForPlatformSpecificNamespace(Gio); 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; // Wrap the invocation return methods to catch if the method has already // returned, in fact in such case we should not try to return anything // especially because the Gio.DBusMethodInvocation.return_* methods take // the ownership of the invocation itself and, calling it more than once // may lead to memory issues. // The invocated methods are saved in a weak map so that we do not // artificially create a toggle reference that may keep the method // invocation alive forever. Object.entries(Object.getOwnPropertyDescriptors(Gio.DBusMethodInvocation.prototype)).forEach(([symbol, desc]) => { if (!symbol.startsWith('return_')) return; const originalMethod = desc.value; desc.value = function (...args) { const oldInvocation = _methodInvocations.get(this); if (oldInvocation) { // We do not throw here, not to break compatibility, but // we make this a no-op, to prevent potential memory issues. logError(new Error(), `${this} (${oldInvocation.methodName}) ` + `already returned @\n${oldInvocation.previousCallStack.split( '\n').slice(1).join('\n')}`); return; } _methodInvocations.set(this, { methodName: this.get_method_name(), previousCallStack: new Error().stack, }); return originalMethod.apply(this, args); }; Object.defineProperty(Gio.DBusMethodInvocation.prototype, symbol, desc); }); // Some helpers _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo); Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo; Gio.DBusExportedObject = GjsPrivate.DBusImplementation; Gio.DBusExportedObject.wrapJSObject = _wrapJSObject; // ListStore Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator; Gio.ListStore.prototype.insert_sorted = function (item, compareFunc) { return GjsPrivate.list_store_insert_sorted(this, item, compareFunc); }; Gio.ListStore.prototype.sort = function (compareFunc) { return GjsPrivate.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 (let {name, activate, parameter_type, state, change_state} of entries) { if (typeof parameter_type === 'string') { if (!GLib.variant_type_string_is_valid(parameter_type)) throw new Error(`parameter_type "${parameter_type}" is not a valid VariantType`); parameter_type = new GLib.VariantType(parameter_type); } if (typeof state === 'boolean') state = GLib.Variant.new_boolean(state); if (typeof state === 'string') state = GLib.Variant.parse(null, state, null, null); const action = new Gio.SimpleAction({ name, parameter_type: parameter_type instanceof GLib.VariantType ? parameter_type : null, state: state instanceof GLib.Variant ? state : 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-140.0/modules/core/overrides/Gtk.js0000664000175000017500000002231415167114161016713 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, GjsPrivate, GLib, GObject} = imports.gi; const {_createBuilderConnectFunc, _createClosure, _registerType} = imports._common; const Gi = imports._gi; let Gtk; let TemplateBuilderScope; 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) { GjsPrivate.gtk_container_child_set_property(this, child, property, value); }; } if (Gtk.CustomSorter) { Gtk.CustomSorter.new = GjsPrivate.gtk_custom_sorter_new; Gtk.CustomSorter.prototype.set_sort_func = function (sortFunc) { GjsPrivate.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; }; } // Everything after this is GTK4-only, for the Gtk.Builder JS implementation if (!Gtk.BuilderScope) return; TemplateBuilderScope = 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(thisArg, handlerName, swapped, connectObject) ); } }); const NonTemplateBuilderScope = GObject.registerClass(class extends GObject.Object { static [GObject.GTypeName] = 'NonTemplateBuilderScope'; static [GObject.interfaces] = [Gtk.BuilderScope]; #callbacks; constructor(callbacks = {}) { super(); this.#callbacks = callbacks; } vfunc_create_closure(builder, handlerName, flags, connectObject) { const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED; const builderCurrentObject = builder.get_current_object(); if (connectObject || builderCurrentObject || this.#callbacks instanceof GObject.Object) { const lifetimeObject = connectObject ?? builderCurrentObject ?? this.#callbacks; return Gi.associateClosure(lifetimeObject, _createClosure(this.#callbacks, handlerName, swapped, connectObject ?? builderCurrentObject)); } return _createClosure(this.#callbacks, handlerName, swapped); } }); class GtkJSBuilder extends Gtk.Builder { static [GObject.GTypeName] = 'GtkJSBuilder'; static { GObject.registerClass(GtkJSBuilder); } /** * @param {object} [props] Construct properties * @param {string | Uint8Array} [props.data] XML interface description * @param {string} [props.filename] Local filename for XML interface * @param {string} [props.resource] Resource path for XML interface * @param {object} [props.callbacks] Object with callbacks to expose * @param {object} [props.objects] Object with other objects to expose * @param {Gtk.BuilderConstructParameters} [props.props] Other * Gtk.Builder construct properties */ constructor({data, filename, resource, callbacks, objects, ...props} = {}) { const countOfDataSources = [data, filename, resource] .filter(source => source !== undefined) .length; if (countOfDataSources === 0) { if (!callbacks && !objects) { super(props); return; // default behaviour if no extra properties passed } } else if (countOfDataSources !== 1) { throw new Error('Pass at most one of data, filename, or resource'); } if (props.scope) throw new Error("Don't pass a scope property when using JS Gtk.Builder features"); const scope = new NonTemplateBuilderScope(callbacks); super({...props, scope}); if (objects) this.exposeObjects(objects); if (data) { const str = typeof data === 'string' ? data : new TextDecoder().decode(data); this.add_from_string(str, -1); } else if (resource) { this.add_from_resource(resource); } else if (filename) { this.add_from_file(filename); } } // override for original get_objects() method that returns a Proxy which // accesses get_object() on property accesses, so that you can do // const {name1, name2} = builder.get_objects(); // Note the builder instance is kept alive by the proxy! get_objects() { const builder = this; const proxyHandler = { get(objectsArray, id, receiver) { if (Reflect.has(objectsArray, id)) return Reflect.get(objectsArray, id, receiver); if (typeof id !== 'string') return undefined; const obj = builder.get_object(id); objectsArray[id] = obj; return obj; }, }; const objectsArray = super.get_objects(); return new Proxy(objectsArray, proxyHandler); } // convenience method exposeObjects(objects) { for (const [name, object] of Object.entries(objects)) this.expose_object(name, object); } } Gtk.Builder = GtkJSBuilder; } 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 (TemplateBuilderScope) Gtk.Widget.set_template_scope.call(klass, new TemplateBuilderScope()); 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-140.0/modules/core/overrides/cairo.js0000664000175000017500000000051115167114161017256 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-140.0/modules/esm/0000775000175000017500000000000015167114161013460 5ustar fabiofabiocjs-140.0/modules/esm/_bootstrap/0000775000175000017500000000000015167114161015634 5ustar fabiofabiocjs-140.0/modules/esm/_bootstrap/default.js0000664000175000017500000000050015167114161017611 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-140.0/modules/esm/_encoding/0000775000175000017500000000000015167114161015405 5ustar fabiofabiocjs-140.0/modules/esm/_encoding/encoding.js0000664000175000017500000001137515167114161017540 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-140.0/modules/esm/_encoding/encodingMap.js0000664000175000017500000001676415167114161020205 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-140.0/modules/esm/_encoding/util.js0000664000175000017500000000174215167114161016724 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-140.0/modules/esm/_timers.js0000664000175000017500000001015015167114161015455 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-140.0/modules/esm/cairo.js0000664000175000017500000000036615167114161015120 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-140.0/modules/esm/console.js0000664000175000017500000004544215167114161015471 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2021 Evan Welsh const DEFAULT_LOG_DOMAIN = 'Gjs-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.GjsPrivate.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-140.0/modules/esm/gettext.js0000664000175000017500000000103715167114161015503 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-140.0/modules/esm/gi.js0000664000175000017500000000207215167114161014416 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-140.0/modules/esm/system.js0000664000175000017500000000120515167114161015340 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-140.0/modules/internal/0000775000175000017500000000000015167114161014510 5ustar fabiofabiocjs-140.0/modules/internal/environment.d.ts0000664000175000017500000000306015167114161017645 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh declare var moduleGlobalThis: Global; declare var atob: (text: string) => string; declare var compileInternalModule: CompileFunc; declare var compileModule: CompileFunc; declare var getRegistry: (global: Global) => Map; declare var getSourceMapRegistry: (global: Global) => Map; declare var loadResourceOrFile: (uri: string) => string; declare var loadResourceOrFileAsync: (uri: string) => Promise; declare var parseURI: (uri: string) => Uri; declare var resolveRelativeResourceOrFile: (uri: string, relativePath: string) => Uri; declare var setGlobalModuleLoader: (global: Global, loader: InternalModuleLoader) => void; declare var setModulePrivate: (module: Module, private: ModulePrivate) => void; declare var uriExists: (uri: string) => boolean; /** * Use '__internal: never' to prevent any object from being type compatible with * Module because it is an internal type. */ declare type Module = { __internal: never }; declare type Global = typeof globalThis; declare type SchemeHandler = { load(uri: Uri): string; loadAsync(uri: Uri): Promise; }; declare type Query = { [key: string]: string | undefined }; declare type CompileFunc = (uri: string, source: string) => Module; declare type ResolvedModule = [Module, string, string]; declare type Uri = { uri: string; uriWithQuery: string; scheme: string; host: string; path: string; query: Query; }; cjs-140.0/modules/internal/internalLoader.js0000664000175000017500000001416415167114161020017 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh // eslint-disable-next-line spaced-comment /// // @ts-check /** * Thrown when there is an error importing a module. */ export class ImportError extends moduleGlobalThis.Error { name = 'ImportError'; } /** * ModulePrivate is the "private" object of every module. */ export 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 */ export class InternalModuleLoader { /** * @param {typeof globalThis} global the global object to handle module * resolution * @param {CompileFunc} 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; } /** * Determines whether a module gets access to import.meta.importSync(). * * @param {Uri} uri real URI of the module (file:/// or resource:///) */ isInternal(uri) { void uri; return false; } /** * Loads a file or resource URI synchronously * * @param {Uri} uri the file or resource URI to load * @returns {string} the contents of the module */ loadURI(uri) { if (uri.scheme === 'file' || uri.scheme === 'resource') return loadResourceOrFile(uri.uri); throw new ImportError(`Unsupported URI scheme for importing: ${uri.scheme ?? uri}`); } /** * Resolves an import specifier given an optional parent importer. * * @param {string} specifier the import specifier * @param {Uri | null} [parentURI] the URI of the module importing the specifier * @returns {Uri} */ resolveSpecifier(specifier, parentURI = null) { try { const uri = parseURI(specifier); if (uri) return uri; } catch { // 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 resolveRelativeResourceOrFile(parentURI.uriWithQuery, specifier); } throw new ImportError(`Module not found: ${specifier}`); } /** * 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 {Uri | null} importingModuleURI the URI of the module * triggering this resolve * * @returns {ResolvedModule} */ 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); module = registry.get(uri.uriWithQuery); // Check if module is already loaded (relative handling) if (module) return [module, '', '']; const text = this.loadURI(uri); const internal = this.isInternal(uri); const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); const compiled = this.compileModule(priv, text); registry.set(uri.uriWithQuery, compiled); return [compiled, text, uri.uri]; } /** * Called by SpiderMonkey as part of gjs_module_resolve(). * * @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 {Module} */ moduleResolveHook(importingModulePriv, specifier) { const importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; const [resolved] = this.resolveModule(specifier, importingModuleURI); return resolved; } /** * Called by SpiderMonkey as part of gjs_module_load(). * * @param {string} id - the module specifier * @param {string} uri - the URI where the module is to be found * @returns {Module} */ moduleLoadHook(id, uri) { const priv = new ModulePrivate(id, uri); const text = this.loadURI(parseURI(uri)); const compiled = this.compileModule(priv, text); const registry = getRegistry(this.global); registry.set(id, compiled); return compiled; } } export const internalModuleLoader = new InternalModuleLoader(globalThis, compileInternalModule); setGlobalModuleLoader(globalThis, internalModuleLoader); cjs-140.0/modules/internal/loader.js0000664000175000017500000002346715167114161016330 0ustar fabiofabio// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Evan Welsh // eslint-disable-next-line spaced-comment /// // @ts-check import {ImportError, InternalModuleLoader, ModulePrivate} from './internalLoader.js'; import {extractUrl} from './source-map/extractUrl.js'; import {SourceMapConsumer} from './source-map/source-map-consumer.js'; const DATA_URI_PREFIX = 'data:application/json;base64,'; 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; } /** * Overrides InternalModuleLoader.isInternal * * @param {Uri} uri real URI of the module (file:/// or resource:///) */ isInternal(uri) { const s = uri.uri; for (const internalURIPattern of this.moduleURIs) { const [start, end] = internalURIPattern.split('*'); if (s.startsWith(start) && s.endsWith(end)) return true; } return false; } /** * @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 * @returns {string} */ loadURI(uri) { if (uri.scheme) { const loader = this.schemeHandlers.get(uri.scheme); if (loader) return loader.load(uri); } return super.loadURI(uri); } /** * Overrides InternalModuleLoader.resolveSpecifier. Adds the behaviour of * resolving a bare specifier like 'system' against internal resources. * * @param {string} specifier the import specifier * @param {Uri | null} [parentURI] the URI of the module importing the * specifier * @returns {Uri} */ resolveSpecifier(specifier, parentURI = null) { try { return super.resolveSpecifier(specifier, parentURI); } catch (error) { // On failure due to bare specifiers, try resolving the bare // specifier to an internal module if (!specifier.startsWith('.')) { const realURI = this.buildInternalURIs(specifier).find(uriExists); if (realURI) return parseURI(realURI); } // Re-throw other errors throw error; } } /** * Populates the source map registry of a given module * Extracts the source map URL from the given code, parses the source map and build the SourceMapConsumer * This function will fail gracefully and not throw * * @param {string} text The JS code of the module * @param {string} uri The URI of the module or file with the sourceMappingURL definition * @param {string} [absoluteUri] The Absolute URI of the file containing the * sourceMappingURL definition. This is only used for non-module files. */ populateSourceMap(text, uri, absoluteUri) { if (!text) return; const sourceMapUrl = extractUrl(text); if (!sourceMapUrl) return; let jsonText = null; try { // check if we have an inlined data uri if (sourceMapUrl?.startsWith(DATA_URI_PREFIX)) { jsonText = atob(sourceMapUrl.substring(DATA_URI_PREFIX.length)); } else { // load the source map resource or file // resolve the source map file relative to the source file const sourceMapUri = resolveRelativeResourceOrFile(absoluteUri ?? uri, `./${sourceMapUrl}`); jsonText = this.loadURI(sourceMapUri); } } catch {} if (jsonText) { try { const sourceMapRegistry = getSourceMapRegistry(this.global); const sourceMap = JSON.parse(jsonText); const consumer = new SourceMapConsumer(sourceMap); sourceMapRegistry.set(uri, consumer); } catch {} } } /** * 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 {Module} */ moduleResolveHook(importingModulePriv, specifier) { const importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; const [module, text, uri] = this.resolveModule(specifier, importingModuleURI); this.populateSourceMap(text, uri); return module; } /** * Overrides InternalModuleLoader.moduleLoadHook * * @param {string} id - the module specifier * @param {string} uri - the URI where the module is to be found * @returns {Module} */ moduleLoadHook(id, uri) { const text = this.loadURI(parseURI(uri)); this.populateSourceMap(text, uri); return super.moduleLoadHook(id, uri); } /** * 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 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 importingModuleURI = importingModulePriv ? parseURI(importingModulePriv.uri) : null; const uri = this.resolveSpecifier(specifier, importingModuleURI); module = registry.get(uri.uriWithQuery); // Check if module is already loaded (relative handling) if (module) return module; const text = await this.loadURIAsync(uri); // Check if module loaded while awaiting. module = registry.get(uri.uriWithQuery); if (module) return module; const internal = this.isInternal(uri); const priv = new ModulePrivate(uri.uriWithQuery, uri.uri, internal); const compiled = this.compileModule(priv, text); registry.set(uri.uriWithQuery, compiled); this.populateSourceMap(text, uri.uri); return compiled; } /** * Loads a file or resource URI asynchronously * * @param {Uri} uri the file or resource URI to load * @returns {Promise} */ 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') return loadResourceOrFileAsync(uri.uri); throw new ImportError(`Unsupported URI scheme for importing: ${uri.scheme ?? uri}`); } } 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); }, /** * @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 Promise.resolve(this.load(uri)); }, }); cjs-140.0/modules/internal/source-map/0000775000175000017500000000000015167114161016563 5ustar fabiofabiocjs-140.0/modules/internal/source-map/array-set.js0000664000175000017500000000650115167114161021032 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/array-set.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ import * as util from './util.js'; var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; /** * A data structure which is a combination of an array and a set. Adding a new * member is O(1), testing for membership is O(1), and finding the index of an * element is O(1). Removing elements from the set is not supported. Only * strings are supported for membership. */ export function ArraySet() { this._array = []; this._set = hasNativeMap ? new Map() : Object.create(null); } /** * Static method for creating ArraySet instances from an existing array. */ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { var set = new ArraySet(); for (var i = 0, len = aArray.length; i < len; i++) { set.add(aArray[i], aAllowDuplicates); } return set; }; /** * Return how many unique items are in this ArraySet. If duplicates have been * added, than those do not count towards the size. * * @returns Number */ ArraySet.prototype.size = function ArraySet_size() { return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length; }; /** * Add the given string to this set. * * @param String aStr */ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { var sStr = hasNativeMap ? aStr : util.toSetString(aStr); var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr); var idx = this._array.length; if (!isDuplicate || aAllowDuplicates) { this._array.push(aStr); } if (!isDuplicate) { if (hasNativeMap) { this._set.set(aStr, idx); } else { this._set[sStr] = idx; } } }; /** * Is the given string a member of this set? * * @param String aStr */ ArraySet.prototype.has = function ArraySet_has(aStr) { if (hasNativeMap) { return this._set.has(aStr); } else { var sStr = util.toSetString(aStr); return has.call(this._set, sStr); } }; /** * What is the index of the given string in the array? * * @param String aStr */ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { if (hasNativeMap) { var idx = this._set.get(aStr); if (idx >= 0) { return idx; } } else { var sStr = util.toSetString(aStr); if (has.call(this._set, sStr)) { return this._set[sStr]; } } throw new Error('"' + aStr + '" is not in the set.'); }; /** * What is the element at the given index? * * @param Number aIdx */ ArraySet.prototype.at = function ArraySet_at(aIdx) { if (aIdx >= 0 && aIdx < this._array.length) { return this._array[aIdx]; } throw new Error('No element indexed by ' + aIdx); }; /** * Returns the array representation of this set (which has the proper indices * indicated by indexOf). Note that this is a copy of the internal array used * for storing the members so that no one can mess with internal state. */ ArraySet.prototype.toArray = function ArraySet_toArray() { return this._array.slice(); }; cjs-140.0/modules/internal/source-map/base64-vlq.js0000664000175000017500000001154715167114161021015 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/base64-vlq.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause * * Based on the Base 64 VLQ implementation in Closure Compiler: * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java * * Copyright 2011 The Closure Compiler Authors. All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. 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 * OWNER 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. */ import * as base64 from './base64.js'; // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, // the next four bits are the actual value, and the 6th bit is the // continuation bit. The continuation bit tells us whether there are more // digits in this value following this digit. // // Continuation // | Sign // | | // V V // 101011 var VLQ_BASE_SHIFT = 5; // binary: 100000 var VLQ_BASE = 1 << VLQ_BASE_SHIFT; // binary: 011111 var VLQ_BASE_MASK = VLQ_BASE - 1; // binary: 100000 var VLQ_CONTINUATION_BIT = VLQ_BASE; /** * Converts from a two-complement value to a value where the sign bit is * placed in the least significant bit. For example, as decimals: * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) */ function toVLQSigned(aValue) { return aValue < 0 ? ((-aValue) << 1) + 1 : (aValue << 1) + 0; } /** * Converts to a two-complement value from a value where the sign bit is * placed in the least significant bit. For example, as decimals: * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 */ function fromVLQSigned(aValue) { var isNegative = (aValue & 1) === 1; var shifted = aValue >> 1; return isNegative ? -shifted : shifted; } /** * Returns the base 64 VLQ encoded value. */ function base64VLQ_encode(aValue) { var encoded = ""; var digit; var vlq = toVLQSigned(aValue); do { digit = vlq & VLQ_BASE_MASK; vlq >>>= VLQ_BASE_SHIFT; if (vlq > 0) { // There are still more digits in this value, so we must make sure the // continuation bit is marked. digit |= VLQ_CONTINUATION_BIT; } encoded += base64.encode(digit); } while (vlq > 0); return encoded; }; /** * Decodes the next base 64 VLQ value from the given string and returns the * value and the rest of the string via the out parameter. */ function base64VLQ_decode(aStr, aIndex, aOutParam) { var strLen = aStr.length; var result = 0; var shift = 0; var continuation, digit; do { if (aIndex >= strLen) { throw new Error("Expected more digits in base 64 VLQ value."); } digit = base64.decode(aStr.charCodeAt(aIndex++)); if (digit === -1) { throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); } continuation = !!(digit & VLQ_CONTINUATION_BIT); digit &= VLQ_BASE_MASK; result = result + (digit << shift); shift += VLQ_BASE_SHIFT; } while (continuation); aOutParam.value = fromVLQSigned(result); aOutParam.rest = aIndex; }; export { base64VLQ_decode as decode, base64VLQ_encode as encode } cjs-140.0/modules/internal/source-map/base64.js0000664000175000017500000000333515167114161020211 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/base64.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); /** * Encode an integer in the range of 0 to 63 to a single base 64 digit. */ function encode(number) { if (0 <= number && number < intToCharMap.length) { return intToCharMap[number]; } throw new TypeError("Must be between 0 and 63: " + number); }; /** * Decode a single base 64 character code digit to an integer. Returns -1 on * failure. */ function decode(charCode) { var bigA = 65; // 'A' var bigZ = 90; // 'Z' var littleA = 97; // 'a' var littleZ = 122; // 'z' var zero = 48; // '0' var nine = 57; // '9' var plus = 43; // '+' var slash = 47; // '/' var littleOffset = 26; var numberOffset = 52; // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ if (bigA <= charCode && charCode <= bigZ) { return (charCode - bigA); } // 26 - 51: abcdefghijklmnopqrstuvwxyz if (littleA <= charCode && charCode <= littleZ) { return (charCode - littleA + littleOffset); } // 52 - 61: 0123456789 if (zero <= charCode && charCode <= nine) { return (charCode - zero + numberOffset); } // 62: + if (charCode == plus) { return 62; } // 63: / if (charCode == slash) { return 63; } // Invalid base64 digit. return -1; }; export { decode, encode }; cjs-140.0/modules/internal/source-map/binary-search.js0000664000175000017500000001060315167114161021650 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/binary-search.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ const GREATEST_LOWER_BOUND = 1; const LEAST_UPPER_BOUND = 2; /** * Recursive implementation of binary search. * * @param aLow Indices here and lower do not contain the needle. * @param aHigh Indices here and higher do not contain the needle. * @param aNeedle The element being searched for. * @param aHaystack The non-empty array being searched. * @param aCompare Function which takes two elements and returns -1, 0, or 1. * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the * closest element that is smaller than or greater than the one we are * searching for, respectively, if the exact element cannot be found. */ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { // This function terminates when one of the following is true: // // 1. We find the exact element we are looking for. // // 2. We did not find the exact element, but we can return the index of // the next-closest element. // // 3. We did not find the exact element, and there is no next-closest // element than the one we are searching for, so we return -1. var mid = Math.floor((aHigh - aLow) / 2) + aLow; var cmp = aCompare(aNeedle, aHaystack[mid], true); if (cmp === 0) { // Found the element we are looking for. return mid; } else if (cmp > 0) { // Our needle is greater than aHaystack[mid]. if (aHigh - mid > 1) { // The element is in the upper half. return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); } // The exact needle element was not found in this haystack. Determine if // we are in termination case (3) or (2) and return the appropriate thing. if (aBias == LEAST_UPPER_BOUND) { return aHigh < aHaystack.length ? aHigh : -1; } else { return mid; } } else { // Our needle is less than aHaystack[mid]. if (mid - aLow > 1) { // The element is in the lower half. return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); } // we are in termination case (3) or (2) and return the appropriate thing. if (aBias == LEAST_UPPER_BOUND) { return mid; } else { return aLow < 0 ? -1 : aLow; } } } /** * This is an implementation of binary search which will always try and return * the index of the closest element if there is no exact hit. This is because * mappings between original and generated line/col pairs are single points, * and there is an implicit region between each of them, so a miss just means * that you aren't on the very start of a region. * * @param aNeedle The element you are looking for. * @param aHaystack The array that is being searched. * @param aCompare A function which takes the needle and an element in the * array and returns -1, 0, or 1 depending on whether the needle is less * than, equal to, or greater than the element, respectively. * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the * closest element that is smaller than or greater than the one we are * searching for, respectively, if the exact element cannot be found. * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. */ function search(aNeedle, aHaystack, aCompare, aBias) { if (aHaystack.length === 0) { return -1; } var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare, aBias || GREATEST_LOWER_BOUND); if (index < 0) { return -1; } // We have found either the exact element, or the next-closest element than // the one we are searching for. However, there may be more than one such // element. Make sure we always return the smallest of these. while (index - 1 >= 0) { if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { break; } --index; } return index; }; export { search, LEAST_UPPER_BOUND, GREATEST_LOWER_BOUND }; cjs-140.0/modules/internal/source-map/extractUrl.js0000664000175000017500000000274415167114161021265 0ustar fabiofabio// SPDX-License-Identifier: CC-BY-3.0 // SPDX-FileCopyrightText: 2024 Source Maps Task Group (TC39-TG4) // Reference implementation in Source Map V3 Standard 6.2.2.1 // @ts-nocheck export function extractUrl(source) { const JS_NEWLINE = /^/m; // This RegExp will always match one of the following: // - single-line comments // - "single-line" multi-line comments // - unclosed multi-line comments // - just trailing whitespaces // - a code character // The loop below differentiates between all these cases. const JS_COMMENT = /\s*(?:\/\/(?.*)|\/\*(?.*?)\*\/|\/\*.*|$|(?[^\/]+))/muy; const PATTERN = /^[@#]\s*sourceMappingURL=(\S*?)\s*$/; let lastURL = null; for (const line of source.split(JS_NEWLINE)) { JS_COMMENT.lastIndex = 0; while (JS_COMMENT.lastIndex < line.length) { const exec = JS_COMMENT.exec(line); if (!exec) break; // added let commentMatch = exec.groups; let comment = commentMatch.single ?? commentMatch.multi; if (comment != null) { let match = PATTERN.exec(comment); if (match !== null) lastURL = match[1]; } else if (commentMatch.code != null) { lastURL = null; } else { // We found either trailing whitespaces or an unclosed comment. // Assert: JS_COMMENT.lastIndex === line.length } } } return lastURL; } cjs-140.0/modules/internal/source-map/source-map-consumer.js0000664000175000017500000011726515167114161023041 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/source-map-consumer.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ import * as util from './util.js'; import * as binarySearch from './binary-search.js'; import { ArraySet } from './array-set.js'; import * as base64VLQ from './base64-vlq.js'; function SourceMapConsumer(aSourceMap, aSourceMapURL) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { sourceMap = util.parseSourceMapInput(aSourceMap); } return sourceMap.sections != null ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL) : new BasicSourceMapConsumer(sourceMap, aSourceMapURL); } SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) { return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL); } /** * The version of the source mapping spec that we are consuming. */ SourceMapConsumer.prototype._version = 3; // `__generatedMappings` and `__originalMappings` are arrays that hold the // parsed mapping coordinates from the source map's "mappings" attribute. They // are lazily instantiated, accessed via the `_generatedMappings` and // `_originalMappings` getters respectively, and we only parse the mappings // and create these arrays once queried for a source location. We jump through // these hoops because there can be many thousands of mappings, and parsing // them is expensive, so we only want to do it if we must. // // Each object in the arrays is of the form: // // { // generatedLine: The line number in the generated code, // generatedColumn: The column number in the generated code, // source: The path to the original source file that generated this // chunk of code, // originalLine: The line number in the original source that // corresponds to this chunk of generated code, // originalColumn: The column number in the original source that // corresponds to this chunk of generated code, // name: The name of the original symbol which generated this chunk of // code. // } // // All properties except for `generatedLine` and `generatedColumn` can be // `null`. // // `_generatedMappings` is ordered by the generated positions. // // `_originalMappings` is ordered by the original positions. SourceMapConsumer.prototype.__generatedMappings = null; Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { configurable: true, enumerable: true, get: function () { if (!this.__generatedMappings) { this._parseMappings(this._mappings, this.sourceRoot); } return this.__generatedMappings; } }); SourceMapConsumer.prototype.__originalMappings = null; Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { configurable: true, enumerable: true, get: function () { if (!this.__originalMappings) { this._parseMappings(this._mappings, this.sourceRoot); } return this.__originalMappings; } }); SourceMapConsumer.prototype._charIsMappingSeparator = function SourceMapConsumer_charIsMappingSeparator(aStr, index) { var c = aStr.charAt(index); return c === ";" || c === ","; }; /** * Parse the mappings in a string in to a data structure which we can easily * query (the ordered arrays in the `this.__generatedMappings` and * `this.__originalMappings` properties). */ SourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { throw new Error("Subclasses must implement _parseMappings"); }; SourceMapConsumer.GENERATED_ORDER = 1; SourceMapConsumer.ORIGINAL_ORDER = 2; SourceMapConsumer.GREATEST_LOWER_BOUND = 1; SourceMapConsumer.LEAST_UPPER_BOUND = 2; /** * Iterate over each mapping between an original source/line/column and a * generated line/column in this source map. * * @param Function aCallback * The function that is called with each mapping. * @param Object aContext * Optional. If specified, this object will be the value of `this` every * time that `aCallback` is called. * @param aOrder * Either `SourceMapConsumer.GENERATED_ORDER` or * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to * iterate over the mappings sorted by the generated file's line/column * order or the original's source/line/column order, respectively. Defaults to * `SourceMapConsumer.GENERATED_ORDER`. */ SourceMapConsumer.prototype.eachMapping = function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { var context = aContext || null; var order = aOrder || SourceMapConsumer.GENERATED_ORDER; var mappings; switch (order) { case SourceMapConsumer.GENERATED_ORDER: mappings = this._generatedMappings; break; case SourceMapConsumer.ORIGINAL_ORDER: mappings = this._originalMappings; break; default: throw new Error("Unknown order of iteration."); } var sourceRoot = this.sourceRoot; mappings.map(function (mapping) { var source = mapping.source === null ? null : this._sources.at(mapping.source); source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL); return { source: source, generatedLine: mapping.generatedLine, generatedColumn: mapping.generatedColumn, originalLine: mapping.originalLine, originalColumn: mapping.originalColumn, name: mapping.name === null ? null : this._names.at(mapping.name) }; }, this).forEach(aCallback, context); }; /** * Returns all generated line and column information for the original source, * line, and column provided. If no column is provided, returns all mappings * corresponding to a either the line we are searching for or the next * closest line that has any mappings. Otherwise, returns all mappings * corresponding to the given line and either the column we are searching for * or the next closest column that has any offsets. * * The only argument is an object with the following properties: * * - source: The filename of the original source. * - line: The line number in the original source. The line number is 1-based. * - column: Optional. the column number in the original source. * The column number is 0-based. * * and an array of objects is returned, each with the following properties: * * - line: The line number in the generated source, or null. The * line number is 1-based. * - column: The column number in the generated source, or null. * The column number is 0-based. */ SourceMapConsumer.prototype.allGeneratedPositionsFor = function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { var line = util.getArg(aArgs, 'line'); // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping // returns the index of the closest mapping less than the needle. By // setting needle.originalColumn to 0, we thus find the last mapping for // the given line, provided such a mapping exists. var needle = { source: util.getArg(aArgs, 'source'), originalLine: line, originalColumn: util.getArg(aArgs, 'column', 0) }; needle.source = this._findSourceIndex(needle.source); if (needle.source < 0) { return []; } var mappings = []; var index = this._findMapping(needle, this._originalMappings, "originalLine", "originalColumn", util.compareByOriginalPositions, binarySearch.LEAST_UPPER_BOUND); if (index >= 0) { var mapping = this._originalMappings[index]; if (aArgs.column === undefined) { var originalLine = mapping.originalLine; // Iterate until either we run out of mappings, or we run into // a mapping for a different line than the one we found. Since // mappings are sorted, this is guaranteed to find all mappings for // the line we found. while (mapping && mapping.originalLine === originalLine) { mappings.push({ line: util.getArg(mapping, 'generatedLine', null), column: util.getArg(mapping, 'generatedColumn', null), lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) }); mapping = this._originalMappings[++index]; } } else { var originalColumn = mapping.originalColumn; // Iterate until either we run out of mappings, or we run into // a mapping for a different line than the one we were searching for. // Since mappings are sorted, this is guaranteed to find all mappings for // the line we are searching for. while (mapping && mapping.originalLine === line && mapping.originalColumn == originalColumn) { mappings.push({ line: util.getArg(mapping, 'generatedLine', null), column: util.getArg(mapping, 'generatedColumn', null), lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) }); mapping = this._originalMappings[++index]; } } } return mappings; }; /** * A BasicSourceMapConsumer instance represents a parsed source map which we can * query for information about the original file positions by giving it a file * position in the generated source. * * The first parameter is the raw source map (either as a JSON string, or * already parsed to an object). According to the spec, source maps have the * following attributes: * * - version: Which version of the source map spec this map is following. * - sources: An array of URLs to the original source files. * - names: An array of identifiers which can be referrenced by individual mappings. * - sourceRoot: Optional. The URL root from which all sources are relative. * - sourcesContent: Optional. An array of contents of the original source files. * - mappings: A string of base64 VLQs which contain the actual mappings. * - file: Optional. The generated file this source map is associated with. * * Here is an example source map, taken from the source map spec[0]: * * { * version : 3, * file: "out.js", * sourceRoot : "", * sources: ["foo.js", "bar.js"], * names: ["src", "maps", "are", "fun"], * mappings: "AA,AB;;ABCDE;" * } * * The second parameter, if given, is a string whose value is the URL * at which the source map was found. This URL is used to compute the * sources array. * * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# */ function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { sourceMap = util.parseSourceMapInput(aSourceMap); } var version = util.getArg(sourceMap, 'version'); var sources = util.getArg(sourceMap, 'sources'); // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which // requires the array) to play nice here. var names = util.getArg(sourceMap, 'names', []); var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); var mappings = util.getArg(sourceMap, 'mappings'); var file = util.getArg(sourceMap, 'file', null); // Once again, Sass deviates from the spec and supplies the version as a // string rather than a number, so we use loose equality checking here. if (version != this._version) { throw new Error('Unsupported version: ' + version); } if (sourceRoot) { sourceRoot = util.normalize(sourceRoot); } sources = sources .map(String) // Some source maps produce relative source paths like "./foo.js" instead of // "foo.js". Normalize these first so that future comparisons will succeed. // See bugzil.la/1090768. .map(util.normalize) // Always ensure that absolute sources are internally stored relative to // the source root, if the source root is absolute. Not doing this would // be particularly problematic when the source root is a prefix of the // source (valid, but why??). See github issue #199 and bugzil.la/1188982. .map(function (source) { return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) ? util.relative(sourceRoot, source) : source; }); // Pass `true` below to allow duplicate names and sources. While source maps // are intended to be compressed and deduplicated, the TypeScript compiler // sometimes generates source maps with duplicates in them. See Github issue // #72 and bugzil.la/889492. this._names = ArraySet.fromArray(names.map(String), true); this._sources = ArraySet.fromArray(sources, true); this._absoluteSources = this._sources.toArray().map(function (s) { return util.computeSourceURL(sourceRoot, s, aSourceMapURL); }); this.sourceRoot = sourceRoot; this.sourcesContent = sourcesContent; this._mappings = mappings; this._sourceMapURL = aSourceMapURL; this.file = file; } BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; /** * Utility function to find the index of a source. Returns -1 if not * found. */ BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) { var relativeSource = aSource; if (this.sourceRoot != null) { relativeSource = util.relative(this.sourceRoot, relativeSource); } if (this._sources.has(relativeSource)) { return this._sources.indexOf(relativeSource); } // Maybe aSource is an absolute URL as returned by |sources|. In // this case we can't simply undo the transform. var i; for (i = 0; i < this._absoluteSources.length; ++i) { if (this._absoluteSources[i] == aSource) { return i; } } return -1; }; /** * Create a BasicSourceMapConsumer from a SourceMapGenerator. * * @param SourceMapGenerator aSourceMap * The source map that will be consumed. * @param String aSourceMapURL * The URL at which the source map can be found (optional) * @returns BasicSourceMapConsumer */ BasicSourceMapConsumer.fromSourceMap = function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) { var smc = Object.create(BasicSourceMapConsumer.prototype); var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); smc.sourceRoot = aSourceMap._sourceRoot; smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), smc.sourceRoot); smc.file = aSourceMap._file; smc._sourceMapURL = aSourceMapURL; smc._absoluteSources = smc._sources.toArray().map(function (s) { return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL); }); // Because we are modifying the entries (by converting string sources and // names to indices into the sources and names ArraySets), we have to make // a copy of the entry or else bad things happen. Shared mutable state // strikes again! See github issue #191. var generatedMappings = aSourceMap._mappings.toArray().slice(); var destGeneratedMappings = smc.__generatedMappings = []; var destOriginalMappings = smc.__originalMappings = []; for (var i = 0, length = generatedMappings.length; i < length; i++) { var srcMapping = generatedMappings[i]; var destMapping = new Mapping; destMapping.generatedLine = srcMapping.generatedLine; destMapping.generatedColumn = srcMapping.generatedColumn; if (srcMapping.source) { destMapping.source = sources.indexOf(srcMapping.source); destMapping.originalLine = srcMapping.originalLine; destMapping.originalColumn = srcMapping.originalColumn; if (srcMapping.name) { destMapping.name = names.indexOf(srcMapping.name); } destOriginalMappings.push(destMapping); } destGeneratedMappings.push(destMapping); } smc.__originalMappings.sort(util.compareByOriginalPositions); return smc; }; /** * The version of the source mapping spec that we are consuming. */ BasicSourceMapConsumer.prototype._version = 3; /** * The list of original sources. */ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { get: function () { return this._absoluteSources.slice(); } }); /** * Provide the JIT with a nice shape / hidden class. */ function Mapping() { this.generatedLine = 0; this.generatedColumn = 0; this.source = null; this.originalLine = null; this.originalColumn = null; this.name = null; } /** * Parse the mappings in a string in to a data structure which we can easily * query (the ordered arrays in the `this.__generatedMappings` and * `this.__originalMappings` properties). */ BasicSourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { var generatedLine = 1; var previousGeneratedColumn = 0; var previousOriginalLine = 0; var previousOriginalColumn = 0; var previousSource = 0; var previousName = 0; var length = aStr.length; var index = 0; var cachedSegments = {}; var temp = {}; var originalMappings = []; var generatedMappings = []; var mapping, str, segment, end, value; while (index < length) { if (aStr.charAt(index) === ';') { generatedLine++; index++; previousGeneratedColumn = 0; } else if (aStr.charAt(index) === ',') { index++; } else { mapping = new Mapping(); mapping.generatedLine = generatedLine; // Because each offset is encoded relative to the previous one, // many segments often have the same encoding. We can exploit this // fact by caching the parsed variable length fields of each segment, // allowing us to avoid a second parse if we encounter the same // segment again. for (end = index; end < length; end++) { if (this._charIsMappingSeparator(aStr, end)) { break; } } str = aStr.slice(index, end); segment = cachedSegments[str]; if (segment) { index += str.length; } else { segment = []; while (index < end) { base64VLQ.decode(aStr, index, temp); value = temp.value; index = temp.rest; segment.push(value); } if (segment.length === 2) { throw new Error('Found a source, but no line and column'); } if (segment.length === 3) { throw new Error('Found a source and line, but no column'); } cachedSegments[str] = segment; } // Generated column. mapping.generatedColumn = previousGeneratedColumn + segment[0]; previousGeneratedColumn = mapping.generatedColumn; if (segment.length > 1) { // Original source. mapping.source = previousSource + segment[1]; previousSource += segment[1]; // Original line. mapping.originalLine = previousOriginalLine + segment[2]; previousOriginalLine = mapping.originalLine; // Lines are stored 0-based mapping.originalLine += 1; // Original column. mapping.originalColumn = previousOriginalColumn + segment[3]; previousOriginalColumn = mapping.originalColumn; if (segment.length > 4) { // Original name. mapping.name = previousName + segment[4]; previousName += segment[4]; } } generatedMappings.push(mapping); if (typeof mapping.originalLine === 'number') { originalMappings.push(mapping); } } } generatedMappings.sort(util.compareByGeneratedPositionsDeflated); this.__generatedMappings = generatedMappings; originalMappings.sort(util.compareByOriginalPositions); this.__originalMappings = originalMappings; }; /** * Find the mapping that best matches the hypothetical "needle" mapping that * we are searching for in the given "haystack" of mappings. */ BasicSourceMapConsumer.prototype._findMapping = function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, aColumnName, aComparator, aBias) { // To return the position we are searching for, we must first find the // mapping for the given position and then return the opposite position it // points to. Because the mappings are sorted, we can use binary search to // find the best mapping. if (aNeedle[aLineName] <= 0) { throw new TypeError('Line must be greater than or equal to 1, got ' + aNeedle[aLineName]); } if (aNeedle[aColumnName] < 0) { throw new TypeError('Column must be greater than or equal to 0, got ' + aNeedle[aColumnName]); } return binarySearch.search(aNeedle, aMappings, aComparator, aBias); }; /** * Compute the last column for each generated mapping. The last column is * inclusive. */ BasicSourceMapConsumer.prototype.computeColumnSpans = function SourceMapConsumer_computeColumnSpans() { for (var index = 0; index < this._generatedMappings.length; ++index) { var mapping = this._generatedMappings[index]; // Mappings do not contain a field for the last generated columnt. We // can come up with an optimistic estimate, however, by assuming that // mappings are contiguous (i.e. given two consecutive mappings, the // first mapping ends where the second one starts). if (index + 1 < this._generatedMappings.length) { var nextMapping = this._generatedMappings[index + 1]; if (mapping.generatedLine === nextMapping.generatedLine) { mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; continue; } } // The last mapping for each line spans the entire line. mapping.lastGeneratedColumn = Infinity; } }; /** * Returns the original source, line, and column information for the generated * source's line and column positions provided. The only argument is an object * with the following properties: * * - line: The line number in the generated source. The line number * is 1-based. * - column: The column number in the generated source. The column * number is 0-based. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the * closest element that is smaller than or greater than the one we are * searching for, respectively, if the exact element cannot be found. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. * * and an object is returned with the following properties: * * - source: The original source file, or null. * - line: The line number in the original source, or null. The * line number is 1-based. * - column: The column number in the original source, or null. The * column number is 0-based. * - name: The original identifier, or null. */ BasicSourceMapConsumer.prototype.originalPositionFor = function SourceMapConsumer_originalPositionFor(aArgs) { var needle = { generatedLine: util.getArg(aArgs, 'line'), generatedColumn: util.getArg(aArgs, 'column') }; var index = this._findMapping( needle, this._generatedMappings, "generatedLine", "generatedColumn", util.compareByGeneratedPositionsDeflated, util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) ); if (index >= 0) { var mapping = this._generatedMappings[index]; if (mapping.generatedLine === needle.generatedLine) { var source = util.getArg(mapping, 'source', null); if (source !== null) { source = this._sources.at(source); source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL); } var name = util.getArg(mapping, 'name', null); if (name !== null) { name = this._names.at(name); } return { source: source, line: util.getArg(mapping, 'originalLine', null), column: util.getArg(mapping, 'originalColumn', null), name: name }; } } return { source: null, line: null, column: null, name: null }; }; /** * Return true if we have the source content for every source in the source * map, false otherwise. */ BasicSourceMapConsumer.prototype.hasContentsOfAllSources = function BasicSourceMapConsumer_hasContentsOfAllSources() { if (!this.sourcesContent) { return false; } return this.sourcesContent.length >= this._sources.size() && !this.sourcesContent.some(function (sc) { return sc == null; }); }; /** * Returns the original source content. The only argument is the url of the * original source file. Returns null if no original source content is * available. */ BasicSourceMapConsumer.prototype.sourceContentFor = function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { if (!this.sourcesContent) { return null; } var index = this._findSourceIndex(aSource); if (index >= 0) { return this.sourcesContent[index]; } var relativeSource = aSource; if (this.sourceRoot != null) { relativeSource = util.relative(this.sourceRoot, relativeSource); } var url; if (this.sourceRoot != null && (url = util.urlParse(this.sourceRoot))) { // XXX: file:// URIs and absolute paths lead to unexpected behavior for // many users. We can help them out when they expect file:// URIs to // behave like it would if they were running a local HTTP server. See // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. var fileUriAbsPath = relativeSource.replace(/^file:\/\//, ""); if (url.scheme == "file" && this._sources.has(fileUriAbsPath)) { return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] } if ((!url.path || url.path == "/") && this._sources.has("/" + relativeSource)) { return this.sourcesContent[this._sources.indexOf("/" + relativeSource)]; } } // This function is used recursively from // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we // don't want to throw if we can't find the source - we just want to // return null, so we provide a flag to exit gracefully. if (nullOnMissing) { return null; } else { throw new Error('"' + relativeSource + '" is not in the SourceMap.'); } }; /** * Returns the generated line and column information for the original source, * line, and column positions provided. The only argument is an object with * the following properties: * * - source: The filename of the original source. * - line: The line number in the original source. The line number * is 1-based. * - column: The column number in the original source. The column * number is 0-based. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the * closest element that is smaller than or greater than the one we are * searching for, respectively, if the exact element cannot be found. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. * * and an object is returned with the following properties: * * - line: The line number in the generated source, or null. The * line number is 1-based. * - column: The column number in the generated source, or null. * The column number is 0-based. */ BasicSourceMapConsumer.prototype.generatedPositionFor = function SourceMapConsumer_generatedPositionFor(aArgs) { var source = util.getArg(aArgs, 'source'); source = this._findSourceIndex(source); if (source < 0) { return { line: null, column: null, lastColumn: null }; } var needle = { source: source, originalLine: util.getArg(aArgs, 'line'), originalColumn: util.getArg(aArgs, 'column') }; var index = this._findMapping( needle, this._originalMappings, "originalLine", "originalColumn", util.compareByOriginalPositions, util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) ); if (index >= 0) { var mapping = this._originalMappings[index]; if (mapping.source === needle.source) { return { line: util.getArg(mapping, 'generatedLine', null), column: util.getArg(mapping, 'generatedColumn', null), lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) }; } } return { line: null, column: null, lastColumn: null }; }; /** * An IndexedSourceMapConsumer instance represents a parsed source map which * we can query for information. It differs from BasicSourceMapConsumer in * that it takes "indexed" source maps (i.e. ones with a "sections" field) as * input. * * The first parameter is a raw source map (either as a JSON string, or already * parsed to an object). According to the spec for indexed source maps, they * have the following attributes: * * - version: Which version of the source map spec this map is following. * - file: Optional. The generated file this source map is associated with. * - sections: A list of section definitions. * * Each value under the "sections" field has two fields: * - offset: The offset into the original specified at which this section * begins to apply, defined as an object with a "line" and "column" * field. * - map: A source map definition. This source map could also be indexed, * but doesn't have to be. * * Instead of the "map" field, it's also possible to have a "url" field * specifying a URL to retrieve a source map from, but that's currently * unsupported. * * Here's an example source map, taken from the source map spec[0], but * modified to omit a section which uses the "url" field. * * { * version : 3, * file: "app.js", * sections: [{ * offset: {line:100, column:10}, * map: { * version : 3, * file: "section.js", * sources: ["foo.js", "bar.js"], * names: ["src", "maps", "are", "fun"], * mappings: "AAAA,E;;ABCDE;" * } * }], * } * * The second parameter, if given, is a string whose value is the URL * at which the source map was found. This URL is used to compute the * sources array. * * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt */ function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { sourceMap = util.parseSourceMapInput(aSourceMap); } var version = util.getArg(sourceMap, 'version'); var sections = util.getArg(sourceMap, 'sections'); if (version != this._version) { throw new Error('Unsupported version: ' + version); } this._sources = new ArraySet(); this._names = new ArraySet(); var lastOffset = { line: -1, column: 0 }; this._sections = sections.map(function (s) { if (s.url) { // The url field will require support for asynchronicity. // See https://github.com/mozilla/source-map/issues/16 throw new Error('Support for url field in sections not implemented.'); } var offset = util.getArg(s, 'offset'); var offsetLine = util.getArg(offset, 'line'); var offsetColumn = util.getArg(offset, 'column'); if (offsetLine < lastOffset.line || (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { throw new Error('Section offsets must be ordered and non-overlapping.'); } lastOffset = offset; return { generatedOffset: { // The offset fields are 0-based, but we use 1-based indices when // encoding/decoding from VLQ. generatedLine: offsetLine + 1, generatedColumn: offsetColumn + 1 }, consumer: new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL) } }); } IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; /** * The version of the source mapping spec that we are consuming. */ IndexedSourceMapConsumer.prototype._version = 3; /** * The list of original sources. */ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { get: function () { var sources = []; for (var i = 0; i < this._sections.length; i++) { for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { sources.push(this._sections[i].consumer.sources[j]); } } return sources; } }); /** * Returns the original source, line, and column information for the generated * source's line and column positions provided. The only argument is an object * with the following properties: * * - line: The line number in the generated source. The line number * is 1-based. * - column: The column number in the generated source. The column * number is 0-based. * * and an object is returned with the following properties: * * - source: The original source file, or null. * - line: The line number in the original source, or null. The * line number is 1-based. * - column: The column number in the original source, or null. The * column number is 0-based. * - name: The original identifier, or null. */ IndexedSourceMapConsumer.prototype.originalPositionFor = function IndexedSourceMapConsumer_originalPositionFor(aArgs) { var needle = { generatedLine: util.getArg(aArgs, 'line'), generatedColumn: util.getArg(aArgs, 'column') }; // Find the section containing the generated position we're trying to map // to an original position. var sectionIndex = binarySearch.search(needle, this._sections, function(needle, section) { var cmp = needle.generatedLine - section.generatedOffset.generatedLine; if (cmp) { return cmp; } return (needle.generatedColumn - section.generatedOffset.generatedColumn); }); var section = this._sections[sectionIndex]; if (!section) { return { source: null, line: null, column: null, name: null }; } return section.consumer.originalPositionFor({ line: needle.generatedLine - (section.generatedOffset.generatedLine - 1), column: needle.generatedColumn - (section.generatedOffset.generatedLine === needle.generatedLine ? section.generatedOffset.generatedColumn - 1 : 0), bias: aArgs.bias }); }; /** * Return true if we have the source content for every source in the source * map, false otherwise. */ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = function IndexedSourceMapConsumer_hasContentsOfAllSources() { return this._sections.every(function (s) { return s.consumer.hasContentsOfAllSources(); }); }; /** * Returns the original source content. The only argument is the url of the * original source file. Returns null if no original source content is * available. */ IndexedSourceMapConsumer.prototype.sourceContentFor = function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { for (var i = 0; i < this._sections.length; i++) { var section = this._sections[i]; var content = section.consumer.sourceContentFor(aSource, true); if (content) { return content; } } if (nullOnMissing) { return null; } else { throw new Error('"' + aSource + '" is not in the SourceMap.'); } }; /** * Returns the generated line and column information for the original source, * line, and column positions provided. The only argument is an object with * the following properties: * * - source: The filename of the original source. * - line: The line number in the original source. The line number * is 1-based. * - column: The column number in the original source. The column * number is 0-based. * * and an object is returned with the following properties: * * - line: The line number in the generated source, or null. The * line number is 1-based. * - column: The column number in the generated source, or null. * The column number is 0-based. */ IndexedSourceMapConsumer.prototype.generatedPositionFor = function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { for (var i = 0; i < this._sections.length; i++) { var section = this._sections[i]; // Only consider this section if the requested source is in the list of // sources of the consumer. if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) { continue; } var generatedPosition = section.consumer.generatedPositionFor(aArgs); if (generatedPosition) { var ret = { line: generatedPosition.line + (section.generatedOffset.generatedLine - 1), column: generatedPosition.column + (section.generatedOffset.generatedLine === generatedPosition.line ? section.generatedOffset.generatedColumn - 1 : 0) }; return ret; } } return { line: null, column: null }; }; /** * Parse the mappings in a string in to a data structure which we can easily * query (the ordered arrays in the `this.__generatedMappings` and * `this.__originalMappings` properties). */ IndexedSourceMapConsumer.prototype._parseMappings = function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { this.__generatedMappings = []; this.__originalMappings = []; for (var i = 0; i < this._sections.length; i++) { var section = this._sections[i]; var sectionMappings = section.consumer._generatedMappings; for (var j = 0; j < sectionMappings.length; j++) { var mapping = sectionMappings[j]; var source = section.consumer._sources.at(mapping.source); source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL); this._sources.add(source); source = this._sources.indexOf(source); var name = null; if (mapping.name) { name = section.consumer._names.at(mapping.name); this._names.add(name); name = this._names.indexOf(name); } // The mappings coming from the consumer for the section have // generated positions relative to the start of the section, so we // need to offset them to be relative to the start of the concatenated // generated file. var adjustedMapping = { source: source, generatedLine: mapping.generatedLine + (section.generatedOffset.generatedLine - 1), generatedColumn: mapping.generatedColumn + (section.generatedOffset.generatedLine === mapping.generatedLine ? section.generatedOffset.generatedColumn - 1 : 0), originalLine: mapping.originalLine, originalColumn: mapping.originalColumn, name: name }; this.__generatedMappings.push(adjustedMapping); if (typeof adjustedMapping.originalLine === 'number') { this.__originalMappings.push(adjustedMapping); } } } this.__generatedMappings.sort(util.compareByGeneratedPositionsDeflated); this.__originalMappings.sort(util.compareByOriginalPositions); }; export { BasicSourceMapConsumer, IndexedSourceMapConsumer, SourceMapConsumer }cjs-140.0/modules/internal/source-map/util.js0000664000175000017500000003105115167114161020076 0ustar fabiofabio/* -*- Mode: js; js-indent-level: 2; -*- */ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: Copyright 2011 Mozilla Foundation and contributors // Upstream https://github.com/mozilla/source-map/blob/master/lib/util.js // @ts-nocheck /* * Copyright 2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ /** * This is a helper function for getting values from parameter/options * objects. * * @param args The object we are extracting values from * @param name The name of the property we are getting. * @param defaultValue An optional value to return if the property is missing * from the object. If this is not specified and the property is missing, an * error will be thrown. */ export function getArg(aArgs, aName, aDefaultValue) { if (aName in aArgs) { return aArgs[aName]; } else if (arguments.length === 3) { return aDefaultValue; } else { throw new Error('"' + aName + '" is a required argument.'); } } var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/; var dataUrlRegexp = /^data:.+\,.+$/; export function urlParse(aUrl) { var match = aUrl.match(urlRegexp); if (!match) { return null; } return { scheme: match[1], auth: match[2], host: match[3], port: match[4], path: match[5] }; } export function urlGenerate(aParsedUrl) { var url = ''; if (aParsedUrl.scheme) { url += aParsedUrl.scheme + ':'; } url += '//'; if (aParsedUrl.auth) { url += aParsedUrl.auth + '@'; } if (aParsedUrl.host) { url += aParsedUrl.host; } if (aParsedUrl.port) { url += ":" + aParsedUrl.port } if (aParsedUrl.path) { url += aParsedUrl.path; } return url; } export function isAbsolute(aPath) { return aPath.charAt(0) === '/' || urlRegexp.test(aPath); }; /** * Normalizes a path, or the path portion of a URL: * * - Replaces consecutive slashes with one slash. * - Removes unnecessary '.' parts. * - Removes unnecessary '/..' parts. * * Based on code in the Node.js 'path' core module. * * @param aPath The path or url to normalize. */ export function normalize(aPath) { var isAbsolute2 = (aPath) => { return aPath.charAt(0) === '/' || urlRegexp.test(aPath); }; var path = aPath; var url = urlParse(aPath); if (url) { if (!url.path) { return aPath; } path = url.path; } var isAbsolute = isAbsolute2(path); var parts = path.split(/\/+/); for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { part = parts[i]; if (part === '.') { parts.splice(i, 1); } else if (part === '..') { up++; } else if (up > 0) { if (part === '') { // The first part is blank if the path is absolute. Trying to go // above the root is a no-op. Therefore we can remove all '..' parts // directly after the root. parts.splice(i + 1, up); up = 0; } else { parts.splice(i, 2); up--; } } } path = parts.join('/'); if (path === '') { path = isAbsolute ? '/' : '.'; } if (url) { url.path = path; return urlGenerate(url); } return path; } /** * Joins two paths/URLs. * * @param aRoot The root path or URL. * @param aPath The path or URL to be joined with the root. * * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a * scheme-relative URL: Then the scheme of aRoot, if any, is prepended * first. * - Otherwise aPath is a path. If aRoot is a URL, then its path portion * is updated with the result and aRoot is returned. Otherwise the result * is returned. * - If aPath is absolute, the result is aPath. * - Otherwise the two paths are joined with a slash. * - Joining for example 'http://' and 'www.example.com' is also supported. */ export function join(aRoot, aPath) { if (aRoot === "") { aRoot = "."; } if (aPath === "") { aPath = "."; } var aPathUrl = urlParse(aPath); var aRootUrl = urlParse(aRoot); if (aRootUrl) { aRoot = aRootUrl.path || '/'; } // `join(foo, '//www.example.org')` if (aPathUrl && !aPathUrl.scheme) { if (aRootUrl) { aPathUrl.scheme = aRootUrl.scheme; } return urlGenerate(aPathUrl); } if (aPathUrl || aPath.match(dataUrlRegexp)) { return aPath; } // `join('http://', 'www.example.com')` if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { aRootUrl.host = aPath; return urlGenerate(aRootUrl); } var joined = aPath.charAt(0) === '/' ? aPath : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); if (aRootUrl) { aRootUrl.path = joined; return urlGenerate(aRootUrl); } return joined; } /** * Make a path relative to a URL or another path. * * @param aRoot The root path or URL. * @param aPath The path or URL to be made relative to aRoot. */ export function relative(aRoot, aPath) { if (aRoot === "") { aRoot = "."; } aRoot = aRoot.replace(/\/$/, ''); // It is possible for the path to be above the root. In this case, simply // checking whether the root is a prefix of the path won't work. Instead, we // need to remove components from the root one by one, until either we find // a prefix that fits, or we run out of components to remove. var level = 0; while (aPath.indexOf(aRoot + '/') !== 0) { var index = aRoot.lastIndexOf("/"); if (index < 0) { return aPath; } // If the only part of the root that is left is the scheme (i.e. http://, // file:///, etc.), one or more slashes (/), or simply nothing at all, we // have exhausted all components, so the path is not relative to the root. aRoot = aRoot.slice(0, index); if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { return aPath; } ++level; } // Make sure we add a "../" for each component we removed from the root. return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); } var supportsNullProto = (function () { var obj = Object.create(null); return !('__proto__' in obj); }()); function identity (s) { return s; } /** * Because behavior goes wacky when you set `__proto__` on objects, we * have to prefix all the strings in our set with an arbitrary character. * * See https://github.com/mozilla/source-map/pull/31 and * https://github.com/mozilla/source-map/issues/30 * * @param String aStr */ function toSetString2(aStr) { if (isProtoString(aStr)) { return '$' + aStr; } return aStr; } export const toSetString = supportsNullProto ? identity : toSetString2; function fromSetString2(aStr) { if (isProtoString(aStr)) { return aStr.slice(1); } return aStr; } export const fromSetString = supportsNullProto ? identity : fromSetString2; function isProtoString(s) { if (!s) { return false; } var length = s.length; if (length < 9 /* "__proto__".length */) { return false; } if (s.charCodeAt(length - 1) !== 95 /* '_' */ || s.charCodeAt(length - 2) !== 95 /* '_' */ || s.charCodeAt(length - 3) !== 111 /* 'o' */ || s.charCodeAt(length - 4) !== 116 /* 't' */ || s.charCodeAt(length - 5) !== 111 /* 'o' */ || s.charCodeAt(length - 6) !== 114 /* 'r' */ || s.charCodeAt(length - 7) !== 112 /* 'p' */ || s.charCodeAt(length - 8) !== 95 /* '_' */ || s.charCodeAt(length - 9) !== 95 /* '_' */) { return false; } for (var i = length - 10; i >= 0; i--) { if (s.charCodeAt(i) !== 36 /* '$' */) { return false; } } return true; } /** * Comparator between two mappings where the original positions are compared. * * Optionally pass in `true` as `onlyCompareGenerated` to consider two * mappings with the same original source/line/column, but different generated * line and column the same. Useful when searching for a mapping with a * stubbed out mapping. */ export function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { var cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; } cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; } cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0 || onlyCompareOriginal) { return cmp; } cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; } cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; } return strcmp(mappingA.name, mappingB.name); } /** * Comparator between two mappings with deflated source and name indices where * the generated positions are compared. * * Optionally pass in `true` as `onlyCompareGenerated` to consider two * mappings with the same generated line and column, but different * source/name/original line and column the same. Useful when searching for a * mapping with a stubbed out mapping. */ export function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { var cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; } cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0 || onlyCompareGenerated) { return cmp; } cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; } cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; } cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; } return strcmp(mappingA.name, mappingB.name); } function strcmp(aStr1, aStr2) { if (aStr1 === aStr2) { return 0; } if (aStr1 === null) { return 1; // aStr2 !== null } if (aStr2 === null) { return -1; // aStr1 !== null } if (aStr1 > aStr2) { return 1; } return -1; } /** * Comparator between two mappings with inflated source and name strings where * the generated positions are compared. */ export function compareByGeneratedPositionsInflated(mappingA, mappingB) { var cmp = mappingA.generatedLine - mappingB.generatedLine; if (cmp !== 0) { return cmp; } cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp !== 0) { return cmp; } cmp = strcmp(mappingA.source, mappingB.source); if (cmp !== 0) { return cmp; } cmp = mappingA.originalLine - mappingB.originalLine; if (cmp !== 0) { return cmp; } cmp = mappingA.originalColumn - mappingB.originalColumn; if (cmp !== 0) { return cmp; } return strcmp(mappingA.name, mappingB.name); } /** * Strip any JSON XSSI avoidance prefix from the string (as documented * in the source maps specification), and then parse the string as * JSON. */ export function parseSourceMapInput(str) { return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, '')); } /** * Compute the URL of a source given the the source root, the source's * URL, and the source map's URL. */ export function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) { sourceURL = sourceURL || ''; if (sourceRoot) { // This follows what Chrome does. if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') { sourceRoot += '/'; } // The spec says: // Line 4: An optional source root, useful for relocating source // files on a server or removing repeated values in the // “sources†entry. This value is prepended to the individual // entries in the “source†field. sourceURL = sourceRoot + sourceURL; } // Historically, SourceMapConsumer did not take the sourceMapURL as // a parameter. This mode is still somewhat supported, which is why // this code block is conditional. However, it's preferable to pass // the source map URL to SourceMapConsumer, so that this function // can implement the source URL resolution algorithm as outlined in // the spec. This block is basically the equivalent of: // new URL(sourceURL, sourceMapURL).toString() // ... except it avoids using URL, which wasn't available in the // older releases of node still supported by this library. // // The spec says: // If the sources are not absolute URLs after prepending of the // “sourceRootâ€, the sources are resolved relative to the // SourceMap (like resolving script src in a html document). if (sourceMapURL) { var parsed = urlParse(sourceMapURL); if (!parsed) { throw new Error("sourceMapURL could not be parsed"); } if (parsed.path) { // Strip the last path component, but keep the "/". var index = parsed.path.lastIndexOf('/'); if (index >= 0) { parsed.path = parsed.path.substring(0, index + 1); } } sourceURL = join(urlGenerate(parsed), sourceURL); } return normalize(sourceURL); } cjs-140.0/modules/print.cpp0000664000175000017500000002030315167114161014532 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 args = 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, args[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()); args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_log_error(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if ((argc != 1 && argc != 2) || !args[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, args[1]); exc_state.restore(); } gjs_log_exception_full(cx, args[0], jstr, G_LOG_LEVEL_WARNING); args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_print_parse_args(JSContext* cx, const JS::CallArgs& args, std::string* buffer) { g_assert(buffer && "forgot out parameter"); buffer->clear(); for (unsigned n = 0; n < args.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, args[n])}; exc_state.restore(); if (jstr) { JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr)); if (!s) return false; *buffer += s.get(); if (n < (args.length() - 1)) *buffer += ' '; } else { *buffer = ""; return true; } } return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_print(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); std::string buffer; if (!gjs_print_parse_args(cx, args, &buffer)) return false; g_print("%s\n", buffer.c_str()); args.rval().setUndefined(); return true; } GJS_JSAPI_RETURN_CONVENTION static bool gjs_printerr(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); std::string buffer; if (!gjs_print_parse_args(cx, args, &buffer)) return false; g_printerr("%s\n", buffer.c_str()); args.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 && static_cast(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; 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.emplace_back(format_arg.get()); } gjs_warn_deprecated_once_per_callsite( cx, GjsDeprecationMessageId(message_id), format_args, 2); return true; } 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}; static constexpr JSPropertySpec props[] = { JSPropertySpec::int32Value( "PLATFORM_SPECIFIC_TYPELIB", GJS_MODULE_PROP_FLAGS, GjsDeprecationMessageId::PlatformSpecificTypelib), JSPropertySpec::int32Value("RENAMED", GJS_MODULE_PROP_FLAGS, GjsDeprecationMessageId::Renamed), JS_PS_END}; bool gjs_define_print_stuff(JSContext* cx, JS::MutableHandleObject module) { module.set(JS_NewPlainObject(cx)); if (!module) return false; return JS_DefineFunctions(cx, module, funcs) && JS_DefineProperties(cx, module, props); } cjs-140.0/modules/print.h0000664000175000017500000000057015167114161014203 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 #pragma once #include #include #include "cjs/macros.h" GJS_JSAPI_RETURN_CONVENTION bool gjs_define_print_stuff(JSContext*, JS::MutableHandleObject module); cjs-140.0/modules/script/0000775000175000017500000000000015167114161014200 5ustar fabiofabiocjs-140.0/modules/script/_bootstrap/0000775000175000017500000000000015167114161016354 5ustar fabiofabiocjs-140.0/modules/script/_bootstrap/coverage.js0000664000175000017500000000044315167114161020506 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-140.0/modules/script/_bootstrap/debugger.js0000664000175000017500000007611615167114161020511 0ustar fabiofabio/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ /* global debuggee, quit, loadNative, readline, uneval, getSourceMapRegistry */ // 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; } // Build a nested tree of all private fields wherever they reside. Each level has KV tuples and their descendents: // {cur: [[key1, value1], ...], children: {key1: {...next level}}} function getProperties(dv, result, seen = new WeakSet()) { if (!dv || seen.has(dv)) return; if (typeof dv === 'object') seen.add(dv); const privateKVs = dv.getOwnPrivateProperties?.().map(k => [k.description, dv.getProperty(k).return]) ?? []; const nonPrivateKVs = dv.getOwnPropertyNames?.().concat(dv.getOwnPropertySymbols()).map(k => [k, dv.getProperty(k).return]) ?? []; result.cur = privateKVs; result.children = {}; // a private field can be under a non-private field privateKVs.concat(nonPrivateKVs).forEach(([k, v]) => { result.children[k] = {}; getProperties(v, result.children[k], seen); }); // prettyPrint in the debuggee compartment needs access to the original private field value and not Debugger.Object result.cur.forEach(kv => kv[1]?.unsafeDereference && (kv[1] = kv[1].unsafeDereference())); } 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 properties = {}; getProperties(dv, properties); const str = exec('imports._print.getPrettyPrintFunction(globalThis)(v, extra)', {v: dv, extra: dv.makeDebuggeeValue(properties)}); 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; }, }); Object.defineProperty(Debugger.Frame.prototype, 'column', { configurable: true, enumerable: false, get() { return this.script?.getOffsetLocation(this.offset).columnNumber ?? null; }, }); Debugger.Script.prototype.describeOffset = function describeOffset(offset) { const {lineNumber, columnNumber} = this.getOffsetLocation(offset); const url = this.url || ''; const registry = getSourceMapRegistry(); const consumer = registry.get(url); let description = `${url}:${lineNumber}:${columnNumber}`; const original = consumer?.originalPositionFor({line: lineNumber, column: columnNumber}); if (original?.source || Number.isInteger(original?.line) || Number.isInteger(original?.column)) description += ' -> '; if (original?.source) description += original.source; if (Number.isInteger(original?.line)) description += `:${original.line}`; if (Number.isInteger(original?.column)) description += `:${original.column + 1}`; return description; }; 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