diff --git a/.github/workflows/functional_tests.yaml b/.github/workflows/functional_tests.yaml index 772bf714a34..35059eef091 100644 --- a/.github/workflows/functional_tests.yaml +++ b/.github/workflows/functional_tests.yaml @@ -22,7 +22,7 @@ jobs: matrix: ${{ steps.testGen.outputs.tests }} steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash @@ -43,7 +43,7 @@ jobs: # Download the build and runtime package dependencies. sudo apt-get -o "Dir::Cache::archives=$(pwd)/build/archive" install -y \ - $(./scripts/linux/getdeps.py -a linux/debian/control.qt6) + $(./scripts/linux/getdeps.py -a linux/debian/control) sudo chown -R $USER:$USER build/archive - name: Compile test client @@ -88,7 +88,7 @@ jobs: test: ${{ fromJson(needs.build_test_app.outputs.matrix) }} steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: name: test-client-${{ github.sha }} @@ -127,12 +127,13 @@ jobs: continue-on-error: true # Ignore GRCOV parsing errors, see github.com/mozilla/grcov/issues/570 timeout-minutes: 1 # Give GRCOV a short timeout in case it hangs after a panic run: | + chmod +x ./build/bin/grcov ./build/bin/grcov build/cmake/src/CMakeFiles/mozillavpn.dir \ -s src -t lcov --branch --ignore-not-existing \ -o ${{runner.temp}}/artifacts/functional_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 if: steps.generateGrcov.outcome == 'success' with: directory: . @@ -142,7 +143,7 @@ jobs: verbose: true - name: Uploading artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 if: ${{ always() }} with: name: ${{matrix.test.name}} Logs diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml index e0fd4696cb6..d005d59ce7c 100644 --- a/.github/workflows/gh_pages.yaml +++ b/.github/workflows/gh_pages.yaml @@ -24,8 +24,8 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Clone repository - uses: actions/checkout@v2 - - uses: denoland/setup-deno@v1.0.0 + uses: actions/checkout@v3 + - uses: denoland/setup-deno@v1 with: deno-version: v1.x # Run with latest stable Deno. - name: Generate Dynamic Files @@ -33,7 +33,7 @@ jobs: cd tools/wasm_chrome deno run --allow-all generate_branch_file.ts -T ${{ secrets.GITHUB_TOKEN }} - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: Wasm-Chrome path: tools/wasm_chrome @@ -43,9 +43,9 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: Logviewer path: tools/logviewer @@ -55,11 +55,11 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' - name: Building shell: bash @@ -69,7 +69,7 @@ jobs: npm run build - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: Inspector Build path: tools/inspector/dist @@ -80,7 +80,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash @@ -112,11 +112,11 @@ jobs: ADDON_PRIVATE_KEY: ${{ secrets.ADDON_PRIVATE_KEY }} run: | echo -n "$ADDON_PRIVATE_KEY" > addon_private_key.pem - openssl dgst -sha256 -sign addon_private_key.pem -out addons/generated/addons/manifest.json.sign addons/generated/addons/manifest.json + openssl dgst -sha256 -sign addon_private_key.pem -out addons/generated/addons/manifest.json.sig addons/generated/addons/manifest.json rm addon_private_key.pem - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: Addons path: addons/generated/addons @@ -129,25 +129,25 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Download Wasm-chrome - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: Wasm-Chrome # Destination path path: _site - name: Download a Addons Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: Addons path: _site/addons - name: Download the Logviewer Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: Logviewer path: _site/logviewer - name: Download a Inspector Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: Inspector Build path: _site/inspector @@ -166,4 +166,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 \ No newline at end of file + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml index c4bd7fb3290..7a71a4a7ccf 100644 --- a/.github/workflows/linters.yaml +++ b/.github/workflows/linters.yaml @@ -21,7 +21,7 @@ jobs: aqt install-qt --outputdir /opt linux desktop $QTVERSION gcc_64 -m all - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash @@ -31,7 +31,7 @@ jobs: git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Set up Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.8' diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 2283aeac659..0e2a7729db8 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install source dependencies shell: bash @@ -34,7 +34,7 @@ jobs: run: ./scripts/linux/script.sh --source --gitref ${GITREF} - name: Upload source bundle - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Sources path: .tmp @@ -48,6 +48,7 @@ jobs: - { name: "Bionic", dist: bionic, ppa: "ppa:okirby/qt6-backports" } - { name: "Focal", dist: focal, ppa: "ppa:okirby/qt6-backports" } - { name: "Jammy", dist: jammy, ppa: "" } + - { name: "Kinetic", dist: kinetic, ppa: "" } runs-on: ubuntu-latest env: @@ -55,7 +56,7 @@ jobs: QTPPA: ${{ matrix.config.ppa }} steps: - name: Download Source Package - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: Sources @@ -80,7 +81,7 @@ jobs: sudo pbuilder build --basetgz $BASETGZ --buildresult $(pwd)/packages mozillavpn_*-${{matrix.config.dist}}[0-9].dsc - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: ${{matrix.config.name}} Build path: packages @@ -94,7 +95,7 @@ jobs: steps: - name: Download Source Package - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: Sources @@ -108,7 +109,7 @@ jobs: run: rpmbuild -D "_topdir $(pwd)" -D "_sourcedir $(pwd)" -ba mozillavpn.spec - name: Uploading - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: RPM Build path: | diff --git a/.github/workflows/ppa-automation.yaml b/.github/workflows/ppa-automation.yaml index 30244229b35..de9bd1c9216 100644 --- a/.github/workflows/ppa-automation.yaml +++ b/.github/workflows/ppa-automation.yaml @@ -16,7 +16,7 @@ jobs: environment: PPA Automation steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -54,7 +54,7 @@ jobs: fi - name: Uploading sources - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Sources path: .tmp diff --git a/.github/workflows/test_lottie.yaml b/.github/workflows/test_lottie.yaml index 1764fb5095b..66ccccf60e0 100644 --- a/.github/workflows/test_lottie.yaml +++ b/.github/workflows/test_lottie.yaml @@ -29,11 +29,11 @@ jobs: aqt install-qt --outputdir /opt linux desktop $QTVERSION gcc_64 -m all - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Cache grcov id: cache-grcov - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: grcov-build/ key: ${{runner.os}}-grcov-v0.8.9 @@ -64,7 +64,7 @@ jobs: -t lcov --branch --ignore-not-existing > ${{github.workspace}}/lottie_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: lottie_tests @@ -78,7 +78,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Qt6 shell: bash @@ -93,7 +93,7 @@ jobs: - name: Cache grcov id: cache-grcov - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: grcov-build/ key: ${{runner.os}}-grcov-v0.8.9 @@ -111,7 +111,7 @@ jobs: ./scripts/tests/lottie_tests.sh --grcov lottie_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: lottie_tests @@ -125,7 +125,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Qt shell: bash diff --git a/.github/workflows/test_qml.yaml b/.github/workflows/test_qml.yaml index 6419bd5be4a..f89caee571f 100644 --- a/.github/workflows/test_qml.yaml +++ b/.github/workflows/test_qml.yaml @@ -22,7 +22,7 @@ jobs: QTVERSION: 6.2.4 steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install dependencies run: | @@ -61,7 +61,7 @@ jobs: -t lcov --branch --ignore-not-existing > ${{github.workspace}}/qml_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: qml_tests @@ -75,7 +75,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Qt6 shell: bash @@ -119,7 +119,7 @@ jobs: ./scripts/tests/qml_tests.sh --grcov qml_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: qml_tests @@ -133,7 +133,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash diff --git a/.github/workflows/test_unit.yaml b/.github/workflows/test_unit.yaml index 84b288711c6..e75612f0532 100644 --- a/.github/workflows/test_unit.yaml +++ b/.github/workflows/test_unit.yaml @@ -22,7 +22,7 @@ jobs: name: Run Unit tests on Linux steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install dependences run: | @@ -41,7 +41,7 @@ jobs: - name: Cache grcov id: cache-grcov - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: grcov-build/ key: ${{runner.os}}-grcov-v0.8.9 @@ -81,7 +81,7 @@ jobs: -t lcov --branch --ignore-not-existing > ${{github.workspace}}/unit_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: unit_tests @@ -95,7 +95,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash @@ -129,7 +129,7 @@ jobs: - name: Cache grcov id: cache-grcov - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: grcov-build/ key: ${{runner.os}}-grcov-v0.8.9 @@ -147,7 +147,7 @@ jobs: ./scripts/tests/unit_tests.sh --grcov unit_lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: directory: . flags: unit_tests @@ -161,7 +161,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash diff --git a/.github/workflows/wasm.yaml b/.github/workflows/wasm.yaml index 57b46117728..dd228296eb0 100644 --- a/.github/workflows/wasm.yaml +++ b/.github/workflows/wasm.yaml @@ -25,7 +25,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout submodules shell: bash @@ -81,7 +81,7 @@ jobs: echo $TEST_LIST | jq - name: Uploading - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: WebAssembly Build Qt6 path: wasm @@ -98,11 +98,11 @@ jobs: test: ${{ fromJson(needs.wasmQt6.outputs.matrix) }} steps: - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Cache build id: cache-build - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: build/ key: ${{ github.sha }} @@ -114,7 +114,7 @@ jobs: npm install - name: Download a Build Artifact - uses: actions/download-artifact@v2.0.8 + uses: actions/download-artifact@v3 with: name: WebAssembly Build Qt6 # Destination path diff --git a/.github/workflows/wiki.yaml b/.github/workflows/wiki.yaml index e606de54200..3a382545a28 100644 --- a/.github/workflows/wiki.yaml +++ b/.github/workflows/wiki.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Clone Code Repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: 'vpn' repository: '' diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fef43be3fa..949e4a23304 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ if(NOT (DEFINED CMAKE_CONFIGURATION_TYPES OR DEFINED CMAKE_BUILD_TYPE)) message("Setting build type ${CMAKE_BUILD_TYPE}") endif() -project("Mozilla VPN" VERSION 2.11.0 LANGUAGES C CXX +project("Mozilla VPN" VERSION 2.12.0 LANGUAGES C CXX DESCRIPTION "A fast, secure and easy to use VPN. Built by the makers of Firefox." HOMEPAGE_URL "https://vpn.mozilla.org" ) diff --git a/README.md b/README.md index 594274a8846..333d55b43a1 100644 --- a/README.md +++ b/README.md @@ -54,19 +54,29 @@ In order to build this application, you need to install a few dependencies. Qt6 can be installed in a number of ways: -- download a binary package or the installer from the official QT website: - https://www.qt.io/download - this is the recommended way for Android and iOS - builds. -- use a package manager. For instance, we use +- Download a binary package or the installer from the official QT website: + https://www.qt.io/download-qt-installer - this is the recommended way for + Android and iOS builds. (Our usage is covered by the QT open source license. + While you will need to register with an email address, it will be free.) + During the install, there will be many components available. Install: + - QT version (recommend most recent LTS): + - WebAssembly (TP) + - macOS (if you'll be doing macOS work) + - Android (if you'll be doing Android work) + - iOS (if you'll be doing iOS work) + - QT 5 Compatability Module + - Additional Libraries + - Qt Networking Authorization + - Qt WebSockets + - Developer and Designer Tools + - CMake + - Ninja +- Use a package manager. For instance, we use [aqt](https://github.com/miurahr/aqtinstall) for WASM builds. -- compile Qt6 (dynamically or statically). If you want to choose this path, you +- Compile Qt6 (dynamically or statically). If you want to choose this path, you can use our bash script for macOS and Linux: ```bash ./scripts/utils/qt6_compile.sh -``` - ... or our batch script for windows: -```bash -./scripts/Qt5_static_compile.bat ``` #### Install Python 3 @@ -78,19 +88,31 @@ a few python modules using [pip](https://pypi.org/): pip install -r requirements.txt --user ``` +(`pip3` may need to be substituted for `pip` in the above line.) + +#### Install CMake + +There are many ways to install [CMake](https://cmake.org). + +On macOS, it is easy to do with [Homebrew](https://brew.sh/). After +[installing Homebrew](https://brew.sh/#install), run: +```bash +brew install cmake +``` + #### Install rust -[Rust](https://www.rust-lang.org/) is required for desktop builds (MacOS, Linux +[Rust](https://www.rust-lang.org/) is required for desktop builds (macOS, Linux and Windows). See the official rust documentation to know how to install it. #### What's next? -We support the following platforms: Linux, Windows, MacOS, iOS, Android and +We support the following platforms: Linux, Windows, macOS, iOS, Android and WASM. Each one is unique and it has a different section in this document. ### How to build from source code for Desktop -On deskop platforms, such as Windows, Linux and MacOS, we build the Mozilla VPN +On deskop platforms, such as Windows, Linux and macOS, we build the Mozilla VPN using CMake, and as long as the required dependencies can be located in your PATH the build process is effectively the same on each of the supported platform. @@ -101,11 +123,13 @@ mkdir build && cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr ``` This generation step can be augmented by providing variable definitions on the -command line to adjust features, or help CMake locate its dependencies. The -following variables may be of use: +command line to adjust features, or help CMake locate its dependencies. (Using these +may look like `cmake -S . -B build -GXcode -DCMAKE_PREFIX_PATH=/Users/[your account]/Qt/6.2.4/macos/lib/cmake`) +The following variables may be of use: - `CMAKE_PREFIX_PATH=/lib/cmake`: can be set if CMake is unable - to locate a viable Qt installation in your `PATH`. + to locate a viable Qt installation in your `PATH`. (This may be an error message + like `Unknown CMake command "qt_add_executable”.`) - `CMAKE_BUILD_TYPE=Release`: can be set to generate a release build, otherwise a Debug build is generated by default. - `BUILD_TESTING=ON`: can be set to build, and execute the unit tests using `CTest` @@ -179,9 +203,18 @@ mozillavpn linuxdaemon needs privileged access and so if you do not run as root, you will get an authentication prompt every time you try to reconnect the vpn. -### How to build from source code on MacOS +### How to build from source code on macOS + +There are two ways to build the project on macOS - Through the terminal or with Xcode. +For both, you must install a copy of the VPN application in the main macOS Applications +folder so that the daemon is available for your debug build. +[Any recent build](https://archive.mozilla.org/pub/vpn/releases/) should suffice. +(If major changes were made to the daemon since the last release, you may need to use +a copy of the debug build for the macOS Applications folder.) + +#### Building from terminal -1. On MacOS, we compile the app using +1. On macOS, we compile the app using [XCode](https://developer.apple.com/xcode/) version 12 or higher. 2. You also need to install go >= v1.16. If you don't have it done already, @@ -189,6 +222,7 @@ download go from the [official website](https://golang.org/dl/). 3. Create a build directory, and configure the project for building using `cmake`. ```bash +# Run this next line from the main project folder mkdir build && cmake -S . -B build ``` @@ -245,14 +279,20 @@ Once XCode has opened the project, building is as simple as selecting the `mozil and starting the build from the `Product->Build For->Testing` menu. *Note*: some developers have experienced that XCode reports that `go` isn't -available and so you can't build the app and dependencies in XCode. In this +available and so you can't build the app and dependencies in XCode. (This may show up as build +errors like `Run custom shell script ‘Generate extension/CMakeFiles/cargo_mozillavpnnp’ \ No +such file or directory \ Command PhaseScriptExecution failed with a nonzero exit code`) In this case, a workaround is to symlink `go` into XCode directory as follows: * Make sure go is 1.16+: `go version` * Find the location of go binary `which go` example output `/usr/local/go/bin/go` * Symlink e.g. ```bash -sudo ln -s /usr/local/go/bin/go /Applications/Xcode.app/Contents/Developer/usr/bin/go +cd /Applications/Xcode.app/Contents/Developer/usr/bin/ +sudo ln -s /usr/local/go/bin/go +# You may also need to symlink Cargo and Rust +sudo ln -s $(which cargo) +sudo ln -s $(which rustc) ``` This step needs to be executed each time XCode updates. @@ -267,24 +307,24 @@ extra components such as the wireguard, the browser bridge and so on. We patch the XCode project using [xcodeproj](https://github.com/CocoaPods/Xcodeproj). To install it: ``` -gem install xcodeproj # probably you want to run this command wityh `sudo` +gem install xcodeproj # probably you want to run this command with `sudo` ``` 3. You also need to install go >= v1.16. If you don't have it done already, download go from the [official website](https://golang.org/dl/). -4. Copy `xcode.config.template` to `xcode.config` +4. Copy `xcode.xconfig.template` to `xcode.xconfig` ```bash -cp xcode.config.template xcode.config +cp xcode.xconfig.template xcode.xconfig ``` -5. Modify the xcode.config to something like: +5. Modify the xcode.xconfig to something like: ``` -# MacOS configuration +# macOS configuration APP_ID_MACOS = org.mozilla.macos.FirefoxVPN LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login-item -# IOS configuration +# iOS configuration GROUP_ID_IOS = group.org.mozilla.ios.Guardian APP_ID_IOS = org.mozilla.ios.FirefoxVPN NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension @@ -294,12 +334,16 @@ NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension ```bash ./scripts/macos/apple_compile.sh ios [--adjust ] ``` +(If you get an error like `Step 7: Generate translation resources... +sh: /Users/[username]/Qt/6.2.4/ios/bin/lconvert: No such file or directory` +you may need to provide the macOS Qt bin folder through the -q flag: +`./scripts/macos/apple_compile.sh ios -q ~/Qt/6.2.4/macos/bin`) 7. Xcode should automatically open. You can then run/test/archive/ship the app. -If you prefer to compile the appa in command-line mode, use the following +If you prefer to compile the app in command-line mode, use the following command: ```bash -xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -project "Mozilla VPN.xcodeproj" +xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -project "Mozilla VPN.xcodeproj" ``` ### How to build from source code for Android @@ -331,12 +375,16 @@ adb install .tmp/src/android-build/build/outputs/apk/debug/android-build-debug.a - perl: http://strawberryperl.com/ - nasm: https://www.nasm.us/ - Visual Studio 2019: https://visualstudio.microsoft.com/vs/ + - Select the `Desktop development with C++` and `Python development` workloads. - OpenSSL: https://www.openssl.org/source/ + - On windows you can choose to bundle OpenSSL when installing python. Skip this step if you have done so. - Go: https://golang.org/dl/ We strongly recommend using CMake version 3.21 or later when building with Visual Studio. Earlier versions of CMake have bugs that can cause the build to hang. +It is also recommended to use the `x64 Native Tools Command Prompt for VS 2019` for CLI builds on Windows. + 2. Create a build directory, and configure the project for building using `cmake`. ```bash mkdir build && cmake -S . -B build @@ -352,6 +400,13 @@ when building: ```bash cmake --build build --config Release --target msi ``` +5. **Optional**: To build and debug through the VS 2019 UI, follow these steps: + - Open VS2019 + - From the top menu, select File -> Open -> CMake + - Choose the top-level `CMakeLists.txt` of the VPN project. + - Choose `x64-Debug` as the build config + - Choose `src/Mozilla VPN.exe` as the startup item. + - Click on the green play button to launch the client attached to a debugger. ### How to build from source code for WASM @@ -392,12 +447,23 @@ cmake --build build -j$(nproc) [Docs](https://www.selenium.dev/documentation/getting_started/installing_browser_drivers/) * Make a .env file with: * `MVPN_API_BASE_URL` (where proxy runs, most likely http://localhost:5000) - * `MVPN_BIN` (location of compiled mvpn binary) + * `MVPN_BIN` (location of compiled mvpn binary. This must be a dummy binary, see note below.) * `ARTIFACT_DIR` (directory to put screenshots from test failures) * (Optional) In one window run `./tests/proxy/wsgi.py --mock-devices` * Run a test from the root of the project: `./scripts/tests/functional_tests.sh {test_file}.js`. To run, say, the authentication tests: `./scripts/tests/functional_tests.sh tests/functional/testAuthentication.js`. +> **Note**: Functional tests require a dummy build of the application. +> In order to create such a build, on the root folder of this repository run: +> +> ``` +> cmake -S . -B ./dummybuild -DBUILD_DUMMY=ON +> cmake --build dummybuild -j$(nproc) +> ``` +> +> This will create a dummy build under the `dummybuild/` folder. To run the functional tests against this build, +> make sure the `MVPN_BIN` environment variable is pointing to the application under the `dummybuild/` folder. + ## Developer Options and staging environment To enable the staging environment, open the `Get Help` window, and click on the @@ -419,73 +485,58 @@ From the inspector, type `help` to see the list of available commands. ## Glean [Glean](https://docs.telemetry.mozilla.org/concepts/glean/glean.html) is a -Mozilla new product analytics & telemetry solution that provides a consistent +Mozilla analytics & telemetry solution that provides a consistent experience and behavior across all of Mozilla products. -When the client is built in debug mode, pings will have the applicationId -`MozillaVPN-debug`. Additionally, ping contents will be logged to the client -logs and will also be sent to the -[glean debug -viewer](https://debug-ping-preview.firebaseapp.com/pings/MozillaVPN) (login -required) where they are retained for 3 weeks. - -More info on debug view in [glean -docs](https://mozilla.github.io/glean/book/user/debugging/index.html). +When the client is built in debug mode, pings will have the [app channel](app-channel) set to +`debug`. Additionally, ping contents will be logged to the client +logs. When the client is in staging mode, but not debug mode, pings will have the -applicationId `MozillaVPN-staging` which allows for filtering between staging -and production pings. +[app channel](app-channel) set to `staging` which allows for filtering between staging +and production pings through the `client_info.app_channel` metric present in all pings. -### A note on glean embedding +[app-channel]: https://mozilla.github.io/glean/book/reference/general/initializing.html?highlight=app%20channel#gleaninitializeconfiguration + +### A note on Glean embedding Qt only accepts `major.minor` versions for importing. So if, for example, you're embedding glean v0.21.2 then it will still, for Qt's purpose, be v0.21. -### Working on tickets with new glean events - -If you are responsible for a piece of work that adds new glean events you will need to do a data review for the new events. This is the recommended process along with some pointers on doing that. - -The basic process is this: -* work on your PR that adds glean events including updating glean/metrics.yaml (necessary for your code to compile) -* in your metrics.yaml: - * include a link to the *github* bug that describes the work - * put TBD in the `data_reviews` entry - * think about whether the data you are collecting is technical or interaction, sometimes it's both. in that case pick interaction which is a higher category of data. (more details https://wiki.mozilla.org/Data_Collection) -* open a **draft** PR on github -* file a bugzilla ticket for the data review (more info below) -* update your PR with the id of the bugzilla bug in data `data_reviews` entry -* once you have an r+ from data review, move your PR out of draft state. - -It is **ok** for a reviewer to review and approve your code while you're waiting for data review. - -It is **not** ok to merge a PR that contains a change to metrics.yaml without a datareview r+ +### Working on tickets with new Glean instrumentation -#### Filing a bugzilla ticket for data review +If you are responsible for a piece of work that adds new Glean instrumentation you will need to do a data review. +Follwoing is the recommended process along with some pointers. -The data review process is described here: https://wiki.mozilla.org/Data_Collection - -In brief, specifically for VPN: - -* You need a bugzilla account. This is not an ldap service, but do use your ldap email address to sign-up for an account. -* Make a new bug in Product: Mozilla, Component: General. Or clone an old data review bug e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=1770530 -* See the above bug, the ticket can be simple just a link to a bug and a PR and the attachment with the data review details (see below). -* The trick to flagging this for data review is adding the attachment and setting the flag (data review ?), as described under "Step 1: Submit Request" on https://wiki.mozilla.org/Data_Collection -* We usually use `chutten` for VPN data reviews -* If you cannot see the flags in the attachment area (screenshot below) make sure "Show Advanced Fields" is checked - -![](attachment_flag_for_datareview.png) - -Filling out the data review details: - -* The data review questionnaire is here: https://github.com/mozilla/data-review/blob/main/request.md -* It can seem quite intimidating, but don't panic. First, look at an old bug such as the one linked above. Many questions will always be the same for VPN data review bugs. There are four questions that require your attention and thought: - - 1. What questions will you answer with this data? - - 2. Why does Mozilla need to answer these questions? Are there benefits for users? Do we need this information to address product or business requirements? - - 3. What alternative methods did you consider to answer these questions? Why were they not sufficient? - - 4. Please provide a general description of how you will analyze this data. -* If you don't know the answers to these questions, reach out to Sarah Bird or the product manager so you can answer these with full confidence. +> The data review process is also described here: https://wiki.mozilla.org/Data_Collection +The basic process is this: +* Implement the new instrumentation. Refer to [the Glean book](https://mozilla.github.io/glean/book/user/metrics/adding-new-metrics.html) on how to do that. +* When adding or updating new metrics or pings, the [Glean YAML files](https://github.com/mozilla-mobile/mozilla-vpn-client/tree/main/glean) might need to be updated. + When that is the case a new data-review must be requested and added to the list of data-reviews for the updated/added instrumentation. + When updating data-review links on the YAML files, these are the things to keep in mind: + * Include a link to the *github* bug that describes the work, this must be a public link; + * Put "TBD" in the `data_reviews` entry, that needs to be updated *before* releasing the new instrumentation and ideally before merging it; + * Think about whether the data you are collecting is technical or interaction, sometimes it's both. In that case pick interaction which is a higher category of data. (See more details on https://wiki.mozilla.org/Data_Collection); +* Open a **draft** PR on GitHub; +* Fill out the data-review[^1] form and request a data-review from one of the [Mozilla Data Stewards](https://wiki.mozilla.org/Data_Collection)[^2]. + That can be done by opening a Bugzilla ticket or more easily by attaching the questionaire as a comment on the PR that implements the instrumentation changes. + For Bugzilla, there is a special Bugzilla datareview request option and for GitHub it's enough to add the chosen data steward as a reviwer for the PR. +* The data-review questionaire will result in a data review response. The link to that response is what should be added to the `data_review` entry on the Glean YAML files. + It must be a public link. + +> Note: +> - It is **ok** for a reviewer to review and approve your code while you're waiting for data review. +> - It is **not** ok to release code that contains instrumentation changes without a datareview r+. It is good practice not to merge code that does not have a datareview r+. + +[^1]: The data-review questionaire can be found at https://github.com/mozilla/data-review/blob/main/request.md. That can be copy pasted and filled out manually. However, +since the VPN application uses Glean for data collection developers can also use the [`glean_parser data-review`](https://mozilla.github.io/glean_parser/) command, +which generates a mostly filled out data-review questionaire for Glean users. The questionaire can seem quite intimidating, but don't panic. +First, look at an old data-review such as https://github.com/mozilla-mobile/mozilla-vpn-client/pull/4594. +Questions 1, 2, 3 an 10 are the ones that require most of your attention and thought. +If you don't know the answers to these questions, reach out to Sarah Bird or the product manager so you can answer these with full confidence. +[^2]: Feel free to ping any of the data-stewards. If the collection is time sensitive consider pinging all data-stewards directly on the [data-stewards](https://matrix.to/#/#data-stewards:mozilla.org) matrix channel. ## Status @@ -494,7 +545,7 @@ Filling out the data review details: [![Lottie Tests](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/test_lottie.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/test_lottie.yaml) [![Linters (clang, l10n)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linters.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linters.yaml) [![Linux Packages](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linux.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linux.yaml) -[![MacOS](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml) +[![macOS](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml) [![WebAssembly](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/wasm.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/wasm.yaml) [![Windows](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/windows-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/windows-build.yaml) [![iOS](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/ios-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/ios-build.yaml) diff --git a/addons/message_privacy_bundle_staging/EnableBundleUpgrade.js b/addons/deprecated/message_privacy_bundle_staging/EnableBundleUpgrade.js similarity index 100% rename from addons/message_privacy_bundle_staging/EnableBundleUpgrade.js rename to addons/deprecated/message_privacy_bundle_staging/EnableBundleUpgrade.js diff --git a/addons/message_privacy_bundle_staging/OpenBundleUpgradeFlow.js b/addons/deprecated/message_privacy_bundle_staging/OpenBundleUpgradeFlow.js similarity index 100% rename from addons/message_privacy_bundle_staging/OpenBundleUpgradeFlow.js rename to addons/deprecated/message_privacy_bundle_staging/OpenBundleUpgradeFlow.js diff --git a/addons/message_privacy_bundle_staging/OpenGetHelpScreen.js b/addons/deprecated/message_privacy_bundle_staging/OpenGetHelpScreen.js similarity index 100% rename from addons/message_privacy_bundle_staging/OpenGetHelpScreen.js rename to addons/deprecated/message_privacy_bundle_staging/OpenGetHelpScreen.js diff --git a/addons/message_privacy_bundle_staging/manifest.json b/addons/deprecated/message_privacy_bundle_staging/manifest.json similarity index 100% rename from addons/message_privacy_bundle_staging/manifest.json rename to addons/deprecated/message_privacy_bundle_staging/manifest.json diff --git a/addons/message_whats_new_v2.10/manifest.json b/addons/message_whats_new_v2.10/manifest.json index ca8914e3bf1..0877dbe246e 100644 --- a/addons/message_whats_new_v2.10/manifest.json +++ b/addons/message_whats_new_v2.10/manifest.json @@ -11,7 +11,7 @@ "id": "message_whats_new_v2.10", "title": "Welcome to Mozilla VPN 2.10", "subtitle": "You’re using the latest version of Mozilla VPN. Take a look at the newest features and updates available in Mozilla VPN 2.10:", - "date": 1661021990, + "date": 1666659600, "badge": "whats_new", "blocks": [ { "id": "c_1", @@ -23,8 +23,6 @@ "content": "Easier-to-use navigation bar" }, { "id": "l_3", "content": "New tips and tricks to help you use the app’s features and settings" }, - { "id": "l_4", - "content": "The speed test feature now also tests upload speeds in addition to download speeds" }, { "id": "l_5", "content": "Other bug fixes and UI adjustments" }, { "id": "l_6", diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 8dfb17f3400..359d53f232a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -36,6 +36,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. android:screenOrientation="portrait" android:theme="@style/AppTheme.Splash" android:windowSoftInputMode="adjustPan" + android:exported="true" android:launchMode="singleTop"> diff --git a/android/build.gradle b/android/build.gradle index 6927ca26247..56cc1df5901 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,9 +13,9 @@ buildscript { } } dependencies { - classpath Dependencies.com_android_tools_build_gradle - classpath Dependencies.org_jetbrains_kotlin_kotlin_gradle_plugin - classpath Dependencies.org_jetbrains_kotlin_kotlin_serialization + classpath SharedDependencies.com_android_tools_build_gradle + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" + classpath "org.jetbrains.kotlin:kotlin-serialization:1.4.30-M1" } } repositories { @@ -47,17 +47,14 @@ dependencies { implementation project(path: ':client') implementation project(path: ':daemon') - implementation Dependencies.androidx_core - implementation Dependencies.android_installreferrer - implementation Dependencies.android_billingclient - implementation Dependencies.androidx_lifecycle - implementation Dependencies.androidx_security_security_crypto - implementation Dependencies.androidx_security_security_identity_credential - implementation Dependencies.com_google_android_gms_play_services_ads_identifier - implementation Dependencies.org_jetbrains_kotlinx_kotlinx_serialization_json + implementation SharedDependencies.androidx_core + implementation "com.android.installreferrer:installreferrer:2.2" + implementation "com.android.billingclient:billing-ktx:4.0.0" + implementation "com.google.android.gms:play-services-ads-identifier:17.0.1" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" implementation 'org.bouncycastle:bcprov-jdk15on:1.70' - coreLibraryDesugaring Dependencies.com_android_tools_desugar_jdk_libs + coreLibraryDesugaring SharedDependencies.com_android_tools_desugar_jdk_libs } android { diff --git a/android/buildSrc/src/main/kotlin/Config.kt b/android/buildSrc/src/main/kotlin/Config.kt index 5e082a82c43..1234ee17c7f 100644 --- a/android/buildSrc/src/main/kotlin/Config.kt +++ b/android/buildSrc/src/main/kotlin/Config.kt @@ -3,10 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object Config { - const val compileSdkVersion = 30 + const val compileSdkVersion = 31 const val buildToolsVersion = "30.0.3" const val minSdkVersion = 24 - const val targetSdkVersion = 30 + const val targetSdkVersion = 31 const val ndkVersion = "23.1.7779620" const val cmakeVersion = "3.18.1" } diff --git a/android/buildSrc/src/main/kotlin/Dependencies.kt b/android/buildSrc/src/main/kotlin/Dependencies.kt index 67c0a29532d..62b9a13c3e7 100644 --- a/android/buildSrc/src/main/kotlin/Dependencies.kt +++ b/android/buildSrc/src/main/kotlin/Dependencies.kt @@ -2,21 +2,9 @@ * 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/. */ -const val Mozilla_ANDROID_COMPONENT_VERSION = "101.0.7" - -object Dependencies { - const val org_jetbrains_kotlin_kotlin_serialization = "org.jetbrains.kotlin:kotlin-serialization:1.4.30-M1" - const val org_jetbrains_kotlin_kotlin_gradle_plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" - const val com_android_tools_build_gradle = "com.android.tools.build:gradle:4.1.1" - const val androidx_core = "androidx.core:core-ktx:1.6.0" - const val android_installreferrer = "com.android.installreferrer:installreferrer:2.2" - const val android_billingclient = "com.android.billingclient:billing-ktx:4.0.0" - const val androidx_lifecycle = "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha02" - const val androidx_security_security_crypto = "androidx.security:security-crypto:1.1.0-alpha03" - const val androidx_security_security_identity_credential = "androidx.security:security-identity-credential:1.0.0-alpha02" - const val com_google_android_gms_play_services_ads_identifier = "com.google.android.gms:play-services-ads-identifier:17.0.1" - const val org_jetbrains_kotlinx_kotlinx_serialization_json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" +// Dependencies used by at least more then 1 Subproject. +object SharedDependencies { + const val com_android_tools_build_gradle = "com.android.tools.build:gradle:4.0.0" + const val androidx_core = "androidx.core:core-ktx:1.9.0" const val com_android_tools_desugar_jdk_libs = "com.android.tools:desugar_jdk_libs:1.0.10" - const val org_mozilla_components_tooling_glean_gradle = "org.mozilla.components:tooling-glean-gradle:$Mozilla_ANDROID_COMPONENT_VERSION" - const val org_mozilla_telemetry_glean = "org.mozilla.telemetry:glean:44.2.0" } diff --git a/android/daemon/build.gradle b/android/daemon/build.gradle index 8532296998e..727751f9e40 100644 --- a/android/daemon/build.gradle +++ b/android/daemon/build.gradle @@ -23,8 +23,8 @@ buildscript { } dependencies { - classpath Dependencies.com_android_tools_build_gradle - classpath Dependencies.org_mozilla_components_tooling_glean_gradle + classpath SharedDependencies.com_android_tools_build_gradle + classpath "org.mozilla.components:tooling-glean-gradle:107.0.2" } } @@ -103,17 +103,11 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation Dependencies.androidx_core - implementation Dependencies.android_installreferrer - implementation Dependencies.android_billingclient - implementation Dependencies.androidx_lifecycle - implementation Dependencies.androidx_security_security_crypto - implementation Dependencies.androidx_security_security_identity_credential - implementation Dependencies.com_google_android_gms_play_services_ads_identifier - implementation Dependencies.org_jetbrains_kotlinx_kotlinx_serialization_json - implementation Dependencies.org_mozilla_telemetry_glean - coreLibraryDesugaring Dependencies.com_android_tools_desugar_jdk_libs - implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.10" + implementation SharedDependencies.androidx_core + implementation "androidx.security:security-crypto:1.1.0-alpha03" + implementation "androidx.security:security-identity-credential:1.0.0-alpha03" + implementation "org.mozilla.telemetry:glean:51.6.0" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.20" } ext.gleanNamespace = "mozilla.telemetry.glean" diff --git a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/NotificationUtil.kt b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/NotificationUtil.kt index 165c3de9a1e..e2663a9bc0e 100644 --- a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/NotificationUtil.kt +++ b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/NotificationUtil.kt @@ -114,7 +114,7 @@ class NotificationUtil { val mainActivityName = "org.mozilla.firefox.vpn.qt.VPNActivity" val activity = Class.forName(mainActivityName) val intent = Intent(service, activity) - val pendingIntent = PendingIntent.getActivity(service, 0, intent, 0) + val pendingIntent = PendingIntent.getActivity(service, 0, intent, PendingIntent.FLAG_IMMUTABLE) // Build our notification mNotificationBuilder .setSmallIcon(R.drawable.icon_mozillavpn_notifiaction) diff --git a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt index 9392f056de2..e6cf828ee30 100644 --- a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt +++ b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt @@ -235,17 +235,21 @@ class VPNService : android.net.VpnService() { if (useFallbackServer) { mConnectionHealth.start( - json.getJSONObject("server").getString("ipv4AddrIn"), + json.getJSONObject("serverFallback").getString("ipv4AddrIn"), json.getJSONObject("serverFallback").getString("ipv4Gateway"), json.getJSONObject("serverFallback").getString("ipv4Gateway"), - json.getJSONObject("serverFallback").getString("ipv4AddrIn") + json.getJSONObject("server").getString("ipv4AddrIn") ) } else { + var fallbackIpv4 = "" + if (json.has("serverFallback")) { + fallbackIpv4 = json.getJSONObject("serverFallback").getString("ipv4AddrIn") + } mConnectionHealth.start( json.getJSONObject("server").getString("ipv4AddrIn"), json.getJSONObject("server").getString("ipv4Gateway"), json.getString("dns"), - json.getJSONObject("serverFallback").getString("ipv4Gateway") + fallbackIpv4 ) } } diff --git a/docs/add-on.md b/docs/add-on.md index cb630d40ef7..4c2cfa02217 100644 --- a/docs/add-on.md +++ b/docs/add-on.md @@ -114,4 +114,4 @@ If you want to implement new add-ons, you need to follow these steps: 7. be sure you are doing all of this using a staging environment If all has done correctly, you can see the app fetching the manifest.json (and -not! the manifest.json.sign) resource from the webservice. +not! the manifest.json.sig) resource from the webservice. diff --git a/docs/releasenotes/v2.10.0.md b/docs/releasenotes/v2.10.0.md new file mode 100644 index 00000000000..d5ee426ac1c --- /dev/null +++ b/docs/releasenotes/v2.10.0.md @@ -0,0 +1,17 @@ +# v2.10.0 Release Notes + +## New Features + - New message center, where you can learn about important updates. + - Easier-to-use navigation. + - Tips and tricks to help you use the app’s features and settings. + - Improved resiliency against network blocking by gracefully falling back to Port 53 upon server connectivity failures. + - Improved icons in the Windows, macOS and Linux OS tray to help you quickly assess your connection + +## Other improvements + - Fixed network connectivity issues with iOS 16 + - More bug fixes and UI adjustments. + + +**Full Changelog:** [2.9.0...2.10.0](https://github.com/mozilla-mobile/mozilla-vpn-client/compare/v2.9.0...v2.10.0) + +**Downloads at:** [archive.mozilla.org/](https://archive.mozilla.org/pub/vpn/releases/2.10.0/) diff --git a/docs/releasenotes/v2.9.0.md b/docs/releasenotes/v2.9.0.md new file mode 100644 index 00000000000..9fcaee01da6 --- /dev/null +++ b/docs/releasenotes/v2.9.0.md @@ -0,0 +1,13 @@ +# v2.9.0 Release Notes + +## New Features + - + - Tips and tricks to help you use the app’s features and settings. + +## Other improvements + - More bug fixes and UI adjustments. + + +**Full Changelog:** [2.8.0...2.9.0](https://github.com/mozilla-mobile/mozilla-vpn-client/compare/v2.8.0...v2.9.0) + +**Downloads at:** [archive.mozilla.org/](https://archive.mozilla.org/pub/vpn/releases/2.9.0/) diff --git a/glean/metrics.yaml b/glean/metrics.yaml index eed7f43c62f..4fa239c320c 100644 --- a/glean/metrics.yaml +++ b/glean/metrics.yaml @@ -18,7 +18,6 @@ $schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 sample: - addon_state_changed: type: event lifetime: ping @@ -27,9 +26,9 @@ sample: description: | An addon's state changed bugs: - - https://github.com/mozilla-mobile/mozilla-vpn-client/issues/1620 + - https://github.com/mozilla-mobile/mozilla-vpn-client/pull/4487 data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1746457 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1794825 data_sensitivity: - technical notification_emails: @@ -53,9 +52,9 @@ sample: description: | The addon message' state changed bugs: - - https://github.com/mozilla-mobile/mozilla-vpn-client/issues/1620 + - https://github.com/mozilla-mobile/mozilla-vpn-client/pull/4487 data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1746457 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1794825 data_sensitivity: - technical notification_emails: @@ -71,7 +70,6 @@ sample: Received, Notified, Read, or Dismissed type: string - addon_cta_clicked: type: event lifetime: ping @@ -80,9 +78,9 @@ sample: description: | User clicked primary CTA in an addon bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1711123 + - https://github.com/mozilla-mobile/mozilla-vpn-client/pull/4487 data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1711123#c1 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1794825 data_sensitivity: - interaction extra_keys: @@ -566,7 +564,6 @@ sample: - amarchesini@mozilla.com expires: never - connection_health_no_signal: type: event lifetime: ping @@ -1047,6 +1044,23 @@ sample: description: The Host network type (i.e wifi or 4g) type: string + server_unavailable_error: + type: event + lifetime: ping + send_in_pings: + - main + description: | + A "Server unavailable" error has occured. + bugs: + - https://github.com/mozilla-mobile/mozilla-vpn-client/issues/4589 + data_reviews: + - https://github.com/mozilla-mobile/mozilla-vpn-client/pull/4594#issuecomment-1271601927 + data_sensitivity: + - technical + notification_emails: + - brizental@mozilla.com + expires: never + app_step: type: event lifetime: ping diff --git a/i18n b/i18n index ec729f908b7..e09cf6424c4 160000 --- a/i18n +++ b/i18n @@ -1 +1 @@ -Subproject commit ec729f908b716ed456a6374797dbe54acbc8f82c +Subproject commit e09cf6424c4c9dcb025866cb5c5df6da6dc67013 diff --git a/ios/app/WireGuard-Bridging-Header.h b/ios/app/WireGuard-Bridging-Header.h index 405a8879244..e3b27030e2f 100644 --- a/ios/app/WireGuard-Bridging-Header.h +++ b/ios/app/WireGuard-Bridging-Header.h @@ -2,7 +2,7 @@ * 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 "wireguard-go-version.h" +#include "3rdparty/wireguard-apple/Sources/WireGuardKitGo/wireguard-go-version.h" #include "3rdparty/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include diff --git a/ios/gobridge/.gitignore b/ios/gobridge/.gitignore deleted file mode 100644 index 5d25f8f5f9e..00000000000 --- a/ios/gobridge/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.cache/ -.tmp/ -out/ diff --git a/ios/gobridge/Makefile b/ios/gobridge/Makefile deleted file mode 100644 index 1d89b5a11db..00000000000 --- a/ios/gobridge/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. - -# These are generally passed to us by xcode, but we set working defaults for standalone compilation too. -ARCHS ?= x86_64 arm64 -PLATFORM_NAME ?= macosx -SDKROOT ?= $(shell xcrun --sdk $(PLATFORM_NAME) --show-sdk-path) -CONFIGURATION_BUILD_DIR ?= $(CURDIR)/out -CONFIGURATION_TEMP_DIR ?= $(CURDIR)/.tmp - -export CC ?= clang -LIPO ?= lipo -DESTDIR ?= $(CONFIGURATION_BUILD_DIR) -BUILDDIR ?= $(CONFIGURATION_TEMP_DIR)/wireguard-go-bridge - -CFLAGS_PREFIX := $(if $(DEPLOYMENT_TARGET_CLANG_FLAG_NAME),-$(DEPLOYMENT_TARGET_CLANG_FLAG_NAME)=$($(DEPLOYMENT_TARGET_CLANG_ENV_NAME)),) -isysroot $(SDKROOT) -arch -GOARCH_arm64 := arm64 -GOARCH_x86_64 := amd64 -GOOS_macosx := darwin -GOOS_iphoneos := ios - -build: $(DESTDIR)/libwg-go.a -version-header: $(DESTDIR)/wireguard-go-version.h - -REAL_GOROOT := $(shell go env GOROOT 2>/dev/null) -export GOROOT := $(BUILDDIR)/goroot -$(GOROOT)/.prepared: - [ -n "$(REAL_GOROOT)" ] - mkdir -p "$(GOROOT)" - rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/" - cat goruntime-*.diff | patch -p1 -f -N -r- -d "$(GOROOT)" - touch "$@" - -define libwg-go-a -$(BUILDDIR)/libwg-go-$(1).a: export CGO_ENABLED := 1 -$(BUILDDIR)/libwg-go-$(1).a: export CGO_CFLAGS := $(CFLAGS_PREFIX) $(ARCH) -$(BUILDDIR)/libwg-go-$(1).a: export CGO_LDFLAGS := $(CFLAGS_PREFIX) $(ARCH) -$(BUILDDIR)/libwg-go-$(1).a: export GOOS := $(GOOS_$(PLATFORM_NAME)) -$(BUILDDIR)/libwg-go-$(1).a: export GOARCH := $(GOARCH_$(1)) -$(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared go.mod - go build -ldflags=-w -trimpath -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive - rm -f "$(BUILDDIR)/libwg-go-$(1).h" -endef -$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH)))) - -$(DESTDIR)/wireguard-go-version.h: go.mod $(GOROOT)/.prepared - sed -E -n 's/.*golang\.zx2c4\.com\/wireguard +v[0-9.]+-[0-9]+-([0-9a-f]{8})[0-9a-f]{4}.*/#define WIREGUARD_GO_VERSION "\1"/p' "$<" > "$@" - -$(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a) - @mkdir -vp "$(DESTDIR)" - $(LIPO) -create -output "$@" $^ - -clean: - rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h" - -install: build - -.PHONY: clean build version-header install diff --git a/ios/gobridge/api.go b/ios/gobridge/api.go deleted file mode 100644 index b6b4d311e38..00000000000 --- a/ios/gobridge/api.go +++ /dev/null @@ -1,225 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. - */ - -package main - -// #include -// #include -// static void callLogger(void *func, void *ctx, int level, const char *msg) -// { -// ((void(*)(void *, int, const char *))func)(ctx, level, msg); -// } -import "C" - -import ( - "fmt" - "math" - "os" - "os/signal" - "runtime" - "runtime/debug" - "strings" - "time" - "unsafe" - - "golang.org/x/sys/unix" - "golang.zx2c4.com/wireguard/conn" - "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/tun" -) - -var loggerFunc unsafe.Pointer -var loggerCtx unsafe.Pointer - -type CLogger int - -func cstring(s string) *C.char { - b, err := unix.BytePtrFromString(s) - if err != nil { - b := [1]C.char{} - return &b[0] - } - return (*C.char)(unsafe.Pointer(b)) -} - -func (l CLogger) Printf(format string, args ...interface{}) { - if uintptr(loggerFunc) == 0 { - return - } - C.callLogger(loggerFunc, loggerCtx, C.int(l), cstring(fmt.Sprintf(format, args...))) -} - -type tunnelHandle struct { - *device.Device - *device.Logger -} - -var tunnelHandles = make(map[int32]tunnelHandle) - -func init() { - signals := make(chan os.Signal) - signal.Notify(signals, unix.SIGUSR2) - go func() { - buf := make([]byte, os.Getpagesize()) - for { - select { - case <-signals: - n := runtime.Stack(buf, true) - buf[n] = 0 - if uintptr(loggerFunc) != 0 { - C.callLogger(loggerFunc, loggerCtx, 0, (*C.char)(unsafe.Pointer(&buf[0]))) - } - } - } - }() -} - -//export wgSetLogger -func wgSetLogger(context, loggerFn uintptr) { - loggerCtx = unsafe.Pointer(context) - loggerFunc = unsafe.Pointer(loggerFn) -} - -//export wgTurnOn -func wgTurnOn(settings *C.char, tunFd int32) int32 { - logger := &device.Logger{ - Verbosef: CLogger(0).Printf, - Errorf: CLogger(1).Printf, - } - dupTunFd, err := unix.Dup(int(tunFd)) - if err != nil { - logger.Errorf("Unable to dup tun fd: %v", err) - return -1 - } - - err = unix.SetNonblock(dupTunFd, true) - if err != nil { - logger.Errorf("Unable to set tun fd as non blocking: %v", err) - unix.Close(dupTunFd) - return -1 - } - tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0) - if err != nil { - logger.Errorf("Unable to create new tun device from fd: %v", err) - unix.Close(dupTunFd) - return -1 - } - logger.Verbosef("Attaching to interface") - dev := device.NewDevice(tun, conn.NewStdNetBind(), logger) - - err = dev.IpcSet(C.GoString(settings)) - if err != nil { - logger.Errorf("Unable to set IPC settings: %v", err) - unix.Close(dupTunFd) - dev.Close() - return -1 - } - - dev.Up() - logger.Verbosef("Device started") - - var i int32 - for i = 0; i < math.MaxInt32; i++ { - if _, exists := tunnelHandles[i]; !exists { - break - } - } - if i == math.MaxInt32 { - unix.Close(dupTunFd) - dev.Close() - return -1 - } - tunnelHandles[i] = tunnelHandle{dev, logger} - return i -} - -//export wgTurnOff -func wgTurnOff(tunnelHandle int32) { - dev, ok := tunnelHandles[tunnelHandle] - if !ok { - return - } - delete(tunnelHandles, tunnelHandle) - dev.Close() -} - -//export wgSetConfig -func wgSetConfig(tunnelHandle int32, settings *C.char) int64 { - dev, ok := tunnelHandles[tunnelHandle] - if !ok { - return 0 - } - err := dev.IpcSet(C.GoString(settings)) - if err != nil { - dev.Errorf("Unable to set IPC settings: %v", err) - if ipcErr, ok := err.(*device.IPCError); ok { - return ipcErr.ErrorCode() - } - return -1 - } - return 0 -} - -//export wgGetConfig -func wgGetConfig(tunnelHandle int32) *C.char { - device, ok := tunnelHandles[tunnelHandle] - if !ok { - return nil - } - settings, err := device.IpcGet() - if err != nil { - return nil - } - return C.CString(settings) -} - -//export wgBumpSockets -func wgBumpSockets(tunnelHandle int32) { - dev, ok := tunnelHandles[tunnelHandle] - if !ok { - return - } - go func() { - for i := 0; i < 10; i++ { - err := dev.BindUpdate() - if err == nil { - dev.SendKeepalivesToPeersWithCurrentKeypair() - return - } - dev.Errorf("Unable to update bind, try %d: %v", i+1, err) - time.Sleep(time.Second / 2) - } - dev.Errorf("Gave up trying to update bind; tunnel is likely dysfunctional") - }() -} - -//export wgDisableSomeRoamingForBrokenMobileSemantics -func wgDisableSomeRoamingForBrokenMobileSemantics(tunnelHandle int32) { - dev, ok := tunnelHandles[tunnelHandle] - if !ok { - return - } - dev.DisableSomeRoamingForBrokenMobileSemantics() -} - -//export wgVersion -func wgVersion() *C.char { - info, ok := debug.ReadBuildInfo() - if !ok { - return C.CString("unknown") - } - for _, dep := range info.Deps { - if dep.Path == "golang.zx2c4.com/wireguard" { - parts := strings.Split(dep.Version, "-") - if len(parts) == 3 && len(parts[2]) == 12 { - return C.CString(parts[2][:7]) - } - return C.CString(dep.Version) - } - } - return C.CString("unknown") -} - -func main() {} diff --git a/ios/gobridge/dummy.c b/ios/gobridge/dummy.c deleted file mode 100644 index d15abba5976..00000000000 --- a/ios/gobridge/dummy.c +++ /dev/null @@ -1 +0,0 @@ -// Empty diff --git a/ios/gobridge/go.mod b/ios/gobridge/go.mod deleted file mode 100644 index 8ec48163b6f..00000000000 --- a/ios/gobridge/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module golang.zx2c4.com/wireguard/apple - -go 1.16 - -require ( - golang.org/x/sys v0.0.0-20210308170721-88b6017d0656 - golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39 -) diff --git a/ios/gobridge/go.sum b/ios/gobridge/go.sum deleted file mode 100644 index 89d522bb399..00000000000 --- a/ios/gobridge/go.sum +++ /dev/null @@ -1,19 +0,0 @@ -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210308170721-88b6017d0656 h1:FuBaiPCiXkq4v+JY5JEGPU/HwEZwpVyDbu/KBz9fU+4= -golang.org/x/sys v0.0.0-20210308170721-88b6017d0656/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39 h1:yv331J9aB1fuvxzneUKsRnWyhwK+aj495rADUXSP7Uk= -golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39/go.mod h1:ojGPy+9W6ZSM8anL+xC67fvh8zPQJwA6KpFOHyDWLX4= diff --git a/ios/gobridge/goruntime-boottime-over-monotonic.diff b/ios/gobridge/goruntime-boottime-over-monotonic.diff deleted file mode 100644 index 2f7f54edd01..00000000000 --- a/ios/gobridge/goruntime-boottime-over-monotonic.diff +++ /dev/null @@ -1,61 +0,0 @@ -From 516dc0c15ff1ab781e0677606b5be72919251b3e Mon Sep 17 00:00:00 2001 -From: "Jason A. Donenfeld" -Date: Wed, 9 Dec 2020 14:07:06 +0100 -Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin - -This makes timers account for having expired while a computer was -asleep, which is quite common on mobile devices. Note that -continuous_time absolute_time, except that it takes into account -time spent in suspend. - -Fixes #24595 - -Change-Id: Ia3282e8bd86f95ad2b76427063e60a005563f4eb ---- - src/runtime/sys_darwin.go | 2 +- - src/runtime/sys_darwin_amd64.s | 2 +- - src/runtime/sys_darwin_arm64.s | 2 +- - 3 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go -index 4a3f2fc453..4a69403b32 100644 ---- a/src/runtime/sys_darwin.go -+++ b/src/runtime/sys_darwin.go -@@ -440,7 +440,7 @@ func setNonblock(fd int32) { - //go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib" - - //go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib" --//go:cgo_import_dynamic libc_mach_absolute_time mach_absolute_time "/usr/lib/libSystem.B.dylib" -+//go:cgo_import_dynamic libc_mach_continuous_time mach_continuous_time "/usr/lib/libSystem.B.dylib" - //go:cgo_import_dynamic libc_clock_gettime clock_gettime "/usr/lib/libSystem.B.dylib" - //go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib" - //go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" -diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s -index 630fb5df64..4499c88802 100644 ---- a/src/runtime/sys_darwin_amd64.s -+++ b/src/runtime/sys_darwin_amd64.s -@@ -114,7 +114,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0 - PUSHQ BP - MOVQ SP, BP - MOVQ DI, BX -- CALL libc_mach_absolute_time(SB) -+ CALL libc_mach_continuous_time(SB) - MOVQ AX, 0(BX) - MOVL timebase<>+machTimebaseInfo_numer(SB), SI - MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read -diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s -index 96d2ed1076..f046545395 100644 ---- a/src/runtime/sys_darwin_arm64.s -+++ b/src/runtime/sys_darwin_arm64.s -@@ -143,7 +143,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size) - - TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40 - MOVD R0, R19 -- BL libc_mach_absolute_time(SB) -+ BL libc_mach_continuous_time(SB) - MOVD R0, 0(R19) - MOVW timebase<>+machTimebaseInfo_numer(SB), R20 - MOVD $timebase<>+machTimebaseInfo_denom(SB), R21 --- -2.30.1 - diff --git a/ios/gobridge/module.modulemap b/ios/gobridge/module.modulemap deleted file mode 100644 index 2ca39160f10..00000000000 --- a/ios/gobridge/module.modulemap +++ /dev/null @@ -1,5 +0,0 @@ -module WireGuardKitGo { - umbrella header "wireguard.h" - link "wg-go" - export * -} diff --git a/ios/gobridge/wireguard.h b/ios/gobridge/wireguard.h deleted file mode 100644 index b442173f4ef..00000000000 --- a/ios/gobridge/wireguard.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 - * - * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. - */ - -#ifndef WIREGUARD_H -#define WIREGUARD_H - -#include -#include -#include - -typedef void (*logger_fn_t)(void* context, int level, const char* msg); -extern void wgSetLogger(void* context, logger_fn_t logger_fn); -extern int wgTurnOn(const char* settings, int32_t tun_fd); -extern void wgTurnOff(int handle); -extern int64_t wgSetConfig(int handle, const char* settings); -extern char* wgGetConfig(int handle); -extern void wgBumpSockets(int handle); -extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle); -extern const char* wgVersion(); - -#endif diff --git a/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h index e39386929bd..25c7aac5cc5 100644 --- a/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -2,8 +2,8 @@ * 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 "ios/gobridge/wireguard.h" -#include "wireguard-go-version.h" +#include "3rdparty/wireguard-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rdparty/wireguard-apple/Sources/WireGuardKitGo/wireguard-go-version.h" #include "3rdparty/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include diff --git a/linux/bionic/qt_ppa.sh b/linux/bionic/qt_ppa.sh deleted file mode 100755 index aaf795e5920..00000000000 --- a/linux/bionic/qt_ppa.sh +++ /dev/null @@ -1,222 +0,0 @@ -#!/bin/bash - -# 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/. - -printv() { - if [ -t 1 ]; then - NCOLORS=$(tput colors) - - if test -n "$NCOLORS" && test "$NCOLORS" -ge 8; then - NORMAL="$(tput sgr0)" - RED="$(tput setaf 1)" - GREEN="$(tput setaf 2)" - YELLOW="$(tput setaf 3)" - fi - fi - - if [[ $2 = 'G' ]]; then - # shellcheck disable=SC2086 - echo $1 -e "${GREEN}$3${NORMAL}" - elif [[ $2 = 'Y' ]]; then - # shellcheck disable=SC2086 - echo $1 -e "${YELLOW}$3${NORMAL}" - elif [[ $2 = 'N' ]]; then - # shellcheck disable=SC2086 - echo $1 -e "$3" - else - # shellcheck disable=SC2086 - echo $1 -e "${RED}$3${NORMAL}" - fi -} - -print() { - printv '' "$1" "$2" -} - -printn() { - printv "-n" "$1" "$2" -} - -error() { - printv '' R "$1" -} - -die() { - if [[ "$1" ]]; then - error "$1" - else - error Failed - fi - - exit 1 -} - -print Y "Installing dependencies..." -apt-get update || die -apt-get install -y \ - wget \ - xz-utils \ - devscripts || die - -printn Y "Creating /tmp/qt_ppa_final... " -rm -rf /tmp/qt_ppa_final || die -mkdir /tmp/qt_ppa_final || die -print G "done." - -printn Y "Creating /tmp/qt_ppa... " -rm -rf /tmp/qt_ppa || die -mkdir /tmp/qt_ppa || die -cd /tmp/qt_ppa || die -print G "done." - -magic() { - NAME=$1 - FOLDER=$2 - ORIGURL=$3 - ORIG=$4 - DEBURL=$5 - DEB=$6 - - if ! [[ -f $ORIG ]]; then - print Y "Downloading the orig... " - wget $ORIGURL || die - tar xf $ORIG || die - fi - - [[ -d $FOLDER ]] || die "$FOLDER doesn't exist." - - print Y "Downloading the deb for qt base... " - wget $DEBURL || die - tar xf $DEB || die - rm -f $DEB || die - print G "done." - - print Y "Patching debian files... " - cat > tmp << EOF -$NAME (5.15.2-bionic1) bionic; urgency=low - - * Bionic build - - -- Andrea Marchesini $(date -R) - -EOF - - cat debian/changelog >> tmp || die - mv tmp debian/changelog || die - print G "done." - - print Y "Installing dependencies... " - - BD= - LIST=$( - cat debian/control | while read LINE; do - if [[ $(echo $LINE | grep "^Build-Depends:") ]]; then - BD=1 - LINE=$(echo $LINE | cut -d: -f2) - elif [[ $(echo $LINE | grep ":") ]]; then - BD= - fi - - if [[ $BD ]]; then - echo $LINE | cut -d, -f1 | cut -d\[ -f1 | cut -d\( -f1 | grep -v g++-4.6 - fi - done - ) - - echo $LIST - apt-get install -y $LIST - - print Y "Configuring the source folder... " - mv debian $FOLDER || die - cd $FOLDER || die - - print Y "Creating the debian package... " - debuild -S --no-sign || die - - dpkg-buildpackage -b -rfakeroot -us -uc || die - - dpkg -i ../"$NAME"_*.deb - - print Y "Clean up... " - cd .. || die - rm -rf $FOLDER || die - mv * /tmp/qt_ppa_final || die - cp /tmp/qt_ppa_final/*orig.tar.xz . || die - print G "done." -} - -magic \ - qt515base \ - qtbase-everywhere-src-5.15.2 \ - https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515base/5.15.2-1basyskom4/qt515base_5.15.2.orig.tar.xz \ - qt515base_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515base/5.15.2-1basyskom4/qt515base_5.15.2-1basyskom4.debian.tar.xz \ - qt515base_5.15.2-1basyskom4.debian.tar.xz || die - -magic \ - qt515xmlpatterns \ - qtxmlpatterns-everywhere-src-5.15.2 \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515xmlpatterns/5.15.2-1basyskom1/qt515xmlpatterns_5.15.2.orig.tar.xz \ - qt515xmlpatterns_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515xmlpatterns/5.15.2-1basyskom1/qt515xmlpatterns_5.15.2-1basyskom1.debian.tar.xz \ - qt515xmlpatterns_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515declarative \ - qtdeclarative-everywhere-src-5.15.2 \ - https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515declarative/5.15.2-1basyskom1/qt515declarative_5.15.2.orig.tar.xz \ - qt515declarative_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515declarative/5.15.2-1basyskom1/qt515declarative_5.15.2-1basyskom1.debian.tar.xz \ - qt515declarative_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515graphicaleffects \ - qtgraphicaleffects-everywhere-src-5.15.2 \ - https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515graphicaleffects/5.15.2-1basyskom1/qt515graphicaleffects_5.15.2.orig.tar.xz \ - qt515graphicaleffects_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515graphicaleffects/5.15.2-1basyskom1/qt515graphicaleffects_5.15.2-1basyskom1.debian.tar.xz \ - qt515graphicaleffects_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515imageformats \ - qtimageformats-everywhere-src-5.15.2 \ - https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515imageformats/5.15.2-1basyskom1/qt515imageformats_5.15.2.orig.tar.xz \ - qt515imageformats_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515imageformats/5.15.2-1basyskom2/qt515imageformats_5.15.2-1basyskom2.debian.tar.xz \ - qt515imageformats_5.15.2-1basyskom2.debian.tar.xz || die - -magic \ - qt515networkauth-no-lgpl \ - qtnetworkauth-everywhere-src-5.15.2 \ - https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515networkauth-no-lgpl/5.15.2-1basyskom1/qt515networkauth-no-lgpl_5.15.2.orig.tar.xz \ - qt515networkauth-no-lgpl_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515networkauth-no-lgpl/5.15.2-1basyskom1/qt515networkauth-no-lgpl_5.15.2-1basyskom1.debian.tar.xz \ - qt515networkauth-no-lgpl_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515quickcontrols \ - qtquickcontrols-everywhere-src-5.15.2 \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols/5.15.2-1basyskom1/qt515quickcontrols_5.15.2.orig.tar.xz \ - qt515quickcontrols_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols/5.15.2-1basyskom1/qt515quickcontrols_5.15.2-1basyskom1.debian.tar.xz \ - qt515quickcontrols_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515quickcontrols2 \ - qtquickcontrols2-everywhere-src-5.15.2 \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols2/5.15.2-1basyskom1/qt515quickcontrols2_5.15.2.orig.tar.xz \ - qt515quickcontrols2_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols2/5.15.2-1basyskom1/qt515quickcontrols2_5.15.2-1basyskom1.debian.tar.xz \ - qt515quickcontrols2_5.15.2-1basyskom1.debian.tar.xz || die - -magic \ - qt515svg \ - qtsvg-everywhere-src-5.15.2 \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515svg/5.15.2-1basyskom1/qt515svg_5.15.2.orig.tar.xz \ - qt515svg_5.15.2.orig.tar.xz \ - https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515svg/5.15.2-1basyskom1/qt515svg_5.15.2-1basyskom1.debian.tar.xz \ - qt515svg_5.15.2-1basyskom1.debian.tar.xz || die - -print G "Now, upload all the files from /tmp/qt_ppa_final" diff --git a/linux/debian/control.qt6 b/linux/debian/control similarity index 100% rename from linux/debian/control.qt6 rename to linux/debian/control diff --git a/linux/debian/control.beineri b/linux/debian/control.beineri deleted file mode 100644 index 16d8d38336e..00000000000 --- a/linux/debian/control.beineri +++ /dev/null @@ -1,93 +0,0 @@ -Source: mozillavpn -Section: net -Priority: optional -Maintainer: mozilla -Build-Depends: debhelper (>= 9.20160709), - cdbs, - quilt, - flex, - golang (>=2:1.13~) | golang-1.13, - cargo, - python3-yaml, - python3-lxml, - qt515base (>=5.15.2-1basyskom4), - qt515declarative (>= 5.15.2-1basyskom1), - qt515graphicaleffects (>= 5.15.2-1basyskom1), - qt515imageformats (>= 5.15.2-1basyskom), - qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), - qt515quickcontrols2 (>=5.15.2-1basyskom1), - qt515svg (>=5.15.2-1basyskom1), - qt515tools (>=5.15.2-1basyskom1), - qt515websockets (>=5.15.2-1basyskom1), - libxcb-render0-dev, - libxcb-image0-dev, - libxcb-shape0-dev, - libxcb-sync0-dev, - libxcb-render-util0-dev, - libxcb1-dev, - libxcb-xfixes0-dev, - libxcb-icccm4-dev, - libxcb1-dev, - libx11-xcb-dev, - libxcb-keysyms1-dev, - libxcb-image0-dev, - libxcb-shm0-dev, - libxcb-icccm4-dev, - libxcb-sync0-dev, - libxcb-xfixes0-dev, - libxrender-dev, - libxcb-shape0-dev, - libasound2-dev [linux-any], - libaudio-dev, - libcups2-dev, - libdbus-1-dev, - libfreetype6-dev, - libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], - libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], - libglib2.0-dev, - libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], - libice-dev, - libjpeg-dev, - libmng-dev, - libpng-dev, - libsm-dev, - libsqlite3-dev, - libssl-dev, - libtiff5-dev, - libx11-dev, - libxcursor-dev, - libxext-dev, - libxft-dev, - libxi-dev, - libxinerama-dev, - libxmu-dev, - libxrandr-dev, - libxrender-dev, - libxt-dev, - libxv-dev, - zlib1g-dev, - libedit-dev, - libvulkan-dev, - libpolkit-gobject-1-dev -Standards-Version: 4.4.1 -Homepage: https://vpn.mozilla.org/ -Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client - -Package: mozillavpn -Architecture: any -Depends: libpolkit-gobject-1-0 (>=0.105), - wireguard (>=1.0.20200319), - wireguard-tools (>=1.0.20200319), - libxcb-xinerama0 (>=1.13-1), - qt515base (>=5.15.2-1basyskom4), - qt515declarative (>= 5.15.2-1basyskom1), - qt515graphicaleffects (>= 5.15.2-1basyskom1), - qt515imageformats (>= 5.15.2-1basyskom), - qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), - qt515quickcontrols2 (>=5.15.2-1basyskom1), - qt515svg (>=5.15.2-1basyskom1), - qt515websockets (>=5.15.2-1basyskom1) -Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. - Read more on https://vpn.mozilla.org - - diff --git a/linux/debian/control.focal b/linux/debian/control.focal deleted file mode 100644 index 379ac7ae8ff..00000000000 --- a/linux/debian/control.focal +++ /dev/null @@ -1,94 +0,0 @@ -Source: mozillavpn -Section: net -Priority: optional -Maintainer: mozilla -Build-Depends: debhelper (>= 9.20160709), - cdbs, - quilt, - flex, - golang (>=1.13), - cargo, - python3-yaml, - python3-lxml, - qt515base (>=5.15.2-1basyskom4), - qt515declarative (>= 5.15.2-1basyskom1), - qt515graphicaleffects (>= 5.15.2-1basyskom1), - qt515imageformats (>= 5.15.2-1basyskom), - qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), - qt515quickcontrols2 (>=5.15.2-1basyskom1), - qt515svg (>=5.15.2-1basyskom1), - qt515tools (>=5.15.2-1basyskom1), - qt515websockets (>=5.15.2-1basyskom1), - libxcb-render0-dev, - libxcb-image0-dev, - libxcb-shape0-dev, - libxcb-sync0-dev, - libxcb-render-util0-dev, - libxcb1-dev, - libxcb-xfixes0-dev, - libxcb-icccm4-dev, - libxcb1-dev, - libx11-xcb-dev, - libxcb-keysyms1-dev, - libxcb-image0-dev, - libxcb-shm0-dev, - libxcb-icccm4-dev, - libxcb-sync0-dev, - libxcb-xfixes0-dev, - libxrender-dev, - libxcb-shape0-dev, - g++-4.6 (>= 4.6.0-7~) [armel], - libasound2-dev [linux-any], - libaudio-dev, - libcups2-dev, - libdbus-1-dev, - libfreetype6-dev, - libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], - libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], - libglib2.0-dev, - libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], - libice-dev, - libjpeg-dev, - libmng-dev, - libpng-dev, - libsm-dev, - libsqlite3-dev, - libssl-dev, - libtiff5-dev, - libx11-dev, - libxcursor-dev, - libxext-dev, - libxft-dev, - libxi-dev, - libxinerama-dev, - libxmu-dev, - libxrandr-dev, - libxrender-dev, - libxt-dev, - libxv-dev, - zlib1g-dev, - libedit-dev, - libvulkan-dev, - libpolkit-gobject-1-dev -Standards-Version: 4.4.1 -Homepage: https://vpn.mozilla.org/ -Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client - -Package: mozillavpn -Architecture: any -Depends: libpolkit-gobject-1-0 (>=0.105-20ubuntu0.18.04.5), - wireguard (>=1.0.20200513-1~18.04.2), - wireguard-tools (>=1.0.20200513-1~18.04.2), - libicu66 (>=66.1-2ubuntu2), - libxcb-xinerama0 (>=1.14-2), - resolvconf (>=1.82), - qt515base (>=5.15.2-1basyskom4), - qt515declarative (>= 5.15.2-1basyskom1), - qt515graphicaleffects (>= 5.15.2-1basyskom1), - qt515imageformats (>= 5.15.2-1basyskom), - qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), - qt515quickcontrols2 (>=5.15.2-1basyskom1), - qt515svg (>=5.15.2-1basyskom1), - qt515websockets (>=5.15.2-1basyskom1) -Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. - Read more on https://vpn.mozilla.org diff --git a/linux/debian/control.qt5 b/linux/debian/control.qt5 deleted file mode 100644 index 13c8a138779..00000000000 --- a/linux/debian/control.qt5 +++ /dev/null @@ -1,59 +0,0 @@ -Source: mozillavpn -Section: net -Priority: optional -Maintainer: mozilla -Build-Depends: debhelper (>= 9.20160709), - cdbs, - quilt, - flex, - golang (>=2:1.13~) | golang-1.13, - cargo, - python3-yaml, - python3-lxml, - libqt5networkauth5-dev (>=5.15.2), - libqt5websockets5-dev (>=5.15.2), - qtbase5-dev (>=5.15.2), - qtbase5-dev-tools (>=5.15.2), - qtdeclarative5-dev (>=5.15.2), - qtdeclarative5-dev-tools (>=5.15.2), - qt5-qmake-bin (>=5.15.2), - qttools5-dev-tools (>=5.15.2), - libpolkit-gobject-1-dev -Standards-Version: 4.4.1 -Homepage: https://vpn.mozilla.org/ -Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client - -Package: mozillavpn -Architecture: any -Depends: libpolkit-gobject-1-0 (>=0.105), - wireguard (>=1.0.20200319), - wireguard-tools (>=1.0.20200319), - resolvconf (>=1.82), - libqt5quick5 (>=5.15.2), - libqt5widgets5 (>=5.15.2), - libqt5gui5 (>=5.15.2), - libqt5qml5 (>=5.15.2), - libqt5network5 (>=5.15.2), - libqt5networkauth5 (>=5.15.2), - libqt5dbus5 (>=5.15.2), - libqt5core5a (>=5.15.2), - libqt5qmlmodels5 (>=5.15.2), - libqt5svg5 (>=5.15.2), - libqt5quickcontrols2-5 (>=5.15.2), - libqt5websockets5 (>= 5.15.2), - libqt5test5 (>=5.15.2), - qml-module-qtgraphicaleffects (>=5.15.2), - qml-module-qtquick-controls (>=5.15.2), - qml-module-qtquick-controls2 (>=5.15.2), - qml-module-qtquick-extras (>=5.15.2), - qml-module-qtquick-layouts (>=5.15.2), - qml-module-qtquick-localstorage (>=5.15.2), - qml-module-qtquick-window2 (>=5.15.2), - qml-module-qtquick2 (>=5.15.2), - qml-module-qtqml-models2 (>=5.15.2), - qml-module-qtqml (>=5.15.2), - qml-module-qt-labs-qmlmodels (>=5.15.2) -Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. - Read more on https://vpn.mozilla.org - - diff --git a/linux/debian/rules.qt6 b/linux/debian/rules similarity index 100% rename from linux/debian/rules.qt6 rename to linux/debian/rules diff --git a/linux/debian/rules.beineri b/linux/debian/rules.beineri deleted file mode 100755 index c889c9efdc7..00000000000 --- a/linux/debian/rules.beineri +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/make -f - -export DH_VERBOSE=1 -export QTDIR := /opt/qt515 -export PATH := $(QTDIR)/bin:$(PATH) -export LD_LIBRARY_PATH := $(QTDIR)/lib:$(LD_LIBRARY_PATH) - -DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) -DEB_VERSION ?= $(shell dpkg-parsechangelog -SVersion) - -GOLANG_NATIVE_VERSION := $(shell dpkg-query --showformat='${Version}' --show golang 2>/dev/null || echo 0) -ifneq (ok,$(dpkg --compare-versions $(GOLANG_NATIVE_VERSION) ge 2:1.13 && echo ok)) - export GODIR := /usr/lib/go-1.13 - export PATH := $(GODIR)/bin:$(PATH) -endif - -%: - dh $@ --with=systemd --warn-missing - -override_dh_auto_configure: - python3 scripts/utils/import_languages.py - qmake CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release QT+=svg BUILD_ID=$(DEB_VERSION) - -override_dh_installdocs: - -override_dh_installinfo: - -override_dh_installsystemd: - dh_installsystemd debian/mozillavpn/lib/systemd/system/mozillavpn.service - -override_dh_systemd_start: - dh_systemd_start debian/mozillavpn/lib/systemd/system/mozillavpn.service - -override_dh_systemd_enable: - dh_systemd_enable debian/mozillavpn/lib/systemd/system/mozillavpn.service - diff --git a/linux/debian/rules.qt5 b/linux/debian/rules.qt5 deleted file mode 100755 index 9f46611918e..00000000000 --- a/linux/debian/rules.qt5 +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/make -f - -export DH_VERBOSE=1 - -DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) -DEB_VERSION ?= $(shell dpkg-parsechangelog -SVersion) - -GOLANG_NATIVE_VERSION := $(shell dpkg-query --showformat='${Version}' --show golang 2>/dev/null || echo 0) -ifneq (ok,$(dpkg --compare-versions $(GOLANG_NATIVE_VERSION) ge 2:1.13 && echo ok)) - export GODIR := /usr/lib/go-1.13 - export PATH := $(GODIR)/bin:$(PATH) -endif - -%: - dh $@ --with=systemd --warn-missing - -override_dh_auto_configure: - python3 scripts/utils/import_languages.py - qmake CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release BUILD_ID=$(DEB_VERSION) - -override_dh_installdocs: - -override_dh_installinfo: - -override_dh_installsystemd: - dh_installsystemd debian/mozillavpn/lib/systemd/system/mozillavpn.service - -override_dh_systemd_start: - dh_systemd_start debian/mozillavpn/lib/systemd/system/mozillavpn.service - -override_dh_systemd_enable: - dh_systemd_enable debian/mozillavpn/lib/systemd/system/mozillavpn.service - diff --git a/linux/netfilter/netfilter.go b/linux/netfilter/netfilter.go index 6313d7cceb2..58f6fbf5ecf 100644 --- a/linux/netfilter/netfilter.go +++ b/linux/netfilter/netfilter.go @@ -63,6 +63,7 @@ type nftCtx struct { conntrack *nftables.Chain preroute *nftables.Chain preroute_v6 *nftables.Chain + addrset *nftables.Set fwmark uint32 conn nftables.Conn } @@ -120,43 +121,56 @@ func (ctx* nftCtx) nftIfup(ifname string) { }, }) - // Inbound packets from outside the tunnel should be marked for RPF + // Inbound packets from wireguard servers should be marked for RPF // and moved into the external conntrack zone. ctx.conn.AddRule(&nftables.Rule{ Table: ctx.table_inet, Chain: ctx.preroute, Exprs: []expr.Any{ - // Match packets arriving from outside the tunnel - &expr.Meta{ - Key: expr.MetaKeyIIFNAME, - Register: 1, + // For now, we only support IPv4 in this check. + // TODO: Support IPv6. + &expr.Meta{ + Key: expr.MetaKeyPROTOCOL, + Register: 1, }, &expr.Cmp{ - Op: expr.CmpOpNeq, - Register: 1, - Data: nftIfname(ifname), + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(linux.ETH_P_IP), }, - // Match packets arriving on non-localhost. - &expr.Meta{ - Key: expr.MetaKeyIIFTYPE, - Register: 1, + // Match UDP packets + &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, }, &expr.Cmp{ - Op: expr.CmpOpNeq, - Register: 1, - Data: binaryutil.NativeEndian.PutUint16(linux.ARPHRD_LOOPBACK), + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{linux.IPPROTO_UDP}, + }, + // Lookup the IPv4 source address. + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: uint32(12), + Len: uint32(4), + }, + &expr.Lookup{ + SourceRegister: 1, + SetName: ctx.addrset.Name, + SetID: ctx.addrset.ID, }, - // Set the firewall mark for the packets + // Set the firewall mark. &expr.Immediate{ - Register: 1, - Data: binaryutil.NativeEndian.PutUint32(ctx.fwmark), + Register: 1, + Data: binaryutil.NativeEndian.PutUint32(ctx.fwmark), }, &expr.Meta{ - Key: expr.MetaKeyMARK, - Register: 1, + Key: expr.MetaKeyMARK, + Register: 1, SourceRegister: true, }, - // Set the conntrack zone + // Set the conntrack zone. &immctzone, &setctzone, }, @@ -413,6 +427,13 @@ func NetfilterCreateTables() int32 { Priority: nftables.ChainPriorityRaw, }) + mozvpn_ctx.addrset = &nftables.Set{ + Table: mozvpn_ctx.table_inet, + Name: "mozvpn-addrset", + KeyType: nftables.TypeIPAddr, + } + mozvpn_ctx.conn.AddSet(mozvpn_ctx.addrset, nil) + log.Println("Creating netfilter tables") return mozvpn_ctx.nftCommit() } @@ -434,6 +455,7 @@ func NetfilterClearTables() int32 { mozvpn_ctx.conn.FlushChain(mozvpn_ctx.conntrack) mozvpn_ctx.conn.FlushChain(mozvpn_ctx.preroute) mozvpn_ctx.conn.FlushChain(mozvpn_ctx.preroute_v6) + mozvpn_ctx.conn.FlushSet(mozvpn_ctx.addrset) log.Println("Clearing netfilter tables") return mozvpn_ctx.nftCommit() @@ -450,6 +472,29 @@ func NetfilterIfup(ifname string, fwmark uint32) int32 { return mozvpn_ctx.nftCommit() } +//export NetfilterMarkInbound +func NetfilterMarkInbound(ipaddr string, port uint32) int32 { + element := []nftables.SetElement{ + { Key: net.ParseIP(ipaddr).To4(), }, + } + + mozvpn_ctx.conn.SetAddElements(mozvpn_ctx.addrset, element) + + log.Println("Marking inbound traffic from server") + return mozvpn_ctx.nftCommit() +} + +//export NetfilterClearInbound +func NetfilterClearInbound(ipaddr string) int32 { + element := []nftables.SetElement{ + { Key: net.ParseIP(ipaddr).To4(), }, + } + + mozvpn_ctx.conn.SetDeleteElements(mozvpn_ctx.addrset, element) + log.Println("Clearing traffic marks for server") + return mozvpn_ctx.nftCommit() +} + //export NetfilterIsolateIpv6 func NetfilterIsolateIpv6(ifname string, ipv6addr string) int32 { // Inbound packets from any interface other than the tunnel should @@ -459,7 +504,7 @@ func NetfilterIsolateIpv6(ifname string, ipv6addr string) int32 { Chain: mozvpn_ctx.preroute_v6, Exprs: []expr.Any{ // Match packets arriving from outside the tunnel - &expr.Meta{ + &expr.Meta{ Key: expr.MetaKeyIIFNAME, Register: 1, }, diff --git a/lottie/lib/lottieprivate.cpp b/lottie/lib/lottieprivate.cpp index b2c534a5f02..fee950cc222 100644 --- a/lottie/lib/lottieprivate.cpp +++ b/lottie/lib/lottieprivate.cpp @@ -9,6 +9,7 @@ #include "lottiestatus.h" #include +#include #include constexpr const char* FILLMODE_STRETCH = "stretch"; @@ -18,7 +19,7 @@ constexpr const char* FILLMODE_PRESERVEASPECTCROP = "preserveAspectCrop"; namespace { static QJSEngine* s_engine = nullptr; -static QString s_userAgent; +Q_GLOBAL_STATIC(QString, s_userAgent); } // namespace // static @@ -30,7 +31,9 @@ void LottiePrivate::initialize(QJSEngine* engine, const QString& userAgent) { qmlRegisterModule("vpn.mozilla.lottie", 1, 0); Q_ASSERT(!userAgent.isEmpty()); - s_userAgent = userAgent; + if (s_userAgent) { + *s_userAgent = userAgent; + } } // static @@ -40,7 +43,9 @@ QJSEngine* LottiePrivate::engine() { } // static -const QString& LottiePrivate::userAgent() { return s_userAgent; } +const QString LottiePrivate::userAgent() { + return s_userAgent ? *s_userAgent : QString(); +} LottiePrivate::LottiePrivate(QQuickItem* parent) : QQuickItem(parent), m_loops(false) {} @@ -130,8 +135,6 @@ void LottiePrivate::createAnimation() { return; } - QJSValue containerValue = engine()->toScriptValue(m_container); - QJSValue rendererSettings = engine()->newObject(); rendererSettings.setProperty("clearCanvas", true); rendererSettings.setProperty( @@ -279,7 +282,7 @@ void LottiePrivate::clearCanvas() { if (ctx.isObject() && ctx.hasProperty("reset")) { QJSValue ctxReset = ctx.property("reset"); Q_ASSERT(ctxReset.isCallable()); - QJSValue ctx = ctxReset.callWithInstance(canvasValue, QList()); + ctxReset.callWithInstance(canvasValue, QList()); } runFunction(canvasValue, "requestPaint", QList()); diff --git a/lottie/lib/lottieprivate.h b/lottie/lib/lottieprivate.h index 75f65023897..4a645076d3f 100644 --- a/lottie/lib/lottieprivate.h +++ b/lottie/lib/lottieprivate.h @@ -30,7 +30,7 @@ class LottiePrivate : public QQuickItem { public: static void initialize(QJSEngine* engine, const QString& userAgent); - static const QString& userAgent(); + static const QString userAgent(); static QJSEngine* engine(); LottiePrivate(QQuickItem* parent = 0); diff --git a/macos/loginitem/main.m b/macos/loginitem/main.m index ef278cfc97b..6d51780c83a 100644 --- a/macos/loginitem/main.m +++ b/macos/loginitem/main.m @@ -6,14 +6,34 @@ int main() { - NSString *appId = [NSString stringWithUTF8String: APP_ID]; + if (@available(macOS 10.15, *)) { + // When an app is sandboxed, the configuration.arguments array is ignored. Let's use env + // variables to minimize the app. + NSDictionary* env = @{@"MVPN_MINIMIZED" : @"1"}; + + NSWorkspaceOpenConfiguration* configuration = [NSWorkspaceOpenConfiguration new]; + [configuration setEnvironment:env]; [[NSWorkspace sharedWorkspace] - launchAppWithBundleIdentifier:appId - options:NSWorkspaceLaunchDefault - additionalEventParamDescriptor:NULL - launchIdentifier:NULL]; + openApplicationAtURL:[NSURL fileURLWithPath:@"/Applications/Mozilla VPN.app"] + configuration:configuration + completionHandler:^(NSRunningApplication* _Nullable app, NSError* _Nullable error) { + if (error) { + NSLog(@"Failed to run the Mozilla VPN app: %@", error.localizedDescription); + } + exit(0); + }]; + + [NSThread sleepForTimeInterval:10]; + } else { + NSString* appId = [NSString stringWithUTF8String:APP_ID]; + + [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:appId + options:NSWorkspaceLaunchDefault + additionalEventParamDescriptor:NULL + launchIdentifier:NULL]; + } - return 0; + return 0; } diff --git a/nebula/nebula.cpp b/nebula/nebula.cpp index 9d9cb715940..d68e293138b 100644 --- a/nebula/nebula.cpp +++ b/nebula/nebula.cpp @@ -11,12 +11,8 @@ void Nebula::Initialize(QQmlEngine* engine) { Q_INIT_RESOURCE(components); Q_INIT_RESOURCE(themes); Q_INIT_RESOURCE(nebula_resources); -# if QT_VERSION >= 0x060000 Q_INIT_RESOURCE(compatQt6); Q_INIT_RESOURCE(resourcesQt6); -# else - Q_INIT_RESOURCE(compatQt5); -# endif #endif engine->addImportPath(QRC_ROOT); diff --git a/nebula/nebula.pri b/nebula/nebula.pri index 6362b428ae2..95a88bcb018 100644 --- a/nebula/nebula.pri +++ b/nebula/nebula.pri @@ -19,9 +19,5 @@ RESOURCES += $$PWD/ui/nebula_resources.qrc QML_IMPORT_PATH += $$PWD/ui -versionAtLeast(QT_VERSION, 6.0.0) { - RESOURCES += $$PWD/ui/compatQt6.qrc - RESOURCES += $$PWD/ui/resourcesQt6.qrc -} else { - RESOURCES += $$PWD/ui/compatQt5.qrc -} +RESOURCES += $$PWD/ui/compatQt6.qrc +RESOURCES += $$PWD/ui/resourcesQt6.qrc diff --git a/nebula/ui/compat/qt5/VPNAnimatedRingsShader.qml b/nebula/ui/compat/qt5/VPNAnimatedRingsShader.qml deleted file mode 100644 index 3e732fd26d9..00000000000 --- a/nebula/ui/compat/qt5/VPNAnimatedRingsShader.qml +++ /dev/null @@ -1,66 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 - -ShaderEffect { - fragmentShader: " - #ifdef GL_ES - precision mediump float; - #endif - varying mediump vec2 qt_TexCoord0; - uniform mediump float animationProgress; - uniform mediump float animationOpacity; - - float drawCircle(float distance, float radius) { - float antialias = 0.005; - - return smoothstep(radius, radius - antialias, distance); - } - - float composeRing(float distance, float strokeWidth, float radius) { - float circle1 = drawCircle(distance, radius); - float circle2 = drawCircle(distance, radius - strokeWidth); - - return circle1 - circle2; - } - - float calcRingRadius(float minRadius, float maxRadius, float currentRadius, float offset) { - return mod(maxRadius * offset + currentRadius, maxRadius) + minRadius; - } - - void main() { - // Center coordinate - vec2 center = vec2(0.5, 0.275); - float centerDistance = length(qt_TexCoord0 - center); - - // Ring properties - float strokeWidth = 0.015; - float minRadius = 0.0; - float maxRadius = 0.5; - vec4 color = vec4(1.0, 1.0, 1.0, 1.0) * animationOpacity; - - // Rings - float ringRadius1 = calcRingRadius(minRadius, maxRadius, animationProgress, 0.0); - float ring1 = composeRing(centerDistance, strokeWidth, ringRadius1); - - float ringRadius2 = calcRingRadius(minRadius, maxRadius, animationProgress, 0.33 * animationOpacity); - float ring2 = composeRing(centerDistance, strokeWidth, ringRadius2); - - float ringRadius3 = calcRingRadius(minRadius, maxRadius, animationProgress, 0.66 * animationOpacity); - float ring3 = composeRing(centerDistance, strokeWidth, ringRadius3); - - // Radial gradient - float gradientWidth = 5.0; - float gradientBlur = 0.8; - float gradientOffset = 0.28; - float gradientMask = 1.0 - max(0.0, 0.25 - pow(max(0.0, abs(centerDistance - gradientOffset) * gradientWidth), gradientWidth * gradientBlur)); - - // Output rings - float rings = ring1 + ring2 + ring3; - gl_FragColor = color * rings - gradientMask; - } - " -} - diff --git a/nebula/ui/compat/qt5/VPNColorOverlay.qml b/nebula/ui/compat/qt5/VPNColorOverlay.qml deleted file mode 100644 index b66126fd26c..00000000000 --- a/nebula/ui/compat/qt5/VPNColorOverlay.qml +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 -import QtGraphicalEffects 1.0 - -ColorOverlay { -} diff --git a/nebula/ui/compat/qt5/VPNDropShadow.qml b/nebula/ui/compat/qt5/VPNDropShadow.qml deleted file mode 100644 index dd7e9b5b7b0..00000000000 --- a/nebula/ui/compat/qt5/VPNDropShadow.qml +++ /dev/null @@ -1,10 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 -import QtGraphicalEffects 1.0 - -DropShadow { - samples: 33 -} diff --git a/nebula/ui/compat/qt5/VPNLinearGradient.qml b/nebula/ui/compat/qt5/VPNLinearGradient.qml deleted file mode 100644 index 2961b3ab0d9..00000000000 --- a/nebula/ui/compat/qt5/VPNLinearGradient.qml +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 -import QtGraphicalEffects 1.0 - -LinearGradient { -} diff --git a/nebula/ui/compat/qt5/VPNRadialGradient.qml b/nebula/ui/compat/qt5/VPNRadialGradient.qml deleted file mode 100644 index 43a5cf124de..00000000000 --- a/nebula/ui/compat/qt5/VPNRadialGradient.qml +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 -import QtGraphicalEffects 1.0 - -RadialGradient { -} diff --git a/nebula/ui/compat/qt5/VPNRectangularGlow.qml b/nebula/ui/compat/qt5/VPNRectangularGlow.qml deleted file mode 100644 index 640b38dab35..00000000000 --- a/nebula/ui/compat/qt5/VPNRectangularGlow.qml +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/. */ - -import QtQuick 2.0 -import QtGraphicalEffects 1.0 - -RectangularGlow { -} diff --git a/nebula/ui/compat/qt5/qmldir b/nebula/ui/compat/qt5/qmldir deleted file mode 100644 index 9ea8685cb55..00000000000 --- a/nebula/ui/compat/qt5/qmldir +++ /dev/null @@ -1,7 +0,0 @@ -VPNAnimatedRingsShader 0.1 VPNAnimatedRingsShader.qml -VPNColorOverlay 0.1 VPNColorOverlay.qml -VPNDropShadow 0.1 VPNDropShadow.qml -VPNLinearGradient 0.1 VPNLinearGradient.qml -VPNOpacityMask 0.1 VPNOpacityMask.qml -VPNRadialGradient 0.1 VPNRadialGradient.qml -VPNRectangularGlow 0.1 VPNRectangularGlow.qml diff --git a/nebula/ui/compatQt5.qrc b/nebula/ui/compatQt5.qrc deleted file mode 100644 index 530780b2811..00000000000 --- a/nebula/ui/compatQt5.qrc +++ /dev/null @@ -1,12 +0,0 @@ - - - compat/qt5/qmldir - compat/qt5/VPNAnimatedRingsShader.qml - compat/qt5/VPNColorOverlay.qml - compat/qt5/VPNDropShadow.qml - compat/qt5/VPNLinearGradient.qml - compat/qt5/VPNOpacityMask.qml - compat/qt5/VPNRadialGradient.qml - compat/qt5/VPNRectangularGlow.qml - - diff --git a/nebula/ui/components/VPNCancelButton.qml b/nebula/ui/components/VPNCancelButton.qml index 1f09d276acc..e8aa9b52d4d 100644 --- a/nebula/ui/components/VPNCancelButton.qml +++ b/nebula/ui/components/VPNCancelButton.qml @@ -12,5 +12,5 @@ import components 0.1 VPNLinkButton { labelText: VPNl18n.InAppSupportWorkflowSupportSecondaryActionText // "Cancel" fontName: VPNTheme.theme.fontBoldFamily - linkColor: VPNTheme.theme.redButton + linkColor: VPNTheme.theme.redLinkButton } diff --git a/nebula/ui/components/VPNCollapsibleCard.qml b/nebula/ui/components/VPNCollapsibleCard.qml index 6e1e481b167..b774439aaae 100644 --- a/nebula/ui/components/VPNCollapsibleCard.qml +++ b/nebula/ui/components/VPNCollapsibleCard.qml @@ -10,6 +10,7 @@ import Mozilla.VPN 1.0 Rectangle { id: root + objectName: "vpnCollapsibleCard" property int animationDuration: 150 property bool expanded: false @@ -128,6 +129,7 @@ Rectangle { VPNIconButton { id: stateIndicator + objectName: "vpnCollapsibleCardChevron" onClicked: { handleToggleCard(); diff --git a/nebula/ui/components/VPNConnectionInfoContent.qml b/nebula/ui/components/VPNConnectionInfoContent.qml index bf09bd5dad0..12cd5f5d5cb 100644 --- a/nebula/ui/components/VPNConnectionInfoContent.qml +++ b/nebula/ui/components/VPNConnectionInfoContent.qml @@ -143,14 +143,30 @@ VPNFlickable { VPNConnectionInfoItem { //% "Download" title: qsTrId("vpn.connectionInfo.download") - subtitle: root.getConnectionLabel(VPNConnectionBenchmark.bitsPerSec) + subtitle: root.getConnectionLabel(VPNConnectionBenchmark.downloadBps) iconPath: "qrc:/nebula/resources/download.svg" } - } - } + Rectangle { + color: VPNTheme.colors.white + height: 1 + opacity: 0.2 + visible: VPNFeatureList.get("benchmarkUpload").isSupported + + Layout.fillWidth: true + } + + VPNConnectionInfoItem { + title: VPNl18n.ConnectionInfoLabelUpload + subtitle: root.getConnectionLabel(VPNConnectionBenchmark.uploadBps) + iconPath: "qrc:/nebula/resources/upload.svg" + visible: VPNFeatureList.get("benchmarkUpload").isSupported + } + } } } + } + function getConnectionLabel(connectionValueBits) { return `${computeValue(connectionValueBits)} ${computeRange(connectionValueBits)}`; } diff --git a/nebula/ui/components/VPNLinkButton.qml b/nebula/ui/components/VPNLinkButton.qml index 12d499d4675..0d3604f07f2 100644 --- a/nebula/ui/components/VPNLinkButton.qml +++ b/nebula/ui/components/VPNLinkButton.qml @@ -27,7 +27,6 @@ VPNButtonBase { return } if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) { - root.clicked(); state = uiState.stateDefault; } } diff --git a/nebula/ui/components/VPNRecentConnections.qml b/nebula/ui/components/VPNRecentConnections.qml index 2112c9a1cd5..709d5b683e3 100644 --- a/nebula/ui/components/VPNRecentConnections.qml +++ b/nebula/ui/components/VPNRecentConnections.qml @@ -13,6 +13,57 @@ ColumnLayout { property bool hasVisibleConnections: false property bool showMultiHopRecentConnections: true property real numVisibleConnections: recentConnectionsRepeater.count + property var recentConnectionsModel: getRecentConnectionsModel() + + function getRecentConnectionsModel() { + const maxNumVisibleConnections = 2 + const recentConnections = [] + for (let i=1; i "); + const isMultiHop = servers.length > 1; + + if (isMultiHop !== showMultiHopRecentConnections) { + return recentConnections; + } + + const connection = []; + + for(let x = 0; x < servers.length; x++) { + let index = servers[x].lastIndexOf(","); + if (index <= 0) { + console.log("Unable to parse server from " + servers[x]); + continue; + } + let countryCode = servers[x].slice(index+1).trim(); + let serverCityName = servers[x].slice(0, index).trim(); + + connection.push({ + countryCode: countryCode, + serverCityName: serverCityName, + localizedCityName: VPNLocalizer.localizedCityName(countryCode, serverCityName) + }); + } + + const [{ localizedCityName: firstCityLocalizedName }, secondServer] = connection; + const accessibleLabel = secondServer + ? VPNl18n.MultiHopFeatureAccessibleNameRecentConnection + .arg(firstCityLocalizedName) + .arg(secondServer.localizedCityName) + : firstCityLocalizedName; + + recentConnections.push({ + isMultiHop, + connection, + accessibleLabel + }); + } + return recentConnections + } function focusItemAt(idx) { if (!visible) { @@ -26,7 +77,7 @@ ColumnLayout { id: root spacing: VPNTheme.theme.windowMargin / 2 - visible: repeaterModel.count > 0 + visible: root.recentConnectionsModel.length > 0 function popStack() { stackview.pop() @@ -38,7 +89,7 @@ ColumnLayout { Layout.leftMargin: VPNTheme.theme.windowMargin Layout.minimumHeight: VPNTheme.theme.vSpacing verticalAlignment: Text.AlignVCenter - visible: repeaterModel.count > 0 + visible: root.recentConnectionsModel.length > 0 } @@ -48,59 +99,15 @@ ColumnLayout { spacing: VPNTheme.theme.windowMargin / 2 Layout.fillWidth: true - ListModel { - property real maxNumVisibleConnections: 2 - id: repeaterModel - - Component.onCompleted: { - // don't show the first/current entry - for (let i=1; i "); - const isMultiHop = servers.length > 1; - - if (isMultiHop !== showMultiHopRecentConnections) { - return; - } - - const connection = []; - - for(let x = 0; x < servers.length; x++) { - let index = servers[x].lastIndexOf(","); - if (index <= 0) { - console.log("Unable to parse server from " + servers[x]); - continue; - } - let countryCode = servers[x].slice(index+1).trim(); - let serverCityName = servers[x].slice(0, index).trim(); - - connection.push({ - countryCode: countryCode, - serverCityName: serverCityName, - localizedCityName: VPNLocalizer.localizedCityName(countryCode, serverCityName) - }); - } - - append({isMultiHop, connection }); - } - } - } - Repeater { property real maxVisibleConnections: 2 property real visibleConnections: 0 id: recentConnectionsRepeater - model: repeaterModel + model: root.recentConnectionsModel delegate: VPNClickableRow { id: del - // MULTIHOP TODO - Use real string - accessibleName: "TODO" + accessibleName: modelData.accessibleLabel Layout.fillWidth: true Layout.preferredHeight: VPNTheme.theme.rowHeight @@ -116,11 +123,11 @@ ColumnLayout { let args = []; popStack(); - if (isMultiHop) { - return VPNController.changeServer(connection.get(1).countryCode, connection.get(1).serverCityName, connection.get(0).countryCode, connection.get(0).serverCityName) + if (modelData.isMultiHop) { + return VPNController.changeServer(modelData.connection[1].countryCode, modelData.connection[1].serverCityName, modelData.connection[0].countryCode, modelData.connection[0].serverCityName) } - return VPNController.changeServer(connection.get(0).countryCode, connection.get(0).serverCityName) + return VPNController.changeServer(modelData.connection[0].countryCode, modelData.connection[0].serverCityName) } @@ -135,7 +142,7 @@ ColumnLayout { id: serverLabel Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - serversList: connection + serversList: modelData.connection } } } @@ -147,7 +154,7 @@ ColumnLayout { Layout.preferredHeight: 1 Layout.alignment: Qt.AlignHCenter color: VPNTheme.colors.grey10 - visible: repeaterModel.count > 0 + visible: root.recentConnectionsModel.length > 0 } } diff --git a/nebula/ui/components/VPNSegmentedToggle.qml b/nebula/ui/components/VPNSegmentedToggle.qml index 10432f327d6..3bf92942ec3 100644 --- a/nebula/ui/components/VPNSegmentedToggle.qml +++ b/nebula/ui/components/VPNSegmentedToggle.qml @@ -74,6 +74,7 @@ Rectangle { RowLayout { + objectName: "multiHopSelector" anchors { fill: parent topMargin: 8 diff --git a/nebula/ui/components/VPNSignOut.qml b/nebula/ui/components/VPNSignOut.qml index e03afc5f429..af0bff7c0b5 100644 --- a/nebula/ui/components/VPNSignOut.qml +++ b/nebula/ui/components/VPNSignOut.qml @@ -11,7 +11,7 @@ VPNFooterLink { //% "Sign out" labelText: qsTrId("vpn.main.signOut2") fontName: VPNTheme.theme.fontBoldFamily - linkColor: VPNTheme.theme.redButton + linkColor: VPNTheme.theme.redLinkButton onClicked: () => { VPNController.logout(); } diff --git a/nebula/ui/components/VPNStackView.qml b/nebula/ui/components/VPNStackView.qml index 1fef10339b0..0b3275206a8 100644 --- a/nebula/ui/components/VPNStackView.qml +++ b/nebula/ui/components/VPNStackView.qml @@ -11,14 +11,16 @@ import Mozilla.VPN 1.0 StackView { id: stackView - onCurrentItemChanged: { - var objString = currentItem.toString().split("(")[0]; - VPN.currentView = objString.split("_QML")[0]; - } - Component.onCompleted: function(){ - if(!currentItem && typeof initialItem === "number" ){ - console.error("Failed to parse initialItem, try Component.OnComplete:push(someURI)"); + if(!currentItem && initialItem) { + // We don't show anything right now and inital item is set, + // On android if initialItem is anything But a component + // it will totaly parse that into garbage values and fail + // + // See https://github.com/mozilla-mobile/mozilla-vpn-client/pull/2638 + console.error("Using the initialItem property does not work on some platforms. Use Component.onCompleted: stackview.push(someURI)"); + VPN.exitForUnrecoverableError("Setting initialItem on a StackView is illegal. See previous logs for more information."); + } } diff --git a/nebula/ui/components/VPNSystemAlert.qml b/nebula/ui/components/VPNSystemAlert.qml index f1bacee164c..bce2144f3b9 100644 --- a/nebula/ui/components/VPNSystemAlert.qml +++ b/nebula/ui/components/VPNSystemAlert.qml @@ -14,17 +14,17 @@ VPNAlert { Item { id: alertStates - state: VPN.alert + state: VPNErrorHandler.alert states: [ State { - name: VPN.NoAlert + name: VPNErrorHandler.NoAlert PropertyChanges { target: alertBox visible: false } }, State{ - name: VPN.AuthCodeSentAlert + name: VPNErrorHandler.AuthCodeSentAlert PropertyChanges { target: alertBox alertType: alertTypes.success @@ -33,7 +33,7 @@ VPNAlert { } }, State { - name: VPN.AuthenticationFailedAlert + name: VPNErrorHandler.AuthenticationFailedAlert PropertyChanges { target: alertBox //% "Authentication error" @@ -48,7 +48,7 @@ VPNAlert { } }, State { - name: VPN.ConnectionFailedAlert + name: VPNErrorHandler.ConnectionFailedAlert PropertyChanges { target: alertBox //% "Unable to connect" @@ -58,7 +58,7 @@ VPNAlert { } }, State { - name: VPN.NoConnectionAlert + name: VPNErrorHandler.NoConnectionAlert PropertyChanges { target: alertBox //% "No internet connection" @@ -68,7 +68,7 @@ VPNAlert { } }, State { - name: VPN.ControllerErrorAlert + name: VPNErrorHandler.ControllerErrorAlert PropertyChanges { target: alertBox //% "Background service error" @@ -83,7 +83,7 @@ VPNAlert { } }, State { - name: VPN.UnrecoverableErrorAlert + name: VPNErrorHandler.UnrecoverableErrorAlert PropertyChanges { target: alertBox alertText: qsTrId("vpn.alert.backendServiceError") @@ -94,7 +94,7 @@ VPNAlert { } }, State { - name: VPN.RemoteServiceErrorAlert + name: VPNErrorHandler.RemoteServiceErrorAlert PropertyChanges { target: alertBox //% "Remote service error" @@ -106,7 +106,7 @@ VPNAlert { } }, State { - name: VPN.SubscriptionFailureAlert + name: VPNErrorHandler.SubscriptionFailureAlert PropertyChanges { target: alertBox //% "Subscription failed" @@ -116,7 +116,7 @@ VPNAlert { } }, State { - name: VPN.GeoIpRestrictionAlert + name: VPNErrorHandler.GeoIpRestrictionAlert PropertyChanges { target: alertBox //% "Operation not allowed from current location" @@ -125,7 +125,7 @@ VPNAlert { } }, State { - name: VPN.LogoutAlert + name: VPNErrorHandler.LogoutAlert PropertyChanges { target: alertBox alertType: alertTypes.success diff --git a/nebula/ui/components/VPNViewBase.qml b/nebula/ui/components/VPNViewBase.qml index a8747ca75de..71ce9d1107a 100644 --- a/nebula/ui/components/VPNViewBase.qml +++ b/nebula/ui/components/VPNViewBase.qml @@ -20,7 +20,7 @@ Item { anchors { top: if (parent) parent.top - topMargin: _menuTitle !== "" || titleComponent || rightMenuButton ? VPNTheme.theme.menuHeight : 0 + topMargin: menu.visible ? VPNTheme.theme.menuHeight : 0 } Rectangle { diff --git a/nebula/ui/components/forms/VPNPasswordInput.qml b/nebula/ui/components/forms/VPNPasswordInput.qml index c24a9524ec4..da0df946a84 100644 --- a/nebula/ui/components/forms/VPNPasswordInput.qml +++ b/nebula/ui/components/forms/VPNPasswordInput.qml @@ -15,6 +15,14 @@ VPNTextField { property alias placeholder: passwordInput._placeholderText id: passwordInput + + Accessible.ignored: charactersMasked + Accessible.passwordEdit: charactersMasked + // In order for screen readers to respect the `Accessible.ignored` property + // `Accessible.role` has to be set to `EditableText`. For more infos see + // QTBUG-82433: https://bugreports.qt.io/browse/QTBUG-82433. + Accessible.role: Accessible.EditableText + echoMode: charactersMasked ? TextInput.Password : TextInput.Normal hasError: !isValid height: VPNTheme.theme.rowHeight diff --git a/nebula/ui/components/forms/VPNSearchBar.qml b/nebula/ui/components/forms/VPNSearchBar.qml index 9df3606a454..3bf922280f3 100644 --- a/nebula/ui/components/forms/VPNSearchBar.qml +++ b/nebula/ui/components/forms/VPNSearchBar.qml @@ -16,40 +16,45 @@ ColumnLayout { property var _sortProxyCallback: () => {} property var _editCallback: () => {} property alias _filterProxySource: model.source - property bool _searchBarHasError: false property alias _searchBarPlaceholderText: searchBar._placeholderText + property bool _searchBarHasError: false spacing: VPNTheme.theme.windowMargin / 2 VPNTextField { - // TODO Add strings for Accessible.description, Accessible.name - id: searchBar - background: VPNInputBackground {} - leftInset: 48 - leftPadding: 48 - onActiveFocusChanged: if (focus && vpnFlickable.ensureVisible) vpnFlickable.ensureVisible(searchBar) + Accessible.editable: false + Accessible.searchEdit: true Layout.fillWidth: true - onTextChanged: hasError = _searchBarHasError + + _accessibleName: _placeholderText + background: VPNInputBackground {} + leftInset: VPNTheme.theme.windowMargin * 3 + leftPadding: VPNTheme.theme.windowMargin * 3 + + onActiveFocusChanged: if (focus && vpnFlickable.ensureVisible) { + vpnFlickable.ensureVisible(searchBar); + } onLengthChanged: text => model.invalidate() + onTextChanged: { + hasError = _searchBarHasError; + if (focus) { + _editCallback(); + } + } VPNIcon { + anchors { + left: parent.left + leftMargin: VPNTheme.theme.hSpacing + verticalCenter: parent.verticalCenter + } source: "qrc:/nebula/resources/search.svg" - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 20 sourceSize.height: VPNTheme.theme.windowMargin sourceSize.width: VPNTheme.theme.windowMargin opacity: parent.focus ? 1 : 0.8 } - - - Keys.onPressed: event => { - if (focus && _searchBarHasError && (/[\w\[\]`!@#$%\^&*()={}:;<>+'-]/).test(event.text)) { - _editCallback(); - } - } } VPNContextualAlerts { @@ -73,7 +78,7 @@ ColumnLayout { } function getProxyModel() { - return model + return model; } function getSearchBarText() { @@ -81,6 +86,6 @@ ColumnLayout { } function clearText() { - searchBar.text = "" + searchBar.text = ""; } } diff --git a/nebula/ui/components/forms/VPNTextArea.qml b/nebula/ui/components/forms/VPNTextArea.qml index 1abb7e81a75..18dc55729a4 100644 --- a/nebula/ui/components/forms/VPNTextArea.qml +++ b/nebula/ui/components/forms/VPNTextArea.qml @@ -17,20 +17,22 @@ Item { id: root - Layout.preferredHeight: VPNTheme.theme.rowHeight * 3 - Layout.preferredWidth: parent.width Layout.maximumHeight: VPNTheme.theme.rowHeight * 3 Layout.minimumHeight: VPNTheme.theme.rowHeight * 3 + Layout.preferredHeight: VPNTheme.theme.rowHeight * 3 + Layout.preferredWidth: parent.width Flickable { id: flickable anchors.fill: parent - contentWidth: width contentHeight: textArea.implicitHeight + contentWidth: width ScrollBar.vertical: ScrollBar { - policy: flickable.contentHeight > root.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + policy: flickable.contentHeight > root.height + ? ScrollBar.AlwaysOn + : ScrollBar.AlwaysOff Accessible.ignored: true } @@ -42,36 +44,52 @@ Item { property bool showInteractionStates: true id: textArea + + Accessible.focused: textArea.focus + background: VPNInputBackground { + itemToFocus: textArea + z: -1 + } + bottomPadding: VPNTheme.theme.windowMargin clip: true - textFormat: Text.PlainText - font.pixelSize: VPNTheme.theme.fontSizeSmall - font.family: VPNTheme.theme.fontInterFamily color: VPNTheme.colors.input.default.text - wrapMode: Text.WrapAtWordBoundaryOrAnywhere + cursorDelegate: VPNCursorDelegate {} + enabled: root.enabled + font.family: VPNTheme.theme.fontInterFamily + font.pixelSize: VPNTheme.theme.fontSizeSmall + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhSensitiveData leftPadding: VPNTheme.theme.windowMargin rightPadding: VPNTheme.theme.windowMargin - bottomPadding: VPNTheme.theme.windowMargin - topPadding: VPNTheme.theme.windowMargin - Keys.onTabPressed: nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason) - onTextChanged: handleOnTextChanged(text) selectByMouse: true selectionColor: VPNTheme.theme.input.highlight - inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhSensitiveData - enabled: root.enabled - background: VPNInputBackground { - itemToFocus: textArea - z: -1 - } + textFormat: Text.PlainText + topPadding: VPNTheme.theme.windowMargin + wrapMode: Text.WrapAtWordBoundaryOrAnywhere - cursorDelegate: VPNCursorDelegate {} + Keys.onTabPressed: nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason) + onTextChanged: { + handleOnTextChanged(textArea.text); + + // This is a workaround to make VoiceOver on macOS work. + // After gaining initial focus or typing in TextArea the screen reader + // fails to narrate any accessible content and action. After regaining + // active focus the screen reader keeps working as expected. + textArea.focus = false; + textArea.forceActiveFocus(); + } VPNTextBlock { id: formattedPlaceholderText - anchors.fill: textArea - anchors.leftMargin: VPNTheme.theme.windowMargin - anchors.rightMargin: VPNTheme.theme.windowMargin - anchors.topMargin: VPNTheme.theme.windowMargin - color: textAreaStates.state === "emptyHovered" ? VPNTheme.colors.input.hover.placeholder : VPNTheme.colors.input.default.placeholder + + anchors { + fill: textArea + leftMargin: VPNTheme.theme.windowMargin + rightMargin: VPNTheme.theme.windowMargin + topMargin: VPNTheme.theme.windowMargin + } + color: textAreaStates.state === "emptyHovered" + ? VPNTheme.colors.input.hover.placeholder + : VPNTheme.colors.input.default.placeholder visible: textArea.text.length < 1 PropertyAnimation on opacity { @@ -99,7 +117,11 @@ Item { // Remember cursor position const prevCursorPosition = textArea.cursorPosition - textInputLength; // Strip overflowing chars - const strippedString = removeCharsInRange(text, prevCursorPosition, prevCursorPosition + textInputLength); + const strippedString = removeCharsInRange( + text, + prevCursorPosition, + prevCursorPosition + textInputLength + ); textArea.text = strippedString; // Restore previous cursor position textArea.cursorPosition = prevCursorPosition; @@ -109,13 +131,15 @@ Item { } Text { - anchors.top: parent.bottom - anchors.topMargin: 10 - text: textArea.length + " / " + textArea.maxCharacterCount - font.pixelSize: 13 - anchors.rightMargin: 8 - anchors.right: parent.right + anchors { + top: parent.bottom + topMargin: VPNTheme.theme.listSpacing * 1.25 + right: parent.right + rightMargin: VPNTheme.theme.listSpacing + } color: VPNTheme.theme.fontColor + font.pixelSize: VPNTheme.theme.fontSizeSmall + text: textArea.length + " / " + textArea.maxCharacterCount } } diff --git a/nebula/ui/components/forms/VPNTextField.qml b/nebula/ui/components/forms/VPNTextField.qml index 05f5bcdea0f..ef057d85279 100644 --- a/nebula/ui/components/forms/VPNTextField.qml +++ b/nebula/ui/components/forms/VPNTextField.qml @@ -10,50 +10,69 @@ import Mozilla.VPN 1.0 import components 0.1 TextField { - // TODO Add strings for Accessible.description, Accessible.name property bool hasError: false property bool showInteractionStates: true property bool forceBlurOnOutsidePress: true property alias _placeholderText: centeredPlaceholderText.text + property string _accessibleName: _placeholderText + property string _accessibleDescription: "" id: textField + Accessible.name: _accessibleName + Accessible.description: _accessibleDescription + Accessible.focused: textField.focus + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: VPNTheme.theme.rowHeight + background: VPNInputBackground { id: textFieldBackground } - - font.pixelSize: VPNTheme.theme.fontSizeSmall - font.family: VPNTheme.theme.fontInterFamily + bottomPadding: VPNTheme.theme.windowMargin / 2 color: VPNTheme.colors.input.default.text + cursorDelegate: VPNCursorDelegate {} echoMode: TextInput.Normal + focus: true + font.family: VPNTheme.theme.fontInterFamily + font.pixelSize: VPNTheme.theme.fontSizeSmall inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhSensitiveData - onActiveFocusChanged: if (focus && typeof(vpnFlickable) !== "undefined" && vpnFlickable.ensureVisible) vpnFlickable.ensureVisible(textField) - selectByMouse: true - Layout.preferredHeight: VPNTheme.theme.rowHeight - Layout.alignment: Qt.AlignVCenter - verticalAlignment: TextInput.AlignVCenter - placeholderTextColor: VPNTheme.colors.grey40 leftPadding: VPNTheme.theme.windowMargin + placeholderTextColor: VPNTheme.colors.grey40 rightPadding: VPNTheme.theme.windowMargin + selectByMouse: true topPadding: VPNTheme.theme.windowMargin / 2 - bottomPadding: VPNTheme.theme.windowMargin / 2 - focus: true - cursorDelegate: VPNCursorDelegate {} + verticalAlignment: TextInput.AlignVCenter + + onActiveFocusChanged: if (focus && typeof(vpnFlickable) !== "undefined" && vpnFlickable.ensureVisible) { + vpnFlickable.ensureVisible(textField); + } + // This is a workaround to make VoiceOver on macOS work. + // After gaining initial focus or typing in TextField the screen reader + // fails to narrate any accessible content and action. After regaining + // active focus the screen reader keeps working as expected. + onTextChanged: { + textField.focus = false; + textField.forceActiveFocus(); + } Text { id: centeredPlaceholderText + + Accessible.ignored: true + + color: textField.placeholderTextColor + elide: Text.ElideRight + font: textField.font + height: VPNTheme.theme.rowHeight verticalAlignment: Text.AlignVCenter + visible: !textField.length && !textField.preeditText width: textField.width - (textField.leftPadding + textField.rightPadding) - height: VPNTheme.theme.rowHeight - elide: Text.ElideRight x: textField.leftPadding - visible: !textField.length && !textField.preeditText - font: textField.font - color: textField.placeholderTextColor } VPNInputStates { id: textFieldState + itemToTarget: textField } diff --git a/nebula/ui/themes/themes.js b/nebula/ui/themes/themes.js index df754e81444..c142e0467d7 100644 --- a/nebula/ui/themes/themes.js +++ b/nebula/ui/themes/themes.js @@ -203,6 +203,15 @@ theme.redButton = { 'focusBorder': theme.redPressed, }; +theme.redLinkButton = { + 'defaultColor': theme.redHovered, + 'buttonHovered': theme.redPressed, + 'buttonPressed': theme.redPressed, + 'buttonDisabled': theme.redDisabled, + 'focusOutline': theme.redfocusOutline, + 'focusBorder': theme.redPressed, +}; + theme.removeDeviceBtn = { 'defaultColor': theme.bgColorTransparent, 'buttonHovered': '#FFDFE7', diff --git a/scripts/README.md b/scripts/README.md index 1dcee6c17ee..03fbc817a6b 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -32,10 +32,8 @@ TODO: unify build.sh and script.sh (or remove build.sh) - ./macos/utils/xcode_patcher.rb - tool to patch xcode project - ./macos/utils/commons.sh - common functions for cross-platform scrips - ./macos/import_pkg_resources.py - configure resources for the PKG generation -- ./macos/scope_only_change.sh - runs tests when needed TODO: -1. scope_only_change.sh should be croll-platform! Do we actually use it? 2. remove/merge/move macos_build.sh # Windows-specific scripts @@ -54,7 +52,6 @@ TODO: - ./utils/generate_strings.py - process the string.yaml file and generate resources - ./utils/import_languages.py - process languages and import them - ./utils/inspector.py - send commands to the VPN client inspector -- ./utils/qt5_compile.sh - compile qt5 for linux and macos - ./utils/qt6_compile.sh - compile qt6 for linux and macos # Clang-format utils diff --git a/scripts/linux/ppa_script.sh b/scripts/linux/ppa_script.sh index 1c4644c8217..e04181bfdff 100755 --- a/scripts/linux/ppa_script.sh +++ b/scripts/linux/ppa_script.sh @@ -76,7 +76,7 @@ while [[ $# -gt 0 ]]; do esac done -[ "$RELEASE" != "focal" ] && [ "$RELEASE" != "bionic" ] && [ "$RELEASE" != "jammy" ] && die "We support RELEASE focal, jammy and bionic only" +[ "$RELEASE" != "focal" ] && [ "$RELEASE" != "bionic" ] && [ "$RELEASE" != "jammy" ] && [ "$RELEASE" != "kinetic" ] && die "We support RELEASE focal, jammy, kinetic and bionic only" printn Y "Computing the version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) diff --git a/scripts/linux/script.sh b/scripts/linux/script.sh index a6434d1f426..97a75c0b653 100755 --- a/scripts/linux/script.sh +++ b/scripts/linux/script.sh @@ -4,12 +4,13 @@ # 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/. +set -e + . $(dirname $0)/../utils/commons.sh REVISION=1 RELEASE= GITREF= -QTVERSION="qt6" SOURCEONLY=N PPA_URL= DPKG_SIGN="--no-sign" @@ -27,9 +28,6 @@ helpFunction() { print N " -r, --release DIST Build packages for distribution DIST" print N " -g, --gitref REF Generated version suffix from REF" print N " -v, --version REV Set package revision to REV" - print N " --beineri Build using Stephan Binner's Qt5.15 PPA" - print N " --qt5 Build using Qt5 packages" - print N " --qt6 Build using Qt6 packages (default)" print N " --source Build source packages only (no binary)" print N " --ppa URL Upload source packages to PPA at URL (implies: --source)" print N "" @@ -66,20 +64,8 @@ while [[ $# -gt 0 ]]; do shift shift ;; - --beineri) - QTVERSION="beineri" - shift - ;; - --qt5) - QTVERSION="qt5" - shift - ;; - --qt6) - QTVERSION="qt6" - shift - ;; --source) - RELEASE="bionic focal jammy fedora" + RELEASE="bionic focal jammy fedora kinetic" SOURCEONLY=Y shift ;; @@ -208,11 +194,6 @@ build_deb_source() { rm -rf $WORKDIR/debian || die "Failed" cp -r ../linux/debian $WORKDIR || die "Failed" - mv $WORKDIR/debian/rules.$QTVERSION $WORKDIR/debian/rules - mv $WORKDIR/debian/control.$QTVERSION $WORKDIR/debian/control - rm $WORKDIR/debian/control.* - rm $WORKDIR/debian/rules.* - mv $WORKDIR/debian/changelog.template $WORKDIR/debian/changelog || die "Failed" sed -i -e "s/SHORTVERSION/$SHORTVERSION/g" $WORKDIR/debian/changelog || die "Failed" sed -i -e "s/VERSION/$buildrev/g" $WORKDIR/debian/changelog || die "Failed" diff --git a/scripts/macos/apple_compile.sh b/scripts/macos/apple_compile.sh index 0a6894f98e7..b99afbb09d6 100755 --- a/scripts/macos/apple_compile.sh +++ b/scripts/macos/apple_compile.sh @@ -95,7 +95,7 @@ export PATH="$QT_BIN:$PATH" if [[ "$OS" == "ios" ]]; then printn Y "Retrieve the wireguard-go version... " - (cd ios/gobridge && go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p') > ios/gobridge/wireguard-go-version.h + (cd 3rdparty/wireguard-apple/Sources/WireGuardKitGo/ && go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p') > 3rdparty/wireguard-apple/Sources/WireGuardKitGo/wireguard-go-version.h print G "done." fi diff --git a/scripts/macos/scope_only_change.sh b/scripts/macos/scope_only_change.sh deleted file mode 100644 index f585ca44fd8..00000000000 --- a/scripts/macos/scope_only_change.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -# 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/. - -lottie="$(git diff lottie/ | wc -l)" -qml_src="$(git diff src/ui | wc -l)" -qml_neb="$(git diff nebula/ui | wc -l)" -cpp="$(git diff src/ | wc -l)" -changeCount=$(($lottie + $qml_neb + $qml_src + $cpp)) - -if [ $lottie -gt 0 ]; then - echo "number of changes in lottie code: $lottie" - echo "running lottie unit tests" - ./scripts/tests/lottie_tests.sh -fi - -if [ $cpp -gt 0 ]; then - echo "number of changes in Main C++ code: $cpp" - echo "running unit tests" - ./scripts/tests/lottie_tests.sh -fi - -if [[ $qml_src -gt 0 || $qml_neb -gt 0 ]]; then - echo "number of changes in qml src code: $qml_src" - echo "number of changes in qml nebula code: $qml_neb" - echo "run qml tests" - ./scripts/tests/qml_tests.sh -fi - -if [ $changeCount -lt 1 ]; then - echo "There are no changes in the src, nebula or qml ui" -fi diff --git a/scripts/macos/utils/xcode_patcher.rb b/scripts/macos/utils/xcode_patcher.rb index a80435c7dab..3b0fe19bdba 100644 --- a/scripts/macos/utils/xcode_patcher.rb +++ b/scripts/macos/utils/xcode_patcher.rb @@ -52,7 +52,9 @@ def setup_target_main(configHash, adjust_sdk_token) config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= [ "$(inherited)", - "$(PROJECT_DIR)/3rdparty" + "$(PROJECT_DIR)/3rdparty", + "$(PROJECT_DIR)/3rdparty/wireguard-apple/Sources/WireGuardKitC", + "$(PROJECT_DIR)/3rdparty/wireguard-apple/Sources/WireGuardKitGo", ] config.build_settings['PRODUCT_NAME'] = 'Mozilla VPN' @@ -73,7 +75,7 @@ def setup_target_main(configHash, adjust_sdk_token) group = @project.main_group.new_group('WireGuard') [ - 'ios/gobridge/wireguard-go-version.h', + '3rdparty/wireguard-apple/Sources/WireGuardKitGo/wireguard-go-version.h', '3rdparty/wireguard-apple/Sources/Shared/Keychain.swift', '3rdparty/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift', '3rdparty/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift', @@ -197,7 +199,7 @@ def setup_target_main(configHash, adjust_sdk_token) end def setup_target_extension(shortVersion, fullVersion, configHash) - @target_extension = @project.new_target(:app_extension, 'WireGuardNetworkExtension', :ios) + @target_extension = @project.new_target(:app_extension, 'MozillaVPNNetworkExtension', :ios) @target_extension.build_configurations.each do |config| config.base_configuration_reference = @configFile @@ -213,7 +215,7 @@ def setup_target_extension(shortVersion, fullVersion, configHash) config.build_settings['MARKETING_VERSION'] ||= shortVersion config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_IOS'] - config.build_settings['PRODUCT_NAME'] = 'WireGuardNetworkExtension' + config.build_settings['PRODUCT_NAME'] = 'MozillaVPNNetworkExtension' # other configs config.build_settings['INFOPLIST_FILE'] ||= 'ios/networkextension/Info.plist' @@ -223,6 +225,10 @@ def setup_target_extension(shortVersion, fullVersion, configHash) config.build_settings['ENABLE_BITCODE'] ||= 'NO' config.build_settings['SDKROOT'] = 'iphoneos' + config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= [ + "$(PROJECT_DIR)/3rdparty/wireguard-apple/Sources/WireGuardKitC", + "$(PROJECT_DIR)/3rdparty/wireguard-apple/Sources/WireGuardKitGo", + ] config.build_settings['OTHER_LDFLAGS'] ||= [ "-stdlib=libc++", "-Wl,-rpath,@executable_path/Frameworks", @@ -313,7 +319,7 @@ def setup_target_extension(shortVersion, fullVersion, configHash) def setup_target_gobridge target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) - target_gobridge.build_working_directory = 'ios/gobridge' + target_gobridge.build_working_directory = '3rdparty/wireguard-apple/Sources/WireGuardKitGo' target_gobridge.build_tool_path = 'make' target_gobridge.pass_build_settings_in_environment = '1' target_gobridge.build_arguments_string = '$(ACTION)' diff --git a/scripts/tests/functional_tests.sh b/scripts/tests/functional_tests.sh index 5525d44c2b9..27d52302f6f 100755 --- a/scripts/tests/functional_tests.sh +++ b/scripts/tests/functional_tests.sh @@ -30,4 +30,3 @@ else runTest "$i" done fi - diff --git a/scripts/utils/commons.sh b/scripts/utils/commons.sh index 1803bde1ed4..d8935100ca3 100755 --- a/scripts/utils/commons.sh +++ b/scripts/utils/commons.sh @@ -52,4 +52,3 @@ die() { exit 1 } - diff --git a/scripts/utils/import_languages.py b/scripts/utils/import_languages.py index 4a6b2e5b7cf..6c8922a2739 100755 --- a/scripts/utils/import_languages.py +++ b/scripts/utils/import_languages.py @@ -35,7 +35,7 @@ def title(text): print(f"\033[96m\033[1mStep {stepnum}\033[0m: \033[97m{text}\033[0m") stepnum = stepnum+1 -# Step 0 +# Step 1 title("Find the Qt localization tools...") def qtquery(qmake, propname): try: @@ -52,10 +52,6 @@ def qtquery(qmake, propname): qtbinpath = qtquery('qmake', 'QT_INSTALL_BINS') if qtbinpath is None: qtbinpath = qtquery('qmake6', 'QT_INSTALL_BINS') -if qtbinpath is None: - qtbinpath = qtquery('qmake5', 'QT_INSTALL_BINS') -if qtbinpath is None: - qtbinpath = qtquery('qmake-qt5', 'QT_INSTALL_BINS') if qtbinpath is None: print('Unable to locate qmake tool.') sys.exit(1) @@ -68,12 +64,12 @@ def qtquery(qmake, propname): lconvert = os.path.join(qtbinpath, 'lconvert') lrelease = os.path.join(qtbinpath, 'lrelease') -# Step 0 +# Step 1 (continued) # Let's update the i18n repo os.system(f"git submodule init") os.system(f"git submodule update --remote --depth 1 i18n") -# Step 1 +# Step 2 # Go through the i18n repo, check each XLIFF file and take # note which locale is complete above the minimum threshold. # Adds path of .xliff and .ts to l10n_files. @@ -126,7 +122,7 @@ def qtquery(qmake, propname): 'xliff': xliff_path }) -# Step 2 +# Step 3 title("Create folders and localization files for the languages...") for file in l10n_files: locdirectory = os.path.join('translations', 'generated', file['locale']) @@ -156,7 +152,7 @@ def qtquery(qmake, propname): macospri.write(f"LANGUAGES_FILES_{file['locale']}.path = Contents/Resources/{file['locale']}.lproj\n") macospri.write(f"QMAKE_BUNDLE_DATA += LANGUAGES_FILES_{file['locale']}\n\n") -# Step 3 +# Step 4 title("Write resource file to import the locales that are ready...") with open('translations/generated/translations.qrc', 'w') as qrcfile: qrcfile.write('\n') @@ -167,7 +163,7 @@ def qtquery(qmake, propname): qrcfile.write(' \n') qrcfile.write('\n') -# Step 4 +# Step 5 title("Generate the Js/C++ string definitions...") try: subprocess.call([sys.executable, os.path.join('scripts', 'utils', 'generate_strings.py'), @@ -178,6 +174,7 @@ def qtquery(qmake, propname): print(e) exit(1) +# Step 6 # Build a dummy project to glob together everything that might contain strings. title("Scanning for new strings...") def scan_sources(projfile, dirpath): @@ -197,7 +194,7 @@ def scan_sources(projfile, dirpath): scan_sources(dummyproj, '../../src') scan_sources(dummyproj, '../../nebula') -# Step 5 +# Step 7 title("Generate translation resources...") for l10n_file in l10n_files: os.system(f"{lconvert} -if xlf -i {l10n_file['xliff']} -o {l10n_file['ts']}") diff --git a/scripts/utils/qt5_compile.sh b/scripts/utils/qt5_compile.sh deleted file mode 100755 index 35af03c246f..00000000000 --- a/scripts/utils/qt5_compile.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash - -# 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/. - -. $(dirname $0)/commons.sh - -POSITIONAL=() -JOBS=8 - -helpFunction() { - print G "Usage:" - print N "\t$0 [-j|--jobs ] [anything else will be use as argument for the QT configure script]" - print N "" - exit 0 -} - -print N "This script compiles Qt5 statically" -print N "" - -while [[ $# -gt 0 ]]; do - key="$1" - - case $key in - -j | --jobs) - JOBS="$2" - shift - shift - ;; - -h | --help) - helpFunction - ;; - *) - POSITIONAL+=("$1") - shift - ;; - esac -done - -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [[ $# -lt 2 ]]; then - helpFunction -fi - -[ -d "$1" ] || die "Unable to find the QT source folder." - -cd "$1" || die "Unable to enter into the QT source folder" - -shift - -PREFIX=$1 -shift - -printn Y "Cleaning the folder... " -make distclean -j $JOBS &>/dev/null; -print G "done." - -LINUX=" - -platform linux-clang \ - -egl \ - -opengl es2 \ - -no-linuxfb \ - -bundled-xcb-xinput \ - -xcb \ -" - -MACOS=" - -debug-and-release \ - -appstore-compliant \ - -no-feature-qdbus \ - -no-speechd -" - -if [[ "$OSTYPE" == "linux-gnu"* ]]; then - print N "Configure for linux" - PLATFORM=$LINUX -elif [[ "$OSTYPE" == "darwin"* ]]; then - print N "Configure for darwin" - PLATFORM=$MACOS -else - die "Unsupported platform (yet?)" -fi - -print Y "Wait..." -./configure \ - $* \ - --prefix=$PREFIX \ - --recheck-all \ - -opensource \ - -confirm-license \ - -static \ - -strip \ - -silent \ - -no-compile-examples \ - -nomake tests \ - -make libs \ - -no-sql-psql \ - -sql-sqlite \ - -skip qt3d \ - -skip webengine \ - -skip qtmultimedia \ - -skip qtserialport \ - -skip qtsensors \ - -skip qtgamepad \ - -skip qtwebchannel \ - -skip qtandroidextras \ - -feature-imageformat_png \ - -qt-doubleconversion \ - -qt-libpng \ - -qt-zlib \ - -qt-pcre \ - -qt-freetype \ - $PLATFORM || die "Configuration error." - -print Y "Compiling..." -make -j $JOBS || die "Make failed" - -print Y "Installing..." -make -j $JOBS install || die "Make install failed" - -print G "All done!" diff --git a/src/addons/addon.cpp b/src/addons/addon.cpp index da6659c8c9c..67c04148437 100644 --- a/src/addons/addon.cpp +++ b/src/addons/addon.cpp @@ -52,7 +52,8 @@ struct ConditionCallback { QList s_conditionCallbacks{ {"enabled_features", [](const QJsonValue& value) -> bool { - for (const QJsonValue& enabledFeature : value.toArray()) { + const QJsonArray enabledFeatures = value.toArray(); + for (const QJsonValue& enabledFeature : enabledFeatures) { QString featureName = enabledFeature.toString(); // If the feature doesn't exist, we crash. @@ -68,7 +69,8 @@ QList s_conditionCallbacks{ }, [](Addon* addon, const QJsonValue& value) -> AddonConditionWatcher* { QStringList features; - for (const QJsonValue& v : value.toArray()) { + QJsonArray featureArray = value.toArray(); + for (const QJsonValue& v : featureArray) { features.append(v.toString()); } @@ -79,7 +81,8 @@ QList s_conditionCallbacks{ {"platforms", [](const QJsonValue& value) -> bool { QStringList platforms; - for (QJsonValue platform : value.toArray()) { + QJsonArray platformArray = value.toArray(); + for (const QJsonValue& platform : platformArray) { platforms.append(platform.toString()); } @@ -96,7 +99,8 @@ QList s_conditionCallbacks{ {"settings", [](const QJsonValue& value) -> bool { - for (QJsonValue setting : value.toArray()) { + QJsonArray settings = value.toArray(); + for (const QJsonValue& setting : settings) { QJsonObject obj = setting.toObject(); QString op = obj["op"].toString(); @@ -216,7 +220,8 @@ QList s_conditionCallbacks{ }, [](Addon* addon, const QJsonValue& value) -> AddonConditionWatcher* { QStringList locales; - for (const QJsonValue& v : value.toArray()) { + QJsonArray localeArray = value.toArray(); + for (const QJsonValue& v : localeArray) { locales.append(v.toString().toLower()); } @@ -354,7 +359,8 @@ Addon* Addon::create(QObject* parent, const QString& manifestFileName) { addon->maybeCreateConditionWatchers(conditions); - if (addon->enabled()) { + if (!addon->m_conditionWatcher || + addon->m_conditionWatcher->conditionApplied()) { addon->enable(); } @@ -389,10 +395,7 @@ Addon::Addon(QObject* parent, const QString& manifestFileName, } } -Addon::~Addon() { - MVPN_COUNT_DTOR(Addon); - disable(); -} +Addon::~Addon() { MVPN_COUNT_DTOR(Addon); } void Addon::updateAddonState(State newState) { Q_ASSERT(newState != Unknown); @@ -469,10 +472,12 @@ void Addon::maybeCreateConditionWatchers(const QJsonObject& conditions) { connect(m_conditionWatcher, &AddonConditionWatcher::conditionChanged, this, [this](bool enabled) { - if (enabled) { - enable(); - } else { - disable(); + if (enabled != m_enabled) { + if (enabled) { + enable(); + } else { + disable(); + } } }); } @@ -500,15 +505,9 @@ bool Addon::evaluateConditions(const QJsonObject& conditions) { return true; } -bool Addon::enabled() const { - if (!m_conditionWatcher) { - return true; - } - - return m_conditionWatcher->conditionApplied(); -} - void Addon::enable() { + m_enabled = true; + QCoreApplication::installTranslator(&m_translator); retranslate(); @@ -529,6 +528,8 @@ void Addon::enable() { } void Addon::disable() { + m_enabled = false; + QCoreApplication::removeTranslator(&m_translator); if (m_jsDisableFunction.isCallable()) { diff --git a/src/addons/addon.h b/src/addons/addon.h index 8d274e2f500..f47516d6cde 100644 --- a/src/addons/addon.h +++ b/src/addons/addon.h @@ -55,10 +55,13 @@ class Addon : public QObject { virtual void retranslate(); - virtual bool enabled() const; + virtual bool enabled() const { return m_enabled; } AddonApi* api(); + virtual void enable(); + virtual void disable(); + signals: void conditionChanged(bool enabled); void retranslationCompleted(); @@ -67,9 +70,6 @@ class Addon : public QObject { Addon(QObject* parent, const QString& manifestFileName, const QString& id, const QString& name, const QString& type); - virtual void enable(); - virtual void disable(); - private: void updateAddonState(State newState); @@ -100,6 +100,8 @@ class Addon : public QObject { QJSValue m_jsEnableFunction; QJSValue m_jsDisableFunction; + + bool m_enabled = false; }; #endif // ADDON_H diff --git a/src/addons/addonmessage.cpp b/src/addons/addonmessage.cpp index 014bca956b5..acdfdf927e9 100644 --- a/src/addons/addonmessage.cpp +++ b/src/addons/addonmessage.cpp @@ -66,6 +66,10 @@ Addon* AddonMessage::create(QObject* parent, const QString& manifestFileName, message->setBadge(messageObj["badge"].toString()); guard.dismiss(); + + connect(message, &Addon::retranslationCompleted, message->m_composer, + &Composer::retranslationCompleted); + return message; } @@ -73,9 +77,6 @@ AddonMessage::AddonMessage(QObject* parent, const QString& manifestFileName, const QString& id, const QString& name) : Addon(parent, manifestFileName, id, name, "message") { MVPN_COUNT_CTOR(AddonMessage); - - connect(this, &Addon::retranslationCompleted, m_composer, - &Composer::retranslationCompleted); } AddonMessage::~AddonMessage() { MVPN_COUNT_DTOR(AddonMessage); } diff --git a/src/addons/addonmessage.h b/src/addons/addonmessage.h index 1b75f7af74f..a7b27f30c7d 100644 --- a/src/addons/addonmessage.h +++ b/src/addons/addonmessage.h @@ -81,7 +81,7 @@ class AddonMessage final : public Addon { const QDateTime& messageDateTime); signals: - void stateChanged(MessageState state); + void stateChanged(AddonMessage::MessageState state); void badgeChanged(); void dateChanged(); diff --git a/src/addons/addontutorial.cpp b/src/addons/addontutorial.cpp index bc475a52c2f..5706e08d726 100644 --- a/src/addons/addontutorial.cpp +++ b/src/addons/addontutorial.cpp @@ -75,7 +75,8 @@ Addon* AddonTutorial::create(QObject* parent, const QString& manifestFileName, return nullptr; } - for (QJsonValue stepValue : stepsArray.toArray()) { + const QJsonArray steps = stepsArray.toArray(); + for (QJsonValue stepValue : steps) { if (!stepValue.isObject()) { logger.warning() << "Expected JSON tutorialObjects as steps for tutorial"; return nullptr; diff --git a/src/addons/manager/addonindex.cpp b/src/addons/manager/addonindex.cpp index 5248ec5f90b..cb246747944 100644 --- a/src/addons/manager/addonindex.cpp +++ b/src/addons/manager/addonindex.cpp @@ -186,7 +186,8 @@ bool AddonIndex::validateIndex(const QByteArray& index, QJsonObject* indexObj) { return false; } - for (const QJsonValue item : obj["addons"].toArray()) { + const QJsonArray addons = obj["addons"].toArray(); + for (const QJsonValue& item : addons) { if (!validateSingleAddonIndex(item)) { return false; } @@ -252,7 +253,8 @@ bool AddonIndex::validateIndexSignature(const QByteArray& index, QList AddonIndex::extractAddonsFromIndex( const QJsonObject& indexObj) { QList addons; - for (const QJsonValue item : indexObj["addons"].toArray()) { + const QJsonArray addonArray = indexObj["addons"].toArray(); + for (const QJsonValue& item : addonArray) { QJsonObject addon = item.toObject(); addons.append( {QByteArray::fromHex(addon["sha256"].toString().toLocal8Bit()), diff --git a/src/addons/manager/addonindex.h b/src/addons/manager/addonindex.h index 36f9ff28b04..e98d183e8f1 100644 --- a/src/addons/manager/addonindex.h +++ b/src/addons/manager/addonindex.h @@ -17,7 +17,7 @@ class Addon; constexpr const char* ADDONS_API_VERSION = "0.1"; constexpr const char* ADDON_INDEX_FILENAME = "manifest.json"; -constexpr const char* ADDON_INDEX_SIGNATURE_FILENAME = "manifest.json.sign"; +constexpr const char* ADDON_INDEX_SIGNATURE_FILENAME = "manifest.json.sig"; // This struct can be partially empty in case the sha does not match, or the // addon does not need to be loaded for unmatched conditions. diff --git a/src/addons/manager/addonmanager.cpp b/src/addons/manager/addonmanager.cpp index 5849cb4219a..9b49ee2650d 100644 --- a/src/addons/manager/addonmanager.cpp +++ b/src/addons/manager/addonmanager.cpp @@ -128,18 +128,16 @@ void AddonManager::updateAddonsList(QList addons) { } } - if (!m_loadCompleted) { - if (taskAdded) { - TaskScheduler::scheduleTask(new TaskFunction( - [this]() { - m_loadCompleted = true; - emit loadCompletedChanged(); - }, - Task::Reschedulable)); - } else { - m_loadCompleted = true; - emit loadCompletedChanged(); - } + if (taskAdded) { + TaskScheduler::scheduleTask(new TaskFunction( + [this]() { + m_loadCompleted = true; + emit loadCompletedChanged(); + }, + Task::Reschedulable)); + } else { + m_loadCompleted = true; + emit loadCompletedChanged(); } } @@ -174,11 +172,9 @@ bool AddonManager::loadManifest(const QString& manifestFileName) { } if (!enabled) { beginRemoveRows(QModelIndex(), pos, pos); - removeRow(pos); endRemoveRows(); } else { beginInsertRows(QModelIndex(), pos, pos); - insertRow(pos); endInsertRows(); } break; @@ -201,30 +197,23 @@ void AddonManager::unload(const QString& addonId) { Addon* addon = m_addons[addonId].m_addon; - if (!addon) { - m_addons.remove(addonId); - return; - } - - bool addonEnabled = addon->enabled(); - if (addonEnabled) { - beginResetModel(); - } - - m_addons.remove(addonId); + if (addon) { + if (addon->enabled()) { + addon->disable(); + } - if (addonEnabled) { - endResetModel(); - } + QDir dir; + if (m_addonDirectory.getDirectory(&dir)) { + QString addonFileName(QString("%1.rcc").arg(addonId)); + QString addonFilePath(dir.filePath(addonFileName)); + QResource::unregisterResource(addonFilePath, mountPath(addonId)); + } - QDir dir; - if (m_addonDirectory.getDirectory(&dir)) { - QString addonFileName(QString("%1.rcc").arg(addonId)); - QString addonFilePath(dir.filePath(addonFileName)); - QResource::unregisterResource(addonFilePath, mountPath(addonId)); + addon->deleteLater(); } - addon->deleteLater(); + m_addons.remove(addonId); + emit countChanged(); } void AddonManager::retranslate() { @@ -296,6 +285,7 @@ bool AddonManager::validateAndLoad(const QString& addonId, return false; } + emit countChanged(); return true; } @@ -332,7 +322,9 @@ QHash AddonManager::roleNames() const { return roles; } -int AddonManager::rowCount(const QModelIndex&) const { +int AddonManager::rowCount(const QModelIndex&) const { return count(); } + +int AddonManager::count() const { int count = 0; for (QMap::const_iterator i(m_addons.constBegin()); i != m_addons.constEnd(); ++i) { diff --git a/src/addons/manager/addonmanager.h b/src/addons/manager/addonmanager.h index d67cee24b0a..ac2cb1d723a 100644 --- a/src/addons/manager/addonmanager.h +++ b/src/addons/manager/addonmanager.h @@ -18,6 +18,7 @@ class AddonManager final : public QAbstractListModel { Q_PROPERTY( bool loadCompleted MEMBER m_loadCompleted NOTIFY loadCompletedChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) public: static QString addonServerAddress(); @@ -53,6 +54,8 @@ class AddonManager final : public QAbstractListModel { QStringList addonIds() const; #endif + int count() const; + private: explicit AddonManager(QObject* parent); @@ -76,6 +79,7 @@ class AddonManager final : public QAbstractListModel { signals: void loadCompletedChanged(); + void countChanged(); private: QMap m_addons; diff --git a/src/adjust/adjusthandler.cpp b/src/adjust/adjusthandler.cpp index 672ab034705..c17bd1937d8 100644 --- a/src/adjust/adjusthandler.cpp +++ b/src/adjust/adjusthandler.cpp @@ -42,7 +42,6 @@ void AdjustHandler::initialize() { } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // If the app has not started yet, let's wait. if (vpn->state() == MozillaVPN::StateInitialize) { diff --git a/src/appimageprovider.h b/src/appimageprovider.h index 76a06097637..47d53a862bc 100644 --- a/src/appimageprovider.h +++ b/src/appimageprovider.h @@ -8,25 +8,12 @@ #include #include -// Helper class to support Qt5 and Qt6 -class AppImageProvider : public QQuickImageProvider -#if QT_VERSION < 0x060000 - , - public QObject -#endif -{ +class AppImageProvider : public QQuickImageProvider { public: AppImageProvider(QObject* parent, QQmlImageProviderBase::ImageType type, QQmlImageProviderBase::Flags flags = Flags()) - : QQuickImageProvider(type, flags) -#if QT_VERSION < 0x060000 - , - QObject(parent) -#endif - { -#if QT_VERSION >= 0x060000 + : QQuickImageProvider(type, flags) { setParent(parent); -#endif } virtual ~AppImageProvider() = default; diff --git a/src/apppermission.cpp b/src/apppermission.cpp index 00c712f40eb..ea85aa8233e 100644 --- a/src/apppermission.cpp +++ b/src/apppermission.cpp @@ -107,7 +107,7 @@ void AppPermission::flip(const QString& appID) { settingsHolder->setVpnDisabledApps(applist); int index = m_applist.indexOf(AppDescription(appID)); - dataChanged(createIndex(index, 0), createIndex(index, 0)); + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); } void AppPermission::requestApplist() { @@ -173,7 +173,7 @@ void AppPermission::receiveAppList(const QMap& applist) { beginResetModel(); logger.debug() << "Recived new Applist -- Entrys: " << applistCopy.size(); m_applist.clear(); - for (auto id : keys) { + for (const auto& id : keys) { m_applist.append(AppDescription(id, applistCopy[id])); } std::sort(m_applist.begin(), m_applist.end()); @@ -196,18 +196,18 @@ void AppPermission::protectAll() { logger.debug() << "Protected all"; SettingsHolder::instance()->setVpnDisabledApps(QStringList()); - dataChanged(createIndex(0, 0), createIndex(m_applist.size(), 0)); + emit dataChanged(createIndex(0, 0), createIndex(m_applist.size(), 0)); }; void AppPermission::unprotectAll() { logger.debug() << "Unprotected all"; QStringList allAppIds; - for (auto app : m_applist) { + for (const auto& app : m_applist) { allAppIds.append(app.id); } SettingsHolder::instance()->setVpnDisabledApps(allAppIds); - dataChanged(createIndex(0, 0), createIndex(m_applist.size(), 0)); + emit dataChanged(createIndex(0, 0), createIndex(m_applist.size(), 0)); } void AppPermission::openFilePicker() { diff --git a/src/authenticationinapp/authenticationinapp.cpp b/src/authenticationinapp/authenticationinapp.cpp index 52d38713771..c7c6c6596fa 100644 --- a/src/authenticationinapp/authenticationinapp.cpp +++ b/src/authenticationinapp/authenticationinapp.cpp @@ -251,8 +251,8 @@ bool AuthenticationInApp::validateEmailAddress(const QString& emailAddress) { return false; } - QRegularExpression emailRE("^[A-Z0-9.!#$%&'*+/=?^_`{|}~-]{1,64}$", - QRegularExpression::CaseInsensitiveOption); + static QRegularExpression emailRE("^[A-Z0-9.!#$%&'*+/=?^_`{|}~-]{1,64}$", + QRegularExpression::CaseInsensitiveOption); // We don't have to convert the first part of the email address to ASCII // Compatible Encoding (ace). if (!emailRE.match(parts[0]).hasMatch()) { @@ -260,7 +260,7 @@ bool AuthenticationInApp::validateEmailAddress(const QString& emailAddress) { } QByteArray domainAce = QUrl::toAce(parts[1]); - QRegularExpression domainRE( + static QRegularExpression domainRE( "^[A-Z0-9](?:[A-Z0-9-]{0,253}[A-Z0-9])?(?:.[A-Z0-9](?:[A-Z0-9-]{0,253}[A-" "Z0-9])?)+$", QRegularExpression::CaseInsensitiveOption); diff --git a/src/authenticationinapp/authenticationinapp.h b/src/authenticationinapp/authenticationinapp.h index 2649e0ae834..f1f8c51644a 100644 --- a/src/authenticationinapp/authenticationinapp.h +++ b/src/authenticationinapp/authenticationinapp.h @@ -162,7 +162,7 @@ class AuthenticationInApp final : public QObject { signals: void stateChanged(); - void errorOccurred(ErrorType error, uint32_t retryAfter); + void errorOccurred(AuthenticationInApp::ErrorType error, uint32_t retryAfter); void emailAddressChanged(); void attachedClientsChanged(); diff --git a/src/authenticationinapp/authenticationinappsession.cpp b/src/authenticationinapp/authenticationinappsession.cpp index a20e5346ea0..b479d4ffc7e 100644 --- a/src/authenticationinapp/authenticationinappsession.cpp +++ b/src/authenticationinapp/authenticationinappsession.cpp @@ -521,7 +521,8 @@ void AuthenticationInAppSession::startAccountDeletionFlow() { return; } - for (QJsonValue clientValue : json.array()) { + const QJsonArray clientArray = json.array(); + for (const QJsonValue& clientValue : clientArray) { if (!clientValue.isObject()) { logger.error() << "Attach clients: no client object found"; emit failed(ErrorHandler::AuthenticationError); @@ -705,8 +706,9 @@ void AuthenticationInAppSession::processErrorObject(const QJsonObject& obj) { case 107: { // Invalid parameter in request body QJsonObject objValidation = obj["validation"].toObject(); + const QJsonArray keyArray = objValidation["keys"].toArray(); QStringList keys; - for (QJsonValue key : objValidation["keys"].toArray()) { + for (const QJsonValue& key : keyArray) { if (key.isString()) { keys.append(key.toString()); } @@ -747,7 +749,14 @@ void AuthenticationInAppSession::processErrorObject(const QJsonObject& obj) { } case 114: // Client has sent too many requests - aia->requestState(AuthenticationInApp::StateStart, this); + if (m_typeAuthentication == TypeDefault) { + aia->requestState(AuthenticationInApp::StateStart, this); + } else { + // For non-default authentication flows, we go back to the password + // request, because the email request step is implicit. + aia->requestState(AuthenticationInApp::StateSignIn, this); + } + aia->requestErrorPropagation(this, AuthenticationInApp::ErrorTooManyRequests, obj["retryAfter"].toInt()); diff --git a/src/captiveportal/captiveportal.cpp b/src/captiveportal/captiveportal.cpp index 72ab82a63e3..9d8afef26ac 100644 --- a/src/captiveportal/captiveportal.cpp +++ b/src/captiveportal/captiveportal.cpp @@ -33,7 +33,7 @@ bool CaptivePortal::fromJson(const QByteArray& data) { QStringList ipv6Addresses; QJsonArray array = json.array(); - for (QJsonValue entry : array) { + for (const QJsonValue& entry : array) { if (!entry.isObject()) { logger.error() << "Object expected"; return false; diff --git a/src/captiveportal/captiveportaldetection.cpp b/src/captiveportal/captiveportaldetection.cpp index 20694dd5f55..cb1ee119ffc 100644 --- a/src/captiveportal/captiveportaldetection.cpp +++ b/src/captiveportal/captiveportaldetection.cpp @@ -36,7 +36,6 @@ void CaptivePortalDetection::initialize() { void CaptivePortalDetection::networkChanged() { MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // The captive portal background monitor is looking // wheter a portal on the current network is gone. @@ -64,7 +63,6 @@ void CaptivePortalDetection::stateChanged() { } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Controller::State state = vpn->controller()->state(); if (state == Controller::StateOff) { @@ -111,7 +109,6 @@ void CaptivePortalDetection::detectCaptivePortal() { captivePortalMonitor()->stop(); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // This method is called by the inspector too. Let's check the status of the // VPN. @@ -178,7 +175,6 @@ void CaptivePortalDetection::captivePortalDetected() { Navigator::instance()->requestScreen(Navigator::ScreenCaptivePortal); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->controller()->state() == Controller::StateOn) { captivePortalNotifier()->notifyCaptivePortalBlock(); diff --git a/src/captiveportal/captiveportalrequest.h b/src/captiveportal/captiveportalrequest.h index 91db28202f7..4a593d97871 100644 --- a/src/captiveportal/captiveportalrequest.h +++ b/src/captiveportal/captiveportalrequest.h @@ -24,7 +24,7 @@ class CaptivePortalRequest final : public QObject { void run(); signals: - void completed(CaptivePortalResult detected); + void completed(CaptivePortalRequest::CaptivePortalResult detected); private: void createRequest(const QUrl& url); diff --git a/src/cmake/golang.cmake b/src/cmake/golang.cmake index 923e51370f5..64847a61cf0 100644 --- a/src/cmake/golang.cmake +++ b/src/cmake/golang.cmake @@ -52,11 +52,4 @@ function(add_go_library GOTARGET SOURCE) INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} INTERFACE_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME} IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}) - - if(MSVC) - # prevent error LNK2019: unresolved external symbol fprintf referenced in function ... - set_property(TARGET ${GOTARGET} APPEND PROPERTY - INTERFACE_SOURCES ${CMAKE_SOURCE_DIR}/src/platforms/windows/golang-msvc-fixup.cpp) - endif() - endfunction(add_go_library) diff --git a/src/cmake/sources.cmake b/src/cmake/sources.cmake index 51f37071572..af00f2f8247 100644 --- a/src/cmake/sources.cmake +++ b/src/cmake/sources.cmake @@ -114,13 +114,15 @@ target_sources(mozillavpn PRIVATE composer/composer.h connectionbenchmark/benchmarktask.cpp connectionbenchmark/benchmarktask.h - connectionbenchmark/benchmarktaskdownload.cpp - connectionbenchmark/benchmarktaskdownload.h connectionbenchmark/benchmarktaskping.cpp connectionbenchmark/benchmarktaskping.h connectionbenchmark/benchmarktasksentinel.h + connectionbenchmark/benchmarktasktransfer.cpp + connectionbenchmark/benchmarktasktransfer.h connectionbenchmark/connectionbenchmark.cpp connectionbenchmark/connectionbenchmark.h + connectionbenchmark/uploaddatagenerator.cpp + connectionbenchmark/uploaddatagenerator.h connectionhealth.cpp connectionhealth.h constants.cpp @@ -316,6 +318,8 @@ target_sources(mozillavpn PRIVATE tasks/servers/taskservers.h telemetry.cpp telemetry.h + temporarydir.cpp + temporarydir.h theme.cpp theme.h timersingleshot.cpp diff --git a/src/cmake/windows.cmake b/src/cmake/windows.cmake index 0a73f8fcfd0..35f9dbf2933 100644 --- a/src/cmake/windows.cmake +++ b/src/cmake/windows.cmake @@ -14,7 +14,7 @@ set_target_properties(mozillavpn PROPERTIES # and then we can remove this :) target_compile_options(mozillavpn PRIVATE - $<$:/ZI>> + $<$:/ZI> ) # Generate the Windows version resource file. @@ -87,10 +87,24 @@ endif() include(cmake/golang.cmake) -# Enable Balrog for update support. +# Build the Balrog library as a DLL +add_custom_target(balrogdll ALL + BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/balrog.dll ${CMAKE_CURRENT_BINARY_DIR}/balrog.h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/balrog + COMMAND ${CMAKE_COMMAND} -E env + GOCACHE=${CMAKE_BINARY_DIR}/go-cache + GOOS=windows CGO_ENABLED=1 + CC=gcc + CGO_CFLAGS="-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601" + CGO_LDFLAGS="-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols -Wl,--high-entropy-va" + go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "${CMAKE_CURRENT_BINARY_DIR}/balrog.dll" +) +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) +add_dependencies(mozillavpn balrogdll) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/balrog.dll DESTINATION .) + +# Use Balrog for update support. add_definitions(-DMVPN_BALROG) -add_go_library(balrog ../balrog/balrog-api.go) -target_link_libraries(mozillavpn PRIVATE balrog) target_sources(mozillavpn PRIVATE update/balrog.cpp update/balrog.h diff --git a/src/command.cpp b/src/command.cpp index 18a7f731db7..6cc726f5aa1 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -151,9 +151,6 @@ int Command::runQmlApp(std::function&& a_callback) { // Ensure that external styling hints are disabled. qunsetenv("QT_STYLE_OVERRIDE"); - logger.info() << "MozillaVPN" << Constants::versionString(); - logger.info() << "User-Agent:" << NetworkManager::userAgent(); - #ifdef MVPN_WINDOWS SetProcessDPIAware(); #endif diff --git a/src/commandlineparser.cpp b/src/commandlineparser.cpp index 4037f1cd02d..00cfc9230a4 100644 --- a/src/commandlineparser.cpp +++ b/src/commandlineparser.cpp @@ -82,7 +82,7 @@ int CommandLineParser::parse(int argc, char* argv[]) { QVector commands = Command::commands(this); for (Command* command : commands) { if (command->name() == tokens[0]) { - tokens[0] = QString("%1 %2").arg(argv[0]).arg(tokens[0]); + tokens[0] = QString("%1 %2").arg(argv[0], tokens[0]); return command->run(tokens); } } @@ -189,7 +189,7 @@ void CommandLineParser::showHelp(QObject* parent, const QString& app, stream << Qt::endl; stream << "List of options:" << Qt::endl; for (const Option* o : options) { - QString desc = QString("-%1 | --%2").arg(o->m_short).arg(o->m_long); + QString desc = QString("-%1 | --%2").arg(o->m_short, o->m_long); stream << " " << desc << " "; for (int i = desc.length(); i < 20; ++i) { diff --git a/src/commands/commandactivate.cpp b/src/commands/commandactivate.cpp index 6928e5029c0..c1f78e55500 100644 --- a/src/commands/commandactivate.cpp +++ b/src/commands/commandactivate.cpp @@ -36,7 +36,7 @@ int CommandActivate::run(QStringList& tokens) { } QEventLoop loop; - QObject::connect(vpn.controller(), &Controller::stateChanged, [&] { + QObject::connect(vpn.controller(), &Controller::stateChanged, &vpn, [&] { if (vpn.controller()->state() == Controller::StateOff || vpn.controller()->state() == Controller::StateOn) { loop.exit(); @@ -51,7 +51,7 @@ int CommandActivate::run(QStringList& tokens) { // completed. if (vpn.controller()->state() != Controller::StateOff && vpn.controller()->state() != Controller::StateOn) { - QObject::connect(vpn.controller(), &Controller::stateChanged, [&] { + QObject::connect(vpn.controller(), &Controller::stateChanged, &vpn, [&] { if (vpn.controller()->state() == Controller::StateOff || vpn.controller()->state() == Controller::StateOn) { loop.exit(); @@ -67,7 +67,7 @@ int CommandActivate::run(QStringList& tokens) { return 0; } - QObject::connect(vpn.controller(), &Controller::stateChanged, [&] { + QObject::connect(vpn.controller(), &Controller::stateChanged, &vpn, [&] { if (vpn.controller()->state() == Controller::StateOff || vpn.controller()->state() == Controller::StateOn) { loop.exit(); diff --git a/src/commands/commanddeactivate.cpp b/src/commands/commanddeactivate.cpp index 29a62969467..04611b081bd 100644 --- a/src/commands/commanddeactivate.cpp +++ b/src/commands/commanddeactivate.cpp @@ -38,7 +38,7 @@ int CommandDeactivate::run(QStringList& tokens) { Controller controller; QEventLoop loop; - QObject::connect(&controller, &Controller::stateChanged, [&] { + QObject::connect(&controller, &Controller::stateChanged, &vpn, [&] { if (controller.state() == Controller::StateOff || controller.state() == Controller::StateOn) { loop.exit(); @@ -52,7 +52,7 @@ int CommandDeactivate::run(QStringList& tokens) { // completed. if (controller.state() != Controller::StateOff && controller.state() != Controller::StateOn) { - QObject::connect(&controller, &Controller::stateChanged, [&] { + QObject::connect(&controller, &Controller::stateChanged, &vpn, [&] { if (controller.state() == Controller::StateOff || controller.state() == Controller::StateOn) { loop.exit(); @@ -68,7 +68,7 @@ int CommandDeactivate::run(QStringList& tokens) { return 0; } - QObject::connect(&controller, &Controller::stateChanged, [&] { + QObject::connect(&controller, &Controller::stateChanged, &vpn, [&] { if (controller.state() == Controller::StateOff || controller.state() == Controller::StateOn) { loop.exit(); diff --git a/src/commands/commanddevice.cpp b/src/commands/commanddevice.cpp index 62f968d7aaf..3c493541940 100644 --- a/src/commands/commanddevice.cpp +++ b/src/commands/commanddevice.cpp @@ -69,7 +69,7 @@ int CommandDevice::run(QStringList& tokens) { task.run(); QEventLoop loop; - QObject::connect(&task, &Task::completed, [&] { loop.exit(); }); + QObject::connect(&task, &Task::completed, &task, [&] { loop.exit(); }); loop.exec(); return 0; diff --git a/src/commands/commandlogin.cpp b/src/commands/commandlogin.cpp index 4df649d1d7a..7795bbe9995 100644 --- a/src/commands/commandlogin.cpp +++ b/src/commands/commandlogin.cpp @@ -78,7 +78,7 @@ int CommandLogin::run(QStringList& tokens) { if (passwordOption.m_set) { AuthenticationInApp* aia = AuthenticationInApp::instance(); - QObject::connect(aia, &AuthenticationInApp::stateChanged, [&] { + QObject::connect(aia, &AuthenticationInApp::stateChanged, aia, [&] { switch (AuthenticationInApp::instance()->state()) { case AuthenticationInApp::StateInitializing: break; @@ -201,20 +201,22 @@ int CommandLogin::run(QStringList& tokens) { }); } - QObject::connect(&vpn, &MozillaVPN::stateChanged, [&] { + QObject::connect(&vpn, &MozillaVPN::stateChanged, &vpn, [&] { if (vpn.state() == MozillaVPN::StatePostAuthentication || vpn.state() == MozillaVPN::StateTelemetryPolicy || vpn.state() == MozillaVPN::StateMain) { loop.exit(); } - if (vpn.alert() == MozillaVPN::AuthenticationFailedAlert) { + if (ErrorHandler::instance()->alert() == + ErrorHandler::AuthenticationFailedAlert) { loop.exit(); } }); loop.exec(); - if (vpn.alert() == MozillaVPN::AuthenticationFailedAlert) { + if (ErrorHandler::instance()->alert() == + ErrorHandler::AuthenticationFailedAlert) { QTextStream stream(stdout); stream << "Authentication failed" << Qt::endl; return 1; diff --git a/src/commands/commandlogout.cpp b/src/commands/commandlogout.cpp index 2067c9eccaa..cfbfaf0c05c 100644 --- a/src/commands/commandlogout.cpp +++ b/src/commands/commandlogout.cpp @@ -47,7 +47,7 @@ int CommandLogout::run(QStringList& tokens) { task.run(); QEventLoop loop; - QObject::connect(&task, &Task::completed, [&] { loop.exit(); }); + QObject::connect(&task, &Task::completed, &task, [&] { loop.exit(); }); loop.exec(); } vpn.reset(false); diff --git a/src/commands/commandservers.cpp b/src/commands/commandservers.cpp index 3198cf558b3..c37fd3563f5 100644 --- a/src/commands/commandservers.cpp +++ b/src/commands/commandservers.cpp @@ -58,11 +58,11 @@ int CommandServers::run(QStringList& tokens) { MozillaVPN vpn; if (!cacheOption.m_set) { - TaskServers task; + TaskServers task(ErrorHandler::PropagateError); task.run(); QEventLoop loop; - QObject::connect(&task, &Task::completed, [&] { loop.exit(); }); + QObject::connect(&task, &Task::completed, &task, [&] { loop.exit(); }); loop.exec(); } else if (!loadModels()) { return 0; diff --git a/src/commands/commandstatus.cpp b/src/commands/commandstatus.cpp index 2fc75cd2b1e..4cdb55eff2b 100644 --- a/src/commands/commandstatus.cpp +++ b/src/commands/commandstatus.cpp @@ -62,11 +62,11 @@ int CommandStatus::run(QStringList& tokens) { } if (!cacheOption.m_set) { - TaskAccount task; + TaskAccount task(ErrorHandler::PropagateError); task.run(); QEventLoop loop; - QObject::connect(&task, &Task::completed, [&] { loop.exit(); }); + QObject::connect(&task, &Task::completed, &task, [&] { loop.exit(); }); loop.exec(); } @@ -112,7 +112,7 @@ int CommandStatus::run(QStringList& tokens) { Controller controller; QEventLoop loop; - QObject::connect(&controller, &Controller::stateChanged, [&] { + QObject::connect(&controller, &Controller::stateChanged, &controller, [&] { if (controller.state() == Controller::StateOff || controller.state() == Controller::StateOn) { loop.exit(); diff --git a/src/commands/commandui.cpp b/src/commands/commandui.cpp index 7d2b6daf0ef..013aaf14e00 100644 --- a/src/commands/commandui.cpp +++ b/src/commands/commandui.cpp @@ -26,6 +26,7 @@ #include "qmlengineholder.h" #include "settingsholder.h" #include "telemetry/gleansample.h" +#include "temporarydir.h" #include "theme.h" #include "tutorial/tutorial.h" #include "update/updater.h" @@ -136,6 +137,9 @@ int CommandUI::run(QStringList& tokens) { Constants::setStaging(); } + logger.info() << "MozillaVPN" << Constants::versionString(); + logger.info() << "User-Agent:" << NetworkManager::userAgent(); + logger.debug() << "UI starting"; if (startAtBootOption.m_set) { @@ -195,8 +199,8 @@ int CommandUI::run(QStringList& tokens) { // https://bugreports.qt.io/browse/QTBUG-82617 // Currently there is a crash happening on exit with Huawei devices. // Until this is fixed, setting this variable is the "official" workaround. - // We certainly should look at this once 6.4 is out. -# if QT_VERSION >= 0x060400 + // We certainly should look at this once 6.6 is out. +# if QT_VERSION >= 0x060600 # error We have forgotten to remove this Huawei hack! # endif if (AndroidUtils::GetManufacturer() == "Huawei") { @@ -216,8 +220,16 @@ int CommandUI::run(QStringList& tokens) { Nebula::Initialize(engine); L18nStrings::initialize(); + // Cleanup previous temporary files. + TemporaryDir::cleanupAll(); + MozillaVPN vpn; - vpn.setStartMinimized(minimizedOption.m_set); + + bool minimized = minimizedOption.m_set; + if (qgetenv("MVPN_MINIMIZED") == "1") { + minimized = true; + } + vpn.setStartMinimized(minimized); #ifdef MVPN_ANDROID AndroidGlean::initialize(engine); diff --git a/src/composer/composer.cpp b/src/composer/composer.cpp index 252a2c14597..a776941735f 100644 --- a/src/composer/composer.cpp +++ b/src/composer/composer.cpp @@ -37,7 +37,8 @@ Composer* Composer::create(Addon* addon, const QString& prefix, return nullptr; } - for (QJsonValue blockValue : blocksArray.toArray()) { + const QJsonArray blocks = blocksArray.toArray(); + for (const QJsonValue& blockValue : blocks) { if (!blockValue.isObject()) { logger.warning() << "Expected JSON objects as blocks for composer"; return nullptr; diff --git a/src/composer/composerblockbutton.cpp b/src/composer/composerblockbutton.cpp index 2912bf2da18..9f4a334e223 100644 --- a/src/composer/composerblockbutton.cpp +++ b/src/composer/composerblockbutton.cpp @@ -68,7 +68,7 @@ ComposerBlock* ComposerBlockButton::create(Composer* composer, Addon* addon, ComposerBlockButton* block = new ComposerBlockButton(composer, addon, blockId, style, function); - block->m_text.initialize(QString("%1.block.%2").arg(prefix).arg(blockId), + block->m_text.initialize(QString("%1.block.%2").arg(prefix, blockId), json["content"].toString()); return block; diff --git a/src/composer/composerblocktext.cpp b/src/composer/composerblocktext.cpp index 426238315a1..c04b02f1042 100644 --- a/src/composer/composerblocktext.cpp +++ b/src/composer/composerblocktext.cpp @@ -18,7 +18,7 @@ ComposerBlock* ComposerBlockText::create(Composer* composer, const QString& prefix, const QJsonObject& json) { ComposerBlockText* block = new ComposerBlockText(composer, blockId); - block->m_text.initialize(QString("%1.block.%2").arg(prefix).arg(blockId), + block->m_text.initialize(QString("%1.block.%2").arg(prefix, blockId), json["content"].toString()); return block; } diff --git a/src/composer/composerblocktitle.cpp b/src/composer/composerblocktitle.cpp index 6e968f47f71..7f6ad687719 100644 --- a/src/composer/composerblocktitle.cpp +++ b/src/composer/composerblocktitle.cpp @@ -18,7 +18,7 @@ ComposerBlock* ComposerBlockTitle::create(Composer* composer, const QString& prefix, const QJsonObject& json) { ComposerBlockTitle* block = new ComposerBlockTitle(composer, blockId); - block->m_title.initialize(QString("%1.block.%2").arg(prefix).arg(blockId), + block->m_title.initialize(QString("%1.block.%2").arg(prefix, blockId), json["content"].toString()); return block; } diff --git a/src/composer/composerblockunorderedlist.cpp b/src/composer/composerblockunorderedlist.cpp index fdeb0e2405a..ed44dccfe31 100644 --- a/src/composer/composerblockunorderedlist.cpp +++ b/src/composer/composerblockunorderedlist.cpp @@ -42,7 +42,8 @@ bool ComposerBlockUnorderedList::parseJson(const QString& prefix, return false; } - for (QJsonValue subBlockValue : subBlockArray.toArray()) { + const QJsonArray subBlocks = subBlockArray.toArray(); + for (const QJsonValue& subBlockValue : subBlocks) { if (!subBlockValue.isObject()) { logger.error() << "Expected JSON object for block content list in composer"; @@ -57,7 +58,7 @@ bool ComposerBlockUnorderedList::parseJson(const QString& prefix, } m_subBlocks.append( - QString("%1.block.%2.%3").arg(prefix).arg(blockId).arg(subBlockId), + QString("%1.block.%2.%3").arg(prefix, blockId, subBlockId), subBlockObj["content"].toString()); } diff --git a/src/connectionbenchmark/benchmarktask.h b/src/connectionbenchmark/benchmarktask.h index 1ee935693cc..31ac7253431 100644 --- a/src/connectionbenchmark/benchmarktask.h +++ b/src/connectionbenchmark/benchmarktask.h @@ -26,7 +26,7 @@ class BenchmarkTask : public Task { const BenchmarkTaskSentinel* sentinel() const { return &m_sentinel; } signals: - void stateChanged(State state); + void stateChanged(BenchmarkTask::State state); private: void setState(State state); diff --git a/src/connectionbenchmark/benchmarktaskdownload.cpp b/src/connectionbenchmark/benchmarktaskdownload.cpp index d109ba8e61f..5d9389feff5 100644 --- a/src/connectionbenchmark/benchmarktaskdownload.cpp +++ b/src/connectionbenchmark/benchmarktaskdownload.cpp @@ -47,7 +47,7 @@ void BenchmarkTaskDownload::handleState(BenchmarkTask::State state) { m_dnsLookup.setNameserver(QHostAddress(MULLVAD_DEFAULT_DNS)); #endif -#if QT_VERSION >= 0x060400 +#if QT_VERSION >= 0x060500 # error Check if QT added support for QDnsLookup::lookup() on Android #endif diff --git a/src/connectionbenchmark/benchmarktaskdownload.h b/src/connectionbenchmark/benchmarktaskdownload.h deleted file mode 100644 index f927b147030..00000000000 --- a/src/connectionbenchmark/benchmarktaskdownload.h +++ /dev/null @@ -1,45 +0,0 @@ -/* 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/. */ - -#ifndef BENCHMARKTASKDOWNLOAD_H -#define BENCHMARKTASKDOWNLOAD_H - -#include "benchmarktask.h" - -#include -#include -#include -#include - -class NetworkRequest; - -class BenchmarkTaskDownload final : public BenchmarkTask { - Q_OBJECT - Q_DISABLE_COPY_MOVE(BenchmarkTaskDownload) - - public: - explicit BenchmarkTaskDownload(const QUrl& url); - ~BenchmarkTaskDownload(); - - signals: - void finished(quint64 bytesPerSecond, bool hasUnexpectedError); - - private: - void connectNetworkRequest(NetworkRequest* request); - void dnsLookupFinished(); - void downloadProgressed(qint64 bytesReceived, qint64 bytesTotal, - QNetworkReply* reply); - void downloadReady(QNetworkReply::NetworkError error, const QByteArray& data); - void handleState(BenchmarkTask::State state); - - private: - QDnsLookup m_dnsLookup; - QList m_requests; - const QUrl m_fileUrl; - - qint64 m_bytesReceived = 0; - QElapsedTimer m_elapsedTimer; -}; - -#endif // BENCHMARKTASKDOWNLOAD_H diff --git a/src/connectionbenchmark/benchmarktasksentinel.h b/src/connectionbenchmark/benchmarktasksentinel.h index 44aab9bb86f..f64d0eb8714 100644 --- a/src/connectionbenchmark/benchmarktasksentinel.h +++ b/src/connectionbenchmark/benchmarktasksentinel.h @@ -13,10 +13,10 @@ class BenchmarkTaskSentinel final : public QObject { public: BenchmarkTaskSentinel() = default; - ~BenchmarkTaskSentinel() { emit destroyed(); } + ~BenchmarkTaskSentinel() { emit sentinelDestroyed(); } signals: - void destroyed(); + void sentinelDestroyed(); }; #endif // BENCHMARKTASKSENTINEL_H diff --git a/src/connectionbenchmark/benchmarktasktransfer.cpp b/src/connectionbenchmark/benchmarktasktransfer.cpp new file mode 100644 index 00000000000..05063cff5bc --- /dev/null +++ b/src/connectionbenchmark/benchmarktasktransfer.cpp @@ -0,0 +1,246 @@ +/* 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 "benchmarktasktransfer.h" +#include "constants.h" +#include "leakdetector.h" +#include "logger.h" +#include "networkrequest.h" +#include "uploaddatagenerator.h" + +#include +#include +#include +#include + +#if !defined(MVPN_DUMMY) && !defined(MVPN_ANDROID) && !defined(MVPN_WASM) +constexpr const char* MULLVAD_DEFAULT_DNS = "10.64.0.1"; +#endif + +namespace { +Logger logger(LOG_MAIN, "BenchmarkTaskTransfer"); +} + +BenchmarkTaskTransfer::BenchmarkTaskTransfer(const QString& name, + BenchmarkType type, + const QUrl& url) + : BenchmarkTask(name, Constants::BENCHMARK_MAX_DURATION_TRANSFER), + m_type(type), + m_dnsLookup(QDnsLookup::A, url.host()), + m_url(url) { + MVPN_COUNT_CTOR(BenchmarkTaskTransfer); + + connect(this, &BenchmarkTask::stateChanged, this, + &BenchmarkTaskTransfer::handleState); + connect(&m_dnsLookup, &QDnsLookup::finished, this, + &BenchmarkTaskTransfer::dnsLookupFinished); +} + +BenchmarkTaskTransfer::~BenchmarkTaskTransfer() { + MVPN_COUNT_DTOR(BenchmarkTaskTransfer); +} + +void BenchmarkTaskTransfer::handleState(BenchmarkTask::State state) { + logger.debug() << "Handle state" << state; + + if (state == BenchmarkTask::StateActive) { +#if defined(MVPN_DUMMY) || defined(MVPN_ANDROID) || defined(MVPN_WASM) + createNetworkRequest(); +#else + // Start DNS resolution + m_dnsLookup.setNameserver(QHostAddress(MULLVAD_DEFAULT_DNS)); + m_dnsLookup.lookup(); +#endif + +#if QT_VERSION >= 0x060500 +# error Check if QT added support for QDnsLookup::lookup() on Android +#endif + + m_elapsedTimer.start(); + } else if (state == BenchmarkTask::StateInactive) { + for (NetworkRequest* request : m_requests) { + request->abort(); + } + m_requests.clear(); + m_dnsLookup.abort(); + } +} + +void BenchmarkTaskTransfer::createNetworkRequest() { + logger.debug() << "Create network request"; + + NetworkRequest* request = nullptr; + switch (m_type) { + case BenchmarkDownload: { + request = NetworkRequest::createForGetUrl(this, m_url.toString()); + break; + } + case BenchmarkUpload: { + UploadDataGenerator* uploadData = + new UploadDataGenerator(Constants::BENCHMARK_MAX_BITS_UPLOAD); + + if (!uploadData->open(UploadDataGenerator::ReadOnly)) { + emit finished(0, true); + emit completed(); + }; + request = NetworkRequest::createForUploadData(this, m_url.toString(), + uploadData); + break; + } + } + + Q_ASSERT(request); + connectNetworkRequest(request); +} + +void BenchmarkTaskTransfer::createNetworkRequestWithRecord( + const QDnsHostAddressRecord& record) { + logger.debug() << "Create network request with record"; + + NetworkRequest* request = nullptr; + switch (m_type) { + case BenchmarkDownload: { + request = NetworkRequest::createForGetHostAddress(this, m_url.toString(), + record.value()); + break; + } + case BenchmarkUpload: { + UploadDataGenerator* uploadData = + new UploadDataGenerator(Constants::BENCHMARK_MAX_BITS_UPLOAD); + + if (!uploadData->open(UploadDataGenerator::ReadOnly)) { + emit finished(0, true); + emit completed(); + }; + request = NetworkRequest::createForUploadDataHostAddress( + this, m_url.toString(), uploadData, record.value()); + break; + } + default: { + logger.error() << "Unhandled benchmark type"; + break; + } + } + + Q_ASSERT(request); + connectNetworkRequest(request); +} + +void BenchmarkTaskTransfer::connectNetworkRequest(NetworkRequest* request) { + logger.debug() << "Connect network requests"; + + switch (m_type) { + case BenchmarkDownload: { + connect(request, &NetworkRequest::requestUpdated, this, + &BenchmarkTaskTransfer::transferProgressed); + break; + } + case BenchmarkUpload: { + connect(request, &NetworkRequest::uploadProgressed, this, + &BenchmarkTaskTransfer::transferProgressed); + break; + } + default: { + logger.error() << "Unhandled benchmark type"; + break; + } + } + connect(request, &NetworkRequest::requestFailed, this, + &BenchmarkTaskTransfer::transferReady); + connect(request, &NetworkRequest::requestCompleted, this, + [&](const QByteArray& data) { + transferReady(QNetworkReply::NoError, data); + }); + + logger.debug() << "Starting request"; + m_requests.append(request); +} + +void BenchmarkTaskTransfer::dnsLookupFinished() { + auto guard = qScopeGuard([&] { + emit finished(0, true); + emit completed(); + }); + + if (m_dnsLookup.error() != QDnsLookup::NoError) { + logger.error() << "DNS Lookup Failed:" << m_dnsLookup.errorString(); + return; + } + if (m_dnsLookup.hostAddressRecords().isEmpty()) { + logger.error() << "DNS Lookup Failed: no records"; + return; + } + if (state() != BenchmarkTask::StateActive) { + logger.warning() << "DNS Lookup finished after task aborted"; + return; + } + + logger.debug() << "DNS Lookup Finished"; + for (const QDnsHostAddressRecord& record : m_dnsLookup.hostAddressRecords()) { + logger.debug() << "Host record:" << record.value().toString(); + createNetworkRequestWithRecord(record); + } + + m_elapsedTimer.start(); + guard.dismiss(); +} + +void BenchmarkTaskTransfer::transferProgressed(qint64 bytesSent, + qint64 bytesTotal, + QNetworkReply* reply) { +#ifdef MVPN_DEBUG + logger.debug() << "Transfer progressed:" << bytesSent << "(transferred)" + << bytesTotal << "(total)"; +#else + Q_UNUSED(bytesTotal); +#endif + + switch (m_type) { + case BenchmarkDownload: { + // Count and discard downloaded data + m_bytesTransferred += reply->skip(bytesTotal); + break; + } + case BenchmarkUpload: { + Q_UNUSED(reply); + m_bytesTransferred += bytesSent; + break; + } + default: { + logger.error() << "Unhandled benchmark type"; + break; + } + } +} + +void BenchmarkTaskTransfer::transferReady(QNetworkReply::NetworkError error, + const QByteArray& data) { + logger.debug() << "Transfer ready" << error; + Q_UNUSED(data); + + NetworkRequest* request = qobject_cast(QObject::sender()); + m_requests.removeOne(request); + + quint64 bitsPerSec = 0; + double msecs = static_cast(m_elapsedTimer.elapsed()); + if (m_bytesTransferred > 0 && msecs > 0) { + bitsPerSec = static_cast( + static_cast(m_bytesTransferred * 8) / (msecs / 1000.00)); + } + + bool hasUnexpectedError = (error != QNetworkReply::NoError && + error != QNetworkReply::OperationCanceledError && + error != QNetworkReply::TimeoutError) +#ifndef MVPN_WASM + || bitsPerSec == 0 +#endif + ; + + logger.debug() << "Transfer completed" << bitsPerSec << "baud"; + + if (m_requests.isEmpty()) { + emit finished(bitsPerSec, hasUnexpectedError); + emit completed(); + } +} diff --git a/src/connectionbenchmark/benchmarktasktransfer.h b/src/connectionbenchmark/benchmarktasktransfer.h new file mode 100644 index 00000000000..32554e18576 --- /dev/null +++ b/src/connectionbenchmark/benchmarktasktransfer.h @@ -0,0 +1,55 @@ +/* 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/. */ + +#ifndef BENCHMARKTASKTRANSFER_H +#define BENCHMARKTASKTRANSFER_H + +#include "benchmarktask.h" + +#include +#include +#include +#include + +class NetworkRequest; + +class BenchmarkTaskTransfer : public BenchmarkTask { + Q_OBJECT + Q_DISABLE_COPY_MOVE(BenchmarkTaskTransfer) + Q_PROPERTY(BenchmarkType type MEMBER m_type CONSTANT) + + public: + enum BenchmarkType { + BenchmarkDownload, + BenchmarkUpload, + }; + + explicit BenchmarkTaskTransfer(const QString& name, BenchmarkType type, + const QUrl& url); + virtual ~BenchmarkTaskTransfer(); + + signals: + void finished(quint64 bitsPerSec, bool hasUnexpectedError); + + private: + void createNetworkRequest(); + void createNetworkRequestWithRecord(const QDnsHostAddressRecord& record); + void connectNetworkRequest(NetworkRequest* request); + void dnsLookupFinished(); + void handleState(BenchmarkTask::State state); + void transferProgressed(qint64 bytesTransferred, qint64 bytesTotal, + QNetworkReply* reply); + void transferReady(QNetworkReply::NetworkError error, const QByteArray& data); + + private: + BenchmarkType m_type; + QDnsLookup m_dnsLookup; + QList m_requests; + const QUrl m_url; + + qint64 m_bytesTransferred = 0; + QElapsedTimer m_elapsedTimer; +}; + +#endif // BENCHMARKTASKTRANSFER_H diff --git a/src/connectionbenchmark/connectionbenchmark.cpp b/src/connectionbenchmark/connectionbenchmark.cpp index 288d8448d6f..bd6316f6558 100644 --- a/src/connectionbenchmark/connectionbenchmark.cpp +++ b/src/connectionbenchmark/connectionbenchmark.cpp @@ -3,13 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "connectionbenchmark.h" -#include "benchmarktaskdownload.h" #include "benchmarktaskping.h" +#include "benchmarktasktransfer.h" #include "connectionhealth.h" #include "controller.h" -#include "constants.h" #include "leakdetector.h" #include "logger.h" +#include "models/feature.h" #include "mozillavpn.h" #include "taskscheduler.h" @@ -17,8 +17,7 @@ namespace { Logger logger(LOG_MODEL, "ConnectionBenchmark"); } -ConnectionBenchmark::ConnectionBenchmark() - : m_downloadUrl(Constants::BENCHMARK_DOWNLOAD_URL) { +ConnectionBenchmark::ConnectionBenchmark() { MVPN_COUNT_CTOR(ConnectionBenchmark); } @@ -28,7 +27,6 @@ ConnectionBenchmark::~ConnectionBenchmark() { void ConnectionBenchmark::initialize() { MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Controller* controller = vpn->controller(); Q_ASSERT(controller); @@ -40,11 +38,12 @@ void ConnectionBenchmark::initialize() { } void ConnectionBenchmark::setConnectionSpeed() { - logger.debug() << "Set speed"; + logger.debug() << "Set connection speed"; - if (m_bitsPerSec >= Constants::BENCHMARK_THRESHOLD_SPEED_FAST) { + // TODO: Take uploadBps for calculating speed into account + if (m_downloadBps >= Constants::BENCHMARK_THRESHOLD_SPEED_FAST) { m_speed = SpeedFast; - } else if (m_bitsPerSec >= Constants::BENCHMARK_THRESHOLD_SPEED_MEDIUM) { + } else if (m_downloadBps >= Constants::BENCHMARK_THRESHOLD_SPEED_MEDIUM) { m_speed = SpeedMedium; } else { m_speed = SpeedSlow; @@ -67,7 +66,6 @@ void ConnectionBenchmark::start() { Q_ASSERT(m_state != StateRunning); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Controller* controller = vpn->controller(); Controller::State controllerState = controller->state(); @@ -79,20 +77,37 @@ void ConnectionBenchmark::start() { BenchmarkTaskPing* pingTask = new BenchmarkTaskPing(); connect(pingTask, &BenchmarkTaskPing::finished, this, &ConnectionBenchmark::pingBenchmarked); - connect(pingTask->sentinel(), &BenchmarkTask::destroyed, this, + connect(pingTask->sentinel(), &BenchmarkTaskSentinel::sentinelDestroyed, this, [this, pingTask]() { m_benchmarkTasks.removeOne(pingTask); }); m_benchmarkTasks.append(pingTask); TaskScheduler::scheduleTask(pingTask); // Create download benchmark - BenchmarkTaskDownload* downloadTask = - new BenchmarkTaskDownload(m_downloadUrl); - connect(downloadTask, &BenchmarkTaskDownload::finished, this, + BenchmarkTaskTransfer* downloadTask = new BenchmarkTaskTransfer( + "BenchmarkTaskDownload", BenchmarkTaskTransfer::BenchmarkDownload, + m_downloadUrl); + connect(downloadTask, &BenchmarkTaskTransfer::finished, this, &ConnectionBenchmark::downloadBenchmarked); - connect(downloadTask->sentinel(), &BenchmarkTask::destroyed, this, + connect(downloadTask->sentinel(), &BenchmarkTaskSentinel::sentinelDestroyed, + this, [this, downloadTask]() { m_benchmarkTasks.removeOne(downloadTask); }); m_benchmarkTasks.append(downloadTask); TaskScheduler::scheduleTask(downloadTask); + + // Create upload benchmark + if (Feature::get(Feature::Feature_benchmarkUpload)->isSupported()) { + BenchmarkTaskTransfer* uploadTask = new BenchmarkTaskTransfer( + "BenchmarkTaskUpload", BenchmarkTaskTransfer::BenchmarkUpload, + m_uploadUrl); + Q_UNUSED(uploadTask); + + connect(uploadTask, &BenchmarkTaskTransfer::finished, this, + &ConnectionBenchmark::uploadBenchmarked); + connect(uploadTask->sentinel(), &BenchmarkTask::destroyed, this, + [this, uploadTask]() { m_benchmarkTasks.removeOne(uploadTask); }); + m_benchmarkTasks.append(uploadTask); + TaskScheduler::scheduleTask(uploadTask); + } } void ConnectionBenchmark::stop() { @@ -116,7 +131,8 @@ void ConnectionBenchmark::reset() { stop(); - m_bitsPerSec = 0; + m_downloadBps = 0; + m_uploadBps = 0; m_pingLatency = 0; setState(StateInitial); @@ -131,10 +147,13 @@ void ConnectionBenchmark::downloadBenchmarked(quint64 bitsPerSec, return; } - m_bitsPerSec = bitsPerSec; - emit bitsPerSecChanged(); + m_downloadBps = bitsPerSec; + emit downloadBpsChanged(); - setConnectionSpeed(); + if (!Feature::get(Feature::Feature_benchmarkUpload)->isSupported()) { + // All benchmarks ran successfully and we can set the connection speed. + setConnectionSpeed(); + } } void ConnectionBenchmark::pingBenchmarked(quint64 pingLatency) { @@ -144,6 +163,24 @@ void ConnectionBenchmark::pingBenchmarked(quint64 pingLatency) { emit pingLatencyChanged(); } +void ConnectionBenchmark::uploadBenchmarked(quint64 bitsPerSec, + bool hasUnexpectedError) { + logger.debug() << "Benchmarked upload" << bitsPerSec; + + if (hasUnexpectedError) { + setState(StateError); + return; + } + + m_uploadBps = bitsPerSec; + emit uploadBpsChanged(); + + if (Feature::get(Feature::Feature_benchmarkUpload)->isSupported()) { + // All benchmarks ran successfully and we can set the connection speed. + setConnectionSpeed(); + } +} + void ConnectionBenchmark::handleControllerState() { if (m_state == StateInitial || m_state == StateReady) { return; diff --git a/src/connectionbenchmark/connectionbenchmark.h b/src/connectionbenchmark/connectionbenchmark.h index a903448b401..0412f609a17 100644 --- a/src/connectionbenchmark/connectionbenchmark.h +++ b/src/connectionbenchmark/connectionbenchmark.h @@ -6,6 +6,7 @@ #define CONNECTIONBENCHMARK_H #include "benchmarktask.h" +#include "constants.h" #include #include @@ -19,10 +20,13 @@ class ConnectionBenchmark final : public QObject { Q_PROPERTY(QString downloadUrl READ downloadUrl WRITE setDownloadUrl NOTIFY downloadUrlChanged) + Q_PROPERTY(QString uploadUrl READ uploadUrl WRITE setUploadUrl NOTIFY + uploadUrlChanged) Q_PROPERTY(State state READ state NOTIFY stateChanged); Q_PROPERTY(Speed speed READ speed NOTIFY speedChanged); - Q_PROPERTY(quint64 bitsPerSec READ bitsPerSec NOTIFY bitsPerSecChanged); + Q_PROPERTY(quint64 downloadBps READ downloadBps NOTIFY downloadBpsChanged); Q_PROPERTY(quint16 pingLatency READ pingLatency NOTIFY pingLatencyChanged); + Q_PROPERTY(quint64 uploadBps READ uploadBps NOTIFY uploadBpsChanged); public: ConnectionBenchmark(); @@ -51,7 +55,8 @@ class ConnectionBenchmark final : public QObject { State state() const { return m_state; } Speed speed() const { return m_speed; } quint16 pingLatency() const { return m_pingLatency; } - quint64 bitsPerSec() const { return m_bitsPerSec; } + quint64 downloadBps() const { return m_downloadBps; } + quint64 uploadBps() const { return m_uploadBps; } QString downloadUrl() const { return m_downloadUrl.toString(); } void setDownloadUrl(QString url) { @@ -59,16 +64,25 @@ class ConnectionBenchmark final : public QObject { emit downloadUrlChanged(); } + QString uploadUrl() const { return m_uploadUrl.toString(); } + void setUploadUrl(QString url) { + m_uploadUrl.setUrl(url); + emit uploadUrlChanged(); + } + signals: - void bitsPerSecChanged(); + void downloadBpsChanged(); void pingLatencyChanged(); + void uploadBpsChanged(); void speedChanged(); void stateChanged(); void downloadUrlChanged(); + void uploadUrlChanged(); private: void downloadBenchmarked(quint64 bitsPerSec, bool hasUnexpectedError); void pingBenchmarked(quint64 pingLatencyLatency); + void uploadBenchmarked(quint64 bitsPerSec, bool hasUnexpectedError); void handleControllerState(); void handleStabilityChange(); @@ -77,15 +91,17 @@ class ConnectionBenchmark final : public QObject { void stop(); private: - QUrl m_downloadUrl; + QUrl m_downloadUrl = QUrl(Constants::BENCHMARK_DOWNLOAD_URL); + QUrl m_uploadUrl = QUrl(Constants::BENCHMARK_UPLOAD_URL); QList m_benchmarkTasks; State m_state = StateInitial; Speed m_speed = SpeedSlow; - quint64 m_bitsPerSec = 0; + quint64 m_downloadBps = 0; quint16 m_pingLatency = 0; + quint64 m_uploadBps = 0; }; #endif // CONNECTIONBENCHMARK_H diff --git a/src/connectionbenchmark/uploaddatagenerator.cpp b/src/connectionbenchmark/uploaddatagenerator.cpp new file mode 100644 index 00000000000..558ab1ffa94 --- /dev/null +++ b/src/connectionbenchmark/uploaddatagenerator.cpp @@ -0,0 +1,49 @@ +/* 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 "uploaddatagenerator.h" +#include "leakdetector.h" +#include "logger.h" + +#include +#include + +namespace { +Logger logger(LOG_MAIN, "UploadDataGenerator"); +} + +UploadDataGenerator::UploadDataGenerator(const qint64 totalSize) + : m_totalSize(totalSize) { + MVPN_COUNT_CTOR(UploadDataGenerator); + + memset(m_dataBuffer, 0x00, sizeof(m_dataBuffer)); +} + +UploadDataGenerator::~UploadDataGenerator() { + MVPN_COUNT_DTOR(UploadDataGenerator); +} + +qint64 UploadDataGenerator::readData(char* data, qint64 maxSize) { + qint64 maxBufferSize = qMin(MAX_BUFFER_SIZE, maxSize); + qint64 maxReadSize = qMin(m_totalSize - pos(), maxBufferSize); + +#ifdef MVPN_DEBUG + logger.debug() << "Read data" << maxReadSize; +#endif + + if (maxReadSize < 0) { + return -1; + } + + memcpy(data, m_dataBuffer, maxReadSize); + return maxReadSize; +}; + +qint64 UploadDataGenerator::writeData(const char* data, qint64 maxSize) { + logger.debug() << "Write data"; + Q_UNUSED(data); + Q_UNUSED(maxSize); + + return -1; +}; diff --git a/src/connectionbenchmark/uploaddatagenerator.h b/src/connectionbenchmark/uploaddatagenerator.h new file mode 100644 index 00000000000..5e7077e90d0 --- /dev/null +++ b/src/connectionbenchmark/uploaddatagenerator.h @@ -0,0 +1,31 @@ +/* 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/. */ + +#ifndef UPLOADDATAGENERATOR_H +#define UPLOADDATAGENERATOR_H + +#include + +constexpr int32_t MAX_BUFFER_SIZE = 4096; + +class UploadDataGenerator final : public QIODevice { + Q_OBJECT + Q_DISABLE_COPY_MOVE(UploadDataGenerator) + + public: + explicit UploadDataGenerator(const qint64 totalSize); + ~UploadDataGenerator(); + + qint64 size() const override { return m_totalSize; } + + private: + virtual qint64 readData(char* data, qint64 maxSize) override; + virtual qint64 writeData(const char* data, qint64 maxSize) override; + + private: + const qint64 m_totalSize; + char m_dataBuffer[MAX_BUFFER_SIZE]; +}; + +#endif // UPLOADDATAGENERATOR_H diff --git a/src/constants.h b/src/constants.h index cf21c3f520a..4df80d65869 100644 --- a/src/constants.h +++ b/src/constants.h @@ -39,12 +39,15 @@ constexpr int RECENT_CONNECTIONS_MAX_COUNT = 5; constexpr uint32_t SERVER_UNRESPONSIVE_COOLDOWN_SEC = 300; // Number of msecs for max runtime of the connection benchmarks. +constexpr uint32_t BENCHMARK_MAX_BITS_UPLOAD = 80000000; // 10 Megabyte constexpr uint32_t BENCHMARK_MAX_DURATION_PING = 3000; -constexpr uint32_t BENCHMARK_MAX_DURATION_DOWNLOAD = 15000; +constexpr uint32_t BENCHMARK_MAX_DURATION_TRANSFER = 15000; constexpr uint32_t BENCHMARK_THRESHOLD_SPEED_FAST = 25000000; // 25 Megabit constexpr uint32_t BENCHMARK_THRESHOLD_SPEED_MEDIUM = 10000000; // 10 Megabit constexpr const char* BENCHMARK_DOWNLOAD_URL = "https://archive.mozilla.org/pub/vpn/speedtest/50m.data"; +// TODO: Add url for upload benchmark +constexpr const char* BENCHMARK_UPLOAD_URL = ""; #if defined(UNIT_TEST) # define CONSTEXPR(type, functionName, releaseValue, debugValue, \ @@ -117,12 +120,10 @@ PRODBETAEXPR(const char*, fxaUrl, "https://accounts.firefox.com", PRODBETAEXPR( const char*, balrogUrl, "https://aus5.mozilla.org/json/1/FirefoxVPN/%1/%2/release/update.json", - "https://stage.balrog.nonprod.cloudops.mozgcp.net/json/1/FirefoxVPN/%1/%2/" - "release-cdntest/update.json"); -PRODBETAEXPR( - const char*, balrogRootCertFingerprint, - "97e8ba9cf12fb3de53cc42a4e6577ed64df493c247b414fea036818d3823560e", - "3c01446abe9036cea9a09acaa3a520ac628f20a7ae32ce861cb2efb70fa0c745"); + "https://aus5.mozilla.org/json/1/FirefoxVPN/%1/%2/release-cdntest/" + "update.json"); +constexpr const char* AUTOGRAPH_ROOT_CERT_FINGERPRINT = + "97e8ba9cf12fb3de53cc42a4e6577ed64df493c247b414fea036818d3823560e"; PRODBETAEXPR(const char*, relayUrl, "https://relay.firefox.com", "https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net"); diff --git a/src/controller.cpp b/src/controller.cpp index abe7fb9caf9..aabc102e127 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -122,14 +122,13 @@ void Controller::initialize() { connect(this, &Controller::stateChanged, this, &Controller::maybeEnableDisconnectInConfirming); - connect(&m_ping_canary, &PingHelper::pingSentAndReceived, [this]() { + connect(&m_ping_canary, &PingHelper::pingSentAndReceived, this, [this]() { m_ping_canary.stop(); m_ping_received = true; logger.info() << "Canary Ping Succeeded"; }); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); const Device* device = vpn->deviceModel()->currentDevice(vpn->keys()); m_impl->initialize(device, vpn->keys()); @@ -144,7 +143,7 @@ void Controller::implInitialized(bool status, bool a_connected, Q_ASSERT(m_state == StateInitializing); if (!status) { - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); setState(StateOff); return; } @@ -199,7 +198,6 @@ void Controller::activateInternal(bool forcePort53) { m_activationQueue.clear(); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Server exitServer = Server::weightChooser(vpn->exitServers()); if (!exitServer.initialized()) { @@ -293,7 +291,7 @@ void Controller::activateNext() { MozillaVPN* vpn = MozillaVPN::instance(); const Device* device = vpn->deviceModel()->currentDevice(vpn->keys()); if (device == nullptr) { - vpn->errorHandle(ErrorHandler::AuthenticationError); + ErrorHandler::instance()->errorHandle(ErrorHandler::AuthenticationError); vpn->reset(false); return; } @@ -316,7 +314,6 @@ bool Controller::silentSwitchServers() { } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // Set a cooldown timer on the current server. QList servers = vpn->exitServers(); @@ -361,21 +358,27 @@ bool Controller::deactivate() { void Controller::connected(const QString& pubkey) { logger.debug() << "handshake completed with:" << logger.keys(pubkey); if (m_activationQueue.isEmpty()) { - logger.warning() << "Unexpected handshake: no pending connections."; - return; - } - if (m_activationQueue.first().m_server.publicKey() != pubkey) { + MozillaVPN* vpn = MozillaVPN::instance(); + Q_ASSERT(vpn); + if (vpn->exitServerPublicKey() != pubkey) { + logger.warning() << "Unexpected handshake: no pending connections."; + return; + } + // Continue anyways if the VPN service was activated externally. + logger.info() << "Unexpected handshake: external VPN activation."; + } else if (m_activationQueue.first().m_server.publicKey() != pubkey) { logger.warning() << "Unexpected handshake: public key mismatch."; return; + } else { + // Start the next connection if there is more work to do. + m_activationQueue.removeFirst(); + if (!m_activationQueue.isEmpty()) { + activateNext(); + return; + } } m_handshakeTimer.stop(); m_ping_canary.stop(); - // Start the next connection if there is more work to do. - m_activationQueue.removeFirst(); - if (!m_activationQueue.isEmpty()) { - activateNext(); - return; - } // Clear the retry counter after all connections have succeeded. m_connectionRetry = 0; @@ -396,7 +399,6 @@ void Controller::handshakeTimeout() { logger.debug() << "Timeout while waiting for handshake"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Q_ASSERT(!m_activationQueue.isEmpty()); // Block the offending server and try again. @@ -435,7 +437,6 @@ void Controller::setCooldownForAllServersInACity(const QString& countryCode, Q_ASSERT(!Constants::inProduction()); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); vpn->setCooldownForAllServersInACity(countryCode, cityCode); } @@ -475,7 +476,6 @@ void Controller::changeServer(const QString& countryCode, const QString& city, Q_ASSERT(m_state == StateOn || m_state == StateOff); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->currentServer()->exitCountryCode() == countryCode && vpn->currentServer()->exitCityName() == city && @@ -608,6 +608,7 @@ bool Controller::processNextStep() { if (nextStep == ServerUnavailable) { logger.info() << "Server Unavailable - Ping succeeded: " << m_ping_received; + emit readyToServerUnavailable(m_ping_received); return true; } diff --git a/src/crashreporter/crashuploader.cpp b/src/crashreporter/crashuploader.cpp index f9b131730ff..7d9c7427cb9 100644 --- a/src/crashreporter/crashuploader.cpp +++ b/src/crashreporter/crashuploader.cpp @@ -87,11 +87,13 @@ void CrashUploader::startRequest(const QString& file) { multipart->append(namePart); multipart->append(formPart); - auto urlStr = Constants::inProduction() ? Constants::CRASH_STAGING_URL - : Constants::CRASH_PRODUCTION_URL; + auto urlStr = #ifdef MVPN_DEBUG - urlStr = Constants::CRASH_STAGING_URL; -#endif // MVPN_DEBUG + Constants::CRASH_STAGING_URL; +#else + Constants::inProduction() ? Constants::CRASH_STAGING_URL + : Constants::CRASH_PRODUCTION_URL; +#endif logger.debug() << "Uploading to : " << urlStr; QUrl url(urlStr); @@ -106,7 +108,7 @@ void CrashUploader::startRequest(const QString& file) { connect(reply, &QNetworkReply::sslErrors, [](QList errors) { logger.error() << "SSL Errors: "; - for (auto err : errors) { + for (const auto& err : errors) { logger.error() << err.errorString(); } }); @@ -136,7 +138,7 @@ void CrashUploader::dumpResponse(QNetworkReply* reply) { auto response = reply->readAll(); auto headers = reply->rawHeaderList(); logger.debug() << "Reply headers"; - for (auto header : headers) { + for (const auto& header : headers) { logger.debug() << "Header: " << QString::fromLocal8Bit(header) << " = " << QString::fromLocal8Bit(reply->rawHeader(header)); } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 07ae5ecfb48..d86f455c550 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -167,7 +167,7 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name, return false; } QJsonArray array = value.toArray(); - for (QJsonValue i : array) { + for (const QJsonValue& i : array) { if (!i.isString()) { logger.error() << name << "must contain only strings"; return false; @@ -248,7 +248,7 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { } QJsonArray array = value.toArray(); - for (QJsonValue i : array) { + for (const QJsonValue& i : array) { if (!i.isObject()) { logger.error() << JSON_ALLOWEDIPADDRESSRANGES << "must contain only objects"; @@ -325,7 +325,7 @@ bool Daemon::deactivate(bool emitSignals) { } // Cleanup peers and routing - for (const ConnectionState& state : m_connections.values()) { + for (const ConnectionState& state : m_connections) { const InterfaceConfig& config = state.m_config; logger.debug() << "Deleting routes for hop" << config.m_hopindex; for (const IPAddress& ip : config.m_allowedIPAddressRanges) { @@ -448,7 +448,7 @@ QJsonObject Daemon::getStatus() { const ConnectionState& connection = m_connections.value(0); QList peers = wgutils()->getPeerStatus(); - for (WireguardUtils::PeerStatus status : peers) { + for (const WireguardUtils::PeerStatus& status : peers) { if (status.m_pubkey != connection.m_config.m_serverPublicKey) { continue; } diff --git a/src/errorhandler.cpp b/src/errorhandler.cpp index f74848841af..9dd364698db 100644 --- a/src/errorhandler.cpp +++ b/src/errorhandler.cpp @@ -5,9 +5,14 @@ #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" +#include "mozillavpn.h" +#include "telemetry/gleansample.h" #include +// in seconds, hide alerts +constexpr const uint32_t HIDE_ALERT_SEC = 4; + namespace { ErrorHandler* s_instance = nullptr; Logger logger(LOG_MAIN, "ErrorHandler"); @@ -23,6 +28,9 @@ ErrorHandler* ErrorHandler::instance() { ErrorHandler::ErrorHandler(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(ErrorHandler); + + connect(&m_alertTimer, &QTimer::timeout, this, + [this]() { setAlert(NoAlert); }); } ErrorHandler::~ErrorHandler() { MVPN_COUNT_DTOR(ErrorHandler); } @@ -119,3 +127,110 @@ ErrorHandler::ErrorType ErrorHandler::toErrorType( return IgnoredError; } + +void ErrorHandler::errorHandle(ErrorHandler::ErrorType error) { + logger.debug() << "Handling error" << error; + + Q_ASSERT(error != ErrorHandler::NoError); + + AlertType alert = NoAlert; + + MozillaVPN* vpn = MozillaVPN::instance(); + + switch (error) { + case ErrorHandler::VPNDependentConnectionError: + if (vpn->controller()->state() == Controller::State::StateOn || + vpn->controller()->state() == Controller::State::StateConfirming) { + // connection likely isn't stable yet + logger.error() << "Ignore network error probably caused by enabled VPN"; + return; + } else if (vpn->controller()->state() == Controller::State::StateOff) { + // We are off, so this means a request failed, not the + // VPN. Change it to No Connection + alert = NoConnectionAlert; + break; + } + [[fallthrough]]; + case ErrorHandler::ConnectionFailureError: + alert = ConnectionFailedAlert; + break; + + case ErrorHandler::NoConnectionError: + if (vpn->connectionHealth() && vpn->connectionHealth()->isUnsettled()) { + return; + } + alert = NoConnectionAlert; + break; + + case ErrorHandler::AuthenticationError: + alert = AuthenticationFailedAlert; + break; + + case ErrorHandler::ControllerError: + alert = ControllerErrorAlert; + break; + + case ErrorHandler::RemoteServiceError: + alert = RemoteServiceErrorAlert; + break; + + case ErrorHandler::SubscriptionFailureError: + alert = SubscriptionFailureAlert; + break; + + case ErrorHandler::GeoIpRestrictionError: + alert = GeoIpRestrictionAlert; + break; + + case ErrorHandler::UnrecoverableError: + alert = UnrecoverableErrorAlert; + break; + + default: + break; + } + + setAlert(alert); + + logger.error() << "Alert:" << alert << "State:" << vpn->state(); + + if (alert == NoAlert) { + return; + } + + // Any error in authenticating state sends to the Initial state. + if (vpn->state() == MozillaVPN::StateAuthenticating) { + if (alert == GeoIpRestrictionAlert) { + emit vpn->recordGleanEvent(GleanSample::authenticationFailureByGeo); + } else { + emit vpn->recordGleanEvent(GleanSample::authenticationFailure); + } + vpn->reset(true); + return; + } + + if (alert == AuthenticationFailedAlert) { + vpn->reset(true); + return; + } +} + +void ErrorHandler::setAlert(AlertType alert) { + m_alertTimer.stop(); + + if (alert != NoAlert) { + m_alertTimer.start(1000 * HIDE_ALERT_SEC); + } + + m_alert = alert; + emit alertChanged(); +} + +// static +void ErrorHandler::networkErrorHandle( + QNetworkReply::NetworkError error, + ErrorPropagationPolicy errorPropagationPolicy) { + if (errorPropagationPolicy == PropagateError) { + ErrorHandler::instance()->errorHandle(toErrorType(error)); + } +} diff --git a/src/errorhandler.h b/src/errorhandler.h index 24cafd49985..1be78dd15e9 100644 --- a/src/errorhandler.h +++ b/src/errorhandler.h @@ -5,13 +5,16 @@ #ifndef ERRORHANDLER_H #define ERRORHANDLER_H -#include #include +#include +#include class ErrorHandler final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(ErrorHandler) + Q_PROPERTY(AlertType alert READ alert NOTIFY alertChanged) + private: explicit ErrorHandler(QObject* parent); @@ -30,17 +33,61 @@ class ErrorHandler final : public QObject { IgnoredError, }; + enum AlertType { + NoAlert, + AuthenticationFailedAlert, + ConnectionFailedAlert, + LogoutAlert, + NoConnectionAlert, + ControllerErrorAlert, + RemoteServiceErrorAlert, + SubscriptionFailureAlert, + GeoIpRestrictionAlert, + UnrecoverableErrorAlert, + AuthCodeSentAlert, + }; + Q_ENUM(AlertType) + + enum ErrorPropagationPolicy { + // Do not propagate the error up to the frontend code. The error will be + // logged but it will not be shown to the user. + DoNotPropagateError, + + // The error needs to be propagated through the frontend code and shown to + // the user as an alert or something else. + PropagateError, + }; + Q_ENUM(ErrorPropagationPolicy); + static ErrorType toErrorType(QNetworkReply::NetworkError error); + static void networkErrorHandle( + QNetworkReply::NetworkError error, + ErrorPropagationPolicy errorPropagationPolicy = PropagateError); + ~ErrorHandler(); static ErrorHandler* instance(); + AlertType alert() const { return m_alert; } + + void errorHandle(ErrorType error); + + void hideAlert() { setAlert(NoAlert); } + Q_INVOKABLE void setAlert(ErrorHandler::AlertType alert); + #define ERRORSTATE(name) \ void name##Error(); \ Q_SIGNAL void name(); #include "errorlist.h" #undef ERRORSTATE + + signals: + void alertChanged(); + + private: + AlertType m_alert = NoAlert; + QTimer m_alertTimer; }; #endif // ERRORHANDLER_H diff --git a/src/eventlistener.cpp b/src/eventlistener.cpp index e422f8fad0f..1b4038dab0f 100644 --- a/src/eventlistener.cpp +++ b/src/eventlistener.cpp @@ -42,7 +42,7 @@ EventListener::EventListener() { return; } - connect(&m_server, &QLocalServer::newConnection, [&] { + connect(&m_server, &QLocalServer::newConnection, &m_server, [&] { logger.debug() << "New connection received"; if (!m_server.hasPendingConnections()) { @@ -52,7 +52,7 @@ EventListener::EventListener() { QLocalSocket* socket = m_server.nextPendingConnection(); Q_ASSERT(socket); - connect(socket, &QLocalSocket::readyRead, [socket]() { + connect(socket, &QLocalSocket::readyRead, socket, [socket]() { QByteArray input = socket->readAll(); input = input.trimmed(); diff --git a/src/externalophandler.cpp b/src/externalophandler.cpp index f5d1527dbdb..2fa29cc2e2a 100644 --- a/src/externalophandler.cpp +++ b/src/externalophandler.cpp @@ -49,7 +49,6 @@ void ExternalOpHandler::request(Op op) { logger.debug() << "Op request received"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); for (Blocker* blocker : m_blockers) { if (blocker->maybeBlockRequest(op)) { diff --git a/src/featureslist.h b/src/featureslist.h index a421bcc332b..6b89be79fe6 100644 --- a/src/featureslist.h +++ b/src/featureslist.h @@ -18,117 +18,125 @@ L18nStrings::Empty, "", "", "", releaseVersion, flippableOn, \ flippableOff, dependencies, callback) -FEATURE_SIMPLE(accountDeletion, // Feature ID - "Account deletion", // Feature name - "2.9", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(accountDeletion, // Feature ID + "Account deletion", // Feature name + "2.9", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_accountDeletion) -FEATURE_SIMPLE(addon, // Feature ID - "Addon support", // Feature name - "2.9.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(addon, // Feature ID + "Addon support", // Feature name + "2.9.0", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_true) -FEATURE_SIMPLE(addonSignature, // Feature ID - "Addons Signature", // Feature name - "2.10.0", // released - !Constants::inProduction(), // Can be flipped on - !Constants::inProduction(), // Can be flipped off - QStringList{"addon"}, // feature dependencies +FEATURE_SIMPLE(addonSignature, // Feature ID + "Addons Signature", // Feature name + "2.10.0", // released + FeatureCallback_inStaging, // Can be flipped on + FeatureCallback_inStaging, // Can be flipped off + QStringList{"addon"}, // feature dependencies FeatureCallback_true) -FEATURE_SIMPLE(appReview, // Feature ID - "App Review", // Feature name - "2.5", // released - false, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(appReview, // Feature ID + "App Review", // Feature name + "2.5", // released + FeatureCallback_false, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_iosOrAndroid) -FEATURE_SIMPLE(bundleUpgrade, // Feature ID - "Bundle Upgrade", // Feature name - "2.10", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(benchmarkUpload, // Feature ID + "Benchmark Upload", // Feature name + "2.10", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_false) -FEATURE_SIMPLE(captivePortal, // Feature ID - "Captive Portal", // Feature name - "2.1", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(bundleUpgrade, // Feature ID + "Bundle Upgrade", // Feature name + "2.10", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies + FeatureCallback_false) + +FEATURE_SIMPLE(captivePortal, // Feature ID + "Captive Portal", // Feature name + "2.1", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_captivePortal) -FEATURE_SIMPLE(customDNS, // Feature ID - "Custom DNS", // Feature name - "2.5", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(customDNS, // Feature ID + "Custom DNS", // Feature name + "2.5", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_true) -FEATURE_SIMPLE(freeTrial, // Feature ID - "Free trial", // Feature name - "2.8.1", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - FeatureCallback_iosOrAndroid) +FEATURE_SIMPLE(freeTrial, // Feature ID + "Free trial", // Feature name + "2.8.1", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies + FeatureCallback_freeTrial) FEATURE_SIMPLE(inAppAccountCreate, // Feature ID "In-app Account Cretion", // Feature name "2.6", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList{"inAppAuthentication"}, // feature dependencies FeatureCallback_iosOrAndroid) FEATURE_SIMPLE(inAppAuthentication, // Feature ID "In-app Authentication", // Feature name "2.4", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_iosOrAndroid) -FEATURE_SIMPLE(inAppPurchase, // Feature ID - "In app Purchase", // Feature name - "2.4", // released - false, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(inAppPurchase, // Feature ID + "In app Purchase", // Feature name + "2.4", // released + FeatureCallback_false, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_inAppPurchase) -FEATURE_SIMPLE(keyRegeneration, // Feature ID - "Key Regeneration", // Feature name - "2.10.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(keyRegeneration, // Feature ID + "Key Regeneration", // Feature name + "2.10.0", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies // TODO: Disabled - VPN-2693 FeatureCallback_false) FEATURE_SIMPLE(lanAccess, // Feature ID "Local area network access", // Feature name "2.2", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_lanAccess) -FEATURE_SIMPLE(mobileOnboarding, // Feature ID - "Mobile Onboarding", // Feature name - "2.8", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(mobileOnboarding, // Feature ID + "Mobile Onboarding", // Feature name + "2.8", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_iosOrAndroid) #if defined(MVPN_ANDROID) || defined(MVPN_IOS) @@ -137,74 +145,74 @@ FEATURE_SIMPLE(mobileOnboarding, // Feature ID # define MULTIHOP_RELEASE "2.5" #endif -FEATURE_SIMPLE(multiHop, // Feature ID - "Multi-hop", // Feature name - MULTIHOP_RELEASE, // released for android - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(multiHop, // Feature ID + "Multi-hop", // Feature name + MULTIHOP_RELEASE, // released for android + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_true) FEATURE_SIMPLE(notificationControl, // Feature ID "Notification control", // Feature name "2.3", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_true) FEATURE_SIMPLE(serverUnavailableNotification, // Feature ID "Server unavailable notification", // Feature name "2.7", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_true) -FEATURE_SIMPLE(shareLogs, // Feature ID - "Share Logs", // Feature name - "2.6", // released - false, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(shareLogs, // Feature ID + "Share Logs", // Feature name + "2.6", // released + FeatureCallback_false, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_shareLogs) -FEATURE_SIMPLE(splitTunnel, // Feature ID - "Split-tunnel", // Feature name - "2.4", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(splitTunnel, // Feature ID + "Split-tunnel", // Feature name + "2.4", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_splitTunnel) -FEATURE_SIMPLE(startOnBoot, // Feature ID - "Start on boot", // Feature name - "2.0", // released - true, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(startOnBoot, // Feature ID + "Start on boot", // Feature name + "2.0", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_startOnBoot) FEATURE_SIMPLE(subscriptionManagement, // Feature ID "Subscription management", // Feature name "2.9", // released - true, // Can be flipped on - true, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_true) FEATURE_SIMPLE(unsecuredNetworkNotification, // Feature ID "Unsecured network notification", // Feature name "2.2", // released - true, // Can be flipped on - false, // Can be flipped off + FeatureCallback_true, // Can be flipped on + FeatureCallback_false, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_unsecuredNetworkNotification) -FEATURE_SIMPLE(websocket, // Feature ID - "WebSocket", // Feature name - "2.9.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies +FEATURE_SIMPLE(websocket, // Feature ID + "WebSocket", // Feature name + "2.9.0", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies FeatureCallback_true) diff --git a/src/featureslistcallback.h b/src/featureslistcallback.h index 25d3b86857a..0b7b20374aa 100644 --- a/src/featureslistcallback.h +++ b/src/featureslistcallback.h @@ -34,6 +34,8 @@ bool FeatureCallback_iosOrAndroid() { #endif } +bool FeatureCallback_inStaging() { return !Constants::inProduction(); } + // Custom callback functions // ------------------------- @@ -164,4 +166,12 @@ bool FeatureCallback_unsecuredNetworkNotification() { #endif } +bool FeatureCallback_freeTrial() { +#if defined(MVPN_IOS) + return true; +#else + return false; +#endif +} + #endif // FEATURELISTCALLBACK_H diff --git a/src/filterproxymodel.cpp b/src/filterproxymodel.cpp index 5daf26217a8..80a1405c695 100644 --- a/src/filterproxymodel.cpp +++ b/src/filterproxymodel.cpp @@ -52,17 +52,19 @@ void FilterProxyModel::setSource(QAbstractListModel* sourceModel) { if (sourceModel) { connect(sourceModel, &QAbstractItemModel::rowsInserted, this, - &FilterProxyModel::countChanged); + &FilterProxyModel::sourceChanged); connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, - &FilterProxyModel::countChanged); + &FilterProxyModel::sourceChanged); connect(sourceModel, &QAbstractItemModel::modelReset, this, - &FilterProxyModel::countChanged); + &FilterProxyModel::sourceChanged); connect(sourceModel, &QAbstractItemModel::layoutChanged, this, - &FilterProxyModel::countChanged); + &FilterProxyModel::sourceChanged); m_sourceModelRoleNames = sourceModel->roleNames(); } else { m_sourceModelRoleNames.clear(); } + + emit sourceChanged(); } QVariant FilterProxyModel::get(int pos) const { diff --git a/src/filterproxymodel.h b/src/filterproxymodel.h index fd828c3c285..c3bfa511577 100644 --- a/src/filterproxymodel.h +++ b/src/filterproxymodel.h @@ -22,8 +22,9 @@ class FilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { NOTIFY filterCallbackChanged) Q_PROPERTY(QJSValue sortCallback READ sortCallback WRITE setSortCallback NOTIFY sortCallbackChanged) - Q_PROPERTY(QAbstractListModel* source READ source WRITE setSource) - Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QAbstractListModel* source READ source WRITE setSource NOTIFY + sourceChanged) + Q_PROPERTY(int count READ count NOTIFY sourceChanged) public: FilterProxyModel(QObject* parent = 0); @@ -35,7 +36,7 @@ class FilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { signals: void filterCallbackChanged(); void sortCallbackChanged(); - void countChanged(); + void sourceChanged(); public: QJSValue filterCallback() const; diff --git a/src/frontend/navigator.h b/src/frontend/navigator.h index d8aacc8e85d..b2a4520965e 100644 --- a/src/frontend/navigator.h +++ b/src/frontend/navigator.h @@ -72,12 +72,13 @@ class Navigator final : public QObject { ~Navigator(); - Q_INVOKABLE void requestScreen(Screen screen, - LoadingFlags loadingFlags = NoFlags); + Q_INVOKABLE void requestScreen( + Navigator::Screen screen, Navigator::LoadingFlags loadingFlags = NoFlags); Q_INVOKABLE void requestPreviousScreen(); - Q_INVOKABLE void addStackView(Screen screen, const QVariant& stackView); - Q_INVOKABLE void addView(Screen screen, const QVariant& view); + Q_INVOKABLE void addStackView(Navigator::Screen screen, + const QVariant& stackView); + Q_INVOKABLE void addView(Navigator::Screen screen, const QVariant& view); Q_INVOKABLE bool eventHandled(); diff --git a/src/inspector/inspectorhandler.cpp b/src/inspector/inspectorhandler.cpp index d9cace6daad..70391935e7a 100644 --- a/src/inspector/inspectorhandler.cpp +++ b/src/inspector/inspectorhandler.cpp @@ -217,6 +217,25 @@ static QList s_settingCommands{ ? "true" : "false"; }}, + + InspectorSettingCommand{ + "addon/customServer", InspectorSettingCommand::Boolean, + [](const QByteArray& value) { + SettingsHolder::instance()->setAddonCustomServer(value == "true"); + }, + []() { + return SettingsHolder::instance()->addonCustomServer() ? "true" + : "false"; + }}, + + InspectorSettingCommand{ + "addon/customServerAddress", InspectorSettingCommand::String, + [](const QByteArray& value) { + SettingsHolder::instance()->setAddonCustomServerAddress(value); + }, + []() { + return SettingsHolder::instance()->addonCustomServerAddress(); + }}, }; struct InspectorCommand { @@ -252,7 +271,7 @@ static QList s_commands{ Q_ASSERT(vpn); vpn->reset(true); - vpn->hideAlert(); + ErrorHandler::instance()->hideAlert(); SettingsHolder* settingsHolder = SettingsHolder::instance(); @@ -491,7 +510,7 @@ static QList s_commands{ "Set Glean Source Tags (supply a comma seperated list)", 1, [](InspectorHandler*, const QList& arguments) { QStringList tags = QString(arguments[1]).split(','); - MozillaVPN::instance()->setGleanSourceTags(tags); + emit MozillaVPN::instance()->setGleanSourceTags(tags); return QJsonObject(); }}, @@ -956,6 +975,14 @@ static QList s_commands{ message.executeAction(); return QJsonObject(); }}, + + InspectorCommand{"set_installation_time", "Set the installation time", 1, + [](InspectorHandler*, const QList& arguments) { + qint64 epoch = arguments[1].toLongLong(); + SettingsHolder::instance()->setInstallationTime( + QDateTime::fromSecsSinceEpoch(epoch)); + return QJsonObject(); + }}, }; // static @@ -1059,7 +1086,7 @@ void InspectorHandler::networkRequestFinished(QNetworkReply* reply) { // Serialize the Response QJsonObject responseHeader; - for (auto headerPair : reply->rawHeaderPairs()) { + for (const auto& headerPair : reply->rawHeaderPairs()) { responseHeader[QString(headerPair.first)] = QString(headerPair.second); } response["headers"] = responseHeader; @@ -1072,7 +1099,7 @@ void InspectorHandler::networkRequestFinished(QNetworkReply* reply) { auto qrequest = reply->request(); // Serialize the Request QJsonArray requestHeaders; - for (auto header : qrequest.rawHeaderList()) { + for (const auto& header : qrequest.rawHeaderList()) { requestHeaders.append(QString(header)); } request["headers"] = requestHeaders; @@ -1150,7 +1177,6 @@ QJsonObject InspectorHandler::serialize(QQuickItem* item) { auto metaObject = item->metaObject(); int propertyCount = metaObject->propertyCount(); out["__propertyCount__"] = propertyCount; - QJsonArray props; for (int i = 0; i < metaObject->propertyCount(); i++) { auto property = metaObject->property(i); if (!property.isValid()) { diff --git a/src/ipaddresslookup.cpp b/src/ipaddresslookup.cpp index e1a5a6e1e00..c3e95b9c9cd 100644 --- a/src/ipaddresslookup.cpp +++ b/src/ipaddresslookup.cpp @@ -32,7 +32,6 @@ IpAddressLookup::~IpAddressLookup() { MVPN_COUNT_DTOR(IpAddressLookup); } void IpAddressLookup::initialize() { MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn, &MozillaVPN::stateChanged, this, &IpAddressLookup::stateChanged); diff --git a/src/keyregenerator.cpp b/src/keyregenerator.cpp index 2116c31d8a7..d22c627e6ba 100644 --- a/src/keyregenerator.cpp +++ b/src/keyregenerator.cpp @@ -22,7 +22,6 @@ KeyRegenerator::KeyRegenerator() { MVPN_COUNT_CTOR(KeyRegenerator); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn, &MozillaVPN::stateChanged, this, &KeyRegenerator::stateChanged); connect(vpn->controller(), &Controller::stateChanged, this, @@ -47,7 +46,6 @@ void KeyRegenerator::stateChanged() { } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->state() != MozillaVPN::StateMain || vpn->controller()->state() != Controller::StateOff) { @@ -78,7 +76,7 @@ void KeyRegenerator::stateChanged() { TaskScheduler::scheduleTask( new TaskAddDevice(Device::currentDeviceName(), Device::uniqueDeviceId())); - TaskScheduler::scheduleTask(new TaskAccount()); + TaskScheduler::scheduleTask(new TaskAccount(ErrorHandler::PropagateError)); m_timer.start(Constants::keyRegeneratorTimeSec() * 1000); } diff --git a/src/localsocketcontroller.cpp b/src/localsocketcontroller.cpp index e45d3acaa8a..ef9a286e8d9 100644 --- a/src/localsocketcontroller.cpp +++ b/src/localsocketcontroller.cpp @@ -10,7 +10,6 @@ #include "models/device.h" #include "models/keys.h" #include "models/server.h" -#include "mozillavpn.h" #include "settingsholder.h" #include @@ -67,7 +66,7 @@ void LocalSocketController::errorOccurred( emit initialized(false, false, QDateTime()); } - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); disconnectInternal(); } @@ -360,7 +359,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } if (type == "backendFailure") { - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); return; } diff --git a/src/models/devicemodel.cpp b/src/models/devicemodel.cpp index 3da2cba327b..4990819a60f 100644 --- a/src/models/devicemodel.cpp +++ b/src/models/devicemodel.cpp @@ -101,8 +101,8 @@ bool DeviceModel::fromJsonInternal(const Keys* keys, const QByteArray& json) { return false; } - QJsonArray devicesArray = devices.toArray(); - for (QJsonValue deviceValue : devicesArray) { + const QJsonArray devicesArray = devices.toArray(); + for (const QJsonValue& deviceValue : devicesArray) { Device device; if (!device.fromJson(deviceValue)) { return false; diff --git a/src/models/feature.cpp b/src/models/feature.cpp index 8c139594fc4..2b18297f882 100644 --- a/src/models/feature.cpp +++ b/src/models/feature.cpp @@ -43,7 +43,8 @@ Feature::Feature(const QString& id, const QString& name, bool isMajor, L18nStrings::String shortDesc_id, L18nStrings::String desc_id, const QString& imgPath, const QString& iconPath, const QString& linkUrl, const QString& aReleaseVersion, - bool flippableOn, bool flippableOff, + std::function&& flippableOn, + std::function&& flippableOff, const QStringList& featureDependencies, std::function&& callback) : QObject(qApp), @@ -57,10 +58,10 @@ Feature::Feature(const QString& id, const QString& name, bool isMajor, m_iconPath(iconPath), m_linkUrl(linkUrl), m_releaseVersion(aReleaseVersion), - m_flippableOn(flippableOn), - m_flippableOff(flippableOff), + m_flippableOn(std::move(flippableOn)), + m_flippableOff(std::move(flippableOff)), m_featureDependencies(featureDependencies), - m_callback(callback) { + m_callback(std::move(callback)) { logger.debug() << "Initializing feature" << id; Q_ASSERT(s_featuresHashtable); @@ -89,7 +90,7 @@ Feature::Feature(const QString& id, const QString& name, bool isMajor, SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); - if (m_flippableOn) { + if (m_flippableOn()) { if (settingsHolder->featuresFlippedOn().contains(m_id)) { m_state = FlippedOn; } @@ -98,7 +99,7 @@ Feature::Feature(const QString& id, const QString& name, bool isMajor, &Feature::maybeFlipOnOrOff); } - if (m_flippableOff) { + if (m_flippableOff()) { if (m_state == DefaultValue && settingsHolder->featuresFlippedOff().contains(m_id)) { m_state = FlippedOff; @@ -142,7 +143,7 @@ const Feature* Feature::get(const QString& featureID) { } bool Feature::isFlippedOn(bool ignoreCache) const { - if (!m_flippableOn) { + if (!m_flippableOn()) { return false; } @@ -154,7 +155,7 @@ bool Feature::isFlippedOn(bool ignoreCache) const { } bool Feature::isFlippedOff(bool ignoreCache) const { - if (!m_flippableOff) { + if (!m_flippableOff()) { return false; } @@ -258,7 +259,7 @@ void Feature::maybeFlipOnOrOff() { if (feature->isSupported(true)) continue; - if (!feature->m_flippableOn) { + if (!feature->m_flippableOn()) { logger.debug() << "Unable to activate feature" << id() << "because feature" << feature->id() << "cannot be enabled in dev mode"; @@ -297,11 +298,11 @@ void Feature::maybeFlipOnOrOff() { bool Feature::isToggleable() const { bool isOnByDefault = isSupportedIgnoringFlip(); - if (isOnByDefault && m_flippableOff) { + if (isOnByDefault && m_flippableOff()) { return true; } - if (!isOnByDefault && m_flippableOn) { + if (!isOnByDefault && m_flippableOn()) { return true; } diff --git a/src/models/feature.h b/src/models/feature.h index 081114164f8..453a484391f 100644 --- a/src/models/feature.h +++ b/src/models/feature.h @@ -46,7 +46,8 @@ class Feature : public QObject { L18nStrings::String displayName_id, L18nStrings::String shortDesc_id, L18nStrings::String desc_id, const QString& imgPath, const QString& iconPath, const QString& linkUrl, - const QString& releaseVersion, bool flippableOn, bool flippableOff, + const QString& releaseVersion, std::function&& flippableOn, + std::function&& flippableOff, const QStringList& otherFeatureDependencies, std::function&& callback); ~Feature(); @@ -70,8 +71,8 @@ class Feature : public QObject { // Checks if the feature is released ignoring the flip on/off bool isSupportedIgnoringFlip() const; - bool isFlippableOn() const { return m_flippableOn; } - bool isFlippableOff() const { return m_flippableOff; } + bool isFlippableOn() const { return m_flippableOn(); } + bool isFlippableOff() const { return m_flippableOff(); } bool isToggleable() const; @@ -133,10 +134,10 @@ class Feature : public QObject { // Version that the feature was released in const QString m_releaseVersion; - // If true, the feature can be enabled. - const bool m_flippableOn; - // If true, the feature can be disabled. - const bool m_flippableOff; + // If this callback returns true, the feature can be enabled. + std::function m_flippableOn; + // If this callback returns true, the feature can be disabled. + std::function m_flippableOff; // List of other features to be supported in order to support this one. const QStringList m_featureDependencies; diff --git a/src/models/featuremodel.cpp b/src/models/featuremodel.cpp index 686ac10d1de..4bd96f6e1e0 100644 --- a/src/models/featuremodel.cpp +++ b/src/models/featuremodel.cpp @@ -146,7 +146,7 @@ QVariant FeatureModel::data(const QModelIndex& index, int role) const { }; QObject* FeatureModel::get(const QString& feature) { - const Feature* f = Feature::get(feature); + const Feature* f = Feature::getOrNull(feature); auto obj = (QObject*)f; QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership); return obj; diff --git a/src/models/keys.cpp b/src/models/keys.cpp index dd9db1ce4a6..37d4134de7d 100644 --- a/src/models/keys.cpp +++ b/src/models/keys.cpp @@ -48,7 +48,7 @@ bool Keys::fromSettings() { } QJsonArray devicesArray = devices.toArray(); - for (QJsonValue deviceValue : devicesArray) { + for (const QJsonValue& deviceValue : devicesArray) { Device device; if (!device.fromJson(deviceValue)) { return false; diff --git a/src/models/server.cpp b/src/models/server.cpp index adb85d0d18f..db47b0c5c56 100644 --- a/src/models/server.cpp +++ b/src/models/server.cpp @@ -86,7 +86,7 @@ bool Server::fromJson(const QJsonObject& obj) { QList> prList; QJsonArray portRangesArray = portRanges.toArray(); - for (QJsonValue portRangeValue : portRangesArray) { + for (const QJsonValue& portRangeValue : portRangesArray) { if (!portRangeValue.isArray()) { return false; } @@ -193,6 +193,7 @@ uint32_t Server::choosePort() const { length += range.second - range.first + 1; } Q_ASSERT(length < 65536); + Q_ASSERT(length > 0); // Pick a port at random. quint32 r = QRandomGenerator::global()->generate() % length; diff --git a/src/models/servercity.cpp b/src/models/servercity.cpp index 6dc2bdfacc7..f890239f419 100644 --- a/src/models/servercity.cpp +++ b/src/models/servercity.cpp @@ -60,7 +60,7 @@ bool ServerCity::fromJson(const QJsonObject& obj) { QList servers; if (!Constants::inProduction() || !name.toString().contains("BETA")) { QJsonArray serversArray = serversValue.toArray(); - for (QJsonValue serverValue : serversArray) { + for (const QJsonValue& serverValue : serversArray) { if (!serverValue.isObject()) { return false; } diff --git a/src/models/servercountry.cpp b/src/models/servercountry.cpp index d697be0cf51..5375f58305a 100644 --- a/src/models/servercountry.cpp +++ b/src/models/servercountry.cpp @@ -50,7 +50,7 @@ bool ServerCountry::fromJson(const QJsonObject& countryObj) { QList scList; QJsonArray citiesArray = cities.toArray(); - for (QJsonValue cityValue : citiesArray) { + for (const QJsonValue& cityValue : citiesArray) { if (!cityValue.isObject()) { return false; } diff --git a/src/models/servercountrymodel.cpp b/src/models/servercountrymodel.cpp index 8be8b74d7af..944ac1f8aa7 100644 --- a/src/models/servercountrymodel.cpp +++ b/src/models/servercountrymodel.cpp @@ -79,7 +79,7 @@ bool ServerCountryModel::fromJsonInternal(const QByteArray& s) { } QJsonArray countriesArray = countries.toArray(); - for (QJsonValue countryValue : countriesArray) { + for (const QJsonValue& countryValue : countriesArray) { if (!countryValue.isObject()) { return false; } @@ -101,7 +101,9 @@ bool ServerCountryModel::fromJsonInternal(const QByteArray& s) { if (!cities.isArray()) { return false; } - for (QJsonValue cityValue : cities.toArray()) { + + QJsonArray cityArray = cities.toArray(); + for (const QJsonValue& cityValue : cityArray) { if (!cityValue.isObject()) { return false; } @@ -110,7 +112,9 @@ bool ServerCountryModel::fromJsonInternal(const QByteArray& s) { if (!servers.isArray()) { return false; } - for (QJsonValue serverValue : servers.toArray()) { + + QJsonArray serverArray = servers.toArray(); + for (const QJsonValue& serverValue : serverArray) { Server server; if (!server.fromJson(serverValue.toObject())) { return false; @@ -162,7 +166,7 @@ QVariant ServerCountryModel::data(const QModelIndex& index, int role) const { const QList& cities = country.cities(); for (const ServerCity& city : cities) { int activeServerCount = 0; - for (QString pubkey : city.servers()) { + for (const QString& pubkey : city.servers()) { if (m_servers.value(pubkey).cooldownTimeout() <= now) { activeServerCount++; } diff --git a/src/models/subscriptiondata.cpp b/src/models/subscriptiondata.cpp index 5aa0a52a924..b507220fcf3 100644 --- a/src/models/subscriptiondata.cpp +++ b/src/models/subscriptiondata.cpp @@ -163,7 +163,7 @@ bool SubscriptionData::fromJson(const QByteArray& json) { } QJsonArray subscriptionsList = paymentData["subscriptions"].toArray(); - for (QJsonValue subscriptionValue : subscriptionsList) { + for (const QJsonValue& subscriptionValue : subscriptionsList) { QJsonObject subscription = subscriptionValue.toObject(); if (subscription["product_id"] == Constants::privacyBundleProductId()) { m_isPrivacyBundleSubscriber = true; diff --git a/src/mozillavpn.cpp b/src/mozillavpn.cpp index 59eab600ee5..88ba2590d1d 100644 --- a/src/mozillavpn.cpp +++ b/src/mozillavpn.cpp @@ -75,9 +75,6 @@ #include #include -// in seconds, hide alerts -constexpr const uint32_t HIDE_ALERT_SEC = 4; - namespace { Logger logger(LOG_MAIN, "MozillaVPN"); MozillaVPN* s_instance = nullptr; @@ -100,12 +97,11 @@ MozillaVPN::MozillaVPN() : m_private(new Private()) { Q_ASSERT(!s_instance); s_instance = this; - connect(&m_alertTimer, &QTimer::timeout, this, - [this]() { setAlert(NoAlert); }); - connect(&m_periodicOperationsTimer, &QTimer::timeout, []() { TaskScheduler::scheduleTask(new TaskGroup( - {new TaskAccount(), new TaskServers(), new TaskCaptivePortalLookup(), + {new TaskAccount(ErrorHandler::DoNotPropagateError), + new TaskServers(ErrorHandler::DoNotPropagateError), + new TaskCaptivePortalLookup(ErrorHandler::DoNotPropagateError), new TaskHeartbeat(), new TaskGetFeatureList(), new TaskAddonIndex()})); }); @@ -194,6 +190,10 @@ MozillaVPN::~MozillaVPN() { delete m_private; } +ConnectionHealth* MozillaVPN::connectionHealth() { + return &m_private->m_connectionHealth; +} + Controller* MozillaVPN::controller() { return &m_private->m_controller; } MozillaVPN::State MozillaVPN::state() const { return m_state; } @@ -328,8 +328,10 @@ void MozillaVPN::initialize() { m_private->m_serverData.writeSettings(); } - QList refreshTasks{new TaskAccount(), new TaskServers(), - new TaskCaptivePortalLookup()}; + QList refreshTasks{ + new TaskAccount(ErrorHandler::PropagateError), + new TaskServers(ErrorHandler::PropagateError), + new TaskCaptivePortalLookup(ErrorHandler::PropagateError)}; if (Feature::get(Feature::Feature_inAppPurchase)->isSupported()) { refreshTasks.append(new TaskProducts()); @@ -350,13 +352,18 @@ void MozillaVPN::setState(State state) { emit MozillaVPN::instance()->recordGleanEventWithExtraKeys( GleanSample::appStep, {{"state", QVariant::fromValue(state).toString()}}); - // If we are activating the app, let's initialize the controller. - if (m_state == StateMain) { - m_private->m_controller.initialize(); + // If we have a token, we can start periodic operations. + // If the timer is already started, this is a no-op. + if (SettingsHolder::instance()->hasToken()) { startSchedulingPeriodicOperations(); } else { stopSchedulingPeriodicOperations(); } + + // If we are activating the app, let's initialize the controller. + if (m_state == StateMain) { + m_private->m_controller.initialize(); + } } void MozillaVPN::maybeStateMain() { @@ -401,7 +408,7 @@ void MozillaVPN::maybeStateMain() { if (!modelsInitialized()) { logger.warning() << "Models not initialized yet"; SettingsHolder::instance()->clear(); - errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); setUserState(UserNotAuthenticated); setState(StateInitialize); return; @@ -456,7 +463,7 @@ void MozillaVPN::authenticateWithType( setState(StateAuthenticating); - hideAlert(); + ErrorHandler::instance()->hideAlert(); if (m_userState != UserNotAuthenticated) { // If we try to start an authentication flow when already logged in, there @@ -500,13 +507,13 @@ void MozillaVPN::authenticationCompleted(const QByteArray& json, if (!m_private->m_user.fromJson(json)) { logger.error() << "Failed to parse the User JSON data"; - errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); return; } if (!m_private->m_deviceModel.fromJson(keys(), json)) { logger.error() << "Failed to parse the DeviceModel JSON data"; - errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); return; } @@ -570,7 +577,8 @@ void MozillaVPN::completeActivation() { } else { // Let's fetch the account and the servers. TaskScheduler::scheduleTask( - new TaskGroup({new TaskAccount(), new TaskServers()})); + new TaskGroup({new TaskAccount(ErrorHandler::PropagateError), + new TaskServers(ErrorHandler::PropagateError)})); } if (Feature::get(Feature::Feature_inAppPurchase)->isSupported()) { @@ -679,12 +687,6 @@ void MozillaVPN::deviceRemovalCompleted(const QString& publicKey) { void MozillaVPN::removeDeviceFromPublicKey(const QString& publicKey) { logger.debug() << "Remove device"; - const Device* device = - m_private->m_deviceModel.deviceFromPublicKey(publicKey); - if (!device) { - return; - } - // Let's emit a signal to inform the user about the starting of the device // removal. The front-end code will show a loading icon or something // similar. @@ -807,6 +809,11 @@ bool MozillaVPN::checkCurrentDevice() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); + // We are not able to check the device at this stage. + if (m_state == StateDeviceLimit) { + return false; + } + if (m_private->m_deviceModel.hasCurrentDevice(keys())) { return true; } @@ -835,7 +842,7 @@ bool MozillaVPN::checkCurrentDevice() { void MozillaVPN::logout() { logger.debug() << "Logout"; - setAlert(LogoutAlert); + ErrorHandler::instance()->setAlert(ErrorHandler::LogoutAlert); setUserState(UserLoggingOut); TaskScheduler::deleteTasks(); @@ -852,7 +859,18 @@ void MozillaVPN::logout() { } if (m_private->m_deviceModel.hasCurrentDevice(keys())) { - TaskScheduler::scheduleTask(new TaskRemoveDevice(keys()->publicKey())); + TaskScheduler::scheduleTask(new TaskGroup( + {new TaskRemoveDevice(keys()->publicKey()), + // Immediately after the scheduling of the device removal, we want to + // delete the session token, so that, in case the app is terminated, at + // the next execution we go back to the init screen. + new TaskFunction([this]() { reset(false); })})); + + // In case the app is closed even before scheduling the previous TaskGroup, + // removing the key we will enforce a new authentication at the first + // TaskAccount execution. + m_private->m_keys.forgetKeys(); + return; } TaskScheduler::scheduleTask(new TaskFunction([this]() { reset(false); })); @@ -880,102 +898,6 @@ void MozillaVPN::reset(bool forceInitialState) { } } -void MozillaVPN::setAlert(AlertType alert) { - m_alertTimer.stop(); - - if (alert != NoAlert) { - m_alertTimer.start(1000 * HIDE_ALERT_SEC); - } - - m_alert = alert; - emit alertChanged(); -} - -void MozillaVPN::errorHandle(ErrorHandler::ErrorType error) { - logger.debug() << "Handling error" << error; - - Q_ASSERT(error != ErrorHandler::NoError); - - AlertType alert = NoAlert; - - switch (error) { - case ErrorHandler::VPNDependentConnectionError: - if (controller()->state() == Controller::State::StateOn || - controller()->state() == Controller::State::StateConfirming) { - // connection likely isn't stable yet - logger.error() << "Ignore network error probably caused by enabled VPN"; - return; - } else if (controller()->state() == Controller::State::StateOff) { - // We are off, so this means a request failed, not the - // VPN. Change it to No Connection - alert = NoConnectionAlert; - break; - } - [[fallthrough]]; - case ErrorHandler::ConnectionFailureError: - alert = ConnectionFailedAlert; - break; - - case ErrorHandler::NoConnectionError: - if (connectionHealth()->isUnsettled()) { - return; - } - alert = NoConnectionAlert; - break; - - case ErrorHandler::AuthenticationError: - alert = AuthenticationFailedAlert; - break; - - case ErrorHandler::ControllerError: - alert = ControllerErrorAlert; - break; - - case ErrorHandler::RemoteServiceError: - alert = RemoteServiceErrorAlert; - break; - - case ErrorHandler::SubscriptionFailureError: - alert = SubscriptionFailureAlert; - break; - - case ErrorHandler::GeoIpRestrictionError: - alert = GeoIpRestrictionAlert; - break; - - case ErrorHandler::UnrecoverableError: - alert = UnrecoverableErrorAlert; - break; - - default: - break; - } - - setAlert(alert); - - logger.error() << "Alert:" << alert << "State:" << m_state; - - if (alert == NoAlert) { - return; - } - - // Any error in authenticating state sends to the Initial state. - if (m_state == StateAuthenticating) { - if (alert == GeoIpRestrictionAlert) { - emit recordGleanEvent(GleanSample::authenticationFailureByGeo); - } else { - emit recordGleanEvent(GleanSample::authenticationFailure); - } - reset(true); - return; - } - - if (alert == AuthenticationFailedAlert) { - reset(true); - return; - } -} - void MozillaVPN::setServerCooldown(const QString& publicKey) { m_private->m_serverCountryModel.setServerCooldown( publicKey, Constants::SERVER_UNRESPONSIVE_COOLDOWN_SEC); @@ -1386,13 +1308,14 @@ void MozillaVPN::refreshDevices() { if (m_state == StateMain) { TaskScheduler::scheduleTask( - new TaskGroup({new TaskAccount(), new TaskServers()})); + new TaskGroup({new TaskAccount(ErrorHandler::DoNotPropagateError), + new TaskServers(ErrorHandler::DoNotPropagateError)})); } } void MozillaVPN::quit() { logger.debug() << "quit"; - TaskScheduler::deleteTasks(); + TaskScheduler::forceDeleteTasks(); #if QT_VERSION >= 0x060000 && QT_VERSION < 0x060300 // Qt5Compat.GraphicalEffects makes the app crash on shutdown. Let's do a @@ -1512,11 +1435,13 @@ void MozillaVPN::subscriptionFailedInternal(bool canceledByUser) { setState(StateSubscriptionNeeded); if (!canceledByUser) { - errorHandle(ErrorHandler::SubscriptionFailureError); + ErrorHandler::instance()->errorHandle( + ErrorHandler::SubscriptionFailureError); } TaskScheduler::scheduleTask( - new TaskGroup({new TaskAccount(), new TaskServers()})); + new TaskGroup({new TaskAccount(ErrorHandler::PropagateError), + new TaskServers(ErrorHandler::PropagateError)})); TaskScheduler::scheduleTask(new TaskFunction([this]() { if (!m_private->m_user.subscriptionNeeded() && m_state == StateSubscriptionNeeded) { @@ -1619,7 +1544,8 @@ void MozillaVPN::addCurrentDeviceAndRefreshData() { TaskScheduler::scheduleTask( new TaskAddDevice(Device::currentDeviceName(), Device::uniqueDeviceId())); TaskScheduler::scheduleTask( - new TaskGroup({new TaskAccount(), new TaskServers()})); + new TaskGroup({new TaskAccount(ErrorHandler::PropagateError), + new TaskServers(ErrorHandler::PropagateError)})); } void MozillaVPN::openAppStoreReviewLink() { @@ -1657,7 +1583,7 @@ void MozillaVPN::maybeRegenerateDeviceKey() { TaskScheduler::scheduleTask(new TaskFunction([this]() { if (!modelsInitialized()) { logger.error() << "Failed to complete the key regeneration"; - errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); setUserState(UserNotAuthenticated); return; } @@ -1676,6 +1602,12 @@ void MozillaVPN::hardResetAndQuit() { quit(); } +void MozillaVPN::exitForUnrecoverableError(const QString& reason) { + Q_ASSERT(!reason.isEmpty()); + logger.error() << "Unrecoverable error detected: " << reason; + quit(); +} + void MozillaVPN::crashTest() { logger.debug() << "Crashing Application"; char* text = new char[100]; @@ -1699,9 +1631,6 @@ QString MozillaVPN::devVersion() { // static QString MozillaVPN::graphicsApi() { -#if QT_VERSION < 0x060000 - return "qt5-angle"; -#else QQuickWindow* window = qobject_cast(QmlEngineHolder::instance()->window()); Q_ASSERT(window); @@ -1723,7 +1652,6 @@ QString MozillaVPN::graphicsApi() { default: return "unknown"; } -#endif } void MozillaVPN::requestDeleteAccount() { diff --git a/src/mozillavpn.h b/src/mozillavpn.h index a668faea1c4..2833c865d20 100644 --- a/src/mozillavpn.h +++ b/src/mozillavpn.h @@ -80,24 +80,8 @@ class MozillaVPN final : public QObject { }; Q_ENUM(UserState); - enum AlertType { - NoAlert, - AuthenticationFailedAlert, - ConnectionFailedAlert, - LogoutAlert, - NoConnectionAlert, - ControllerErrorAlert, - RemoteServiceErrorAlert, - SubscriptionFailureAlert, - GeoIpRestrictionAlert, - UnrecoverableErrorAlert, - AuthCodeSentAlert, - }; - Q_ENUM(AlertType) - private: Q_PROPERTY(State state READ state NOTIFY stateChanged) - Q_PROPERTY(AlertType alert READ alert NOTIFY alertChanged) Q_PROPERTY(QString devVersion READ devVersion CONSTANT) Q_PROPERTY(const Env* env READ env CONSTANT) Q_PROPERTY(UserState userState READ userState NOTIFY userStateChanged) @@ -105,8 +89,6 @@ class MozillaVPN final : public QObject { Q_PROPERTY(bool updating READ updating NOTIFY updatingChanged) Q_PROPERTY(bool stagingMode READ stagingMode CONSTANT) Q_PROPERTY(bool debugMode READ debugMode CONSTANT) - Q_PROPERTY(QString currentView READ currentView WRITE setCurrentView NOTIFY - currentViewChanged) public: MozillaVPN(); @@ -121,7 +103,6 @@ class MozillaVPN final : public QObject { void initialize(); State state() const; - AlertType alert() const { return m_alert; } const QString& exitServerPublicKey() const { return m_exitServerPublicKey; } const QString& entryServerPublicKey() const { return m_entryServerPublicKey; } @@ -139,8 +120,6 @@ class MozillaVPN final : public QObject { Q_INVOKABLE void authenticate(); Q_INVOKABLE void cancelAuthentication(); Q_INVOKABLE void removeDeviceFromPublicKey(const QString& publicKey); - Q_INVOKABLE void hideAlert() { setAlert(NoAlert); } - Q_INVOKABLE void setAlert(AlertType alert); Q_INVOKABLE void postAuthenticationCompleted(); Q_INVOKABLE void telemetryPolicyCompleted(); Q_INVOKABLE void mainWindowLoaded(); @@ -164,6 +143,7 @@ class MozillaVPN final : public QObject { Q_INVOKABLE bool validateUserDNS(const QString& dns) const; Q_INVOKABLE void hardResetAndQuit(); Q_INVOKABLE void crashTest(); + Q_INVOKABLE void exitForUnrecoverableError(const QString& reason); Q_INVOKABLE void requestDeleteAccount(); Q_INVOKABLE void cancelReauthentication(); Q_INVOKABLE void updateViewShown(); @@ -182,9 +162,7 @@ class MozillaVPN final : public QObject { ConnectionBenchmark* connectionBenchmark() { return &m_private->m_connectionBenchmark; } - ConnectionHealth* connectionHealth() { - return &m_private->m_connectionHealth; - } + ConnectionHealth* connectionHealth(); Controller* controller(); ServerData* currentServer() { return &m_private->m_serverData; } DeviceModel* deviceModel() { return &m_private->m_deviceModel; } @@ -234,8 +212,6 @@ class MozillaVPN final : public QObject { const QList entryServers() const; bool multihop() const { return m_private->m_serverData.multihop(); } - void errorHandle(ErrorHandler::ErrorType error); - void abortAuthentication(); void changeServer(const QString& countryCode, const QString& city, @@ -287,12 +263,6 @@ class MozillaVPN final : public QObject { void addCurrentDeviceAndRefreshData(); - const QString& currentView() const { return m_currentView; } - void setCurrentView(const QString& name) { - m_currentView = name; - emit currentViewChanged(); - } - void createTicketAnswerRecieved(bool successful) { emit ticketCreationAnswer(successful); } @@ -353,7 +323,6 @@ class MozillaVPN final : public QObject { signals: void stateChanged(); - void alertChanged(); void userStateChanged(); void deviceRemoving(const QString& publicKey); void aboutNeeded(); @@ -373,8 +342,6 @@ class MozillaVPN final : public QObject { void logsReady(const QString& logs); - void currentViewChanged(); - void ticketCreationAnswer(bool successful); private: @@ -411,15 +378,12 @@ class MozillaVPN final : public QObject { Private* m_private = nullptr; State m_state = StateInitialize; - AlertType m_alert = NoAlert; - QString m_currentView; UserState m_userState = UserNotAuthenticated; QString m_exitServerPublicKey; QString m_entryServerPublicKey; - QTimer m_alertTimer; QTimer m_periodicOperationsTimer; QTimer m_gleanTimer; diff --git a/src/networkrequest.cpp b/src/networkrequest.cpp index 1cda5711a43..de3db3f217c 100644 --- a/src/networkrequest.cpp +++ b/src/networkrequest.cpp @@ -147,6 +147,50 @@ NetworkRequest* NetworkRequest::createForGetHostAddress( return r; } +// static +NetworkRequest* NetworkRequest::createForUploadData(Task* parent, + const QString& url, + QIODevice* uploadData) { + Q_ASSERT(parent); + Q_ASSERT(uploadData); + QUrl requestUrl(url); + + NetworkRequest* r = new NetworkRequest(parent, 200, false); + r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + r->m_request.setUrl(url); + + r->uploadDataRequest(uploadData); + return r; +} + +// static +NetworkRequest* NetworkRequest::createForUploadDataHostAddress( + Task* parent, const QString& url, QIODevice* uploadData, + const QHostAddress& address) { + Q_ASSERT(parent); + Q_ASSERT(uploadData); + QUrl requestUrl(url); + QString hostname = requestUrl.host(); + + NetworkRequest* r = new NetworkRequest(parent, 200, false); + r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + + // Rewrite the request URL to use an explicit host address. + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + requestUrl.setHost("[" + address.toString() + "]"); + } else { + requestUrl.setHost(address.toString()); + } + r->m_request.setUrl(requestUrl); + r->m_request.setRawHeader("Host", hostname.toLocal8Bit()); + r->m_request.setPeerVerifyName(hostname); + + r->uploadDataRequest(uploadData); + return r; +} + // static NetworkRequest* NetworkRequest::createForAuthenticationVerification( Task* parent, const QString& pkceCodeSuccess, @@ -190,7 +234,7 @@ NetworkRequest* NetworkRequest::createForAdjustProxy( r->m_request.setUrl(url); QJsonObject headersObj; - for (QPair header : headers) { + for (const QPair& header : headers) { headersObj.insert(header.first, header.second); } @@ -202,7 +246,7 @@ NetworkRequest* NetworkRequest::createForAdjustProxy( obj.insert("bodyParameters", bodyParameters); QJsonArray unknownParametersArray; - for (QString unknownParameter : unknownParameters) { + for (const QString& unknownParameter : unknownParameters) { unknownParametersArray.append(unknownParameter); } obj.insert("unknownParameters", unknownParametersArray); @@ -251,7 +295,6 @@ NetworkRequest* NetworkRequest::createForDeviceRemoval(Task* parent, url.append("/api/v1/vpn/device/"); url.append(QUrl::toPercentEncoding(pubKey)); - QUrl u(url); r->m_request.setUrl(QUrl(url)); #ifdef MVPN_DEBUG @@ -1049,6 +1092,13 @@ void NetworkRequest::postRequest(const QByteArray& body) { m_timer.start(REQUEST_TIMEOUT_MSEC); } +void NetworkRequest::uploadDataRequest(QIODevice* data) { + QNetworkAccessManager* manager = + NetworkManager::instance()->networkAccessManager(); + handleReply(manager->post(m_request, data)); + m_timer.start(REQUEST_TIMEOUT_MSEC); +} + void NetworkRequest::handleReply(QNetworkReply* reply) { Q_ASSERT(reply); Q_ASSERT(!m_reply); @@ -1069,7 +1119,11 @@ void NetworkRequest::handleReply(QNetworkReply* reply) { &NetworkRequest::maybeDeleteLater); connect(m_reply, &QNetworkReply::downloadProgress, this, [&](qint64 bytesReceived, qint64 bytesTotal) { - requestUpdated(bytesReceived, bytesTotal, m_reply); + emit requestUpdated(bytesReceived, bytesTotal, m_reply); + }); + connect(m_reply, &QNetworkReply::uploadProgress, this, + [&](qint64 bytesSent, qint64 bytesTotal) { + uploadProgressed(bytesSent, bytesTotal, m_reply); }); } diff --git a/src/networkrequest.h b/src/networkrequest.h index fb01ec4d317..3c4f295c161 100644 --- a/src/networkrequest.h +++ b/src/networkrequest.h @@ -28,10 +28,18 @@ class NetworkRequest final : public QObject { static NetworkRequest* createForGetUrl(Task* parent, const QString& url, int status = 0); + static NetworkRequest* createForGetHostAddress(Task* parent, const QString& url, const QHostAddress& address); + static NetworkRequest* createForUploadData(Task* parent, const QString& url, + QIODevice* uploadData); + + static NetworkRequest* createForUploadDataHostAddress( + Task* parent, const QString& url, QIODevice* uploadData, + const QHostAddress& address); + static NetworkRequest* createForAuthenticationVerification( Task* parent, const QString& pkceCodeSuccess, const QString& pkceCodeVerifier); @@ -168,6 +176,7 @@ class NetworkRequest final : public QObject { void deleteRequest(); void getRequest(); void postRequest(const QByteArray& body); + void uploadDataRequest(QIODevice* data); void handleReply(QNetworkReply* reply); void handleHeaderReceived(); @@ -196,6 +205,8 @@ class NetworkRequest final : public QObject { void requestCompleted(const QByteArray& data); void requestUpdated(qint64 bytesReceived, qint64 bytesTotal, QNetworkReply* reply); + void uploadProgressed(qint64 bytesReceived, qint64 bytesTotal, + QNetworkReply* reply); private: QNetworkRequest m_request; diff --git a/src/networkwatcher.cpp b/src/networkwatcher.cpp index 60f91e10317..27ec505691f 100644 --- a/src/networkwatcher.cpp +++ b/src/networkwatcher.cpp @@ -121,7 +121,6 @@ void NetworkWatcher::unsecuredNetwork(const QString& networkName, } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->state() != MozillaVPN::StateMain) { logger.debug() << "VPN not ready. Ignoring unsecured network"; diff --git a/src/networkwatcherimpl.h b/src/networkwatcherimpl.h index 0ad843bb13e..d156c0db55b 100644 --- a/src/networkwatcherimpl.h +++ b/src/networkwatcherimpl.h @@ -45,7 +45,7 @@ class NetworkWatcherImpl : public QObject { void networkChanged(QString newBSSID); // Fired when the Device changed the Type of Transport - void transportChanged(TransportType transportType); + void transportChanged(NetworkWatcherImpl::TransportType transportType); private: bool m_active = false; diff --git a/src/notificationhandler.cpp b/src/notificationhandler.cpp index 6bc18654bd5..5f53a52ff33 100644 --- a/src/notificationhandler.cpp +++ b/src/notificationhandler.cpp @@ -37,6 +37,13 @@ NotificationHandler* s_instance = nullptr; // static NotificationHandler* NotificationHandler::create(QObject* parent) { + NotificationHandler* handler = createInternal(parent); + handler->initialize(); + return handler; +} + +// static +NotificationHandler* NotificationHandler::createInternal(QObject* parent) { #if defined(MVPN_IOS) return new IOSNotificationHandler(parent); #endif @@ -119,10 +126,9 @@ void NotificationHandler::showNotification() { //: Shown as message body in a notification. %1 and %3 are countries, %2 //: and %4 are cities. message = qtTrId("vpn.systray.statusSwtich.message") - .arg(m_switchingLocalizedServerCountry) - .arg(m_switchingLocalizedServerCity) - .arg(localizedCountryName) - .arg(localizedCityName); + .arg(m_switchingLocalizedServerCountry, + m_switchingLocalizedServerCity, localizedCountryName, + localizedCityName); } else { if (!SettingsHolder::instance()->connectionChangeNotification()) { // Notifications for ConnectionChange are disabled @@ -134,8 +140,7 @@ void NotificationHandler::showNotification() { //: Shown as message body in a notification. %1 is the country, %2 is //: the city. message = qtTrId("vpn.systray.statusConnected.message") - .arg(localizedCountryName) - .arg(localizedCityName); + .arg(localizedCountryName, localizedCityName); } break; @@ -153,8 +158,7 @@ void NotificationHandler::showNotification() { //: Shown as message body in a notification. %1 is the country, %2 is //: the city. message = qtTrId("vpn.systray.statusDisconnected.message") - .arg(localizedCountryName) - .arg(localizedCityName); + .arg(localizedCountryName, localizedCityName); } break; diff --git a/src/notificationhandler.h b/src/notificationhandler.h index c8f96e4dcce..8596e513005 100644 --- a/src/notificationhandler.h +++ b/src/notificationhandler.h @@ -52,7 +52,7 @@ class NotificationHandler : public QObject { signals: void notificationShown(const QString& title, const QString& message); - void notificationClicked(Message message); + void notificationClicked(NotificationHandler::Message message); protected: explicit NotificationHandler(QObject* parent); @@ -60,7 +60,11 @@ class NotificationHandler : public QObject { virtual void notify(Message type, const QString& title, const QString& message, int timerMsec) = 0; + virtual void initialize() {} + private: + static NotificationHandler* createInternal(QObject* parent); + virtual void notifyInternal(Message type, const QString& title, const QString& message, int timerMsec); diff --git a/src/platforms/android/androidapplistprovider.cpp b/src/platforms/android/androidapplistprovider.cpp index dd16857df2a..de96d9774ef 100644 --- a/src/platforms/android/androidapplistprovider.cpp +++ b/src/platforms/android/androidapplistprovider.cpp @@ -38,7 +38,10 @@ void AndroidAppListProvider::getApplicationList() { QStringList keys = listObj.keys(); QMap out; - foreach (auto key, keys) { out[key] = listObj[key].toString(); } + foreach (auto key, keys) { + // This comment is to make linter happy. + out[key] = listObj[key].toString(); + } emit newAppList(out); } diff --git a/src/platforms/android/androidcontroller.cpp b/src/platforms/android/androidcontroller.cpp index 2c33b7abc8b..bbeb2d7382c 100644 --- a/src/platforms/android/androidcontroller.cpp +++ b/src/platforms/android/androidcontroller.cpp @@ -93,7 +93,7 @@ AndroidController::AndroidController() { logger.error() << "Service Error while activating the VPN: " << parcelBody; } - MozillaVPN::instance()->errorHandle( + ErrorHandler::instance()->errorHandle( ErrorHandler::ConnectionFailureError); emit disconnected(); }, @@ -101,7 +101,7 @@ AndroidController::AndroidController() { connect( activity, &AndroidVPNActivity::serviceDisconnected, this, []() { - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); }, Qt::QueuedConnection); } diff --git a/src/platforms/android/androidiaphandler.cpp b/src/platforms/android/androidiaphandler.cpp index 19991e2b372..efc744a118d 100644 --- a/src/platforms/android/androidiaphandler.cpp +++ b/src/platforms/android/androidiaphandler.cpp @@ -319,14 +319,13 @@ void AndroidIAPHandler::validatePurchase(QJsonObject purchase) { TaskPurchase* purchaseTask = TaskPurchase::createForAndroid(sku, token); Q_ASSERT(purchaseTask); - connect( - purchaseTask, &TaskPurchase::failed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Purchase validation request to guardian failed"; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - stopSubscription(); - emit subscriptionNotValidated(); - }); + connect(purchaseTask, &TaskPurchase::failed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Purchase validation request to guardian failed"; + ErrorHandler::networkErrorHandle(error); + stopSubscription(); + emit subscriptionNotValidated(); + }); connect(purchaseTask, &TaskPurchase::succeeded, this, [this, token](const QByteArray& data) { diff --git a/src/platforms/android/androidnotificationhandler.cpp b/src/platforms/android/androidnotificationhandler.cpp index 71e99099bb4..2e0631823dd 100644 --- a/src/platforms/android/androidnotificationhandler.cpp +++ b/src/platforms/android/androidnotificationhandler.cpp @@ -18,14 +18,17 @@ Logger logger(LOG_ANDROID, "AndroidNotificationHandler"); AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(AndroidNotificationHandler); +} + +AndroidNotificationHandler::~AndroidNotificationHandler() { + MVPN_COUNT_DTOR(AndroidNotificationHandler); +} +void AndroidNotificationHandler::initialize() { connect(AndroidVPNActivity::instance(), &AndroidVPNActivity::serviceConnected, this, &AndroidNotificationHandler::applyStrings, Qt::QueuedConnection); } -AndroidNotificationHandler::~AndroidNotificationHandler() { - MVPN_COUNT_DTOR(AndroidNotificationHandler); -} void AndroidNotificationHandler::notify(NotificationHandler::Message type, const QString& title, @@ -53,4 +56,4 @@ void AndroidNotificationHandler::applyStrings() { QJsonDocument doc(localisedMessages); AndroidVPNActivity::sendToService( ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); -} \ No newline at end of file +} diff --git a/src/platforms/android/androidnotificationhandler.h b/src/platforms/android/androidnotificationhandler.h index 505708fa40b..01d6db6565c 100644 --- a/src/platforms/android/androidnotificationhandler.h +++ b/src/platforms/android/androidnotificationhandler.h @@ -22,6 +22,8 @@ class AndroidNotificationHandler final : public NotificationHandler { private: void applyStrings(); + + void initialize() override; }; #endif // ANDROIDNOTIFICATIONHANDLER_H diff --git a/src/platforms/android/androidsharedprefs.cpp b/src/platforms/android/androidsharedprefs.cpp index 1353c637acf..a254468e5a9 100644 --- a/src/platforms/android/androidsharedprefs.cpp +++ b/src/platforms/android/androidsharedprefs.cpp @@ -75,7 +75,6 @@ QVariant AndroidSharedPrefs::GetValue(const QString& fileName, stringElementList.at(x); // value auto attribName = stringNode.attributes().namedItem("name"); if (attribName.nodeValue() == prefKey) { - QString val = stringNode.nodeValue(); auto textNode = stringNode.firstChild(); if (!textNode.isText()) { logger.warning() << "Non TextNode Value? " << textNode.nodeValue(); diff --git a/src/platforms/dummy/dummyappimageprovider.cpp b/src/platforms/dummy/dummyappimageprovider.cpp deleted file mode 100644 index 52ee8aa9fcd..00000000000 --- a/src/platforms/dummy/dummyappimageprovider.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* 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 "dummyappimageprovider.h" -#include "logger.h" -#include "leakdetector.h" - -namespace { -Logger logger(LOG_CONTROLLER, "DummyAppImageProvider"); -} - -DummyAppImageProvider::DummyAppImageProvider(QObject* parent) - : QQuickImageProvider(QQuickImageProvider::Pixmap), QObject(parent) { - MVPN_COUNT_CTOR(DummyAppImageProvider); -} - -DummyAppImageProvider::~DummyAppImageProvider() { - MVPN_COUNT_DTOR(DummyAppImageProvider); -} - -// Returns an example image for any provided APPID (a red square) -QPixmap DummyAppImageProvider::requestPixmap(const QString& id, QSize* size, - const QSize& requestedSize) { - logger.debug() << "Request Image for " << id; - int width = 50; - int height = 50; - - if (size) *size = QSize(width, height); - QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width, - requestedSize.height() > 0 ? requestedSize.height() : height); - pixmap.fill(QColor("red").rgba()); - return pixmap; -} diff --git a/src/platforms/dummy/dummyappimageprovider.h b/src/platforms/dummy/dummyappimageprovider.h deleted file mode 100644 index b0d7dd25b23..00000000000 --- a/src/platforms/dummy/dummyappimageprovider.h +++ /dev/null @@ -1,19 +0,0 @@ -/* 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/. */ - -#ifndef DUMMYAPPIMAGEPROVIDER_H -#define DUMMYAPPIMAGEPROVIDER_H - -#include -#include - -class DummyAppImageProvider : public QQuickImageProvider, public QObject { - public: - DummyAppImageProvider(QObject* parent); - ~DummyAppImageProvider(); - QPixmap requestPixmap(const QString& id, QSize* size, - const QSize& requestedSize) override; -}; - -#endif // DUMMYAPPIMAGEPROVIDER_H diff --git a/src/platforms/ios/ioscontroller.mm b/src/platforms/ios/ioscontroller.mm index 0a7c08aa427..5d98645d322 100644 --- a/src/platforms/ios/ioscontroller.mm +++ b/src/platforms/ios/ioscontroller.mm @@ -4,6 +4,7 @@ #include "ioscontroller.h" #include "Mozilla_VPN-Swift.h" +#include "controller.h" #include "device.h" #include "ipaddress.h" #include "keys.h" @@ -81,8 +82,13 @@ return; } case ConnectionStateDisconnected: - // Just in case we are connecting, let's call disconnect. - [impl disconnect]; + Controller* controller = MozillaVPN::instance()->controller(); + Q_ASSERT(controller); + if (controller->state() != Controller::StateInitializing) { + // Just in case we are connecting, let's call disconnect. + [impl disconnect]; + } + emit initialized(true, false, QDateTime()); return; } diff --git a/src/platforms/ios/ioscontroller.swift b/src/platforms/ios/ioscontroller.swift index f5d3f321049..68315692756 100644 --- a/src/platforms/ios/ioscontroller.swift +++ b/src/platforms/ios/ioscontroller.swift @@ -106,7 +106,40 @@ public class IOSControllerImpl : NSObject { return } - stateChangeCallback?(session.status == .connected) + // If disconnected, we know for sure that this is true + if (session.status == .disconnected) { + stateChangeCallback?(false) + return + } + + // This timer is used to workaround a Schrödinger's cat bug: if we check + // the connection status immediately when connected, we alter the iOS16 + // connectivity state and we break the VPN tunnel (intermittently). With + // a timer this does not happen. + _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) {_ in + self.monitorConnection() + } + } + + private func monitorConnection() -> Void { + // Let's call `stateChangeCallback` if connected and if + // last_handshake_time_sec is not 0. + self.checkStatus { _, _, configString in + if self.tunnel?.connection.status != .connected { return; } + + let lines = configString.splitToArray(separator: "\n") + if let line = lines.first(where: { $0.starts(with: "last_handshake_time_sec") }) { + let parts = line.splitToArray(separator: "=") + if parts.count > 1 && Int(parts[1]) ?? 0 > 0 { + self.stateChangeCallback?(true) + return + } + } + + _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in + self.monitorConnection() + } + } } private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool { diff --git a/src/platforms/ios/iosdatamigration.mm b/src/platforms/ios/iosdatamigration.mm index 0bc5dcdce17..492903db105 100644 --- a/src/platforms/ios/iosdatamigration.mm +++ b/src/platforms/ios/iosdatamigration.mm @@ -21,7 +21,6 @@ void migrateUserDefaultData() { MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); NSUserDefaults* sud = [NSUserDefaults standardUserDefaults]; if (!sud) { diff --git a/src/platforms/ios/iosiaphandler.mm b/src/platforms/ios/iosiaphandler.mm index 4c80fdd42a3..4b0f60a0f0e 100644 --- a/src/platforms/ios/iosiaphandler.mm +++ b/src/platforms/ios/iosiaphandler.mm @@ -339,7 +339,7 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue QJsonDocument json = QJsonDocument::fromJson(data); if (!json.isObject()) { - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); emit subscriptionFailed(); ErrorHandler::instance()->subscriptionGenericError(); return; @@ -348,7 +348,7 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue QJsonObject obj = json.object(); QJsonValue errorValue = obj.value("errno"); if (!errorValue.isDouble()) { - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); emit subscriptionFailed(); ErrorHandler::instance()->subscriptionGenericError(); return; @@ -356,14 +356,14 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue int errorNumber = errorValue.toInt(); if (errorNumber == GUARDIAN_ERROR_RECEIPT_NOT_VALID) { - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); emit subscriptionFailed(); ErrorHandler::instance()->subscriptionExpiredError(); return; } if (errorNumber == GUARDIAN_ERROR_RECEIPT_IN_USE) { - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); emit subscriptionFailed(); ErrorHandler::instance()->subscriptionInUseError(); return; diff --git a/src/platforms/ios/ioslogger.swift b/src/platforms/ios/ioslogger.swift index deb2285ea5b..9f873755d95 100644 --- a/src/platforms/ios/ioslogger.swift +++ b/src/platforms/ios/ioslogger.swift @@ -37,8 +37,7 @@ public class Logger { appVersion += " (\(appBuild))" } - let goBackendVersion = WIREGUARD_GO_VERSION - Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)") + Logger.global?.log(message: "App version: \(appVersion)") } } diff --git a/src/platforms/ios/iosnotificationhandler.h b/src/platforms/ios/iosnotificationhandler.h index b05898989d5..e1579108d06 100644 --- a/src/platforms/ios/iosnotificationhandler.h +++ b/src/platforms/ios/iosnotificationhandler.h @@ -20,6 +20,9 @@ class IOSNotificationHandler final : public NotificationHandler { void notify(Message type, const QString& title, const QString& message, int timerMsec) override; + private: + void initialize() override; + private: void* m_delegate = nullptr; }; diff --git a/src/platforms/ios/iosnotificationhandler.mm b/src/platforms/ios/iosnotificationhandler.mm index 636c8ea65c5..38cb284ba3b 100644 --- a/src/platforms/ios/iosnotificationhandler.mm +++ b/src/platforms/ios/iosnotificationhandler.mm @@ -44,7 +44,11 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(IOSNotificationHandler); +} + +IOSNotificationHandler::~IOSNotificationHandler() { MVPN_COUNT_DTOR(IOSNotificationHandler); } +void IOSNotificationHandler::initialize() { UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) @@ -56,8 +60,6 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center }]; } -IOSNotificationHandler::~IOSNotificationHandler() { MVPN_COUNT_DTOR(IOSNotificationHandler); } - void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title, const QString& message, int timerMsec) { Q_UNUSED(type); diff --git a/src/platforms/linux/daemon/apptracker.cpp b/src/platforms/linux/daemon/apptracker.cpp index b65a7d5a833..68aed80e8d7 100644 --- a/src/platforms/linux/daemon/apptracker.cpp +++ b/src/platforms/linux/daemon/apptracker.cpp @@ -39,7 +39,7 @@ AppTracker::~AppTracker() { MVPN_COUNT_DTOR(AppTracker); logger.debug() << "AppTracker destroyed."; - for (AppData* data : m_runningApps.values()) { + for (AppData* data : m_runningApps) { delete data; } m_runningApps.clear(); @@ -85,8 +85,8 @@ void AppTracker::userCreated(uint userid, const QDBusObjectPath& path) { QString userCgroupPath = s_cgroupMount + qv.toString(); logger.debug() << "Monitoring Control Groups v2 at:" << userCgroupPath; - connect(&m_cgroupWatcher, SIGNAL(directoryChanged(const QString&)), this, - SLOT(cgroupsChanged(const QString&))); + connect(&m_cgroupWatcher, SIGNAL(directoryChanged(QString)), this, + SLOT(cgroupsChanged(QString))); m_cgroupWatcher.addPath(userCgroupPath); m_cgroupWatcher.addPath(userCgroupPath + "/app.slice"); diff --git a/src/platforms/linux/daemon/dbusservice.cpp b/src/platforms/linux/daemon/dbusservice.cpp index b2cd15f5b92..baf73d46c63 100644 --- a/src/platforms/linux/daemon/dbusservice.cpp +++ b/src/platforms/linux/daemon/dbusservice.cpp @@ -39,18 +39,17 @@ DBusService::DBusService(QObject* parent) : Daemon(parent) { } m_appTracker = new AppTracker(this); - connect(m_appTracker, - SIGNAL(appLaunched(const QString&, const QString&, int)), this, - SLOT(appLaunched(const QString&, const QString&, int))); - connect(m_appTracker, SIGNAL(appTerminated(const QString&, const QString&)), - this, SLOT(appTerminated(const QString&, const QString&))); + connect(m_appTracker, SIGNAL(appLaunched(QString, QString, int)), this, + SLOT(appLaunched(QString, QString, int))); + connect(m_appTracker, SIGNAL(appTerminated(QString, QString)), this, + SLOT(appTerminated(QString, QString))); // Setup to track user login sessions. QDBusConnection bus = QDBusConnection::systemBus(); bus.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserNew", this, - SLOT(userCreated(uint, const QDBusObjectPath&))); + SLOT(userCreated(uint, QDBusObjectPath))); bus.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserRemoved", this, - SLOT(userRemoved(uint, const QDBusObjectPath&))); + SLOT(userRemoved(uint, QDBusObjectPath))); QDBusMessage listUsersCall = QDBusMessage::createMethodCall( DBUS_LOGIN_SERVICE, DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "ListUsers"); @@ -149,7 +148,7 @@ void DBusService::userListCompleted(QDBusPendingCallWatcher* watcher) { QDBusPendingReply reply = *watcher; if (reply.isValid()) { UserDataList list = reply.value(); - for (auto user : list) { + for (const auto& user : list) { m_appTracker->userCreated(user.userid, user.path); } } diff --git a/src/platforms/linux/daemon/dnsutilslinux.cpp b/src/platforms/linux/daemon/dnsutilslinux.cpp index 81ac15e5fe6..76498497214 100644 --- a/src/platforms/linux/daemon/dnsutilslinux.cpp +++ b/src/platforms/linux/daemon/dnsutilslinux.cpp @@ -97,7 +97,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex, QList resolverList; char ifnamebuf[IF_NAMESIZE]; const char* ifname = if_indextoname(ifindex, ifnamebuf); - for (auto ip : resolvers) { + for (const auto& ip : resolvers) { resolverList.append(ip); if (ifname) { logger.debug() << "Adding DNS resolver" << ip.toString() << "via" @@ -121,7 +121,7 @@ void DnsUtilsLinux::setLinkDomains(int ifindex, char ifnamebuf[IF_NAMESIZE]; const char* ifname = if_indextoname(ifindex, ifnamebuf); if (ifname) { - for (auto d : domains) { + for (const auto& d : domains) { // The DNS search domains often winds up revealing user's ISP which // can correlate back to their location. logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain) @@ -181,7 +181,7 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) { m_linkDomains.clear(); QDBusArgument args = qvariant_cast(reply.value()); QList list = qdbus_cast>(args); - for (auto d : list) { + for (const auto& d : list) { if (d.ifindex == 0) { continue; } diff --git a/src/platforms/linux/daemon/linuxdaemon.cpp b/src/platforms/linux/daemon/linuxdaemon.cpp index 2a3026b6dba..23ed9621c85 100644 --- a/src/platforms/linux/daemon/linuxdaemon.cpp +++ b/src/platforms/linux/daemon/linuxdaemon.cpp @@ -44,7 +44,7 @@ class CommandLinuxDaemon final : public Command { } SignalHandler sh; - QObject::connect(&sh, &SignalHandler::quitRequested, [&]() { + QObject::connect(&sh, &SignalHandler::quitRequested, &sh, [&]() { dbus->deactivate(); qApp->quit(); }); diff --git a/src/platforms/linux/daemon/wireguardutilslinux.cpp b/src/platforms/linux/daemon/wireguardutilslinux.cpp index 06f2d9c6a4a..e76ca51c65b 100644 --- a/src/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/src/platforms/linux/daemon/wireguardutilslinux.cpp @@ -240,6 +240,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { } } + // Update the firewall to mark inbound traffic from the server. + GoString goAddress = {.p = qPrintable(config.m_serverIpv4AddrIn), + .n = (ptrdiff_t)config.m_serverIpv4AddrIn.length()}; + NetfilterMarkInbound(goAddress, config.m_serverPort); + // Set/update peer strncpy(device->name, WG_INTERFACE, IFNAMSIZ); device->flags = (wg_device_flags)0; @@ -276,6 +281,11 @@ bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) { peer->flags = (wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REMOVE_ME); wg_key_from_base64(peer->public_key, qPrintable(config.m_serverPublicKey)); + // Clear firewall settings for this server. + GoString goAddress = {.p = qPrintable(config.m_serverIpv4AddrIn), + .n = (ptrdiff_t)config.m_serverIpv4AddrIn.length()}; + NetfilterClearInbound(goAddress); + // Set/update device strncpy(device->name, WG_INTERFACE, IFNAMSIZ); device->flags = (wg_device_flags)0; diff --git a/src/platforms/linux/linuxcontroller.cpp b/src/platforms/linux/linuxcontroller.cpp index cb3a595efce..6f2b564d255 100644 --- a/src/platforms/linux/linuxcontroller.cpp +++ b/src/platforms/linux/linuxcontroller.cpp @@ -12,7 +12,6 @@ #include "models/device.h" #include "models/keys.h" #include "models/server.h" -#include "mozillavpn.h" #include #include @@ -98,7 +97,7 @@ void LinuxController::operationCompleted(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; if (reply.isError()) { logger.error() << "Error received from the DBus service"; - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); emit disconnected(); return; } @@ -111,7 +110,7 @@ void LinuxController::operationCompleted(QDBusPendingCallWatcher* call) { } logger.error() << "DBus service says: error."; - MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); + ErrorHandler::instance()->errorHandle(ErrorHandler::ControllerError); emit disconnected(); } diff --git a/src/platforms/linux/linuxdependencies.cpp b/src/platforms/linux/linuxdependencies.cpp index ebf0ec62cbb..3c261b93ff4 100644 --- a/src/platforms/linux/linuxdependencies.cpp +++ b/src/platforms/linux/linuxdependencies.cpp @@ -34,7 +34,7 @@ bool checkDaemonVersion() { bool completed = false; bool value = false; QObject::connect( - watcher, &QDBusPendingCallWatcher::finished, + watcher, &QDBusPendingCallWatcher::finished, watcher, [completed = &completed, value = &value](QDBusPendingCallWatcher* call) { *completed = true; diff --git a/src/platforms/linux/linuxsystemtraynotificationhandler.cpp b/src/platforms/linux/linuxsystemtraynotificationhandler.cpp index 170747c016c..e30b0c5aad4 100644 --- a/src/platforms/linux/linuxsystemtraynotificationhandler.cpp +++ b/src/platforms/linux/linuxsystemtraynotificationhandler.cpp @@ -39,16 +39,20 @@ LinuxSystemTrayNotificationHandler::LinuxSystemTrayNotificationHandler( QObject* parent) : SystemTrayNotificationHandler(parent) { MVPN_COUNT_CTOR(LinuxSystemTrayNotificationHandler); - - QDBusConnection::sessionBus().connect(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE, - "ActionInvoked", this, - SLOT(actionInvoked(uint, QString))); } LinuxSystemTrayNotificationHandler::~LinuxSystemTrayNotificationHandler() { MVPN_COUNT_DTOR(LinuxSystemTrayNotificationHandler); } +void LinuxSystemTrayNotificationHandler::initialize() { + SystemTrayNotificationHandler::initialize(); + + QDBusConnection::sessionBus().connect(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE, + "ActionInvoked", this, + SLOT(actionInvoked(uint, QString))); +} + void LinuxSystemTrayNotificationHandler::notify(Message type, const QString& title, const QString& message, diff --git a/src/platforms/linux/linuxsystemtraynotificationhandler.h b/src/platforms/linux/linuxsystemtraynotificationhandler.h index a1158009320..762066c1b1d 100644 --- a/src/platforms/linux/linuxsystemtraynotificationhandler.h +++ b/src/platforms/linux/linuxsystemtraynotificationhandler.h @@ -21,6 +21,8 @@ class LinuxSystemTrayNotificationHandler final ~LinuxSystemTrayNotificationHandler(); private: + void initialize() override; + void notify(Message type, const QString& title, const QString& message, int timerMsec) override; diff --git a/src/platforms/macos/macossystemtraynotificationhandler.cpp b/src/platforms/macos/macossystemtraynotificationhandler.cpp index 2a5e401abc3..57c6fb7159b 100644 --- a/src/platforms/macos/macossystemtraynotificationhandler.cpp +++ b/src/platforms/macos/macossystemtraynotificationhandler.cpp @@ -22,20 +22,20 @@ MacosSystemTrayNotificationHandler::MacosSystemTrayNotificationHandler( QObject* parent) : SystemTrayNotificationHandler(parent) { MVPN_COUNT_CTOR(MacosSystemTrayNotificationHandler); - - setStatusMenu(); - updateIcon(); } MacosSystemTrayNotificationHandler::~MacosSystemTrayNotificationHandler() { MVPN_COUNT_DTOR(MacosSystemTrayNotificationHandler); } +void MacosSystemTrayNotificationHandler::initialize() { + SystemTrayNotificationHandler::initialize(); +} + void MacosSystemTrayNotificationHandler::setStatusMenu() { logger.debug() << "Set status menu"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn->statusIcon(), &StatusIcon::iconUpdateNeeded, this, &MacosSystemTrayNotificationHandler::updateIconIndicator); @@ -62,7 +62,6 @@ void MacosSystemTrayNotificationHandler::updateIcon() { logger.debug() << "Update icon"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); m_macOSStatusIcon->setIcon(vpn->statusIcon()->iconString()); } @@ -70,6 +69,5 @@ void MacosSystemTrayNotificationHandler::updateIconIndicator() { logger.debug() << "Update icon indicator"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); m_macOSStatusIcon->setIndicatorColor(vpn->statusIcon()->indicatorColor()); } diff --git a/src/platforms/macos/macossystemtraynotificationhandler.h b/src/platforms/macos/macossystemtraynotificationhandler.h index 43747d24b2c..1c754fc878d 100644 --- a/src/platforms/macos/macossystemtraynotificationhandler.h +++ b/src/platforms/macos/macossystemtraynotificationhandler.h @@ -26,6 +26,9 @@ class MacosSystemTrayNotificationHandler virtual void updateIcon() override; + private: + void initialize() override; + private: MacOSStatusIcon* m_macOSStatusIcon = nullptr; }; diff --git a/src/platforms/wasm/wasmiaphandler.cpp b/src/platforms/wasm/wasmiaphandler.cpp index 229d7215fc0..13b2937970e 100644 --- a/src/platforms/wasm/wasmiaphandler.cpp +++ b/src/platforms/wasm/wasmiaphandler.cpp @@ -6,7 +6,6 @@ #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" -#include "mozillavpn.h" #include "tasks/purchase/taskpurchase.h" #include "taskscheduler.h" @@ -48,14 +47,13 @@ void WasmIAPHandler::nativeStartSubscription(Product* product) { TaskPurchase* purchaseTask = TaskPurchase::createForWasm(product->m_name); Q_ASSERT(purchaseTask); - connect( - purchaseTask, &TaskPurchase::failed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Purchase validation request to guardian failed"; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - stopSubscription(); - emit subscriptionNotValidated(); - }); + connect(purchaseTask, &TaskPurchase::failed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Purchase validation request to guardian failed"; + ErrorHandler::networkErrorHandle(error); + stopSubscription(); + emit subscriptionNotValidated(); + }); connect(purchaseTask, &TaskPurchase::succeeded, this, [this](const QByteArray& data) { diff --git a/src/platforms/windows/golang-msvc-fixup.cpp b/src/platforms/windows/golang-msvc-fixup.cpp deleted file mode 100644 index 18b478d4d36..00000000000 --- a/src/platforms/windows/golang-msvc-fixup.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* 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 - -#pragma comment(lib, "legacy_stdio_definitions.lib") - -extern "C" { -FILE __iob_func[3] = {*stdin, *stdout, *stderr}; -} diff --git a/src/platforms/windows/windowsnetworkwatcher.cpp b/src/platforms/windows/windowsnetworkwatcher.cpp index 2b4f87352a0..e0bb6d70347 100644 --- a/src/platforms/windows/windowsnetworkwatcher.cpp +++ b/src/platforms/windows/windowsnetworkwatcher.cpp @@ -11,6 +11,7 @@ #include #pragma comment(lib, "Wlanapi.lib") +#pragma comment(lib, "windowsapp.lib") #include // See diff --git a/src/platforms/windows/windowsstartatbootwatcher.cpp b/src/platforms/windows/windowsstartatbootwatcher.cpp index dfd6bf3f9d0..82a4d842b9d 100644 --- a/src/platforms/windows/windowsstartatbootwatcher.cpp +++ b/src/platforms/windows/windowsstartatbootwatcher.cpp @@ -31,9 +31,10 @@ void WindowsStartAtBootWatcher::startAtBootChanged(const bool& startAtBoot) { "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); if (startAtBoot) { - settings.setValue( - "Mozilla_VPN", - QDir::toNativeSeparators(QCoreApplication::applicationFilePath())); + settings.setValue("Mozilla_VPN", + QString("\"%1\" ui -m") + .arg(QDir::toNativeSeparators( + QCoreApplication::applicationFilePath()))); } else { settings.remove("Mozilla_VPN"); } diff --git a/src/profileflow.cpp b/src/profileflow.cpp index 1dc67586e94..4035f0152eb 100644 --- a/src/profileflow.cpp +++ b/src/profileflow.cpp @@ -26,7 +26,7 @@ void ProfileFlow::setState(State state) { logger.debug() << "Set state" << state; if (state == StateError) { - MozillaVPN::instance()->errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); } m_state = state; @@ -51,7 +51,6 @@ void ProfileFlow::start() { setState(StateLoading); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); User* user = vpn->user(); Q_ASSERT(user); diff --git a/src/profileflow.h b/src/profileflow.h index 8a00a5259f3..3cc37f7fe60 100644 --- a/src/profileflow.h +++ b/src/profileflow.h @@ -36,7 +36,7 @@ class ProfileFlow final : public QObject { void setForceReauthFlow(const bool forceReauthFlow); signals: - void stateChanged(State state); + void stateChanged(ProfileFlow::State state); private: void setState(State state); diff --git a/src/qmake/sources.pri b/src/qmake/sources.pri index e6f5838a6b6..e252fff134f 100644 --- a/src/qmake/sources.pri +++ b/src/qmake/sources.pri @@ -55,9 +55,10 @@ SOURCES += \ composer/composerblockorderedlist.cpp \ composer/composerblockunorderedlist.cpp \ connectionbenchmark/benchmarktask.cpp \ - connectionbenchmark/benchmarktaskdownload.cpp \ connectionbenchmark/benchmarktaskping.cpp \ + connectionbenchmark/benchmarktasktransfer.cpp \ connectionbenchmark/connectionbenchmark.cpp \ + connectionbenchmark/uploaddatagenerator.cpp \ connectionhealth.cpp \ constants.cpp \ controller.cpp \ @@ -156,6 +157,7 @@ SOURCES += \ tasks/servers/taskservers.cpp \ taskscheduler.cpp \ telemetry.cpp \ + temporarydir.cpp \ theme.cpp \ timersingleshot.cpp \ tutorial/tutorial.cpp \ @@ -225,10 +227,11 @@ HEADERS += \ composer/composerblockorderedlist.h \ composer/composerblockunorderedlist.h \ connectionbenchmark/benchmarktask.h \ - connectionbenchmark/benchmarktaskdownload.h \ connectionbenchmark/benchmarktaskping.h \ connectionbenchmark/benchmarktasksentinel.h \ + connectionbenchmark/benchmarktasktransfer.h \ connectionbenchmark/connectionbenchmark.h \ + connectionbenchmark/uploaddatagenerator.h \ connectionhealth.h \ constants.h \ controller.h \ @@ -326,6 +329,7 @@ HEADERS += \ tasks/servers/taskservers.h \ taskscheduler.h \ telemetry.h \ + temporarydir.h \ theme.h \ tutorial/tutorial.h \ tutorial/tutorialstep.h \ diff --git a/src/releasemonitor.cpp b/src/releasemonitor.cpp index 8a5d4ae2e57..036aef27185 100644 --- a/src/releasemonitor.cpp +++ b/src/releasemonitor.cpp @@ -1,72 +1,77 @@ -/* 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 "releasemonitor.h" -#include "constants.h" -#include "leakdetector.h" -#include "logger.h" -#include "mozillavpn.h" -#include "tasks/release/taskrelease.h" -#include "taskscheduler.h" -#include "timersingleshot.h" -#include "update/updater.h" - -namespace { -Logger logger(LOG_MAIN, "ReleaseMonitor"); -} - -ReleaseMonitor::ReleaseMonitor() { - MVPN_COUNT_CTOR(ReleaseMonitor); - - m_timer.setSingleShot(true); - connect(&m_timer, &QTimer::timeout, this, &ReleaseMonitor::runSoon); -} - -ReleaseMonitor::~ReleaseMonitor() { MVPN_COUNT_DTOR(ReleaseMonitor); } - -void ReleaseMonitor::runSoon() { - logger.debug() << "Scheduling a release-check task"; - - TimerSingleShot::create(this, 0, [this] { - TaskRelease* task = new TaskRelease(TaskRelease::Check); - - connect(task, &TaskRelease::updateRequired, this, - &ReleaseMonitor::updateRequired); - connect(task, &TaskRelease::updateRequiredOrRecommended, this, - &ReleaseMonitor::updateRequiredOrRecommended); - connect(task, &TaskRelease::updateNotAvailable, this, - &ReleaseMonitor::updateNotAvailable); - connect(task, &Task::completed, this, &ReleaseMonitor::releaseChecked); - connect(task, &Task::completed, this, &ReleaseMonitor::schedule); - - TaskScheduler::scheduleTask(task); - }); -} - -void ReleaseMonitor::schedule() { - logger.debug() << "ReleaseMonitor scheduling"; - m_timer.start(Constants::releaseMonitorMsec()); -} - -void ReleaseMonitor::updateRequired() { - logger.warning() << "update required"; - MozillaVPN::instance()->controller()->updateRequired(); -} - -void ReleaseMonitor::updateSoon() { - logger.debug() << "Scheduling a release-update task"; - - TimerSingleShot::create(this, 0, [] { - TaskRelease* task = new TaskRelease(TaskRelease::Update); - // The updater, in download mode, is not destroyed. So, if this happens, - // probably something went wrong. - connect(task, &Task::completed, [] { - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - vpn->setUpdating(false); - }); - - TaskScheduler::scheduleTask(task); - }); -} +/* 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 "releasemonitor.h" +#include "constants.h" +#include "leakdetector.h" +#include "logger.h" +#include "mozillavpn.h" +#include "tasks/release/taskrelease.h" +#include "taskscheduler.h" +#include "timersingleshot.h" +#include "update/updater.h" + +namespace { +Logger logger(LOG_MAIN, "ReleaseMonitor"); +} + +ReleaseMonitor::ReleaseMonitor() { + MVPN_COUNT_CTOR(ReleaseMonitor); + + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, this, [this]() { + ReleaseMonitor::runSoon(ErrorHandler::DoNotPropagateError); + }); +} + +ReleaseMonitor::~ReleaseMonitor() { MVPN_COUNT_DTOR(ReleaseMonitor); } + +void ReleaseMonitor::runSoon( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) { + logger.debug() << "Scheduling a release-check task"; + + TimerSingleShot::create(this, 0, [this, errorPropagationPolicy] { + TaskRelease* task = + new TaskRelease(TaskRelease::Check, errorPropagationPolicy); + + connect(task, &TaskRelease::updateRequired, this, + &ReleaseMonitor::updateRequired); + connect(task, &TaskRelease::updateRequiredOrRecommended, this, + &ReleaseMonitor::updateRequiredOrRecommended); + connect(task, &TaskRelease::updateNotAvailable, this, + &ReleaseMonitor::updateNotAvailable); + connect(task, &Task::completed, this, &ReleaseMonitor::releaseChecked); + connect(task, &Task::completed, this, &ReleaseMonitor::schedule); + + TaskScheduler::scheduleTask(task); + }); +} + +void ReleaseMonitor::schedule() { + logger.debug() << "ReleaseMonitor scheduling"; + m_timer.start(Constants::releaseMonitorMsec()); +} + +void ReleaseMonitor::updateRequired() { + logger.warning() << "update required"; + MozillaVPN::instance()->controller()->updateRequired(); +} + +void ReleaseMonitor::updateSoon() { + logger.debug() << "Scheduling a release-update task"; + + TimerSingleShot::create(this, 0, [] { + TaskRelease* task = + new TaskRelease(TaskRelease::Update, ErrorHandler::PropagateError); + // The updater, in download mode, is not destroyed. So, if this happens, + // probably something went wrong. + connect(task, &Task::completed, task, [] { + MozillaVPN* vpn = MozillaVPN::instance(); + Q_ASSERT(vpn); + vpn->setUpdating(false); + }); + + TaskScheduler::scheduleTask(task); + }); +} diff --git a/src/releasemonitor.h b/src/releasemonitor.h index bd4b18c1615..89f9fa44aa1 100644 --- a/src/releasemonitor.h +++ b/src/releasemonitor.h @@ -1,40 +1,44 @@ -/* 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/. */ - -#ifndef RELEASEMONITOR_H -#define RELEASEMONITOR_H - -#include -#include - -class ReleaseMonitor final : public QObject { - Q_OBJECT - Q_DISABLE_COPY_MOVE(ReleaseMonitor) - - public: - ReleaseMonitor(); - ~ReleaseMonitor(); - - Q_INVOKABLE void runSoon(); - - void updateSoon(); - - signals: - // for testing - void releaseChecked(); - // Is fired once balrog/guardian was checked - // Retuns if any update is available (both recommended/required) - void updateRequiredOrRecommended(); - void updateNotAvailable(); - - private: - void schedule(); - - void updateRequired(); - - private: - QTimer m_timer; -}; - -#endif // RELEASEMONITOR_H +/* 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/. */ + +#ifndef RELEASEMONITOR_H +#define RELEASEMONITOR_H + +#include "errorhandler.h" + +#include +#include + +class ReleaseMonitor final : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(ReleaseMonitor) + + public: + ReleaseMonitor(); + ~ReleaseMonitor(); + + Q_INVOKABLE void runSoon( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy = + ErrorHandler::PropagateError); + + void updateSoon(); + + signals: + // for testing + void releaseChecked(); + // Is fired once balrog/guardian was checked + // Retuns if any update is available (both recommended/required) + void updateRequiredOrRecommended(); + void updateNotAvailable(); + + private: + void schedule(); + + void updateRequired(); + + private: + QTimer m_timer; +}; + +#endif // RELEASEMONITOR_H diff --git a/src/resources/public_keys/production.der b/src/resources/public_keys/production.der index dda72abe4b8..f2f00b98d7c 100644 Binary files a/src/resources/public_keys/production.der and b/src/resources/public_keys/production.der differ diff --git a/src/server/serverconnection.cpp b/src/server/serverconnection.cpp index a9551257037..c9902384a75 100644 --- a/src/server/serverconnection.cpp +++ b/src/server/serverconnection.cpp @@ -84,7 +84,6 @@ void serializeServerCountry(ServerCountryModel* model, QJsonObject& obj) { QJsonObject serializeStatus() { MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); QJsonObject obj; obj["authenticated"] = vpn->userState() == MozillaVPN::UserAuthenticated; @@ -173,7 +172,6 @@ ServerConnection::ServerConnection(QObject* parent, QTcpSocket* connection) &ServerConnection::readData); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn, &MozillaVPN::stateChanged, this, &ServerConnection::writeState); connect(vpn->controller(), &Controller::stateChanged, this, diff --git a/src/serveri18n.cpp b/src/serveri18n.cpp index 8d53587f2f1..8d3903e91e1 100644 --- a/src/serveri18n.cpp +++ b/src/serveri18n.cpp @@ -21,7 +21,7 @@ QHash s_items; QString itemKey(const QString& languageCode, const QString& countryCode, const QString& city = QString()) { - return QString("%1^%2^%3").arg(languageCode).arg(countryCode).arg(city); + return QString("%1^%2^%3").arg(languageCode, countryCode, city); } void addCity(const QString& countryCode, const QJsonValue& value) { @@ -82,7 +82,7 @@ void addCountry(const QJsonValue& value) { } QJsonArray cityArray = cities.toArray(); - for (const QJsonValue city : cityArray) { + for (const QJsonValue& city : cityArray) { addCity(countryCode, city); } } @@ -107,7 +107,7 @@ void maybeInitialize() { } QJsonArray array = json.array(); - for (const QJsonValue country : array) { + for (const QJsonValue& country : array) { addCountry(country); } } diff --git a/src/settingsholder.cpp b/src/settingsholder.cpp index f0157ccc133..755c530aa3f 100644 --- a/src/settingsholder.cpp +++ b/src/settingsholder.cpp @@ -114,7 +114,7 @@ QString SettingsHolder::getReport() const { QString buff; QTextStream out(&buff); auto settingsKeys = m_settings.childKeys(); - for (auto setting : settingsKeys) { + for (const auto& setting : settingsKeys) { if (SENSITIVE_SETTINGS.contains(setting)) { out << setting << " -> " << Qt::endl; continue; @@ -167,7 +167,7 @@ void SettingsHolder::clearAddonSettings(const QString& group) { logger.debug() << "Clean up the settings for group" << group; const QString groupKey( - QString("%1/%2").arg(Constants::ADDON_SETTINGS_GROUP).arg(group)); + QString("%1/%2").arg(Constants::ADDON_SETTINGS_GROUP, group)); m_settings.beginGroup(groupKey); m_settings.remove(""); @@ -179,10 +179,8 @@ void SettingsHolder::clearAddonSettings(const QString& group) { // static QString SettingsHolder::getAddonSettingKey(const AddonSettingQuery& query) { return QString("%1/%2/%3/%4") - .arg(Constants::ADDON_SETTINGS_GROUP) - .arg(query.m_addonGroup) - .arg(query.m_addonId) - .arg(query.m_setting); + .arg(Constants::ADDON_SETTINGS_GROUP, query.m_addonGroup, query.m_addonId, + query.m_setting); } QString SettingsHolder::getAddonSetting(const AddonSettingQuery& query) { diff --git a/src/statusicon.cpp b/src/statusicon.cpp index dba0d604c0e..6af3f7b5e53 100644 --- a/src/statusicon.cpp +++ b/src/statusicon.cpp @@ -85,7 +85,6 @@ const QString StatusIcon::iconString() { logger.debug() << "Icon string" << m_animatedIconIndex; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // If we are in a non-main state, we don't need to show special icons. if (vpn->state() != MozillaVPN::StateMain) { @@ -124,7 +123,6 @@ const QColor StatusIcon::indicatorColor() const { logger.debug() << "Set color"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->state() != MozillaVPN::StateMain || vpn->controller()->state() != Controller::StateOn) { @@ -160,7 +158,6 @@ QIcon StatusIcon::drawStatusIndicator() { QPixmap iconPixmap = QPixmap(iconString()); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); // Only draw a status indicator if the VPN is connected if (vpn->controller()->state() == Controller::StateOn) { diff --git a/src/systemtraynotificationhandler.cpp b/src/systemtraynotificationhandler.cpp index c7310a1ab50..93f486a6ae2 100644 --- a/src/systemtraynotificationhandler.cpp +++ b/src/systemtraynotificationhandler.cpp @@ -24,12 +24,17 @@ Logger logger(LOG_MAIN, "SystemTrayNotificationHandler"); SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(SystemTrayNotificationHandler); +} - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); +SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { + MVPN_COUNT_DTOR(SystemTrayNotificationHandler); +} +void SystemTrayNotificationHandler::initialize() { m_menu.reset(new QMenu()); - m_systemTrayIcon = new QSystemTrayIcon(parent); + m_systemTrayIcon = new QSystemTrayIcon(parent()); + + MozillaVPN* vpn = MozillaVPN::instance(); connect(vpn, &MozillaVPN::stateChanged, this, &SystemTrayNotificationHandler::updateContextMenu); @@ -59,10 +64,6 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) updateIcon(); } -SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { - MVPN_COUNT_DTOR(SystemTrayNotificationHandler); -} - #ifdef MVPN_WASM QMenu* SystemTrayNotificationHandler::contextMenu() { return m_systemTrayIcon->contextMenu(); @@ -109,11 +110,9 @@ void SystemTrayNotificationHandler::createStatusMenu() { void SystemTrayNotificationHandler::setStatusMenu() { logger.debug() << "Set status menu"; - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - // TODO: Check if method is called on these devices. #if defined(MVPN_LINUX) || defined(MVPN_WINDOWS) + MozillaVPN* vpn = MozillaVPN::instance(); m_systemTrayIcon->setToolTip(qtTrId("vpn.main.productName")); m_systemTrayIcon->setContextMenu(m_menu.get()); m_systemTrayIcon->show(); @@ -228,9 +227,9 @@ void SystemTrayNotificationHandler::updateContextMenu() { vpn->serverCountryModel()->localizedCountryName(countryCode); m_lastLocationLabel->setIcon(flagIcon); - m_lastLocationLabel->setText(l18nStrings->t(L18nStrings::SystrayLocation2) - .arg(localizedCountryName) - .arg(localizedCityName)); + m_lastLocationLabel->setText( + l18nStrings->t(L18nStrings::SystrayLocation2) + .arg(localizedCountryName, localizedCityName)); m_lastLocationLabel->setEnabled(vpn->controller()->state() == Controller::StateOff); } @@ -238,10 +237,8 @@ void SystemTrayNotificationHandler::updateContextMenu() { void SystemTrayNotificationHandler::updateIcon() { logger.debug() << "Update icon"; - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - #if defined(MVPN_LINUX) || defined(MVPN_WINDOWS) + MozillaVPN* vpn = MozillaVPN::instance(); m_systemTrayIcon->setIcon(vpn->statusIcon()->icon()); #endif } diff --git a/src/systemtraynotificationhandler.h b/src/systemtraynotificationhandler.h index 656624cc0eb..fa5aa744219 100644 --- a/src/systemtraynotificationhandler.h +++ b/src/systemtraynotificationhandler.h @@ -33,9 +33,7 @@ class SystemTrayNotificationHandler : public NotificationHandler { virtual void updateIcon(); - protected: - QScopedPointer m_menu; - QSystemTrayIcon* m_systemTrayIcon; + void initialize() override; private: void createStatusMenu(); @@ -44,6 +42,10 @@ class SystemTrayNotificationHandler : public NotificationHandler { void updateContextMenu(); + protected: + QScopedPointer m_menu; + QSystemTrayIcon* m_systemTrayIcon; + private: QAction* m_statusLabel = nullptr; QAction* m_lastLocationLabel = nullptr; diff --git a/src/tasks/account/taskaccount.cpp b/src/tasks/account/taskaccount.cpp index 295c9581cd3..cc539e03338 100644 --- a/src/tasks/account/taskaccount.cpp +++ b/src/tasks/account/taskaccount.cpp @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskaccount.h" -#include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" @@ -13,7 +12,9 @@ namespace { Logger logger(LOG_MAIN, "TaskAccount"); } -TaskAccount::TaskAccount() : Task("TaskAccount") { +TaskAccount::TaskAccount( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) + : Task("TaskAccount"), m_errorPropagationPolicy(errorPropagationPolicy) { MVPN_COUNT_CTOR(TaskAccount); } @@ -22,13 +23,12 @@ TaskAccount::~TaskAccount() { MVPN_COUNT_DTOR(TaskAccount); } void TaskAccount::run() { NetworkRequest* request = NetworkRequest::createForAccount(this); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Account request failed" << error; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit completed(); - }); + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Account request failed" << error; + ErrorHandler::networkErrorHandle(error, m_errorPropagationPolicy); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray& data) { diff --git a/src/tasks/account/taskaccount.h b/src/tasks/account/taskaccount.h index 09ebc41dc58..f3b6bd388e5 100644 --- a/src/tasks/account/taskaccount.h +++ b/src/tasks/account/taskaccount.h @@ -5,6 +5,7 @@ #ifndef TASKACCOUNT_H #define TASKACCOUNT_H +#include "errorhandler.h" #include "task.h" #include @@ -13,10 +14,15 @@ class TaskAccount final : public Task { Q_DISABLE_COPY_MOVE(TaskAccount) public: - TaskAccount(); + explicit TaskAccount( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); ~TaskAccount(); void run() override; + + private: + ErrorHandler::ErrorPropagationPolicy m_errorPropagationPolicy = + ErrorHandler::DoNotPropagateError; }; #endif // TASKACCOUNT_H diff --git a/src/tasks/adddevice/taskadddevice.cpp b/src/tasks/adddevice/taskadddevice.cpp index 74e575a82d8..c4b9153326e 100644 --- a/src/tasks/adddevice/taskadddevice.cpp +++ b/src/tasks/adddevice/taskadddevice.cpp @@ -58,10 +58,7 @@ void TaskAddDevice::run() { connect(request, &NetworkRequest::requestFailed, this, [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.error() << "Failed to add the device" << error; - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - - vpn->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); emit completed(); }); diff --git a/src/tasks/addon/taskaddon.cpp b/src/tasks/addon/taskaddon.cpp index 259c4bdc7c7..2f747ad8737 100644 --- a/src/tasks/addon/taskaddon.cpp +++ b/src/tasks/addon/taskaddon.cpp @@ -22,9 +22,7 @@ TaskAddon::~TaskAddon() { MVPN_COUNT_DTOR(TaskAddon); } void TaskAddon::run() { NetworkRequest* request = NetworkRequest::createForGetUrl( this, - QString("%1%2.rcc") - .arg(AddonManager::addonServerAddress()) - .arg(m_addonId), + QString("%1%2.rcc").arg(AddonManager::addonServerAddress(), m_addonId), 200); connect(request, &NetworkRequest::requestFailed, this, diff --git a/src/tasks/addonindex/taskaddonindex.cpp b/src/tasks/addonindex/taskaddonindex.cpp index 228204e79cd..5584aa31b89 100644 --- a/src/tasks/addonindex/taskaddonindex.cpp +++ b/src/tasks/addonindex/taskaddonindex.cpp @@ -46,7 +46,7 @@ void TaskAddonIndex::run() { if (Feature::get(Feature::Feature_addonSignature)->isSupported()) { NetworkRequest* request = NetworkRequest::createForGetUrl( this, - QString("%1manifest.json.sign").arg(AddonManager::addonServerAddress()), + QString("%1manifest.json.sig").arg(AddonManager::addonServerAddress()), 200); connect(request, &NetworkRequest::requestFailed, this, diff --git a/src/tasks/authenticate/taskauthenticate.cpp b/src/tasks/authenticate/taskauthenticate.cpp index 68768f9d5ce..032db29f2a9 100644 --- a/src/tasks/authenticate/taskauthenticate.cpp +++ b/src/tasks/authenticate/taskauthenticate.cpp @@ -59,8 +59,7 @@ void TaskAuthenticate::run() { [](QNetworkReply::NetworkError error, const QByteArray&) { logger.error() << "Failed to complete the authentication" << error; - MozillaVPN::instance()->errorHandle( - ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); }); connect(request, &NetworkRequest::requestCompleted, this, @@ -72,7 +71,7 @@ void TaskAuthenticate::run() { connect(m_authenticationListener, &AuthenticationListener::failed, this, [this](const ErrorHandler::ErrorType error) { - MozillaVPN::instance()->errorHandle(error); + ErrorHandler::instance()->errorHandle(error); m_authenticationListener->aboutToFinish(); }); @@ -89,19 +88,16 @@ void TaskAuthenticate::run() { void TaskAuthenticate::authenticationCompleted(const QByteArray& data) { logger.debug() << "Authentication completed"; - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - QJsonDocument json = QJsonDocument::fromJson(data); if (json.isNull()) { - vpn->errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); return; } QJsonObject obj = json.object(); QJsonValue userObj = obj.value("user"); if (!userObj.isObject()) { - vpn->errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); return; } @@ -111,7 +107,7 @@ void TaskAuthenticate::authenticationCompleted(const QByteArray& data) { QJsonValue tokenValue = obj.value("token"); if (!tokenValue.isString()) { - vpn->errorHandle(ErrorHandler::RemoteServiceError); + ErrorHandler::instance()->errorHandle(ErrorHandler::RemoteServiceError); return; } diff --git a/src/tasks/captiveportallookup/taskcaptiveportallookup.cpp b/src/tasks/captiveportallookup/taskcaptiveportallookup.cpp index 84d377d587c..d893e69d965 100644 --- a/src/tasks/captiveportallookup/taskcaptiveportallookup.cpp +++ b/src/tasks/captiveportallookup/taskcaptiveportallookup.cpp @@ -13,8 +13,10 @@ namespace { Logger logger(LOG_NETWORKING, "TaskCaptivePortalLookup"); } -TaskCaptivePortalLookup::TaskCaptivePortalLookup() - : Task("TaskCaptivePortalLookup") { +TaskCaptivePortalLookup::TaskCaptivePortalLookup( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) + : Task("TaskCaptivePortalLookup"), + m_errorPropagationPolicy(errorPropagationPolicy) { MVPN_COUNT_CTOR(TaskCaptivePortalLookup); } @@ -26,16 +28,15 @@ void TaskCaptivePortalLookup::run() { logger.debug() << "Resolving the captive portal detector URL"; NetworkRequest* request = NetworkRequest::createForCaptivePortalLookup(this); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - if (m_cancelled) { - return; - } - logger.error() << "Failed to obtain captive portal IPs" << error; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit completed(); - }); + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + if (m_cancelled) { + return; + } + logger.error() << "Failed to obtain captive portal IPs" << error; + ErrorHandler::networkErrorHandle(error, m_errorPropagationPolicy); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray& data) { diff --git a/src/tasks/captiveportallookup/taskcaptiveportallookup.h b/src/tasks/captiveportallookup/taskcaptiveportallookup.h index 2fff5c5ce86..462aac67c1c 100644 --- a/src/tasks/captiveportallookup/taskcaptiveportallookup.h +++ b/src/tasks/captiveportallookup/taskcaptiveportallookup.h @@ -5,6 +5,7 @@ #ifndef TASKCAPTIVEPORTALLOOKUP_H #define TASKCAPTIVEPORTALLOOKUP_H +#include "errorhandler.h" #include "task.h" #include @@ -13,10 +14,15 @@ class TaskCaptivePortalLookup final : public Task { Q_DISABLE_COPY_MOVE(TaskCaptivePortalLookup) public: - TaskCaptivePortalLookup(); + explicit TaskCaptivePortalLookup( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); ~TaskCaptivePortalLookup(); void run() override; + + private: + ErrorHandler::ErrorPropagationPolicy m_errorPropagationPolicy = + ErrorHandler::DoNotPropagateError; }; #endif // TASKCAPTIVEPORTALLOOKUP_H diff --git a/src/tasks/deleteaccount/taskdeleteaccount.cpp b/src/tasks/deleteaccount/taskdeleteaccount.cpp index 9ccb9b00a63..0709852919f 100644 --- a/src/tasks/deleteaccount/taskdeleteaccount.cpp +++ b/src/tasks/deleteaccount/taskdeleteaccount.cpp @@ -54,8 +54,7 @@ void TaskDeleteAccount::run() { [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.error() << "Failed to complete the authentication" << error; - MozillaVPN::instance()->errorHandle( - ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error); m_authenticationInAppSession->terminate(); }); @@ -69,7 +68,7 @@ void TaskDeleteAccount::run() { connect(m_authenticationInAppSession, &AuthenticationInAppSession::failed, this, [this](const ErrorHandler::ErrorType error) { - MozillaVPN::instance()->errorHandle(error); + ErrorHandler::instance()->errorHandle(error); m_authenticationInAppSession->terminate(); }); @@ -86,7 +85,7 @@ void TaskDeleteAccount::run() { case AuthenticationInApp::StateSignUp: [[fallthrough]]; case AuthenticationInApp::StateFallbackInBrowser: - MozillaVPN::instance()->errorHandle( + ErrorHandler::instance()->errorHandle( ErrorHandler::AuthenticationError); m_authenticationInAppSession->terminate(); break; diff --git a/src/tasks/getsubscriptiondetails/taskgetsubscriptiondetails.cpp b/src/tasks/getsubscriptiondetails/taskgetsubscriptiondetails.cpp index 226f88d868b..f9d52958236 100644 --- a/src/tasks/getsubscriptiondetails/taskgetsubscriptiondetails.cpp +++ b/src/tasks/getsubscriptiondetails/taskgetsubscriptiondetails.cpp @@ -9,7 +9,6 @@ #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" -#include "mozillavpn.h" #include "networkrequest.h" namespace { @@ -44,36 +43,36 @@ void TaskGetSubscriptionDetails::run() { NetworkRequest* request = NetworkRequest::createForGetSubscriptionDetails(this); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Get subscription details failed" << error; - - // Network request failed after authentication for a second time - if (m_authenticationInAppSession) { - logger.error() << "Network request failed after authentication"; - - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit failed(); - m_authenticationInAppSession->terminate(); - return; - } - - // User needs to (re)authenticate - if (error == QNetworkReply::AuthenticationRequiredError) { - logger.error() << "Needs authentication"; - initAuthentication(); - return; - } - - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - - // We need to emit two separate signals here. - // `failed`: Signal for connected objects that are monitoring this Task - // `completed`: Notify `TaskScheduler` that this Task can be deleted - emit failed(); - emit completed(); - }); + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Get subscription details failed" << error; + + // Network request failed after authentication for a second time + if (m_authenticationInAppSession) { + logger.error() << "Network request failed after authentication"; + + ErrorHandler::networkErrorHandle(error); + emit failed(); + m_authenticationInAppSession->terminate(); + return; + } + + // User needs to (re)authenticate + if (error == QNetworkReply::AuthenticationRequiredError) { + logger.error() << "Needs authentication"; + initAuthentication(); + return; + } + + ErrorHandler::networkErrorHandle(error); + + // We need to emit two separate signals here. + // `failed`: Signal for connected objects that are monitoring this + // Task `completed`: Notify `TaskScheduler` that this Task can be + // deleted + emit failed(); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray& data) { @@ -135,7 +134,7 @@ void TaskGetSubscriptionDetails::initAuthentication() { connect(m_authenticationInAppSession, &AuthenticationInAppSession::failed, this, [this](const ErrorHandler::ErrorType error) { - MozillaVPN::instance()->errorHandle(error); + ErrorHandler::instance()->errorHandle(error); m_authenticationInAppSession->terminate(); }); @@ -145,7 +144,7 @@ void TaskGetSubscriptionDetails::initAuthentication() { case AuthenticationInApp::StateSignUp: [[fallthrough]]; case AuthenticationInApp::StateFallbackInBrowser: - MozillaVPN::instance()->errorHandle( + ErrorHandler::instance()->errorHandle( ErrorHandler::AuthenticationError); emit failed(); m_authenticationInAppSession->terminate(); diff --git a/src/tasks/ipfinder/taskipfinder.cpp b/src/tasks/ipfinder/taskipfinder.cpp index 981adf70991..7c6c4bcf674 100644 --- a/src/tasks/ipfinder/taskipfinder.cpp +++ b/src/tasks/ipfinder/taskipfinder.cpp @@ -7,7 +7,6 @@ #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" -#include "mozillavpn.h" #include "networkrequest.h" #include "settingsholder.h" @@ -102,7 +101,7 @@ void TaskIPFinder::createRequest(const QHostAddress& address, bool ipv6) { ErrorHandler::ErrorType errorType = ErrorHandler::toErrorType(error); if (errorType == ErrorHandler::AuthenticationError) { - MozillaVPN::instance()->errorHandle(errorType); + ErrorHandler::instance()->errorHandle(errorType); } m_requestCount = 0; diff --git a/src/tasks/products/taskproducts.cpp b/src/tasks/products/taskproducts.cpp index bad1c781df1..f13fd12d903 100644 --- a/src/tasks/products/taskproducts.cpp +++ b/src/tasks/products/taskproducts.cpp @@ -3,10 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskproducts.h" +#include "errorhandler.h" #include "iaphandler.h" #include "leakdetector.h" #include "logger.h" -#include "mozillavpn.h" #include "networkrequest.h" namespace { @@ -22,13 +22,12 @@ TaskProducts::~TaskProducts() { MVPN_COUNT_DTOR(TaskProducts); } void TaskProducts::run() { NetworkRequest* request = NetworkRequest::createForProducts(this); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Products request to guardian failed" << error; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit completed(); - }); + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Products request to guardian failed" << error; + ErrorHandler::networkErrorHandle(error); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray& data) { diff --git a/src/tasks/release/taskrelease.cpp b/src/tasks/release/taskrelease.cpp index a59f1614382..35998c80d8c 100644 --- a/src/tasks/release/taskrelease.cpp +++ b/src/tasks/release/taskrelease.cpp @@ -11,7 +11,11 @@ namespace { Logger logger(LOG_MAIN, "TaskRelease"); } -TaskRelease::TaskRelease(Op op) : Task("TaskRelease"), m_op(op) { +TaskRelease::TaskRelease( + Op op, ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) + : Task("TaskRelease"), + m_op(op), + m_errorPropagationPolicy(errorPropagationPolicy) { MVPN_COUNT_CTOR(TaskRelease); } @@ -20,7 +24,8 @@ TaskRelease::~TaskRelease() { MVPN_COUNT_DTOR(TaskRelease); } void TaskRelease::run() { logger.debug() << "Release check started"; - Updater* updater = Updater::create(this, m_op == Update); + Updater* updater = + Updater::create(this, m_op == Update, m_errorPropagationPolicy); Q_ASSERT(updater); connect(updater, &Updater::updateRequired, this, diff --git a/src/tasks/release/taskrelease.h b/src/tasks/release/taskrelease.h index 4c96f0ee452..6e80443a0fa 100644 --- a/src/tasks/release/taskrelease.h +++ b/src/tasks/release/taskrelease.h @@ -5,6 +5,7 @@ #ifndef TASKRELEASE_H #define TASKRELEASE_H +#include "errorhandler.h" #include "task.h" #include @@ -19,7 +20,8 @@ class TaskRelease final : public Task { Update, }; - explicit TaskRelease(Op op); + TaskRelease(Op op, + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); ~TaskRelease(); void run() override; @@ -35,7 +37,9 @@ class TaskRelease final : public Task { void updateNotAvailable(); private: - Op m_op; + Op m_op = Check; + ErrorHandler::ErrorPropagationPolicy m_errorPropagationPolicy = + ErrorHandler::DoNotPropagateError; }; #endif // TASKRELEASE_H diff --git a/src/tasks/removedevice/taskremovedevice.cpp b/src/tasks/removedevice/taskremovedevice.cpp index 842c8a4af81..67decb2e3b9 100644 --- a/src/tasks/removedevice/taskremovedevice.cpp +++ b/src/tasks/removedevice/taskremovedevice.cpp @@ -39,22 +39,21 @@ void TaskRemoveDevice::run() { NetworkRequest* request = NetworkRequest::createForDeviceRemoval(this, m_publicKey); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - if (error == QNetworkReply::ContentNotFoundError) { - logger.error() << "The device has been removed in the meantime. " - "Let's consider it a success"; - MozillaVPN::instance()->deviceRemoved(m_publicKey, - "TaskRemoveDevice"); - emit completed(); - return; - } + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + if (error == QNetworkReply::ContentNotFoundError) { + logger.error() << "The device has been removed in the meantime. " + "Let's consider it a success"; + MozillaVPN::instance()->deviceRemoved(m_publicKey, + "TaskRemoveDevice"); + emit completed(); + return; + } - logger.error() << "Failed to remove the device" << error; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit completed(); - }); + logger.error() << "Failed to remove the device" << error; + ErrorHandler::networkErrorHandle(error); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray&) { diff --git a/src/tasks/servers/taskservers.cpp b/src/tasks/servers/taskservers.cpp index 65e86bfe5c6..a9413cf91a1 100644 --- a/src/tasks/servers/taskservers.cpp +++ b/src/tasks/servers/taskservers.cpp @@ -13,7 +13,9 @@ namespace { Logger logger(LOG_MAIN, "TaskServers"); } -TaskServers::TaskServers() : Task("TaskServers") { +TaskServers::TaskServers( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) + : Task("TaskServers"), m_errorPropagationPolicy(errorPropagationPolicy) { MVPN_COUNT_CTOR(TaskServers); } @@ -22,13 +24,12 @@ TaskServers::~TaskServers() { MVPN_COUNT_DTOR(TaskServers); } void TaskServers::run() { NetworkRequest* request = NetworkRequest::createForServers(this); - connect( - request, &NetworkRequest::requestFailed, this, - [this](QNetworkReply::NetworkError error, const QByteArray&) { - logger.error() << "Failed to retrieve servers"; - MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit completed(); - }); + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + logger.error() << "Failed to retrieve servers"; + ErrorHandler::networkErrorHandle(error, m_errorPropagationPolicy); + emit completed(); + }); connect(request, &NetworkRequest::requestCompleted, this, [this](const QByteArray& data) { diff --git a/src/tasks/servers/taskservers.h b/src/tasks/servers/taskservers.h index d3d6c08a7a6..99b8ef0347c 100644 --- a/src/tasks/servers/taskservers.h +++ b/src/tasks/servers/taskservers.h @@ -5,6 +5,7 @@ #ifndef TASKSERVERS_H #define TASKSERVERS_H +#include "errorhandler.h" #include "task.h" #include @@ -13,10 +14,15 @@ class TaskServers final : public Task { Q_DISABLE_COPY_MOVE(TaskServers) public: - TaskServers(); + explicit TaskServers( + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); ~TaskServers(); void run() override; + + private: + ErrorHandler::ErrorPropagationPolicy m_errorPropagationPolicy = + ErrorHandler::DoNotPropagateError; }; #endif // TASKSERVERS_H diff --git a/src/taskscheduler.cpp b/src/taskscheduler.cpp index b161a95c0b3..a483b3ace69 100644 --- a/src/taskscheduler.cpp +++ b/src/taskscheduler.cpp @@ -29,7 +29,14 @@ void TaskScheduler::scheduleTaskNow(Task* task) { } // static -void TaskScheduler::deleteTasks() { maybeCreate()->deleteTasksInternal(); } +void TaskScheduler::deleteTasks() { + maybeCreate()->deleteTasksInternal(/* forced */ false); +} + +// static +void TaskScheduler::forceDeleteTasks() { + maybeCreate()->deleteTasksInternal(/* forced */ true); +} // static TaskScheduler* TaskScheduler::maybeCreate() { @@ -78,10 +85,17 @@ void TaskScheduler::taskCompleted() { maybeRunTask(); } -void TaskScheduler::deleteTasksInternal() { +void TaskScheduler::deleteTasksInternal(bool forced) { QMutableListIterator i(m_tasks); while (i.hasNext()) { Task* task = i.next(); + + if (forced) { + task->deleteLater(); + i.remove(); + continue; + } + switch (task->deletePolicy()) { case Task::Deletable: task->deleteLater(); @@ -100,10 +114,14 @@ void TaskScheduler::deleteTasksInternal() { } } - if (m_running_task && m_running_task->deletePolicy() == Task::Deletable) { - m_running_task->cancel(); - m_running_task->deleteLater(); - m_running_task->disconnect(); - m_running_task = nullptr; + if (m_running_task) { + if (forced || m_running_task->deletePolicy() == Task::Deletable) { + m_running_task->cancel(); + m_running_task->deleteLater(); + m_running_task->disconnect(); + m_running_task = nullptr; + } } + + maybeRunTask(); } diff --git a/src/taskscheduler.h b/src/taskscheduler.h index d90b6e1cca6..1b62ecf1c94 100644 --- a/src/taskscheduler.h +++ b/src/taskscheduler.h @@ -15,6 +15,7 @@ class TaskScheduler final : public QObject { public: static void scheduleTask(Task* task); static void deleteTasks(); + static void forceDeleteTasks(); // This method should never been used! It's used only when we are 100% sure // that the current tasks do not conflict with this one. @@ -27,7 +28,7 @@ class TaskScheduler final : public QObject { static TaskScheduler* maybeCreate(); void scheduleTaskInternal(Task* task); - void deleteTasksInternal(); + void deleteTasksInternal(bool forced); void maybeRunTask(); diff --git a/src/telemetry.cpp b/src/telemetry.cpp index 181193387fb..564678e3534 100644 --- a/src/telemetry.cpp +++ b/src/telemetry.cpp @@ -75,13 +75,19 @@ void Telemetry::initialize() { emit vpn->recordGleanEvent(GleanSample::controllerStateOff); } }); + + connect(controller, &Controller::readyToServerUnavailable, this, []() { + MozillaVPN* vpn = MozillaVPN::instance(); + Q_ASSERT(vpn); + + emit vpn->recordGleanEvent(GleanSample::serverUnavailableError); + }); } void Telemetry::connectionStabilityEvent() { logger.info() << "Send a connection stability event"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Controller* controller = vpn->controller(); Q_ASSERT(controller); @@ -100,7 +106,6 @@ void Telemetry::connectionStabilityEvent() { void Telemetry::periodicStateRecorder() { // On mobile this is handled seperately in a background process MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); Controller* controller = vpn->controller(); Q_ASSERT(controller); diff --git a/src/temporarydir.cpp b/src/temporarydir.cpp new file mode 100644 index 00000000000..593ae8d31e8 --- /dev/null +++ b/src/temporarydir.cpp @@ -0,0 +1,158 @@ +/* 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 "temporarydir.h" +#include "leakdetector.h" +#include "logger.h" + +#include +#include + +namespace { +Logger logger(LOG_MAIN, "TemporaryDir"); + +constexpr const char* TMP_FOLDER = "tmp"; + +QString rootAppFolder() { +#ifdef MVPN_WASM + // https://wiki.qt.io/Qt_for_WebAssembly#Files_and_local_file_system_access + return "/"; +#elif defined(UNIT_TEST) + return QStandardPaths::writableLocation(QStandardPaths::TempLocation); +#else + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +#endif +} +} // namespace + +TemporaryDir::TemporaryDir(QObject* parent) : QObject(parent) { + MVPN_COUNT_CTOR(TemporaryDir); +} + +TemporaryDir::~TemporaryDir() { + MVPN_COUNT_DTOR(TemporaryDir); + + if (m_state == Fallback && !m_fallbackDir.removeRecursively()) { + logger.debug() << "Failed to remove the fallback dir" + << m_fallbackDir.path(); + } +} + +QString TemporaryDir::errorString() { + if (isValid()) { + return QString(); + } + + if (m_state == QT) { + return m_tmpDir.errorString(); + } + + return "Unable to create the fallback directory"; +} + +QString TemporaryDir::filePath(const QString& fileName) { + if (!isValid()) { + return QString(); + } + + if (m_state == QT) { + return m_tmpDir.filePath(fileName); + } + + return m_fallbackDir.filePath(fileName); +} + +void TemporaryDir::fallback() { + m_fallbackDir = QDir(rootAppFolder()); + + if (!m_fallbackDir.exists(TMP_FOLDER) && !m_fallbackDir.mkpath(TMP_FOLDER)) { + logger.error() << "Unable to create the tmp fallback folder" + << rootAppFolder(); + return; + } + + if (!m_fallbackDir.cd(TMP_FOLDER)) { + logger.error() << "Unable to open the tmp fallback folder" + << m_fallbackDir.path(); + return; + } + + int retry = 0; + while (true) { + if (++retry > 10) { + logger.error() + << "Unable to find a valid UUID for the tmp fallback folder" + << m_fallbackDir.path(); + return; + } + + QByteArray uuid = QUuid::createUuid().toByteArray(QUuid::WithoutBraces); + if (m_fallbackDir.exists(uuid)) { + continue; + } + + if (!m_fallbackDir.mkpath(uuid)) { + logger.error() << "Unable to create the tmp fallback folder" + << m_fallbackDir.path() << uuid; + return; + } + + if (!m_fallbackDir.cd(uuid)) { + logger.error() << "Unable to open the tmp fallback folder" + << m_fallbackDir.path() << uuid; + return; + } + + break; + } + + m_state = Fallback; +} + +bool TemporaryDir::isValid() { + if (m_state == QT) { + if (m_tmpDir.isValid()) { + return true; + } + + fallback(); + } + + if (m_state == QT) { + return m_tmpDir.isValid(); + } + + return m_fallbackDir.exists(); +} + +QString TemporaryDir::path() { + if (!isValid()) { + return QString(); + } + + if (m_state == QT) { + return m_tmpDir.path(); + } + + return m_fallbackDir.path(); +} + +// static +void TemporaryDir::cleanupAll() { + QDir fallbackDir(rootAppFolder()); + + if (!fallbackDir.exists(TMP_FOLDER)) { + return; + } + + if (!fallbackDir.cd(TMP_FOLDER)) { + logger.error() << "Unable to open the tmp fallback folder" + << fallbackDir.path(); + return; + } + + if (!fallbackDir.removeRecursively()) { + logger.debug() << "Failed to remove the fallback dir" << fallbackDir.path(); + } +} diff --git a/src/temporarydir.h b/src/temporarydir.h new file mode 100644 index 00000000000..fdd113bd906 --- /dev/null +++ b/src/temporarydir.h @@ -0,0 +1,40 @@ +/* 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/. */ + +#ifndef TEMPORARYDIR_H +#define TEMPORARYDIR_H + +#include +#include +#include + +class TemporaryDir final : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(TemporaryDir) + + public: + TemporaryDir(QObject* parent = nullptr); + ~TemporaryDir(); + + static void cleanupAll(); + + QString errorString(); + QString filePath(const QString& fileName); + bool isValid(); + QString path(); + + // public to be used in unit-tests + void fallback(); + + private: + enum { + QT, + Fallback, + } m_state = QT; + + QTemporaryDir m_tmpDir; + QDir m_fallbackDir; +}; + +#endif // TEMPORARYDIR_H diff --git a/src/theme.h b/src/theme.h index cb9e1ae36b4..28e15abc565 100644 --- a/src/theme.h +++ b/src/theme.h @@ -49,7 +49,7 @@ class Theme final : public QAbstractListModel { StatusBarTextColorDark, }; Q_ENUM(StatusBarTextColor) - Q_INVOKABLE void setStatusBarTextColor(StatusBarTextColor color); + Q_INVOKABLE void setStatusBarTextColor(Theme::StatusBarTextColor color); private: void parseTheme(QJSEngine* engine, const QString& themeName); diff --git a/src/tutorial/tutorial.cpp b/src/tutorial/tutorial.cpp index adfb0612836..f93909537cb 100644 --- a/src/tutorial/tutorial.cpp +++ b/src/tutorial/tutorial.cpp @@ -31,7 +31,6 @@ Tutorial::Tutorial(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(Tutorial); MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn, &MozillaVPN::stateChanged, this, &Tutorial::stop); diff --git a/src/tutorial/tutorialstep.cpp b/src/tutorial/tutorialstep.cpp index 577b2a2aa06..729c81414c9 100644 --- a/src/tutorial/tutorialstep.cpp +++ b/src/tutorial/tutorialstep.cpp @@ -34,7 +34,7 @@ TutorialStep* TutorialStep::create(AddonTutorial* parent, return nullptr; } - stepId = QString("tutorial.%1.step.%2").arg(tutorialId).arg(stepId); + stepId = QString("tutorial.%1.step.%2").arg(tutorialId, stepId); QString element = obj["element"].toString(); if (element.isEmpty()) { diff --git a/src/tutorial/tutorialstepbefore.cpp b/src/tutorial/tutorialstepbefore.cpp index 0a6695491c3..729efdcabcb 100644 --- a/src/tutorial/tutorialstepbefore.cpp +++ b/src/tutorial/tutorialstepbefore.cpp @@ -235,7 +235,8 @@ QList TutorialStepBefore::create( QObject* parent, const QString& elementForTooltip, const QJsonValue& json) { QList list; - for (QJsonValue value : json.toArray()) { + QJsonArray array = json.toArray(); + for (const QJsonValue& value : array) { QJsonObject obj = value.toObject(); TutorialStepBefore* tsb = nullptr; diff --git a/src/ui/authenticationInApp/ViewAuthenticationUnblockCodeNeeded.qml b/src/ui/authenticationInApp/ViewAuthenticationUnblockCodeNeeded.qml index e5d7ab6f2c3..e0dc0addb88 100644 --- a/src/ui/authenticationInApp/ViewAuthenticationUnblockCodeNeeded.qml +++ b/src/ui/authenticationInApp/ViewAuthenticationUnblockCodeNeeded.qml @@ -49,7 +49,7 @@ VPNInAppAuthenticationBase { anchors.horizontalCenter: parent.horizontalCenter onClicked: { VPNAuthInApp.resendUnblockCodeEmail(); - VPN.setAlert(VPN.AuthCodeSentAlert); + VPNErrorHandler.setAlert(VPNErrorHandler.AuthCodeSentAlert); } } diff --git a/src/ui/authenticationInApp/ViewAuthenticationVerificationSessionByEmailNeeded.qml b/src/ui/authenticationInApp/ViewAuthenticationVerificationSessionByEmailNeeded.qml index 98718135696..74fb7d47681 100644 --- a/src/ui/authenticationInApp/ViewAuthenticationVerificationSessionByEmailNeeded.qml +++ b/src/ui/authenticationInApp/ViewAuthenticationVerificationSessionByEmailNeeded.qml @@ -45,7 +45,7 @@ VPNInAppAuthenticationBase { anchors.horizontalCenter: parent.horizontalCenter onClicked: { VPNAuthInApp.resendVerificationSessionCodeEmail(); - VPN.setAlert(VPN.AuthCodeSentAlert); + VPNErrorHandler.setAlert(VPNErrorHandler.AuthCodeSentAlert); } } diff --git a/src/ui/deleteAccount/ViewDeleteAccountRequest.qml b/src/ui/deleteAccount/ViewDeleteAccountRequest.qml index 7a57121389d..e89ea691cd6 100644 --- a/src/ui/deleteAccount/ViewDeleteAccountRequest.qml +++ b/src/ui/deleteAccount/ViewDeleteAccountRequest.qml @@ -102,7 +102,7 @@ VPNInAppAuthenticationBase { fontName: VPNTheme.theme.fontBoldFamily // Cancel labelText: VPNl18n.InAppSupportWorkflowSupportSecondaryActionText - linkColor: VPNTheme.theme.redButton + linkColor: VPNTheme.theme.redLinkButton onClicked: { cancelAuthenticationFlow(); } diff --git a/src/ui/main.qml b/src/ui/main.qml index 7ee80d9ea8e..526a4f82b8d 100644 --- a/src/ui/main.qml +++ b/src/ui/main.qml @@ -43,6 +43,10 @@ Window { case 2778: // iPhone_12_Pro_Max case 2340: // iPhone_12_mini return 34; + case 2556: // iPhone_14_Pro + return 48; + case 2796: // iPhone_14_Pro_Max + return 48; default: return 20; } @@ -141,7 +145,10 @@ Window { if (VPN.debugMode) { console.debug("Initializing glean with debug mode"); Glean.setLogPings(true); - Glean.setDebugViewTag("MozillaVPN"); + // Uncomment for debugging purposes. + // See: https://mozilla.github.io/glean/book/reference/debug/debugViewTag.html#debug-view-tag + // + // Glean.setDebugViewTag("MozillaVPN"); } var channel = VPN.stagingMode ? "staging" : "production"; @@ -176,7 +183,7 @@ Window { } function onAboutToQuit() { - console.debug("about to quit, shutdown Glean"); + console.debug("about to quit, shutdown Glean"); // Submit the main ping in case there are outstading metrics in storage before shutdown. Pings.main.submit(); // Use glean's built-in shutdown method - https://mozilla.github.io/glean/book/reference/general/shutdown.html diff --git a/src/ui/navigator/navigationBar/VPNBottomNavigationBar.qml b/src/ui/navigator/navigationBar/VPNBottomNavigationBar.qml index 963d21226f0..686d15dcf6f 100644 --- a/src/ui/navigator/navigationBar/VPNBottomNavigationBar.qml +++ b/src/ui/navigator/navigationBar/VPNBottomNavigationBar.qml @@ -173,6 +173,13 @@ Rectangle { } } + Connections { + target: VPNAddonManager + function onLoadCompletedChanged() { + root.getUnreadNotificationStatus() + } + } + function setNavBarOpacity() { if (VPNNavigator.screen === VPNNavigator.ScreenHome) { navbar.opacity = VPNConnectionBenchmark.state === VPNConnectionBenchmark.StateInitial ? 1 : 0 diff --git a/src/ui/screens/ScreenInitialize.qml b/src/ui/screens/ScreenInitialize.qml index 3afd4fb169a..8587b8a3593 100644 --- a/src/ui/screens/ScreenInitialize.qml +++ b/src/ui/screens/ScreenInitialize.qml @@ -12,9 +12,9 @@ VPNStackView { id: stackview objectName: "initialStackView" anchors.fill: parent - initialItem: "qrc:/ui/screens/initialize/ViewInitialize.qml" Component.onCompleted: function() { + stackview.push("qrc:/ui/screens/initialize/ViewInitialize.qml") VPNNavigator.addStackView(VPNNavigator.ScreenInitialize, stackview) } } diff --git a/src/ui/screens/ScreenInitializeMobileOnboarding.qml b/src/ui/screens/ScreenInitializeMobileOnboarding.qml index 756d5fc278f..785f425b754 100644 --- a/src/ui/screens/ScreenInitializeMobileOnboarding.qml +++ b/src/ui/screens/ScreenInitializeMobileOnboarding.qml @@ -12,9 +12,9 @@ VPNStackView { id: stackview objectName: "initialStackView" anchors.fill: parent - initialItem: "qrc:/ui/screens/initialize/ViewMobileOnboarding.qml" Component.onCompleted: function() { + stackview.push("qrc:/ui/screens/initialize/ViewMobileOnboarding.qml") VPNNavigator.addStackView(VPNNavigator.ScreenInitialize, stackview) } } diff --git a/src/ui/screens/getHelp/contactUs/ViewContactUsForm.qml b/src/ui/screens/getHelp/contactUs/ViewContactUsForm.qml index 80564aa0c66..bb1fa1955b0 100644 --- a/src/ui/screens/getHelp/contactUs/ViewContactUsForm.qml +++ b/src/ui/screens/getHelp/contactUs/ViewContactUsForm.qml @@ -25,11 +25,6 @@ VPNViewBase { VPN.createSupportTicket(email, subject, issueText, category); } - function fxaBrowserLink() { - VPNUrlOpener.openLink(VPNUrlOpener.LinkHelpSupport); - contactUsRoot.tryAgain(); - } - Connections { target: VPN function onTicketCreationAnswer(successful) { @@ -41,12 +36,15 @@ VPNViewBase { headlineText: VPNl18n.InAppSupportWorkflowSupportErrorHeader, errorMessage: VPNl18n.InAppSupportWorkflowSupportErrorText, primaryButtonText: VPNl18n.InAppSupportWorkflowSupportErrorButton, - primaryButtonOnClick: contactUsRoot.tryAgain, + primaryButtonOnClick: () => getHelpStackView.pop(), primaryButtonObjectName: "errorTryAgainButton", secondaryButtonIsSignOff: false, secondaryButtonText: VPNl18n.InAppSupportWorkflowSupportErrorBrowserButton, secondaryButtonObjectName: "errorFxALinkButton", - secondaryButtonOnClick: contactUsRoot.fxaBrowserLink + secondaryButtonOnClick: () => { + VPNUrlOpener.openLink(VPNUrlOpener.LinkHelpSupport); + getHelpStackView.pop(); + }, } ); } diff --git a/src/ui/screens/getHelp/giveFeedback/GiveFeedbackRadioDelegate.qml b/src/ui/screens/getHelp/giveFeedback/GiveFeedbackRadioDelegate.qml index d99b62f7f0f..2615d8c4d76 100644 --- a/src/ui/screens/getHelp/giveFeedback/GiveFeedbackRadioDelegate.qml +++ b/src/ui/screens/getHelp/giveFeedback/GiveFeedbackRadioDelegate.qml @@ -14,7 +14,7 @@ import compat 0.1 RadioDelegate { property var value property alias iconSource: img.source - property var heightWidth: 30 + property var iconSize: 30 property var uiState: VPNTheme.theme.uiState id: radio @@ -23,18 +23,31 @@ RadioDelegate { implicitWidth: VPNTheme.theme.rowHeight activeFocusOnTab: true focus: true + Component.onCompleted: { state = uiState.stateDefault } - indicator: VPNIcon { - id: img + indicator: Item { anchors.centerIn: parent - antialiasing: true - sourceSize.height: heightWidth - sourceSize.width: heightWidth - } + height: iconSize + width: iconSize + VPNIcon { + id: img + antialiasing: true + sourceSize.height: parent.height + sourceSize.width: parent.width + } + + VPNColorOverlay { + anchors.fill: parent + source: img + color: VPNTheme.colors.blue + visible: radio.checked || radio.state === uiState.stateHovered + || radio.state === uiState.statePressed + } + } Keys.onPressed: event => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) @@ -46,28 +59,27 @@ RadioDelegate { radio.clicked(); } - background: VPNFocusOutline { - opacity: radio.checked || radio.activeFocus ? 1 : 0 - color: VPNTheme.theme.transparent - border.width: 4 - border.color: VPNTheme.theme.blueFocusOutline - anchors.margins: 2 - radius: height + anchors.margins: VPNTheme.theme.focusBorderWidth + + border.color: VPNTheme.theme.blueFocusOutline + border.width: VPNTheme.theme.focusBorderWidth * 2 + color: VPNTheme.theme.transparent + opacity: radio.checked || radio.activeFocus ? 1 : 0 + radius: height - Behavior on opacity { - PropertyAnimation { - duration: 100 - } + Behavior on opacity { + PropertyAnimation { + duration: 100 } } + } VPNMouseArea { id: mouseArea - onPressedChanged: if (pressed) radio.forceActiveFocus() onMouseAreaClicked: function() { + radio.forceActiveFocus(); radio.checked = !radio.checked; } } - } diff --git a/src/ui/screens/getHelp/giveFeedback/ViewGiveFeedbackReview.qml b/src/ui/screens/getHelp/giveFeedback/ViewGiveFeedbackReview.qml index 924694db6c6..8b9fa77dccb 100644 --- a/src/ui/screens/getHelp/giveFeedback/ViewGiveFeedbackReview.qml +++ b/src/ui/screens/getHelp/giveFeedback/ViewGiveFeedbackReview.qml @@ -49,7 +49,7 @@ VPNViewBase { id: skipLink labelText: qsTrId("vpn.feedbackForm.skip") - onClicked: getHelpStackView.pop(null, StackView.Immediate); + onClicked: getHelpStackView.push("qrc:/ui/screens/getHelp/giveFeedback/ViewGiveFeedbackThankYou.qml"); implicitHeight: VPNTheme.theme.rowHeight Layout.alignment: Qt.AlignHCenter } diff --git a/src/ui/screens/messaging/ViewMessagesInbox.qml b/src/ui/screens/messaging/ViewMessagesInbox.qml index d49280ba179..6537a2be9f0 100644 --- a/src/ui/screens/messaging/ViewMessagesInbox.qml +++ b/src/ui/screens/messaging/ViewMessagesInbox.qml @@ -33,6 +33,11 @@ VPNViewBase { } } + signal editModeChanged + + //Weird workaround to fix VPN-2895 + onIsEditingChanged: editModeChanged() + _menuTitle: VPNl18n.InAppMessagingMenuTitle onVisibleChanged: if (!visible) resetPage() @@ -178,7 +183,6 @@ VPNViewBase { VPNSwipeDelegate { id: swipeDelegate - property bool isEditing: vpnFlickable.isEditing property real deleteLabelWidth: 0.0 //avoids qml warnings when addon messages get disabled via condition @@ -190,24 +194,14 @@ VPNViewBase { Layout.preferredHeight: content.item.implicitHeight Accessible.name: swipeDelegate.title + ". " + swipeDelegate.formattedDate + ". " + swipeDelegate.subtitle - onIsEditingChanged: { - if(isEditing) { - swipe.open(SwipeDelegate.Left) - } - else { - swipe.close() - } - } - onSwipeOpen: () => { deleteLabelWidth = swipe.leftItem.width if (vpnFlickable.allSwipesOpen() && !vpnFlickable.isEditing) vpnFlickable.isEditing = true } - onSwipeClose: () => { - if (!vpnFlickable.anySwipesOpen() && vpnFlickable.isEditing) vpnFlickable.isEditing = false - - } + onIsSwipeOpenChanged: { + if(!isSwipeOpen && !vpnFlickable.anySwipesOpen() && vpnFlickable.isEditing) vpnFlickable.isEditing = false + } onClicked: { if (vpnFlickable.anySwipesOpen()) vpnFlickable.closeAllSwipes() @@ -230,6 +224,7 @@ VPNViewBase { SwipeDelegate.onClicked: { swipeDelegate.swipe.close() // prevents weird iOS animation bug + swipeDelegate.isSwipeOpen = false divider.visible = false if(index === listView.count - 1) { dismissMessageAnimation.start() @@ -316,6 +311,19 @@ VPNViewBase { maximumLineCount: 1 } } + + Connections { + target: vpnFlickable + function onEditModeChanged() { + if(vpnFlickable.isEditing) { + swipeDelegate.swipe.open(SwipeDelegate.Left) + } + else { + swipeDelegate.swipe.close() + } + } + } + } Rectangle { diff --git a/src/ui/screens/settings/ViewSubscriptionManagement/ViewSubscriptionManagement.qml b/src/ui/screens/settings/ViewSubscriptionManagement/ViewSubscriptionManagement.qml index a1480cb15b4..d94df165438 100644 --- a/src/ui/screens/settings/ViewSubscriptionManagement/ViewSubscriptionManagement.qml +++ b/src/ui/screens/settings/ViewSubscriptionManagement/ViewSubscriptionManagement.qml @@ -107,7 +107,7 @@ VPNViewBase { objectName: "accountDeletionButton" fontName: VPNTheme.theme.fontBoldFamily labelText: VPNl18n.DeleteAccountButtonLabel - linkColor: VPNTheme.theme.redButton + linkColor: VPNTheme.theme.redLinkButton visible: VPNFeatureList.get("accountDeletion").isSupported onClicked: { diff --git a/src/ui/screens/settings/appPermissions/ViewAppPermissions.qml b/src/ui/screens/settings/appPermissions/ViewAppPermissions.qml index 059d3d589fd..439dc625add 100644 --- a/src/ui/screens/settings/appPermissions/ViewAppPermissions.qml +++ b/src/ui/screens/settings/appPermissions/ViewAppPermissions.qml @@ -66,7 +66,7 @@ VPNViewBase { console.debug("Error:"+ component.errorString() ); } - var alert = component.createObject(root, { + var alert = component.createObject(vpnFlickable, { isLayout:false, visible:true, alertText: message, diff --git a/src/ui/screens/tipsAndTricks/ViewTipsAndTricks.qml b/src/ui/screens/tipsAndTricks/ViewTipsAndTricks.qml index fca78a31042..de2f60d05bf 100644 --- a/src/ui/screens/tipsAndTricks/ViewTipsAndTricks.qml +++ b/src/ui/screens/tipsAndTricks/ViewTipsAndTricks.qml @@ -79,6 +79,7 @@ VPNViewBase { // All VPNViewBase { objectName: 'allTab' + anchors.topMargin: 0 _viewContentData: ColumnLayout { id: layoutAll @@ -135,6 +136,8 @@ VPNViewBase { // Tutorials VPNViewBase { + anchors.topMargin: 0 + _viewContentData: ColumnLayout { id: layoutTutorial @@ -166,6 +169,8 @@ VPNViewBase { // Tips VPNViewBase { + anchors.topMargin: 0 + _viewContentData: ColumnLayout { id: layoutGuide diff --git a/src/update/balrog.cpp b/src/update/balrog.cpp index 6d5df486278..490184461e4 100644 --- a/src/update/balrog.cpp +++ b/src/update/balrog.cpp @@ -18,7 +18,6 @@ #include #include #include -#include // Terrible hacking for Windows #if defined(MVPN_WINDOWS) @@ -27,8 +26,8 @@ # include "platforms/windows/golang-msvc-types.h" #endif -// Import balrog C/Go library (unless we are building on Windows with qmake) -#if !(defined(MVPN_WINDOWS) && defined(BUILD_QMAKE)) +// Import balrog C/Go library, except on windows where we need to use a DLL. +#if !defined(MVPN_WINDOWS) extern "C" { # include "balrog-api.h" } @@ -55,8 +54,11 @@ void balrogLogger(int level, const char* msg) { } // namespace -Balrog::Balrog(QObject* parent, bool downloadAndInstall) - : Updater(parent), m_downloadAndInstall(downloadAndInstall) { +Balrog::Balrog(QObject* parent, bool downloadAndInstall, + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) + : Updater(parent), + m_downloadAndInstall(downloadAndInstall), + m_errorPropagationPolicy(errorPropagationPolicy) { MVPN_COUNT_CTOR(Balrog); logger.debug() << "Balrog created"; } @@ -184,7 +186,7 @@ bool Balrog::checkSignature(Task* task, const QByteArray& x5uData, bool Balrog::validateSignature(const QByteArray& x5uData, const QByteArray& updateData, const QByteArray& signatureBlob) { -#if defined(MVPN_WINDOWS) && defined(BUILD_QMAKE) +#if defined(MVPN_WINDOWS) typedef void BalrogSetLogger(GoUintptr func); typedef GoUint8 BalrogValidate(GoString x5uData, GoString updateData, GoString signature, GoString rootHash, @@ -232,7 +234,7 @@ bool Balrog::validateSignature(const QByteArray& x5uData, QByteArray updateDataCopy = updateData; GoString updateDataGo{updateDataCopy.constData(), updateDataCopy.length()}; - QByteArray rootHashCopy = Constants::balrogRootCertFingerprint(); + QByteArray rootHashCopy = Constants::AUTOGRAPH_ROOT_CERT_FINGERPRINT; rootHashCopy = rootHashCopy.toUpper(); GoString rootHashGo{rootHashCopy.constData(), rootHashCopy.length()}; @@ -503,15 +505,12 @@ void Balrog::propagateError(NetworkRequest* request, QNetworkReply::NetworkError error) { Q_ASSERT(request); - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - // 451 Unavailable For Legal Reasons if (request->statusCode() == 451) { logger.debug() << "Geo IP restriction detected"; - vpn->errorHandle(ErrorHandler::GeoIpRestrictionError); + ErrorHandler::instance()->errorHandle(ErrorHandler::GeoIpRestrictionError); return; } - vpn->errorHandle(ErrorHandler::toErrorType(error)); + ErrorHandler::networkErrorHandle(error, m_errorPropagationPolicy); } diff --git a/src/update/balrog.h b/src/update/balrog.h index ff307dfb04a..8ef3777dd20 100644 --- a/src/update/balrog.h +++ b/src/update/balrog.h @@ -5,11 +5,12 @@ #ifndef BALROG_H #define BALROG_H +#include "errorhandler.h" #include "updater.h" +#include "temporarydir.h" #include #include -#include class NetworkRequest; @@ -17,7 +18,8 @@ class Balrog final : public Updater { Q_DISABLE_COPY_MOVE(Balrog) public: - Balrog(QObject* parent, bool downloadAndInstall); + Balrog(QObject* parent, bool downloadAndInstall, + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); ~Balrog(); void start(Task* task) override; @@ -42,8 +44,10 @@ class Balrog final : public Updater { QNetworkReply::NetworkError error); private: - QTemporaryDir m_tmpDir; + TemporaryDir m_tmpDir; bool m_downloadAndInstall; + ErrorHandler::ErrorPropagationPolicy m_errorPropagationPolicy = + ErrorHandler::DoNotPropagateError; }; #endif // BALROG_H diff --git a/src/update/updater.cpp b/src/update/updater.cpp index ca909f70e16..80214bb7909 100644 --- a/src/update/updater.cpp +++ b/src/update/updater.cpp @@ -21,15 +21,15 @@ Logger logger(LOG_NETWORKING, "Updater"); } // static -Updater* Updater::create(QObject* parent, bool downloadAndInstall) { +Updater* Updater::create( + QObject* parent, bool downloadAndInstall, + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy) { #ifdef MVPN_BALROG - if (!downloadAndInstall) { - return new Balrog(parent, false); - } - - return new Balrog(parent, true); + return new Balrog(parent, downloadAndInstall, errorPropagationPolicy); #endif + Q_UNUSED(errorPropagationPolicy); + if (!downloadAndInstall) { return new VersionApi(parent); } diff --git a/src/update/updater.h b/src/update/updater.h index 33a57e7be24..37191427f01 100644 --- a/src/update/updater.h +++ b/src/update/updater.h @@ -5,6 +5,8 @@ #ifndef UPDATER_H #define UPDATER_H +#include "errorhandler.h" + #include class Task; @@ -27,7 +29,9 @@ class Updater : public QObject { }; Q_ENUM(Step); - static Updater* create(QObject* parent, bool downloadAndInstall); + static Updater* create( + QObject* parent, bool downloadAndInstall, + ErrorHandler::ErrorPropagationPolicy errorPropagationPolicy); static void updateViewShown(); diff --git a/src/update/versionapi.cpp b/src/update/versionapi.cpp index 86e8f056f5a..e9c5d886a76 100644 --- a/src/update/versionapi.cpp +++ b/src/update/versionapi.cpp @@ -30,7 +30,7 @@ VersionApi::~VersionApi() { void VersionApi::start(Task* task) { NetworkRequest* request = NetworkRequest::createForVersions(task); - connect(request, &NetworkRequest::requestFailed, + connect(request, &NetworkRequest::requestFailed, request, [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.error() << "Request failed" << error; deleteLater(); @@ -134,7 +134,7 @@ int VersionApi::compareVersions(const QString& a, const QString& b) { return -1; } - QRegularExpression re("[^0-9a-z.]"); + static QRegularExpression re("[^0-9a-z.]"); QStringList aParts; int aMatchLength = a.indexOf(re); @@ -165,7 +165,7 @@ QString VersionApi::stripMinor(const QString& a) { QStringList aParts; if (!a.isEmpty()) { - QRegularExpression re("[^0-9a-z.]"); + static QRegularExpression re("[^0-9a-z.]"); int matchLength = a.indexOf(re); aParts = (matchLength < 0) ? a.split(".") : a.left(matchLength).split("."); } diff --git a/src/update/webupdater.cpp b/src/update/webupdater.cpp index 8315484459c..29444701998 100644 --- a/src/update/webupdater.cpp +++ b/src/update/webupdater.cpp @@ -25,9 +25,6 @@ WebUpdater::~WebUpdater() { } void WebUpdater::start(Task*) { - MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); - emit MozillaVPN::instance()->recordGleanEventWithExtraKeys( GleanSample::updateStep, {{"state", QVariant::fromValue(FallbackInBrowser).toString()}}); diff --git a/src/urlopener.h b/src/urlopener.h index 84222cb7831..1510cb48191 100644 --- a/src/urlopener.h +++ b/src/urlopener.h @@ -41,7 +41,7 @@ class UrlOpener final : public QObject { static UrlOpener* instance(); ~UrlOpener(); - Q_INVOKABLE void openLink(LinkType linkType); + Q_INVOKABLE void openLink(UrlOpener::LinkType linkType); Q_INVOKABLE void openUrl(const QString& linkUrl); void open(QUrl url, bool addEmailAddress = false); diff --git a/src/websocket/pushmessage.cpp b/src/websocket/pushmessage.cpp index 43db8e47749..37fbec63c23 100644 --- a/src/websocket/pushmessage.cpp +++ b/src/websocket/pushmessage.cpp @@ -137,7 +137,6 @@ bool PushMessage::handleDeviceDeleted(const QJsonObject& payload) { } MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); if (vpn->keys()->publicKey() == publicKey) { logger.info() << "Current device has been deleted from this subscription."; vpn->reset(true); diff --git a/src/websocket/websockethandler.cpp b/src/websocket/websockethandler.cpp index fbe85935a3c..6eba06dca49 100644 --- a/src/websocket/websockethandler.cpp +++ b/src/websocket/websockethandler.cpp @@ -84,7 +84,6 @@ void WebSocketHandler::initialize() { logger.debug() << "Initialize"; MozillaVPN* vpn = MozillaVPN::instance(); - Q_ASSERT(vpn); connect(vpn, &MozillaVPN::userStateChanged, this, &WebSocketHandler::onUserStateChanged); diff --git a/src/wgquickprocess.cpp b/src/wgquickprocess.cpp index f6d166c9195..db48c023594 100644 --- a/src/wgquickprocess.cpp +++ b/src/wgquickprocess.cpp @@ -6,7 +6,6 @@ #include "../../src/logger.h" #include -#include #include namespace { @@ -97,4 +96,4 @@ QString WgQuickProcess::createConfigString( logger.debug() << content; #endif return content; -} \ No newline at end of file +} diff --git a/taskcluster/ci/beetmover/kind.yml b/taskcluster/ci/beetmover/kind.yml index a303800a62e..54c235356cc 100644 --- a/taskcluster/ci/beetmover/kind.yml +++ b/taskcluster/ci/beetmover/kind.yml @@ -20,7 +20,7 @@ tasks: worker: chain-of-trust: true max-run-time: 1800 - run-on-tasks-for: [github-release, action:release-promotion] + run-on-tasks-for: [action] release-artifacts: [MozillaVPN.pkg] dependencies: signing: signing-macos/opt @@ -37,7 +37,7 @@ tasks: worker: chain-of-trust: true max-run-time: 1800 - run-on-tasks-for: [github-release, action:release-promotion] + run-on-tasks-for: [action] release-artifacts: [MozillaVPN.msi] dependencies: repackage-signing: repackage-signing-msi @@ -54,7 +54,7 @@ tasks: worker: chain-of-trust: true max-run-time: 1800 - run-on-tasks-for: [action:release-promotion] + run-on-tasks-for: [action] # The addons-bundle release-artifacts are dynamically generated in the beetmover transform release-artifacts: [] dependencies: @@ -72,7 +72,7 @@ tasks: worker: chain-of-trust: true max-run-time: 1800 - run-on-tasks-for: [action:release-promotion] + run-on-tasks-for: [action] release-artifacts: - manifest.json - manifest.json.sig diff --git a/taskcluster/ci/build/android.yml b/taskcluster/ci/build/android.yml index 47e1f4fbf04..0d9f8288b6a 100644 --- a/taskcluster/ci/build/android.yml +++ b/taskcluster/ci/build/android.yml @@ -17,6 +17,19 @@ task-defaults: use-caches: true cwd: '{checkout}' +android-qt-next/debug: + description: "Android Next QT Build (arm64-v8a)" + treeherder: + platform: android/arm64-v8a + symbol: NEXT + worker: + docker-image: {in-tree: android-qt-next} + run: + command: ./taskcluster/scripts/build/android_build_debug.sh arm64-v8a + release-artifacts: + # APK Artifacts expects file to be in /builds/worker/artifacts/ + - mozillavpn-arm64-v8a-debug.apk + # Debug Builds: android-arm64/debug: description: "Android Debug (arm64-v8a)" diff --git a/taskcluster/ci/docker-image/kind.yml b/taskcluster/ci/docker-image/kind.yml index 6b8a0733b3d..4d01ea46c67 100644 --- a/taskcluster/ci/docker-image/kind.yml +++ b/taskcluster/ci/docker-image/kind.yml @@ -62,3 +62,10 @@ tasks: args: ANDROID_ARCH: android_x86_64 QT_VERSION: 6.2.4 + android-qt-next: + parent: base + symbol: I(android_arm64_v8a_next) + definition: android-qt6-build + args: + ANDROID_ARCH: android_arm64_v8a + QT_VERSION: 6.4.0 diff --git a/taskcluster/docker/android-qt6-build/Dockerfile b/taskcluster/docker/android-qt6-build/Dockerfile index 60ce5e1f1c3..9f417013c20 100644 --- a/taskcluster/docker/android-qt6-build/Dockerfile +++ b/taskcluster/docker/android-qt6-build/Dockerfile @@ -41,10 +41,12 @@ RUN python3 -m aqt install-qt --outputdir /opt linux android ${QT_VERSION} ${AND pip install -r requirements.txt &&\ unzip commandlinetools-linux-7583922_latest.zip -d /opt &&\ unzip ${NDK_FILE} -d /opt/NDK/ &&\ - echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "platforms;android-30" &&\ + echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "platforms;android-31" &&\ # :qtBindings:compileDebugAidl requires build-tools;29.0.2 echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "build-tools;29.0.2" &&\ echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "build-tools;30.0.3" &&\ + echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "build-tools;31.0.0" &&\ + echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "cmake;3.10.2.4988404" &&\ echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "cmake;3.18.1" &&\ # Note: Not sure why we need emulator, need to investiage the gradle dependencies echo y | /opt/cmdline-tools/bin/sdkmanager --sdk_root=/opt/android/sdk --install "emulator" &&\ diff --git a/taskcluster/docker/lint/Dockerfile b/taskcluster/docker/lint/Dockerfile index 01a018acd20..0915f3f2a75 100644 --- a/taskcluster/docker/lint/Dockerfile +++ b/taskcluster/docker/lint/Dockerfile @@ -1,11 +1,9 @@ FROM $DOCKER_IMAGE_PARENT -# Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 RUN apt-get update && \ apt-get install --yes software-properties-common && \ apt-get update && \ - add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y && \ - apt-get install --yes git qt515tools clang-format-11 openjdk-11-jdk python3-lxml && \ + apt-get install --yes git clang-format-11 openjdk-11-jdk python3-lxml && \ apt-get remove --yes software-properties-common && \ apt-get clean diff --git a/taskcluster/mozillavpn_taskgraph/transforms/beetmover.py b/taskcluster/mozillavpn_taskgraph/transforms/beetmover.py index db711e24698..98a6455f9bd 100644 --- a/taskcluster/mozillavpn_taskgraph/transforms/beetmover.py +++ b/taskcluster/mozillavpn_taskgraph/transforms/beetmover.py @@ -16,6 +16,7 @@ def add_addons_release_artifacts(config, tasks): if task["attributes"]["build-type"] == "addons/opt" and task["name"] == "addons-bundle": addons = set(os.listdir("addons")) addons.remove("examples") + addons.remove("deprecated") for addon in addons: task["attributes"]["release-artifacts"].append( { @@ -38,7 +39,11 @@ def add_beetmover_worker_config(config, tasks): bucket = "release" if is_relpro else "dep" build_id = config.params["moz_build_date"] build_type = task["attributes"]["build-type"] - build_os = os.path.dirname(build_type) + build_type_os = { + 'macos/opt': 'mac', + 'windows/opt': 'windows', + } + build_os = build_type_os.get(build_type) shipping_phase = config.params.get("shipping_phase", "") if config.params["version"]: diff --git a/taskcluster/mozillavpn_taskgraph/transforms/signing.py b/taskcluster/mozillavpn_taskgraph/transforms/signing.py index a1d79ed722d..2c635d73a6d 100644 --- a/taskcluster/mozillavpn_taskgraph/transforms/signing.py +++ b/taskcluster/mozillavpn_taskgraph/transforms/signing.py @@ -20,13 +20,14 @@ "linux/opt", "macos/opt", "windows/opt", + "addons/opt", ] SIGNING_BUILD_TYPES = PRODUCTION_SIGNING_BUILD_TYPES + [ # Note: it appears we don't have infra for debug signing # android builds. Contact releng if you need it :) # "android-debug", - "addons/opt", + # "addons/opt", # TODO: Add addons debug builds? We have the infra to debug sign them. ] diff --git a/taskcluster/scripts/build/macos_build.sh b/taskcluster/scripts/build/macos_build.sh index bbfb1d6e2f6..adabb4d6fc8 100755 --- a/taskcluster/scripts/build/macos_build.sh +++ b/taskcluster/scripts/build/macos_build.sh @@ -62,7 +62,7 @@ echo "DEVELOPMENT_TEAM = 43AQ936H96" >> xcode.xconfig echo "GROUP_ID_MACOS = group.org.mozilla.macos.Guardian" >> xcode.xconfig echo "APP_ID_MACOS = org.mozilla.macos.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_MACOS = org.mozilla.macos.FirefoxVPN.network-extension" >> xcode.xconfig -echo "LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login" >> xcode.xconfig +echo "LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login-item" >> xcode.xconfig echo "GROUP_ID_IOS = group.org.mozilla.ios.Guardian" >> xcode.xconfig echo "APP_ID_IOS = org.mozilla.ios.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension" >> xcode.xconfig diff --git a/taskcluster/scripts/build/windows.ps1 b/taskcluster/scripts/build/windows.ps1 index 0eaf6424609..201a8460ef5 100644 --- a/taskcluster/scripts/build/windows.ps1 +++ b/taskcluster/scripts/build/windows.ps1 @@ -20,7 +20,7 @@ Remove-Item $FETCHES_PATH/VisualStudio/VC/Tools/MSVC/14.30.30705/bin/HostX64/x64 # Fetch 3rdparty stuff. python3 -m pip install -r requirements.txt --user -git submodule update --init --force --recursive --depth=1 +git submodule update --init --force --recursive --remote --depth=1 # Fix: pip scripts are not on path by default on tc, so glean would fail $PYTHON_SCRIPTS =resolve-path "$env:APPDATA\Python\Python36\Scripts" diff --git a/taskcluster/scripts/push-addons/deploy.sh b/taskcluster/scripts/push-addons/deploy.sh index b64a970bf00..b1ba3bf8305 100755 --- a/taskcluster/scripts/push-addons/deploy.sh +++ b/taskcluster/scripts/push-addons/deploy.sh @@ -16,6 +16,6 @@ echo "Fetching Tokens!" ls cd /builds/worker/fetches/ ls -openssl dgst -sha256 -sign /builds/worker/checkouts/vcs/addons_key.pem -out addons/manifest.json.sign addons/manifest.json +openssl dgst -sha256 -sign /builds/worker/checkouts/vcs/addons_key.pem -out addons/manifest.json.sig addons/manifest.json -cp -r /builds/worker/fetches/addons /builds/worker/artifacts \ No newline at end of file +cp -r /builds/worker/fetches/addons /builds/worker/artifacts diff --git a/tests/auth/CMakeLists.txt b/tests/auth/CMakeLists.txt index de3fe518451..0079c1bdba6 100644 --- a/tests/auth/CMakeLists.txt +++ b/tests/auth/CMakeLists.txt @@ -46,6 +46,9 @@ target_sources(auth_tests PRIVATE ${MVPN_SOURCE_DIR}/authenticationlistener.h ${MVPN_SOURCE_DIR}/constants.cpp ${MVPN_SOURCE_DIR}/constants.h + ${MVPN_SOURCE_DIR}/controller.h + ${MVPN_SOURCE_DIR}/dnspingsender.cpp + ${MVPN_SOURCE_DIR}/dnspingsender.h ${MVPN_SOURCE_DIR}/env.h ${MVPN_SOURCE_DIR}/errorhandler.cpp ${MVPN_SOURCE_DIR}/errorhandler.h @@ -73,6 +76,14 @@ target_sources(auth_tests PRIVATE ${MVPN_SOURCE_DIR}/networkmanager.h ${MVPN_SOURCE_DIR}/networkrequest.cpp ${MVPN_SOURCE_DIR}/networkrequest.h + ${MVPN_SOURCE_DIR}/pinghelper.cpp + ${MVPN_SOURCE_DIR}/pinghelper.h + ${MVPN_SOURCE_DIR}/pingsender.cpp + ${MVPN_SOURCE_DIR}/pingsender.h + ${MVPN_SOURCE_DIR}/pingsenderfactory.cpp + ${MVPN_SOURCE_DIR}/pingsenderfactory.h + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.cpp + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.h ${MVPN_SOURCE_DIR}/rfc/rfc1918.cpp ${MVPN_SOURCE_DIR}/rfc/rfc1918.h ${MVPN_SOURCE_DIR}/rfc/rfc4193.cpp @@ -126,6 +137,7 @@ target_sources(auth_tests PRIVATE testsignupandin.cpp testsignupandin.h mocmozillavpn.cpp + ../qml/moccontroller.cpp ../unit/mocinspectorhandler.cpp ) diff --git a/tests/auth/auth.pro b/tests/auth/auth.pro index aecc39dc065..839481833cb 100644 --- a/tests/auth/auth.pro +++ b/tests/auth/auth.pro @@ -42,6 +42,8 @@ HEADERS += \ ../../src/authenticationinapp/incrementaldecoder.h \ ../../src/authenticationlistener.h \ ../../src/constants.h \ + ../../src/controller.h \ + ../../src/dnspingsender.h \ ../../src/env.h \ ../../src/errorhandler.h \ ../../src/hawkauth.h \ @@ -57,6 +59,10 @@ HEADERS += \ ../../src/mozillavpn.h \ ../../src/networkmanager.h \ ../../src/networkrequest.h \ + ../../src/pinghelper.h \ + ../../src/pingsender.h \ + ../../src/pingsenderfactory.h \ + ../../src/platforms/dummy/dummypingsender.h \ ../../src/rfc/rfc1918.h \ ../../src/rfc/rfc4193.h \ ../../src/rfc/rfc4291.h \ @@ -79,6 +85,7 @@ HEADERS += \ SOURCES += \ mocmozillavpn.cpp \ + ../qml/moccontroller.cpp \ ../unit/mocinspectorhandler.cpp \ ../../src/authenticationinapp/authenticationinapp.cpp \ ../../src/authenticationinapp/authenticationinapplistener.cpp \ @@ -86,6 +93,7 @@ SOURCES += \ ../../src/authenticationinapp/incrementaldecoder.cpp \ ../../src/authenticationlistener.cpp \ ../../src/constants.cpp \ + ../../src/dnspingsender.cpp \ ../../src/errorhandler.cpp \ ../../src/hawkauth.cpp \ ../../src/hkdf.cpp \ @@ -98,6 +106,10 @@ SOURCES += \ ../../src/models/server.cpp \ ../../src/networkmanager.cpp \ ../../src/networkrequest.cpp \ + ../../src/pinghelper.cpp \ + ../../src/pingsender.cpp \ + ../../src/pingsenderfactory.cpp \ + ../../src/platforms/dummy/dummypingsender.cpp \ ../../src/rfc/rfc1918.cpp \ ../../src/rfc/rfc4193.cpp \ ../../src/rfc/rfc4291.cpp \ diff --git a/tests/auth/mocmozillavpn.cpp b/tests/auth/mocmozillavpn.cpp index a6f27696c20..400bb38850b 100644 --- a/tests/auth/mocmozillavpn.cpp +++ b/tests/auth/mocmozillavpn.cpp @@ -23,6 +23,8 @@ MozillaVPN::MozillaVPN() {} MozillaVPN::~MozillaVPN() {} +ConnectionHealth* MozillaVPN::connectionHealth() { return nullptr; } + Controller* MozillaVPN::controller() { return nullptr; } MozillaVPN::State MozillaVPN::state() const { return StateInitialize; } @@ -62,10 +64,6 @@ void MozillaVPN::cancelAuthentication() {} void MozillaVPN::logout() {} -void MozillaVPN::setAlert(AlertType) {} - -void MozillaVPN::errorHandle(ErrorHandler::ErrorType) {} - const QList MozillaVPN::exitServers() const { return QList(); } const QList MozillaVPN::entryServers() const { return QList(); } @@ -157,6 +155,8 @@ void MozillaVPN::hardReset() {} void MozillaVPN::crashTest() {} +void MozillaVPN::exitForUnrecoverableError(const QString& reason) {} + QString MozillaVPN::devVersion() { return qVersion(); } QString MozillaVPN::graphicsApi() { return ""; } diff --git a/tests/auth/testpasswordvalidation.cpp b/tests/auth/testpasswordvalidation.cpp index 21375cd4002..409a0eb0e71 100644 --- a/tests/auth/testpasswordvalidation.cpp +++ b/tests/auth/testpasswordvalidation.cpp @@ -16,7 +16,7 @@ class EventLoop final : public QEventLoop { public: void exec() { QTimer timer; - connect(&timer, &QTimer::timeout, [&]() { + connect(&timer, &QTimer::timeout, &timer, [&]() { qDebug() << "TIMEOUT!"; exit(); }); @@ -119,7 +119,7 @@ void TestPasswordValidation::emailPassword() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateStart) { loop.exit(); } @@ -133,7 +133,7 @@ void TestPasswordValidation::emailPassword() { // Account aia->checkAccount(emailAddress); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { QVERIFY(aia->state() != AuthenticationInApp::StateSignIn); if (aia->state() == AuthenticationInApp::StateSignUp) { loop.exit(); diff --git a/tests/auth/testsignupandin.cpp b/tests/auth/testsignupandin.cpp index 96ba29aff2f..4f6edd41fb1 100644 --- a/tests/auth/testsignupandin.cpp +++ b/tests/auth/testsignupandin.cpp @@ -25,7 +25,7 @@ class EventLoop final : public QEventLoop { public: void exec() { QTimer timer; - connect(&timer, &QTimer::timeout, [&]() { + connect(&timer, &QTimer::timeout, &timer, [&]() { qDebug() << "TIMEOUT!"; exit(); }); @@ -56,7 +56,7 @@ void TestSignUpAndIn::signUp() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateStart) { loop.exit(); } @@ -73,7 +73,7 @@ void TestSignUpAndIn::signUp() { aia->checkAccount(emailAddress); QCOMPARE(aia->state(), AuthenticationInApp::StateCheckingAccount); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { QVERIFY(aia->state() != AuthenticationInApp::StateSignIn); if (aia->state() == AuthenticationInApp::StateSignUp) { loop.exit(); @@ -95,7 +95,7 @@ void TestSignUpAndIn::signUp() { aia->signUp(); QCOMPARE(aia->state(), AuthenticationInApp::StateSigningUp); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateVerificationSessionByEmailNeeded) { loop.exit(); @@ -111,7 +111,7 @@ void TestSignUpAndIn::signUp() { aia->verifySessionEmailCode("000000"); QCOMPARE(aia->state(), AuthenticationInApp::StateVerifyingSessionEmailCode); - connect(aia, &AuthenticationInApp::errorOccurred, + connect(aia, &AuthenticationInApp::errorOccurred, aia, [&](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorInvalidOrExpiredVerificationCode) { @@ -130,7 +130,7 @@ void TestSignUpAndIn::signUp() { aia->verifySessionEmailCode(code); QCOMPARE(aia->state(), AuthenticationInApp::StateVerifyingSessionEmailCode); - connect(&task, &Task::completed, [&]() { loop.exit(); }); + connect(&task, &Task::completed, &task, [&]() { loop.exit(); }); if (m_totpCreation) { waitForTotpCodes(); @@ -157,7 +157,7 @@ void TestSignUpAndIn::signUpWithError() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateStart) { loop.exit(); } @@ -174,7 +174,7 @@ void TestSignUpAndIn::signUpWithError() { aia->checkAccount(emailAddress); QCOMPARE(aia->state(), AuthenticationInApp::StateCheckingAccount); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateSignIn) { loop.exit(); } @@ -192,7 +192,7 @@ void TestSignUpAndIn::signUpWithError() { QCOMPARE(aia->state(), AuthenticationInApp::StateSigningUp); connect( - aia, &AuthenticationInApp::errorOccurred, + aia, &AuthenticationInApp::errorOccurred, aia, [&](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorAccountAlreadyExists) { qDebug() << "The account already exist. Error correctly propagated."; @@ -216,7 +216,7 @@ void TestSignUpAndIn::signIn() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateStart) { loop.exit(); } @@ -237,7 +237,7 @@ void TestSignUpAndIn::signIn() { aia->checkAccount(emailAddress); QCOMPARE(aia->state(), AuthenticationInApp::StateCheckingAccount); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateSignIn) { loop.exit(); } @@ -253,7 +253,7 @@ void TestSignUpAndIn::signIn() { // We run this part only for non-blocked accounts because for them, the // password doesn't really matter. aia->setPassword("Invalid!"); - connect(aia, &AuthenticationInApp::errorOccurred, + connect(aia, &AuthenticationInApp::errorOccurred, aia, [&](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorIncorrectPassword) { qDebug() << "Incorrect password!"; @@ -279,7 +279,7 @@ void TestSignUpAndIn::signIn() { bool wrongUnblockCodeSent = false; - connect(aia, &AuthenticationInApp::errorOccurred, + connect(aia, &AuthenticationInApp::errorOccurred, aia, [this](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorInvalidUnblockCode) { qDebug() << "Invalid unblock code. Sending a good one"; @@ -297,7 +297,7 @@ void TestSignUpAndIn::signIn() { } }); - connect(&task, &Task::completed, [&]() { loop.exit(); }); + connect(&task, &Task::completed, &task, [&]() { loop.exit(); }); loop.exec(); disconnect(aia, nullptr, nullptr, nullptr); @@ -315,7 +315,7 @@ void TestSignUpAndIn::signInWithError() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateStart) { loop.exit(); } @@ -332,7 +332,7 @@ void TestSignUpAndIn::signInWithError() { aia->checkAccount(emailAddress); QCOMPARE(aia->state(), AuthenticationInApp::StateCheckingAccount); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateSignUp) { loop.exit(); } @@ -349,7 +349,7 @@ void TestSignUpAndIn::signInWithError() { aia->signIn(); QCOMPARE(aia->state(), AuthenticationInApp::StateSigningIn); - connect(aia, &AuthenticationInApp::errorOccurred, + connect(aia, &AuthenticationInApp::errorOccurred, aia, [&](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorUnknownAccount) { qDebug() << "The account does not exist yet"; @@ -374,7 +374,7 @@ void TestSignUpAndIn::deleteAccount() { task.run(); EventLoop loop; - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateCheckingAccount) { loop.exit(); } @@ -382,7 +382,7 @@ void TestSignUpAndIn::deleteAccount() { loop.exec(); disconnect(aia, nullptr, nullptr, nullptr); - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateSignIn) { loop.exit(); } @@ -403,7 +403,7 @@ void TestSignUpAndIn::deleteAccount() { waitForTotpCodes(); } - connect(aia, &AuthenticationInApp::stateChanged, [&]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [&]() { if (aia->state() == AuthenticationInApp::StateUnblockCodeNeeded) { fetchAndSendUnblockCode(); } else if (aia->state() == @@ -419,7 +419,7 @@ void TestSignUpAndIn::deleteAccount() { aia->deleteAccount(); - connect(&task, &Task::completed, [&]() { loop.exit(); }); + connect(&task, &Task::completed, &task, [&]() { loop.exit(); }); loop.exec(); disconnect(aia, nullptr, nullptr, nullptr); } @@ -437,7 +437,7 @@ void TestSignUpAndIn::waitForTotpCodes() { AuthenticationInApp* aia = AuthenticationInApp::instance(); - connect(aia, &AuthenticationInApp::errorOccurred, + connect(aia, &AuthenticationInApp::errorOccurred, aia, [this](AuthenticationInApp::ErrorType error, uint32_t) { if (error == AuthenticationInApp::ErrorInvalidTotpCode) { qDebug() << "Invalid code. Let's send the right one"; @@ -470,7 +470,7 @@ void TestSignUpAndIn::waitForTotpCodes() { } }); - connect(aia, &AuthenticationInApp::unitTestTotpCodeCreated, + connect(aia, &AuthenticationInApp::unitTestTotpCodeCreated, aia, [this](const QByteArray& data) { qDebug() << "Codes received"; QJsonDocument json = QJsonDocument::fromJson(data); @@ -479,7 +479,7 @@ void TestSignUpAndIn::waitForTotpCodes() { QVERIFY(!m_totpSecret.isEmpty()); }); - connect(aia, &AuthenticationInApp::stateChanged, [this]() { + connect(aia, &AuthenticationInApp::stateChanged, aia, [this]() { AuthenticationInApp* aia = AuthenticationInApp::instance(); qDebug() << "Send wrong code:" << m_sendWrongTotpCode; @@ -508,15 +508,16 @@ QString TestSignUpAndIn::fetchCode(const QString& code) { QByteArray jsonData; EventLoop loop; - connect(nr, &NetworkRequest::requestFailed, + connect(nr, &NetworkRequest::requestFailed, nr, [&](QNetworkReply::NetworkError, const QByteArray&) { qDebug() << "Failed to fetch the restmail.net content"; loop.exit(); }); - connect(nr, &NetworkRequest::requestCompleted, [&](const QByteArray& data) { - jsonData = data; - loop.exit(); - }); + connect(nr, &NetworkRequest::requestCompleted, nr, + [&](const QByteArray& data) { + jsonData = data; + loop.exit(); + }); loop.exec(); QJsonDocument doc(QJsonDocument::fromJson(jsonData)); @@ -532,7 +533,7 @@ QString TestSignUpAndIn::fetchCode(const QString& code) { qDebug() << "Email not received yet"; QTimer timer; - connect(&timer, &QTimer::timeout, [&]() { loop.exit(); }); + connect(&timer, &QTimer::timeout, &timer, [&]() { loop.exit(); }); timer.setSingleShot(true); timer.start(2000 /* 2 seconds */); loop.exec(); diff --git a/tests/functional/actions.js b/tests/functional/actions.js new file mode 100644 index 00000000000..644e5edfb9b --- /dev/null +++ b/tests/functional/actions.js @@ -0,0 +1,37 @@ +/* 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/. */ + +const elements = require('./elements.js'); +const vpn = require('./helper.js'); + +const setConnectionChangeNotification = async (status) => { + return await vpn.setSetting('connection-change-notification', status ? "true" : "false"); +} + +const setServerSwitchNotification = async (status) => { + return await vpn.setSetting('server-switch-notification', status ? "true" : "false"); +} + +const selectCountryFromList = async (countryId) => { + await vpn.setElementProperty(elements.SERVER_COUNTRY_VIEW, 'contentY', 'i', parseInt(await vpn.getElementProperty(countryId, 'y'))); + await vpn.wait() +} + +const selectCityFromList = async (cityId, countryId) => { + await vpn.setElementProperty(elements.SERVER_COUNTRY_VIEW, 'contentY', 'i', parseInt(await vpn.getElementProperty(cityId, 'y')) + parseInt(await vpn.getElementProperty(countryId, 'y'))); + await vpn.wait() +} + +module.exports = { + actions: { + settings: { + setConnectionChangeNotification, + setServerSwitchNotification, + }, + locations: { + selectCountryFromList, + selectCityFromList + } + } +} \ No newline at end of file diff --git a/tests/functional/elements.js b/tests/functional/elements.js new file mode 100644 index 00000000000..2add06f6cfe --- /dev/null +++ b/tests/functional/elements.js @@ -0,0 +1,28 @@ +/* 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/. */ + +const elements = { + MULTIHOP_SELECTOR_TAB: "multiHopSelector/tabMultiHop", + SINGLEHOP_SELECTOR_TAB: "multiHopSelector/tabSingleHop", + SERVERLIST_BACK_BUTTON: "serverListBackButton", + SERVER_ENTRY_BUTTON: "buttonSelectEntry", + SERVER_EXIT_BUTTON: "buttonSelectExit", + SERVER_LIST_BUTTON: "serverListButton", + SERVER_COUNTRY_VIEW: "serverCountryView", + CONTROLLER_TITLE: "controllerTitle", + CITYLIST_VISIBLE: "cityListVisible", + VPN_MULTHOP_CHEVRON: 'vpnCollapsibleCardChevron', + VPN_COLLAPSIBLE_CARD: 'vpnCollapsibleCard', + + generateCountryId: (serverCode) => { + return 'serverCountryList/serverCountry-' + serverCode; + }, + + generateCityId: (countryId, cityName) => { + return countryId + '/serverCityList/serverCity-' + cityName.replace(/ /g, '_'); + } +} + + +module.exports = elements \ No newline at end of file diff --git a/tests/functional/helper.js b/tests/functional/helper.js index 9dd8e96f1f9..55e368c01e1 100644 --- a/tests/functional/helper.js +++ b/tests/functional/helper.js @@ -160,6 +160,12 @@ module.exports = { `Command failed: ${json.error}`); }, + async waitForElementAndClick(id) { + await this.waitForElementAndProperty(id, 'visible', 'true'); + await this.clickOnElement(id) + await this.wait() + }, + async clickOnNotification() { const json = await this._writeCommand('click_notification'); assert( @@ -223,6 +229,11 @@ module.exports = { } }, + async waitForElementAndProperty(id, property, value) { + await this.waitForElement(id) + await this.waitForElementProperty(id, property, value) + }, + async setGleanAutomationHeader() { const json = await this._writeCommand('set_glean_source_tags automation'); assert( @@ -235,15 +246,15 @@ module.exports = { return await this.getElementProperty('VPNUrlOpener', 'lastUrl'); }, - async waitForCondition(condition) { + async waitForCondition(condition, waitTimeInMilliSecs = 200) { while (true) { if (await condition()) return; - await new Promise(resolve => setTimeout(resolve, 200)); + await new Promise(resolve => setTimeout(resolve, waitTimeInMilliSecs)); } }, - wait() { - return new Promise(resolve => setTimeout(resolve, 1000)); + wait(waitTimeInMilliSecs = 1000) { + return new Promise(resolve => setTimeout(resolve, waitTimeInMilliSecs)); }, // TODO - The expected staging urls are hardcoded, we may want to diff --git a/tests/functional/testAddons.js b/tests/functional/testAddons.js new file mode 100644 index 00000000000..9608a1c4cf5 --- /dev/null +++ b/tests/functional/testAddons.js @@ -0,0 +1,56 @@ +/* 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/. */ + +const assert = require('assert'); +const vpn = require('./helper.js'); + +describe('Addons', function() { + this.ctx.authenticationNeeded = true; + + it('Addons are loaded', async () => { + if (!(await vpn.isFeatureFlippedOff('addonSignature'))) { + await vpn.flipFeatureOff('addonSignature'); + assert(await vpn.isFeatureFlippedOff('addonSignature')); + } + + await vpn.setSetting('addon/customServer', 'false'); + + await vpn.waitForElementProperty( + 'VPNAddonManager', 'loadCompleted', 'true'); + + let count = 0; + await vpn.waitForCondition(async () => { + count = parseInt( + await vpn.getElementProperty('VPNAddonManager', 'count'), 10); + return count > 0; + }); + + await vpn.setSetting('addon/customServer', 'true'); + await vpn.setSetting( + 'addon/customServerAddress', + 'https://bakulf.github.io/vpn-addons-test/empty_manifest/'); + await vpn.waitForCondition(async () => { + return parseInt( + await vpn.getElementProperty('VPNAddonManager', 'count'), + 10) === 0; + }); + + await vpn.setSetting('addon/customServer', 'false'); + await vpn.waitForCondition(async () => { + return parseInt( + await vpn.getElementProperty('VPNAddonManager', 'count'), + 10) === count; + }); + + await vpn.setSetting('addon/customServer', 'true'); + await vpn.setSetting( + 'addon/customServerAddress', + 'https://archive.mozilla.org/pub/vpn/addons/releases/latest/'); + + // Now we need to wait. Unfortunately there is not an easy way to know when + // the loading of the new addons happen. In case we will add a signal, or + // something, we can do better than this. + await vpn.wait(5000); + }); +}); diff --git a/tests/functional/testBenchmark.js b/tests/functional/testBenchmark.js index a26199b11ab..d8171756403 100644 --- a/tests/functional/testBenchmark.js +++ b/tests/functional/testBenchmark.js @@ -31,7 +31,7 @@ describe('Benchmark', function() { let state = await vpn.getElementProperty('VPNConnectionBenchmark', 'state'); let speed = await vpn.getElementProperty('VPNConnectionBenchmark', 'speed'); let bps = parseInt( - await vpn.getElementProperty('VPNConnectionBenchmark', 'bitsPerSec')); + await vpn.getElementProperty('VPNConnectionBenchmark', 'downloadBps')); assert.strictEqual(state, 'StateReady'); assert.strictEqual( @@ -133,7 +133,7 @@ describe('Benchmark', function() { await vpn.wait(); let speed = await vpn.getElementProperty('VPNConnectionBenchmark', 'speed'); let bps = parseInt( - await vpn.getElementProperty('VPNConnectionBenchmark', 'bitsPerSec')); + await vpn.getElementProperty('VPNConnectionBenchmark', 'downloadBps')); assert.strictEqual( speed, (bps >= 25000000) ? 'SpeedFast' : diff --git a/tests/functional/testMultihop.js b/tests/functional/testMultihop.js new file mode 100644 index 00000000000..4ecb3b25404 --- /dev/null +++ b/tests/functional/testMultihop.js @@ -0,0 +1,530 @@ +/* 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/. */ + + const assert = require('assert'); + const { actions } = require('./actions.js'); + const elements = require('./elements.js'); + const vpn = require('./helper.js'); + +describe('Server list', function() { + let servers; + let currentCountryCode; + let currentCity; + + this.timeout(240000); + this.ctx.authenticationNeeded = true; + + beforeEach(async () => { + await vpn.waitForElementAndClick(elements.SERVER_LIST_BUTTON); + + servers = await vpn.servers(); + currentCountryCode = await vpn.getSetting('current-server-country-code'); + currentCity = await vpn.getSetting('current-server-city'); + + for (let server of servers) { + if (currentCountryCode === server.code) { + for (let city of server.cities) { + if (city.name == currentCity) { + currentCity = city.localizedName; + break; + } + } + } + } + console.log( + 'Current city (localized):', currentCity, + '| Current country code:', currentCountryCode); + }); + + it('opening the entry and exit server list', async () => { + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + assert(await vpn.getElementProperty(elements.VPN_COLLAPSIBLE_CARD, 'expanded') === 'false') + + await vpn.waitForElement(elements.SERVER_ENTRY_BUTTON); + await vpn.waitForElementProperty(elements.SERVER_ENTRY_BUTTON, 'visible', 'true'); + + await vpn.waitForElement(elements.SERVER_EXIT_BUTTON); + await vpn.waitForElementProperty(elements.SERVER_EXIT_BUTTON, 'visible', 'true'); + + await vpn.waitForElementAndClick(elements.VPN_MULTHOP_CHEVRON) + assert(await vpn.getElementProperty(elements.VPN_COLLAPSIBLE_CARD, 'expanded')) + }); + + it('check the countries and cities for multihop entries', async () => { + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + for (let server of servers) { + const countryId = elements.generateCountryId(server.code); + await vpn.waitForElement(countryId); + await vpn.waitForElementProperty(countryId, 'visible', 'true'); + + await actions.locations.selectCountryFromList(countryId); + await vpn.wait(); + + if (currentCountryCode === server.code) { + assert( + await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === + 'true'); + } + + if (await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === + 'false') { + await vpn.clickOnElement(countryId); + } + + for (let city of server.cities) { + const cityId = countryId + '/serverCityList/serverCity-' + + city.name.replace(/ /g, '_'); + + await vpn.waitForElement(cityId); + await vpn.waitForElementProperty(cityId, 'visible', 'true'); + } + } + }) + + it('check the countries and cities for multihop exits', async () => { + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + for (let server of servers) { + const countryId = elements.generateCountryId(server.code); + + await vpn.waitForElement(countryId); + await vpn.waitForElementProperty(countryId, 'visible', 'true'); + + await actions.locations.selectCountryFromList(countryId); + await vpn.wait(); + + if (currentCountryCode === server.code) { + assert( + await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === + 'true'); + } + + if (await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === + 'false') { + await vpn.clickOnElement(countryId); + } + + for (let city of server.cities) { + const cityId = countryId + '/serverCityList/serverCity-' + + city.name.replace(/ /g, '_'); + + await vpn.waitForElement(cityId); + await vpn.waitForElementProperty(cityId, 'visible', 'true'); + await vpn.waitForElementProperty( + cityId, 'checked', + currentCountryCode === server.code && + currentCity === city.localizedName ? + 'true' : + 'false'); + } + } + }) + + it('Pick cities for entries', async () => { + let countryId; + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + for (let server of servers) { + countryId = elements.generateCountryId(server.code); + await vpn.waitForElement(countryId); + + await actions.locations.selectCountryFromList(countryId); + await vpn.wait(); + + if (await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === 'false') { + await vpn.clickOnElement(countryId); + } + + await vpn.waitForElementProperty(countryId, elements.CITYLIST_VISIBLE, 'true'); + + for (let city of server.cities) { + const cityId = countryId + '/serverCityList/serverCity-' + city.name.replace(/ /g, '_'); + await vpn.waitForElement(cityId); + + await actions.locations.selectCityFromList(cityId, countryId); + await vpn.waitForElementProperty(cityId, 'visible', 'true'); + const cityName = await vpn.getElementProperty(cityId, 'radioButtonLabelText'.split(" ")); + + await vpn.wait(); + await vpn.clickOnElement(cityId); + await vpn.wait(); + + // Back to the main view. + await vpn.waitForElement(elements.SERVER_ENTRY_BUTTON); + await vpn.waitForElementProperty(elements.SERVER_ENTRY_BUTTON, 'visible', 'true'); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + // One selected + await vpn.waitForElement(cityId); + await vpn.waitForElementProperty(cityId, 'checked', 'true'); + assert(cityName.includes(city.name)) + } + } + }); + + it('Pick cities for exits', async () => { + let countryId; + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + for (let server of servers) { + countryId = elements.generateCountryId(server.code); + await vpn.waitForElement(countryId); + + await actions.locations.selectCountryFromList(countryId) + await vpn.wait(); + + if (await vpn.getElementProperty(countryId, elements.CITYLIST_VISIBLE) === 'false') { + await vpn.clickOnElement(countryId); + } + + await vpn.waitForElementProperty(countryId, elements.CITYLIST_VISIBLE, 'true'); + + for (let city of server.cities) { + const cityId = countryId + '/serverCityList/serverCity-' + city.name.replace(/ /g, '_'); + await vpn.waitForElement(cityId); + + await actions.locations.selectCityFromList(cityId, countryId) + await vpn.waitForElementProperty(cityId, 'visible', 'true'); + const cityName = await vpn.getElementProperty(cityId, 'radioButtonLabelText'.split(" ")); + + await vpn.wait(); + await vpn.clickOnElement(cityId); + await vpn.wait(); + + // Back to the main view. + await vpn.waitForElement(elements.SERVER_EXIT_BUTTON); + await vpn.waitForElementProperty(elements.SERVER_EXIT_BUTTON, 'visible', 'true'); + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // One selected + await vpn.waitForElement(cityId); + await vpn.waitForElementProperty(cityId, 'checked', 'true'); + assert(cityName.includes(city.name)) + } + } + }); + + it('Server switching -- same country different cities', async () => { + await actions.settings.setServerSwitchNotification(true) + await actions.settings.setConnectionChangeNotification(true) + + let newCurrentCountry; + let newCurrentCity; + let currentCountry; + let currentCity; + + // wait for select entry and select entry + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + // exit server details + const firstServer = servers[0] + const cityTwo = firstServer.cities[0] + const cityThree = firstServer.cities[1] + const exitFirstCountryId = elements.generateCountryId(firstServer.code); + + // entry server details + const secondServer = servers[1] + const cityOne = secondServer.cities[0] + const entryCountryId = elements.generateCountryId(secondServer.code); + + // select the first country + await actions.locations.selectCountryFromList(entryCountryId); + await vpn.wait() + if (await vpn.getElementProperty(entryCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(entryCountryId); + } + await vpn.waitForElementProperty(entryCountryId, 'cityListVisible', 'true'); + + // select first city + const cityOneId = elements.generateCityId(entryCountryId, cityOne.name); + + await vpn.setElementProperty( + elements.SERVER_COUNTRY_VIEW, 'contentY', 'i', + parseInt(await vpn.getElementProperty(cityOneId, 'y')) + + parseInt(await vpn.getElementProperty(entryCountryId, 'y'))); + await vpn.wait() + await vpn.waitForElementAndClick(cityOneId) + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // select first country again + await actions.locations.selectCountryFromList(exitFirstCountryId) + await vpn.wait(); + if (await vpn.getElementProperty(exitFirstCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(exitFirstCountryId); + } + await vpn.waitForElementProperty(exitFirstCountryId, 'cityListVisible', 'true'); + + // select first city in exit country + const cityTwoId = elements.generateCityId(exitFirstCountryId, cityTwo.name); + + await vpn.setElementProperty( + elements.SERVER_COUNTRY_VIEW, 'contentY', 'i', + parseInt(await vpn.getElementProperty(cityTwoId, 'y')) + + parseInt(await vpn.getElementProperty(exitFirstCountryId, 'y'))); + await vpn.wait() + await vpn.waitForElementAndClick(cityTwoId); + + // navigate back to connection view + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON); + + // define connected server + currentCountry = firstServer.localizedName; + currentCity = cityTwo.localizedName; + + // connect vpn + await vpn.activate(); + + // wait and assert vpn connection + await vpn.waitForCondition(async () => { + return await vpn.getElementProperty(elements.CONTROLLER_TITLE, 'text') == + 'VPN is on'; + }); + assert.strictEqual(vpn.lastNotification().title, 'VPN Connected'); + assert.strictEqual(vpn.lastNotification().message, `Connected to ${currentCountry}, ${currentCity}`); + + // back to main view + await vpn.waitForElementAndClick(elements.SERVER_LIST_BUTTON); + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // select first country again + await actions.locations.selectCountryFromList(exitFirstCountryId) + await vpn.wait(); + if (await vpn.getElementProperty(exitFirstCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(exitFirstCountryId); + } + await vpn.waitForElementProperty(exitFirstCountryId, 'cityListVisible', 'true'); + + // select first city in exit country + const cityThreeId = elements.generateCityId(exitFirstCountryId, cityThree.name); + + await vpn.setElementProperty( + elements.SERVER_COUNTRY_VIEW, 'contentY', 'i', + parseInt(await vpn.getElementProperty(cityThreeId, 'y')) + + parseInt(await vpn.getElementProperty(exitFirstCountryId, 'y'))); + await vpn.waitForElementAndClick(cityThreeId); + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON); + + // define new connected server + newCurrentCountry = firstServer.localizedName; + newCurrentCity = cityThree.localizedName; + + // wait and assert server switching for multihop + await vpn.waitForCondition(async () => { + return vpn.lastNotification().title == "VPN Switched Servers" + }, 20) + assert.strictEqual( + vpn.lastNotification().message, + `Switched from ${currentCountry}, ${currentCity} to ${ + newCurrentCountry}, ${newCurrentCity}`); + }); + + it.only('Server switching -- different country different cities', async () => { + await actions.settings.setServerSwitchNotification(true) + await actions.settings.setConnectionChangeNotification(true) + + let newCurrentCountry; + let newCurrentCity; + let currentCountry; + let currentCity; + + // wait for select entry and select entry + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + // exit server details + const firstServer = servers[0] + const cityTwo = firstServer.cities[0] + const exitFirstCountryId = elements.generateCountryId(firstServer.code); + + // second exit server details + const thirdServer = servers[2] + const cityThree = thirdServer.cities[0] + const exitThirdCountryId = elements.generateCountryId(thirdServer.code); + + // entry server details + const secondServer = servers[1] + const cityOne = secondServer.cities[0] + const entryCountryId = elements.generateCountryId(secondServer.code); + + // select the first country + await actions.locations.selectCountryFromList(entryCountryId); + await vpn.wait() + if (await vpn.getElementProperty(entryCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(entryCountryId); + } + await vpn.waitForElementProperty(entryCountryId, 'cityListVisible', 'true'); + + // select first city + const cityOneId = elements.generateCityId(entryCountryId, cityOne.name); + await actions.locations.selectCityFromList(cityOneId, entryCountryId) + await vpn.waitForElementAndClick(cityOneId) + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // select first country again + await actions.locations.selectCountryFromList(exitFirstCountryId) + await vpn.wait(); + if (await vpn.getElementProperty(exitFirstCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(exitFirstCountryId); + } + await vpn.waitForElementProperty(exitFirstCountryId, 'cityListVisible', 'true'); + + // select first city in exit country + const cityTwoId = elements.generateCityId(exitFirstCountryId, cityTwo.name); + await actions.locations.selectCityFromList(cityTwoId, exitFirstCountryId) + await vpn.waitForElementAndClick(cityTwoId); + + // navigate back to connection view + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON); + + // define connected server + currentCountry = firstServer.localizedName; + currentCity = cityTwo.localizedName; + + // connect vpn + await vpn.activate(); + + // wait and assert vpn connection + await vpn.waitForCondition(async () => { + return await vpn.getElementProperty(elements.CONTROLLER_TITLE, 'text') == + 'VPN is on'; + }); + assert.strictEqual(vpn.lastNotification().title, 'VPN Connected'); + assert.strictEqual(vpn.lastNotification().message, `Connected to ${currentCountry}, ${currentCity}`); + + // back to main view + await vpn.waitForElementAndClick(elements.SERVER_LIST_BUTTON); + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // select first country again + await actions.locations.selectCountryFromList(exitThirdCountryId) + await vpn.wait(); + if (await vpn.getElementProperty(exitThirdCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(exitThirdCountryId); + } + await vpn.waitForElementProperty(exitThirdCountryId, 'cityListVisible', 'true'); + + // select first city in exit country + const cityThreeId = elements.generateCityId(exitThirdCountryId, cityThree.name); + await actions.locations.selectCityFromList(cityThreeId, exitThirdCountryId) + await vpn.waitForElementAndClick(cityThreeId); + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON); + + // define new connected server + newCurrentCountry = thirdServer.localizedName; + newCurrentCity = cityThree.localizedName; + + // wait and assert server switching for multihop + await vpn.waitForCondition(async () => { + return vpn.lastNotification().title == "VPN Switched Servers" + }, 20) + assert.strictEqual( + vpn.lastNotification().message, + `Switched from ${currentCountry}, ${currentCity} to ${ + newCurrentCountry}, ${newCurrentCity}`); + }); + + it('Single and multihop switching', async () => { + await actions.settings.setServerSwitchNotification(true) + await actions.settings.setConnectionChangeNotification(true) + + let currentCountry; + let currentCity; + + // wait for select entry and select entry + await vpn.waitForElementAndClick(elements.MULTIHOP_SELECTOR_TAB); + await vpn.waitForElementAndClick(elements.SERVER_ENTRY_BUTTON); + + // exit server details + const firstServer = servers[0] + const cityTwo = firstServer.cities[0] + const exitFirstCountryId = elements.generateCountryId(firstServer.code); + + // entry server details + const secondServer = servers[1] + const cityOne = secondServer.cities[0] + const entryCountryId = elements.generateCountryId(secondServer.code); + + // select the first country + await actions.locations.selectCountryFromList(entryCountryId); + await vpn.wait() + if (await vpn.getElementProperty(entryCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(entryCountryId); + } + await vpn.waitForElementProperty(entryCountryId, 'cityListVisible', 'true'); + + // select first city + const cityOneId = elements.generateCityId(entryCountryId, cityOne.name); + await actions.locations.selectCityFromList(cityOneId, entryCountryId) + await vpn.waitForElementAndClick(cityOneId) + + // Back at the main view. select the exit entries + await vpn.waitForElementAndClick(elements.SERVER_EXIT_BUTTON); + + // select first country again + await actions.locations.selectCountryFromList(exitFirstCountryId) + await vpn.wait(); + if (await vpn.getElementProperty(exitFirstCountryId, 'cityListVisible') === 'false') { + await vpn.clickOnElement(exitFirstCountryId); + } + await vpn.waitForElementProperty(exitFirstCountryId, 'cityListVisible', 'true'); + + // select first city in exit country + const cityTwoId = elements.generateCityId(exitFirstCountryId, cityTwo.name); + await actions.locations.selectCityFromList(cityTwoId, exitFirstCountryId) + await vpn.waitForElementAndClick(cityTwoId); + + // navigate back to connection view + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON); + + // define connected server + currentCountry = firstServer.localizedName; + currentCity = cityTwo.localizedName; + + // connect vpn + await vpn.activate(); + + // wait and assert vpn connection + await vpn.waitForCondition(async () => { + return await vpn.getElementProperty(elements.CONTROLLER_TITLE, 'text') == + 'VPN is on'; + }); + assert.strictEqual(vpn.lastNotification().title, 'VPN Connected'); + assert.strictEqual(vpn.lastNotification().message, `Connected to ${currentCountry}, ${currentCity}`); + + // back to main view + await vpn.waitForElementAndClick(elements.SERVER_LIST_BUTTON); + + + // switch from multihop to singlehop + await vpn.waitForElementAndClick(elements.SINGLEHOP_SELECTOR_TAB) + await vpn.waitForElementAndClick(elements.SERVERLIST_BACK_BUTTON) + + // wait and assert vpn connection + await vpn.waitForCondition(async () => { + return await vpn.getElementProperty(elements.CONTROLLER_TITLE, 'text') == + 'VPN is on'; + }); + assert.strictEqual(vpn.lastNotification().title, 'VPN Connected'); + assert.strictEqual(vpn.lastNotification().message, `Connected to ${currentCountry}, ${currentCity}`); + }); +}); \ No newline at end of file diff --git a/tests/nativemessaging/helper.cpp b/tests/nativemessaging/helper.cpp index 425fa0066fb..540bd197246 100644 --- a/tests/nativemessaging/helper.cpp +++ b/tests/nativemessaging/helper.cpp @@ -118,7 +118,7 @@ bool TestHelper::waitForConnection() { timer.start(500); QEventLoop loop; - QObject::connect(&timer, &QTimer::timeout, [&] { loop.exit(); }); + QObject::connect(&timer, &QTimer::timeout, &timer, [&] { loop.exit(); }); loop.exec(); } return connected; diff --git a/tests/nativemessaging/helperserver.cpp b/tests/nativemessaging/helperserver.cpp index b64095c0c67..43e172b8fac 100644 --- a/tests/nativemessaging/helperserver.cpp +++ b/tests/nativemessaging/helperserver.cpp @@ -44,7 +44,7 @@ EchoConnection::EchoConnection(QTcpSocket* socket, int fuzzy) : m_socket(socket), m_fuzzy(fuzzy) { m_timer.setSingleShot(true); - connect(&m_timer, &QTimer::timeout, [this]() { + connect(&m_timer, &QTimer::timeout, &m_timer, [this]() { Q_ASSERT(!m_buffer.isEmpty()); m_socket->write(m_buffer.constData(), 1); m_socket->flush(); @@ -52,7 +52,7 @@ EchoConnection::EchoConnection(QTcpSocket* socket, int fuzzy) maybeStartTimer(); }); - connect(m_socket, &QTcpSocket::readyRead, [this]() { + connect(m_socket, &QTcpSocket::readyRead, m_socket, [this]() { QByteArray buffer = m_socket->readAll(); if (!m_fuzzy) { m_socket->write(buffer); diff --git a/tests/nativemessaging/testbridge.cpp b/tests/nativemessaging/testbridge.cpp index 5e2492c2b4a..291dda20326 100644 --- a/tests/nativemessaging/testbridge.cpp +++ b/tests/nativemessaging/testbridge.cpp @@ -32,7 +32,7 @@ void TestBridge::app_ping_success() { // Let's turn on a "VPN client" (echo-server)... QEventLoop loop; - connect(&hs, &HelperServer::ready, [&] { loop.exit(); }); + connect(&hs, &HelperServer::ready, &hs, [&] { loop.exit(); }); loop.exec(); // let's wait for a client-up message @@ -80,7 +80,7 @@ void TestBridge::async_disconnection() { hs.start(); QEventLoop loop; - connect(&hs, &HelperServer::ready, [&] { loop.exit(); }); + connect(&hs, &HelperServer::ready, &hs, [&] { loop.exit(); }); loop.exec(); QVERIFY(waitForConnection()); @@ -113,7 +113,7 @@ void TestBridge::fuzzy() { // Let's turn on a "VPN client" (echo-server)... QEventLoop loop; - connect(&hs, &HelperServer::ready, [&] { loop.exit(); }); + connect(&hs, &HelperServer::ready, &hs, [&] { loop.exit(); }); loop.exec(); // let's wait for a client-up message diff --git a/tests/qml/CMakeLists.txt b/tests/qml/CMakeLists.txt index 2b962cf90d4..1e7efae9028 100644 --- a/tests/qml/CMakeLists.txt +++ b/tests/qml/CMakeLists.txt @@ -30,6 +30,8 @@ target_link_libraries(qml_tests PRIVATE glean lottie nebula translations) target_sources(qml_tests PRIVATE ${MVPN_SOURCE_DIR}/constants.h ${MVPN_SOURCE_DIR}/controller.h + ${MVPN_SOURCE_DIR}/dnspingsender.cpp + ${MVPN_SOURCE_DIR}/dnspingsender.h ${MVPN_SOURCE_DIR}/env.h ${MVPN_SOURCE_DIR}/externalophandler.cpp ${MVPN_SOURCE_DIR}/externalophandler.h @@ -64,12 +66,10 @@ target_sources(qml_tests PRIVATE ${MVPN_SOURCE_DIR}/pinghelper.h ${MVPN_SOURCE_DIR}/pingsender.cpp ${MVPN_SOURCE_DIR}/pingsender.h - ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.cpp - ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.h - ${MVPN_SOURCE_DIR}/dnspingsender.cpp - ${MVPN_SOURCE_DIR}/dnspingsender.h ${MVPN_SOURCE_DIR}/pingsenderfactory.cpp ${MVPN_SOURCE_DIR}/pingsenderfactory.h + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.cpp + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.h ${MVPN_SOURCE_DIR}/update/updater.cpp ${MVPN_SOURCE_DIR}/update/updater.h ${MVPN_SOURCE_DIR}/update/versionapi.cpp diff --git a/tests/qml/helper.h b/tests/qml/helper.h index 20214a0868e..337b430737b 100644 --- a/tests/qml/helper.h +++ b/tests/qml/helper.h @@ -24,9 +24,9 @@ class TestHelper final : public QObject { Q_INVOKABLE void triggerRecordGleanEvent(const QString& event) const; Q_INVOKABLE void triggerSendGleanPings() const; Q_INVOKABLE void triggerSetGleanSourceTags(const QStringList& tags) const; - Q_PROPERTY(bool mainWindowLoadedCalled READ mainWindowLoadedCalled) - Q_PROPERTY(bool stagingMode READ stagingMode WRITE setStagingMode) - Q_PROPERTY(bool debugMode READ debugMode WRITE setDebugMode) + Q_PROPERTY(bool mainWindowLoadedCalled READ mainWindowLoadedCalled CONSTANT) + Q_PROPERTY(bool stagingMode READ stagingMode WRITE setStagingMode CONSTANT) + Q_PROPERTY(bool debugMode READ debugMode WRITE setDebugMode CONSTANT) bool mainWindowLoadedCalled() const; void setMainWindowLoadedCalled(bool val); diff --git a/tests/qml/mocmozillavpn.cpp b/tests/qml/mocmozillavpn.cpp index fc516c4ca17..697ac5f2a62 100644 --- a/tests/qml/mocmozillavpn.cpp +++ b/tests/qml/mocmozillavpn.cpp @@ -25,6 +25,8 @@ MozillaVPN::MozillaVPN() {} MozillaVPN::~MozillaVPN() {} +ConnectionHealth* MozillaVPN::connectionHealth() { return nullptr; } + Controller* MozillaVPN::controller() { return new Controller(); } MozillaVPN::State MozillaVPN::state() const { return StateInitialize; } @@ -68,10 +70,6 @@ void MozillaVPN::cancelAuthentication() {} void MozillaVPN::logout() {} -void MozillaVPN::setAlert(AlertType) {} - -void MozillaVPN::errorHandle(ErrorHandler::ErrorType) {} - const QList MozillaVPN::exitServers() const { return QList(); } const QList MozillaVPN::entryServers() const { return QList(); } @@ -163,6 +161,8 @@ void MozillaVPN::hardResetAndQuit() {} void MozillaVPN::hardReset() {} +void MozillaVPN::exitForUnrecoverableError(const QString& reason) {} + void MozillaVPN::crashTest() {} QString MozillaVPN::devVersion() { return qVersion(); } diff --git a/tests/qml/qml.pro b/tests/qml/qml.pro index 46f64380c6f..87a5935fc19 100644 --- a/tests/qml/qml.pro +++ b/tests/qml/qml.pro @@ -43,6 +43,7 @@ SOURCES += \ moccontroller.cpp \ mocmozillavpn.cpp \ ../unit/mocinspectorhandler.cpp \ + ../../src/dnspingsender.cpp \ ../../src/externalophandler.cpp \ ../../src/filterproxymodel.cpp \ ../../src/hawkauth.cpp \ @@ -60,11 +61,10 @@ SOURCES += \ ../../src/update/updater.cpp \ ../../src/update/versionapi.cpp \ ../../src/update/webupdater.cpp \ - ../../src/pinghelper.cpp \ + ../../src/pinghelper.cpp \ ../../src/pingsender.cpp \ - ../../src/platforms/dummy/dummypingsender.cpp \ - ../../src/dnspingsender.cpp \ ../../src/pingsenderfactory.cpp \ + ../../src/platforms/dummy/dummypingsender.cpp \ ../../src/qmlengineholder.cpp \ ../../src/urlopener.cpp @@ -87,7 +87,7 @@ HEADERS += \ ../../src/networkmanager.h \ ../../src/networkrequest.h \ ../../src/settingsholder.h \ - ../../src/pinghelper.h \ + ../../src/pinghelper.h \ ../../src/pingsender.h \ ../../src/platforms/dummy/dummypingsender.h \ ../../src/dnspingsender.h \ diff --git a/tests/qml/tst_mainWindowGleanMocks.qml b/tests/qml/tst_mainWindowGleanMocks.qml index 16f14fe7878..0f263713b09 100644 --- a/tests/qml/tst_mainWindowGleanMocks.qml +++ b/tests/qml/tst_mainWindowGleanMocks.qml @@ -19,7 +19,6 @@ Item { property var spyConfig property var spyTags property var spyLogPings - property var spySetDebugViewTag property var spyShutdownCalled: false property var spyUploadEnabled @@ -40,10 +39,6 @@ Item { spyLogPings = flag } Glean.setLogPings = mockGleanSetLogPings; - function mockGleanSetDebugViewTag(tag) { - spySetDebugViewTag = tag - } - Glean.setDebugViewTag = mockGleanSetDebugViewTag; function mockGleanShutdown() { // Should be false before setting it to true in this function. // Helps protect us from bad testing state. @@ -112,22 +107,20 @@ Item { TestHelper.debugMode = false TestHelper.triggerInitializeGlean() compare(spyLogPings, undefined) - compare(spySetDebugViewTag, undefined) TestHelper.debugMode = true TestHelper.triggerInitializeGlean() compare(spyLogPings, true) - compare(spySetDebugViewTag, "MozillaVPN") } /* - * TODO - We should also have a companion unit test for the + * TODO - We should also have a companion unit test for the * mozillavpn method mainWindowLoaded that checks that: * a) mainWindowLoaded calls initializeGlean * b) sets up a timer * * But we don't have a way to test mozillavpn.cpp functions yet. - * I have added to the integration test cases to cover our bases + * I have added to the integration test cases to cover our bases * on these test cases. */ } diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7bb24395ee9..990afc58f52 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -231,6 +231,8 @@ target_sources(unit_tests PRIVATE ${MVPN_SOURCE_DIR}/tasks/servers/taskservers.h ${MVPN_SOURCE_DIR}/taskscheduler.cpp ${MVPN_SOURCE_DIR}/taskscheduler.h + ${MVPN_SOURCE_DIR}/temporarydir.cpp + ${MVPN_SOURCE_DIR}/temporarydir.h ${MVPN_SOURCE_DIR}/theme.cpp ${MVPN_SOURCE_DIR}/theme.h ${MVPN_SOURCE_DIR}/timersingleshot.cpp @@ -316,6 +318,8 @@ target_sources(unit_tests PRIVATE teststatusicon.h testtasks.cpp testtasks.h + testtemporarydir.cpp + testtemporarydir.h testthemes.cpp testthemes.h testtimersingleshot.cpp diff --git a/tests/unit/mocmozillavpn.cpp b/tests/unit/mocmozillavpn.cpp index ff6afb6d81c..d20e660e9c5 100644 --- a/tests/unit/mocmozillavpn.cpp +++ b/tests/unit/mocmozillavpn.cpp @@ -34,6 +34,8 @@ MozillaVPN::UserState MozillaVPN::userState() const { bool MozillaVPN::stagingMode() const { return true; } bool MozillaVPN::debugMode() const { return true; } +ConnectionHealth* MozillaVPN::connectionHealth() { return nullptr; } + Controller* MozillaVPN::controller() { return new Controller(); } void MozillaVPN::initialize() {} @@ -67,10 +69,6 @@ void MozillaVPN::cancelAuthentication() {} void MozillaVPN::logout() {} -void MozillaVPN::setAlert(AlertType) {} - -void MozillaVPN::errorHandle(ErrorHandler::ErrorType) {} - const QList MozillaVPN::exitServers() const { return QList(); } const QList MozillaVPN::entryServers() const { return QList(); } @@ -160,6 +158,8 @@ void MozillaVPN::hardReset() {} void MozillaVPN::crashTest() {} +void MozillaVPN::exitForUnrecoverableError(const QString& reason) {} + QString MozillaVPN::devVersion() { return qVersion(); } QString MozillaVPN::graphicsApi() { return ""; } diff --git a/tests/unit/mocsystemtraynotificationhandler.cpp b/tests/unit/mocsystemtraynotificationhandler.cpp index d0a27ef5d14..02964a81b70 100644 --- a/tests/unit/mocsystemtraynotificationhandler.cpp +++ b/tests/unit/mocsystemtraynotificationhandler.cpp @@ -14,6 +14,8 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) SystemTrayNotificationHandler::~SystemTrayNotificationHandler() {} +void SystemTrayNotificationHandler::initialize() {} + void SystemTrayNotificationHandler::createStatusMenu() {} void SystemTrayNotificationHandler::setStatusMenu() {} diff --git a/tests/unit/testaddon.cpp b/tests/unit/testaddon.cpp index cb141ae9dbb..042de3ba09c 100644 --- a/tests/unit/testaddon.cpp +++ b/tests/unit/testaddon.cpp @@ -26,6 +26,7 @@ #include "helper.h" #include +#include void TestAddon::property() { AddonProperty p; @@ -257,7 +258,7 @@ void TestAddon::conditionWatcher_javascript() { QEventLoop loop; bool currentStatus = false; - connect(a, &AddonConditionWatcher::conditionChanged, [&](bool status) { + connect(a, &AddonConditionWatcher::conditionChanged, a, [&](bool status) { currentStatus = status; loop.exit(); }); @@ -334,19 +335,20 @@ void TestAddon::conditionWatcher_featuresEnabled() { &parent, QStringList{"invalid"})); QVERIFY(!Feature::getOrNull("testFeatureAddon")); - Feature feature("testFeatureAddon", "Feature Addon", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature feature( + "testFeatureAddon", "Feature Addon", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureAddon")); QVERIFY(!Feature::get("testFeatureAddon")->isSupported()); @@ -402,10 +404,11 @@ void TestAddon::conditionWatcher_group() { QEventLoop loop; bool currentStatus = false; - connect(acw1, &AddonConditionWatcher::conditionChanged, [&](bool status) { - currentStatus = status; - loop.exit(); - }); + connect(acw1, &AddonConditionWatcher::conditionChanged, acw1, + [&](bool status) { + currentStatus = status; + loop.exit(); + }); loop.exec(); QVERIFY(currentStatus); @@ -414,10 +417,11 @@ void TestAddon::conditionWatcher_group() { QVERIFY(!acwGroup->conditionApplied()); currentStatus = false; - connect(acw2, &AddonConditionWatcher::conditionChanged, [&](bool status) { - currentStatus = status; - loop.exit(); - }); + connect(acw2, &AddonConditionWatcher::conditionChanged, acw2, + [&](bool status) { + currentStatus = status; + loop.exit(); + }); loop.exec(); QVERIFY(currentStatus); @@ -438,7 +442,7 @@ void TestAddon::conditionWatcher_triggerTime() { QEventLoop loop; bool currentStatus = false; - connect(acw, &AddonConditionWatcher::conditionChanged, [&](bool status) { + connect(acw, &AddonConditionWatcher::conditionChanged, acw, [&](bool status) { currentStatus = status; loop.exit(); }); @@ -461,7 +465,7 @@ void TestAddon::conditionWatcher_startTime() { QEventLoop loop; bool currentStatus = false; - connect(acw, &AddonConditionWatcher::conditionChanged, [&](bool status) { + connect(acw, &AddonConditionWatcher::conditionChanged, acw, [&](bool status) { currentStatus = status; loop.exit(); }); @@ -484,7 +488,7 @@ void TestAddon::conditionWatcher_endTime() { QEventLoop loop; bool currentStatus = false; - connect(acw, &AddonConditionWatcher::conditionChanged, [&](bool status) { + connect(acw, &AddonConditionWatcher::conditionChanged, acw, [&](bool status) { currentStatus = status; loop.exit(); }); @@ -600,7 +604,7 @@ void TestAddon::guide_create() { obj["guide"] = content; QObject parent; - Addon* guide = AddonGuide::create(&parent, "foo", "bar", "name", obj); + Addon* guide = AddonGuide::create(&parent, id, "bar", "name", obj); QCOMPARE(!!guide, created); if (!guide) { @@ -731,7 +735,7 @@ void TestAddon::tutorial_create() { obj["tutorial"] = content; QObject parent; - Addon* tutorial = AddonTutorial::create(&parent, "foo", "bar", "name", obj); + Addon* tutorial = AddonTutorial::create(&parent, id, "bar", "name", obj); QCOMPARE(!!tutorial, created); if (!tutorial) { @@ -1052,9 +1056,18 @@ void TestAddon::message_dismiss() { QJsonObject obj; obj["message"] = messageObj; + obj["type"] = "message"; + obj["api_version"] = "0.1"; + obj["id"] = "bar"; + obj["name"] = "bar"; + + QTemporaryFile file; + QVERIFY(file.open()); + file.write(QJsonDocument(obj).toJson()); + file.close(); QObject parent; - Addon* message = AddonMessage::create(&parent, "foo", "bar", "name", obj); + Addon* message = Addon::create(&parent, file.fileName()); QVERIFY(!!message); QVERIFY(message->enabled()); diff --git a/tests/unit/testaddonapi.cpp b/tests/unit/testaddonapi.cpp index c78da6a6917..221aa25b100 100644 --- a/tests/unit/testaddonapi.cpp +++ b/tests/unit/testaddonapi.cpp @@ -56,19 +56,20 @@ void TestAddonApi::featurelist() { QVERIFY(!!message); QVERIFY(!Feature::getOrNull("testFeatureAddonApi")); - Feature feature("testFeatureAddonApi", "Feature Addon API", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature feature( + "testFeatureAddonApi", "Feature Addon API", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureAddonApi")); QVERIFY(!Feature::get("testFeatureAddonApi")->isSupported()); diff --git a/tests/unit/testadjust.cpp b/tests/unit/testadjust.cpp index 612913b0dd1..43fc577bebe 100644 --- a/tests/unit/testadjust.cpp +++ b/tests/unit/testadjust.cpp @@ -178,38 +178,38 @@ void TestAdjust::stateMachine_data() { } void TestAdjust::stateMachine() { - AdjustProxyPackageHandler* packageHandler = new AdjustProxyPackageHandler(); + AdjustProxyPackageHandler packageHandler; - QCOMPARE(packageHandler->getProcessingState(), + QCOMPARE(packageHandler.getProcessingState(), AdjustProxyPackageHandler::ProcessingState::NotStarted); QFETCH(QByteArray, firstLine); - packageHandler->processData(firstLine); + packageHandler.processData(firstLine); QFETCH(QString, method); QFETCH(QString, path); - QCOMPARE(packageHandler->getMethod(), method); - QCOMPARE(packageHandler->getPath(), path); - QCOMPARE(packageHandler->getProcessingState(), + QCOMPARE(packageHandler.getMethod(), method); + QCOMPARE(packageHandler.getPath(), path); + QCOMPARE(packageHandler.getProcessingState(), AdjustProxyPackageHandler::ProcessingState::FirstLineDone); QFETCH(QByteArray, headers); QFETCH(QByteArray, body); - packageHandler->processData(headers); + packageHandler.processData(headers); QFETCH(PairList, parsedHeaders); - QCOMPARE(packageHandler->getHeaders(), parsedHeaders); + QCOMPARE(packageHandler.getHeaders(), parsedHeaders); if (body.isEmpty()) { - QCOMPARE(packageHandler->getProcessingState(), + QCOMPARE(packageHandler.getProcessingState(), AdjustProxyPackageHandler::ProcessingState::ProcessingDone); return; } - QCOMPARE(packageHandler->getProcessingState(), + QCOMPARE(packageHandler.getProcessingState(), AdjustProxyPackageHandler::ProcessingState::HeadersDone); - packageHandler->processData(body); + packageHandler.processData(body); - QCOMPARE(packageHandler->getProcessingState(), + QCOMPARE(packageHandler.getProcessingState(), AdjustProxyPackageHandler::ProcessingState::ProcessingDone); } diff --git a/tests/unit/testfeature.cpp b/tests/unit/testfeature.cpp index 4f936451971..e8c88caef81 100644 --- a/tests/unit/testfeature.cpp +++ b/tests/unit/testfeature.cpp @@ -23,53 +23,56 @@ void TestFeature::flipOnOff() { // Let's create a few features QVERIFY(!Feature::getOrNull("testFeatureA")); - Feature fA("testFeatureA", "Feature A", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return true; }); + Feature fA( + "testFeatureA", "Feature A", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return true; }); QVERIFY(!!Feature::get("testFeatureA")); QVERIFY(Feature::get("testFeatureA")->isSupported()); QVERIFY(!Feature::getOrNull("testFeatureB")); - Feature fB("testFeatureB", "Feature B", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature fB( + "testFeatureB", "Feature B", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureB")); QVERIFY(!Feature::get("testFeatureB")->isSupported()); QVERIFY(!Feature::getOrNull("testFeatureC")); - Feature fC("testFeatureC", "Feature C", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - false, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature fC( + "testFeatureC", "Feature C", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return false; }, // Can be flipped on + []() -> bool { return false; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureC")); QVERIFY(!Feature::get("testFeatureC")->isSupported()); @@ -118,51 +121,54 @@ void TestFeature::enableByAPI() { // Let's create a few features - Feature fA("testFeatureA", "Feature A", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return true; }); + Feature fA( + "testFeatureA", "Feature A", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return true; }); QVERIFY(!!Feature::get("testFeatureA")); QVERIFY(Feature::get("testFeatureA")->isSupported()); - Feature fB("testFeatureB", "Feature B", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - true, // Can be flipped on - true, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature fB( + "testFeatureB", "Feature B", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return true; }, // Can be flipped on + []() -> bool { return true; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureB")); QVERIFY(!Feature::get("testFeatureB")->isSupported()); - Feature fC("testFeatureC", "Feature C", - false, // Is Major Feature - L18nStrings::Empty, // Display name - L18nStrings::Empty, // Description - L18nStrings::Empty, // LongDescr - "", // ImagePath - "", // IconPath - "", // link URL - "1.0", // released - false, // Can be flipped on - false, // Can be flipped off - QStringList(), // feature dependencies - []() -> bool { return false; }); + Feature fC( + "testFeatureC", "Feature C", + false, // Is Major Feature + L18nStrings::Empty, // Display name + L18nStrings::Empty, // Description + L18nStrings::Empty, // LongDescr + "", // ImagePath + "", // IconPath + "", // link URL + "1.0", // released + []() -> bool { return false; }, // Can be flipped on + []() -> bool { return false; }, // Can be flipped off + QStringList(), // feature dependencies + []() -> bool { return false; }); QVERIFY(!!Feature::get("testFeatureC")); QVERIFY(!Feature::get("testFeatureC")->isSupported()); diff --git a/tests/unit/testipaddresslookup.cpp b/tests/unit/testipaddresslookup.cpp index 5ab80a368e5..630b584a360 100644 --- a/tests/unit/testipaddresslookup.cpp +++ b/tests/unit/testipaddresslookup.cpp @@ -20,7 +20,7 @@ void TestIpAddressLookup::checkIpAddressFailure() { TestHelper::NetworkConfig::Failure, QByteArray())); QEventLoop loop; - connect(&ial, &IpAddressLookup::ipAddressChecked, [&] { loop.exit(); }); + connect(&ial, &IpAddressLookup::ipAddressChecked, &ial, [&] { loop.exit(); }); ial.updateIpAddress(); loop.exec(); @@ -63,7 +63,7 @@ void TestIpAddressLookup::checkIpAddressSucceess() { TestHelper::NetworkConfig(TestHelper::NetworkConfig::Success, json)); QEventLoop loop; - connect(&ial, &IpAddressLookup::ipAddressChecked, [&] { + connect(&ial, &IpAddressLookup::ipAddressChecked, &ial, [&] { QFETCH(QString, ipAddress); QCOMPARE(ial.ipv4Address(), ipAddress); diff --git a/tests/unit/testipfinder.cpp b/tests/unit/testipfinder.cpp index 17aa40174e0..22fbe1a6c73 100644 --- a/tests/unit/testipfinder.cpp +++ b/tests/unit/testipfinder.cpp @@ -55,7 +55,7 @@ void TestIpFinder::ipv4AndIpv6() { TaskIPFinder* ipFinder = new TaskIPFinder(); connect( - ipFinder, &TaskIPFinder::operationCompleted, + ipFinder, &TaskIPFinder::operationCompleted, ipFinder, [&](const QString& ipv4, const QString& ipv6, const QString& country) { if (ipv4Expected) { QVERIFY(ipv4 == "43" || ipv4 == "42"); diff --git a/tests/unit/testlocalizer.cpp b/tests/unit/testlocalizer.cpp index b8bc03082c8..0bcb264b184 100644 --- a/tests/unit/testlocalizer.cpp +++ b/tests/unit/testlocalizer.cpp @@ -28,7 +28,6 @@ void TestLocalizer::systemLanguage() { l.setCode(""); QCOMPARE(l.code(), ""); - QCOMPARE(l.previousCode(), "en"); l.setCode("en"); QCOMPARE(l.code(), "en"); diff --git a/tests/unit/testreleasemonitor.cpp b/tests/unit/testreleasemonitor.cpp index f17afff8194..1f6e086e733 100644 --- a/tests/unit/testreleasemonitor.cpp +++ b/tests/unit/testreleasemonitor.cpp @@ -20,7 +20,7 @@ void TestReleaseMonitor::failure() { rm.runSoon(); QEventLoop loop; - connect(&rm, &ReleaseMonitor::releaseChecked, [&] { loop.exit(); }); + connect(&rm, &ReleaseMonitor::releaseChecked, &rm, [&] { loop.exit(); }); loop.exec(); } @@ -108,7 +108,7 @@ void TestReleaseMonitor::success() { TestHelper::NetworkConfig(TestHelper::NetworkConfig::Success, json)); QEventLoop loop; - connect(&rm, &ReleaseMonitor::releaseChecked, [&] { loop.exit(); }); + connect(&rm, &ReleaseMonitor::releaseChecked, &rm, [&] { loop.exit(); }); loop.exec(); } diff --git a/tests/unit/teststatusicon.cpp b/tests/unit/teststatusicon.cpp index a11e5cc19a0..b4aa239b339 100644 --- a/tests/unit/teststatusicon.cpp +++ b/tests/unit/teststatusicon.cpp @@ -31,7 +31,7 @@ void TestStatusIcon::basic() { int i = 0; QEventLoop loop; - connect(&si, &StatusIcon::iconUpdateNeeded, [&]() { + connect(&si, &StatusIcon::iconUpdateNeeded, &si, [&]() { if (i > 10) { si.disconnect(); loop.exit(); diff --git a/tests/unit/testtasks.cpp b/tests/unit/testtasks.cpp index 3f168edde2a..e07fd0dcd1f 100644 --- a/tests/unit/testtasks.cpp +++ b/tests/unit/testtasks.cpp @@ -17,10 +17,10 @@ void TestTasks::account() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); - TaskAccount* task = new TaskAccount(); + TaskAccount* task = new TaskAccount(ErrorHandler::PropagateError); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -31,10 +31,10 @@ void TestTasks::account() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QByteArray())); - TaskAccount* task = new TaskAccount(); + TaskAccount* task = new TaskAccount(ErrorHandler::DoNotPropagateError); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -47,10 +47,10 @@ void TestTasks::servers() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); - TaskServers* task = new TaskServers(); + TaskServers* task = new TaskServers(ErrorHandler::DoNotPropagateError); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -61,10 +61,10 @@ void TestTasks::servers() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QByteArray())); - TaskServers* task = new TaskServers(); + TaskServers* task = new TaskServers(ErrorHandler::PropagateError); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -78,7 +78,7 @@ void TestTasks::addDevice_success() { TaskAddDevice* task = new TaskAddDevice("foobar", "id"); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -91,7 +91,7 @@ void TestTasks::addDevice_failure() { TaskAddDevice* task = new TaskAddDevice("foobar", "id"); QEventLoop loop; - connect(task, &Task::completed, [&]() { loop.exit(); }); + connect(task, &Task::completed, task, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -140,7 +140,7 @@ void TestTasks::deletePolicy() { Task::NonDeletable); QEventLoop loop; - connect(sentinel, &Task::completed, [&]() { loop.exit(); }); + connect(sentinel, &Task::completed, sentinel, [&]() { loop.exit(); }); TaskScheduler::scheduleTask(task); loop.exec(); @@ -198,4 +198,125 @@ void TestTasks::deletePolicy_group() { } } +void TestTasks::deletePolicy_async() { + class TaskAsync final : public Task { + public: + TaskAsync(Task::DeletePolicy deletePolicy) + : Task("TaskAsync"), m_deletePolicy(deletePolicy) {} + + void run() override { QTimer::singleShot(500, this, &Task::completed); } + + DeletePolicy deletePolicy() const override { return m_deletePolicy; } + + private: + DeletePolicy m_deletePolicy = Deletable; + }; + + Task* t1 = new TaskAsync(Task::Deletable); + TaskScheduler::scheduleTask(t1); + + Task* t2 = new TaskAsync(Task::NonDeletable); + TaskScheduler::scheduleTask(t2); + + QEventLoop loop; + connect(t2, &Task::completed, [&]() { loop.exit(); }); + + TaskScheduler::deleteTasks(); + loop.exec(); +} + +void TestTasks::deleteTasks() { + QStringList sequence; + + class TaskAsync final : public Task { + public: + TaskAsync(Task::DeletePolicy deletePolicy, QStringList* sequence, + const QString& name) + : Task("TaskAsync"), + m_deletePolicy(deletePolicy), + m_sequence(sequence), + m_name(name) {} + + void run() override { + QTimer::singleShot(500, this, [this]() { + m_sequence->append(m_name); + emit completed(); + }); + } + + DeletePolicy deletePolicy() const override { return m_deletePolicy; } + + private: + DeletePolicy m_deletePolicy = Deletable; + QStringList* m_sequence = nullptr; + QString m_name; + }; + + Task* t1 = new TaskAsync(Task::Deletable, &sequence, "t1"); + TaskScheduler::scheduleTask(t1); + + Task* t2 = new TaskAsync(Task::NonDeletable, &sequence, "t2"); + TaskScheduler::scheduleTask(t2); + + TaskScheduler::deleteTasks(); + + Task* t3 = new TaskAsync(Task::NonDeletable, &sequence, "t3"); + + QEventLoop loop; + connect(t3, &Task::completed, [&]() { loop.exit(); }); + + TaskScheduler::scheduleTask(t3); + loop.exec(); + + QCOMPARE(sequence.length(), 2); + QCOMPARE(sequence.at(0), "t2"); + QCOMPARE(sequence.at(1), "t3"); +} + +void TestTasks::forceDeleteTasks() { + QStringList sequence; + + class TaskAsync final : public Task { + public: + TaskAsync(Task::DeletePolicy deletePolicy, QStringList* sequence, + const QString& name) + : Task("TaskAsync"), + m_deletePolicy(deletePolicy), + m_sequence(sequence), + m_name(name) {} + + void run() override { + QTimer::singleShot(500, this, [this]() { + m_sequence->append(m_name); + emit completed(); + }); + } + + DeletePolicy deletePolicy() const override { return m_deletePolicy; } + + private: + DeletePolicy m_deletePolicy = Deletable; + QStringList* m_sequence = nullptr; + QString m_name; + }; + + Task* t1 = new TaskAsync(Task::Deletable, &sequence, "t1"); + TaskScheduler::scheduleTask(t1); + + Task* t2 = new TaskAsync(Task::NonDeletable, &sequence, "t2"); + TaskScheduler::scheduleTask(t2); + + TaskScheduler::forceDeleteTasks(); + + Task* t3 = new TaskAsync(Task::NonDeletable, &sequence, "t3"); + + QEventLoop loop; + connect(t3, &Task::completed, [&]() { loop.exit(); }); + TaskScheduler::scheduleTask(t3); + loop.exec(); + + QCOMPARE(sequence.length(), 1); + QCOMPARE(sequence.at(0), "t3"); +} + static TestTasks s_testTasks; diff --git a/tests/unit/testtasks.h b/tests/unit/testtasks.h index 7f7af9888a7..e45ab4cb982 100644 --- a/tests/unit/testtasks.h +++ b/tests/unit/testtasks.h @@ -18,4 +18,8 @@ class TestTasks final : public TestHelper { void deletePolicy(); void deletePolicy_group(); + void deletePolicy_async(); + + void deleteTasks(); + void forceDeleteTasks(); }; diff --git a/tests/unit/testtemporarydir.cpp b/tests/unit/testtemporarydir.cpp new file mode 100644 index 00000000000..7a6dc232fa6 --- /dev/null +++ b/tests/unit/testtemporarydir.cpp @@ -0,0 +1,48 @@ +/* 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 "testtemporarydir.h" +#include "../../src/temporarydir.h" + +void TestTemporaryDir::basic() { + TemporaryDir td; + QVERIFY(td.isValid()); + QVERIFY(td.errorString().isEmpty()); + QVERIFY(!td.path().isEmpty()); + QVERIFY(!td.filePath("wow").isEmpty()); +} + +void TestTemporaryDir::fallback() { + QString path; + { + TemporaryDir td; + td.fallback(); + + QVERIFY(td.isValid()); + QVERIFY(td.errorString().isEmpty()); + path = td.path(); + QVERIFY(!path.isEmpty()); + QVERIFY(!td.filePath("wow").isEmpty()); + + QVERIFY(QDir(path).exists()); + } + + QVERIFY(!path.isEmpty()); + QVERIFY(!QDir(path).exists()); +} + +void TestTemporaryDir::cleanupAll() { + TemporaryDir td; + td.fallback(); + + QVERIFY(td.isValid()); + QString path = td.path(); + QVERIFY(QDir(path).exists()); + + TemporaryDir::cleanupAll(); + QVERIFY(!QDir(path).exists()); + QVERIFY(!td.isValid()); +} + +static TestTemporaryDir s_testTemporaryDir; diff --git a/nebula/ui/compat/qt5/VPNOpacityMask.qml b/tests/unit/testtemporarydir.h similarity index 56% rename from nebula/ui/compat/qt5/VPNOpacityMask.qml rename to tests/unit/testtemporarydir.h index 21dff5653d3..3fc67975c7d 100644 --- a/nebula/ui/compat/qt5/VPNOpacityMask.qml +++ b/tests/unit/testtemporarydir.h @@ -2,8 +2,13 @@ * 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/. */ -import QtQuick 2.0 -import QtGraphicalEffects 1.0 +#include "helper.h" -OpacityMask { -} +class TestTemporaryDir final : public TestHelper { + Q_OBJECT + + private slots: + void basic(); + void fallback(); + void cleanupAll(); +}; diff --git a/tests/unit/testtimersingleshot.cpp b/tests/unit/testtimersingleshot.cpp index f6e18af1236..2acf12ebd6d 100644 --- a/tests/unit/testtimersingleshot.cpp +++ b/tests/unit/testtimersingleshot.cpp @@ -14,7 +14,7 @@ void TestTimerSingleShot::basic() { QEventLoop loop; QTimer t; - connect(&t, &QTimer::timeout, [&] { loop.exit(); }); + connect(&t, &QTimer::timeout, &t, [&] { loop.exit(); }); t.start(0); loop.exec(); @@ -29,7 +29,7 @@ void TestTimerSingleShot::basic() { QEventLoop loop; QTimer t; - connect(&t, &QTimer::timeout, [&] { loop.exit(); }); + connect(&t, &QTimer::timeout, &t, [&] { loop.exit(); }); t.start(0); loop.exec(); @@ -46,7 +46,7 @@ void TestTimerSingleShot::basic() { QEventLoop loop; QTimer t; - connect(&t, &QTimer::timeout, [&] { loop.exit(); }); + connect(&t, &QTimer::timeout, &t, [&] { loop.exit(); }); t.start(0); loop.exec(); diff --git a/tests/unit/testurlopener.cpp b/tests/unit/testurlopener.cpp index 54c5632339b..6433ceef2a4 100644 --- a/tests/unit/testurlopener.cpp +++ b/tests/unit/testurlopener.cpp @@ -22,12 +22,9 @@ void TestUrlOpener::urlQueryReplacement_data() { "d=__" "VPN_PLATFORM__&e=__VPN_ARCH__&f=__VPN_GRAPHICSAPI__") << QUrl(QString("http://example.com?a=%1&b=%2&c=%3&d=%4&e=%5&f=%6") - .arg(Env::versionString()) - .arg(Env::buildNumber()) - .arg(Env::osVersion()) - .arg(Env::platform()) - .arg(Env::architecture()) - .arg(MozillaVPN::graphicsApi())); + .arg(Env::versionString(), Env::buildNumber(), + Env::osVersion(), Env::platform(), Env::architecture(), + MozillaVPN::graphicsApi())); } void TestUrlOpener::urlQueryReplacement() { diff --git a/tests/unit/unit.pro b/tests/unit/unit.pro index 44578a1dbe0..401bef8e8a8 100644 --- a/tests/unit/unit.pro +++ b/tests/unit/unit.pro @@ -142,6 +142,7 @@ HEADERS += \ ../../src/tasks/release/taskrelease.h \ ../../src/tasks/servers/taskservers.h \ ../../src/taskscheduler.h \ + ../../src/temporarydir.h \ ../../src/theme.h \ ../../src/timersingleshot.h \ ../../src/tutorial/tutorial.h \ @@ -177,6 +178,7 @@ HEADERS += \ testserveri18n.h \ teststatusicon.h \ testtasks.h \ + testtemporarydir.h \ testthemes.h \ testtimersingleshot.h \ testurlopener.h \ @@ -280,6 +282,7 @@ SOURCES += \ ../../src/tasks/ipfinder/taskipfinder.cpp \ ../../src/tasks/servers/taskservers.cpp \ ../../src/taskscheduler.cpp \ + ../../src/temporarydir.cpp \ ../../src/theme.cpp \ ../../src/timersingleshot.cpp \ ../../src/tutorial/tutorial.cpp \ @@ -320,6 +323,7 @@ SOURCES += \ testserveri18n.cpp \ teststatusicon.cpp \ testtasks.cpp \ + testtemporarydir.cpp \ testthemes.cpp \ testtimersingleshot.cpp \ testurlopener.cpp \ diff --git a/tools/wasm_chrome/branch_selector.mjs b/tools/wasm_chrome/branch_selector.mjs index f8f4095732f..3f84cf728ce 100644 --- a/tools/wasm_chrome/branch_selector.mjs +++ b/tools/wasm_chrome/branch_selector.mjs @@ -2,6 +2,7 @@ * 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/. */ +import "./fluent_components.mjs"; import { Octokit } from "https://cdn.skypack.dev/@octokit/core"; const octokit = new Octokit({}); @@ -106,27 +107,33 @@ class BranchSelector extends HTMLElement { this.update(); } this.#dom.innerHTML = ` - - `; + + `; const selector = this.#dom.querySelector("#selector"); selector.addEventListener("change", (e) => { // Forward the event to the parent element; - this.value = selector.value; - this.name = selector.options[selector.selectedIndex].text; + this.name = selector.value; + const option = Array.from(selector.querySelectorAll(`fluent-option`)) + .find( + (e) => e.innerText == this.name, + ); + this.value = option.value; this.dispatchEvent(new CustomEvent("change", e)); }); } update() { const selector = this.#dom.querySelector("#selector"); selector.innerHTML = ` - + Select a VPN-Branch ${ this.#data.map((e) => { - return ``; - }) - }; + return `${e.name}`; + }).join("") + } `; if (!this.#firedOnload) { @@ -136,9 +143,10 @@ class BranchSelector extends HTMLElement { if (!name) { name = "main"; } - const option = Array.from(selector.querySelectorAll(`option`)).find( - (e) => e.text == name, - ); + const option = Array.from(selector.querySelectorAll(`fluent-option`)) + .find( + (e) => e.innerText == name, + ); if (option) { option.selected = true; this.value = option.value; diff --git a/tools/wasm_chrome/fluent_components.mjs b/tools/wasm_chrome/fluent_components.mjs new file mode 100644 index 00000000000..bba083af24f --- /dev/null +++ b/tools/wasm_chrome/fluent_components.mjs @@ -0,0 +1,17 @@ +/* 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/. */ + +import { + fluentCombobox, + fluentOption, + provideFluentDesignSystem, +} from "https://unpkg.com/@fluentui/web-components@2.5.6/dist/web-components.min.js"; + +provideFluentDesignSystem() + .register( + fluentCombobox(), + fluentOption(), + ); + +console.log("Fluent Loaded!"); diff --git a/tools/wasm_chrome/index.html b/tools/wasm_chrome/index.html index 7846de2d793..d23738b2c2d 100644 --- a/tools/wasm_chrome/index.html +++ b/tools/wasm_chrome/index.html @@ -23,17 +23,77 @@ padding: 0; } nav{ - color:white; - background: black; + color: white; + background: #232328; padding: 10px; + display: flex; + justify-content: space-between; + align-items: center; + } + :root { + --max-height: 300px; + font-family: sans-serif; + } + + .menu-button{ + position: relative; + } + .menu{ + display: none; + } + .menu-button:hover .menu{ + display: block; + position: absolute; + right: 0px; + top: 0; + background: #232328; + list-style: none; + padding: 0px; + width: 200px; + display: flex; + flex-direction: column; + justify-content: end; + align-items: end; + + } + .menu li{ + padding: 10px 20px; + } + + .menu a{ + color:white; + text-decoration: none; } + .menu button{ + color: white; + background: transparent; + border: none; + } + @@ -67,5 +127,7 @@ console.log("ready"); }); + + \ No newline at end of file diff --git a/tools/wasm_chrome/taskcluster_frame.mjs b/tools/wasm_chrome/taskcluster_frame.mjs index 290d5a9ae8c..c9609576cc9 100644 --- a/tools/wasm_chrome/taskcluster_frame.mjs +++ b/tools/wasm_chrome/taskcluster_frame.mjs @@ -96,7 +96,7 @@ class TaskclusterFrame extends HTMLElement { // Tries to load branch $name from the taskcluster index. // Returns false if the build is not on it. - async loadFromIndex(name) { + loadFromIndex(name) { if (name === "main") { this.#iframe.src = `https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/mozillavpn.v2.mozilla-vpn-client.branch.main.latest.build.wasm/artifacts/public%2Fbuild%2Findex.html`; diff --git a/translations/servers.json b/translations/servers.json index b02da9ae050..38cdbbe41a7 100644 --- a/translations/servers.json +++ b/translations/servers.json @@ -233,10 +233,10 @@ "en": "São Paulo", "en_CA": "São Paulo", "en_GB": "São Paulo", - "es_AR": "San Pablo", - "es_CL": "San Pablo", - "es_ES": "San Pablo", - "es_MX": "San Pablo", + "es_AR": "São Paulo", + "es_CL": "São Paulo", + "es_ES": "São Paulo", + "es_MX": "São Paulo", "fi": "São Paulo", "fr": "São Paulo", "fy_NL": "São Paulo", @@ -969,7 +969,7 @@ "countryCode": "it", "languages": { "co": "Italia", - "cy": "Yr Eidal", + "cy": "yr Eidal", "de": "Italien", "dsb": "Italska", "el": "Ιταλία", @@ -1396,7 +1396,7 @@ "ru": "Окленд", "sq": "Okland", "uk": "Окленд", - "zh_CN": "奧克蘭都會區", + "zh_CN": "奧克蘭", "zh_TW": "奧克蘭" }, "wikiDataID": "Q37100" diff --git a/translations/strings.yaml b/translations/strings.yaml index 13356212956..70971d42be1 100644 --- a/translations/strings.yaml +++ b/translations/strings.yaml @@ -137,6 +137,9 @@ multiHopFeature: value: To comment: This is alt text for an arrow showing the multi-hop route between servers. e.g. Atlanta TO Copenhagen serverSearchResultsError: No results. Try a different search + accessibleNameRecentConnection: + value: "%1 to %2" + comment: Accessible label that is used for recent multi-hop server connections. “%1” represents the entry server and “%2” the exit server. inAppSupportWorkflow: supportNavLinkText: Contact support @@ -328,6 +331,9 @@ connectionInfo: labelPing: value: Ping comment: Latency of the connection + labelUpload: + value: Upload + comment: Label for the upload benchmark shown in the speed test view. unitPing: value: ms commnent: milliseconds diff --git a/version.pri b/version.pri index 2b9456ed5d9..0542b9b8722 100644 --- a/version.pri +++ b/version.pri @@ -2,7 +2,7 @@ # 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/. -!defined(VERSION, var):VERSION = 2.11.0 +!defined(VERSION, var):VERSION = 2.12.0 VERSION_MAJOR = $$section(VERSION, ., 0, 0) !defined(BUILD_ID, var) { diff --git a/windows/installer/MozillaVPN.wxs b/windows/installer/MozillaVPN.wxs index 3fddd222876..705717edda3 100644 --- a/windows/installer/MozillaVPN.wxs +++ b/windows/installer/MozillaVPN.wxs @@ -54,7 +54,7 @@ AllowDowngrades="no" AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of [ProductName] is already installed." - Schedule="afterInstallExecute" /> + Schedule="afterInstallValidate" /> - + diff --git a/windows/installer/MozillaVPN_cmake.wxs b/windows/installer/MozillaVPN_cmake.wxs index 645c7811f6c..a3218b1dfa6 100644 --- a/windows/installer/MozillaVPN_cmake.wxs +++ b/windows/installer/MozillaVPN_cmake.wxs @@ -25,6 +25,11 @@ Description="Mozilla VPN" ReadOnly="yes" /> + + = 603)]]> + + %PROCESSOR_ARCHITECTURE="AMD64" + + Schedule="afterInstallValidate" /> - + + - NOT Installed OR WIX_UPGRADE_DETECTED + NOT WIX_UPGRADE_DETECTED + WIX_UPGRADE_DETECTED - +