diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..3c72d46bde --- /dev/null +++ b/.babelrc @@ -0,0 +1,25 @@ +{ + "compact": false, + "retainLines": true, + "presets": [ + "@babel/preset-react" + ], + "ignore": [ + "chrome/content/zotero/include.js", + "chrome/content/zotero/xpcom/citeproc.js", + "chrome/content/ace/*", + "chrome/content/scaffold/templates/*", + "resource/react.js", + "resource/react-dom.js", + "resource/react-virtualized.js", + "test/resource/*.js" + ], + "plugins": [ + [ + "transform-es2015-modules-commonjs", + { + "strictMode": false + } + ] + ] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..8c171129bc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,17 @@ +build/* +chrome/content/zotero/xpcom/citeproc.js +chrome/content/zotero/xpcom/isbn.js +chrome/content/zotero/xpcom/rdf/* +chrome/content/zotero/xpcom/xregexp/* +chrome/content/zotero/xpcom/translation/tlds.js +chrome/skin/default/zotero/timeline/bundle.js +chrome/skin/default/zotero/timeline/timeline-api.js +resource/bluebird/* +resource/classnames.js +resource/csl-validator.js +resource/jspath.js +resource/prop-types.js +resource/react* +resource/tinymce/* +test/resource/* +translators/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..640ce90410 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,63 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "globals": { + "Zotero": false, + "ZOTERO_CONFIG": false, + "AddonManager": false, + "Cc": false, + "Ci": false, + "ChromeUtils": false, + "Components": false, + "ConcurrentCaller": false, + "ctypes": false, + "OS": false, + "MozXULElement": false, + "PluralForm": false, + "Services": false, + "windowUtils": false, + "XPCOMUtils": false, + "XRegExp": false, + "XULElement": false, + "XULElementBase": false, + "ItemPaneSectionElementBase": false, + "Cu": false, + "ChromeWorker": false, + "Localization": false, + "L10nFileSource": false, + "L10nRegistry": false, + "ZoteroPane_Local": false, + "ZoteroPane": false, + "Zotero_Tabs": false, + "ZoteroContextPane": false, + "IOUtils": false, + "NetUtil": false, + "FileUtils": false, + "globalThis": false + }, + "extends": [ + "@zotero", + "plugin:react/recommended" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "ecmaVersion": 2018, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" + }, + "plugins": [ + "react", + "@babel" + ], + "settings": { + "react": { + "version": "17.0" + } + }, + "rules": {} +} diff --git a/.forgejo/workflows/generate-tarball.yml b/.forgejo/workflows/generate-tarball.yml deleted file mode 100644 index 5de03640f5..0000000000 --- a/.forgejo/workflows/generate-tarball.yml +++ /dev/null @@ -1,64 +0,0 @@ -on: - workflow_dispatch: - inputs: - ref_name: - description: 'Tag or commit' - required: true - type: string - - push: - tags: - - '*' - -jobs: - build-tarball: - name: Build tarball w/ submodules - runs-on: x86_64 - container: - image: alpine:latest - env: - CI_PROJECT_NAME: zotero - steps: - - name: Environment setup - run: apk add nodejs git git-archive-all gzip - - name: Repo pull - uses: actions/checkout@v4 - with: - fetch-depth: 500 - ref: ${{ inputs.ref_name }} - - name: Package build - run: | - if test $GITHUB_REF_NAME == "ci" ; then - CI_REF_NAME=${{ inputs.ref_name }} - else - CI_REF_NAME=$GITHUB_REF_NAME - fi - echo "building tarball for $CI_REF_NAME" - git-archive-all --force-submodules $CI_PROJECT_NAME-$CI_REF_NAME.tar.gz - echo "Generating sha512sum" - sha512sum $CI_PROJECT_NAME-$CI_REF_NAME.tar.gz > $CI_PROJECT_NAME-$CI_REF_NAME.tar.gz.sha512sum - - name: Package upload - uses: forgejo/upload-artifact@v3 - with: - name: tarball - path: zotero-*.tar* - upload-tarball: - name: Upload to generic repo - runs-on: x86_64 - needs: [build-tarball] - container: - image: alpine:latest - steps: - - name: Environment setup - run: apk add nodejs curl - - name: Package download - uses: forgejo/download-artifact@v3 - - name: Package deployment - run: | - if test $GITHUB_REF_NAME == "ci" ; then - CI_REF_NAME=${{ inputs.ref_name }} - else - CI_REF_NAME=$GITHUB_REF_NAME - fi - curl --user ${{ vars.CODE_FORGEJO_USER }}:${{ secrets.CODE_FORGEJO_TOKEN }} --upload-file ./tarball/zotero-*.tar.gz ${{ github.server_url }}/api/packages/mirrors/generic/zotero/$CI_REF_NAME/zotero-$CI_REF_NAME.tar.gz - curl --user ${{ vars.CODE_FORGEJO_USER }}:${{ secrets.CODE_FORGEJO_TOKEN }} --upload-file ./tarball/zotero-*.tar.gz.sha512sum ${{ github.server_url }}/api/packages/mirrors/generic/zotero/$CI_REF_NAME/zotero-$CI_REF_NAME.tar.gz.sha512sum diff --git a/.forgejo/workflows/mirror-repository.yml b/.forgejo/workflows/mirror-repository.yml deleted file mode 100644 index 36ad9d5504..0000000000 --- a/.forgejo/workflows/mirror-repository.yml +++ /dev/null @@ -1,70 +0,0 @@ -on: - workflow_dispatch: - inputs: - ref_name: - description: 'Tag or commit' - required: false - type: string - is_commit: - description: 'Is commit?' - required: false - type: boolean - - - schedule: - - cron: '@hourly' - -jobs: - mirror: - name: Pull from upstream - runs-on: x86_64 - container: - image: alpine:latest - env: - upstream: https://github.com/zotero/zotero - tags: '7.*' - steps: - - name: Environment setup - run: apk add grep git sed coreutils bash nodejs - - name: Fetch destination - uses: actions/checkout@v4 - with: - fetch_depth: 1 - ref: ci - token: ${{ secrets.CODE_FORGEJO_TOKEN }} - - name: Missing tag detecting - run: | - if [ -n "${{ inputs.ref_name }}" ]; then - echo ${{ inputs.ref_name }} > missing_tags - else - git ls-remote $upstream "refs/tags/$tags" | grep -v '{' | sed 's|.*/||' > upstream_tags - git ls-remote ${{ github.server_url}}/${{ github.repository }} "refs/tags/$tags" | grep -v '{' | sed 's|.*/||' > destination_tags - cat upstream_tags destination_tags | tr ' ' '\n' | sort | uniq -u > missing_tags - fi - echo "Missing tags:" - cat missing_tags - - name: Missing tag fetch - run: | - git remote add upstream $upstream - while read tag; do - if test "${{ inputs.is_commit }}" == "true"; then - git fetch upstream $tag --no-tags - else - git fetch upstream tag $tag --no-tags - fi - done < missing_tags - - name: Packaging workflow injection - run: | - while read tag; do - git checkout $tag - if test "${{ inputs.is_commit }}" != "true"; then - git tag -d $tag - fi - git checkout ci -- ./.forgejo - git config user.name "forgejo-actions[bot]" - git config user.email "dev@ayakael.net" - git commit -m 'Inject custom workflow' - git tag -a $tag -m $tag - done < missing_tags - - name: Push to destination - run: git push --force origin refs/tags/*:refs/tags/* --tags diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..4fb3d77151 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.sql text eol=lf +app/win/zotero.exe.tar.xz filter=lfs diff=lfs merge=lfs -text +app/mac/updater.tar.xz filter=lfs diff=lfs merge=lfs -text +app/win/updater.exe.tar.xz filter=lfs diff=lfs merge=lfs -text +app/linux/updater.tar.xz filter=lfs diff=lfs merge=lfs -text diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..91292d7a56 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ +**READ THIS BEFORE CREATING AN ISSUE** + +Zotero does not use GitHub Issues for bug reports or feature requests. + +Please post all such requests to the Zotero Forums at https://forums.zotero.org, where Zotero developers and many others can help. For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories. + +Development questions involving code, APIs, or other technical topics can be posted to the zotero-dev mailing list at http://groups.google.com/group/zotero-dev. + +See https://www.zotero.org/support/zotero_support for more information on how Zotero support works. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..2a1e744cc4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI +on: [push, pull_request] +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true +jobs: + build: + name: Build, Upload, Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: 18 + #cache: npm + + # On GitHub + - name: Install xvfb + if: env.ACT != 'true' + run: sudo apt update && sudo apt install -y xvfb + + # Local via act + - name: Install packages for act + if: env.ACT == 'true' + run: apt update && apt install -y zstd xvfb dbus-x11 libgtk-3-0 libx11-xcb1 libdbus-glib-1-2 libxt6 + + - name: Cache xulrunner + id: xulrunner-cache + uses: actions/cache@v4 + with: + path: app/xulrunner/firefox-x86_64 + key: xulrunner-${{ hashFiles('app/config.sh', 'app/scripts/fetch_xulrunner') }} + + - name: Fetch xulrunner + if: steps.xulrunner-cache.outputs.cache-hit != 'true' + run: app/scripts/fetch_xulrunner -p l + + - name: Cache Node modules + id: node-cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ hashFiles('package-lock.json') }} + + - name: Install Node modules + if: steps.node-cache.outputs.cache-hit != 'true' + run: npm install + + - name: Build Zotero + run: npm run build + # Currently necessary for pdf-worker Webpack: https://stackoverflow.com/a/69746937 + env: + NODE_OPTIONS: --openssl-legacy-provider + + - name: Upload deployment ZIP + if: | + env.ACT != 'true' + && github.repository == 'zotero/zotero' + && github.event_name == 'push' + && (github.ref == 'refs/heads/main' || endsWith(github.ref, '-hotfix')) + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + mkdir build-zip + cd build + zip -r ../build-zip/$GITHUB_SHA.zip * + cd .. + sudo gem install --no-document dpl dpl-s3 + dpl --provider=s3 --bucket=zotero-download --local-dir=build-zip --upload-dir=ci/client --acl=public-read --skip_cleanup=true + + - name: Run tests + run: xvfb-run test/runtests.sh -f + + - name: Cache utilities Node modules + id: utilities-node-cache + uses: actions/cache@v4 + with: + path: chrome/content/zotero/xpcom/utilities/node_modules + key: utilities-node-modules-${{ hashFiles('chrome/content/zotero/xpcom/utilities/package-lock.json') }} + + - name: Install utilities Node modules + if: steps.utilities-node-cache.outputs.cache-hit != 'true' + run: npm install --prefix chrome/content/zotero/xpcom/utilities + + - name: Run utilities tests + run: | + npm test --prefix chrome/content/zotero/xpcom/utilities -- -j resource/schema/global/schema.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3e51b5ea8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +node_modules +build/ +.signatures.json +tmp +app/win/codesign diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..ffb7808f55 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,58 @@ +[submodule "translators"] + path = translators + url = https://github.com/zotero/translators.git + branch = master +[submodule "chrome/content/zotero/locale/csl"] + path = chrome/content/zotero/locale/csl + url = https://github.com/citation-style-language/locales.git + branch = master +[submodule "styles"] + path = styles + url = https://github.com/zotero/bundled-styles.git + branch = master +[submodule "test/resource/chai"] + path = test/resource/chai + url = https://github.com/chaijs/chai.git + branch = master +[submodule "test/resource/mocha"] + path = test/resource/mocha + url = https://github.com/mochajs/mocha.git + branch = master +[submodule "test/resource/chai-as-promised"] + path = test/resource/chai-as-promised + url = https://github.com/domenic/chai-as-promised.git + branch = master +[submodule "resource/schema/global"] + path = resource/schema/global + url = https://github.com/zotero/zotero-schema.git + branch = master +[submodule "resource/SingleFile"] + path = resource/SingleFile + url = https://github.com/gildas-lormeau/SingleFile.git +[submodule "pdf-reader"] + path = reader + url = https://github.com/zotero/reader.git + branch = master +[submodule "pdf-worker"] + path = pdf-worker + url = https://github.com/zotero/pdf-worker.git + branch = master +[submodule "note-editor"] + path = note-editor + url = https://github.com/zotero/note-editor.git + branch = master +[submodule "chrome/content/zotero/xpcom/utilities"] + path = chrome/content/zotero/xpcom/utilities + url = https://github.com/zotero/utilities.git +[submodule "chrome/content/zotero/xpcom/translate"] + path = chrome/content/zotero/xpcom/translate + url = https://github.com/zotero/translate.git +[submodule "app/modules/zotero-word-for-mac-integration"] + path = app/modules/zotero-word-for-mac-integration + url = https://github.com/zotero/zotero-word-for-mac-integration.git +[submodule "app/modules/zotero-word-for-windows-integration"] + path = app/modules/zotero-word-for-windows-integration + url = https://github.com/zotero/zotero-word-for-windows-integration.git +[submodule "app/modules/zotero-libreoffice-integration"] + path = app/modules/zotero-libreoffice-integration + url = https://github.com/zotero/zotero-libreoffice-integration.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..905c2c0531 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing to Zotero + +## Bug Reports and Feature Requests + +Zotero does not use GitHub Issues for bug reports, feature requests, or support questions. Please post all such requests to the [Zotero Forums](https://forums.zotero.org), where Zotero developers and many others can help. See [How Zotero Support Works](https://www.zotero.org/support/zotero_support) for more information. + +For confirmed bugs or agreed-upon changes, Zotero developers will create new issues in the relevant repositories. + +## Working with Zotero Code + +See [Zotero Source Code](https://www.zotero.org/support/dev/source_code). diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..b14d531f57 --- /dev/null +++ b/COPYING @@ -0,0 +1,681 @@ +Zotero is Copyright © 2018 Corporation for Digital Scholarship, +Vienna, Virginia, USA http://digitalscholar.org + +Copyright © 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 +Roy Rosenzweig Center for History and New Media, George Mason University, +Fairfax, Virginia, USA http://zotero.org + +The Corporation for Digital Scholarship distributes the Zotero source code +under the GNU Affero General Public License, version 3 (AGPLv3). The full text +of this license is given below. + +The Zotero name is a registered trademark of the Corporation for Digital Scholarship. +See http://zotero.org/trademark for more information. + +Third-party copyright in this distribution is noted where applicable. + +All rights not expressly granted are reserved. + +========================================================================= + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..b1f49f3314 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Zotero +====== +[![CI](https://github.com/zotero/zotero/actions/workflows/ci.yml/badge.svg)](https://github.com/zotero/zotero/actions/workflows/ci.yml) + +[Zotero](https://www.zotero.org/) is a free, easy-to-use tool to help you collect, organize, cite, and share your research sources. + +Please post feature requests or bug reports to the [Zotero Forums](https://forums.zotero.org/). If you're having trouble with Zotero, see [Getting Help](https://www.zotero.org/support/getting_help). + +For more information on how to use this source code, see the [Zotero documentation](https://www.zotero.org/support/dev/source_code). diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000..c1310cfcac --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,11 @@ +*~ +cache +config-custom.sh +dist +lastrev +staging +tmp +xulrunner +win/resource_hacker +win/firefox-*.win32.zip +win/firefox-*.win64.zip diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000000..f96b3a3d69 --- /dev/null +++ b/app/README.md @@ -0,0 +1,4 @@ +# Zotero Standalone build utility +These files are used to bundle the [Zotero core](https://github.com/zotero/zotero) into distributable bundles for Mac, Windows, and Linux. + +Instructions for building and packaging are available on the [Zotero wiki](https://www.zotero.org/support/dev/client_coding/building_the_standalone_client). diff --git a/app/assets/application.ini b/app/assets/application.ini new file mode 100644 index 0000000000..73990c30ff --- /dev/null +++ b/app/assets/application.ini @@ -0,0 +1,18 @@ +[App] +Vendor=Zotero +Name=Zotero +Version={{VERSION}} +BuildID={{BUILDID}} +Copyright=Copyright (c) 2006-2022 Contributors +ID=zotero@zotero.org + +[Gecko] +MinVersion=115.0 +MaxVersion=115.99.* + +[XRE] +EnableExtensionManager=1 +EnableProfileMigrator=1 + +[AppUpdate] +URL=https://www.zotero.org/download/client/update/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/update.xml diff --git a/app/assets/branding/locale/brand.dtd b/app/assets/branding/locale/brand.dtd new file mode 100644 index 0000000000..cba785bc63 --- /dev/null +++ b/app/assets/branding/locale/brand.dtd @@ -0,0 +1 @@ + diff --git a/app/assets/branding/locale/brand.ftl b/app/assets/branding/locale/brand.ftl new file mode 100644 index 0000000000..896ddc864f --- /dev/null +++ b/app/assets/branding/locale/brand.ftl @@ -0,0 +1,7 @@ +-brand-shorter-name = Zotero +-brand-short-name = Zotero +-brand-full-name = Zotero +-brand-product-name = Zotero +-vendor-short-name = Zotero +-app-name = Zotero +trademarkInfo = Zotero is a trademark of the Corporation for Digital Scholarship. diff --git a/app/assets/branding/locale/brand.properties b/app/assets/branding/locale/brand.properties new file mode 100644 index 0000000000..5adb572994 --- /dev/null +++ b/app/assets/branding/locale/brand.properties @@ -0,0 +1,3 @@ +brandShorterName=Zotero +brandShortName=Zotero +brandFullName=Zotero diff --git a/app/assets/chrome.manifest b/app/assets/chrome.manifest new file mode 100644 index 0000000000..3844885506 --- /dev/null +++ b/app/assets/chrome.manifest @@ -0,0 +1,3 @@ +locale branding en-US chrome/en-US/locale/branding/ +content branding chrome/branding/content/ +skin browser preferences chrome/skin/ diff --git a/app/assets/commandLineHandler.js b/app/assets/commandLineHandler.js new file mode 100644 index 0000000000..931044f9ee --- /dev/null +++ b/app/assets/commandLineHandler.js @@ -0,0 +1,102 @@ +let { CommandLineOptions, TestOptions } = ChromeUtils.importESModule("chrome://zotero/content/modules/commandLineOptions.mjs"); + +// Only allow BrowserContentHandler to open a new window if this is the initial launch, +// meaning our CLH isn't registered yet. +if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) { + cmdLine.preventDefault = true; +} + +// Force debug output to window +if (cmdLine.handleFlag("ZoteroDebug", false)) { + CommandLineOptions.forceDebugLog = 2; +} +// Force debug output to text console +else if (cmdLine.handleFlag("ZoteroDebugText", false)) { + CommandLineOptions.forceDebugLog = 1; +} +// Pressing Ctrl-C via the terminal is interpreted as a crash, and after three crashes +// Firefox starts up in automatic safe mode (troubleshooting mode). To avoid this, we clear the crash +// counter when using one of the debug-logging flags, which generally imply terminal usage. +if (CommandLineOptions.forceDebugLog) { + Services.prefs.getBranch("toolkit.startup.").clearUserPref("recent_crashes"); +} + +CommandLineOptions.forceDataDir = cmdLine.handleFlagWithParam("datadir", false); +// Set here, to be acted upon in xpcom/commandLineHandler.js +CommandLineOptions.file = cmdLine.handleFlagWithParam("file", false); +CommandLineOptions.url = cmdLine.handleFlagWithParam("url", false); +if (CommandLineOptions.url) { + CommandLineOptions.url = cmdLine.resolveURI(CommandLineOptions.url); +} + +var processTestOptions = false; +if (cmdLine.handleFlag("ZoteroTest", false)) { + CommandLineOptions.test = true; + processTestOptions = true; +} +if (cmdLine.handleFlag("ZoteroAutomatedTest", false)) { + CommandLineOptions.automatedTest = true; +} +if (cmdLine.handleFlag("ZoteroSkipBundledFiles", false)) { + CommandLineOptions.skipBundledFiles = true; +} + +if (processTestOptions) { + TestOptions.tests = cmdLine.handleFlagWithParam("test", false); + TestOptions.noquit = cmdLine.handleFlag("noquit", false); + TestOptions.makeTestData = cmdLine.handleFlag("makeTestData", false); + TestOptions.noquit = !TestOptions.makeTestData && this.noquit; + TestOptions.runTests = !TestOptions.makeTestData; + TestOptions.bail = cmdLine.handleFlag("bail", false); + TestOptions.startAt = cmdLine.handleFlagWithParam("startAtTestFile", false); + TestOptions.stopAt = cmdLine.handleFlagWithParam("stopAtTestFile", false); + TestOptions.grep = cmdLine.handleFlagWithParam("grep", false); + TestOptions.timeout = cmdLine.handleFlagWithParam("ZoteroTestTimeout", false); + + Services.ww.openWindow( + null, + "chrome://zotero-unit/content/runtests.html", + "_blank", + "chrome,dialog=no,all", + Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray) + ); + cmdLine.preventDefault = true; +} + +if (cmdLine.handleFlag("debugger", false)) { + (async function () { + try { + let portOrPath = Services.prefs.getBranch('').getIntPref('devtools.debugger.remote-port'); + + const { DevToolsLoader } = ChromeUtils.import( + "resource://devtools/shared/loader/Loader.jsm" + ); + const loader = new DevToolsLoader({ + freshCompartment: true, + }); + const { DevToolsServer } = loader.require("devtools/server/devtools-server"); + const { SocketListener } = loader.require("devtools/shared/security/socket"); + + if (DevToolsServer.initialized) { + dump("Debugger server already initialized\n\n"); + return; + } + + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + DevToolsServer.allowChromeProcess = true; + const socketOptions = { portOrPath }; + const listener = new SocketListener(DevToolsServer, socketOptions); + await listener.open(); + if (!DevToolsServer.listeningSockets) { + throw new Error("No listening sockets"); + } + + dump(`Debugger server started on ${portOrPath}\n\n`); + } + catch (e) { + dump(e + "\n\n"); + Components.utils.reportError(e); + } + })(); +} diff --git a/app/assets/mac/chrome/skin/preferences/preferences.css b/app/assets/mac/chrome/skin/preferences/preferences.css new file mode 100644 index 0000000000..f866b7e1f0 --- /dev/null +++ b/app/assets/mac/chrome/skin/preferences/preferences.css @@ -0,0 +1,242 @@ +/* +# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# 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 Firefox Preferences System. +# +# The Initial Developer of the Original Code is +# Ben Goodger. +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Ben Goodger +# Kevin Gerich +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** +*/ + +.windowDialog { + padding: 12px; + font: -moz-dialog; +} + +.paneSelector { + list-style-image: url("chrome://browser/skin/preferences/Options.png"); +} + +/* ----- GENERAL BUTTON ----- */ + +radio[pane=paneGeneral], +radio[pane=paneMain] { + -moz-image-region: rect(0px, 32px, 32px, 0px); +} + +/* ----- TABS BUTTON ----- */ + +radio[pane=paneTabs] { + -moz-image-region: rect(0px, 64px, 32px, 32px); +} + +/* ----- CONTENT BUTTON ----- */ + +radio[pane=paneContent] { + -moz-image-region: rect(0px, 96px, 32px, 64px); +} + +/* ----- APPLICATIONS BUTTON ----- */ + +radio[pane=paneApplications] { + -moz-image-region: rect(0px, 128px, 32px, 96px); +} + +/* ----- PRIVACY BUTTON ----- */ + +radio[pane=panePrivacy] { + -moz-image-region: rect(0px, 160px, 32px, 128px); +} + +/* ----- SECURITY BUTTON ----- */ + +radio[pane=paneSecurity] { + -moz-image-region: rect(0px, 192px, 32px, 160px); +} + +/* ----- ADVANCED BUTTON ----- */ + +radio[pane=paneAdvanced] { + -moz-image-region: rect(0px, 224px, 32px, 192px); +} + +/* ----- SYNC BUTTON ----- */ + +radio[pane=paneSync] { + list-style-image: url("chrome://browser/skin/preferences/Options-sync.png"); +} + + +/* ----- APPLICATIONS PREFPANE ----- */ +#BrowserPreferences[animated="true"] #handlersView { + height: 25em; +} + +#BrowserPreferences[animated="false"] #handlersView { + -moz-box-flex: 1; +} + +description { + font: small-caption; + font-weight: normal; + line-height: 1.3em; + margin-bottom: 4px !important; +} + +prefpane .groupbox-body { + -moz-appearance: none; + padding: 8px 4px 4px 4px; +} + +#paneTabs > groupbox { + margin: 0; +} + +#tabPrefsBox { + margin: 12px 4px; +} + +prefpane .groupbox-title { + background: url("chrome://global/skin/50pct_transparent_grey.png") repeat-x bottom left; + margin-bottom: 4px; +} + +tabpanels { + padding: 20px 7px 7px; +} + +caption { + -moz-padding-start: 5px; + padding-top: 4px; + padding-bottom: 2px; +} + +#paneMain description, +#paneContent description, +#paneAdvanced description, +#paneSecurity description { + font: -moz-dialog; +} + +#paneContent { + padding-top: 8px; +} + +#paneContent row { + padding: 2px 4px; + -moz-box-align: center; +} + +#popupPolicyRow, +#enableSoftwareInstallRow, +#enableImagesRow { + margin-bottom: 4px !important; + padding-bottom: 4px !important; + border-bottom: 1px solid #ccc; +} + +#browserUseCurrent, +#browserUseBookmark, +#browserUseBlank { + margin-top: 10px; +} + +#advancedPrefs { + margin: 0 8px; +} + +#privacyPrefs { + padding: 0 4px; +} + +#privacyPrefs > tabpanels { + padding: 18px 10px 10px; +} + +#OCSPDialogPane { + font: message-box !important; +} + +/** + * Privacy Pane + */ + +/* styles for the link elements copied from .text-link in global.css */ +.inline-link { + color: -moz-nativehyperlinktext; + text-decoration: underline; +} + +.inline-link:not(:focus) { + outline: 1px dotted transparent; +} + +/** + * Update Preferences + */ +#autoInstallOptions { + -moz-margin-start: 20px; +} + +.updateControls { + -moz-margin-start: 10px; +} + +/** + * Clear Private Data + */ +#SanitizeDialogPane > groupbox { + margin-top: 0; +} + + +/* ----- SYNC PANE ----- */ + +#syncDesc { + padding: 0 8em; +} + +#accountCaptionImage { + list-style-image: url("chrome://mozapps/skin/profile/profileicon.png"); +} + +#syncAddDeviceLabel { + margin-top: 1em; + margin-bottom: 1em; +} + +#syncEnginesList { + height: 10em; +} + diff --git a/app/assets/multilocale.txt b/app/assets/multilocale.txt new file mode 100644 index 0000000000..af1f405563 --- /dev/null +++ b/app/assets/multilocale.txt @@ -0,0 +1 @@ +en-US,ar,bg-BG,br,ca-AD,cs-CZ,da-DK,de,el-GR,en-AU,en-CA,en-GB,en-NZ,es-ES,et-EE,eu-ES,fa,fi-FI,fr-FR,gl-ES,hu-HU,id-ID,is-IS,it-IT,ja-JP,km,ko-KR,lt-LT,nb-NO,nl-NL,pl-PL,pt-BR,pt-PT,ro-RO,ru-RU,sk-SK,sl-SI,sr-RS,sv-SE,ta,th-TH,tr-TR,uk-UA,vi-VN,zh-CN,zh-TW diff --git a/app/assets/prefs.js b/app/assets/prefs.js new file mode 100644 index 0000000000..bf137177d2 --- /dev/null +++ b/app/assets/prefs.js @@ -0,0 +1,157 @@ +// We only want a single window, I think +pref("toolkit.singletonWindowType", "navigator:browser"); + +// For debugging purposes, show errors in console by default +pref("javascript.options.showInConsole", true); + +// Don't retrieve unrequested links when performing standalone translation +pref("network.prefetch-next", false); + +// Let operations run as long as necessary +pref("dom.max_chrome_script_run_time", 0); + +// .dotm Word plugin VBA uses this to find the running Zotero instance +pref("ui.window_class_override", "ZoteroWindowClass"); + +pref("intl.locale.requested", ''); +pref("intl.regional_prefs.use_os_locales", false); + +// Fix error initializing login manager after this was changed in Firefox 57 +// Could also disable this with MOZ_LOADER_SHARE_GLOBAL, supposedly +pref("jsloader.shareGlobal", false); + +// Needed due to https://bugzilla.mozilla.org/show_bug.cgi?id=1181977 +pref("browser.hiddenWindowChromeURL", "chrome://zotero/content/standalone/hiddenWindow.xhtml"); +// Use basicViewer for opening new DOM windows from content (for TinyMCE) +pref("browser.chromeURL", "chrome://zotero/content/standalone/basicViewer.xhtml"); +// We need these to get the save dialog working with contentAreaUtils.js +pref("browser.download.useDownloadDir", false); +pref("browser.download.manager.showWhenStarting", false); +pref("browser.download.folderList", 1); + +// Don't show add-on selection dialog +pref("extensions.shownSelectionUI", true); +pref("extensions.autoDisableScope", 11); + +pref("network.protocol-handler.expose-all", false); +pref("network.protocol-handler.expose.zotero", true); +pref("network.protocol-handler.expose.http", true); +pref("network.protocol-handler.expose.https", true); + +// Never go offline +pref("offline.autoDetect", false); +pref("network.manage-offline-status", false); + +// Without this, we will throw up dialogs if asked to translate strange pages +pref("browser.xul.error_pages.enabled", true); + +// Without this, scripts may decide to open popups +pref("dom.disable_open_during_load", true); + +// Don't show security warning. The "warn_viewing_mixed" warning just lets the user know that some +// page elements were loaded over an insecure connection. This doesn't matter if all we're doing is +// scraping the page, since we don't provide any information to the site. +pref("security.warn_viewing_mixed", false); + +// Preferences for add-on discovery +pref("extensions.getAddons.cache.enabled", false); +//pref("extensions.getAddons.maxResults", 15); +//pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/search/guid:%IDS%?src=thunderbird&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%"); +//pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/%APP%/search?q=%TERMS%"); +//pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%?src=thunderbird"); +//pref("extensions.webservice.discoverURL", "https://www.zotero.org/support/plugins"); + +// Check Windows certificate store for custom CAs +pref("security.enterprise_roots.enabled", true); + +// Disable add-on signature checking with unbranded Firefox build +pref("xpinstall.signatures.required", false); +// Allow legacy extensions (though this might not be necessary) +pref("extensions.legacy.enabled", true); +// Allow installing XPIs from any host +pref("xpinstall.whitelist.required", false); +// Allow installing XPIs when using a custom CA +pref("extensions.install.requireBuiltInCerts", false); +pref("extensions.update.requireBuiltInCerts", false); + +// Don't connect to the Mozilla extensions blocklist +pref("extensions.blocklist.enabled", false); +// Avoid warning in console when opening Tools -> Add-ons +pref("extensions.getAddons.link.url", ""); + +// Disable places +pref("places.history.enabled", false); + +// Probably not used, but prevent an error in the console +pref("app.support.baseURL", "https://www.zotero.org/support/"); + +// Disable Telemetry, Health Report, error reporting, and remote settings +pref("toolkit.telemetry.unified", false); +pref("toolkit.telemetry.enabled", false); +pref("datareporting.policy.dataSubmissionEnabled", false); +pref("toolkit.crashreporter.enabled", false); +pref("extensions.remoteSettings.disabled", true); + +pref("extensions.update.url", ""); + +// Don't try to load the "Get Add-ons" tab on first load of Add-ons window +pref("extensions.ui.lastCategory", "addons://list/extension"); + +// Not set on Windows in Firefox anymore since it's a per-installation pref, +// but we override that in fetch_xulrunner +pref("app.update.auto", true); + +// URL user can browse to manually if for some reason all update installation +// attempts fail. +pref("app.update.url.manual", "https://www.zotero.org/download"); + +// A default value for the "More information about this update" link +// supplied in the "An update is available" page of the update wizard. +pref("app.update.url.details", "https://www.zotero.org/support/changelog"); + +// Interval: Time between checks for a new version (in seconds) +// default=1 day +pref("app.update.interval", 86400); + +// The minimum delay in seconds for the timer to fire. +// default=2 minutes +pref("app.update.timerMinimumDelay", 120); + +// Whether or not we show a dialog box informing the user that the update was +// successfully applied. This is off in Firefox by default since we show a +// upgrade start page instead! Other apps may wish to show this UI, and supply +// a whatsNewURL field in their brand.properties that contains a link to a page +// which tells users what's new in this new update. + +// update channel for this build +pref("app.update.channel", "default"); + +// This should probably not be a preference that's used in toolkit.... +pref("browser.preferences.instantApply", false); + +// Allow elements to be displayed full-screen +pref("full-screen-api.enabled", true); + +// Allow chrome access in DevTools +// This enables the input field in the Browser Console tool +pref("devtools.chrome.enabled", true); + +// Default mousewheel action with Alt/Option is History Back/Forward in Firefox +// We don't have History navigation and users want to scroll the tree with Option +// key held down +pref("mousewheel.with_alt.action", 1); + +// Use the system print dialog instead of the new tab-based print dialog in Firefox +pref("print.prefer_system_dialog", true); + +// Disable libvpx decoding/encoding +pref("media.webm.enabled", false); +pref("media.encoder.webm.enabled", false); +pref("media.mediasource.webm.enabled", false); +pref("media.mediasource.webm.audio.enabled", false); +pref("media.mediasource.vp9.enabled", false); +pref("media.ffvpx.enabled", false); +pref("media.ffvpx.mp3.enabled", false); +pref("media.rdd-vpx.enabled", false); +pref("media.rdd-ffvpx.enabled", false); +pref("media.utility-ffvpx.enabled", false); diff --git a/app/assets/unix/skin/preferences/preferences.css b/app/assets/unix/skin/preferences/preferences.css new file mode 100644 index 0000000000..3a19fbb185 --- /dev/null +++ b/app/assets/unix/skin/preferences/preferences.css @@ -0,0 +1,188 @@ +%if 0 +/* +# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# 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 Firefox Preferences System. +# +# The Initial Developer of the Original Code is +# Ben Goodger. +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Ben Goodger +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** +*/ +%endif + +/* Global Styles */ +#BrowserPreferences radio[pane] { + list-style-image: url("chrome://browser/skin/preferences/Options.png"); +} + +radio[pane=paneMain] { + -moz-image-region: rect(0px, 32px, 32px, 0px) +} + +radio[pane=paneTabs] { + -moz-image-region: rect(0px, 64px, 32px, 32px) +} + +radio[pane=paneContent] { + -moz-image-region: rect(0px, 96px, 32px, 64px) +} + +radio[pane=paneApplications] { + -moz-image-region: rect(0px, 128px, 32px, 96px) +} + +radio[pane=panePrivacy] { + -moz-image-region: rect(0px, 160px, 32px, 128px) +} + +radio[pane=paneSecurity] { + -moz-image-region: rect(0px, 192px, 32px, 160px) +} + +radio[pane=paneAdvanced] { + -moz-image-region: rect(0px, 224px, 32px, 192px) +} + +%ifdef MOZ_SERVICES_SYNC +radio[pane=paneSync] { + list-style-image: url("chrome://browser/skin/preferences/Options-sync.png") !important; +} +%endif + +/* Applications Pane */ +#BrowserPreferences[animated="true"] #handlersView { + height: 25em; +} + +#BrowserPreferences[animated="false"] #handlersView { + -moz-box-flex: 1; +} + +/* Privacy Pane */ + +/* styles for the link elements copied from .text-link in global.css */ +.inline-link { + color: -moz-nativehyperlinktext; + text-decoration: underline; +} + +.inline-link:not(:focus) { + outline: 1px dotted transparent; +} + +/* Modeless Window Dialogs */ +.windowDialog, +.windowDialog prefpane { + padding: 0px; +} + +.contentPane { + margin: 9px 8px 5px 8px; +} + +.actionButtons { + margin: 0px 3px 6px 3px !important; +} + +/* Cookies Manager */ +#cookiesChildren::-moz-tree-image(domainCol) { + width: 16px; + height: 16px; + margin: 0px 2px; + list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); +} + +#paneApplications { + margin-left: 4px; + margin-right: 4px; + padding-left: 0; + padding-right: 0; +} + +#linksOpenInBox { + margin-top: 5px; +} + +#paneAdvanced { + padding-bottom: 10px; +} +#advancedPrefs { + margin-left: 0; + margin-right: 0; +} + +#cookiesChildren::-moz-tree-image(domainCol, container) { + list-style-image: url("moz-icon://stock/gtk-directory?size=menu"); +} + +#cookieInfoBox { + border: 1px solid ThreeDShadow; + border-radius: 0px; + margin: 4px; + padding: 0px; +} + +/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom + of the groupbox from being cutoff */ +.bottomBox { + padding-bottom: 4px; +} + +/** + * Clear Private Data + */ +#SanitizeDialogPane > groupbox { + margin-top: 0; +} + +%ifdef MOZ_SERVICES_SYNC +/* Sync Pane */ + +#syncDesc { + padding: 0 8em; +} + +#accountCaptionImage { + list-style-image: url("chrome://mozapps/skin/profile/profileicon.png"); +} + +#syncAddDeviceLabel { + margin-top: 1em; + margin-bottom: 1em; +} + +#syncEnginesList { + height: 10em; +} + +%endif diff --git a/app/assets/updater.ini b/app/assets/updater.ini new file mode 100644 index 0000000000..e68c8edc2e --- /dev/null +++ b/app/assets/updater.ini @@ -0,0 +1,4 @@ +; This file is in the UTF-8 encoding +[Strings] +Title=Zotero Update +Info=Zotero is installing your updates and will start in a few moments… \ No newline at end of file diff --git a/app/assets/win/skin/preferences/preferences.css b/app/assets/win/skin/preferences/preferences.css new file mode 100644 index 0000000000..d36cb7c285 --- /dev/null +++ b/app/assets/win/skin/preferences/preferences.css @@ -0,0 +1,178 @@ +/* +# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# 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 Firefox Preferences System. +# +# The Initial Developer of the Original Code is +# Ben Goodger. +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Ben Goodger +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** +*/ + +/* Global Styles */ +#BrowserPreferences radio[pane] { + list-style-image: url("chrome://browser/skin/preferences/Options.png"); + padding: 5px 3px 1px; +} + +radio[pane=paneMain] { + -moz-image-region: rect(0, 32px, 32px, 0); +} + +radio[pane=paneTabs] { + -moz-image-region: rect(0, 64px, 32px, 32px); +} + +radio[pane=paneContent] { + -moz-image-region: rect(0, 96px, 32px, 64px); +} + +radio[pane=paneApplications] { + -moz-image-region: rect(0, 128px, 32px, 96px); +} + +radio[pane=panePrivacy] { + -moz-image-region: rect(0, 160px, 32px, 128px); +} + +radio[pane=paneSecurity] { + -moz-image-region: rect(0, 192px, 32px, 160px); +} + +radio[pane=paneAdvanced] { + -moz-image-region: rect(0, 224px, 32px, 192px); +} + +%ifdef MOZ_SERVICES_SYNC +radio[pane=paneSync] { + list-style-image: url("chrome://browser/skin/preferences/Options-sync.png") !important; +} +%endif + +/* Applications Pane */ +#BrowserPreferences[animated="true"] #handlersView { + height: 25em; +} + +#BrowserPreferences[animated="false"] #handlersView { + -moz-box-flex: 1; +} + +/* Privacy Pane */ + +/* styles for the link elements copied from .text-link in global.css */ +.inline-link { + color: -moz-nativehyperlinktext; + text-decoration: underline; +} + +.inline-link:not(:focus) { + outline: 1px dotted transparent; +} + +/* Modeless Window Dialogs */ +.windowDialog, +.windowDialog prefpane { + padding: 0; +} + +.contentPane { + margin: 9px 8px 5px; +} + +.actionButtons { + margin: 0 3px 6px !important; +} + +/* Cookies Manager */ +#cookiesChildren::-moz-tree-image(domainCol) { + width: 16px; + height: 16px; + margin: 0 2px; + list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png") !important; +} + +#cookiesChildren::-moz-tree-image(domainCol, container) { + list-style-image: url("chrome://global/skin/icons/folder-item.png") !important; + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +#cookiesChildren::-moz-tree-image(domainCol, container, open) { + -moz-image-region: rect(16px, 32px, 32px, 16px); +} + +#cookieInfoBox { + border: 1px solid ThreeDShadow; + border-radius: 0; + margin: 4px; + padding: 0; +} + +/* Advanced Pane */ + +/* Adding padding-bottom prevents the bottom of the tabpanel from being cutoff + when browser.preferences.animateFadeIn = true */ +#advancedPrefs { + padding-bottom: 8px; +} + +/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom + of the groupbox from being cutoff */ +.bottomBox { + padding-bottom: 4px; +} + +%ifdef MOZ_SERVICES_SYNC +/* Sync Pane */ + +#syncDesc { + padding: 0 8em; +} + +.syncGroupBox { + padding: 10px; +} + +#accountCaptionImage { + list-style-image: url("chrome://mozapps/skin/profile/profileicon.png"); +} + +#syncAddDeviceLabel { + margin-top: 1em; + margin-bottom: 1em; + } + +#syncEnginesList { + height: 11em; +} + +%endif diff --git a/app/build.sh b/app/build.sh new file mode 100755 index 0000000000..3de6bbabcb --- /dev/null +++ b/app/build.sh @@ -0,0 +1,869 @@ +#!/bin/bash -e + +# Copyright (c) 2011 Zotero +# Center for History and New Media +# George Mason University, Fairfax, Virginia, USA +# http://zotero.org +# +# 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 . + +CALLDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +. "$CALLDIR/config.sh" + +if [ "`uname`" = "Darwin" ]; then + MAC_NATIVE=1 +else + MAC_NATIVE=0 +fi +if [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then + WIN_NATIVE=1 +else + WIN_NATIVE=0 +fi + +function usage { + cat >&2 <&1 + exit 1 + fi +} + +function abspath { + echo $(cd $(dirname $1); pwd)/$(basename $1); +} + +function check_lfs_file { + if [ "$(head --bytes 5 "$1")" = "versi" ]; then + echo "$1 not checked out -- install Git LFS and run 'git lfs pull'" >&2 + exit 1 + fi +} + +SOURCE_DIR="" +ZIP_FILE="" +BUILD_MAC=0 +BUILD_WIN=0 +BUILD_LINUX=0 +PACKAGE=1 +DEVTOOLS=0 +quick_build=0 +while getopts "d:f:p:c:tseq" opt; do + case $opt in + d) + SOURCE_DIR="$OPTARG" + ;; + f) + ZIP_FILE="$OPTARG" + ;; + p) + for i in `seq 0 1 $((${#OPTARG}-1))` + do + case ${OPTARG:i:1} in + m) BUILD_MAC=1;; + w) BUILD_WIN=1;; + l) BUILD_LINUX=1;; + *) + echo "$0: Invalid platform option ${OPTARG:i:1}" + usage + ;; + esac + done + ;; + c) + UPDATE_CHANNEL="$OPTARG" + ;; + t) + DEVTOOLS=1 + ;; + e) + SIGN=1 + ;; + s) + PACKAGE=0 + ;; + q) + quick_build=1 + ;; + *) + usage + ;; + esac + shift $((OPTIND-1)); OPTIND=1 +done + +function check_xulrunner_hash { + platform=$1 + + if [ $platform == "m" ]; then + platform_hash_file="hash-mac" + elif [ $platform == "w" ]; then + platform_hash_file="hash-win" + elif [ $platform == "l" ]; then + platform_hash_file="hash-linux" + else + echo "Platform parameter incorrect. Acceptable values: m/w/l" + exit 1 + fi + + if [ ! -e "$CALLDIR/xulrunner/$platform_hash_file" ]; then + echo "xulrunner not found -- downloading" + echo + $CALLDIR/scripts/fetch_xulrunner -p $platform + else + recalculated_xulrunner_hash=$("$CALLDIR/scripts/xulrunner_hash" -p $platform) + current_xulrunner_hash=$(< "$CALLDIR/xulrunner/$platform_hash_file") + if [ "$current_xulrunner_hash" != "$recalculated_xulrunner_hash" ]; then + echo "xulrunner hashes don't match -- redownloading" + echo + "$CALLDIR/scripts/fetch_xulrunner" -p $platform + current_xulrunner_hash=$(< "$CALLDIR/xulrunner/$platform_hash_file") + if [ "$current_xulrunner_hash" != "$recalculated_xulrunner_hash" ]; then + echo "xulrunner hashes don't match after running fetch_xulrunner!" + exit 1 + fi + fi + fi +} + +#Check if xulrunner and GECKO_VERSION for each platform match +if [ $BUILD_MAC == 1 ]; then + check_xulrunner_hash m +fi +if [ $BUILD_WIN == 1 ]; then + check_xulrunner_hash w +fi +if [ $BUILD_LINUX == 1 ]; then + check_xulrunner_hash l +fi + + + +# Require source dir or ZIP file +if [[ -z "$SOURCE_DIR" ]] && [[ -z "$ZIP_FILE" ]]; then + usage +elif [[ -n "$SOURCE_DIR" ]] && [[ -n "$ZIP_FILE" ]]; then + usage +fi + +# Require at least one platform +if [[ $BUILD_MAC == 0 ]] && [[ $BUILD_WIN == 0 ]] && [[ $BUILD_LINUX == 0 ]]; then + usage +fi + +if [[ -z "${ZOTERO_TEST:-}" ]] || [[ $ZOTERO_TEST == "0" ]]; then + include_tests=0 +else + include_tests=1 +fi + +# Bundle devtools with dev builds +if [ "$UPDATE_CHANNEL" == "beta" ] || [ "$UPDATE_CHANNEL" == "dev" ] || [ "$UPDATE_CHANNEL" == "test" ]; then + DEVTOOLS=1 +fi +if [ -z "$UPDATE_CHANNEL" ]; then UPDATE_CHANNEL="default"; fi +BUILD_ID=`date +%Y%m%d%H%M%S` + +# Paths to Gecko runtimes +MAC_RUNTIME_PATH="$CALLDIR/xulrunner/Firefox.app" +WIN_RUNTIME_PATH_PREFIX="$CALLDIR/xulrunner/firefox-" +LINUX_RUNTIME_PATH_PREFIX="$CALLDIR/xulrunner/firefox-" + +base_dir="$BUILD_DIR/base" +app_dir="$BUILD_DIR/base/app" +omni_dir="$BUILD_DIR/base/app/omni" + +shopt -s extglob +mkdir -p "$app_dir" +rm -rf "$STAGE_DIR" +mkdir "$STAGE_DIR" +rm -rf "$DIST_DIR" +mkdir "$DIST_DIR" + +# Save build id, which is needed for updates manifest +echo $BUILD_ID > "$DIST_DIR/build_id" + +cd "$app_dir" + +# Copy 'browser' files from Firefox +# +# omni.ja is left uncompressed within the Firefox application files by fetch_xulrunner +# +# TEMP: Also extract .hyf hyphenation files from the outer (still compressed) omni.ja +# This works around https://bugzilla.mozilla.org/show_bug.cgi?id=1772900 +set +e +if [ $BUILD_MAC == 1 ]; then + cp -Rp "$MAC_RUNTIME_PATH"/Contents/Resources/browser/omni "$app_dir" + unzip -qj "$MAC_RUNTIME_PATH"/Contents/Resources/omni.ja "hyphenation/*" -d "$app_dir"/hyphenation/ +elif [ $BUILD_WIN == 1 ]; then + # Non-arch-specific files, so just use 64-bit version + cp -Rp "${WIN_RUNTIME_PATH_PREFIX}win-x64"/browser/omni "$app_dir" + unzip -qj "${WIN_RUNTIME_PATH_PREFIX}win-x64"/omni.ja "hyphenation/*" -d "$app_dir"/hyphenation/ +elif [ $BUILD_LINUX == 1 ]; then + # Non-arch-specific files, so just use 64-bit version + cp -Rp "${LINUX_RUNTIME_PATH_PREFIX}x86_64"/browser/omni "$app_dir" + unzip -qj "${LINUX_RUNTIME_PATH_PREFIX}x86_64"/omni.ja "hyphenation/*" -d "$app_dir"/hyphenation/ +fi +set -e +cd $omni_dir +# Move some Firefox files that would be overwritten out of the way +mv chrome.manifest chrome.manifest-fx +mv defaults defaults-fx + +# Extract Zotero files +if [ -n "$ZIP_FILE" ]; then + ZIP_FILE="`abspath $ZIP_FILE`" + echo "Building from $ZIP_FILE" + unzip -q $ZIP_FILE -d "$omni_dir" +else + rsync_params="" + if [ $include_tests -eq 0 ]; then + rsync_params="--exclude /test" + fi + rsync -a $rsync_params "$SOURCE_DIR/" ./ +fi + +mv defaults defaults-z +mv defaults-fx defaults +prefs_file=defaults/preferences/zotero.js + +# Transfer Firefox prefs, omitting some with undesirable overrides from the base prefs +# +# - network.captive-portal-service.enabled +# Disable the captive portal check against Mozilla servers +# - extensions.systemAddon.update.url +egrep -v '(network.captive-portal-service.enabled|extensions.systemAddon.update.url)' defaults/preferences/firefox.js > $prefs_file +rm defaults/preferences/firefox.js + +# Combine app and "extension" Zotero prefs +echo "" >> $prefs_file +echo "#" >> $prefs_file +echo "# Zotero app prefs" >> $prefs_file +echo "#" >> $prefs_file +echo "" >> $prefs_file +cat "$CALLDIR/assets/prefs.js" >> $prefs_file +echo "" >> $prefs_file +echo "# Zotero extension prefs" >> $prefs_file +echo "" >> $prefs_file +cat defaults-z/preferences/zotero.js >> $prefs_file + +rm -rf defaults-z + +# Platform-specific prefs +if [ $BUILD_MAC == 1 ]; then + perl -pi -e 's/pref\("browser\.preferences\.instantApply", false\);/pref\("browser\.preferences\.instantApply", true);/' $prefs_file + perl -pi -e 's/%GECKO_VERSION%/'"$GECKO_VERSION_MAC"'/g' $prefs_file + # Fix horizontal mousewheel scrolling (this is set to 4 in the Fx60 .app greprefs.js, but + # defaults to 1 in later versions of Firefox, and needs to be 1 to work on macOS) + echo 'pref("mousewheel.with_shift.action", 1);' >> $prefs_file +elif [ $BUILD_WIN == 1 ]; then + perl -pi -e 's/%GECKO_VERSION%/'"$GECKO_VERSION_WIN"'/g' $prefs_file +elif [ $BUILD_LINUX == 1 ]; then + # Modify platform-specific prefs + perl -pi -e 's/pref\("browser\.preferences\.instantApply", false\);/pref\("browser\.preferences\.instantApply", true);/' $prefs_file + perl -pi -e 's/%GECKO_VERSION%/'"$GECKO_VERSION_LINUX"'/g' $prefs_file +fi + +# Clear list of built-in add-ons +echo '{"dictionaries": {"en-US": "dictionaries/en-US.dic"}, "system": []}' > chrome/browser/content/browser/built_in_addons.json + +# chrome.manifest +mv chrome.manifest zotero.manifest +mv chrome.manifest-fx chrome.manifest +# TEMP +#echo "manifest zotero.manifest" >> "$base_dir/chrome.manifest" +cat zotero.manifest >> chrome.manifest +rm zotero.manifest + +# Update channel +perl -pi -e 's/pref\("app\.update\.channel", "[^"]*"\);/pref\("app\.update\.channel", "'"$UPDATE_CHANNEL"'");/' $prefs_file +echo -n "Channel: " +grep app.update.channel $prefs_file +echo + +# Add devtools prefs +if [ $DEVTOOLS -eq 1 ]; then + echo >> $prefs_file + echo "// Dev Tools" >> $prefs_file + echo 'pref("devtools.debugger.remote-enabled", true);' >> $prefs_file + echo 'pref("devtools.debugger.remote-port", 6100);' >> $prefs_file + if [ $UPDATE_CHANNEL != "beta" ]; then + echo 'pref("devtools.debugger.prompt-connection", false);' >> $prefs_file + fi +fi + +# 5.0.96.3 / 5.0.97-beta.37+ddc7be75c +VERSION=`cat version` +# 5.0.96 / 5.0.97 +VERSION_NUMERIC=`perl -ne 'print and last if s/^(\d+\.\d+(\.\d+)?).*/\1/;' version` +if [ -z "$VERSION" ]; then + echo "Version number not found in version file" + exit 1 +fi +rm version + +echo +echo "Version: $VERSION" + +# Delete Mozilla signing info if present +rm -rf META-INF + +# Copy branding +cp -R "$CALLDIR"/assets/branding/locale/brand.{dtd,properties} chrome/en-US/locale/branding/ + +# Copy browser localization .ftl files +for locale in `ls chrome/locale/`; do + mkdir -p localization/$locale/branding + cp "$CALLDIR/assets/branding/locale/brand.ftl" localization/$locale/branding/brand.ftl + + mkdir -p localization/$locale/toolkit/global + cp chrome/locale/$locale/zotero/mozilla/textActions.ftl localization/$locale/toolkit/global + cp chrome/locale/$locale/zotero/mozilla/wizard.ftl localization/$locale/toolkit/global + + mkdir -p localization/$locale/browser + cp chrome/locale/$locale/zotero/mozilla/browserSets.ftl localization/$locale/browser + cp chrome/locale/$locale/zotero/mozilla/menubar.ftl localization/$locale/browser + + mkdir -p localization/$locale/devtools/client + cp chrome/locale/$locale/zotero/mozilla/toolbox.ftl localization/$locale/devtools/client + + # TEMP: Until we've created zotero.ftl in all locales + touch chrome/locale/$locale/zotero/zotero.ftl + cp chrome/locale/$locale/zotero/*.ftl localization/$locale/ +done + +# Add to chrome manifest +echo "" >> chrome.manifest +cat "$CALLDIR/assets/chrome.manifest" >> chrome.manifest + +replace_line 'handle: function bch_handle\(cmdLine\) {' 'handle: function bch_handle(cmdLine) { + \/\/ TEST_OPTIONS_PLACEHOLDER + ' modules/BrowserContentHandler.sys.mjs +export CALLDIR && perl -pi -e 'BEGIN { local $/; open $fh, "$ENV{CALLDIR}/assets/commandLineHandler.js"; $replacement = <$fh>; close $fh; } s/\/\/ TEST_OPTIONS_PLACEHOLDER/$replacement/' modules/BrowserContentHandler.sys.mjs + +# Move test files to root directory +if [ $include_tests -eq 1 ]; then + cat test/chrome.manifest >> chrome.manifest + rm test/chrome.manifest + cp -R test/tests "$base_dir/tests" +fi + +# Copy platform-specific assets +if [ $BUILD_MAC == 1 ]; then + rsync -a "$CALLDIR/assets/mac/" ./ +elif [ $BUILD_WIN == 1 ]; then + rsync -a "$CALLDIR/assets/win/" ./ +elif [ $BUILD_LINUX == 1 ]; then + rsync -a "$CALLDIR/assets/unix/" ./ +fi + +# Add word processor plug-ins +echo >> chrome.manifest +if [ $BUILD_MAC == 1 ]; then + pluginDir="$CALLDIR/modules/zotero-word-for-mac-integration" + mkdir -p "integration/word-for-mac" + cp -RH "$pluginDir/components" \ + "$pluginDir/resource" \ + "$pluginDir/chrome.manifest" \ + "integration/word-for-mac" + echo -n "Word for Mac plugin version: " + cat "integration/word-for-mac/resource/version.txt" + echo + echo >> $prefs_file + cat "$CALLDIR/modules/zotero-word-for-mac-integration/defaults/preferences/zoteroMacWordIntegration.js" >> $prefs_file + echo >> $prefs_file + + echo "manifest integration/word-for-mac/chrome.manifest" >> chrome.manifest + +elif [ $BUILD_WIN == 1 ]; then + pluginDir="$CALLDIR/modules/zotero-word-for-windows-integration" + mkdir -p "integration/word-for-windows" + cp -RH "$pluginDir/components" \ + "$pluginDir/resource" \ + "$pluginDir/chrome.manifest" \ + "integration/word-for-windows" + echo -n "Word for Windows plugin version: " + cat "integration/word-for-windows/resource/version.txt" + echo + echo >> $prefs_file + cat "$CALLDIR/modules/zotero-word-for-windows-integration/defaults/preferences/zoteroWinWordIntegration.js" >> $prefs_file + echo >> $prefs_file + + echo "manifest integration/word-for-windows/chrome.manifest" >> chrome.manifest +fi +# Libreoffice plugin for all platforms +pluginDir="$CALLDIR/modules/zotero-libreoffice-integration" +mkdir -p "integration/libreoffice" +cp -RH "$pluginDir/chrome" \ + "$pluginDir/components" \ + "$pluginDir/resource" \ + "$pluginDir/chrome.manifest" \ + "integration/libreoffice" +echo -n "LibreOffice plugin version: " +cat "integration/libreoffice/resource/version.txt" +echo +echo >> $prefs_file +cat "$CALLDIR/modules/zotero-libreoffice-integration/defaults/preferences/zoteroLibreOfficeIntegration.js" >> $prefs_file +echo >> $prefs_file + +echo "manifest integration/libreoffice/chrome.manifest" >> chrome.manifest + +# Delete files that shouldn't be distributed +find chrome -name .DS_Store -exec rm -f {} \; + +# Zip browser and Zotero files into omni.ja +if [ $quick_build -eq 1 ]; then + # If quick build, don't compress or optimize + zip -qrXD omni.ja * +else + zip -qr9XD omni.ja * + python3 "$CALLDIR/scripts/optimizejars.py" --optimize ./ ./ ./ +fi + +mv omni.ja .. +cd "$CALLDIR" +rm -rf "$omni_dir" + +# Copy updater.ini +cp "$CALLDIR/assets/updater.ini" "$base_dir" + +# Adjust chrome.manifest +#perl -pi -e 's^(chrome|resource)/^jar:zotero.jar\!/$1/^g' "$BUILD_DIR/zotero/chrome.manifest" + +# Copy application.ini and modify +cp "$CALLDIR/assets/application.ini" "$app_dir/application.ini" +perl -pi -e "s/\{\{VERSION}}/$VERSION/" "$app_dir/application.ini" +perl -pi -e "s/\{\{BUILDID}}/$BUILD_ID/" "$app_dir/application.ini" + +# Remove unnecessary files +find "$BUILD_DIR" -name .DS_Store -exec rm -f {} \; + +# Mac +if [ $BUILD_MAC == 1 ]; then + echo 'Building Zotero.app' + + # Set up directory structure + APPDIR="$STAGE_DIR/Zotero.app" + rm -rf "$APPDIR" + mkdir "$APPDIR" + chmod 755 "$APPDIR" + cp -r "$CALLDIR/mac/Contents" "$APPDIR" + CONTENTSDIR="$APPDIR/Contents" + + # Merge relevant assets from Firefox + mkdir "$CONTENTSDIR/MacOS" + cp -r "$MAC_RUNTIME_PATH/Contents/MacOS/"!(firefox|firefox-bin|crashreporter.app|pingsender|updater.app) "$CONTENTSDIR/MacOS" + cp -r "$MAC_RUNTIME_PATH/Contents/Resources/"!(application.ini|browser|defaults|precomplete|removed-files|updater.ini|update-settings.ini|webapprt*|*.icns|*.lproj) "$CONTENTSDIR/Resources" + + # Use our own launcher + check_lfs_file "$CALLDIR/mac/zotero.xz" + xz -d --stdout "$CALLDIR/mac/zotero.xz" > "$CONTENTSDIR/MacOS/zotero" + chmod 755 "$CONTENTSDIR/MacOS/zotero" + + # TEMP: Custom version of XUL with some backported Mozilla bug fixes + cp "$MAC_RUNTIME_PATH/../MacOS/XUL" "$CONTENTSDIR/MacOS/" + + # Use our own updater, because Mozilla's requires updates signed by Mozilla + cd "$CONTENTSDIR/MacOS" + check_lfs_file "$CALLDIR/mac/updater.tar.xz" + tar xf "$CALLDIR/mac/updater.tar.xz" + + # Modify Info.plist + perl -pi -e "s/\{\{VERSION\}\}/$VERSION/" "$CONTENTSDIR/Info.plist" + perl -pi -e "s/\{\{VERSION_NUMERIC\}\}/$VERSION_NUMERIC/" "$CONTENTSDIR/Info.plist" + if [ $UPDATE_CHANNEL == "beta" ] || [ $UPDATE_CHANNEL == "dev" ] || [ $UPDATE_CHANNEL == "source" ]; then + perl -pi -e "s/org\.zotero\.zotero/org.zotero.zotero-$UPDATE_CHANNEL/" "$CONTENTSDIR/Info.plist" + fi + perl -pi -e "s/\{\{VERSION\}\}/$VERSION/" "$CONTENTSDIR/Info.plist" + # Needed for "monkeypatch" Windows builds: + # http://www.nntp.perl.org/group/perl.perl5.porters/2010/08/msg162834.html + rm -f "$CONTENTSDIR/Info.plist.bak" + + echo + grep -B 1 org.zotero.zotero "$CONTENTSDIR/Info.plist" + echo + grep -A 1 CFBundleShortVersionString "$CONTENTSDIR/Info.plist" + echo + grep -A 1 CFBundleVersion "$CONTENTSDIR/Info.plist" + echo + + # Copy app files + rsync -a "$base_dir/" "$CONTENTSDIR/Resources/" + + # Add word processor plug-ins + mkdir "$CONTENTSDIR/Resources/integration" + cp -RH "$CALLDIR/modules/zotero-libreoffice-integration/install" "$CONTENTSDIR/Resources/integration/libreoffice" + cp -RH "$CALLDIR/modules/zotero-word-for-mac-integration/install" "$CONTENTSDIR/Resources/integration/word-for-mac" + + # Delete extraneous files + find "$CONTENTSDIR" -depth -type d -name .git -exec rm -rf {} \; + find "$CONTENTSDIR" \( -name .DS_Store -or -name update.rdf \) -exec rm -f {} \; + + # Copy over removed-files and make a precomplete file here since it needs to be stable for the + # signature. This is done in build_autocomplete.sh for other platforms. + cp "$CALLDIR/update-packaging/removed-files_mac" "$CONTENTSDIR/Resources/removed-files" + touch "$CONTENTSDIR/Resources/precomplete" + + # Sign + if [ $SIGN == 1 ]; then + # Unlock keychain if a password is provided (necessary for building from a shell) + if [ -n "$KEYCHAIN_PASSWORD" ]; then + security -v unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/$KEYCHAIN.keychain-db + fi + # Clear extended attributes, which can cause codesign to fail + /usr/bin/xattr -cr "$APPDIR" + + # Sign app + entitlements_file="$CALLDIR/mac/entitlements.xml" + /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" \ + "$APPDIR/Contents/MacOS/XUL" \ + "$APPDIR/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater" + find "$APPDIR/Contents" -name '*.dylib' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; + find "$APPDIR/Contents" -name '*.app' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; + /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" "$APPDIR/Contents/MacOS/zotero" + + # Bundle and sign Safari App Extension + # + # Even though it's signed by Xcode, we sign it again to make sure it matches the parent app signature + if [[ -n "$SAFARI_APPEX" ]] && [[ -d "$SAFARI_APPEX" ]]; then + echo + # Extract entitlements, which differ from parent app + /usr/bin/codesign -d --entitlements :"$BUILD_DIR/safari-entitlements.plist" $SAFARI_APPEX + mkdir "$APPDIR/Contents/PlugIns" + cp -R $SAFARI_APPEX "$APPDIR/Contents/PlugIns/ZoteroSafariExtension.appex" + # Add suffix to appex bundle identifier + if [ $UPDATE_CHANNEL == "beta" ] || [ $UPDATE_CHANNEL == "dev" ] || [ $UPDATE_CHANNEL == "source" ]; then + perl -pi -e "s/org\.zotero\.SafariExtensionApp\.SafariExtension/org.zotero.SafariExtensionApp.SafariExtension-$UPDATE_CHANNEL/" "$APPDIR/Contents/PlugIns/ZoteroSafariExtension.appex/Contents/Info.plist" + fi + find "$APPDIR/Contents/PlugIns/ZoteroSafariExtension.appex/Contents" -name '*.dylib' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; + /usr/bin/codesign --force --options runtime --entitlements "$BUILD_DIR/safari-entitlements.plist" --sign "$DEVELOPER_ID" "$APPDIR/Contents/PlugIns/ZoteroSafariExtension.appex" + fi + + # Sign final app package + echo + /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" "$APPDIR" + + # Verify app + /usr/bin/codesign --verify -vvvv "$APPDIR" + # Verify Safari App Extension + if [[ -n "$SAFARI_APPEX" ]] && [[ -d "$SAFARI_APPEX" ]]; then + echo + /usr/bin/codesign --verify -vvvv "$APPDIR/Contents/PlugIns/ZoteroSafariExtension.appex" + fi + fi + + # Build and notarize disk image + if [ $PACKAGE == 1 ]; then + if [ $MAC_NATIVE == 1 ]; then + echo "Creating Mac installer" + dmg="$DIST_DIR/Zotero-$VERSION.dmg" + "$CALLDIR/mac/pkg-dmg" --source "$STAGE_DIR/Zotero.app" \ + --target "$dmg" \ + --sourcefile --volname Zotero --copy "$CALLDIR/mac/DSStore:/.DS_Store" \ + --symlink /Applications:"/Drag Here to Install" > /dev/null + + if [ "$UPDATE_CHANNEL" != "test" ]; then + # Upload disk image to Apple + "$CALLDIR/scripts/notarize_mac_app" "$dmg" + echo + + # Staple notarization info to disk image + "$CALLDIR/scripts/notarization_stapler" "$dmg" + + echo "Notarization complete" + else + echo "Test build -- skipping notarization" + fi + echo + else + echo 'Not building on Mac; creating Mac distribution as a zip file' + rm -f "$DIST_DIR/Zotero_mac.zip" + cd "$STAGE_DIR" && zip -rqX "$DIST_DIR/Zotero-${VERSION}_mac.zip" Zotero.app + fi + fi +fi + +# Windows +if [ $BUILD_WIN == 1 ]; then + echo "Building Windows common" + + COMMON_APPDIR="$STAGE_DIR/Zotero_common" + mkdir "$COMMON_APPDIR" + + # Package non-arch-specific components + if [ $PACKAGE -eq 1 ]; then + # Copy installer files + cp -r "$CALLDIR/win/installer" "$BUILD_DIR/win_installer" + + perl -pi -e "s/\{\{VERSION}}/$VERSION/" "$BUILD_DIR/win_installer/defines.nsi" + mkdir "$COMMON_APPDIR/uninstall" + + # Compress 7zSD.sfx + upx --best -o "`cygpath -w \"$BUILD_DIR/7zSD.sfx\"`" \ + "`cygpath -w \"$CALLDIR/win/installer/7zstub/firefox/7zSD.sfx\"`" > /dev/null + + fi + + for arch in win32 win-x64 win-aarch64; do + echo "Building Zotero_$arch" + + runtime_path="${WIN_RUNTIME_PATH_PREFIX}${arch}" + + # Set up directory + APPDIR="$STAGE_DIR/Zotero_$arch" + mkdir "$APPDIR" + + # Copy relevant assets from Firefox + cp -R "$runtime_path"/!(application.ini|browser|defaults|devtools-files|crashreporter*|firefox.exe|maintenanceservice*|precomplete|removed-files|uninstall|update*) "$APPDIR" + + # Copy zotero.exe, which is built directly from Firefox source and then modified by + # ResourceHacker to add icons + check_lfs_file "$CALLDIR/win/zotero.exe.tar.xz" + tar xf "$CALLDIR/win/zotero.exe.tar.xz" --to-stdout zotero_$arch.exe > "$APPDIR/zotero.exe" + + # Update .exe version number (only possible on Windows) + if [ $WIN_NATIVE == 1 ]; then + # FileVersion is limited to four integers, so it won't be properly updated for non-release + # builds (e.g., we'll show 5.0.97.0 for 5.0.97-beta.37). ProductVersion will be the full + # version string. + rcedit "`cygpath -w \"$APPDIR/zotero.exe\"`" \ + --set-file-version "$VERSION_NUMERIC" \ + --set-product-version "$VERSION" + fi + + # Use our own updater, because Mozilla's requires updates signed by Mozilla + check_lfs_file "$CALLDIR/win/updater.exe.tar.xz" + tar xf "$CALLDIR/win/updater.exe.tar.xz" --to-stdout updater-$arch.exe > "$APPDIR/updater.exe" + cat "$CALLDIR/win/installer/updater_append.ini" >> "$APPDIR/updater.ini" + + # Sign updater + if [ $SIGN -eq 1 ]; then + "$CALLDIR/win/codesign" "$APPDIR/updater.exe" "$SIGNATURE_DESC Updater" + fi + + # Copy app files + rsync -a "$base_dir/" "$APPDIR/" + #mv "$APPDIR/app/application.ini" "$APPDIR/" + + # Copy in common files + rsync -a "$COMMON_APPDIR/" "$APPDIR/" + + # Add devtools + #if [ $DEVTOOLS -eq 1 ]; then + # # Create devtools.jar + # cd "$BUILD_DIR" + # mkdir -p devtools/locale + # cp -r "$runtime_path"/devtools-files/chrome/devtools/* devtools/ + # cp -r "$runtime_path"/devtools-files/chrome/locale/* devtools/locale/ + # cd devtools + # zip -r -q ../devtools.jar * + # cd .. + # rm -rf devtools + # mv devtools.jar "$APPDIR" + # + # cp "$runtime_path/devtools-files/components/interfaces.xpt" "$APPDIR/components/" + #fi + + # Add word processor plug-ins + mkdir -p "$APPDIR/integration" + cp -RH "$CALLDIR/modules/zotero-libreoffice-integration/install" "$APPDIR/integration/libreoffice" + cp -RH "$CALLDIR/modules/zotero-word-for-windows-integration/install" "$APPDIR/integration/word-for-windows" + if [ $arch = 'win32' ]; then + rm "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_x64.dll" + rm "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_ARM64.dll" + elif [ $arch = 'win-x64' ]; then + mv "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_x64.dll" \ + "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration.dll" + rm "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_ARM64.dll" + elif [ $arch = 'win-aarch64' ]; then + mv "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_ARM64.dll" \ + "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration.dll" + rm "$APPDIR/integration/word-for-windows/libzoteroWinWordIntegration_x64.dll" + fi + + # Delete extraneous files + find "$APPDIR" -depth -type d -name .git -exec rm -rf {} \; + find "$APPDIR" \( -name .DS_Store -or -name '.git*' -or -name '.travis.yml' -or -name update.rdf -or -name '*.bak' \) -exec rm -f {} \; + find "$APPDIR" \( -name '*.exe' -or -name '*.dll' \) -exec chmod 755 {} \; + + if [ $PACKAGE -eq 1 ]; then + if [ $WIN_NATIVE -eq 1 ]; then + echo "Creating Windows installer" + # Build uninstaller + if [ "$arch" = "win32" ]; then + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /V1 "`cygpath -w \"$BUILD_DIR/win_installer/uninstaller.nsi\"`" + elif [ "$arch" = "win-x64" ]; then + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /DHAVE_64BIT_OS /V1 "`cygpath -w \"$BUILD_DIR/win_installer/uninstaller.nsi\"`" + elif [ "$arch" = "win-aarch64" ]; then + # TODO + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /DHAVE_64BIT_OS /V1 "`cygpath -w \"$BUILD_DIR/win_installer/uninstaller.nsi\"`" + fi + + mv "$BUILD_DIR/win_installer/helper.exe" "$APPDIR/uninstall" + + if [ $SIGN -eq 1 ]; then + "$CALLDIR/win/codesign" "$APPDIR/uninstall/helper.exe" "$SIGNATURE_DESC Uninstaller" + sleep $SIGNTOOL_DELAY + fi + + + if [ "$arch" = "win32" ]; then + INSTALLER_PATH="$DIST_DIR/Zotero-${VERSION}_win32_setup.exe" + elif [ "$arch" = "win-x64" ]; then + INSTALLER_PATH="$DIST_DIR/Zotero-${VERSION}_x64_setup.exe" + elif [ "$arch" = "win-aarch64" ]; then + INSTALLER_PATH="$DIST_DIR/Zotero-${VERSION}_aarch64_setup.exe" + fi + + if [ $SIGN -eq 1 ]; then + "$CALLDIR/win/codesign" "$APPDIR/zotero.exe" "$SIGNATURE_DESC" + sleep $SIGNTOOL_DELAY + fi + + # Stage installer + INSTALLER_STAGE_DIR="$BUILD_DIR/win_installer/staging" + rm -rf "$INSTALLER_STAGE_DIR" + mkdir "$INSTALLER_STAGE_DIR" + cp -r "$APPDIR" "$INSTALLER_STAGE_DIR/core" + + # Build and sign setup.exe + if [ "$arch" = "win32" ]; then + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /V1 "`cygpath -w \"$BUILD_DIR/win_installer/installer.nsi\"`" + elif [ "$arch" = "win-x64" ]; then + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /DHAVE_64BIT_OS /V1 "`cygpath -w \"$BUILD_DIR/win_installer/installer.nsi\"`" + elif [ "$arch" = "win-aarch64" ]; then + # TODO + "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" /DHAVE_64BIT_OS /V1 "`cygpath -w \"$BUILD_DIR/win_installer/installer.nsi\"`" + fi + + mv "$BUILD_DIR/win_installer/setup.exe" "$INSTALLER_STAGE_DIR" + + if [ $SIGN == 1 ]; then + "$CALLDIR/win/codesign" "$INSTALLER_STAGE_DIR/setup.exe" "$SIGNATURE_DESC Setup" + sleep $SIGNTOOL_DELAY + fi + + # Compress application + cd "$INSTALLER_STAGE_DIR" && 7z a -r -t7z "`cygpath -w \"$BUILD_DIR/app_$arch.7z\"`" \ + -mx -m0=BCJ2 -m1=LZMA:d24 -m2=LZMA:d19 -m3=LZMA:d19 -mb0:1 -mb0s1:2 -mb0s2:3 > /dev/null + + # Combine 7zSD.sfx and app.tag into setup.exe + cat "$BUILD_DIR/7zSD.sfx" "$CALLDIR/win/installer/app.tag" \ + "$BUILD_DIR/app_$arch.7z" > "$INSTALLER_PATH" + + # Sign installer .exe + if [ $SIGN == 1 ]; then + "$CALLDIR/win/codesign" "$INSTALLER_PATH" "$SIGNATURE_DESC Installer" + fi + + chmod 755 "$INSTALLER_PATH" + else + echo 'Not building on Windows; only building zip file' + fi + cd "$STAGE_DIR" + zip -rqX "$DIST_DIR/Zotero-${VERSION}_$arch.zip" Zotero_$arch + fi + done + + rm -rf "$COMMON_APPDIR" +fi + +# Linux +if [ $BUILD_LINUX == 1 ]; then + # Skip 32-bit build in tests + if [[ "${ZOTERO_TEST:-}" = "1" ]] || [[ "${SKIP_32:-}" = "1" ]]; then + archs="x86_64" + else + archs="i686 x86_64" + fi + + for arch in $archs; do + runtime_path="${LINUX_RUNTIME_PATH_PREFIX}${arch}" + + # Set up directory + echo 'Building Zotero_linux-'$arch + APPDIR="$STAGE_DIR/Zotero_linux-$arch" + rm -rf "$APPDIR" + mkdir "$APPDIR" + + # Merge relevant assets from Firefox + cp -r "$runtime_path/"!(application.ini|browser|defaults|devtools-files|crashreporter|crashreporter.ini|firefox|pingsender|precomplete|removed-files|run-mozilla.sh|update-settings.ini|updater|updater.ini) "$APPDIR" + + # Use our own launcher that calls the original Firefox executable with -app + mv "$APPDIR"/firefox-bin "$APPDIR"/zotero-bin + cp "$CALLDIR/linux/zotero" "$APPDIR"/zotero + + # Copy Ubuntu launcher files + cp "$CALLDIR/linux/zotero.desktop" "$APPDIR" + cp "$CALLDIR/linux/set_launcher_icon" "$APPDIR" + + # Use our own updater, because Mozilla's requires updates signed by Mozilla + check_lfs_file "$CALLDIR/linux/updater.tar.xz" + tar xf "$CALLDIR/linux/updater.tar.xz" --to-stdout updater-$arch > "$APPDIR/updater" + chmod 755 "$APPDIR/updater" + + # Copy app files + rsync -a "$base_dir/" "$APPDIR/" + + # Add word processor plug-ins + mkdir "$APPDIR/integration" + cp -RH "$CALLDIR/modules/zotero-libreoffice-integration/install" "$APPDIR/integration/libreoffice" + + # Copy icons + cp "$CALLDIR/linux/icons/icon32.png" "$APPDIR/icons/" + cp "$CALLDIR/linux/icons/icon64.png" "$APPDIR/icons/" + cp "$CALLDIR/linux/icons/icon128.png" "$APPDIR/icons/" + cp "$CALLDIR/linux/icons/symbolic.svg" "$APPDIR/icons/" + + # Delete extraneous files + find "$APPDIR" -depth -type d -name .git -exec rm -rf {} \; + find "$APPDIR" \( -name .DS_Store -or -name update.rdf \) -exec rm -f {} \; + + if [ $PACKAGE == 1 ]; then + # Create tar + rm -f "$DIST_DIR/Zotero-${VERSION}_linux-$arch.tar.bz2" + cd "$STAGE_DIR" + tar -cjf "$DIST_DIR/Zotero-${VERSION}_linux-$arch.tar.bz2" "Zotero_linux-$arch" + fi + done +fi + +rm -rf $BUILD_DIR diff --git a/app/config.sh b/app/config.sh new file mode 100644 index 0000000000..e1c53dcf3d --- /dev/null +++ b/app/config.sh @@ -0,0 +1,68 @@ +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Version of Gecko to build with +GECKO_VERSION_MAC="115.9.1esr" +GECKO_VERSION_LINUX="115.9.1esr" +GECKO_VERSION_WIN="115.9.1esr" +RUST_VERSION=1.69.0 + +# URL prefix for custom builds of Firefox components +custom_components_url="https://download.zotero.org/dev/firefox-components/" +custom_components_hash_mac="bec8e3adebf8d5021f1f35fd2c65d752d4979839cbdd1ee1aa4b1d3d5ba0953b" + +APP_NAME="Zotero" +APP_ID="zotero\@zotero.org" + +# Whether to sign builds +SIGN=0 + +# OS X Developer ID certificate information +DEVELOPER_ID=F0F1FE48DB909B263AC51C8215374D87FDC12121 +# Keychain and keychain password, if not building via the GUI +KEYCHAIN="" +KEYCHAIN_PASSWORD="" +NOTARIZATION_BUNDLE_ID="" +NOTARIZATION_USER="" +NOTARIZATION_TEAM_ID="" +NOTARIZATION_PASSWORD="" + +# Paths for Windows installer build +NSIS_DIR='C:\Program Files (x86)\NSIS\' + +SIGNTOOL_DELAY=5 + +# Directory for unpacked binaries +STAGE_DIR="$DIR/staging" +# Directory for packed binaries +DIST_DIR="$DIR/dist" + +SOURCE_REPO_URL="https://github.com/zotero/zotero" +S3_BUCKET="zotero-download" +S3_CI_ZIP_PATH="ci/client" +S3_DIST_PATH="client" + +DEPLOY_HOST="deploy.zotero" +DEPLOY_PATH="www/www-production/public/download/client/manifests" +DEPLOY_CMD="ssh $DEPLOY_HOST update-site-files" + +BUILD_PLATFORMS="" +NUM_INCREMENTALS=6 + +if [ "`uname`" = "Darwin" ]; then + shopt -s expand_aliases +fi + +if [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then + export WIN_NATIVE=1 +else + export WIN_NATIVE=0 +fi + +# Make utilities (mar/mbsdiff) available in the path +PATH="$DIR/xulrunner/bin:$PATH" + +if [ -f "$DIR/config-custom.sh" ]; then + . "$DIR/config-custom.sh" +fi + +unset DIR diff --git a/app/linux/icons/icon128.png b/app/linux/icons/icon128.png new file mode 100644 index 0000000000..4a615089e4 Binary files /dev/null and b/app/linux/icons/icon128.png differ diff --git a/app/linux/icons/icon32.png b/app/linux/icons/icon32.png new file mode 100644 index 0000000000..21ad5ded22 Binary files /dev/null and b/app/linux/icons/icon32.png differ diff --git a/app/linux/icons/icon64.png b/app/linux/icons/icon64.png new file mode 100644 index 0000000000..c15b15697f Binary files /dev/null and b/app/linux/icons/icon64.png differ diff --git a/app/linux/icons/symbolic.svg b/app/linux/icons/symbolic.svg new file mode 100644 index 0000000000..5885fdba06 --- /dev/null +++ b/app/linux/icons/symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/linux/mozconfig b/app/linux/mozconfig new file mode 100644 index 0000000000..8e7fa5cf88 --- /dev/null +++ b/app/linux/mozconfig @@ -0,0 +1,27 @@ +# Uncomment to build 32-bit +#ac_add_options --target=i686 + +ac_add_options --enable-bootstrap + +mk_add_options AUTOCLOBBER=1 + +# These don't all affect the stub, but they can't hurt, and we'll want them if +# we switch to custom XUL builds +ac_add_options MOZ_ENABLE_JS_DUMP=1 +ac_add_options MOZ_ENABLE_FORKSERVER= +ac_add_options MOZ_TELEMETRY_REPORTING= +ac_add_options MOZ_DATA_REPORTING= +ac_add_options --disable-tests +ac_add_options --disable-debug +ac_add_options --disable-debug-symbols +ac_add_options --disable-webrtc +ac_add_options --disable-eme + +export MOZILLA_OFFICIAL=1 +export RELEASE_OR_BETA=1 +MOZ_REQUIRE_SIGNING= + +ac_add_options --enable-official-branding + +# Build updater without MAR signature verification +ac_add_options --disable-verify-mar diff --git a/app/linux/set_launcher_icon b/app/linux/set_launcher_icon new file mode 100755 index 0000000000..002377be85 --- /dev/null +++ b/app/linux/set_launcher_icon @@ -0,0 +1,13 @@ +#!/bin/bash -e + +# +# Run this to update the launcher file with the current path to the application icon +# + +APPDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -w "$APPDIR"/zotero.desktop ]; then + sed -i -e "s@^Icon=.*@Icon=$APPDIR/icons/icon128.png@" "$APPDIR"/zotero.desktop +else + echo "$APPDIR"/zotero.desktop is not writable + exit 1 +fi diff --git a/app/linux/updater.tar.xz b/app/linux/updater.tar.xz new file mode 100644 index 0000000000..5df9e2da4c --- /dev/null +++ b/app/linux/updater.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7e9c81d2f86ec0f70d75107a022649292889807bc8b0b7eaf1bdfd5c1030bf1 +size 74516 diff --git a/app/linux/zotero b/app/linux/zotero new file mode 100755 index 0000000000..fbaa031ce3 --- /dev/null +++ b/app/linux/zotero @@ -0,0 +1,20 @@ +#!/bin/bash + +# Increase open files limit +# +# Mozilla file functions (OS.File.move()/copy(), NetUtil.asyncFetch/asyncCopy()) can leave file +# descriptors open for a few seconds (even with an explicit inputStream.close() in the case of +# the latter), so a source installation that copies ~500 translators and styles (with fds for +# source and target) can exceed the default 1024 limit. +# Current hard-limit on Ubuntu 16.10 is 4096 +ulimit -n 4096 + +# Allow profile downgrade for Zotero +export MOZ_ALLOW_DOWNGRADE=1 +# Don't create dedicated profile (default-esr) +export MOZ_LEGACY_PROFILES=1 +# Use wayland backend when available +export MOZ_ENABLE_WAYLAND=1 + +CALLDIR="$(dirname "$(readlink -f "$0")")" +"$CALLDIR/zotero-bin" -app "$CALLDIR/app/application.ini" "$@" diff --git a/app/linux/zotero.desktop b/app/linux/zotero.desktop new file mode 100755 index 0000000000..705d0b3d46 --- /dev/null +++ b/app/linux/zotero.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Zotero +Exec=bash -c "$(dirname $(realpath $(echo %k | sed -e 's/^file:\/\///')))/zotero -url %U" +Icon=zotero.ico +Type=Application +Terminal=false +Categories=Office; +MimeType=text/plain;x-scheme-handler/zotero;application/x-research-info-systems;text/x-research-info-systems;text/ris;application/x-endnote-refer;application/x-inst-for-Scientific-info;application/mods+xml;application/rdf+xml;application/x-bibtex;text/x-bibtex;application/marc;application/vnd.citationstyles.style+xml +X-GNOME-SingleWindow=true diff --git a/app/mac/Contents/Info.plist b/app/mac/Contents/Info.plist new file mode 100644 index 0000000000..95332c7775 --- /dev/null +++ b/app/mac/Contents/Info.plist @@ -0,0 +1,204 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + + CFBundleTypeExtensions + + ris + + + CFBundleTypeMIMETypes + + application/x-research-info-systems + text/x-research-info-systems + text/ris + ris + application/x-endnote-refer + + CFBundleTypeName + Research Information Systems Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + ciw + isi + + + CFBundleTypeMIMETypes + + application/x-inst-for-Scientific-info + + CFBundleTypeName + ISI Common Export Format Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + mods + + + CFBundleTypeMIMETypes + + application/mods+xml + + CFBundleTypeName + Metadata Object Description Schema Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + rdf + + + CFBundleTypeMIMETypes + + application/rdf+xml + + CFBundleTypeName + Resource Description Framework Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + bib + bibtex + + + CFBundleTypeMIMETypes + + application/x-bibtex + text/x-bibtex + + CFBundleTypeName + BibTeX Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + mrc + marc + + + CFBundleTypeMIMETypes + + application/marc + + CFBundleTypeName + MARC Document + CFBundleTypeRole + Viewer + + + + + CFBundleTypeExtensions + + csl + csl.txt + + + CFBundleTypeMIMETypes + + application/vnd.citationstyles.style+xml + + CFBundleTypeName + CSL Citation Style + CFBundleTypeRole + Viewer + + + + + CFBundleTypeExtensions + + xml + + + CFBundleTypeName + XML Document + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + txt + + + CFBundleTypeName + Text File + CFBundleTypeRole + Viewer + + + CFBundleExecutable + zotero + CFBundleGetInfoString + Zotero {{VERSION}}, © 2006-2018 Contributors + CFBundleIconFile + zotero + CFBundleIdentifier + org.zotero.zotero + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Zotero + CFBundlePackageType + APPL + CFBundleShortVersionString + {{VERSION_NUMERIC}} + CFBundleSignature + ZOTR + CFBundleURLTypes + + + + CFBundleURLName + zotero URL + CFBundleURLSchemes + + zotero + + + + CFBundleVersion + {{VERSION_NUMERIC}} + NSAppleScriptEnabled + + CGDisableCoalescedUpdates + + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + + LSMinimumSystemVersion + 10.9.0 + + diff --git a/app/mac/Contents/PkgInfo b/app/mac/Contents/PkgInfo new file mode 100644 index 0000000000..7591ec70b1 --- /dev/null +++ b/app/mac/Contents/PkgInfo @@ -0,0 +1 @@ +APPLZOTR \ No newline at end of file diff --git a/app/mac/Contents/Resources/zotero.icns b/app/mac/Contents/Resources/zotero.icns new file mode 100644 index 0000000000..bb374b2890 Binary files /dev/null and b/app/mac/Contents/Resources/zotero.icns differ diff --git a/app/mac/DSStore b/app/mac/DSStore new file mode 100644 index 0000000000..8f1597ec26 Binary files /dev/null and b/app/mac/DSStore differ diff --git a/app/mac/build-and-unify b/app/mac/build-and-unify new file mode 100755 index 0000000000..6d8f4b4256 --- /dev/null +++ b/app/mac/build-and-unify @@ -0,0 +1,66 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +. "$APP_ROOT_DIR/config.sh" + +fx_app_name=Firefox.app + +# Get Mozilla source directory from command line +if [ -z "${1:-}" ]; then + echo "Usage: $0 /path/to/mozilla-unified" >&2 + exit 1 +fi +GECKO_PATH=$1 +mach=$GECKO_PATH/mach + +BUILD_DIR=`mktemp -d` +function cleanup { + rm -rf $BUILD_DIR +} +trap cleanup EXIT + +set -x + +export MOZ_BUILD_DATE=`date "+%Y%m%d%H%M%S"` + +# Install required Rust version +rustup toolchain install $RUST_VERSION +rustup target add aarch64-apple-darwin +rustup target add x86_64-apple-darwin +rustup default $RUST_VERSION + +cp "$SCRIPT_DIR/mozconfig" "$GECKO_PATH" + +# Build Firefox for Intel and Apple Silicon +export Z_ARCH=x64 +$mach build +$mach package +export Z_ARCH=aarch64 +$mach build +$mach package + +cd $BUILD_DIR + +# Unify into Universal build +# From https://searchfox.org/mozilla-central/rev/97c902e8f92b15dc63eb584bfc594ecb041242a4/taskcluster/scripts/misc/unify.sh +for i in x86_64 aarch64; do + $mach python -m mozbuild.action.unpack_dmg "$GECKO_PATH"/obj-$i-apple-darwin/dist/*.dmg $i +done +mv x86_64 x64 + +$mach python "$GECKO_PATH/toolkit/mozapps/installer/unify.py" x64/*.app aarch64/*.app + +cp x64/$fx_app_name/Contents/MacOS/firefox zotero +xz zotero +mv zotero.xz "$APP_ROOT_DIR/mac/zotero.xz" + +# TEMP: Copy a unified XUL for now +cp x64/$fx_app_name/Contents/MacOS/XUL "$APP_ROOT_DIR/mac/XUL" + +# Uncomment to build updater +#cd x64/$fx_app_name/Contents/MacOS/ +#"$APP_ROOT_DIR/mac/updater_renamer" +#cat updater.app/Contents/Resources/English.lproj/InfoPlist.strings +#tar cfvJ "$APP_ROOT_DIR/mac/updater.tar.xz" updater.app/ diff --git a/app/mac/entitlements.xml b/app/mac/entitlements.xml new file mode 100644 index 0000000000..bcf47d2194 --- /dev/null +++ b/app/mac/entitlements.xml @@ -0,0 +1,34 @@ + + + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + + com.apple.security.cs.disable-library-validation + + + + com.apple.security.device.audio-input + + + + com.apple.security.device.camera + + + + com.apple.security.personal-information.location + + + + com.apple.security.smartcard + + + com.apple.security.automation.apple-events + + diff --git a/app/mac/mozconfig b/app/mac/mozconfig new file mode 100644 index 0000000000..1dde8c9923 --- /dev/null +++ b/app/mac/mozconfig @@ -0,0 +1,30 @@ +if [ "$Z_ARCH" == "x64" ]; then + ac_add_options --target=x86_64-apple-darwin +elif [ "$Z_ARCH" == "aarch64" ]; then + ac_add_options --target=aarch64-apple-darwin +fi +ac_add_options --enable-bootstrap +ac_add_options --with-macos-sdk=$HOME/tmp/MacOSX13.3.sdk + +mk_add_options AUTOCLOBBER=1 + +# These don't all affect the stub, but they can't hurt, and we'll want them if +# we switch to custom XUL builds +ac_add_options MOZ_ENABLE_JS_DUMP=1 +ac_add_options MOZ_ENABLE_FORKSERVER= +ac_add_options MOZ_TELEMETRY_REPORTING= +ac_add_options MOZ_DATA_REPORTING= +ac_add_options --disable-tests +ac_add_options --disable-debug +ac_add_options --disable-debug-symbols +ac_add_options --disable-webrtc +ac_add_options --disable-eme + +export MOZILLA_OFFICIAL=1 +export RELEASE_OR_BETA=1 +MOZ_REQUIRE_SIGNING= + +ac_add_options --enable-official-branding + +# Build updater without MAR signature verification +ac_add_options --enable-unverified-updates diff --git a/app/mac/mozilla-115.patch b/app/mac/mozilla-115.patch new file mode 100644 index 0000000000..9caeccf0cc --- /dev/null +++ b/app/mac/mozilla-115.patch @@ -0,0 +1,1508 @@ +diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp +--- a/browser/app/nsBrowserApp.cpp ++++ b/browser/app/nsBrowserApp.cpp +@@ -154,19 +154,31 @@ static bool IsArg(const char* arg, const + #endif + + return false; + } + + Bootstrap::UniquePtr gBootstrap; + + static int do_main(int argc, char* argv[], char* envp[]) { ++ // Allow profile downgrade for Zotero ++ setenv("MOZ_ALLOW_DOWNGRADE", "1", 1); ++ // Don't create dedicated profile (default-esr) ++ setenv("MOZ_LEGACY_PROFILES", "1", 1); ++ + // Allow firefox.exe to launch XULRunner apps via -app + // Note that -app must be the *first* argument. +- const char* appDataFile = getenv("XUL_APP_FILE"); ++ UniqueFreePtr iniPath = BinaryPath::GetApplicationIni(); ++ if (!iniPath) { ++ Output("Couldn't find application.ini.\n"); ++ return 255; ++ } ++ char *appDataFile = iniPath.get(); ++ ++ + if ((!appDataFile || !*appDataFile) && (argc > 1 && IsArg(argv[1], "app"))) { + if (argc == 2) { + Output("Incorrect number of arguments passed to -app"); + return 255; + } + appDataFile = argv[2]; + + char appEnv[MAXPATHLEN]; +diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp +--- a/layout/base/PresShell.cpp ++++ b/layout/base/PresShell.cpp +@@ -11529,18 +11529,17 @@ static nsIWidget* GetPresContextContaine + return mainWidget; + } + + static bool IsTopLevelWidget(nsIWidget* aWidget) { + using WindowType = mozilla::widget::WindowType; + + auto windowType = aWidget->GetWindowType(); + return windowType == WindowType::TopLevel || +- windowType == WindowType::Dialog || windowType == WindowType::Popup || +- windowType == WindowType::Sheet; ++ windowType == WindowType::Dialog || windowType == WindowType::Popup; + } + + PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() { + nsSize minSize(0, 0); + nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame(); + if (!rootFrame || !mPresContext) { + return {minSize, maxSize}; +diff --git a/layout/xul/nsXULTooltipListener.cpp b/layout/xul/nsXULTooltipListener.cpp +--- a/layout/xul/nsXULTooltipListener.cpp ++++ b/layout/xul/nsXULTooltipListener.cpp +@@ -365,53 +365,56 @@ nsresult nsXULTooltipListener::ShowToolt + + // get the tooltip content designated for the target node + nsCOMPtr tooltipNode; + GetTooltipFor(sourceNode, getter_AddRefs(tooltipNode)); + if (!tooltipNode || sourceNode == tooltipNode) + return NS_ERROR_FAILURE; // the target node doesn't need a tooltip + + // set the node in the document that triggered the tooltip and show it +- if (tooltipNode->GetComposedDoc() && +- nsContentUtils::IsChromeDoc(tooltipNode->GetComposedDoc())) { +- // Make sure the target node is still attached to some document. +- // It might have been deleted. +- if (sourceNode->IsInComposedDoc()) { +- if (!mIsSourceTree) { +- mLastTreeRow = -1; +- mLastTreeCol = nullptr; +- } ++ // Make sure the document still has focus. ++ auto* doc = tooltipNode->GetComposedDoc(); ++ if (!doc || !nsContentUtils::IsChromeDoc(doc) || ++ !doc->HasFocus(IgnoreErrors())) { ++ return NS_OK; ++ } ++ // Make sure the target node is still attached to some document. ++ // It might have been deleted. ++ if (sourceNode->IsInComposedDoc()) { ++ if (!mIsSourceTree) { ++ mLastTreeRow = -1; ++ mLastTreeCol = nullptr; ++ } + +- mCurrentTooltip = do_GetWeakReference(tooltipNode); +- LaunchTooltip(); +- mTargetNode = nullptr; +- +- nsCOMPtr currentTooltip = do_QueryReferent(mCurrentTooltip); +- if (!currentTooltip) return NS_OK; ++ mCurrentTooltip = do_GetWeakReference(tooltipNode); ++ LaunchTooltip(); ++ mTargetNode = nullptr; + +- // listen for popuphidden on the tooltip node, so that we can +- // be sure DestroyPopup is called even if someone else closes the tooltip +- currentTooltip->AddSystemEventListener(u"popuphiding"_ns, this, false, +- false); ++ nsCOMPtr currentTooltip = do_QueryReferent(mCurrentTooltip); ++ if (!currentTooltip) return NS_OK; ++ ++ // listen for popuphidden on the tooltip node, so that we can ++ // be sure DestroyPopup is called even if someone else closes the tooltip ++ currentTooltip->AddSystemEventListener(u"popuphiding"_ns, this, false, ++ false); + +- // listen for mousedown, mouseup, keydown, and mouse events at +- // document level +- if (Document* doc = sourceNode->GetComposedDoc()) { +- // Probably, we should listen to untrusted events for hiding tooltips +- // on content since tooltips might disturb something of web +- // applications. If we don't specify the aWantsUntrusted of +- // AddSystemEventListener(), the event target sets it to TRUE if the +- // target is in content. +- doc->AddSystemEventListener(u"wheel"_ns, this, true); +- doc->AddSystemEventListener(u"mousedown"_ns, this, true); +- doc->AddSystemEventListener(u"mouseup"_ns, this, true); +- doc->AddSystemEventListener(u"keydown"_ns, this, true); +- } +- mSourceNode = nullptr; ++ // listen for mousedown, mouseup, keydown, and mouse events at ++ // document level ++ if (Document* doc = sourceNode->GetComposedDoc()) { ++ // Probably, we should listen to untrusted events for hiding tooltips ++ // on content since tooltips might disturb something of web ++ // applications. If we don't specify the aWantsUntrusted of ++ // AddSystemEventListener(), the event target sets it to TRUE if the ++ // target is in content. ++ doc->AddSystemEventListener(u"wheel"_ns, this, true); ++ doc->AddSystemEventListener(u"mousedown"_ns, this, true); ++ doc->AddSystemEventListener(u"mouseup"_ns, this, true); ++ doc->AddSystemEventListener(u"keydown"_ns, this, true); + } ++ mSourceNode = nullptr; + } + + return NS_OK; + } + + static void SetTitletipLabel(XULTreeElement* aTree, Element* aTooltip, + int32_t aRow, nsTreeColumn* aCol) { + nsCOMPtr view = aTree->GetView(); +diff --git a/python/mozbuild/mozbuild/action/unpack_dmg.py b/python/mozbuild/mozbuild/action/unpack_dmg.py +--- a/python/mozbuild/mozbuild/action/unpack_dmg.py ++++ b/python/mozbuild/mozbuild/action/unpack_dmg.py +@@ -34,18 +34,18 @@ def main(args): + options = parser.parse_args(args) + + dmg_tool = bootstrap_toolchain("dmg/dmg") + hfs_tool = bootstrap_toolchain("dmg/hfsplus") + + dmg.extract_dmg( + dmgfile=Path(options.dmgfile), + output=Path(options.outpath), +- dmg_tool=Path(dmg_tool), +- hfs_tool=Path(hfs_tool), ++ dmg_tool=_path_or_none(dmg_tool), ++ hfs_tool=_path_or_none(hfs_tool), + dsstore=_path_or_none(options.dsstore), + background=_path_or_none(options.background), + icon=_path_or_none(options.icon), + ) + return 0 + + + if __name__ == "__main__": +diff --git a/widget/InitData.h b/widget/InitData.h +--- a/widget/InitData.h ++++ b/widget/InitData.h +@@ -12,17 +12,16 @@ + + namespace mozilla::widget { + + // Window types + enum class WindowType : uint8_t { + TopLevel, // default top level window + Dialog, // top level window but usually handled differently + // by the OS +- Sheet, // MacOSX sheet (special dialog class) + Popup, // used for combo boxes, etc + Child, // child windows (contained inside a window on the + // desktop (has no border)) + Invisible, // windows that are invisible or offscreen + }; + + // Popup types for WindowType::Popup + enum class PopupType : uint8_t { +diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build +--- a/widget/cocoa/moz.build ++++ b/widget/cocoa/moz.build +@@ -6,22 +6,16 @@ + + with Files("**"): + BUG_COMPONENT = ("Core", "Widget: Cocoa") + SCHEDULES.exclusive = ["macosx"] + + with Files("*TextInput*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +-XPIDL_SOURCES += [ +- "nsPIWidgetCocoa.idl", +-] +- +-XPIDL_MODULE = "widget_cocoa" +- + EXPORTS += [ + "CFTypeRefPtr.h", + "DesktopBackgroundImage.h", + "MediaHardwareKeysEventSourceMac.h", + "MediaHardwareKeysEventSourceMacMediaCenter.h", + "mozView.h", + "nsBidiKeyboard.h", + "nsChangeObserver.h", +diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm +--- a/widget/cocoa/nsChildView.mm ++++ b/widget/cocoa/nsChildView.mm +@@ -1678,29 +1678,23 @@ void nsChildView::UpdateThemeGeometries( + + if (![[mView window] isKindOfClass:[ToolbarWindow class]]) return; + + // Update unified toolbar height and sheet attachment position. + int32_t windowWidth = mBounds.width; + int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth); + int32_t unifiedToolbarBottom = + FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom); +- int32_t toolboxBottom = FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeToolbox).YMost(); + + ToolbarWindow* win = (ToolbarWindow*)[mView window]; + int32_t titlebarHeight = + [win drawsContentsIntoWindowFrame] ? 0 : CocoaPointsToDevPixels([win titlebarHeight]); + int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom; + [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)]; + +- int32_t sheetPositionDevPx = std::max(toolboxBottom, unifiedToolbarBottom); +- NSPoint sheetPositionView = {0, DevPixelsToCocoaPoints(sheetPositionDevPx)}; +- NSPoint sheetPositionWindow = [mView convertPoint:sheetPositionView toView:nil]; +- [win setSheetAttachmentPosition:sheetPositionWindow.y]; +- + // Update titlebar control offsets. + LayoutDeviceIntRect windowButtonRect = + FindFirstRectOfType(aThemeGeometries, eThemeGeometryTypeWindowButtons); + [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]]; + } + + static Maybe ThemeGeometryTypeToVibrancyType( + nsITheme::ThemeGeometryType aThemeGeometryType) { +@@ -4899,24 +4893,16 @@ BOOL ChildViewMouseTracker::WindowAccept + return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough); + + case WindowType::TopLevel: + case WindowType::Dialog: + if ([aWindow attachedSheet]) return NO; + + topLevelWindow = aWindow; + break; +- case WindowType::Sheet: { +- nsIWidget* parentWidget = windowWidget->GetSheetWindowParent(); +- if (!parentWidget) return YES; +- +- topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW); +- break; +- } +- + default: + return YES; + } + + if (!topLevelWindow || ([topLevelWindow isMainWindow] && !aIsClickThrough) || + [aEvent type] == NSEventTypeOtherMouseDown || + (([aEvent modifierFlags] & NSEventModifierFlagCommand) != 0 && + [aEvent type] != NSEventTypeMouseMoved)) +diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h +--- a/widget/cocoa/nsCocoaWindow.h ++++ b/widget/cocoa/nsCocoaWindow.h +@@ -7,17 +7,16 @@ + #define nsCocoaWindow_h_ + + #undef DARWIN + + #import + + #include "mozilla/RefPtr.h" + #include "nsBaseWidget.h" +-#include "nsPIWidgetCocoa.h" + #include "nsCocoaUtils.h" + #include "nsTouchBar.h" + #include + #include + + class nsCocoaWindow; + class nsChildView; + class nsMenuBarX; +@@ -191,60 +190,53 @@ typedef struct _nsCocoaWindowList { + // destroyed by updateTitlebarView. + MOZTitlebarView* mTitlebarView; // [STRONG] + // mFullscreenTitlebarTracker attaches an invisible rectangle to the system + // title bar. This allows us to detect when the title bar is showing in + // fullscreen. + FullscreenTitlebarTracker* mFullscreenTitlebarTracker; + + CGFloat mUnifiedToolbarHeight; +- CGFloat mSheetAttachmentPosition; + CGFloat mMenuBarHeight; + /* Store the height of the titlebar when this window is initialized. The + titlebarHeight getter returns 0 when in fullscreen, which is not useful in + some cases. */ + CGFloat mInitialTitlebarHeight; + NSRect mWindowButtonsRect; + } + - (void)setUnifiedToolbarHeight:(CGFloat)aHeight; + - (CGFloat)unifiedToolbarHeight; + - (CGFloat)titlebarHeight; + - (NSRect)titlebarRect; + - (void)setTitlebarNeedsDisplay; + - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState; +-- (void)setSheetAttachmentPosition:(CGFloat)aY; +-- (CGFloat)sheetAttachmentPosition; + - (void)placeWindowButtons:(NSRect)aRect; + - (NSRect)windowButtonsRect; + - (void)windowMainStateChanged; + @end + +-class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa { ++class nsCocoaWindow final : public nsBaseWidget { + private: + typedef nsBaseWidget Inherited; + + public: + nsCocoaWindow(); + +- NS_DECL_ISUPPORTS_INHERITED +- NS_DECL_NSPIWIDGETCOCOA; // semicolon for clang-format bug 1629756 +- + [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const DesktopIntRect& aRect, InitData* = nullptr) override; + + [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + InitData* = nullptr) override; + + virtual void Destroy() override; + + virtual void Show(bool aState) override; + virtual bool NeedsRecreateToReshow() override; + +- virtual nsIWidget* GetSheetWindowParent(void) override; + virtual void Enable(bool aState) override; + virtual bool IsEnabled() const override; + virtual void SetModal(bool aState) override; + virtual void SetFakeModal(bool aState) override; + virtual bool IsRunningAppModal() override; + virtual bool IsVisible() const override; + virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override; + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override; +@@ -393,47 +385,45 @@ class nsCocoaWindow final : public nsBas + virtual ~nsCocoaWindow(); + + nsresult CreateNativeWindow(const NSRect& aRect, BorderStyle aBorderStyle, bool aRectIsFrameRect, + bool aIsPrivateBrowsing); + nsresult CreatePopupContentView(const LayoutDeviceIntRect& aRect, InitData*); + void DestroyNativeWindow(); + void UpdateBounds(); + int32_t GetWorkspaceID(); ++ void SendSetZLevelEvent(); + + void DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint, + bool aConstrainToCurrentScreen); + + void UpdateFullscreenState(bool aFullScreen, bool aNativeMode); + nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition); + + virtual already_AddRefed AllocateChildPopupWidget() override { + return nsIWidget::CreateTopLevelWindow(); + } + + nsIWidget* mParent; // if we're a popup, this is our parent [WEAK] + nsIWidget* mAncestorLink; // link to traverse ancestors [WEAK] + BaseWindow* mWindow; // our cocoa window [STRONG] + WindowDelegate* mDelegate; // our delegate for processing window msgs [STRONG] + RefPtr mMenuBar; +- NSWindow* mSheetWindowParent; // if this is a sheet, this is the NSWindow it's attached to + nsChildView* mPopupContentView; // if this is a popup, this is its content widget + // if this is a toplevel window, and there is any ongoing fullscreen + // transition, it is the animation object. + NSAnimation* mFullscreenTransitionAnimation; + mozilla::StyleWindowShadow mShadowStyle; + + CGFloat mBackingScaleFactor; + CGFloat mAspectRatio; + + WindowAnimationType mAnimationType; + + bool mWindowMadeHere; // true if we created the window, false for embedding +- bool mSheetNeedsShow; // if this is a sheet, are we waiting to be shown? +- // this is used for sibling sheet contention only + nsSizeMode mSizeMode; + bool mInFullScreenMode; + // Whether we are currently using native fullscreen. It could be false because + // we are in the emulated fullscreen where we do not use the native fullscreen. + bool mInNativeFullScreenMode; + + mozilla::Maybe mTransitionCurrent; + std::queue mTransitionsPending; +diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm +--- a/widget/cocoa/nsCocoaWindow.mm ++++ b/widget/cocoa/nsCocoaWindow.mm +@@ -30,16 +30,17 @@ + #include "nsMenuUtilsX.h" + #include "nsStyleConsts.h" + #include "nsNativeThemeColors.h" + #include "nsNativeThemeCocoa.h" + #include "nsChildView.h" + #include "nsCocoaFeatures.h" + #include "nsIScreenManager.h" + #include "nsIWidgetListener.h" ++#include "nsXULPopupManager.h" + #include "SDKDeclarations.h" + #include "VibrancyManager.h" + #include "nsPresContext.h" + #include "nsDocShell.h" + + #include "gfxPlatform.h" + #include "qcms.h" + +@@ -99,28 +100,27 @@ typedef enum { + static NSString* const CGSSpaceIDKey = @"ManagedSpaceID"; + static NSString* const CGSSpacesKey = @"Spaces"; + extern CGSConnection _CGSDefaultConnection(void); + extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid, CGAffineTransform transform); + } + + #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" + +-NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa) +- +-// A note on testing to see if your object is a sheet... +-// |mWindowType == WindowType::Sheet| is true if your gecko nsIWidget is a sheet +-// widget - whether or not the sheet is showing. |[mWindow isSheet]| will return +-// true *only when the sheet is actually showing*. Choose your test wisely. +- + static void RollUpPopups( + nsIRollupListener::AllowAnimations aAllowAnimations = nsIRollupListener::AllowAnimations::Yes) { ++ if (RefPtr pm = nsXULPopupManager::GetInstance()) { ++ pm->RollupTooltips(); ++ } ++ + nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); +- NS_ENSURE_TRUE_VOID(rollupListener); +- ++ ++ if (!rollupListener) { ++ return; ++ } + if (rollupListener->RollupNativeMenu()) { + return; + } + + nsCOMPtr rollupWidget = rollupListener->GetRollupWidget(); + if (!rollupWidget) { + return; + } +@@ -129,24 +129,22 @@ static void RollUpPopups( + rollupListener->Rollup(options); + } + + nsCocoaWindow::nsCocoaWindow() + : mParent(nullptr), + mAncestorLink(nullptr), + mWindow(nil), + mDelegate(nil), +- mSheetWindowParent(nil), + mPopupContentView(nil), + mFullscreenTransitionAnimation(nil), + mShadowStyle(StyleWindowShadow::Default), + mBackingScaleFactor(0.0), + mAnimationType(nsIWidget::eGenericWindowAnimation), + mWindowMadeHere(false), +- mSheetNeedsShow(false), + mSizeMode(nsSizeMode_Normal), + mInFullScreenMode(false), + mInNativeFullScreenMode(false), + mIgnoreOcclusionCount(0), + mHasStartedNativeFullscreen(false), + mModal(false), + mFakeModal(false), + mIsAnimationSuppressed(false), +@@ -431,25 +429,16 @@ nsresult nsCocoaWindow::CreateNativeWind + features |= NSWindowStyleMaskClosable; + } + } + break; + case WindowType::TopLevel: + case WindowType::Dialog: + features = WindowMaskForBorderStyle(aBorderStyle); + break; +- case WindowType::Sheet: +- if (mParent->GetWindowType() != WindowType::Invisible && +- aBorderStyle & BorderStyle::ResizeH) { +- features = NSWindowStyleMaskResizable; +- } else { +- features = NSWindowStyleMaskMiniaturizable; +- } +- features |= NSWindowStyleMaskTitled; +- break; + default: + NS_ERROR("Unhandled window type!"); + return NS_ERROR_FAILURE; + } + + NSRect contentRect; + + if (aRectIsFrameRect) { +@@ -650,24 +639,16 @@ void nsCocoaWindow::Destroy() { + if (mInNativeFullScreenMode) { + DestroyNativeWindow(); + } else if (mWindow) { + nsCocoaUtils::HideOSChromeOnScreen(false); + } + } + } + +-nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) { +- if (mWindowType != WindowType::Sheet) return nullptr; +- nsCocoaWindow* parent = static_cast(mParent); +- while (parent && (parent->mWindowType == WindowType::Sheet)) +- parent = static_cast(parent->mParent); +- return parent; +-} +- + void* nsCocoaWindow::GetNativeData(uint32_t aDataType) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + void* retVal = nullptr; + + switch (aDataType) { + // to emulate how windows works, we always have to return a NSView + // for NS_NATIVE_WIDGET +@@ -707,17 +688,17 @@ void* nsCocoaWindow::GetNativeData(uint3 + return retVal; + + NS_OBJC_END_TRY_BLOCK_RETURN(nullptr); + } + + bool nsCocoaWindow::IsVisible() const { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + +- return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow)); ++ return mWindow && mWindow.isVisibleOrBeingShown; + + NS_OBJC_END_TRY_BLOCK_RETURN(false); + } + + void nsCocoaWindow::SetModal(bool aState) { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + if (!mWindow) return; +@@ -734,64 +715,60 @@ void nsCocoaWindow::SetModal(bool aState + ++gXULModalLevel; + // When a non-sheet window gets "set modal", make the window(s) that it + // appears over behave as they should. We can't rely on native methods to + // do this, for the following reason: The OS runs modal non-sheet windows + // in an event loop (using [NSApplication runModalForWindow:] or similar + // methods) that's incompatible with the modal event loop in AppWindow:: + // ShowModal() (each of these event loops is "exclusive", and can't run at + // the same time as other (similar) event loops). +- if (mWindowType != WindowType::Sheet) { +- while (ancestor) { +- if (ancestor->mNumModalDescendents++ == 0) { +- NSWindow* aWindow = ancestor->GetCocoaWindow(); +- if (ancestor->mWindowType != WindowType::Invisible) { +- [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO]; +- [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; +- [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO]; +- } ++ while (ancestor) { ++ if (ancestor->mNumModalDescendents++ == 0) { ++ NSWindow* aWindow = ancestor->GetCocoaWindow(); ++ if (ancestor->mWindowType != WindowType::Invisible) { ++ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO]; ++ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; ++ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO]; + } +- ancestor = static_cast(ancestor->mParent); + } +- [mWindow setLevel:NSModalPanelWindowLevel]; +- nsCocoaWindowList* windowList = new nsCocoaWindowList; +- if (windowList) { +- windowList->window = this; // Don't ADDREF +- windowList->prev = gGeckoAppModalWindowList; +- gGeckoAppModalWindowList = windowList; +- } ++ ancestor = static_cast(ancestor->mParent); ++ } ++ [mWindow setLevel:NSModalPanelWindowLevel]; ++ nsCocoaWindowList* windowList = new nsCocoaWindowList; ++ if (windowList) { ++ windowList->window = this; // Don't ADDREF ++ windowList->prev = gGeckoAppModalWindowList; ++ gGeckoAppModalWindowList = windowList; + } + } else { + --gXULModalLevel; + NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!"); +- if (mWindowType != WindowType::Sheet) { +- while (ancestor) { +- if (--ancestor->mNumModalDescendents == 0) { +- NSWindow* aWindow = ancestor->GetCocoaWindow(); +- if (ancestor->mWindowType != WindowType::Invisible) { +- [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES]; +- [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES]; +- [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES]; +- } ++ while (ancestor) { ++ if (--ancestor->mNumModalDescendents == 0) { ++ NSWindow* aWindow = ancestor->GetCocoaWindow(); ++ if (ancestor->mWindowType != WindowType::Invisible) { ++ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES]; ++ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES]; ++ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES]; + } +- NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!"); +- ancestor = static_cast(ancestor->mParent); + } +- if (gGeckoAppModalWindowList) { +- NS_ASSERTION(gGeckoAppModalWindowList->window == this, +- "Widget hierarchy changed while modal!"); +- nsCocoaWindowList* saved = gGeckoAppModalWindowList; +- gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev; +- delete saved; // "window" not ADDREFed +- } +- if (mWindowType == WindowType::Popup) +- SetPopupWindowLevel(); +- else +- [mWindow setLevel:NSNormalWindowLevel]; ++ NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!"); ++ ancestor = static_cast(ancestor->mParent); + } ++ if (gGeckoAppModalWindowList) { ++ NS_ASSERTION(gGeckoAppModalWindowList->window == this, ++ "Widget hierarchy changed while modal!"); ++ nsCocoaWindowList* saved = gGeckoAppModalWindowList; ++ gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev; ++ delete saved; // "window" not ADDREFed ++ } ++ if (mWindowType == WindowType::Popup) ++ SetPopupWindowLevel(); ++ else ++ [mWindow setLevel:NSNormalWindowLevel]; + } + + NS_OBJC_END_TRY_IGNORE_BLOCK; + } + + void nsCocoaWindow::SetFakeModal(bool aState) { + mFakeModal = aState; + SetModal(aState); +@@ -800,32 +777,28 @@ void nsCocoaWindow::SetFakeModal(bool aS + bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; } + + // Hide or show this window + void nsCocoaWindow::Show(bool bState) { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + if (!mWindow) return; + +- if (!mSheetNeedsShow) { +- // Early exit if our current visibility state is already the requested state. +- if (bState == ([mWindow isVisible] || [mWindow isBeingShown])) { +- return; +- } ++ // Early exit if our current visibility state is already the requested state. ++ if (bState == ([mWindow isVisible] || [mWindow isBeingShown])) { ++ return; + } + + [mWindow setBeingShown:bState]; + if (bState && !mWasShown) { + mWasShown = true; + } + +- nsIWidget* parentWidget = mParent; +- nsCOMPtr piParentWidget(do_QueryInterface(parentWidget)); + NSWindow* nativeParentWindow = +- (parentWidget) ? (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil; ++ mParent ? (NSWindow*)mParent->GetNativeData(NS_NATIVE_WINDOW) : nil; + + if (bState && !mBounds.IsEmpty()) { + // If we had set the activationPolicy to accessory, then right now we won't + // have a dock icon. Make sure that we undo that and show a dock icon now that + // we're going to show a window. + if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular) { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + PR_SetEnv("MOZ_APP_NO_DOCK="); +@@ -838,87 +811,17 @@ void nsCocoaWindow::Show(bool bState) { + } + } + + if (mPopupContentView) { + // Ensure our content view is visible. We never need to hide it. + mPopupContentView->Show(true); + } + +- if (mWindowType == WindowType::Sheet) { +- // bail if no parent window (its basically what we do in Carbon) +- if (!nativeParentWindow || !piParentWidget) return; +- +- NSWindow* topNonSheetWindow = nativeParentWindow; +- +- // If this sheet is the child of another sheet, hide the parent so that +- // this sheet can be displayed. Leave the parent mSheetNeedsShow alone, +- // that is only used to handle sibling sheet contention. The parent will +- // return once there are no more child sheets. +- bool parentIsSheet = false; +- if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) { +- piParentWidget->GetSheetWindowParent(&topNonSheetWindow); +-#ifdef MOZ_THUNDERBIRD +- [NSApp endSheet:nativeParentWindow]; +-#else +- [nativeParentWindow.sheetParent endSheet:nativeParentWindow]; +-#endif +- } +- +- nsCOMPtr sheetShown; +- if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, getter_AddRefs(sheetShown))) && +- (!sheetShown || sheetShown == this)) { +- // If this sheet is already the sheet actually being shown, don't +- // tell it to show again. Otherwise the number of calls to +-#ifdef MOZ_THUNDERBIRD +- // [NSApp beginSheet...] won't match up with [NSApp endSheet...]. +-#else +- // [NSWindow beginSheet...] won't match up with [NSWindow endSheet...]. +-#endif +- if (![mWindow isVisible]) { +- mSheetNeedsShow = false; +- mSheetWindowParent = topNonSheetWindow; +-#ifdef MOZ_THUNDERBIRD +- // Only set contextInfo if our parent isn't a sheet. +- NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent; +- [TopLevelWindowData deactivateInWindow:mSheetWindowParent]; +- [NSApp beginSheet:mWindow +- modalForWindow:mSheetWindowParent +- modalDelegate:mDelegate +- didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) +- contextInfo:contextInfo]; +-#else +- NSWindow* sheet = mWindow; +- NSWindow* nonSheetParent = parentIsSheet ? nil : mSheetWindowParent; +- [TopLevelWindowData deactivateInWindow:mSheetWindowParent]; +- [mSheetWindowParent beginSheet:sheet +- completionHandler:^(NSModalResponse returnCode) { +- // Note: 'nonSheetParent' (if it is set) is the window that is the parent +- // of the sheet. If it's set, 'nonSheetParent' is always the top- level +- // window, not another sheet itself. But 'nonSheetParent' is nil if our +- // parent window is also a sheet -- in that case we shouldn't send the +- // top-level window any activate events (because it's our parent window +- // that needs to get these events, not the top-level window). +- [TopLevelWindowData deactivateInWindow:sheet]; +- [sheet orderOut:nil]; +- if (nonSheetParent) { +- [TopLevelWindowData activateInWindow:nonSheetParent]; +- } +- }]; +-#endif +- [TopLevelWindowData activateInWindow:mWindow]; +- SendSetZLevelEvent(); +- } +- } else { +- // A sibling of this sheet is active, don't show this sheet yet. +- // When the active sheet hides, its brothers and sisters that have +- // mSheetNeedsShow set will have their opportunities to display. +- mSheetNeedsShow = true; +- } +- } else if (mWindowType == WindowType::Popup) { ++ if (mWindowType == WindowType::Popup) { + // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or + // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000) + // creating CGSWindow", which in turn triggers an internal inconsistency + // NSException. These errors shouldn't be fatal. So we need to wrap + // calls to ...orderFront: in TRY blocks. See bmo bug 470864. + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + [[mWindow contentView] setNeedsDisplay:YES]; + [mWindow orderFront:nil]; +@@ -974,120 +877,30 @@ void nsCocoaWindow::Show(bool bState) { + SendSetZLevelEvent(); + } + } else { + // roll up any popups if a top-level window is going away + if (mWindowType == WindowType::TopLevel || mWindowType == WindowType::Dialog) { + RollUpPopups(); + } + +- // now get rid of the window/sheet +- if (mWindowType == WindowType::Sheet) { +- if (mSheetNeedsShow) { +- // This is an attempt to hide a sheet that never had a chance to +- // be shown. There's nothing to do other than make sure that it +- // won't show. +- mSheetNeedsShow = false; +- } else { +- // get sheet's parent *before* hiding the sheet (which breaks the linkage) +- NSWindow* sheetParent = mSheetWindowParent; +- +- // hide the sheet +-#ifdef MOZ_THUNDERBIRD +- [NSApp endSheet:mWindow]; +-#else +- [mSheetWindowParent endSheet:mWindow]; +-#endif +- [TopLevelWindowData deactivateInWindow:mWindow]; +- +- nsCOMPtr siblingSheetToShow; +- bool parentIsSheet = false; +- +- if (nativeParentWindow && piParentWidget && +- NS_SUCCEEDED( +- piParentWidget->GetChildSheet(false, getter_AddRefs(siblingSheetToShow))) && +- siblingSheetToShow) { +- // First, give sibling sheets an opportunity to show. +- siblingSheetToShow->Show(true); +- } else if (nativeParentWindow && piParentWidget && +- NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) { +-#ifdef MOZ_THUNDERBIRD +- // Only set contextInfo if the parent of the parent sheet we're about +- // to restore isn't itself a sheet. +- NSWindow* contextInfo = sheetParent; +-#else +- // Only set nonSheetGrandparent if the parent of the parent sheet we're about +- // to restore isn't itself a sheet. +- NSWindow* nonSheetGrandparent = sheetParent; +-#endif +- nsIWidget* grandparentWidget = nil; +- if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && +- grandparentWidget) { +- nsCOMPtr piGrandparentWidget(do_QueryInterface(grandparentWidget)); +- bool grandparentIsSheet = false; +- if (piGrandparentWidget && +- NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) && +- grandparentIsSheet) { +-#ifdef MOZ_THUNDERBIRD +- contextInfo = nil; +-#else +- nonSheetGrandparent = nil; +-#endif +- } +- } +- // If there are no sibling sheets, but the parent is a sheet, restore +- // it. It wasn't sent any deactivate events when it was hidden, so +- // don't call through Show, just let the OS put it back up. +-#ifdef MOZ_THUNDERBIRD +- [NSApp beginSheet:nativeParentWindow +- modalForWindow:sheetParent +- modalDelegate:[nativeParentWindow delegate] +- didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) +- contextInfo:contextInfo]; +-#else +- [nativeParentWindow beginSheet:sheetParent +- completionHandler:^(NSModalResponse returnCode) { +- // Note: 'nonSheetGrandparent' (if it is set) is the window that is the +- // parent of sheetParent. If it's set, 'nonSheetGrandparent' is always the +- // top-level window, not another sheet itself. But 'nonSheetGrandparent' +- // is nil if our parent window is also a sheet -- in that case we shouldn't +- // send the top-level window any activate events (because it's our parent +- // window that needs to get these events, not the top-level window). +- [TopLevelWindowData deactivateInWindow:sheetParent]; +- [sheetParent orderOut:nil]; +- if (nonSheetGrandparent) { +- [TopLevelWindowData activateInWindow:nonSheetGrandparent]; +- } +- }]; +-#endif +- } else { +- // Sheet, that was hard. No more siblings or parents, going back +- // to a real window. +- NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; +- [sheetParent makeKeyAndOrderFront:nil]; +- NS_OBJC_END_TRY_IGNORE_BLOCK; +- } +- SendSetZLevelEvent(); +- } +- } else { +- // If the window is a popup window with a parent window we need to +- // unhook it here before ordering it out. When you order out the child +- // of a window it hides the parent window. +- if (mWindowType == WindowType::Popup && nativeParentWindow) +- [nativeParentWindow removeChildWindow:mWindow]; +- +- [mWindow orderOut:nil]; +- +- // If our popup window is a non-native context menu, tell the OS (and +- // other programs) that a menu has closed. +- if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) { +- [[NSDistributedNotificationCenter defaultCenter] +- postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" +- object:@"org.mozilla.gecko.PopupWindow"]; +- } ++ // If the window is a popup window with a parent window we need to ++ // unhook it here before ordering it out. When you order out the child ++ // of a window it hides the parent window. ++ if (mWindowType == WindowType::Popup && nativeParentWindow) ++ [nativeParentWindow removeChildWindow:mWindow]; ++ ++ [mWindow orderOut:nil]; ++ ++ // If our popup window is a non-native context menu, tell the OS (and ++ // other programs) that a menu has closed. ++ if ([mWindow isKindOfClass:[PopupWindow class]] && [(PopupWindow*)mWindow isContextMenu]) { ++ [[NSDistributedNotificationCenter defaultCenter] ++ postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" ++ object:@"org.mozilla.gecko.PopupWindow"]; + } + } + + [mWindow setBeingShown:NO]; + + NS_OBJC_END_TRY_IGNORE_BLOCK; + } + +@@ -2136,59 +1949,22 @@ void nsCocoaWindow::Invalidate(const Lay + // a drop, to a drag enter/leave, or a drag over event. The actual event + // is passed in |aMessage| and is passed along to our event hanlder so Gecko + // knows about it. + bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal, + UInt16 aKeyModifiers) { + return false; + } + +-NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() { +- nsWindowZ placement = nsWindowZTop; +- nsCOMPtr actualBelow; +- if (mWidgetListener) ++void nsCocoaWindow::SendSetZLevelEvent() { ++ if (mWidgetListener) { ++ nsWindowZ placement = nsWindowZTop; ++ nsCOMPtr actualBelow; + mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow)); +- return NS_OK; +-} +- +-NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval) { +- nsIWidget* child = GetFirstChild(); +- +- while (child) { +- if (child->GetWindowType() == WindowType::Sheet) { +- // if it's a sheet, it must be an nsCocoaWindow +- nsCocoaWindow* cocoaWindow = static_cast(child); +- if (cocoaWindow->mWindow && ((aShown && [cocoaWindow->mWindow isVisible]) || +- (!aShown && cocoaWindow->mSheetNeedsShow))) { +- nsCOMPtr widget = cocoaWindow; +- widget.forget(_retval); +- return NS_OK; +- } +- } +- child = child->GetNextSibling(); + } +- +- *_retval = nullptr; +- +- return NS_OK; +-} +- +-NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) { +- *parent = mParent; +- return NS_OK; +-} +- +-NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) { +- mWindowType == WindowType::Sheet ? * isSheet = true : * isSheet = false; +- return NS_OK; +-} +- +-NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent) { +- *sheetWindowParent = mSheetWindowParent; +- return NS_OK; + } + + // Invokes callback and ProcessEvent methods on Event Listener object + nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) { + aStatus = nsEventStatus_eIgnore; + + nsCOMPtr kungFuDeathGrip(event->mWidget); + mozilla::Unused << kungFuDeathGrip; // Not used within this function +@@ -3030,19 +2806,17 @@ void nsCocoaWindow::CocoaWindowDidResize + + - (void)windowDidBecomeKey:(NSNotification*)aNotification { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + RollUpPopups(); + ChildViewMouseTracker::ReEvaluateMouseEnterState(); + + NSWindow* window = [aNotification object]; +- if ([window isSheet]) [WindowDelegate paintMenubarForWindow:window]; +- +- nsChildView* mainChildView = ++ auto* mainChildView = + static_cast([[(BaseWindow*)window mainChildView] widget]); + if (mainChildView) { + if (mainChildView->GetInputContext().IsPasswordEditor()) { + TextInputHandler::EnableSecureEventInput(); + } else { + TextInputHandler::EnsureSecureEventInputDisabled(); + } + } +@@ -3051,22 +2825,16 @@ void nsCocoaWindow::CocoaWindowDidResize + } + + - (void)windowDidResignKey:(NSNotification*)aNotification { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + RollUpPopups(nsIRollupListener::AllowAnimations::No); + + ChildViewMouseTracker::ReEvaluateMouseEnterState(); +- +- // If a sheet just resigned key then we should paint the menu bar +- // for whatever window is now main. +- NSWindow* window = [aNotification object]; +- if ([window isSheet]) [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]]; +- + TextInputHandler::EnsureSecureEventInputDisabled(); + + NS_OBJC_END_TRY_IGNORE_BLOCK; + } + + - (void)windowWillMove:(NSNotification*)aNotification { + RollUpPopups(); + } +@@ -3105,42 +2873,16 @@ void nsCocoaWindow::CocoaWindowDidResize + + - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame { + if (!mHasEverBeenZoomed && [window isZoomed]) return NO; // See bug 429954. + + mHasEverBeenZoomed = YES; + return YES; + } + +-- (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect { +- if ([window isKindOfClass:[ToolbarWindow class]]) { +- rect.origin.y = [(ToolbarWindow*)window sheetAttachmentPosition]; +- } +- return rect; +-} +- +-#ifdef MOZ_THUNDERBIRD +-- (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo { +- NS_OBJC_BEGIN_TRY_ABORT_BLOCK; +- +- // Note: 'contextInfo' (if it is set) is the window that is the parent of +- // the sheet. The value of contextInfo is determined in +- // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top- +- // level window, not another sheet itself. But 'contextInfo' is nil if +- // our parent window is also a sheet -- in that case we shouldn't send +- // the top-level window any activate events (because it's our parent +- // window that needs to get these events, not the top-level window). +- [TopLevelWindowData deactivateInWindow:sheet]; +- [sheet orderOut:self]; +- if (contextInfo) [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo]; +- +- NS_OBJC_END_TRY_ABORT_BLOCK; +-} +-#endif +- + - (void)windowDidChangeBackingProperties:(NSNotification*)aNotification { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + + NSWindow* window = (NSWindow*)[aNotification object]; + + if ([window respondsToSelector:@selector(backingScaleFactor)]) { + CGFloat oldFactor = + [[[aNotification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; +@@ -3787,17 +3529,16 @@ static const NSString* kStateWantsTitleD + NSRect contentRect = frameRect; + + if ((self = [super initWithContentRect:contentRect + styleMask:aStyle + backing:aBufferingType + defer:aFlag])) { + mTitlebarView = nil; + mUnifiedToolbarHeight = 22.0f; +- mSheetAttachmentPosition = aChildViewRect.size.height; + mWindowButtonsRect = NSZeroRect; + mInitialTitlebarHeight = [self titlebarHeight]; + + [self setTitlebarAppearsTransparent:YES]; + #if !defined(MAC_OS_VERSION_11_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_11_0 + if (nsCocoaFeatures::OnBigSurOrLater()) { + #else + if (@available(macOS 11.0, *)) { +@@ -4014,24 +3755,16 @@ static bool ShouldShiftByMenubarHeightIn + [self updateTitlebarView]; + } + + - (void)setWantsTitleDrawn:(BOOL)aDrawTitle { + [super setWantsTitleDrawn:aDrawTitle]; + [self setTitlebarNeedsDisplay]; + } + +-- (void)setSheetAttachmentPosition:(CGFloat)aY { +- mSheetAttachmentPosition = aY; +-} +- +-- (CGFloat)sheetAttachmentPosition { +- return mSheetAttachmentPosition; +-} +- + - (void)placeWindowButtons:(NSRect)aRect { + if (!NSEqualRects(mWindowButtonsRect, aRect)) { + mWindowButtonsRect = aRect; + [self reflowTitlebarElements]; + } + } + + - (NSRect)windowButtonsRect { +diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h +--- a/widget/cocoa/nsMenuX.h ++++ b/widget/cocoa/nsMenuX.h +@@ -181,17 +181,18 @@ class nsMenuX final : public nsMenuParen + already_AddRefed GetMenuPopupContent(); + void WillInsertChild(const MenuChild& aChild); + void WillRemoveChild(const MenuChild& aChild); + void AddMenuChild(MenuChild&& aChild); + void InsertMenuChild(MenuChild&& aChild); + void RemoveMenuChild(const MenuChild& aChild); + mozilla::Maybe CreateMenuChild(nsIContent* aContent); + RefPtr CreateMenuItem(nsIContent* aMenuItemContent); +- GeckoNSMenu* CreateMenuWithGeckoString(nsString& aMenuTitle); ++ GeckoNSMenu* CreateMenuWithGeckoString(nsString& aMenuTitle, ++ bool aShowServices); + void DidFirePopupShowing(); + + // Find the index at which aChild needs to be inserted into mMenuChildren such that mMenuChildren + // remains in correct content order, i.e. the order in mMenuChildren is the same as the order of + // the DOM children of our . + size_t FindInsertionIndex(const MenuChild& aChild); + + // Calculates the index at which aChild's NSMenuItem should be inserted into our NSMenu. +diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm +--- a/widget/cocoa/nsMenuX.mm ++++ b/widget/cocoa/nsMenuX.mm +@@ -89,20 +89,24 @@ nsMenuX::nsMenuX(nsMenuParentX* aParent, + + mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this]; + mMenuDelegate.menuIsInMenubar = mMenuGroupOwner->GetMenuBar() != nullptr; + + if (!nsMenuBarX::sNativeEventTarget) { + nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init]; + } + ++ bool shouldShowServices = false; + if (mContent->IsElement()) { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel); ++ ++ shouldShowServices = ++ mContent->AsElement()->HasAttr(nsGkAtoms::showservicesmenu); + } +- mNativeMenu = CreateMenuWithGeckoString(mLabel); ++ mNativeMenu = CreateMenuWithGeckoString(mLabel, shouldShowServices); + + // register this menu to be notified when changes are made to our content object + NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one"); + mMenuGroupOwner->RegisterForContentChanges(mContent, this); + + mVisible = !nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent); + + NSString* newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel); +@@ -773,28 +777,33 @@ nsresult nsMenuX::SetEnabled(bool aIsEna + } + + nsresult nsMenuX::GetEnabled(bool* aIsEnabled) { + NS_ENSURE_ARG_POINTER(aIsEnabled); + *aIsEnabled = mIsEnabled; + return NS_OK; + } + +-GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& aMenuTitle) { ++GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& aMenuTitle, ++ bool aShowServices) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSString* title = [NSString stringWithCharacters:(UniChar*)aMenuTitle.get() + length:aMenuTitle.Length()]; + GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title]; + myMenu.delegate = mMenuDelegate; + + // We don't want this menu to auto-enable menu items because then Cocoa + // overrides our decisions and things get incorrectly enabled/disabled. + myMenu.autoenablesItems = NO; + ++ // Only show "Services", "Autofill" and similar entries provided by macOS ++ // if our caller wants them: ++ myMenu.allowsContextMenuPlugIns = aShowServices; ++ + // we used to install Carbon event handlers here, but since NSMenu* doesn't + // create its underlying MenuRef until just before display, we delay until + // that happens. Now we install the event handlers when Cocoa notifies + // us that a menu is about to display - see the Cocoa MenuDelegate class. + + return myMenu; + + NS_OBJC_END_TRY_ABORT_BLOCK; +diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm +--- a/widget/cocoa/nsNativeThemeCocoa.mm ++++ b/widget/cocoa/nsNativeThemeCocoa.mm +@@ -387,19 +387,18 @@ static BOOL FrameIsInActiveWindow(nsIFra + if ([win isSheet]) { + return [win isKeyWindow]; + } + return [win isMainWindow] && ![win attachedSheet]; + } + + // Toolbar controls and content controls respond to different window + // activeness states. +-static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) { +- if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow]; +- return FrameIsInActiveWindow(aFrame); ++static BOOL IsActiveToolbarControl(nsIFrame* aFrame) { ++ return NativeWindowForFrame(aFrame).isMainWindow; + } + + static bool IsInSourceList(nsIFrame* aFrame) { + for (nsIFrame* frame = aFrame->GetParent(); frame; + frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) { + if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) { + return true; + } +@@ -2397,17 +2396,17 @@ Maybe ns + return Some(WidgetInfo::Toolbar(isMain)); + } + + case StyleAppearance::MozWindowTitlebar: { + return Nothing(); + } + + case StyleAppearance::Statusbar: +- return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES))); ++ return Some(WidgetInfo::StatusBar(IsActiveToolbarControl(aFrame))); + + case StyleAppearance::MenulistButton: + case StyleAppearance::Menulist: { + ControlParams controlParams = ComputeControlParams(aFrame, elementState); + controlParams.pressed = IsOpenButton(aFrame); + DropdownParams params; + params.controlParams = controlParams; + params.pullsDown = false; +diff --git a/widget/cocoa/nsPIWidgetCocoa.idl b/widget/cocoa/nsPIWidgetCocoa.idl +--- a/widget/cocoa/nsPIWidgetCocoa.idl ++++ b/widget/cocoa/nsPIWidgetCocoa.idl +@@ -1,37 +0,0 @@ +-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +-/* 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/. */ +- +-#include "nsISupports.idl" +- +-interface nsIWidget; +- +-[ptr] native NSWindowPtr(NSWindow); +- +-// +-// nsPIWidgetCocoa +-// +-// A private interface (unfrozen, private to the widget implementation) that +-// gives us access to some extra features on a widget/window. +-// +-[uuid(f75ff69e-3a51-419e-bd29-042f804bc2ed)] +-interface nsPIWidgetCocoa : nsISupports +-{ +- void SendSetZLevelEvent(); +- +- // Find the displayed child sheet (if aShown) or a child sheet that +- // wants to be displayed (if !aShown) +- nsIWidget GetChildSheet(in boolean aShown); +- +- // Get the parent widget (if any) StandardCreate() was called with. +- nsIWidget GetRealParent(); +- +- // If the object implementing this interface is a sheet, this will return the +- // native NSWindow it is attached to +- readonly attribute NSWindowPtr sheetWindowParent; +- +- // True if window is a sheet +- readonly attribute boolean isSheet; +- +-}; // nsPIWidgetCocoa +diff --git a/widget/cocoa/nsWindowMap.mm b/widget/cocoa/nsWindowMap.mm +--- a/widget/cocoa/nsWindowMap.mm ++++ b/widget/cocoa/nsWindowMap.mm +@@ -160,17 +160,17 @@ + // only child widgets (nsChildView objects)). (The notification is sent + // to windowBecameKey: or windowBecameMain: below.) + // + // For use with clients that (like Firefox) do use top-level widgets (and + // have NSWindow delegates of class WindowDelegate). + + (void)activateInWindow:(NSWindow*)aWindow { + NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; + +- WindowDelegate* delegate = (WindowDelegate*)[aWindow delegate]; ++ WindowDelegate* delegate = (WindowDelegate*)aWindow.delegate; + if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) return; + + if ([delegate toplevelActiveState]) return; + [delegate sendToplevelActivateEvents]; + + NS_OBJC_END_TRY_IGNORE_BLOCK; + } + +diff --git a/widget/headless/HeadlessWidget.cpp b/widget/headless/HeadlessWidget.cpp +--- a/widget/headless/HeadlessWidget.cpp ++++ b/widget/headless/HeadlessWidget.cpp +@@ -155,20 +155,19 @@ void HeadlessWidget::GetCompositorWidget + mozilla::widget::CompositorWidgetInitData* aInitData) { + *aInitData = + mozilla::widget::HeadlessCompositorWidgetInitData(GetClientSize()); + } + + nsIWidget* HeadlessWidget::GetTopLevelWidget() { return mTopLevel; } + + void HeadlessWidget::RaiseWindow() { +- MOZ_ASSERT(mWindowType == WindowType::TopLevel || +- mWindowType == WindowType::Dialog || +- mWindowType == WindowType::Sheet, +- "Raising a non-toplevel window."); ++ MOZ_ASSERT( ++ mWindowType == WindowType::TopLevel || mWindowType == WindowType::Dialog, ++ "Raising a non-toplevel window."); + + // Do nothing if this is the currently active window. + RefPtr activeWindow = GetActiveWindow(); + if (activeWindow == this) { + return; + } + + // Raise the window to the top of the stack. +@@ -199,17 +198,17 @@ void HeadlessWidget::Show(bool aState) { + + LOG(("HeadlessWidget::Show [%p] state %d\n", (void*)this, aState)); + + // Top-level window and dialogs are activated/raised when shown. + // NB: alwaysontop windows are generally used for peripheral indicators, + // so we don't focus them by default. + if (aState && !mAlwaysOnTop && + (mWindowType == WindowType::TopLevel || +- mWindowType == WindowType::Dialog || mWindowType == WindowType::Sheet)) { ++ mWindowType == WindowType::Dialog)) { + RaiseWindow(); + } + + ApplySizeModeSideEffects(); + } + + bool HeadlessWidget::IsVisible() const { return mVisible; } + +diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h +--- a/xpcom/build/BinaryPath.h ++++ b/xpcom/build/BinaryPath.h +@@ -128,16 +128,56 @@ class BinaryPath { + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(executableURL); + return rv; + } + ++ static nsresult GetApplicationIni(char aResult[MAXPATHLEN]) ++ { ++ // Works even if we're not bundled. ++ CFBundleRef appBundle = CFBundleGetMainBundle(); ++ if (!appBundle) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ CFURLRef iniURL = CFBundleCopyResourceURL(appBundle, CFSTR("application.ini"), ++ NULL, CFSTR("app")); ++ if (!iniURL) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ nsresult rv; ++ if (CFURLGetFileSystemRepresentation(iniURL, false, (UInt8*)aResult, ++ MAXPATHLEN)) { ++ // Sanitize path in case the app was launched from Terminal via ++ // './firefox' for example. ++ size_t readPos = 0; ++ size_t writePos = 0; ++ while (aResult[readPos] != '\0') { ++ if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { ++ readPos += 2; ++ } else { ++ aResult[writePos] = aResult[readPos]; ++ readPos++; ++ writePos++; ++ } ++ } ++ aResult[writePos] = '\0'; ++ rv = NS_OK; ++ } else { ++ rv = NS_ERROR_FAILURE; ++ } ++ ++ CFRelease(iniURL); ++ return rv; ++ } ++ + #elif defined(ANDROID) + static nsresult Get(char aResult[MAXPATHLEN]) { + // On Android, we use the MOZ_ANDROID_LIBDIR variable that is set by the + // Java bootstrap code. + const char* libDir = getenv("MOZ_ANDROID_LIBDIR"); + if (!libDir) { + return NS_ERROR_FAILURE; + } +@@ -283,16 +323,29 @@ class BinaryPath { + if (NS_FAILED(Get(path))) { + return nullptr; + } + UniqueFreePtr result; + result.reset(strdup(path)); + return result; + } + ++#if defined(XP_MACOSX) ++ static UniqueFreePtr GetApplicationIni() ++ { ++ char path[MAXPATHLEN]; ++ if (NS_FAILED(GetApplicationIni(path))) { ++ return nullptr; ++ } ++ UniqueFreePtr result; ++ result.reset(strdup(path)); ++ return result; ++ } ++#endif ++ + #ifdef MOZILLA_INTERNAL_API + static nsresult GetFile(nsIFile** aResult) { + nsCOMPtr lf; + # ifdef XP_WIN + wchar_t exePath[MAXPATHLEN]; + nsresult rv = GetW(exePath); + # else + char exePath[MAXPATHLEN]; +diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py +--- a/xpcom/ds/StaticAtoms.py ++++ b/xpcom/ds/StaticAtoms.py +@@ -1141,16 +1141,17 @@ STATIC_ATOMS = [ + Atom("self", "self"), + Atom("seltype", "seltype"), + Atom("setcookie", "set-cookie"), + Atom("setter", "setter"), + Atom("shadow", "shadow"), + Atom("shape", "shape"), + Atom("show", "show"), + Atom("showcaret", "showcaret"), ++ Atom("showservicesmenu", "showservicesmenu"), + Atom("sibling", "sibling"), + Atom("simple", "simple"), + Atom("simp_chinese_formal", "simp-chinese-formal"), + Atom("simp_chinese_informal", "simp-chinese-informal"), + Atom("single", "single"), + Atom("size", "size"), + Atom("sizes", "sizes"), + Atom("sizemode", "sizemode"), +diff --git a/xpfe/appshell/nsAppShellService.cpp b/xpfe/appshell/nsAppShellService.cpp +--- a/xpfe/appshell/nsAppShellService.cpp ++++ b/xpfe/appshell/nsAppShellService.cpp +@@ -590,32 +590,16 @@ nsresult nsAppShellService::JustCreateTo + nsIWebBrowserChrome::CHROME_TITLEBAR | + nsIWebBrowserChrome::CHROME_STATUSBAR; + if (widgetInitData.mWindowType == widget::WindowType::Dialog && + ((aChromeMask & pipMask) == pipMask) && !(aChromeMask & barMask)) { + widgetInitData.mPIPWindow = true; + } + #endif + +-#ifdef XP_MACOSX +- // Mac OS X sheet support +- // Adding CHROME_OPENAS_CHROME to sheetMask makes modal windows opened from +- // nsGlobalWindow::ShowModalDialog() be dialogs (not sheets), while modal +- // windows opened from nsPromptService::DoDialog() still are sheets. This +- // fixes bmo bug 395465 (see nsCocoaWindow::StandardCreate() and +- // nsCocoaWindow::SetModal()). +- uint32_t sheetMask = nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | +- nsIWebBrowserChrome::CHROME_MODAL | +- nsIWebBrowserChrome::CHROME_OPENAS_CHROME; +- if (parent && (parent != mHiddenWindow) && +- ((aChromeMask & sheetMask) == sheetMask)) { +- widgetInitData.mWindowType = widget::WindowType::Sheet; +- } +-#endif +- + #if defined(XP_WIN) + if (widgetInitData.mWindowType == widget::WindowType::TopLevel || + widgetInitData.mWindowType == widget::WindowType::Dialog) + widgetInitData.mClipChildren = true; + #endif + + // note default chrome overrides other OS chrome settings, but + // not internal chrome diff --git a/app/mac/pkg-dmg b/app/mac/pkg-dmg new file mode 100755 index 0000000000..f84ba962ca --- /dev/null +++ b/app/mac/pkg-dmg @@ -0,0 +1,1520 @@ +#!/usr/bin/perl +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# 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 pkg-dmg, a Mac OS X disk image (.dmg) packager +# +# The Initial Developer of the Original Code is +# Mark Mentovai . +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +use strict; +use warnings; + +=pod + +=head1 NAME + +B - Mac OS X disk image (.dmg) packager + +=head1 SYNOPSIS + +B +B<--source> I +B<--target> I +[B<--format> I] +[B<--volname> I] +[B<--tempdir> I] +[B<--mkdir> I] +[B<--copy> I[:I]] +[B<--symlink> I[:I]] +[B<--license> I] +[B<--resource> I] +[B<--icon> I] +[B<--attribute> I:I[:I...] +[B<--idme>] +[B<--sourcefile>] +[B<--verbosity> I] +[B<--dry-run>] + +=head1 DESCRIPTION + +I takes a directory identified by I and transforms +it into a disk image stored as I. The disk image will +occupy the least space possible for its format, or the least space that the +authors have been able to figure out how to achieve. + +=head1 OPTIONS + +=over 5 + +==item B<--source> I + +Identifies the directory that will be packaged up. This directory is not +touched, a copy will be made in a temporary directory for staging purposes. +See B<--tempdir>. + +==item B<--target> I + +The disk image to create. If it exists and is not in use, it will be +overwritten. If I already contains a suitable extension, +it will be used unmodified. If no extension is present, or the extension +is incorrect for the selected format, the proper extension will be added. +See B<--format>. + +==item B<--format> I + +The format to create the disk image in. Valid values for I are: + - UDZO - zlib-compressed, read-only; extension I<.dmg> + - UDBZ - bzip2-compressed, read-only; extension I<.dmg>; + create and use on 10.4 ("Tiger") and later only + - UDRW - read-write; extension I<.dmg> + - UDSP - read-write, sparse; extension I<.sparseimage> + +UDBZ is the default format. + +See L for a description of these formats. + +=item B<--volname> I + +The name of the volume in the disk image. If not specified, I +defaults to the name of the source directory from B<--source>. + +=item B<--tempdir> I + +A temporary directory to stage intermediate files in. I must +have enough space available to accommodate twice the size of the files +being packaged. If not specified, defaults to the same directory that +the I is to be placed in. B will remove any +temporary files it places in I. + +=item B<--mkdir> I + +Specifies a directory that should be created in the disk image. +I and any ancestor directories will be created. This is +useful in conjunction with B<--copy>, when copying files to directories +that may not exist in I. B<--mkdir> may appear multiple +times. + +=item B<--copy> I[:I] + +Additional files to copy into the disk image. If I is +specified, I is copied to the location I identifies, +otherwise, I is copied to the root of the new volume. B<--copy> +provides a way to package up a I by adding files to it +without modifying the original I. B<--copy> may appear +multiple times. + +This option is useful for adding .DS_Store files and window backgrounds +to disk images. + +=item B<--symlink> I[:I] + +Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink +destinations are interpreted as "like the source path, but inside the dmg" + +This option is useful for adding symlinks to external resources, +e.g. to /Applications. + +=item B<--license> I + +A plain text file containing a license agreement to be displayed before +the disk image is mounted. English is the only supported language. To +include license agreements in other languages, in multiple languages, +or to use formatted text, prepare a resource and use L<--resource>. + +=item B<--resource> I + +A resource file to merge into I. If I is UDZO or +UDBZ, the disk image will be flattened to a single-fork file that contains +the resource but may be freely transferred without any special encodings. +I must be in a format suitable for L. See L for a +description of the format, and L for a discussion on flattened +disk images. B<--resource> may appear multiple times. + +This option is useful for adding license agreements and other messages +to disk images. + +=item B<--icon> I + +Specifies an I file that will be used as the icon for the root of +the volume. This file will be copied to the new volume and the custom +icon attribute will be set on the root folder. + +=item B<--attribute> I:I[:I...] + +Sets the attributes of I to the attribute list in I. See +L + +=item B<--idme> + +Enable IDME to make the disk image "Internet-enabled." The first time +the image is mounted, if IDME processing is enabled on the system, the +contents of the image will be copied out of the image and the image will +be placed in the trash with IDME disabled. + +=item B<--sourcefile> + +If this option is present, I is treated as a file, and is +placed as a file within the volume's root folder. Without this option, +I is treated as the volume root itself. + +=item B<--verbosity> I + +Adjusts the level of loudness of B. The possible values for +I are: + 0 - Only error messages are displayed. + 1 - Print error messages and command invocations. + 2 - Print everything, including command output. + +The default I is 2. + +=item B<--dry-run> + +When specified, the commands that would be executed are printed, without +actually executing them. When commands depend on the output of previous +commands, dummy values are displayed. + +=back + +=head1 NON-OPTIONS + +=over 5 + +=item + +Resource forks aren't copied. + +=item + +The root folder of the created volume is designated as the folder +to open when the volume is mounted. See L. + +=item + +All files in the volume are set to be world-readable, only writable +by the owner, and world-executable when appropriate. All other +permissions bits are cleared. + +=item + +When possible, disk images are created without any partition tables. This +is what L refers to as I<-layout NONE>, and saves a handful of +kilobytes. The alternative, I, contains a partition table that +is not terribly handy on disk images that are not intended to represent any +physical disk. + +=item + +Read-write images are created with journaling off. Any read-write image +created by this tool is expected to be transient, and the goal of this tool +is to create images which consume a minimum of space. + +=back + +=head1 EXAMPLE + +pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg + --sourcefile --volname DeerPark --icon ~/DeerPark.icns + --mkdir /.background + --copy DeerParkBackground.png:/.background/background.png + --copy DeerParkDSStore:/.DS_Store + --symlink /Applications:"/Drag to here" + +=head1 REQUIREMENTS + +I has been tested with Mac OS X releases 10.2 ("Jaguar") +through 10.4 ("Tiger"). Certain adjustments to behavior are made +depending on the host system's release. Mac OS X 10.3 ("Panther") or +later are recommended. + +=head1 LICENSE + +MPL 1.1/GPL 2.0/LGPL 2.1. Your choice. + +=head1 AUTHOR + +Mark Mentovai + +=head1 SEE ALSO + +L, L, L, L, L, +L, L + +=cut + +use Fcntl; +use POSIX; +use Getopt::Long; + +sub argumentEscape(@); +sub cleanupDie($); +sub command(@); +sub commandInternal($@); +sub commandInternalVerbosity($$@); +sub commandOutput(@); +sub commandOutputVerbosity($@); +sub commandVerbosity($@); +sub copyFiles($@); +sub diskImageMaker($$$$$$$$); +sub giveExtension($$); +sub hdidMountImage($@); +sub isFormatCompressed($); +sub licenseMaker($$); +sub pathSplit($); +sub setAttributes($@); +sub trapSignal($); +sub usage(); + +# Variables used as globals +my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity); + +# Use the commands by name if they're expected to be in the user's +# $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute +# path. These may be overridden with --config. +%gConfig = ('cmd_bless' => 'bless', + 'cmd_chmod' => 'chmod', + 'cmd_diskutil' => 'diskutil', + 'cmd_du' => 'du', + 'cmd_hdid' => 'hdid', + 'cmd_hdiutil' => 'hdiutil', + 'cmd_mkdir' => 'mkdir', + 'cmd_mktemp' => 'mktemp', + 'cmd_Rez' => '/Developer/Tools/Rez', + 'cmd_rm' => 'rm', + 'cmd_rsync' => 'rsync', + 'cmd_SetFile' => '/Developer/Tools/SetFile', + + # create_directly indicates whether hdiutil create supports + # -srcfolder and -srcdevice. It does on >= 10.3 (Panther). + # This is fixed up for earlier systems below. If false, + # hdiutil create is used to create empty disk images that + # are manually filled. + 'create_directly' => 1, + + # If hdiutil attach -mountpoint exists, use it to avoid + # mounting disk images in the default /Volumes. This reduces + # the likelihood that someone will notice a mounted image and + # interfere with it. Only available on >= 10.3 (Panther), + # fixed up for earlier systems below. + # + # This is presently turned off for all systems, because there + # is an infrequent synchronization problem during ejection. + # diskutil eject might return before the image is actually + # unmounted. If pkg-dmg then attempts to clean up its + # temporary directory, it could remove items from a read-write + # disk image or attempt to remove items from a read-only disk + # image (or a read-only item from a read-write image) and fail, + # causing pkg-dmg to abort. This problem is experienced + # under Tiger, which appears to eject asynchronously where + # previous systems treated it as a synchronous operation. + # Using hdiutil attach -mountpoint didn't always keep images + # from showing up on the desktop anyway. + 'hdiutil_mountpoint' => 0, + + # hdiutil makehybrid results in optimized disk images that + # consume less space and mount more quickly. Use it when + # it's available, but that's only on >= 10.3 (Panther). + # If false, hdiutil create is used instead. Fixed up for + # earlier systems below. + 'makehybrid' => 1, + + # hdiutil create doesn't allow specifying a folder to open + # at volume mount time, so those images are mounted and + # their root folders made holy with bless -openfolder. But + # only on >= 10.3 (Panther). Earlier systems are out of luck. + # Even on Panther, bless refuses to run unless root. + # Fixed up below. + 'openfolder_bless' => 1, + + # It's possible to save a few more kilobytes by including the + # partition only without any partition table in the image. + # This is a good idea on any system, so turn this option off. + # + # Except it's buggy. "-layout NONE" seems to be creating + # disk images with more data than just the partition table + # stripped out. You might wind up losing the end of the + # filesystem - the last file (or several) might be incomplete. + 'partition_table' => 1, + + # To create a partition table-less image from something + # created by makehybrid, the hybrid image needs to be + # mounted and a new image made from the device associated + # with the relevant partition. This requires >= 10.4 + # (Tiger), presumably because earlier systems have + # problems creating images from devices themselves attached + # to images. If this is false, makehybrid images will + # have partition tables, regardless of the partition_table + # setting. Fixed up for earlier systems below. + 'recursive_access' => 1); + +# --verbosity +$gVerbosity = 2; + +# --dry-run +$gDryRun = 0; + +# %gConfig fix-ups based on features and bugs present in certain releases. +my($ignore, $uname_r, $uname_s); +($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname(); +if($uname_s eq 'Darwin') { + ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2); + + # $major is the Darwin major release, which for our purposes, is 4 higher + # than the interesting digit in a Mac OS X release. + if($gDarwinMajor <= 6) { + # <= 10.2 (Jaguar) + # hdiutil create does not support -srcfolder or -srcdevice + $gConfig{'create_directly'} = 0; + # hdiutil attach does not support -mountpoint + $gConfig{'hdiutil_mountpoint'} = 0; + # hdiutil mkhybrid does not exist + $gConfig{'makehybrid'} = 0; + } + if($gDarwinMajor <= 7) { + # <= 10.3 (Panther) + # Can't mount a disk image and then make a disk image from the device + $gConfig{'recursive_access'} = 0; + # bless does not support -openfolder on 10.2 (Jaguar) and must run + # as root under 10.3 (Panther) + $gConfig{'openfolder_bless'} = 0; + } +} +else { + # If it's not Mac OS X, just assume all of those good features are + # available. They're not, but things will fail long before they + # have a chance to make a difference. + # + # Now, if someone wanted to document some of these private formats... + print STDERR ($0.": warning, not running on Mac OS X, ". + "this could be interesting.\n"); +} + +# Non-global variables used in Getopt +my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile, + @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder, + $targetImage, $tempDir, $volumeName); + +# --format +$outputFormat = 'UDBZ'; + +# --idme +$idme = 0; + +# --sourcefile +$sourceFile = 0; + +# Leaving this might screw up the Apple tools. +delete $ENV{'NEXT_ROOT'}; + +# This script can get pretty messy, so trap a few signals. +$SIG{'INT'} = \&trapSignal; +$SIG{'HUP'} = \&trapSignal; +$SIG{'TERM'} = \&trapSignal; + +Getopt::Long::Configure('pass_through'); +GetOptions('source=s' => \$sourceFolder, + 'target=s' => \$targetImage, + 'volname=s' => \$volumeName, + 'format=s' => \$outputFormat, + 'tempdir=s' => \$tempDir, + 'mkdir=s' => \@makeDirs, + 'copy=s' => \@copyFiles, + 'symlink=s' => \@createSymlinks, + 'license=s' => \$licenseFile, + 'resource=s' => \@resourceFiles, + 'icon=s' => \$iconFile, + 'attribute=s' => \@attributes, + 'idme' => \$idme, + 'sourcefile' => \$sourceFile, + 'verbosity=i' => \$gVerbosity, + 'dry-run' => \$gDryRun, + 'config=s' => \%gConfig); # "hidden" option not in usage() + +if(@ARGV) { + # All arguments are parsed by Getopt + usage(); + exit(1); +} + +if($gVerbosity<0 || $gVerbosity>2) { + usage(); + exit(1); +} + +if(!defined($sourceFolder) || $sourceFolder eq '' || + !defined($targetImage) || $targetImage eq '') { + # --source and --target are required arguments + usage(); + exit(1); +} + +# Make sure $sourceFolder doesn't contain trailing slashes. It messes with +# rsync. +while(substr($sourceFolder, -1) eq '/') { + chop($sourceFolder); +} + +if(!defined($volumeName)) { + # Default volumeName is the name of the source directory. + my(@components); + @components = pathSplit($sourceFolder); + $volumeName = pop(@components); +} + +my(@tempDirComponents, $targetImageFilename); +@tempDirComponents = pathSplit($targetImage); +$targetImageFilename = pop(@tempDirComponents); + +if(defined($tempDir)) { + @tempDirComponents = pathSplit($tempDir); +} +else { + # Default tempDir is the same directory as what is specified for + # targetImage + $tempDir = join('/', @tempDirComponents); +} + +# Ensure that the path of the target image has a suitable extension. If +# it didn't, hdiutil would add one, and we wouldn't be able to find the +# file. +# +# Note that $targetImageFilename is not being reset. This is because it's +# used to build other names below, and we don't need to be adding all sorts +# of extra unnecessary extensions to the name. +my($originalTargetImage, $requiredExtension); +$originalTargetImage = $targetImage; +if($outputFormat eq 'UDSP') { + $requiredExtension = '.sparseimage'; +} +else { + $requiredExtension = '.dmg'; +} +$targetImage = giveExtension($originalTargetImage, $requiredExtension); + +if($targetImage ne $originalTargetImage) { + print STDERR ($0.": warning: target image extension is being added\n"); + print STDERR (' The new filename is '. + giveExtension($targetImageFilename,$requiredExtension)."\n"); +} + +# Make a temporary directory in $tempDir for our own nefarious purposes. +my(@output, $tempSubdir, $tempSubdirTemplate); +$tempSubdirTemplate=join('/', @tempDirComponents, + 'pkg-dmg.'.$$.'.XXXXXXXX'); +if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d', + $tempSubdirTemplate)) || $#output != 0) { + cleanupDie('mktemp failed'); +} + +if($gDryRun) { + (@output)=($tempSubdirTemplate); +} + +($tempSubdir) = @output; + +push(@gCleanup, + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);}); + +my($tempMount, $tempRoot, @tempsToMake); +$tempRoot = $tempSubdir.'/stage'; +$tempMount = $tempSubdir.'/mount'; +push(@tempsToMake, $tempRoot); +if($gConfig{'hdiutil_mountpoint'}) { + push(@tempsToMake, $tempMount); +} + +if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) { + cleanupDie('mkdir tempRoot/tempMount failed'); +} + +# This cleanup object is not strictly necessary, because $tempRoot is inside +# of $tempSubdir, but the rest of the script relies on this object being +# on the cleanup stack and expects to remove it. +push(@gCleanup, + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);}); + +# If $sourceFile is true, it means that $sourceFolder is to be treated as +# a file and placed as a file within the volume root, as opposed to being +# treated as the volume root itself. rsync will do this by default, if no +# trailing '/' is present. With a trailing '/', $sourceFolder becomes +# $tempRoot, instead of becoming an entry in $tempRoot. +if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', + $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) { + cleanupDie('rsync failed'); +} + +if(@makeDirs) { + my($makeDir, @tempDirsToMake); + foreach $makeDir (@makeDirs) { + if($makeDir =~ /^\//) { + push(@tempDirsToMake, $tempRoot.$makeDir); + } + else { + push(@tempDirsToMake, $tempRoot.'/'.$makeDir); + } + } + if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) { + cleanupDie('mkdir failed'); + } +} + +# copy files and/or create symlinks +copyFiles($tempRoot, 'copy', @copyFiles); +copyFiles($tempRoot, 'symlink', @createSymlinks); + +if($gConfig{'create_directly'}) { + # If create_directly is false, the contents will be rsynced into a + # disk image and they would lose their attributes. + setAttributes($tempRoot, @attributes); +} + +if(defined($iconFile)) { + if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', $iconFile, + $tempRoot.'/.VolumeIcon.icns') != 0) { + cleanupDie('rsync failed for volume icon'); + } + + # It's pointless to set the attributes of the root when diskutil create + # -srcfolder is being used. In that case, the attributes will be set + # later, after the image is already created. + if(isFormatCompressed($outputFormat) && + (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) { + cleanupDie('SetFile failed'); + } +} + +if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w', + $tempRoot) != 0) { + cleanupDie('chmod failed'); +} + +my($unflattenable); +if(isFormatCompressed($outputFormat)) { + $unflattenable = 1; +} +else { + $unflattenable = 0; +} + +diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName, + $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile)); + +if(defined($licenseFile) && $licenseFile ne '') { + my($licenseResource); + $licenseResource = $tempSubdir.'/license.r'; + if(!licenseMaker($licenseFile, $licenseResource)) { + cleanupDie('licenseMaker failed'); + } + push(@resourceFiles, $licenseResource); + # Don't add a cleanup object because licenseResource is in tempSubdir. +} + +if(@resourceFiles) { + # Add resources, such as a license agreement. + + # Only unflatten read-only and compressed images. It's not supported + # on other image times. + if($unflattenable && + (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) { + cleanupDie('hdiutil unflatten failed'); + } + # Don't push flatten onto the cleanup stack. If we fail now, we'll be + # removing $targetImage anyway. + + # Type definitions come from Carbon.r. + if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o', + $targetImage) != 0) { + cleanupDie('Rez failed'); + } + + # Flatten. This merges the resource fork into the data fork, so no + # special encoding is needed to transfer the file. + if($unflattenable && + (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) { + cleanupDie('hdiutil flatten failed'); + } +} + +# $tempSubdir is no longer needed. It's buried on the stack below the +# rm of the fresh image file. Splice in this fashion is equivalent to +# pop-save, pop, push-save. +splice(@gCleanup, -2, 1); +# No need to remove licenseResource separately, it's in tempSubdir. +if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) { + cleanupDie('rm -rf tempSubdir failed'); +} + +if($idme) { + if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes', + $targetImage) != 0) { + cleanupDie('hdiutil internet-enable failed'); + } +} + +# Done. + +exit(0); + +# argumentEscape(@arguments) +# +# Takes a list of @arguments and makes them shell-safe. +sub argumentEscape(@) { + my(@arguments); + @arguments = @_; + my($argument, @argumentsOut); + foreach $argument (@arguments) { + $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g; + push(@argumentsOut, $argument); + } + return @argumentsOut; +} + +# cleanupDie($message) +# +# Displays $message as an error message, and then runs through the +# @gCleanup stack, performing any cleanup operations needed before +# exiting. Does not return, exits with exit status 1. +sub cleanupDie($) { + my($message); + ($message) = @_; + print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n"); + while(@gCleanup) { + my($subroutine); + $subroutine = pop(@gCleanup); + &$subroutine; + } + exit(1); +} + +# command(@arguments) +# +# Runs the specified command at the verbosity level defined by $gVerbosity. +# Returns nonzero on failure, returning the exit status if appropriate. +# Discards command output. +sub command(@) { + my(@arguments); + @arguments = @_; + return commandVerbosity($gVerbosity,@arguments); +} + +# commandInternal($command, @arguments) +# +# Runs the specified internal command at the verbosity level defined by +# $gVerbosity. +# Returns zero(!) on failure, because commandInternal is supposed to be a +# direct replacement for the Perl system call wrappers, which, unlike shell +# commands and C equivalent system calls, return true (instead of 0) to +# indicate success. +sub commandInternal($@) { + my(@arguments, $command); + ($command, @arguments) = @_; + return commandInternalVerbosity($gVerbosity, $command, @arguments); +} + +# commandInternalVerbosity($verbosity, $command, @arguments) +# +# Run an internal command, printing a bogus command invocation message if +# $verbosity is true. +# +# If $command is unlink: +# Removes the files specified by @arguments. Wraps unlink. +# +# If $command is symlink: +# Creates the symlink specified by @arguments. Wraps symlink. +sub commandInternalVerbosity($$@) { + my(@arguments, $command, $verbosity); + ($verbosity, $command, @arguments) = @_; + if($command eq 'unlink') { + if($verbosity || $gDryRun) { + print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n"); + } + if($gDryRun) { + return $#arguments+1; + } + return unlink(@arguments); + } + elsif($command eq 'symlink') { + if($verbosity || $gDryRun) { + print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n"); + } + if($gDryRun) { + return 1; + } + my($source, $target); + ($source, $target) = @arguments; + return symlink($source, $target); + } +} + +# commandOutput(@arguments) +# +# Runs the specified command at the verbosity level defined by $gVerbosity. +# Output is returned in an array of lines. undef is returned on failure. +# The exit status is available in $?. +sub commandOutput(@) { + my(@arguments); + @arguments = @_; + return commandOutputVerbosity($gVerbosity, @arguments); +} + +# commandOutputVerbosity($verbosity, @arguments) +# +# Runs the specified command at the verbosity level defined by the +# $verbosity argument. Output is returned in an array of lines. undef is +# returned on failure. The exit status is available in $?. +# +# If an error occurs in fork or exec, an error message is printed to +# stderr and undef is returned. +# +# If $verbosity is 0, the command invocation is not printed, and its +# stdout is not echoed back to stdout. +# +# If $verbosity is 1, the command invocation is printed. +# +# If $verbosity is 2, the command invocation is printed and the output +# from stdout is echoed back to stdout. +# +# Regardless of $verbosity, stderr is left connected. +sub commandOutputVerbosity($@) { + my(@arguments, $verbosity); + ($verbosity, @arguments) = @_; + my($pid); + if($verbosity || $gDryRun) { + print(join(' ', argumentEscape(@arguments))."\n"); + } + if($gDryRun) { + return(1); + } + if (!defined($pid = open(*COMMAND, '-|'))) { + printf STDERR ($0.': fork: '.$!."\n"); + return undef; + } + elsif ($pid) { + # parent + my(@lines); + while(!eof(*COMMAND)) { + my($line); + chop($line = ); + if($verbosity > 1) { + print($line."\n"); + } + push(@lines, $line); + } + close(*COMMAND); + if ($? == -1) { + printf STDERR ($0.': fork: '.$!."\n"); + return undef; + } + elsif ($? & 127) { + printf STDERR ($0.': exited on signal '.($? & 127). + ($? & 128 ? ', core dumped' : '')."\n"); + return undef; + } + return @lines; + } + else { + # child; this form of exec is immune to shell games + if(!exec {$arguments[0]} (@arguments)) { + printf STDERR ($0.': exec: '.$!."\n"); + exit(-1); + } + } +} + +# commandVerbosity($verbosity, @arguments) +# +# Runs the specified command at the verbosity level defined by the +# $verbosity argument. Returns nonzero on failure, returning the exit +# status if appropriate. Discards command output. +sub commandVerbosity($@) { + my(@arguments, $verbosity); + ($verbosity, @arguments) = @_; + if(!defined(commandOutputVerbosity($verbosity, @arguments))) { + return -1; + } + return $?; +} + +# copyFiles($tempRoot, $method, @arguments) +# +# Copies files or create symlinks in the disk image. +# See --copy and --symlink descriptions for details. +# If $method is 'copy', @arguments are interpreted as source:target, if $method +# is 'symlink', @arguments are interpreted as symlink:target. +sub copyFiles($@) { + my(@fileList, $method, $tempRoot); + ($tempRoot, $method, @fileList) = @_; + my($file, $isSymlink); + $isSymlink = ($method eq 'symlink'); + foreach $file (@fileList) { + my($source, $target); + ($source, $target) = split(/:/, $file); + if(!defined($target) and $isSymlink) { + # empty symlink targets would result in an invalid target and fail, + # but they shall be interpreted as "like source path, but inside dmg" + $target = $source; + } + if(!defined($target)) { + $target = $tempRoot; + } + elsif($target =~ /^\//) { + $target = $tempRoot.$target; + } + else { + $target = $tempRoot.'/'.$target; + } + + my($success); + if($isSymlink) { + $success = commandInternal('symlink', $source, $target); + } + else { + $success = !command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', + $source, $target); + } + if(!$success) { + cleanupDie('copyFiles failed for method '.$method); + } + } +} + +# diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount, +# $baseName, $setRootIcon) +# +# Creates a disk image in $destination of format $format corresponding to the +# source directory $source. $name is the volume name. $tempDir is a good +# place to write temporary files, which should be empty (aside from the other +# things that this script might create there, like stage and mount). +# $tempMount is a mount point for temporary disk images. $baseName is the +# name of the disk image, and is presently unused. $setRootIcon is true if +# a volume icon was added to the staged $source and indicates that the +# custom volume icon bit on the volume root needs to be set. +sub diskImageMaker($$$$$$$$) { + my($baseName, $destination, $format, $name, $setRootIcon, $source, + $tempDir, $tempMount); + ($source, $destination, $format, $name, $tempDir, $tempMount, + $baseName, $setRootIcon) = @_; + if(isFormatCompressed($format)) { + my($uncompressedImage); + + if($gConfig{'makehybrid'}) { + my($hybridImage); + $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg'); + + if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs', + '-hfs-volume-name', $name, '-hfs-openfolder', $source, '-ov', + $source, '-o', $hybridImage) != 0) { + cleanupDie('hdiutil makehybrid failed'); + } + + $uncompressedImage = $hybridImage; + + # $source is no longer needed and will be removed before anything + # else can fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $hybridImage);}); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + + if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) { + # Even if we do want to create disk images without partition tables, + # it's impossible unless recursive_access is set. + my($rootDevice, $partitionDevice, $partitionMountPoint); + + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = + hdidMountImage($tempMount, '-readonly', $hybridImage))) { + cleanupDie('hdid mount failed'); + } + + push(@gCleanup, sub {commandVerbosity(0, + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); + + my($udrwImage); + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); + + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW', + '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) { + cleanupDie('hdiutil create failed'); + } + + $uncompressedImage = $udrwImage; + + # Going to eject before anything else can fail. Get the eject off + # the stack. + pop(@gCleanup); + + # $hybridImage will be removed soon, but until then, it needs to + # stay on the cleanup stack. It needs to wait until after + # ejection. $udrwImage is staying around. Make it appear as + # though it's been done before $hybridImage. + # + # splice in this form is the same as popping one element to + # @tempCleanup and pushing the subroutine. + my(@tempCleanup); + @tempCleanup = splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $udrwImage);}); + push(@gCleanup, @tempCleanup); + + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { + cleanupDie('diskutil eject failed'); + } + + # Pop unlink of $uncompressedImage + pop(@gCleanup); + + if(commandInternal('unlink', $hybridImage) != 1) { + cleanupDie('unlink hybridImage failed: '.$!); + } + } + } + else { + # makehybrid is not available, fall back to making a UDRW and + # converting to a compressed image. It ought to be possible to + # create a compressed image directly, but those come out far too + # large (journaling?) and need to be read-write to fix up the + # volume icon anyway. Luckily, we can take advantage of a single + # call back into this function. + my($udrwImage); + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); + + diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir, + $tempMount, $baseName, $setRootIcon); + + # The call back into diskImageMaker already removed $source. + + $uncompressedImage = $udrwImage; + } + + # The uncompressed disk image is now in its final form. Compress it. + # Jaguar doesn't support hdiutil convert -ov, but it always allows + # overwriting. + # bzip2-compressed UDBZ images can only be created and mounted on 10.4 + # and later. The bzip2-level imagekey is only effective when creating + # images in 10.5. In 10.4, bzip2-level is harmlessly ignored, and the + # default value of 1 is always used. + if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', $format, + '-imagekey', ($format eq 'UDBZ' ? 'bzip2-level=9' : 'zlib-level=9'), + (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')), + $uncompressedImage, '-o', $destination) != 0) { + cleanupDie('hdiutil convert failed'); + } + + # $uncompressedImage is going to be unlinked before anything else can + # fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + if(commandInternal('unlink', $uncompressedImage) != 1) { + cleanupDie('unlink uncompressedImage failed: '.$!); + } + + # At this point, the only thing that the compressed block has added to + # the cleanup stack is the removal of $destination. $source has already + # been removed, and its cleanup entry has been removed as well. + } + elsif($format eq 'UDRW' || $format eq 'UDSP') { + my(@extraArguments); + if(!$gConfig{'partition_table'}) { + @extraArguments = ('-layout', 'NONE'); + } + + if($gConfig{'create_directly'}) { + # Use -fs HFS+ to suppress the journal. + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format, + @extraArguments, '-fs', 'HFS+', '-volname', $name, + '-ov', '-srcfolder', $source, $destination) != 0) { + cleanupDie('hdiutil create failed'); + } + + # $source is no longer needed and will be removed before anything + # else can fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + } + else { + # hdiutil create does not support -srcfolder or -srcdevice, it only + # knows how to create blank images. Figure out how large an image + # is needed, create it, and fill it. This is needed for Jaguar. + + # Use native block size for hdiutil create -sectors. + delete $ENV{'BLOCKSIZE'}; + + my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type); + if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) || + $? != 0) { + cleanupDie('du failed'); + } + ($sizeBlocks, $ignore) = split(' ', $output[0], 2); + + # The filesystem itself takes up 152 blocks of its own blocks for the + # filesystem up to 8192 blocks, plus 64 blocks for every additional + # 4096 blocks or portion thereof. + $sizeOverhead = 152 + 64 * POSIX::ceil( + (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0); + + # The number of blocks must be divisible by 8. + my($mod); + if($mod = ($sizeOverhead % 8)) { + $sizeOverhead += 8 - $mod; + } + + # sectors is taken as the size of a disk, not a filesystem, so the + # partition table eats into it. + if($gConfig{'partition_table'}) { + $sizeOverhead += 80; + } + + # That was hard. Leave some breathing room anyway. Use 1024 sectors + # (512kB). These read-write images wouldn't be useful if they didn't + # have at least a little free space. + $sizeTotal = $sizeBlocks + $sizeOverhead + 1024; + + # Minimum sizes - these numbers are larger on Jaguar than on later + # systems. Just use the Jaguar numbers, since it's unlikely to wind + # up here on any other release. + if($gConfig{'partition_table'} && $sizeTotal < 8272) { + $sizeTotal = 8272; + } + if(!$gConfig{'partition_table'} && $sizeTotal < 8192) { + $sizeTotal = 8192; + } + + # hdiutil create without -srcfolder or -srcdevice will not accept + # -format. It uses -type. Fortunately, the two supported formats + # here map directly to the only two supported types. + if ($format eq 'UDSP') { + $type = 'SPARSE'; + } + else { + $type = 'UDIF'; + } + + if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type, + @extraArguments, '-fs', 'HFS+', '-volname', $name, + '-ov', '-sectors', $sizeTotal, $destination) != 0) { + cleanupDie('hdiutil create failed'); + } + + push(@gCleanup, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + # The rsync will occur shortly. + } + + my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint); + + $mounted=0; + if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} || + $setRootIcon) { + # The disk image only needs to be mounted if: + # create_directly is false, because the content needs to be copied + # openfolder_bless is true, because bless -openfolder needs to run + # setRootIcon is true, because the root needs its attributes set. + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = + hdidMountImage($tempMount, $destination))) { + cleanupDie('hdid mount failed'); + } + + $mounted=1; + + push(@gCleanup, sub {commandVerbosity(0, + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); + } + + if(!$gConfig{'create_directly'}) { + # Couldn't create and copy directly in one fell swoop. Now that + # the volume is mounted, copy the files. --copy-unsafe-links is + # unnecessary since it was used to copy everything to the staging + # area. There can be no more unsafe links. + if(command($gConfig{'cmd_rsync'}, '-a', + $source.'/',$partitionMountPoint) != 0) { + cleanupDie('rsync to new volume failed'); + } + + # We need to get the rm -rf of $source off the stack, because it's + # being cleaned up here. There are two items now on top of it: + # removing the target image and, above that, ejecting it. Splice it + # out. + my(@tempCleanup); + @tempCleanup = splice(@gCleanup, -2); + # The next splice is the same as popping once and pushing @tempCleanup. + splice(@gCleanup, -1, 1, @tempCleanup); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + } + + if($gConfig{'openfolder_bless'}) { + # On Tiger, the bless docs say to use --openfolder, but only + # --openfolder is accepted on Panther. Tiger takes it with a single + # dash too. Jaguar is out of luck. + if(command($gConfig{'cmd_bless'}, '-openfolder', + $partitionMountPoint) != 0) { + cleanupDie('bless failed'); + } + } + + setAttributes($partitionMountPoint, @attributes); + + if($setRootIcon) { + # When "hdiutil create -srcfolder" is used, the root folder's + # attributes are not copied to the new volume. Fix up. + + if(command($gConfig{'cmd_SetFile'}, '-a', 'C', + $partitionMountPoint) != 0) { + cleanupDie('SetFile failed'); + } + } + + if($mounted) { + # Pop diskutil eject + pop(@gCleanup); + + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { + cleanupDie('diskutil eject failed'); + } + } + + # End of UDRW/UDSP section. At this point, $source has been removed + # and its cleanup entry has been removed from the stack. + } + else { + cleanupDie('unrecognized format'); + print STDERR ($0.": unrecognized format\n"); + exit(1); + } +} + +# giveExtension($file, $extension) +# +# If $file does not end in $extension, $extension is added. The new +# filename is returned. +sub giveExtension($$) { + my($extension, $file); + ($file, $extension) = @_; + if(substr($file, -length($extension)) ne $extension) { + return $file.$extension; + } + return $file; +} + +# hdidMountImage($mountPoint, @arguments) +# +# Runs the hdid command with arguments specified by @arguments. +# @arguments may be a single-element array containing the name of the +# disk image to mount. Returns a three-element array, with elements +# corresponding to: +# - The root device of the mounted image, suitable for ejection +# - The device corresponding to the mounted partition +# - The mounted partition's mount point +# +# If running on a system that supports easy mounting at points outside +# of the default /Volumes with hdiutil attach, it is used instead of hdid, +# and $mountPoint is used as the mount point. +# +# The root device will differ from the partition device when the disk +# image contains a partition table, otherwise, they will be identical. +# +# If hdid fails, undef is returned. +sub hdidMountImage($@) { + my(@arguments, @command, $mountPoint); + ($mountPoint, @arguments) = @_; + my(@output); + + if($gConfig{'hdiutil_mountpoint'}) { + @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments, + '-mountpoint', $mountPoint); + } + else { + @command=($gConfig{'cmd_hdid'}, @arguments); + } + + if(!(@output = commandOutput(@command)) || + $? != 0) { + return undef; + } + + if($gDryRun) { + return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName); + } + + my($line, $restOfLine, $rootDevice); + + foreach $line (@output) { + my($device, $mountpoint); + if($line !~ /^\/dev\//) { + # Consider only lines that correspond to /dev entries + next; + } + ($device, $restOfLine) = split(' ', $line, 2); + + if(!defined($rootDevice) || $rootDevice eq '') { + # If this is the first device seen, it's the root device to be + # used for ejection. Keep it. + $rootDevice = $device; + } + + if($restOfLine =~ /(\/.*)/) { + # The first partition with a mount point is the interesting one. It's + # usually Apple_HFS and usually the last one in the list, but beware of + # the possibility of other filesystem types and the Apple_Free partition. + # If the disk image contains no partition table, the partition will not + # have a type, so look for the mount point by looking for a slash. + $mountpoint = $1; + return($rootDevice, $device, $mountpoint); + } + } + + # No mount point? This is bad. If there's a root device, eject it. + if(defined($rootDevice) && $rootDevice ne '') { + # Failing anyway, so don't care about failure + commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice); + } + + return undef; +} + +# isFormatCompressed($format) +# +# Returns true if $format corresponds to a compressed disk image format. +# Returns false otherwise. +sub isFormatCompressed($) { + my($format); + ($format) = @_; + return $format eq 'UDZO' || $format eq 'UDBZ'; +} + +# licenseMaker($text, $resource) +# +# Takes a plain text file at path $text and creates a license agreement +# resource containing the text at path $license. English-only, and +# no special formatting. This is the bare-bones stuff. For more +# intricate license agreements, create your own resource. +# +# ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg +sub licenseMaker($$) { + my($resource, $text); + ($text, $resource) = @_; + if(!sysopen(*TEXT, $text, O_RDONLY)) { + print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n"); + return 0; + } + if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) { + print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n"); + return 0; + } + print RESOURCE << '__EOT__'; +// See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs. +data 'LPic' (5000) { + // Default language ID, 0 = English + $"0000" + // Number of entries in list + $"0001" + + // Entry 1 + // Language ID, 0 = English + $"0000" + // Resource ID, 0 = STR#/TEXT/styl 5000 + $"0000" + // Multibyte language, 0 = no + $"0000" +}; + +resource 'STR#' (5000, "English") { + { + // Language (unused?) = English + "English", + // Agree + "Agree", + // Disagree + "Disagree", +__EOT__ + # This stuff needs double-quotes for interpolations to work. + print RESOURCE (" // Print, ellipsis is 0xC9\n"); + print RESOURCE (" \"Print\xc9\",\n"); + print RESOURCE (" // Save As, ellipsis is 0xC9\n"); + print RESOURCE (" \"Save As\xc9\",\n"); + print RESOURCE (' // Descriptive text, curly quotes are 0xD2 and 0xD3'. + "\n"); + print RESOURCE (' "If you agree to the terms of this license '. + "agreement, click \xd2Agree\xd3 to access the software. If you ". + "do not agree, press \xd2Disagree.\xd3\"\n"); +print RESOURCE << '__EOT__'; + }; +}; + +// Beware of 1024(?) byte (character?) line length limitation. Split up long +// lines. +// If straight quotes are used ("), remember to escape them (\"). +// Newline is \n, to leave a blank line, use two of them. +// 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly +// single quotes ('), 0xD5 is also the apostrophe. +data 'TEXT' (5000, "English") { +__EOT__ + + while(!eof(*TEXT)) { + my($line); + chop($line = ); + + while(defined($line)) { + my($chunk); + + # Rez doesn't care for lines longer than (1024?) characters. Split + # at less than half of that limit, in case everything needs to be + # backwhacked. + if(length($line)>500) { + $chunk = substr($line, 0, 500); + $line = substr($line, 500); + } + else { + $chunk = $line; + $line = undef; + } + + if(length($chunk) > 0) { + # Unsafe characters are the double-quote (") and backslash (\), escape + # them with backslashes. + $chunk =~ s/(["\\])/\\$1/g; + + print RESOURCE ' "'.$chunk.'"'."\n"; + } + } + print RESOURCE ' "\n"'."\n"; + } + close(*TEXT); + + print RESOURCE << '__EOT__'; +}; + +data 'styl' (5000, "English") { + // Number of styles following = 1 + $"0001" + + // Style 1. This is used to display the first two lines in bold text. + // Start character = 0 + $"0000 0000" + // Height = 16 + $"0010" + // Ascent = 12 + $"000C" + // Font family = 1024 (Lucida Grande) + $"0400" + // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline + // 0x10=shadow 0x20=condensed 0x40=extended + $"00" + // Style, unused? + $"02" + // Size = 12 point + $"000C" + // Color, RGB + $"0000 0000 0000" +}; +__EOT__ + close(*RESOURCE); + + return 1; +} + +# pathSplit($pathname) +# +# Splits $pathname into an array of path components. +sub pathSplit($) { + my($pathname); + ($pathname) = @_; + return split(/\//, $pathname); +} + +# setAttributes($root, @attributeList) +# +# @attributeList is an array, each element of which must be in the form +# :. is a list of attributes, per SetFile. is a file +# which is taken as relative to $root (even if it appears as an absolute +# path.) SetFile is called to set the attributes on each file in +# @attributeList. +sub setAttributes($@) { + my(@attributes, $root); + ($root, @attributes) = @_; + my($attribute); + foreach $attribute (@attributes) { + my($attrList, $file, @fileList, @fixedFileList); + ($attrList, @fileList) = split(/:/, $attribute); + if(!defined($attrList) || !@fileList) { + cleanupDie('--attribute requires :'); + } + @fixedFileList=(); + foreach $file (@fileList) { + if($file =~ /^\//) { + push(@fixedFileList, $root.$file); + } + else { + push(@fixedFileList, $root.'/'.$file); + } + } + if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) { + cleanupDie('SetFile failed to set attributes'); + } + } + return; +} + +sub trapSignal($) { + my($signalName); + ($signalName) = @_; + cleanupDie('exiting on SIG'.$signalName); +} + +sub usage() { + print STDERR ( +"usage: pkg-dmg --source \n". +" --target \n". +" [--format ] (default: UDZO)\n". +" [--volname ] (default: same name as source)\n". +" [--tempdir ] (default: same dir as target)\n". +" [--mkdir ] (make directory in image)\n". +" [--copy [:]] (extra files to add)\n". +" [--symlink [:]] (extra symlinks to add)\n". +" [--license ] (plain text license agreement)\n". +" [--resource ] (flat .r files to merge)\n". +" [--icon ] (volume icon)\n". +" [--attribute :] (set file attributes)\n". +" [--idme] (make Internet-enabled image)\n". +" [--sourcefile] (treat --source as a file)\n". +" [--verbosity ] (0, 1, 2; default=2)\n". +" [--dry-run] (print what would be done)\n"); + return; +} diff --git a/app/mac/updater.tar.xz b/app/mac/updater.tar.xz new file mode 100644 index 0000000000..1451d049cc --- /dev/null +++ b/app/mac/updater.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53642619a815ae1c848dfb6f33e1e528db74edc39b3a692b6f312cace640c437 +size 92884 diff --git a/app/mac/updater_renamer b/app/mac/updater_renamer new file mode 100755 index 0000000000..52d5b42eae --- /dev/null +++ b/app/mac/updater_renamer @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +# Convert localized app name from "Firefox Software Update" to "Zotero Software Update" +# +# plist is UTF-16 +f='updater.app/Contents/Resources/English.lproj/InfoPlist.strings' +with open(f, 'r', encoding='utf-16') as inputfile: + newText = inputfile.read().replace('Firefox', 'Zotero') +with open(f, 'w', encoding='utf-16') as outputfile: + outputfile.write(newText) diff --git a/app/mac/zotero.xz b/app/mac/zotero.xz new file mode 100755 index 0000000000..68b5691df2 Binary files /dev/null and b/app/mac/zotero.xz differ diff --git a/app/modules/zotero-libreoffice-integration b/app/modules/zotero-libreoffice-integration new file mode 160000 index 0000000000..e9be47895c --- /dev/null +++ b/app/modules/zotero-libreoffice-integration @@ -0,0 +1 @@ +Subproject commit e9be47895c97e6a61a39b830a7347d9a76c480e5 diff --git a/app/modules/zotero-word-for-mac-integration b/app/modules/zotero-word-for-mac-integration new file mode 160000 index 0000000000..43f8efffa4 --- /dev/null +++ b/app/modules/zotero-word-for-mac-integration @@ -0,0 +1 @@ +Subproject commit 43f8efffa447e542b6804b8698570b00045a7588 diff --git a/app/modules/zotero-word-for-windows-integration b/app/modules/zotero-word-for-windows-integration new file mode 160000 index 0000000000..9ce2a32f2c --- /dev/null +++ b/app/modules/zotero-word-for-windows-integration @@ -0,0 +1 @@ +Subproject commit 9ce2a32f2cc6b2f387524bb23dc6a25d584d7c28 diff --git a/app/scripts/6.0_release_build_and_deploy b/app/scripts/6.0_release_build_and_deploy new file mode 100755 index 0000000000..d3f647946f --- /dev/null +++ b/app/scripts/6.0_release_build_and_deploy @@ -0,0 +1,26 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +CHANNEL="release" +BRANCH="6.0" + +cd "$SCRIPT_DIR" +./check_requirements + +hash=`./get_repo_branch_hash $BRANCH` +source_dir=`./get_commit_files $hash` +build_dir=`mktemp -d` + +function cleanup { + rm -rf "$source_dir" + rm -rf "$build_dir" +} +trap cleanup EXIT + +./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash +./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL diff --git a/app/scripts/7.0_beta_build_and_deploy b/app/scripts/7.0_beta_build_and_deploy new file mode 100755 index 0000000000..079d67fe79 --- /dev/null +++ b/app/scripts/7.0_beta_build_and_deploy @@ -0,0 +1,30 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +CHANNEL="beta" +BRANCH="main" +export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/beta/ZoteroSafariExtension.appex" + +# Set Safari extension LSMinimumSystemVersion to Mojave for betas +perl -pi -e 's/11\.0<\/string>/10.14<\/string>/' "$SAFARI_APPEX"/Contents/Info.plist + +cd "$SCRIPT_DIR" +./check_requirements + +hash=`./get_repo_branch_hash $BRANCH` +source_dir=`./get_commit_files $hash` +build_dir=`mktemp -d` + +function cleanup { + rm -rf "$source_dir" + rm -rf "$build_dir" +} +trap cleanup EXIT + +./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash +./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL diff --git a/app/scripts/7.0_dev_build_and_deploy b/app/scripts/7.0_dev_build_and_deploy new file mode 100755 index 0000000000..7e9cca3006 --- /dev/null +++ b/app/scripts/7.0_dev_build_and_deploy @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +CHANNEL="dev" +BRANCH="main" +export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/dev/ZoteroSafariExtension.appex" + +cd "$SCRIPT_DIR" +./check_requirements + +hash=`./get_repo_branch_hash $BRANCH` +source_dir=`./get_commit_files $hash` +build_dir=`mktemp -d` + +function cleanup { + rm -rf "$source_dir" + rm -rf "$build_dir" +} +trap cleanup EXIT + +./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash +./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 diff --git a/app/scripts/7.0_test_build_and_deploy b/app/scripts/7.0_test_build_and_deploy new file mode 100755 index 0000000000..a2864e80bb --- /dev/null +++ b/app/scripts/7.0_test_build_and_deploy @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +CHANNEL="test" +BRANCH="main" +export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/test/ZoteroSafariExtension.appex" + +cd "$SCRIPT_DIR" +./check_requirements + +hash=`./get_repo_branch_hash $BRANCH` +source_dir=`./get_commit_files $hash` +build_dir=`mktemp -d` + +function cleanup { + rm -rf "$source_dir" + rm -rf "$build_dir" +} +trap cleanup EXIT + +./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash +./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 diff --git a/app/scripts/add_omni_file b/app/scripts/add_omni_file new file mode 100755 index 0000000000..cc309e14da --- /dev/null +++ b/app/scripts/add_omni_file @@ -0,0 +1,52 @@ +#!/bin/bash +set -euo pipefail + +# +# Zip a file directly into app/omni.ja in staging/ +# +# Zip paths are relative to the current directory, so this should be run from +# the client build/ directory +# + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +. "$ROOT_DIR/config.sh" + +function usage { + cat >&2 <&2 <&2 + exit 1 + ;; + esac +done + +if [ $REBUILD -eq 1 ]; then + PARAMS="" + if [ $DEBUGGER -eq 1 ]; then + PARAMS="-t" + fi + + # Check if build watch is running + # If not, run now + if ! ps u | grep js-build/build.js | grep -v grep > /dev/null; then + echo "Running JS build process" + echo + cd $ROOT_DIR + # TEMP: --openssl-legacy-provider avoids a build error in pdf.js + NODE_OPTIONS=--openssl-legacy-provider npm run build + echo + fi + + "$SCRIPT_DIR/dir_build" -q $PARAMS + + if [ "`uname`" = "Darwin" ]; then + # Sign the Word dylib so it works on Apple Silicon + "$SCRIPT_DIR/codesign_local" "$APP_ROOT_DIR/staging/Zotero.app" + fi +fi + +PARAMS="" +if [ $SKIP_BUNDLED_FILES -eq 1 ]; then + PARAMS="$PARAMS -ZoteroSkipBundledFiles" +fi +if [ $DEBUGGER -eq 1 ]; then + PARAMS="$PARAMS -debugger" +fi + +if [ "`uname`" = "Darwin" ]; then + command="Zotero.app/Contents/MacOS/zotero" +elif [ "`uname`" = "Linux" ]; then + command="Zotero_linux-x86_64/zotero" +elif [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then + command="Zotero_win-x64/zotero.exe" +else + echo "Unknown platform" >&2 + exit 1 +fi + +"$APP_ROOT_DIR/staging/$command" $profile -ZoteroDebugText -jsconsole -purgecaches $PARAMS "$@" diff --git a/app/scripts/check_requirements b/app/scripts/check_requirements new file mode 100755 index 0000000000..a42c67548e --- /dev/null +++ b/app/scripts/check_requirements @@ -0,0 +1,161 @@ +#!/bin/bash +set -uo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$APP_ROOT_DIR")" +. "$APP_ROOT_DIR/config.sh" +. "$SCRIPT_DIR/bootstrap.sh" + +cd "$ROOT_DIR" + +platform=`get_current_platform` + +FAIL_CMD='echo -e \033[31;1mFAIL\033[0m' +FAIL_CMD_INLINE='echo -n -e \033[31;1mFAIL\033[0m' +FAILED=0 + +hdr_start=`tput smul` +hdr_stop=`tput rmul` + +echo "${hdr_start}Checking build requirements:${hdr_stop}" +echo + +echo -n "Checking for Node.js: " +which node || { $FAIL_CMD_INLINE; FAILED=1; } +node_version=$(node -v | cut -c 2-) +echo -n "Checking Node version: $node_version" +if [ $(echo $node_version | cut -d '.' -f1 ) -ge "18" ]; then + echo +else + echo -n " -- must be 18 or later " + { $FAIL_CMD; FAILED=1; } +fi + +echo -n "Checking for Git LFS: " +which git-lfs || { $FAIL_CMD_INLINE; FAILED=1; } + +echo "Checking for Git LFS checkouts: " +lfs_files=$(git lfs ls-files -n 2> /dev/null) || { $FAIL_CMD; FAILED=1; } +for lfs_file in $lfs_files; do + echo -n " - $lfs_file: " + file_type=$(file -b $lfs_file) + echo -n $file_type" " + if [ -z "$(echo -n $file_type | grep XZ)" ]; then + { $FAIL_CMD_INLINE; FAILED=1; echo " -- run 'git lfs pull'"; } + fi + echo +done + +echo -n "Checking for perl: " +which perl || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for python3: " +which python3 || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for curl: " +which curl || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for wget: " +which wget || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for zip: " +which zip || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for unzip: " +which unzip || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for xz: " +which xz || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for awk: " +which awk || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for rsync: " +which rsync || { $FAIL_CMD; FAILED=1; } + +if [ $platform = "w" ]; then + echo -n "Checking for 7z: " + which 7z || { $FAIL_CMD; FAILED=1; } + + echo -n "Checking for rcedit: " + which rcedit || { $FAIL_CMD; FAILED=1; echo " -- Install with fetch_rcedit"; } +fi + +if [ $platform = "w" ]; then + echo + echo "${hdr_start}Checking Windows packaging requirements:${hdr_stop}" + echo + + echo -n "Checking for upx: " + which upx || { $FAIL_CMD; FAILED=1; } + + echo -n "Checking for uuidgen: " + which uuidgen || { $FAIL_CMD; FAILED=1; } + + echo -n "Checking for code-signing script: " + if [ -x "$APP_ROOT_DIR/win/codesign" ]; then + echo "$APP_ROOT_DIR/win/codesign" + else + $FAIL_CMD + FAILED=1 + fi + + echo -n "Checking for Unicode NSIS: " + if [ -x "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" ]; then + echo "`cygpath -u \"${NSIS_DIR}makensis.exe\"`" + else + $FAIL_CMD + FAILED=1 + fi + + plugin_path=$(cd "$NSIS_DIR\\Plugins\\x86-unicode" && pwd) + plugins="AppAssocReg ApplicationID InvokeShellVerb ShellLink UAC" + echo "Checking for NSIS plugins in $plugin_path" + for i in $plugins; do + echo -n " $i.dll: " + if [ -f "$plugin_path/$i.dll" ]; then + echo OK + else + $FAIL_CMD + FAILED=1 + fi + done +fi + +if [ $platform = "m" ]; then + echo + echo "${hdr_start}Checking Mac packaging requirements:${hdr_stop}" + echo + + echo -n "Checking for codesign: " + which /usr/bin/codesign || { $FAIL_CMD; FAILED=1; } +fi + +echo +echo "${hdr_start}Checking distribution requirements:${hdr_stop}" +echo + +echo -n "Checking for Mozilla ARchive (MAR) tool: " +which mar || { $FAIL_CMD; FAILED=1; echo " -- Install with fetch_mar_tools"; } + +echo -n "Checking for mbsdiff: " +which mbsdiff || { $FAIL_CMD; FAILED=1; echo " -- Install with fetch_mar_tools"; } + +echo -n "Checking for rsync: " +which rsync || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for sha512sum/shasum: " +which sha512sum 2>/dev/null || which shasum 2>/dev/null || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for AWS CLI: " +which aws || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for AWS S3 access: " +aws s3 ls $S3_BUCKET/$S3_DIST_PATH | sed 's/^[[:blank:]]*//' || { $FAIL_CMD; FAILED=1; } + +echo -n "Checking for deploy host directory access: " +ssh $DEPLOY_HOST ls -d $DEPLOY_PATH || { $FAIL_CMD; FAILED=1; } + + +exit $FAILED diff --git a/app/scripts/codesign_local b/app/scripts/codesign_local new file mode 100755 index 0000000000..cba7af2b17 --- /dev/null +++ b/app/scripts/codesign_local @@ -0,0 +1,40 @@ +#!/bin/bash +set -euo pipefail + +# Perform ad-hoc code signing of Zotero.app for local usage +# +# Currently we sign only the Word dylib, since that's necessary for Zotero developers to work on +# Word integration on Apple Silicon. If we discover other problems, we can uncomment some of the +# other lines. If you're making a custom build, you can modify this file to sign the entire build +# instead of just the bare minimum needed for development. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +. "$ROOT_DIR/config.sh" + +if [ -z "${1:-}" ]; then + echo "Usage: $0 path/to/staging/Zotero.app" + exit 1 +fi + +APPDIR=$1 +DEVELOPER_ID="-" + +entitlements_file="$ROOT_DIR/mac/entitlements.xml" +#/usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" \ +# "$APPDIR/Contents/MacOS/XUL" \ +# "$APPDIR/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater" +#find "$APPDIR/Contents" -name '*.dylib' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; +#find "$APPDIR/Contents" -name '*.app' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; +#/usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" "$APPDIR/Contents/MacOS/zotero" + +# Skip signing of Safari extension, since it's not present for local builds + +# Sign final app package +#echo +#/usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" "$APPDIR" + +# Verify app +#/usr/bin/codesign --verify -vvvv "$APPDIR" + +find "$APPDIR/Contents" -name 'libZoteroWordIntegration.dylib' -exec /usr/bin/codesign --force --options runtime --entitlements "$entitlements_file" --sign "$DEVELOPER_ID" {} \; diff --git a/app/scripts/dir_build b/app/scripts/dir_build new file mode 100755 index 0000000000..0dac8807c0 --- /dev/null +++ b/app/scripts/dir_build @@ -0,0 +1,88 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +function usage { + cat >&2 <&2 + exit 1 + ;; + esac +done + +if [[ -z $PLATFORM ]]; then + if [ "`uname`" = "Darwin" ]; then + PLATFORM="m" + elif [ "`uname`" = "Linux" ]; then + PLATFORM="l" + + # If platform not given explicitly, skip 32-bit build if 64-bit system + if [ "$(arch)" = "x86_64" ]; then + export SKIP_32=1 + fi + elif [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then + PLATFORM="w" + fi +fi + +CHANNEL="source" + +PARAMS="" +if [ $DEVTOOLS -eq 1 ]; then + PARAMS+=" -t" +fi +if [ $quick_build -eq 1 ]; then + PARAMS+=" -q" +fi + +hash=`git -C "$ROOT_DIR" rev-parse --short HEAD` + +build_dir=`mktemp -d` +function cleanup { + rm -rf $build_dir +} +trap cleanup EXIT + +"$SCRIPT_DIR/prepare_build" -s "$ROOT_DIR/build" -o "$build_dir" -c $CHANNEL -m $hash +"$APP_ROOT_DIR/build.sh" -d "$build_dir" -p $PLATFORM -c $CHANNEL -s $PARAMS + +echo Done diff --git a/app/scripts/fetch_mar_tools b/app/scripts/fetch_mar_tools new file mode 100755 index 0000000000..24f836bebc --- /dev/null +++ b/app/scripts/fetch_mar_tools @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$APP_ROOT_DIR" + +mkdir -p "xulrunner/bin" +if [ "`uname`" = "Darwin" ]; then + # Mozilla has Linux executables where the Mac files should be, so supply our own Mac builds + curl -o "xulrunner/bin/mar" https://zotero-download.s3.us-east-1.amazonaws.com/tools/mac/102.11.0esr/mar + curl -o "xulrunner/bin/mbsdiff" https://zotero-download.s3.us-east-1.amazonaws.com/tools/mac/102.11.0esr/mbsdiff +elif [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then + # Mozilla doesn't seem to provide Windows versions via its file server anymore, so supply our own + curl -o "xulrunner/bin/mar.exe" https://zotero-download.s3.us-east-1.amazonaws.com/tools/win/102.11.0esr/mar.exe + curl -o "xulrunner/bin/mbsdiff.exe" https://zotero-download.s3.us-east-1.amazonaws.com/tools/win/102.11.0esr/mbsdiff.exe +else + curl -o "xulrunner/bin/mar" https://ftp.mozilla.org/pub/firefox/nightly/2022/05/2022-05-30-09-39-43-mozilla-central/mar-tools/linux64/mar + curl -o "xulrunner/bin/mbsdiff" https://ftp.mozilla.org/pub/firefox/nightly/2022/05/2022-05-30-09-39-43-mozilla-central/mar-tools/linux64/mbsdiff +fi +chmod 755 xulrunner/bin/mar xulrunner/bin/mbsdiff diff --git a/app/scripts/fetch_rcedit b/app/scripts/fetch_rcedit new file mode 100755 index 0000000000..89ac62bd10 --- /dev/null +++ b/app/scripts/fetch_rcedit @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$ROOT_DIR" + +mkdir -p "xulrunner/bin" +curl -L -o "xulrunner/bin/rcedit.exe" https://github.com/electron/rcedit/releases/download/v1.1.1/rcedit-x86.exe +chmod 755 xulrunner/bin/rcedit diff --git a/app/scripts/fetch_xulrunner b/app/scripts/fetch_xulrunner new file mode 100755 index 0000000000..3cf5d44b97 --- /dev/null +++ b/app/scripts/fetch_xulrunner @@ -0,0 +1,571 @@ +#!/bin/bash +set -euo pipefail + +# Copyright (c) 2011 Zotero +# Center for History and New Media +# George Mason University, Fairfax, Virginia, USA +# http://zotero.org +# +# 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 . + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +. "$APP_ROOT_DIR/config.sh" +cd "$APP_ROOT_DIR" + +function usage { + cat >&2 <&1 + exit 1 + fi +} + +function remove_line { + pattern=$1 + file=$2 + + if egrep -q "$pattern" "$file"; then + egrep -v "$pattern" "$file" > "$file.tmp" + mv "$file.tmp" "$file" + else + echo "$pattern" not found in "$file" -- aborting 2>&1 + exit 1 + fi +} + +function get_utf16_chars { + str=$(echo -n "$1" | xxd -p | fold -w 2 | sed -r 's/(.+)/\\\\x{\1}\\\\x{00}/') + # Add NUL padding + if [ -n "${2:-}" ]; then + # Multiply characters x 2 for UTF-16 + for i in `seq 1 $(($2 * 2))`; do + str+=$(echo '\\x{00}') + done + fi + echo $str | xargs | sed 's/ //g' +} + +# +# Make various modifications to the stock Firefox app +# +function modify_omni { + platform=$1 + + mkdir omni + mv omni.ja omni + cd omni + # omni.ja is an "optimized" ZIP file, so use a script from Mozilla to avoid a warning from unzip + # here and to make it work after rezipping below + python3 "$APP_ROOT_DIR/scripts/optimizejars.py" --deoptimize ./ ./ ./ + rm -f omni.ja.log + unzip omni.ja + rm omni.ja + + replace_line 'BROWSER_CHROME_URL:.+' 'BROWSER_CHROME_URL: "chrome:\/\/zotero\/content\/zoteroPane.xhtml",' modules/AppConstants.sys.mjs + + # https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/internals/preferences.html + # + # It's not clear that most of these do anything anymore when not compiled in, but just in case + replace_line 'MOZ_REQUIRE_SIGNING:' 'MOZ_REQUIRE_SIGNING: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_DATA_REPORTING:' 'MOZ_DATA_REPORTING: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_SERVICES_HEALTHREPORT:' 'MOZ_SERVICES_HEALTHREPORT: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_TELEMETRY_REPORTING:' 'MOZ_TELEMETRY_REPORTING: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_TELEMETRY_ON_BY_DEFAULT:' 'MOZ_TELEMETRY_ON_BY_DEFAULT: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_CRASHREPORTER:' 'MOZ_CRASHREPORTER: false \&\&' modules/AppConstants.sys.mjs + replace_line 'MOZ_UPDATE_CHANNEL:.+' 'MOZ_UPDATE_CHANNEL: "none",' modules/AppConstants.sys.mjs + replace_line '"https:\/\/[^\/]+mozilla.com.+"' '""' modules/AppConstants.sys.mjs + + # Don't use Mozilla Maintenance Service on Windows + replace_line 'MOZ_MAINTENANCE_SERVICE:' 'MOZ_MAINTENANCE_SERVICE: false \&\&' modules/AppConstants.sys.mjs + # Continue using app.update.auto in prefs.js on Windows + replace_line 'PER_INSTALLATION_PREFS_PLATFORMS = \["win"\]' 'PER_INSTALLATION_PREFS_PLATFORMS = []' modules/UpdateUtils.sys.mjs + + # Prompt if major update is available instead of installing automatically on restart + replace_line 'if \(!updateAuto\) \{' 'if (update.type == "major") { + LOG("UpdateService:_selectAndInstallUpdate - prompting because it is a major update"); + AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_PREF); + Services.obs.notifyObservers(update, "update-available", "show-prompt"); + return; + } + if (!updateAuto) {' modules/UpdateService.sys.mjs + + # Avoid console warning about resource://gre/modules/FxAccountsCommon.js + replace_line 'const logins = this._data.logins;' 'const logins = this._data.logins; if (this._data.logins.length != -1) return;' modules/LoginStore.sys.mjs + + # Prevent error during network requests + replace_line 'async lazyInit\(\) \{' 'async lazyInit() { if (this.features) return false;' modules/UrlClassifierExceptionListService.sys.mjs + + replace_line 'pref\("network.captive-portal-service.enabled".+' 'pref("network.captive-portal-service.enabled", false);' greprefs.js + replace_line 'pref\("network.connectivity-service.enabled".+' 'pref("network.connectivity-service.enabled", false);' greprefs.js + replace_line 'pref\("toolkit.telemetry.server".+' 'pref("toolkit.telemetry.server", "");' greprefs.js + replace_line 'pref\("toolkit.telemetry.unified".+' 'pref("toolkit.telemetry.unified", false);' greprefs.js + replace_line 'pref\("media.gmp-manager.url".+' 'pref("media.gmp-manager.url", "");' greprefs.js + + # + # # Disable transaction timeout + # perl -pi -e 's/let timeoutPromise/\/*let timeoutPromise/' modules/Sqlite.jsm + # perl -pi -e 's/return Promise.race\(\[transactionPromise, timeoutPromise\]\);/*\/return transactionPromise;/' modules/Sqlite.jsm + # rm -f jsloader/resource/gre/modules/Sqlite.jsm + # + # Disable unwanted components + remove_line '(RemoteSettings|services-|telemetry|Telemetry|URLDecorationAnnotationsService)' components/components.manifest + + # Do not trigger LoginManager event that logs an error on autocomplete submission + remove_line 'DOMInputPasswordAdded: \{\},' modules/ActorManagerParent.sys.mjs + + # On Mac/Linux, ignore relative paths in PATH in Subprocess.pathSearch(), used when a bare command is passed to Utilities.Internal.subprocess() + if [[ $platform != win* ]]; then + replace_line 'let path = PathUtils.join\(dir, bin\);' 'if (!dir.startsWith("\/")) continue; let path = PathUtils.join(dir, bin);' modules/subprocess/subprocess_unix.sys.mjs + fi + + # Remove unwanted files + rm modules/FxAccounts* + # Causes a startup error -- try an empty file or a shim instead? + #rm modules/Telemetry* + rm modules/URLDecorationAnnotationsService.sys.mjs + rm -rf modules/services-* + + # Clear most WebExtension manifest properties + replace_line 'manifest = normalized.value;' 'manifest = normalized.value; + if (this.type == "extension") { + if (!manifest.applications?.zotero?.id) { + this.manifestError("applications.zotero.id not provided"); + } + if (!manifest.applications?.zotero?.update_url) { + this.manifestError("applications.zotero.update_url not provided"); + } + if (!manifest.applications?.zotero?.strict_max_version) { + this.manifestError("applications.zotero.strict_max_version not provided"); + } + manifest.browser_specific_settings = undefined; + manifest.content_scripts = []; + manifest.permissions = []; + manifest.host_permissions = []; + manifest.web_accessible_resources = undefined; + manifest.experiment_apis = {}; + }' modules/Extension.sys.mjs + + # Use applications.zotero instead of applications.gecko + replace_line 'let bss = manifest.applications\?.gecko' 'let bss = manifest.applications?.zotero' modules/addons/XPIInstall.jsm + replace_line 'manifest.applications\?.gecko' 'manifest.applications?.zotero' modules/Extension.sys.mjs + + # When installing addon, use app version instead of toolkit version for targetApplication + replace_line "id: TOOLKIT_ID," "id: '$APP_ID'," modules/addons/XPIInstall.jsm + + # Accept zotero@chnm.gmu.edu for target application to allow Zotero 6 plugins to remain + # installed in Zotero 7 + replace_line "if \(targetApp.id == Services.appinfo.ID\) \{" "if (targetApp.id == 'zotero\@chnm.gmu.edu') targetApp.id = '$APP_ID'; if (targetApp.id == Services.appinfo.ID) {" modules/addons/XPIDatabase.jsm + + # TEMP: Skip addons without ids until we figure out what's going on with the default theme. + # This fixes plugin updating and other things. + replace_line 'return addons;' 'return addons.filter(addon => addon.id);' modules/addons/XPIDatabase.jsm + + # For updates, look for applications.zotero instead of applications.gecko in manifest.json and + # use the app id and version for strict_min_version/strict_max_version comparisons + replace_line 'gecko: \{\},' 'zotero: {},' modules/addons/AddonUpdateChecker.sys.mjs + replace_line 'if \(!\("gecko" in applications\)\) \{' 'if (!("zotero" in applications)) {' modules/addons/AddonUpdateChecker.sys.mjs + replace_line '"gecko not in application entry' '"zotero not in application entry' modules/addons/AddonUpdateChecker.sys.mjs + replace_line 'let app = getProperty\(applications, "gecko", "object"\);' 'let app = getProperty(applications, "zotero", "object");' modules/addons/AddonUpdateChecker.sys.mjs + replace_line "id: TOOLKIT_ID," "id: '$APP_ID'," modules/addons/AddonUpdateChecker.sys.mjs + replace_line 'lazy.AddonManagerPrivate.webExtensionsMinPlatformVersion' '"7.0"' modules/addons/AddonUpdateChecker.sys.mjs + replace_line 'result.targetApplications.push' 'false && result.targetApplications.push' modules/addons/AddonUpdateChecker.sys.mjs + + # Allow addon installation by bypassing confirmation dialogs. If we want a confirmation dialog, + # we need to either add gXPInstallObserver from browser-addons.js [1][2] or provide our own with + # Ci.amIWebInstallPrompt [3]. + # + # [1] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/browser/base/content/browser.js#1902-1923 + # [2] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/browser/base/content/browser-addons.js#201 + # [3] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/toolkit/mozapps/extensions/AddonManager.sys.mjs#3114-3124 + replace_line 'if \(info.addon.userPermissions\) \{' 'if (false) {' modules/AddonManager.sys.mjs + replace_line '\} else if \(info.addon.sitePermissions\) \{' '} else if (false) {' modules/AddonManager.sys.mjs + replace_line '\} else if \(requireConfirm\) \{' '} else if (false) {' modules/AddonManager.sys.mjs + + # Make addon listener methods wait for promises, to allow calling asynchronous plugin `shutdown` + # and `uninstall` methods in our `onInstalling` handler + replace_line 'callAddonListeners\(aMethod' 'async callAddonListeners(aMethod' modules/AddonManager.sys.mjs + # Don't need this one to be async, but we can't easily avoid modifying its `listener[aMethod].apply()` call + replace_line 'callManagerListeners\(aMethod' 'async callManagerListeners(aMethod' modules/AddonManager.sys.mjs + replace_line 'AddonManagerInternal.callAddonListeners.apply\(AddonManagerInternal, aArgs\);' \ + 'return AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);' modules/AddonManager.sys.mjs + replace_line 'listener\[aMethod\].apply\(listener, aArgs\);' \ + 'let maybePromise = listener[aMethod].apply(listener, aArgs); + if (maybePromise && maybePromise.then) await maybePromise;' modules/AddonManager.sys.mjs + replace_line 'AddonManagerPrivate.callAddonListeners' 'await AddonManagerPrivate.callAddonListeners' modules/addons/XPIInstall.jsm + replace_line 'let uninstall = \(\) => \{' 'let uninstall = async () => {' modules/addons/XPIInstall.jsm + replace_line 'cancelUninstallAddon\(aAddon\)' 'async cancelUninstallAddon(aAddon)' modules/addons/XPIInstall.jsm + + # No idea why this is necessary, but without it initialization fails with "TypeError: "constructor" is read-only" + replace_line 'LoginStore.prototype.constructor = LoginStore;' '\/\/LoginStore.prototype.constructor = LoginStore;' modules/LoginStore.sys.mjs + # + # # Allow proxy password saving + # perl -pi -e 's/get _inPrivateBrowsing\(\) \{/get _inPrivateBrowsing() {if (true) { return false; }/' components/nsLoginManagerPrompter.js + # + # # Change text in update dialog + # perl -pi -e 's/A security and stability update for/A new version of/' chrome/en-US/locale/en-US/mozapps/update/updates.properties + # perl -pi -e 's/updateType_major=New Version/updateType_major=New Major Version/' chrome/en-US/locale/en-US/mozapps/update/updates.properties + # perl -pi -e 's/updateType_minor=Security Update/updateType_minor=New Version/' chrome/en-US/locale/en-US/mozapps/update/updates.properties + # perl -pi -e 's/update for &brandShortName; as soon as possible/update as soon as possible/' chrome/en-US/locale/en-US/mozapps/update/updates.dtd + # + # Set available locales + cp "$APP_ROOT_DIR/assets/multilocale.txt" res/multilocale.txt + + # Use Zotero URL opening in Mozilla dialogs (e.g., app update dialog) + replace_line 'function openURL\(aURL\) \{' 'function openURL(aURL) {let {Zotero} = ChromeUtils.importESModule("chrome:\/\/zotero\/content\/zotero.mjs"); Zotero.launchURL(aURL); if (true) { return; }' chrome/toolkit/content/global/contentAreaUtils.js + + # + # Modify Add-ons window + # + + # Prevent display: block from overriding display: none on
s + replace_line 'display: block !important;' 'display: block;' chrome/toolkit/content/global/elements/panel-list.css + + file="chrome/toolkit/content/mozapps/extensions/aboutaddons.css" + echo >> $file + # Hide search bar, Themes and Plugins tabs, and sidebar footer + echo '.main-search, button[name="theme"], button[name="plugin"], sidebar-footer { display: none; }' >> $file + echo '.main-heading { margin-top: 2em; }' >> $file + # Hide Details/Permissions tabs in addon details so we only show details + echo 'addon-details > button-group { display: none !important; }' >> $file + # Hide "Debug Addons" and "Manage Extension Shortcuts" + echo 'panel-item[action="debug-addons"], panel-item[action="reset-update-states"] + hr, panel-item[action="manage-shortcuts"] { display: none }' >> $file + # Show cursor feedback on plugin homepage links + echo '.addon-detail-row-homepage .text-link { cursor: pointer; color: -moz-nativehyperlinktext; }' >> $file + echo '.addon-detail-row-homepage .text-link:hover { text-decoration: underline; }' >> $file + + file="chrome/toolkit/content/mozapps/extensions/aboutaddons.js" + # Hide unsigned-addon warning + replace_line 'if \(!isCorrectlySigned\(addon\)\) \{' 'if (!isCorrectlySigned(addon)) {return {};' $file + # Hide Private Browsing setting in addon details + replace_line 'pbRow\.' '\/\/pbRow.' $file + replace_line 'let isAllowed = await isAllowedInPrivateBrowsing' '\/\/let isAllowed = await isAllowedInPrivateBrowsing' $file + # Use our own strings for the removal prompt + replace_line 'let \{ BrowserAddonUI \} = windowRoot.ownerGlobal;' '' $file + replace_line 'await BrowserAddonUI.promptRemoveExtension' 'promptRemoveExtension' $file + + # Customize empty-list message + replace_line 'createEmptyListMessage\(\) {' 'createEmptyListMessage() { + var p = document.createElement("p"); + p.id = "empty-list-message"; + return p;' $file + # Swap in include.js, which we need for Zotero.getString(), for abuse-reports.js, which we don't need + + # Open plugin links in external browser + replace_line 'let homepageURL = homepageRow.querySelector\(\"a\"\);' 'let homepageURL = homepageRow.querySelector(\"\.text-link\");' $file + replace_line 'homepageURL.href = addon.homepageURL;' 'homepageURL.setAttribute("href", addon.homepageURL);' $file + replace_line '
<\/a>' \ + '