diff --git a/.github/actions/asana-add-comment/templates/hotfix-branch-ready.yml b/.github/actions/asana-add-comment/templates/hotfix-branch-ready.yml new file mode 100644 index 0000000000..635d4b42b5 --- /dev/null +++ b/.github/actions/asana-add-comment/templates/hotfix-branch-ready.yml @@ -0,0 +1,13 @@ +data: + # yq -o=j | sed -E 's/\\n( *)([^\\n])/\2/g' + html_text: | + +

Hotfix branch ${BRANCH} ready βš™οΈ

+ + + πŸ”— Workflow URL: ${WORKFLOW_URL}. + diff --git a/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml b/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml deleted file mode 100644 index f98641fa62..0000000000 --- a/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml +++ /dev/null @@ -1,16 +0,0 @@ -data: - html_text: | - -

[ACTION NEEDED] Internal release build ${TAG} ready

- - - , please proceed with manual tagging and merging according to instructions. - - - πŸ”— Workflow URL: ${WORKFLOW_URL}. - diff --git a/.github/actions/asana-add-comment/templates/validate-check-for-updates-public.yml b/.github/actions/asana-add-comment/templates/validate-check-for-updates-public.yml index d27c1cae3c..285e94c2a1 100644 --- a/.github/actions/asana-add-comment/templates/validate-check-for-updates-public.yml +++ b/.github/actions/asana-add-comment/templates/validate-check-for-updates-public.yml @@ -4,7 +4,7 @@ data:

Build ${TAG} is available publicly through Sparkle πŸš€

πŸ”— Workflow URL: ${WORKFLOW_URL}. diff --git a/.github/actions/asana-create-action-item/action.yml b/.github/actions/asana-create-action-item/action.yml index 99b0cc8f1d..3a3092d1bc 100644 --- a/.github/actions/asana-create-action-item/action.yml +++ b/.github/actions/asana-create-action-item/action.yml @@ -13,8 +13,12 @@ inputs: description: "Task name" required: false type: string - contents: - description: "Task contents" + notes: + description: "Task notes" + required: false + type: string + html-notes: + description: "Task HTML notes" required: false type: string template-name: @@ -55,16 +59,28 @@ runs: payload="$(envsubst < $TEMPLATE_PATH | yq -o=j | sed -E 's/\\n( *)([^\\n])/\2/g' | jq -c)" echo "payload-base64=$(base64 <<< $payload)" >> $GITHUB_OUTPUT - - id: process-contents-payload - if: ${{ inputs.contents }} + - id: process-notes-payload + if: ${{ inputs.notes }} + shell: bash + env: + ASSIGNEE_ID: ${{ steps.get-automation-subtask.outputs.assignee-id }} + NOTES: ${{ inputs.notes }} + TASK_NAME: ${{ inputs.task-name }} + WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + payload="{ \"data\": { \"name\": \"${TASK_NAME}\", \"notes\": \"${NOTES}\n\nπŸ”— Workflow URL: ${WORKFLOW_URL}\", \"assignee\": \"${ASSIGNEE_ID}\" } }" + echo "payload-base64=$(base64 <<< $payload)" >> $GITHUB_OUTPUT + + - id: process-html-notes-payload + if: ${{ inputs.html-notes }} shell: bash env: ASSIGNEE_ID: ${{ steps.get-automation-subtask.outputs.assignee-id }} - CONTENTS: ${{ inputs.contents }} + HTML_NOTES: ${{ inputs.html-notes }} TASK_NAME: ${{ inputs.task-name }} WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | - payload="{ \"data\": { \"name\": \"${TASK_NAME}\", \"notes\": \"${CONTENTS}\n\nWorkflow URL: ${WORKFLOW_URL}\", \"assignee\": \"${ASSIGNEE_ID}\" } }" + payload="{ \"data\": { \"name\": \"${TASK_NAME}\", \"html_notes\": \"${HTML_NOTES}\", \"assignee\": \"${ASSIGNEE_ID}\" } }" echo "payload-base64=$(base64 <<< $payload)" >> $GITHUB_OUTPUT - id: create-task @@ -73,7 +89,7 @@ runs: ASANA_ACCESS_TOKEN: ${{ inputs.access-token }} ASSIGNEE_ID: ${{ steps.get-automation-subtask.outputs.assignee-id }} TASK_ID: ${{ steps.get-automation-subtask.outputs.automation-task-id }} - PAYLOAD_BASE64: ${{ steps.process-template-payload.outputs.payload-base64 || steps.process-comment-payload.outputs.payload-base64 }} + PAYLOAD_BASE64: ${{ steps.process-template-payload.outputs.payload-base64 || steps.process-notes-payload.outputs.payload-base64 || steps.process-html-notes-payload.outputs.payload-base64 }} run: | # Create a subtask and retrieve its ID from the response (.data.gid) to return as an output new_task_id=$(set -o pipefail && curl -fLSs "https://app.asana.com/api/1.0/tasks/${TASK_ID}/subtasks?opt_fields=gid" \ diff --git a/.github/actions/asana-create-action-item/templates/internal-release-tag-failed.yml b/.github/actions/asana-create-action-item/templates/internal-release-tag-failed.yml index 9d617e0eaf..edb1cc2cef 100644 --- a/.github/actions/asana-create-action-item/templates/internal-release-tag-failed.yml +++ b/.github/actions/asana-create-action-item/templates/internal-release-tag-failed.yml @@ -19,7 +19,12 @@ data:
  • git pull origin ${BASE_BRANCH} pull the latest code
  • git merge ${BRANCH}
  • git push origin ${BASE_BRANCH} push merged branch
  • diff --git a/.github/actions/asana-create-action-item/templates/merge-failed.yml b/.github/actions/asana-create-action-item/templates/merge-failed.yml index f4d997e98b..db6e4c8aa7 100644 --- a/.github/actions/asana-create-action-item/templates/merge-failed.yml +++ b/.github/actions/asana-create-action-item/templates/merge-failed.yml @@ -16,7 +16,12 @@ data:
  • git pull origin ${BASE_BRANCH} pull the latest code
  • git merge ${BRANCH}
  • git push origin ${BASE_BRANCH} push merged branch
  • diff --git a/.github/actions/asana-create-action-item/templates/update-asana-for-public-release.yml b/.github/actions/asana-create-action-item/templates/update-asana-for-public-release.yml new file mode 100644 index 0000000000..17a7c696c5 --- /dev/null +++ b/.github/actions/asana-create-action-item/templates/update-asana-for-public-release.yml @@ -0,0 +1,19 @@ +data: + name: Move release task and included items to "Done" section in macOS App Board and close them if possible + assignee: "${ASSIGNEE_ID}" + html_notes: | + + Automation failed to update Asana for the public release. Please follow the steps below. +
      +
    1. Open and select the List view
    2. +
    3. Scroll to the "Validation" section.
    4. +
    5. Select all the tasks in that section.
    6. +
    7. Drag and drop all the selected tasks to the "Done" section
    8. +
    9. Close all tasks that are not incidents and don't belong to project, including the release task itself.
    10. +
    + + Complete this task when ready. + + + πŸ”— Workflow URL: ${WORKFLOW_URL}. + diff --git a/.github/workflows/build_hotfix_release.yml b/.github/workflows/build_hotfix_release.yml new file mode 100644 index 0000000000..0faa9d7517 --- /dev/null +++ b/.github/workflows/build_hotfix_release.yml @@ -0,0 +1,124 @@ +name: Build Hotfix Release + +on: + workflow_dispatch: + inputs: + asana-task-url: + description: "Asana release task URL" + required: true + type: string + base-branch: + description: "Base branch (defaults to main, only override for testing)" + required: false + type: string + current-internal-release-branch: + description: "Current internal release branch (to merge hotfix branch to - hotfix branch is merged to main if this is not provided)" + required: false + type: string + +jobs: + + assert_release_branch: + + name: Assert Hotfix Branch + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + + - name: Assert hotfix release branch + run: | + case "${{ github.ref }}" in + refs/heads/hotfix/*) ;; + *) echo "πŸ‘Ž Not a hotfix release branch"; exit 1 ;; + esac + + run_tests: + + name: Run Tests + + needs: assert_release_branch + uses: ./.github/workflows/pr.yml + secrets: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + + update_asana: + + name: Update Asana tasks + + needs: run_tests + runs-on: macos-13-xlarge + timeout-minutes: 10 + + steps: + + - name: Check out the code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history and tags in order to extract Asana task URLs from git log + ref: ${{ github.ref_name }} + submodules: recursive + + - name: Extract Asana Task ID + id: task-id + uses: ./.github/actions/asana-extract-task-id + with: + task-url: ${{ github.event.inputs.asana-task-url }} + + - name: Update Asana tasks for the release + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + GH_TOKEN: ${{ github.token }} + BRANCH: ${{ github.ref_name }} + run: | + version="$(cut -d '/' -f 2 <<< "$BRANCH")" + # 'internal', because we start with making a build that still needs to be tested before being published + # and we want Asana tasks to be moved to "Validation" and not already to "Done" + ./scripts/update_asana_for_release.sh internal ${{ steps.task-id.outputs.task-id }} ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} "${version}" + + prepare_release: + name: Prepare Release + needs: run_tests + uses: ./.github/workflows/release.yml + with: + asana-task-url: ${{ github.event.inputs.asana-task-url }} + secrets: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} + RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} + NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + MM_HANDLES_BASE64: ${{ secrets.MM_HANDLES_BASE64 }} + MM_WEBHOOK_URL: ${{ secrets.MM_WEBHOOK_URL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_ACCESS_KEY_ID_RELEASE_S3: ${{ secrets.AWS_ACCESS_KEY_ID_RELEASE_S3 }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY_RELEASE_S3: ${{ secrets.AWS_SECRET_ACCESS_KEY_RELEASE_S3 }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + SSH_PRIVATE_KEY_FASTLANE_MATCH: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} + + tag_and_merge: + name: Tag and Merge Branch + needs: [ prepare_release, update_asana ] + uses: ./.github/workflows/tag_release.yml + with: + asana-task-url: ${{ github.event.inputs.asana-task-url }} + branch: ${{ github.ref_name }} + base-branch: ${{ github.event.inputs.current-internal-release-branch || 'main' }} + prerelease: true # Pre-release for now, and the actual release will be done as part of publish_dmg_release that's called later + secrets: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + GHA_ELEVATED_PERMISSIONS_TOKEN: ${{ secrets.GHA_ELEVATED_PERMISSIONS_TOKEN }} diff --git a/.github/workflows/bump_internal_release.yml b/.github/workflows/bump_internal_release.yml index 0b5a0de3b6..2609c7daed 100644 --- a/.github/workflows/bump_internal_release.yml +++ b/.github/workflows/bump_internal_release.yml @@ -85,7 +85,7 @@ jobs: BRANCH: ${{ github.ref_name }} run: | version="$(cut -d '/' -f 2 <<< "$BRANCH")" - ./scripts/update_asana_for_release.sh ${{ steps.task-id.outputs.task-id }} "${version}" ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} + ./scripts/update_asana_for_release.sh internal ${{ steps.task-id.outputs.task-id }} ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} "${version}" prepare_release: name: Prepare Release diff --git a/.github/workflows/code_freeze.yml b/.github/workflows/code_freeze.yml index 4663641ea4..d3eb6f886c 100644 --- a/.github/workflows/code_freeze.yml +++ b/.github/workflows/code_freeze.yml @@ -82,9 +82,10 @@ jobs: GH_TOKEN: ${{ github.token }} run: | ./scripts/update_asana_for_release.sh \ + internal \ ${{ steps.create_release_task.outputs.asana_task_id }} \ - ${{ steps.create_release_task.outputs.marketing_version }} \ - ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} + ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} \ + ${{ steps.create_release_task.outputs.marketing_version }} run_tests: diff --git a/.github/workflows/create_variants.yml b/.github/workflows/create_variants.yml index 2ec2ebbc33..734d71d8ff 100644 --- a/.github/workflows/create_variants.yml +++ b/.github/workflows/create_variants.yml @@ -7,6 +7,50 @@ on: description: "ATB variants (comma-separated)" required: true type: string + workflow_call: + secrets: + BUILD_CERTIFICATE_BASE64: + required: true + P12_PASSWORD: + required: true + KEYCHAIN_PASSWORD: + required: true + REVIEW_PROVISION_PROFILE_BASE64: + required: true + RELEASE_PROVISION_PROFILE_BASE64: + required: true + DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: + required: true + DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: + required: true + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: + required: true + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: + required: true + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: + required: true + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: + required: true + NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: + required: true + NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: + required: true + APPLE_API_KEY_BASE64: + required: true + APPLE_API_KEY_ID: + required: true + APPLE_API_KEY_ISSUER: + required: true + ASANA_ACCESS_TOKEN: + required: true + MM_HANDLES_BASE64: + required: true + MM_WEBHOOK_URL: + required: true + AWS_ACCESS_KEY_ID_RELEASE_S3: + required: true + AWS_SECRET_ACCESS_KEY_RELEASE_S3: + required: true jobs: @@ -24,8 +68,20 @@ jobs: - name: Set up ATB variants id: atb-variants + env: + ASANA_TASK_ID: ${{ vars.DMG_VARIANTS_LIST_TASK_ID }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} run: | - variant_matrix="$(echo "${{ github.event.inputs.atb-variants }}" | sed 's/,/\",\"/g')" + atb_variants="${{ github.event.inputs.atb-variants }}" + if [[ -z "${atb_variants}" ]]; then + atb_variants="$(curl -fSsL "https://app.asana.com/api/1.0/tasks/${ASANA_TASK_ID}?opt_fields=notes" \ + -H "Authorization: Bearer ${ASANA_ACCESS_TOKEN}" \ + | jq -r .data.notes \ + | grep -A1 '^Variants list' \ + | tail -1)" + fi + echo "atb-variants=${atb_variants}" >> $GITHUB_ENV + variant_matrix="$(sed 's/,/\",\"/g' <<< "${atb_variants}")" echo "matrix={\"variant\": [\"${variant_matrix}\"]}" >> $GITHUB_OUTPUT create-atb-variants: @@ -61,7 +117,7 @@ jobs: run: | mkdir -p "${{ env.DEST_DIR }}" curl -fLSs $(gh api https://api.github.com/repos/${{ github.repository }}/contents/${{ env.DEST_DIR }}/action.yml?ref=${{ github.ref }} --jq .download_url) \ - --output ${{ env.DEST_DIR }}/action.yml + --output ${{ env.DEST_DIR }}/action.yml - name: Install Apple Developer ID Application certificate uses: ./.github/actions/install-certs-and-profiles @@ -88,11 +144,11 @@ jobs: sign_identity="$(security find-certificate -a -c "Developer ID Application" -Z | grep ^SHA-1 | cut -d " " -f3 | uniq)" /usr/bin/codesign \ - --force \ - --sign ${sign_identity} \ - --options runtime \ - --entitlements entitlements.plist \ - --generate-entitlement-der "DuckDuckGo.app" + --force \ + --sign ${sign_identity} \ + --options runtime \ + --entitlements entitlements.plist \ + --generate-entitlement-der "DuckDuckGo.app" rm -f entitlements.plist - name: Notarize the app @@ -123,14 +179,24 @@ jobs: GH_TOKEN: ${{ github.token }} run: | curl -fLSs $(gh api https://api.github.com/repos/${{ github.repository }}/contents/scripts/assets/dmg-background.png?ref=${{ github.ref }} --jq .download_url) \ - --output dmg-background.png - create-dmg --volname "DuckDuckGo" \ + --output dmg-background.png + + retries=3 + + while [[ $retries -gt 0 ]]; do + if create-dmg --volname "DuckDuckGo" \ --icon "DuckDuckGo.app" 140 160 \ --background "dmg-background.png" \ --window-size 600 400 \ --icon-size 120 \ --app-drop-link 430 160 "duckduckgo.dmg" \ "dmg" + then + break + fi + retries=$((retries-1)) + done + - name: Upload variant DMG env: diff --git a/.github/workflows/hotfix.yml b/.github/workflows/hotfix.yml new file mode 100644 index 0000000000..cd841b32ed --- /dev/null +++ b/.github/workflows/hotfix.yml @@ -0,0 +1,90 @@ +name: Set Up Hotfix Release Branch + +on: + workflow_dispatch: + +jobs: + + create_release_branch: + + name: Create Release Branch + + runs-on: macos-13-xlarge + timeout-minutes: 10 + + outputs: + release_branch_name: ${{ steps.make_release_branch.outputs.release_branch_name }} + asana_task_url: ${{ steps.create_release_task.outputs.asana_task_url }} + + steps: + + - name: Assert main branch + run: | + if [ "${{ github.ref_name }}" != "main" ]; then + echo "πŸ‘Ž Not the main branch" + exit 1 + fi + + - name: Check out the code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Prepare fastlane + run: bundle install + + - name: Make release branch + id: make_release_branch + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + GH_TOKEN: ${{ github.token }} + run: | + git config --global user.name "Dax the Duck" + git config --global user.email "dax@duckduckgo.com" + last_release="$(gh api repos/${{ github.repository }}/releases/latest | jq -r .tag_name)" + echo "last_release=$last_release" >> $GITHUB_OUTPUT + bundle exec fastlane prepare_hotfix version:"$last_release" + + - name: Create release task + id: create_release_task + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + run: | + version="$(echo ${{ steps.make_release_branch.outputs.release_branch_name }} | cut -d '/' -f 2)" + task_name="macOS App Hotfix Release $version" + asana_task_id="$(curl -fLSs -X POST "https://app.asana.com/api/1.0/task_templates/${{ vars.MACOS_HOTFIX_TASK_TEMPLATE_ID }}/instantiateTask" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d "{ \"data\": { \"name\": \"$task_name\" }}" \ + | jq -r .data.new_task.gid)" + echo "marketing_version=${version}" >> $GITHUB_OUTPUT + echo "asana_task_id=${asana_task_id}" >> $GITHUB_OUTPUT + echo "asana_task_url=https://app.asana.com/0/0/${asana_task_id}/f" >> $GITHUB_OUTPUT + + curl -fLSs -X POST "https://app.asana.com/api/1.0/sections/${{ vars.MACOS_APP_DEVELOPMENT_RELEASE_SECTION_ID }}/addTask" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + --output /dev/null \ + -d "{\"data\": {\"task\": \"${asana_task_id}\"}}" + + assignee_id="$(curl -fLSs https://raw.githubusercontent.com/duckduckgo/BrowserServicesKit/main/.github/actions/asana-failed-pr-checks/user_ids.json \ + | jq -r .${{ github.actor }})" + + curl -fLSs -X PUT "https://app.asana.com/api/1.0/tasks/${asana_task_id}" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + --output /dev/null \ + -d "{ \"data\": { \"assignee\": \"$assignee_id\" }}" + + - name: Report success + uses: ./.github/actions/asana-add-comment + env: + BRANCH: ${{ steps.make_release_branch.outputs.release_branch_name }} + RELEASE_TAG: ${{ steps.make_release_branch.outputs.last_release }} + WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + task-url: ${{ steps.create_release_task.outputs.asana_task_url }} + template-name: hotfix-branch-ready diff --git a/.github/workflows/publish_dmg_release.yml b/.github/workflows/publish_dmg_release.yml index b749d30a68..a0083f76eb 100644 --- a/.github/workflows/publish_dmg_release.yml +++ b/.github/workflows/publish_dmg_release.yml @@ -190,6 +190,22 @@ jobs: echo "FILES_UPLOADED='No files uploaded.'" >> $GITHUB_ENV fi + - name: Update Asana for the release + id: update-asana + if: ${{ github.event.inputs.release-type != 'internal' }} + continue-on-error: true + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + BRANCH: ${{ github.ref_name }} + run: | + version="$(cut -d '/' -f 2 <<< "$BRANCH")" + ./scripts/update_asana_for_release.sh public \ + ${{ steps.task-id.outputs.task-id }} \ + ${{ vars.MACOS_APP_BOARD_DONE_SECTION_ID }} \ + "${version}" \ + announcement-task-contents.txt + echo "announcement-task-contents=$(sed 's/"/\\"/g' < announcement-task-contents.txt)" >> $GITHUB_OUTPUT + - name: Set common environment variables if: always() env: @@ -235,6 +251,29 @@ jobs: release-task-url: ${{ github.event.inputs.asana-task-url }} template-name: ${{ steps.asana-templates.outputs.task-template }} + - name: Create Asana task to handle Asana paperwork + id: create-asana-paperwork-task + if: ${{ steps.update-asana.outcome == 'failure' }} + uses: ./.github/actions/asana-create-action-item + env: + APP_BOARD_ASANA_PROJECT_ID: ${{ vars.MACOS_APP_BOARD_ASANA_PROJECT_ID }} + with: + access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + release-task-url: ${{ github.event.inputs.asana-task-url }} + template-name: update-asana-for-public-release + + - name: Create Asana task to announce the release + id: create-announcement-task + if: ${{ github.event.inputs.release-type != 'internal' }} + uses: ./.github/actions/asana-create-action-item + env: + html-notes: ${{ steps.update-asana.outputs.announcement-task-contents }} + with: + access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + html-notes: ${{ env.html-notes }} + release-task-url: ${{ github.event.inputs.asana-task-url }} + task-name: Announce the release to the company + - name: Upload patch to the Asana task id: upload-patch if: success() @@ -266,6 +305,7 @@ jobs: if: always() uses: ./.github/actions/asana-log-message env: + ANNOUNCEMENT_TASK_ID: ${{ steps.create-announcement-task.outputs.new-task-id }} ASSIGNEE_ID: ${{ steps.create-task.outputs.assignee-id }} TASK_ID: ${{ steps.create-task.outputs.new-task-id }} with: @@ -280,3 +320,36 @@ jobs: access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} task-url: ${{ github.event.inputs.asana-task-url }} template-name: ${{ steps.asana-templates.outputs.release-task-comment-template }} + + # This is only run for public and hotfix releases + create-variants: + + name: Create DMG Variants + + needs: [publish-to-sparkle] + + if: ${{ github.event.inputs.release-type != 'internal' }} + + uses: ./.github/workflows/create_variants.yml + secrets: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} + RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} + DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} + NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + MM_HANDLES_BASE64: ${{ secrets.MM_HANDLES_BASE64 }} + MM_WEBHOOK_URL: ${{ secrets.MM_WEBHOOK_URL }} + AWS_ACCESS_KEY_ID_RELEASE_S3: ${{ secrets.AWS_ACCESS_KEY_ID_RELEASE_S3 }} + AWS_SECRET_ACCESS_KEY_RELEASE_S3: ${{ secrets.AWS_SECRET_ACCESS_KEY_RELEASE_S3 }} diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index 5a7a1392d2..aeba4860bd 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -67,7 +67,8 @@ jobs: run: | case "${{ env.BRANCH }}" in release/*) ;; - *) echo "πŸ‘Ž Not a release branch"; exit 1 ;; + hotfix/*) ;; + *) echo "πŸ‘Ž Not a release or hotfix branch"; exit 1 ;; esac - name: Check out the code @@ -86,6 +87,7 @@ jobs: - name: Merge to base branch id: merge if: ${{ env.prerelease == 'true' }} + continue-on-error: true uses: actions/github-script@v7 with: github-token: ${{ secrets.GHA_ELEVATED_PERMISSIONS_TOKEN }} @@ -100,6 +102,7 @@ jobs: - name: Delete release branch id: delete if: ${{ env.prerelease == 'false' }} + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: | @@ -111,7 +114,9 @@ jobs: GH_TOKEN: ${{ github.token }} PROMOTED_TAG: ${{ steps.create-tag.outputs.promoted-tag }} TAG: ${{ steps.create-tag.outputs.tag }} + MERGE_OR_DELETE_FAILED: ${{ (steps.merge.outcome == 'failure') || (steps.delete.outcome == 'failure') }} run: | + echo "MERGE_OR_DELETE_FAILED=${MERGE_OR_DELETE_FAILED}" >> $GITHUB_ENV echo "TAG=$TAG" >> $GITHUB_ENV if [[ "${prerelease}" == "true" ]]; then DMG_VERSION=${TAG//-/.} @@ -127,18 +132,8 @@ jobs: echo "LAST_RELEASE_TAG=${last_release_tag}" >> $GITHUB_ENV fi - - name: Set up Asana success comment template - if: success() - id: asana-success-template - run: | - if [[ "${prerelease}" == "true" ]]; then - echo "comment-template=internal-release-ready" >> $GITHUB_OUTPUT - else - echo "comment-template=public-release-tagged" >> $GITHUB_OUTPUT - fi - - name: Set up Asana templates - if: failure() + if: failure() || env.MERGE_OR_DELETE_FAILED == 'true' id: asana-failure-templates run: | if [[ ${{ steps.create-tag.outputs.tag-created }} == "true" ]]; then @@ -161,7 +156,7 @@ jobs: - name: Create Asana task on failure id: create-task-on-failure - if: failure() + if: failure() || env.MERGE_OR_DELETE_FAILED == 'true' uses: ./.github/actions/asana-create-action-item with: access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} @@ -169,7 +164,7 @@ jobs: template-name: ${{ steps.asana-failure-templates.outputs.task-template }} - name: Report failure - if: failure() + if: failure() || env.MERGE_OR_DELETE_FAILED == 'true' uses: ./.github/actions/asana-log-message env: ASSIGNEE_ID: ${{ steps.create-task-on-failure.outputs.assignee-id }} @@ -179,8 +174,18 @@ jobs: task-url: ${{ env.asana-task-url }} template-name: ${{ steps.asana-failure-templates.outputs.comment-template }} + - name: Set up Asana success comment template + if: ${{ env.MERGE_OR_DELETE_FAILED == 'false' }} + id: asana-success-template + run: | + if [[ "${prerelease}" == "true" ]]; then + echo "comment-template=internal-release-ready" >> $GITHUB_OUTPUT + else + echo "comment-template=public-release-tagged" >> $GITHUB_OUTPUT + fi + - name: Report success - if: success() + if: ${{ env.MERGE_OR_DELETE_FAILED == 'false' }} uses: ./.github/actions/asana-log-message with: access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} diff --git a/.gitignore b/.gitignore index a8169587c9..ccd7c7be5f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ playground.xcworkspace # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm +LocalPackages/*/Package.resolved .build/ diff --git a/.xcode-version b/.xcode-version index adbc6d2b1b..dafb659a69 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.1 +15.2 diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 3f4f3b4e21..dcb623008a 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 129 +CURRENT_PROJECT_VERSION = 130 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bffedab9f9..eec7530e9d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ 14505A08256084EF00272CC6 /* UserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14505A07256084EF00272CC6 /* UserAgent.swift */; }; 1456D6E124EFCBC300775049 /* TabBarCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1456D6E024EFCBC300775049 /* TabBarCollectionView.swift */; }; 14D9B8FB24F7E089000D4D13 /* AddressBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D9B8F924F7E089000D4D13 /* AddressBarViewController.swift */; }; - 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; }; + 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */; }; 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; }; 1D1A33492A6FEB170080ACED /* BurnerMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1A33482A6FEB170080ACED /* BurnerMode.swift */; }; @@ -380,7 +380,6 @@ 3706FB68293F65D500E42796 /* NSNotificationName+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B980E202817604000282EE1 /* NSNotificationName+Debug.swift */; }; 3706FB69293F65D500E42796 /* NavigationBarBadgeAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F7F2A5288AD2CA001C0D64 /* NavigationBarBadgeAnimationView.swift */; }; 3706FB6A293F65D500E42796 /* AddressBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */; }; - 3706FB6B293F65D500E42796 /* HistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527D263B05C600B973F8 /* HistoryEntry.swift */; }; 3706FB6C293F65D500E42796 /* FaviconStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA69C275F945C00DCE9C9 /* FaviconStore.swift */; }; 3706FB6D293F65D500E42796 /* SuggestionListCharacteristics.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB8203B26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift */; }; 3706FB6F293F65D500E42796 /* BookmarkListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */; }; @@ -456,7 +455,7 @@ 3706FBC2293F65D500E42796 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4BBA3A25C58FA200C4FB0F /* MainMenu.swift */; }; 3706FBC5293F65D500E42796 /* CallToAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F21276A32B600DC0649 /* CallToAction.swift */; }; 3706FBC6293F65D500E42796 /* MouseOverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953D26F04BE70015B914 /* MouseOverView.swift */; }; - 3706FBC7293F65D500E42796 /* HistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* HistoryStore.swift */; }; + 3706FBC7293F65D500E42796 /* EncryptedHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* EncryptedHistoryStore.swift */; }; 3706FBC8293F65D500E42796 /* FirePopoverCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE246F12709EF3B00BEEAEE /* FirePopoverCollectionViewItem.swift */; }; 3706FBC9293F65D500E42796 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA61C0D12727F59B00E6B681 /* ArrayExtension.swift */; }; 3706FBCA293F65D500E42796 /* CrashReportSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2B268F1ECD00D2D9CD /* CrashReportSender.swift */; }; @@ -496,7 +495,6 @@ 3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7C26A05F250062F04E /* PasswordManagementItemModel.swift */; }; 3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 3706FBF4293F65D500E42796 /* Visit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E91992875B39300AB6B62 /* Visit.swift */; }; 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; @@ -504,7 +502,6 @@ 3706FBF9293F65D500E42796 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; 3706FBFA293F65D500E42796 /* FileDownloadError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23826E742610031CB7F /* FileDownloadError.swift */; }; 3706FBFB293F65D500E42796 /* MoreOrLessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E9F27BFE60E0038AD11 /* MoreOrLessView.swift */; }; - 3706FBFD293F65D500E42796 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46F26146A250067D1B9 /* DateExtension.swift */; }; 3706FBFE293F65D500E42796 /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AAE75278263B046100B973F8 /* History.xcdatamodeld */; }; 3706FBFF293F65D500E42796 /* PermissionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C853C26944B940048FEBE /* PermissionStore.swift */; }; 3706FC00293F65D500E42796 /* PrivacyIconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA75A0AD26F3500C0086B667 /* PrivacyIconViewModel.swift */; }; @@ -562,7 +559,6 @@ 3706FC38293F65D500E42796 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; 3706FC3A293F65D500E42796 /* NSOpenPanelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511DF262CAA8600F6079C /* NSOpenPanelExtensions.swift */; }; 3706FC3B293F65D500E42796 /* FirePopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE99B8827088A19008B6BD9 /* FirePopover.swift */; }; - 3706FC3C293F65D500E42796 /* HistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527F263B0A4D00B973F8 /* HistoryCoordinator.swift */; }; 3706FC3E293F65D500E42796 /* VariantManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50372726A12000758A2B /* VariantManager.swift */; }; 3706FC3F293F65D500E42796 /* ApplicationDockMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA97BF4525135DD30014931A /* ApplicationDockMenu.swift */; }; 3706FC40293F65D500E42796 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; @@ -608,7 +604,6 @@ 3706FC6E293F65D500E42796 /* DuckURLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C5228C8EECA00119F70 /* DuckURLSchemeHandler.swift */; }; 3706FC6F293F65D500E42796 /* FirePopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA13DCB3271480B0006D48D3 /* FirePopoverViewModel.swift */; }; 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; - 3706FC72293F65D500E42796 /* Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919B2875C65000AB6B62 /* Stored.swift */; }; 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853014D525E671A000FB8205 /* PageObserverUserScript.swift */; }; @@ -718,7 +713,6 @@ 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D2377B287EBDA300BCE03B /* TabIndexTests.swift */; }; 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534CA42811987D002621E7 /* AdjacentItemEnumeratorTests.swift */; }; 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44222616CABC00DD1EC2 /* PixelArgumentsTests.swift */; }; - 3706FDE3293F661700E42796 /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AAE75278263B046100B973F8 /* History.xcdatamodeld */; }; 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534C9D28104D9B002621E7 /* TabLazyLoaderTests.swift */; }; 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */; }; 3706FDE6293F661700E42796 /* BookmarkOutlineViewDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292B32667103000AD2C21 /* BookmarkOutlineViewDataSourceTests.swift */; }; @@ -1469,7 +1463,7 @@ 4B957A012AC7AE700062CA31 /* SafariDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CFD26FE191E001E4761 /* SafariDataImporter.swift */; }; 4B957A022AC7AE700062CA31 /* WaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */; }; 4B957A032AC7AE700062CA31 /* LocalBookmarkStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987799F829999973005D8EB6 /* LocalBookmarkStore.swift */; }; - 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; }; + 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 4B957A052AC7AE700062CA31 /* StatisticsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50342726A11F00758A2B /* StatisticsLoader.swift */; }; 4B957A072AC7AE700062CA31 /* PrivacyPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */; }; 4B957A082AC7AE700062CA31 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; }; @@ -1559,7 +1553,6 @@ 4B957A602AC7AE700062CA31 /* NSNotificationName+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B980E202817604000282EE1 /* NSNotificationName+Debug.swift */; }; 4B957A612AC7AE700062CA31 /* NavigationBarBadgeAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F7F2A5288AD2CA001C0D64 /* NavigationBarBadgeAnimationView.swift */; }; 4B957A622AC7AE700062CA31 /* AddressBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */; }; - 4B957A632AC7AE700062CA31 /* HistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527D263B05C600B973F8 /* HistoryEntry.swift */; }; 4B957A642AC7AE700062CA31 /* FaviconStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA69C275F945C00DCE9C9 /* FaviconStore.swift */; }; 4B957A652AC7AE700062CA31 /* WaitlistTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */; }; 4B957A662AC7AE700062CA31 /* SuggestionListCharacteristics.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB8203B26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift */; }; @@ -1664,7 +1657,7 @@ 4B957ACE2AC7AE700062CA31 /* BrowserTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA585D83248FD31100E9A3E2 /* BrowserTabViewController.swift */; }; 4B957ACF2AC7AE700062CA31 /* CallToAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F21276A32B600DC0649 /* CallToAction.swift */; }; 4B957AD02AC7AE700062CA31 /* MouseOverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953D26F04BE70015B914 /* MouseOverView.swift */; }; - 4B957AD12AC7AE700062CA31 /* HistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* HistoryStore.swift */; }; + 4B957AD12AC7AE700062CA31 /* EncryptedHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* EncryptedHistoryStore.swift */; }; 4B957AD22AC7AE700062CA31 /* FirePopoverCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE246F12709EF3B00BEEAEE /* FirePopoverCollectionViewItem.swift */; }; 4B957AD32AC7AE700062CA31 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA61C0D12727F59B00E6B681 /* ArrayExtension.swift */; }; 4B957AD42AC7AE700062CA31 /* NetworkProtectionInviteCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60702A0B29FA00BCD287 /* NetworkProtectionInviteCodeViewModel.swift */; }; @@ -1717,7 +1710,6 @@ 4B957B042AC7AE700062CA31 /* UpdateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD86E51267A0DFF005C11BE /* UpdateController.swift */; }; 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A0118125AF60E700FA6A0C /* FindInPageModel.swift */; }; 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929826670D2A00AD2C21 /* PseudoFolder.swift */; }; - 4B957B072AC7AE700062CA31 /* Visit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E91992875B39300AB6B62 /* Visit.swift */; }; 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */; }; 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; @@ -1727,7 +1719,6 @@ 4B957B0E2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; 4B957B0F2AC7AE700062CA31 /* FileDownloadError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23826E742610031CB7F /* FileDownloadError.swift */; }; 4B957B102AC7AE700062CA31 /* MoreOrLessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E9F27BFE60E0038AD11 /* MoreOrLessView.swift */; }; - 4B957B112AC7AE700062CA31 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46F26146A250067D1B9 /* DateExtension.swift */; }; 4B957B122AC7AE700062CA31 /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AAE75278263B046100B973F8 /* History.xcdatamodeld */; }; 4B957B132AC7AE700062CA31 /* PermissionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C853C26944B940048FEBE /* PermissionStore.swift */; }; 4B957B142AC7AE700062CA31 /* PrivacyIconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA75A0AD26F3500C0086B667 /* PrivacyIconViewModel.swift */; }; @@ -1794,7 +1785,6 @@ 4B957B512AC7AE700062CA31 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; 4B957B522AC7AE700062CA31 /* NSOpenPanelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511DF262CAA8600F6079C /* NSOpenPanelExtensions.swift */; }; 4B957B532AC7AE700062CA31 /* FirePopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE99B8827088A19008B6BD9 /* FirePopover.swift */; }; - 4B957B542AC7AE700062CA31 /* HistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527F263B0A4D00B973F8 /* HistoryCoordinator.swift */; }; 4B957B552AC7AE700062CA31 /* NetworkProtectionOnboardingMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B05829D2A812AC000AC3F7C /* NetworkProtectionOnboardingMenu.swift */; }; 4B957B562AC7AE700062CA31 /* VariantManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50372726A12000758A2B /* VariantManager.swift */; }; 4B957B572AC7AE700062CA31 /* ApplicationDockMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA97BF4525135DD30014931A /* ApplicationDockMenu.swift */; }; @@ -1856,7 +1846,6 @@ 4B957B932AC7AE700062CA31 /* FirePopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA13DCB3271480B0006D48D3 /* FirePopoverViewModel.swift */; }; 4B957B942AC7AE700062CA31 /* BWCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D43EB37292B636E0065E5D6 /* BWCommand.swift */; }; 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; }; - 4B957B962AC7AE700062CA31 /* Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919B2875C65000AB6B62 /* Stored.swift */; }; 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F525D6BF2C007F5990 /* AddressBarButtonsViewController.swift */; }; 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF076028F815AD00EDFBE3 /* BWError.swift */; }; 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; @@ -2359,10 +2348,16 @@ 85C6A29625CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C6A29525CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift */; }; 85CC1D7B26A05ECF0062F04E /* PasswordManagementItemListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7A26A05ECF0062F04E /* PasswordManagementItemListModel.swift */; }; 85CC1D7D26A05F250062F04E /* PasswordManagementItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CC1D7C26A05F250062F04E /* PasswordManagementItemModel.swift */; }; + 85D0327B2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */; }; + 85D0327C2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */; }; + 85D0327D2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */; }; 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 85D438B6256E7C9E00F3BAF8 /* ContextMenuUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D438B5256E7C9E00F3BAF8 /* ContextMenuUserScript.swift */; }; 85D885B026A590A90077C374 /* NSNotificationName+PasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D885AF26A590A90077C374 /* NSNotificationName+PasswordManager.swift */; }; 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D885B226A5A9DE0077C374 /* NSAlert+PasswordManager.swift */; }; + 85E2BBCE2B8F534000DBEC7A /* History in Frameworks */ = {isa = PBXBuildFile; productRef = 85E2BBCD2B8F534000DBEC7A /* History */; }; + 85E2BBD02B8F534A00DBEC7A /* History in Frameworks */ = {isa = PBXBuildFile; productRef = 85E2BBCF2B8F534A00DBEC7A /* History */; }; + 85E2BBD22B8F536F00DBEC7A /* History in Frameworks */ = {isa = PBXBuildFile; productRef = 85E2BBD12B8F536F00DBEC7A /* History */; }; 85F0FF1327CFAB04001C7C6E /* RecentlyVisitedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F0FF1227CFAB04001C7C6E /* RecentlyVisitedView.swift */; }; 85F1B0C925EF9759004792B6 /* URLEventHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1B0C825EF9759004792B6 /* URLEventHandlerTests.swift */; }; 85F487B5276A8F2E003CE668 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F487B4276A8F2E003CE668 /* OnboardingTests.swift */; }; @@ -2493,8 +2488,6 @@ AA75A0AE26F3500C0086B667 /* PrivacyIconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA75A0AD26F3500C0086B667 /* PrivacyIconViewModel.swift */; }; AA7E9176286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E9175286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift */; }; AA7E919728746BCC00AB6B62 /* HistoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919628746BCC00AB6B62 /* HistoryMenu.swift */; }; - AA7E919A2875B39300AB6B62 /* Visit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E91992875B39300AB6B62 /* Visit.swift */; }; - AA7E919C2875C65000AB6B62 /* Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919B2875C65000AB6B62 /* Stored.swift */; }; AA7E919F287872EA00AB6B62 /* VisitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7E919E287872EA00AB6B62 /* VisitViewModel.swift */; }; AA7EB6DF27E7C57D00036718 /* MouseOverAnimationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */; }; AA7EB6E227E7D05500036718 /* flame-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6E027E7D05500036718 /* flame-mouse-over.json */; }; @@ -2572,16 +2565,13 @@ AAE246F8270A406200BEEAEE /* FirePopoverCollectionViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE246F7270A406200BEEAEE /* FirePopoverCollectionViewHeader.swift */; }; AAE39D1B24F44885008EF28B /* TabCollectionViewModelDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE39D1A24F44885008EF28B /* TabCollectionViewModelDelegateMock.swift */; }; AAE7527A263B046100B973F8 /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AAE75278263B046100B973F8 /* History.xcdatamodeld */; }; - AAE7527C263B056C00B973F8 /* HistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* HistoryStore.swift */; }; - AAE7527E263B05C600B973F8 /* HistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527D263B05C600B973F8 /* HistoryEntry.swift */; }; - AAE75280263B0A4D00B973F8 /* HistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527F263B0A4D00B973F8 /* HistoryCoordinator.swift */; }; + AAE7527C263B056C00B973F8 /* EncryptedHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527B263B056C00B973F8 /* EncryptedHistoryStore.swift */; }; AAE8B110258A456C00E81239 /* TabPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE8B10F258A456C00E81239 /* TabPreviewViewController.swift */; }; AAE99B8927088A19008B6BD9 /* FirePopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE99B8827088A19008B6BD9 /* FirePopover.swift */; }; AAEC74B22642C57200C2EFBC /* HistoryCoordinatingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEC74B12642C57200C2EFBC /* HistoryCoordinatingMock.swift */; }; AAEC74B42642C69300C2EFBC /* HistoryCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEC74B32642C69300C2EFBC /* HistoryCoordinatorTests.swift */; }; AAEC74B62642CC6A00C2EFBC /* HistoryStoringMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEC74B52642CC6A00C2EFBC /* HistoryStoringMock.swift */; }; AAEC74B82642E43800C2EFBC /* HistoryStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEC74B72642E43800C2EFBC /* HistoryStoreTests.swift */; }; - AAEC74BC2642F0F800C2EFBC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AAE75278263B046100B973F8 /* History.xcdatamodeld */; }; AAECA42024EEA4AC00EFA63A /* IndexPathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAECA41F24EEA4AC00EFA63A /* IndexPathExtension.swift */; }; AAEEC6A927088ADB008445F7 /* FireCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEEC6A827088ADB008445F7 /* FireCoordinator.swift */; }; AAEF6BC8276A081C0024DCF4 /* FaviconSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEF6BC7276A081C0024DCF4 /* FaviconSelector.swift */; }; @@ -2728,6 +2718,8 @@ B64C85422694590B0048FEBE /* PermissionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C85412694590B0048FEBE /* PermissionButton.swift */; }; B64CE01E2B8622D700126CA5 /* AddressBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64CE01D2B8622D700126CA5 /* AddressBarTests.swift */; }; B64CE01F2B8622D700126CA5 /* AddressBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64CE01D2B8622D700126CA5 /* AddressBarTests.swift */; }; + B64E42AB2B909DC9006C1346 /* test.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B64E42AA2B909DC9006C1346 /* test.pdf */; }; + B64E42AC2B909DC9006C1346 /* test.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B64E42AA2B909DC9006C1346 /* test.pdf */; }; B65211252B29A42C00B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; B65211262B29A42E00B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; B65211272B29A43000B30633 /* BookmarkStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA652CDA25DDAB32009059CC /* BookmarkStoreMock.swift */; }; @@ -2744,6 +2736,9 @@ B658BAB62B0F845D00D1F2C7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B658BAB52B0F845D00D1F2C7 /* Localizable.xcstrings */; }; B658BAB72B0F848D00D1F2C7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B658BAB52B0F845D00D1F2C7 /* Localizable.xcstrings */; }; B658BAB92B0F849100D1F2C7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B658BAB52B0F845D00D1F2C7 /* Localizable.xcstrings */; }; + B65C7DFB2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65C7DFA2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift */; }; + B65C7DFC2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65C7DFA2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift */; }; + B65C7DFD2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65C7DFA2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift */; }; B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CA2B316DF100A595BB /* SnapshotTesting */; }; B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CC2B316DFC00A595BB /* SnapshotTesting */; }; B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CE2B316E0200A595BB /* SnapshotTesting */; }; @@ -2902,7 +2897,6 @@ B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */; }; B6A9E45326142B070067D1B9 /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E45226142B070067D1B9 /* Pixel.swift */; }; B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */; }; - B6A9E47026146A250067D1B9 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E46F26146A250067D1B9 /* DateExtension.swift */; }; B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; @@ -3030,6 +3024,8 @@ B6EC37FC29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; B6EC37FD29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = B6EC37FE29B8D915001ACE79 /* Configuration */; }; + B6EEDD7D2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; }; + B6EEDD7E2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; }; B6F1C80B2761C45400334924 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; }; B6F41031264D2B23003DA42C /* ProgressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F41030264D2B23003DA42C /* ProgressExtension.swift */; }; B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F56566299A414300A04298 /* WKWebViewMockingExtension.swift */; }; @@ -3922,6 +3918,7 @@ 85C6A29525CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsWrapper.swift; sourceTree = ""; }; 85CC1D7A26A05ECF0062F04E /* PasswordManagementItemListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagementItemListModel.swift; sourceTree = ""; }; 85CC1D7C26A05F250062F04E /* PasswordManagementItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagementItemModel.swift; sourceTree = ""; }; + 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryCoordinatorExtension.swift; sourceTree = ""; }; 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; 85D438B5256E7C9E00F3BAF8 /* ContextMenuUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuUserScript.swift; sourceTree = ""; }; 85D885AF26A590A90077C374 /* NSNotificationName+PasswordManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotificationName+PasswordManager.swift"; sourceTree = ""; }; @@ -4044,8 +4041,6 @@ AA7E9175286DB05D00AB6B62 /* RecentlyClosedCoordinatorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyClosedCoordinatorMock.swift; sourceTree = ""; }; AA7E919628746BCC00AB6B62 /* HistoryMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryMenu.swift; sourceTree = ""; }; AA7E91982875AB4700AB6B62 /* History 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "History 6.xcdatamodel"; sourceTree = ""; }; - AA7E91992875B39300AB6B62 /* Visit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Visit.swift; sourceTree = ""; }; - AA7E919B2875C65000AB6B62 /* Stored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stored.swift; sourceTree = ""; }; AA7E919E287872EA00AB6B62 /* VisitViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitViewModel.swift; sourceTree = ""; }; AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseOverAnimationButton.swift; sourceTree = ""; }; AA7EB6E027E7D05500036718 /* flame-mouse-over.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "flame-mouse-over.json"; sourceTree = ""; }; @@ -4125,9 +4120,7 @@ AAE246F7270A406200BEEAEE /* FirePopoverCollectionViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirePopoverCollectionViewHeader.swift; sourceTree = ""; }; AAE39D1A24F44885008EF28B /* TabCollectionViewModelDelegateMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCollectionViewModelDelegateMock.swift; sourceTree = ""; }; AAE75279263B046100B973F8 /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = ""; }; - AAE7527B263B056C00B973F8 /* HistoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryStore.swift; sourceTree = ""; }; - AAE7527D263B05C600B973F8 /* HistoryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryEntry.swift; sourceTree = ""; }; - AAE7527F263B0A4D00B973F8 /* HistoryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryCoordinator.swift; sourceTree = ""; }; + AAE7527B263B056C00B973F8 /* EncryptedHistoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryStore.swift; sourceTree = ""; }; AAE8B10F258A456C00E81239 /* TabPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewViewController.swift; sourceTree = ""; }; AAE99B8827088A19008B6BD9 /* FirePopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirePopover.swift; sourceTree = ""; }; AAEC74B12642C57200C2EFBC /* HistoryCoordinatingMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryCoordinatingMock.swift; sourceTree = ""; }; @@ -4226,6 +4219,7 @@ B64C853C26944B940048FEBE /* PermissionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionStore.swift; sourceTree = ""; }; B64C85412694590B0048FEBE /* PermissionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionButton.swift; sourceTree = ""; }; B64CE01D2B8622D700126CA5 /* AddressBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarTests.swift; sourceTree = ""; }; + B64E42AA2B909DC9006C1346 /* test.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = test.pdf; sourceTree = ""; }; B65349A9265CF45000DCC645 /* DispatchQueueExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueExtensionsTests.swift; sourceTree = ""; }; B6553691268440D700085A79 /* WKProcessPool+GeolocationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKProcessPool+GeolocationProvider.swift"; sourceTree = ""; }; B65536962684413900085A79 /* WKGeolocationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKGeolocationProvider.h; sourceTree = ""; }; @@ -4237,6 +4231,7 @@ B657841925FA484B00D8DB33 /* NSException+Catch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSException+Catch.m"; sourceTree = ""; }; B657841E25FA497600D8DB33 /* NSException+Catch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSException+Catch.swift"; sourceTree = ""; }; B658BAB52B0F845D00D1F2C7 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + B65C7DFA2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKPDFHUDViewWrapper.swift; sourceTree = ""; }; B65CD8D42B316FCA00A595BB /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; B65CD8D72B341FD300A595BB /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; B65E6B9D26D9EC0800095F96 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; @@ -4339,7 +4334,6 @@ B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitDownloadTask.swift; sourceTree = ""; }; B6A9E45226142B070067D1B9 /* Pixel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pixel.swift; sourceTree = ""; }; B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersionExtension.swift; sourceTree = ""; }; - B6A9E46F26146A250067D1B9 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; B6A9E47626146A570067D1B9 /* PixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEvent.swift; sourceTree = ""; }; B6A9E47E26146A800067D1B9 /* PixelArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArguments.swift; sourceTree = ""; }; B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelParameters.swift; sourceTree = ""; }; @@ -4420,6 +4414,7 @@ B6EC37EA29B5DA2A001ACE79 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; B6EC37FA29B6447F001ACE79 /* TestsServer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TestsServer.xcconfig; sourceTree = ""; }; B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestsURLExtension.swift; sourceTree = ""; }; + B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabContentTests.swift; sourceTree = ""; }; B6F41030264D2B23003DA42C /* ProgressExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressExtension.swift; sourceTree = ""; }; B6F56566299A414300A04298 /* WKWebViewMockingExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewMockingExtension.swift; sourceTree = ""; }; B6F7127D29F6779000594A45 /* QRSharingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRSharingService.swift; sourceTree = ""; }; @@ -4493,6 +4488,7 @@ 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, 3706FCAB293F65D500E42796 /* TrackerRadarKit in Frameworks */, + 85E2BBD02B8F534A00DBEC7A /* History in Frameworks */, 4BF97AD52B43C43F00EB4240 /* NetworkProtection in Frameworks */, 3739326529AE4B39009346AE /* DDGSync in Frameworks */, 37DF000729F9C061002B7D3E /* SyncDataProviders in Frameworks */, @@ -4626,6 +4622,7 @@ 1E21F8E32B73E48600FB272E /* Subscription in Frameworks */, 4B957BE42AC7AE700062CA31 /* DDGSync in Frameworks */, 4B957BE52AC7AE700062CA31 /* OpenSSL in Frameworks */, + 85E2BBD22B8F536F00DBEC7A /* History in Frameworks */, 4B957BE62AC7AE700062CA31 /* PrivacyDashboard in Frameworks */, 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */, 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */, @@ -4688,6 +4685,7 @@ buildActionMask = 2147483647; files = ( 373FB4B12B4D6C42004C88D6 /* PreferencesViews in Frameworks */, + 85E2BBCE2B8F534000DBEC7A /* History in Frameworks */, 1EA7B8D32B7E078C000330A4 /* SubscriptionUI in Frameworks */, B6F7128129F681EB00594A45 /* QuickLookUI.framework in Frameworks */, 9DB6E7242AA0DC5800A17F3C /* LoginItems in Frameworks */, @@ -5621,7 +5619,6 @@ children = ( 4B677440255DBEEA00025BD8 /* Database.swift */, B6085D052743905F00A9C456 /* CoreDataStore.swift */, - AA7E919B2875C65000AB6B62 /* Stored.swift */, ); path = Database; sourceTree = ""; @@ -7583,7 +7580,6 @@ 4BA1A6C1258B0A1300F6F690 /* ContiguousBytesExtension.swift */, B603FD9D2A02712E00F3FCA9 /* CIImageExtension.swift */, 85AC3AF625D5DBFD00C7D2AA /* DataExtension.swift */, - B6A9E46F26146A250067D1B9 /* DateExtension.swift */, B6040855274B830F00680351 /* DictionaryExtension.swift */, B63D467025BFA6C100874977 /* DispatchQueueExtensions.swift */, 4B3B848F297A0E1000A384BD /* EmailManagerExtension.swift */, @@ -7646,6 +7642,7 @@ B602E7CE2A93A5FF00F12201 /* WKBackForwardListExtension.swift */, B68412242B6A67920092F66A /* WKBackForwardListItemExtension.swift */, B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */, + B65C7DFA2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift */, B645D8F529FA95440024461F /* WKProcessPoolExtension.swift */, AAA0CC69253CC43C0079BC96 /* WKUserContentControllerExtension.swift */, B63D466725BEB6C200874977 /* WKWebView+Private.h */, @@ -7696,7 +7693,6 @@ isa = PBXGroup; children = ( AA7E919D287872DB00AB6B62 /* ViewModel */, - AAE75277263B038F00B973F8 /* Model */, AAE75276263B038A00B973F8 /* Services */, ); path = History; @@ -7706,21 +7702,12 @@ isa = PBXGroup; children = ( AAE75278263B046100B973F8 /* History.xcdatamodeld */, - AAE7527B263B056C00B973F8 /* HistoryStore.swift */, + AAE7527B263B056C00B973F8 /* EncryptedHistoryStore.swift */, + 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */, ); path = Services; sourceTree = ""; }; - AAE75277263B038F00B973F8 /* Model */ = { - isa = PBXGroup; - children = ( - AAE7527F263B0A4D00B973F8 /* HistoryCoordinator.swift */, - AAE7527D263B05C600B973F8 /* HistoryEntry.swift */, - AA7E91992875B39300AB6B62 /* Visit.swift */, - ); - path = Model; - sourceTree = ""; - }; AAE8B0FD258A416F00E81239 /* TabPreview */ = { isa = PBXGroup; children = ( @@ -7892,8 +7879,10 @@ B644B43C29D56811003FA9AB /* Tab */ = { isa = PBXGroup; children = ( + B64E42AA2B909DC9006C1346 /* test.pdf */, B644B43929D565DB003FA9AB /* SearchNonexistentDomainTests.swift */, B693766D2B6B5F26005BD9D4 /* ErrorPageTests.swift */, + B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */, B64CE01D2B8622D700126CA5 /* AddressBarTests.swift */, ); path = Tab; @@ -8354,6 +8343,7 @@ 373FB4B22B4D6C4B004C88D6 /* PreferencesViews */, 312978892B64131200B67619 /* DataBrokerProtection */, 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */, + 85E2BBCF2B8F534A00DBEC7A /* History */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8635,6 +8625,7 @@ 373FB4B42B4D6C57004C88D6 /* PreferencesViews */, 1E21F8E22B73E48600FB272E /* Subscription */, 7B94E1642B7ED95100E32B96 /* NetworkProtectionProxy */, + 85E2BBD12B8F536F00DBEC7A /* History */, ); productName = DuckDuckGo; productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */; @@ -8797,6 +8788,7 @@ 3722177F2B3337FE00B8E9C2 /* TestUtils */, 373FB4B02B4D6C42004C88D6 /* PreferencesViews */, 7BA076BA2B65D61400D7FB72 /* NetworkProtectionProxy */, + 85E2BBCD2B8F534000DBEC7A /* History */, 1EA7B8D22B7E078C000330A4 /* SubscriptionUI */, 1EA7B8D42B7E078C000330A4 /* Subscription */, ); @@ -8856,7 +8848,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1520; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1520; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { 3706FDD3293F661700E42796 = { @@ -9044,6 +9036,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B64E42AC2B909DC9006C1346 /* test.pdf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9058,6 +9051,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B64E42AB2B909DC9006C1346 /* test.pdf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9289,6 +9283,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3121F62B2B64266A002F706A /* Copy Swift Package resources */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -9383,6 +9378,7 @@ }; 4B2D067D2A13341200DE1F49 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -9457,6 +9453,7 @@ }; 4BBA2D272B6AC09D00F6A470 /* Embed Login Items */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -9513,6 +9510,7 @@ }; 7B557F2A2B8CA2A400099746 /* Embed Debug-only Network Extensions */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -9761,6 +9759,7 @@ 1D36E659298AA3BA00AA485D /* InternalUserDeciderStore.swift in Sources */, B6BCC5242AFCDABB002C5499 /* DataImportSourceViewModel.swift in Sources */, 3706FEBC293F6EFF00E42796 /* BWResponse.swift in Sources */, + B65C7DFC2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */, 3706FAF4293F65D500E42796 /* SafariBookmarksReader.swift in Sources */, 31C9ADE62AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift in Sources */, B65211262B29A42E00B30633 /* BookmarkStoreMock.swift in Sources */, @@ -9911,7 +9910,6 @@ 1D1A334A2A6FEB170080ACED /* BurnerMode.swift in Sources */, B603971B29BA084C00902A34 /* JSAlertController.swift in Sources */, 3706FB6A293F65D500E42796 /* AddressBarButton.swift in Sources */, - 3706FB6B293F65D500E42796 /* HistoryEntry.swift in Sources */, 4B41EDA42B1543B9001EEDF4 /* VPNPreferencesModel.swift in Sources */, 3706FB6C293F65D500E42796 /* FaviconStore.swift in Sources */, 3706FB6D293F65D500E42796 /* SuggestionListCharacteristics.swift in Sources */, @@ -10046,7 +10044,7 @@ 56D145EC29E6C99B00E3488A /* DataImportStatusProviding.swift in Sources */, 85774B002A713D3B00DE0561 /* BookmarksBarMenuFactory.swift in Sources */, B602E81E2A1E25B1006D261F /* NEOnDemandRuleExtension.swift in Sources */, - 3706FBC7293F65D500E42796 /* HistoryStore.swift in Sources */, + 3706FBC7293F65D500E42796 /* EncryptedHistoryStore.swift in Sources */, 3706FBC8293F65D500E42796 /* FirePopoverCollectionViewItem.swift in Sources */, 3706FBC9293F65D500E42796 /* ArrayExtension.swift in Sources */, 3706FBCA293F65D500E42796 /* CrashReportSender.swift in Sources */, @@ -10111,7 +10109,6 @@ 1D9A4E5B2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */, 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */, 1D26EBAD2B74BECB0002A93F /* NSImageSendable.swift in Sources */, - 3706FBF4293F65D500E42796 /* Visit.swift in Sources */, 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */, 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */, 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */, @@ -10121,7 +10118,6 @@ 3706FBFA293F65D500E42796 /* FileDownloadError.swift in Sources */, 379E877729E98729001C8BB0 /* BookmarksCleanupErrorHandling.swift in Sources */, 3706FBFB293F65D500E42796 /* MoreOrLessView.swift in Sources */, - 3706FBFD293F65D500E42796 /* DateExtension.swift in Sources */, 987799FA29999973005D8EB6 /* LocalBookmarkStore.swift in Sources */, B602E7D02A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 3706FBFE293F65D500E42796 /* History.xcdatamodeld in Sources */, @@ -10211,7 +10207,6 @@ 3706FC3A293F65D500E42796 /* NSOpenPanelExtensions.swift in Sources */, 3706FC3B293F65D500E42796 /* FirePopover.swift in Sources */, 4B4D60C12A0C848E00BCD287 /* NetworkProtectionControllerErrorStore.swift in Sources */, - 3706FC3C293F65D500E42796 /* HistoryCoordinator.swift in Sources */, 3706FC3E293F65D500E42796 /* VariantManager.swift in Sources */, 3706FC3F293F65D500E42796 /* ApplicationDockMenu.swift in Sources */, B68412152B694BA10092F66A /* NSObject+performSelector.m in Sources */, @@ -10221,6 +10216,7 @@ 1DB67F2E2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, 3706FC42293F65D500E42796 /* PixelArguments.swift in Sources */, 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */, + 85D0327C2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, B6685E4329A61C470043D2EE /* DownloadsTabExtension.swift in Sources */, 3706FC44293F65D500E42796 /* BookmarkList.swift in Sources */, 3706FC45293F65D500E42796 /* BookmarkTableRowView.swift in Sources */, @@ -10280,7 +10276,6 @@ 37445F9A2A1566420029F789 /* SyncDataProviders.swift in Sources */, 3706FC6F293F65D500E42796 /* FirePopoverViewModel.swift in Sources */, 3706FC71293F65D500E42796 /* NSColorExtension.swift in Sources */, - 3706FC72293F65D500E42796 /* Stored.swift in Sources */, 1DB9618229F67F6100CF5568 /* FaviconNullStore.swift in Sources */, 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */, 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */, @@ -10361,7 +10356,6 @@ 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */, 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */, 4B9DB0572A983B55000927DB /* MockNotificationService.swift in Sources */, - 3706FDE3293F661700E42796 /* History.xcdatamodeld in Sources */, 3706FDE4293F661700E42796 /* TabLazyLoaderTests.swift in Sources */, 3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */, 3706FDE6293F661700E42796 /* BookmarkOutlineViewDataSourceTests.swift in Sources */, @@ -10607,6 +10601,7 @@ B62A234129C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */, B603973929BF0EBE00902A34 /* PrivacyDashboardIntegrationTests.swift in Sources */, B644B43E29D5682B003FA9AB /* SearchNonexistentDomainTests.swift in Sources */, + B6EEDD7E2B8C69E900637EBC /* TabContentTests.swift in Sources */, 3706FEA5293F662100E42796 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, B603973D29BF1D7D00902A34 /* AutoconsentIntegrationTests.swift in Sources */, B60C6F8729B1CAB2007BFAA8 /* TestRunHelper.swift in Sources */, @@ -10649,6 +10644,7 @@ 4B1AD92125FC474E00261379 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, B62A234029C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */, B603973829BF0EBE00902A34 /* PrivacyDashboardIntegrationTests.swift in Sources */, + B6EEDD7D2B8C69E900637EBC /* TabContentTests.swift in Sources */, B644B43D29D56829003FA9AB /* SearchNonexistentDomainTests.swift in Sources */, B603973C29BF1D7D00902A34 /* AutoconsentIntegrationTests.swift in Sources */, B60C6F8629B1CAB0007BFAA8 /* TestRunHelper.swift in Sources */, @@ -11019,6 +11015,7 @@ 4B957A192AC7AE700062CA31 /* PasswordManagerCoordinator.swift in Sources */, 4B957A1A2AC7AE700062CA31 /* PasswordManagementIdentityModel.swift in Sources */, 4B957A1B2AC7AE700062CA31 /* UserDefaultsWrapper.swift in Sources */, + B65C7DFD2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */, 4B957A1C2AC7AE700062CA31 /* PasswordManagementPopover.swift in Sources */, 4B957A1D2AC7AE700062CA31 /* BWCommunicator.swift in Sources */, 4B957A1E2AC7AE700062CA31 /* HomePageRecentlyVisitedModel.swift in Sources */, @@ -11028,6 +11025,7 @@ 4B957A222AC7AE700062CA31 /* FirefoxBookmarksReader.swift in Sources */, 4B0526622B1D55320054955A /* VPNFeedbackSender.swift in Sources */, 4B957A232AC7AE700062CA31 /* DeviceIdleStateDetector.swift in Sources */, + 85D0327D2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, 4B957A242AC7AE700062CA31 /* FlatButton.swift in Sources */, 4B957A252AC7AE700062CA31 /* PinnedTabView.swift in Sources */, 4B957A262AC7AE700062CA31 /* DataEncryption.swift in Sources */, @@ -11098,7 +11096,6 @@ 4B957A602AC7AE700062CA31 /* NSNotificationName+Debug.swift in Sources */, 4B957A612AC7AE700062CA31 /* NavigationBarBadgeAnimationView.swift in Sources */, 4B957A622AC7AE700062CA31 /* AddressBarButton.swift in Sources */, - 4B957A632AC7AE700062CA31 /* HistoryEntry.swift in Sources */, 4B957A642AC7AE700062CA31 /* FaviconStore.swift in Sources */, 4B957A652AC7AE700062CA31 /* WaitlistTermsAndConditionsView.swift in Sources */, B62B48592ADE730D000DECE5 /* FileImportView.swift in Sources */, @@ -11220,7 +11217,7 @@ 4B957ACE2AC7AE700062CA31 /* BrowserTabViewController.swift in Sources */, 4B957ACF2AC7AE700062CA31 /* CallToAction.swift in Sources */, 4B957AD02AC7AE700062CA31 /* MouseOverView.swift in Sources */, - 4B957AD12AC7AE700062CA31 /* HistoryStore.swift in Sources */, + 4B957AD12AC7AE700062CA31 /* EncryptedHistoryStore.swift in Sources */, 4B957AD22AC7AE700062CA31 /* FirePopoverCollectionViewItem.swift in Sources */, 4B957AD32AC7AE700062CA31 /* ArrayExtension.swift in Sources */, 4B957AD42AC7AE700062CA31 /* NetworkProtectionInviteCodeViewModel.swift in Sources */, @@ -11277,7 +11274,6 @@ 4B957B042AC7AE700062CA31 /* UpdateController.swift in Sources */, 4B957B052AC7AE700062CA31 /* FindInPageModel.swift in Sources */, 4B957B062AC7AE700062CA31 /* PseudoFolder.swift in Sources */, - 4B957B072AC7AE700062CA31 /* Visit.swift in Sources */, 4B2F565D2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */, 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */, @@ -11290,7 +11286,6 @@ B69A14FC2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, 4B957B0F2AC7AE700062CA31 /* FileDownloadError.swift in Sources */, 4B957B102AC7AE700062CA31 /* MoreOrLessView.swift in Sources */, - 4B957B112AC7AE700062CA31 /* DateExtension.swift in Sources */, 4B957B122AC7AE700062CA31 /* History.xcdatamodeld in Sources */, 4B957B132AC7AE700062CA31 /* PermissionStore.swift in Sources */, EEC4A6612B277F1100F7C0AA /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -11367,7 +11362,6 @@ 4B957B522AC7AE700062CA31 /* NSOpenPanelExtensions.swift in Sources */, EEC4A66F2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift in Sources */, 4B957B532AC7AE700062CA31 /* FirePopover.swift in Sources */, - 4B957B542AC7AE700062CA31 /* HistoryCoordinator.swift in Sources */, 4B957B552AC7AE700062CA31 /* NetworkProtectionOnboardingMenu.swift in Sources */, 4B957B562AC7AE700062CA31 /* VariantManager.swift in Sources */, 4B957B572AC7AE700062CA31 /* ApplicationDockMenu.swift in Sources */, @@ -11446,7 +11440,6 @@ 4B957B932AC7AE700062CA31 /* FirePopoverViewModel.swift in Sources */, 4B957B942AC7AE700062CA31 /* BWCommand.swift in Sources */, 4B957B952AC7AE700062CA31 /* NSColorExtension.swift in Sources */, - 4B957B962AC7AE700062CA31 /* Stored.swift in Sources */, 4B957B972AC7AE700062CA31 /* AddressBarButtonsViewController.swift in Sources */, 4B957B982AC7AE700062CA31 /* BWError.swift in Sources */, 4B957B9A2AC7AE700062CA31 /* PixelDataRecord.swift in Sources */, @@ -11603,6 +11596,7 @@ B66CA41E2AD910B300447CF0 /* DataImportView.swift in Sources */, B637273D26CCF0C200C8CB02 /* OptionalExtension.swift in Sources */, 4BE65477271FCD41008D1D63 /* PasswordManagementLoginItemView.swift in Sources */, + 85D0327B2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, AA80EC54256BE3BC007083E7 /* UserText.swift in Sources */, B61EF3EC266F91E700B4D78F /* WKWebView+Download.swift in Sources */, 311B262728E73E0A00FD181A /* TabShadowConfig.swift in Sources */, @@ -11635,6 +11629,7 @@ AA6EF9AD25066F42004754E6 /* WindowsManager.swift in Sources */, 1D43EB3A292B63B00065E5D6 /* BWRequest.swift in Sources */, B684121C2B6A1D880092F66A /* ErrorPageHTMLTemplate.swift in Sources */, + B65C7DFB2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */, B68458CD25C7EB9000DC17B6 /* WKWebViewConfigurationExtensions.swift in Sources */, 85AC7ADD27BEB6EE00FFB69B /* HomePageDefaultBrowserModel.swift in Sources */, B6619EFB2B111CC500CD9186 /* InstructionsFormatParser.swift in Sources */, @@ -11879,7 +11874,6 @@ B690152C2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 31F7F2A6288AD2CA001C0D64 /* NavigationBarBadgeAnimationView.swift in Sources */, AAC5E4F125D6BF10007F5990 /* AddressBarButton.swift in Sources */, - AAE7527E263B05C600B973F8 /* HistoryEntry.swift in Sources */, AA5FA69D275F945C00DCE9C9 /* FaviconStore.swift in Sources */, 4B9DB0352A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, AAB8203C26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift in Sources */, @@ -12008,7 +12002,7 @@ AA585D84248FD31100E9A3E2 /* BrowserTabViewController.swift in Sources */, 85707F22276A32B600DC0649 /* CallToAction.swift in Sources */, B693954B26F04BEB0015B914 /* MouseOverView.swift in Sources */, - AAE7527C263B056C00B973F8 /* HistoryStore.swift in Sources */, + AAE7527C263B056C00B973F8 /* EncryptedHistoryStore.swift in Sources */, AAE246F32709EF3B00BEEAEE /* FirePopoverCollectionViewItem.swift in Sources */, 4B41EDA32B1543B9001EEDF4 /* VPNPreferencesModel.swift in Sources */, AA61C0D22727F59B00E6B681 /* ArrayExtension.swift in Sources */, @@ -12068,7 +12062,6 @@ 85A0118225AF60E700FA6A0C /* FindInPageModel.swift in Sources */, 7BA7CC4E2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 4B9292A226670D2A00AD2C21 /* PseudoFolder.swift in Sources */, - AA7E919A2875B39300AB6B62 /* Visit.swift in Sources */, 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */, @@ -12080,7 +12073,6 @@ 4BE5336C286912D40019DBFD /* BookmarksBarCollectionViewItem.swift in Sources */, B6C0B23926E742610031CB7F /* FileDownloadError.swift in Sources */, 85589EA027BFE60E0038AD11 /* MoreOrLessView.swift in Sources */, - B6A9E47026146A250067D1B9 /* DateExtension.swift in Sources */, AAE7527A263B046100B973F8 /* History.xcdatamodeld in Sources */, B64C853D26944B940048FEBE /* PermissionStore.swift in Sources */, AA75A0AE26F3500C0086B667 /* PrivacyIconViewModel.swift in Sources */, @@ -12160,7 +12152,6 @@ B6DB3AF6278EA0130024C5C4 /* BundleExtension.swift in Sources */, 4B0511E1262CAA8600F6079C /* NSOpenPanelExtensions.swift in Sources */, AAE99B8927088A19008B6BD9 /* FirePopover.swift in Sources */, - AAE75280263B0A4D00B973F8 /* HistoryCoordinator.swift in Sources */, 4B0BD7B72A9FE6E500EF609D /* NetworkProtectionOnboardingMenu.swift in Sources */, B69B503D2726A12500758A2B /* VariantManager.swift in Sources */, AA97BF4625135DD30014931A /* ApplicationDockMenu.swift in Sources */, @@ -12227,7 +12218,6 @@ AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */, 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */, F41D174125CB131900472416 /* NSColorExtension.swift in Sources */, - AA7E919C2875C65000AB6B62 /* Stored.swift in Sources */, AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, B6F9BDE42B45CD1900677B33 /* ModalView.swift in Sources */, 1D2DC0072901679C008083A1 /* BWError.swift in Sources */, @@ -12313,7 +12303,6 @@ 37D2377C287EBDA300BCE03B /* TabIndexTests.swift in Sources */, 37534CA52811987D002621E7 /* AdjacentItemEnumeratorTests.swift in Sources */, B6DA44232616CABC00DD1EC2 /* PixelArgumentsTests.swift in Sources */, - AAEC74BC2642F0F800C2EFBC /* History.xcdatamodeld in Sources */, 56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */, 37534C9E28104D9B002621E7 /* TabLazyLoaderTests.swift in Sources */, B6619EF62B10DFF700CD9186 /* InstructionsFormatParserTests.swift in Sources */, @@ -13601,7 +13590,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 113.0.0; + version = 114.1.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { @@ -14111,6 +14100,21 @@ isa = XCSwiftPackageProductDependency; productName = PixelKit; }; + 85E2BBCD2B8F534000DBEC7A /* History */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = History; + }; + 85E2BBCF2B8F534A00DBEC7A /* History */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = History; + }; + 85E2BBD12B8F536F00DBEC7A /* History */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = History; + }; 9807F644278CA16F00E1547B /* BrowserServicesKit */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b43fb91512..c89d4445e6 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "f903ffcbc51e85ac262c355b56726e3387957a80", - "version" : "113.0.0" + "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", + "version" : "114.1.0" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme index a37fddc40f..29eecc0cc8 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme @@ -1,6 +1,6 @@ - - - - diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme index d9bdde676a..58557db074 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser.xcscheme @@ -1,6 +1,6 @@ (key: .onboardingFinished, defaultValue: false) - if !isOnboardingFinished.wrappedValue, - FileManager.default.fileExists(atPath: URL.sandboxApplicationSupportURL.path) { - isOnboardingFinished.wrappedValue = true - } - let internalUserDeciderStore = InternalUserDeciderStore(fileStore: fileStore) internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) @@ -212,7 +205,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel didFinishLaunching = true } - HistoryCoordinator.shared.loadHistory() + HistoryCoordinator.shared.loadHistory { + HistoryCoordinator.shared.migrateModelV5toV6IfNeeded() + } + PrivacyFeatures.httpsUpgrade.loadDataAsync() bookmarksManager.loadBookmarks() if case .normal = NSApp.runType { @@ -284,7 +280,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #if SUBSCRIPTION Task { - var defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.default + let defaultEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment.default let currentEnvironment = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: defaultEnvironment).wrappedValue diff --git a/DuckDuckGo/Bookmarks/Legacy/Bookmark.xcdatamodeld/Bookmark 3.xcdatamodel/contents b/DuckDuckGo/Bookmarks/Legacy/Bookmark.xcdatamodeld/Bookmark 3.xcdatamodel/contents index b622362964..3572e4db8c 100644 --- a/DuckDuckGo/Bookmarks/Legacy/Bookmark.xcdatamodeld/Bookmark 3.xcdatamodel/contents +++ b/DuckDuckGo/Bookmarks/Legacy/Bookmark.xcdatamodeld/Bookmark 3.xcdatamodel/contents @@ -1,13 +1,25 @@ - + - + + + + + - - + + + + + + + + + + diff --git a/DuckDuckGo/Common/Database/Database.swift b/DuckDuckGo/Common/Database/Database.swift index e44d63fb33..ad97938c7e 100644 --- a/DuckDuckGo/Common/Database/Database.swift +++ b/DuckDuckGo/Common/Database/Database.swift @@ -18,8 +18,8 @@ import AppKit import BrowserServicesKit -import Foundation import CoreData +import Foundation import Persistence final class Database { @@ -41,17 +41,15 @@ final class Database { static func makeDatabase() -> (CoreDataDatabase?, Error?) { func makeDatabase(keyStore: EncryptionKeyStoring, containerLocation: URL) -> (CoreDataDatabase?, Error?) { - do { - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - try EncryptedValueTransformer.registerTransformer(keyStore: keyStore) - } catch { - return (nil, error) - } let mainModel = NSManagedObjectModel.mergedModel(from: [.main])! + _=mainModel.registerValueTransformers(withAllowedPropertyClasses: [ + NSImage.self, + NSString.self, + NSURL.self, + NSNumber.self, + NSError.self, + NSData.self + ], keyStore: keyStore) let httpsUpgradeModel = HTTPSUpgrade.managedObjectModel return (CoreDataDatabase(name: Constants.databaseName, @@ -118,6 +116,67 @@ extension Array where Element == CoreDataErrorsParser.ErrorInfo { } } +extension ValueTransformer { + + static func registerValueTransformer(for propertyClass: AnyClass, with keyStore: EncryptionKeyStoring) -> NSValueTransformerName { + guard let encodableType = propertyClass as? (NSObject & NSSecureCoding).Type else { + fatalError("Unsupported type") + } + func registerValueTransformer(for type: T.Type) -> NSValueTransformerName { + (try? EncryptedValueTransformer.registerTransformer(keyStore: keyStore))! + return EncryptedValueTransformer.transformerName + } + return registerValueTransformer(for: encodableType) + } + +} + +extension NSManagedObjectModel { + + private static let transformerUserInfoKey = "transformer" + func registerValueTransformers(withAllowedPropertyClasses allowedPropertyClasses: [AnyClass]? = nil, + keyStore: EncryptionKeyStoring) -> [NSValueTransformerName] { + var registeredTransformers = [NSValueTransformerName]() + let allowedPropertyClassNames = allowedPropertyClasses.map { Set($0.map(NSStringFromClass)) } + + // fix "no NSValueTransformer with class name 'X'" warnings + // https://stackoverflow.com/a/77623593/748453 + for entity in self.entities { + for property in entity.properties { + guard let property = property as? NSAttributeDescription, property.attributeType == .transformableAttributeType else { continue } + + let transformerName: String + if let valueTransformerName = property.valueTransformerName, !valueTransformerName.isEmpty { + transformerName = valueTransformerName + } else if let transformerUserInfoValue = property.userInfo?[Self.transformerUserInfoKey] as? String, !transformerUserInfoValue.isEmpty { + transformerName = transformerUserInfoValue + property.userInfo?.removeValue(forKey: Self.transformerUserInfoKey) + property.valueTransformerName = transformerName + } else { + assertionFailure("Transformer (User Info `transformer` key) not set for \(entity).\(property)") + continue + } + + guard ValueTransformer(forName: .init(rawValue: transformerName)) == nil else { continue } + + let propertyClassName = transformerName.dropping(suffix: "Transformer") + assert(propertyClassName != transformerName, "Expected Transformer name like `NSStringTransformer`") + guard allowedPropertyClassNames?.contains(propertyClassName) != false, + let propertyClass = NSClassFromString(propertyClassName) else { + assertionFailure("Invalid class name `\(propertyClassName)` for \(transformerName)") + continue + } + + let transformer = ValueTransformer.registerValueTransformer(for: propertyClass, with: keyStore) + assert(ValueTransformer(forName: .init(transformerName)) != nil) + registeredTransformers.append(transformer) + } + } + return registeredTransformers + } + +} + extension NSManagedObjectContext { func save(onErrorFire event: Pixel.Event.Debug) throws { diff --git a/DuckDuckGo/Common/Database/Stored.swift b/DuckDuckGo/Common/Database/Stored.swift deleted file mode 100644 index 69f2c40de1..0000000000 --- a/DuckDuckGo/Common/Database/Stored.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Stored.swift -// -// Copyright Β© 2022 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -internal class Stored { - - var savingState = SavingState.initialized - - enum SavingState { - case initialized - case saved - } - -} diff --git a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift index 5b39680e13..4705706483 100644 --- a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift +++ b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift @@ -21,10 +21,16 @@ import Foundation extension FileManager { + @discardableResult func moveItem(at srcURL: URL, to destURL: URL, incrementingIndexIfExists flag: Bool, pathExtension: String? = nil) throws -> URL { return try self.perform(self.moveItem, from: srcURL, to: destURL, incrementingIndexIfExists: flag, pathExtension: pathExtension) } + @discardableResult + func copyItem(at srcURL: URL, to destURL: URL, incrementingIndexIfExists flag: Bool, pathExtension: String? = nil) throws -> URL { + return try self.perform(self.copyItem, from: srcURL, to: destURL, incrementingIndexIfExists: flag, pathExtension: pathExtension) + } + private func perform(_ operation: (URL, URL) throws -> Void, from srcURL: URL, to destURL: URL, diff --git a/DuckDuckGo/Common/Extensions/NSBezierPathExtension.swift b/DuckDuckGo/Common/Extensions/NSBezierPathExtension.swift index 66d3179dc3..e83dc7b9e6 100644 --- a/DuckDuckGo/Common/Extensions/NSBezierPathExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSBezierPathExtension.swift @@ -22,25 +22,31 @@ import AppKit extension NSBezierPath { - var cgPath: CGPath { - let path = CGMutablePath() - var points = [CGPoint](repeating: .zero, count: 3) - for i in 0 ..< self.elementCount { - let type = self.element(at: i, associatedPoints: &points) - switch type { - case .moveTo: - path.move(to: points[0]) - case .lineTo: - path.addLine(to: points[0]) - case .curveTo: - path.addCurve(to: points[2], control1: points[0], control2: points[1]) - case .closePath: - path.closeSubpath() - @unknown default: - break + func asCGPath() -> CGPath { + if #available(macOS 14.0, *) { + return self.cgPath + } else { + let path = CGMutablePath() + var points = [CGPoint](repeating: .zero, count: 3) + for i in 0 ..< self.elementCount { + let type = self.element(at: i, associatedPoints: &points) + switch type { + case .moveTo: + path.move(to: points[0]) + case .lineTo: + path.addLine(to: points[0]) + case .curveTo, .cubicCurveTo: + path.addCurve(to: points[2], control1: points[0], control2: points[1]) + case .quadraticCurveTo: + path.addQuadCurve(to: points[1], control: points[0]) + case .closePath: + path.closeSubpath() + @unknown default: + break + } } + return path } - return path } } diff --git a/DuckDuckGo/Common/Extensions/NavigationActionExtension.swift b/DuckDuckGo/Common/Extensions/NavigationActionExtension.swift index f633e99656..41a11f3a84 100644 --- a/DuckDuckGo/Common/Extensions/NavigationActionExtension.swift +++ b/DuckDuckGo/Common/Extensions/NavigationActionExtension.swift @@ -43,4 +43,5 @@ extension NavigationAction { extension CustomNavigationType { static let userEnteredUrl = CustomNavigationType(rawValue: "userEnteredUrl") static let tabContentUpdate = CustomNavigationType(rawValue: "tabContentUpdate") + static let userRequestedPageDownload = CustomNavigationType(rawValue: "userRequestedPageDownload") } diff --git a/DuckDuckGo/Common/Extensions/WKPDFHUDViewWrapper.swift b/DuckDuckGo/Common/Extensions/WKPDFHUDViewWrapper.swift new file mode 100644 index 0000000000..ef4919b1fd --- /dev/null +++ b/DuckDuckGo/Common/Extensions/WKPDFHUDViewWrapper.swift @@ -0,0 +1,97 @@ +// +// WKPDFHUDViewWrapper.swift +// +// Copyright Β© 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import WebKit + +/// A wrapper for the PDF HUD window with Zoom controls, Download and Open in Preview buttons +/// Used to trigger Save PDF +struct WKPDFHUDViewWrapper { + + static let WKPDFHUDViewClass: AnyClass? = NSClassFromString("WKPDFHUDView") + static let performActionForControlSelector = NSSelectorFromString("_performActionForControl:") + static let visibleKey = "_visible" + static let setVisibleSelector = NSSelectorFromString("_setVisible:") + static let savePDFControlId = "arrow.down.circle" + + private let hudView: NSView + + var isVisible: Bool { + get { + hudView.layer?.sublayers?.first?.opacity ?? 0 > 0 + } + nonmutating set { + guard hudView.responds(to: Self.setVisibleSelector) else { return } + hudView.perform(Self.setVisibleSelector, with: newValue) + } + } + + /// Create a wrapper over the PDF HUD view validating its class is `WKPDFHUDView` + /// - parameter view: the WKPDFHUDView to wrap + /// - returns nil if the view + init?(view: NSView) { + guard type(of: view) == Self.WKPDFHUDViewClass else { return nil } + + guard Self.WKPDFHUDViewClass?.instancesRespond(to: Self.performActionForControlSelector) == true else { + assertionFailure("WKPDFHUDView doesnβ€˜t respond to _performActionForControl:") + return nil + } + self.hudView = view + } + + /// Find WebViewβ€˜s PDF HUD view at a clicked point + /// + /// Used to get PDF controls view of a clicked WebView frame for `Print…` and `Save As…` PDF context menu commands + static func getPdfHudView(in webView: WKWebView, at location: NSPoint? = nil) -> Self? { + guard let hudView = webView.subviews.last(where: { type(of: $0) == Self.WKPDFHUDViewClass && $0.frame.contains(location ?? $0.frame.origin) }) else { +#if DEBUG + Task { + if await webView.mimeType == "application/pdf" { + assertionFailure("WebView doesnβ€˜t have PDF HUD View") + } + } +#endif + return nil + } + return self.init(view: hudView) + } + + func savePDF() { + let wasVisible = isVisible + self.setIsVisibleIVar(true) + defer { + if !wasVisible { + self.setIsVisibleIVar(false) + } + } + hudView.perform(Self.performActionForControlSelector, with: Self.savePDFControlId) + } + + // try to set _visible ivar value directly to avoid actually showing the HUD + private func setIsVisibleIVar(_ value: Bool) { + do { + try NSException.catch { + hudView.setValue(value, forKey: Self.visibleKey) + } + } catch { + assertionFailure("\(error)") + self.isVisible = value + } + } + +} diff --git a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift index 4c70d4bacc..fcd19de900 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift @@ -325,6 +325,16 @@ extension WKWebView { return self.printOperation(with: printInfo) } + func hudView(at point: NSPoint? = nil) -> WKPDFHUDViewWrapper? { + WKPDFHUDViewWrapper.getPdfHudView(in: self, at: point) + } + + func savePDF(_ pdfHUD: WKPDFHUDViewWrapper? = nil) -> Bool { + guard let hudView = pdfHUD ?? hudView() else { return false } + hudView.savePDF() + return true + } + var fullScreenPlaceholderView: NSView? { guard self.responds(to: Selector.fullScreenPlaceholderView) else { return nil } return self.value(forKey: NSStringFromSelector(Selector.fullScreenPlaceholderView)) as? NSView diff --git a/DuckDuckGo/Common/FileSystem/WorkspaceProtocol.swift b/DuckDuckGo/Common/FileSystem/WorkspaceProtocol.swift index ddaf1e337a..3a41f12614 100644 --- a/DuckDuckGo/Common/FileSystem/WorkspaceProtocol.swift +++ b/DuckDuckGo/Common/FileSystem/WorkspaceProtocol.swift @@ -22,5 +22,8 @@ protocol Workspace { func urlForApplication(toOpen url: URL) -> URL? @discardableResult func open(_ url: URL) -> Bool + + @discardableResult + func open(_ urls: [URL], withAppBundleIdentifier bundleIdentifier: String?, options: NSWorkspace.LaunchOptions, additionalEventParamDescriptor descriptor: NSAppleEventDescriptor?, launchIdentifiers identifiers: AutoreleasingUnsafeMutablePointer?) -> Bool } extension NSWorkspace: Workspace {} diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 7a9cdced0c..47681badff 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -375,6 +375,8 @@ struct UserText { static let downloadsAlwaysAsk = NSLocalizedString("downloads.always-ask", value: "Always ask where to save files", comment: "Downloads preferences checkbox") static let downloadsChangeDirectory = NSLocalizedString("downloads.change", value: "Change…", comment: "Change downloads directory button") + static let downloadsOpenPopupOnCompletion = NSLocalizedString("downloads.open.on.completion", value: "Automatically open the Downloads panel when downloads complete", comment: "Checkbox to open a Download Manager popover when downloads are completed") + // MARK: Password Manager static let passwordManagement = NSLocalizedString("passsword.management", value: "Autofill", comment: "Used as title for password management user interface") static let passwordManagementAllItems = NSLocalizedString("passsword.management.all-items", value: "All Items", comment: "Used as title for the Autofill All Items option") @@ -671,6 +673,8 @@ struct UserText { static let downloadCanceled = NSLocalizedString("downloads.error.canceled", value: "Canceled", comment: "Short error description when downloaded file download was canceled") static let downloadFailedToMoveFileToDownloads = NSLocalizedString("downloads.error.move.failed", value: "Could not move file to Downloads", comment: "Short error description when could not move downloaded file to the Downloads folder") static let downloadFailed = NSLocalizedString("downloads.error.other", value: "Error", comment: "Short error description when Download failed") + static let downloadBytesLoadedFormat = NSLocalizedString("downloads.bytes.format", value: "%@ of %@", comment: "Number of bytes out of total bytes downloaded (1Mb of 2Mb)") + static let downloadSpeedFormat = NSLocalizedString("downloads.speed.format", value: "%@/s", comment: "Download speed format (1Mb/sec)") static let cancelDownloadToolTip = NSLocalizedString("downloads.tooltip.cancel", value: "Cancel Download", comment: "Mouse-over tooltip for Cancel Download button") static let restartDownloadToolTip = NSLocalizedString("downloads.tooltip.restart", value: "Restart Download", comment: "Mouse-over tooltip for Restart Download button") diff --git a/DuckDuckGo/Common/Logging/Logging.swift b/DuckDuckGo/Common/Logging/Logging.swift index 25f340d0ae..e6c9d18b36 100644 --- a/DuckDuckGo/Common/Logging/Logging.swift +++ b/DuckDuckGo/Common/Logging/Logging.swift @@ -26,7 +26,6 @@ extension OSLog { case atb = "ATB" case config = "Configuration Downloading" case fire = "Fire" - case history = "History" case dataImportExport = "Data Import/Export" case pixel = "Pixel" case contentBlocking = "Content Blocking" @@ -55,7 +54,6 @@ extension OSLog { @OSLogWrapper(.atb) static var atb @OSLogWrapper(.config) static var config @OSLogWrapper(.fire) static var fire - @OSLogWrapper(.history) static var history @OSLogWrapper(.dataImportExport) static var dataImportExport @OSLogWrapper(.pixel) static var pixel @OSLogWrapper(.contentBlocking) static var contentBlocking diff --git a/DuckDuckGo/Common/Utilities/HardwareModel.swift b/DuckDuckGo/Common/Utilities/HardwareModel.swift index 5b0ab5a74b..70768376ed 100644 --- a/DuckDuckGo/Common/Utilities/HardwareModel.swift +++ b/DuckDuckGo/Common/Utilities/HardwareModel.swift @@ -22,7 +22,15 @@ import IOKit struct HardwareModel { static var model: String? { - let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")) + let port: mach_port_t + + if #available(macOS 12.0, *) { + port = kIOMainPortDefault + } else { + port = kIOMasterPortDefault + } + + let service = IOServiceGetMatchingService(port, IOServiceMatching("IOPlatformExpertDevice")) var modelIdentifier: String? if let modelData = IORegistryEntryCreateCFProperty( diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 8ad5fa3359..272d63cca0 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -58,6 +58,7 @@ public struct UserDefaultsWrapper { case selectedDownloadLocationKey = "preferences.download-location" case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location" case alwaysRequestDownloadLocationKey = "preferences.download-location.always-request" + case openDownloadsPopupOnCompletionKey = "preferences.downloads.open.on.completion" case autoconsentEnabled = "preferences.autoconsent-enabled" case duckPlayerMode = "preferences.duck-player" case youtubeOverlayInteracted = "preferences.youtube-overlay-interacted" diff --git a/DuckDuckGo/Common/View/AppKit/CircularProgressView.swift b/DuckDuckGo/Common/View/AppKit/CircularProgressView.swift index bbeac9b5b1..7afb3d8784 100644 --- a/DuckDuckGo/Common/View/AppKit/CircularProgressView.swift +++ b/DuckDuckGo/Common/View/AppKit/CircularProgressView.swift @@ -275,7 +275,7 @@ private extension CAShapeLayer { self.bounds = CGRect(x: 0, y: 0, width: (radius + lineWidth) * 2, height: (radius + lineWidth) * 2) let rect = NSRect(x: lineWidth, y: lineWidth, width: radius * 2, height: radius * 2) - self.path = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius).cgPath + self.path = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius).asCGPath() self.lineWidth = lineWidth self.fillColor = NSColor.clear.cgColor diff --git a/DuckDuckGo/Common/View/SwiftUI/ViewExtension.swift b/DuckDuckGo/Common/View/SwiftUI/ViewExtension.swift index 8f5cd6f2dd..fa1682f58b 100644 --- a/DuckDuckGo/Common/View/SwiftUI/ViewExtension.swift +++ b/DuckDuckGo/Common/View/SwiftUI/ViewExtension.swift @@ -125,7 +125,7 @@ private struct RoundedCorner: Shape { func path(in rect: CGRect) -> Path { let path = NSBezierPath(roundedRect: rect, forCorners: corners, cornerRadius: radius) - return Path(path.cgPath) + return Path(path.asCGPath()) } } diff --git a/DuckDuckGo/Favicons/Model/FaviconManager.swift b/DuckDuckGo/Favicons/Model/FaviconManager.swift index 28203bafb7..87842b8e88 100644 --- a/DuckDuckGo/Favicons/Model/FaviconManager.swift +++ b/DuckDuckGo/Favicons/Model/FaviconManager.swift @@ -21,6 +21,7 @@ import Cocoa import Combine import BrowserServicesKit import Common +import History @MainActor protocol FaviconManagement: AnyObject { diff --git a/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift b/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift index 9ae1945b25..eb271ab5a6 100644 --- a/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift +++ b/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift @@ -83,10 +83,11 @@ final class FileDownloadManager: FileDownloadManagerProtocol { return url } - var promptForLocation: Bool { + func shouldPromptForLocation(for url: URL?) -> Bool { switch self { case .prompt: return true - case .preset, .auto: return false + case .preset: return false + case .auto: return url?.isFileURL ?? true // always prompt when "downloading" a local file } } } @@ -96,7 +97,7 @@ final class FileDownloadManager: FileDownloadManagerProtocol { dispatchPrecondition(condition: .onQueue(.main)) let task = WebKitDownloadTask(download: download, - promptForLocation: location.promptForLocation, + promptForLocation: location.shouldPromptForLocation(for: download.originalRequest?.url), destinationURL: location.destinationURL, tempURL: location.tempURL, isBurner: fromBurnerWindow) diff --git a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift index 4e96b1f482..a69b84e30d 100644 --- a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift +++ b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift @@ -16,9 +16,9 @@ // limitations under the License. // -import Navigation import Combine import Foundation +import Navigation import UniformTypeIdentifiers import WebKit @@ -33,6 +33,10 @@ protocol WebKitDownloadTaskDelegate: AnyObject { final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable { static let downloadExtension = "duckload" + private enum Constants { + static let remainingDownloadTimeEstimationDelay: TimeInterval = 1 + static let downloadSpeedSmoothingFactor = 0.1 + } let progress: Progress let shouldPromptForLocation: Bool @@ -72,7 +76,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable private weak var delegate: WebKitDownloadTaskDelegate? private let download: WebKitDownload - private var cancellables = Set() + private var progressCancellable: AnyCancellable? private var decideDestinationCompletionHandler: ((URL?) -> Void)? @@ -114,12 +118,37 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable private func start() { self.progress.fileDownloadingSourceURL = download.originalRequest?.url if let progress = (self.download as? ProgressReporting)?.progress { - progress.publisher(for: \.totalUnitCount) - .assign(to: \.totalUnitCount, onWeaklyHeld: self.progress) - .store(in: &self.cancellables) - progress.publisher(for: \.completedUnitCount) - .assign(to: \.completedUnitCount, onWeaklyHeld: self.progress) - .store(in: &self.cancellables) + + var startTime: Date? + progressCancellable = progress.publisher(for: \.totalUnitCount) + .combineLatest(progress.publisher(for: \.completedUnitCount)) + .sink { [weak progress=self.progress] total, completed in + guard let progress else { return } + if progress.totalUnitCount != total { + progress.totalUnitCount = total + } + progress.completedUnitCount = completed + + if total > 0, completed > 0 { + guard let startTime else { + startTime = Date() + return + } + let elapsedTime = Date().timeIntervalSince(startTime) + // delay before we start calculating the estimated time - because initially itβ€˜s not reliable + guard elapsedTime > Constants.remainingDownloadTimeEstimationDelay else { return } + + // calculate instantaneous download speed + var throughput = Double(completed) / elapsedTime + + // calculate the moving average of download speed + if let oldThroughput = progress.throughput.map(Double.init) { + throughput = Constants.downloadSpeedSmoothingFactor * throughput + (1 - Constants.downloadSpeedSmoothingFactor) * oldThroughput + } + progress.throughput = Int(throughput) + progress.estimatedTimeRemaining = Double(total - completed) / throughput + } + } } } @@ -269,7 +298,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { // sometimes suggesteFilename has an extension appended to already present URL file extension // e.g. feed.xml.rss for www.domain.com/rss.xml if let urlSuggestedFilename = response.url?.suggestedFilename, - !urlSuggestedFilename.pathExtension.isEmpty, + !(urlSuggestedFilename.pathExtension.isEmpty || (self.suggestedFileType == .html && urlSuggestedFilename.pathExtension == "html")), suggestedFilename.hasPrefix(urlSuggestedFilename) { suggestedFilename = urlSuggestedFilename } diff --git a/DuckDuckGo/FileDownload/View/Downloads.storyboard b/DuckDuckGo/FileDownload/View/Downloads.storyboard index 411575e7a9..41e5511da3 100644 --- a/DuckDuckGo/FileDownload/View/Downloads.storyboard +++ b/DuckDuckGo/FileDownload/View/Downloads.storyboard @@ -11,14 +11,14 @@ - + - + - + @@ -29,7 +29,7 @@ - + - + - + - + @@ -111,7 +111,7 @@ - + @@ -123,7 +123,7 @@ - + @@ -134,7 +134,7 @@ - + @@ -142,7 +142,7 @@ - + - + @@ -247,12 +247,12 @@ - + - + @@ -270,11 +270,11 @@ - + - + @@ -282,7 +282,7 @@