diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 702fb53dc39b..3cb208ae8136 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,4 +18,5 @@ bitrise.yml @mozilla-mobile/fxios-automation-1 .taskcluster.yml @mozilla-mobile/fxios-automation-1 /taskcluster @mozilla-mobile/fxios-automation-1 .github/workflows @mozilla-mobile/fxios-automation-1 -/test-fixtures @mozilla-mobile/fxios-automation-1 \ No newline at end of file +/test-fixtures @mozilla-mobile/fxios-automation-1 +/test-fixtures/generate-metrics.sh @mozilla-mobile/fxios-eng \ No newline at end of file diff --git a/.github/workflows/firefox-check-rust-component-dependency.yml b/.github/workflows/firefox-check-rust-component-dependency.yml index 911921fd7a6f..0cf6b928cba0 100644 --- a/.github/workflows/firefox-check-rust-component-dependency.yml +++ b/.github/workflows/firefox-check-rust-component-dependency.yml @@ -20,43 +20,64 @@ jobs: with: persist-credentials: false token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r ./test-fixtures/requirements.txt + - name: Modifing Swift Package dependencies run: | python ./test-fixtures/update-rust-component-version.py + - name: Get new A-S tag to be used in the PR info run: | cd test-fixtures/ chmod u+x read-rust-component-tag.sh - echo "version=$(./read-rust-component-tag.sh)" >> $GITHUB_ENV + echo "rust_version=$(./read-rust-component-tag.sh)" >> $GITHUB_ENV + - name: Remove temp file created to store the tag info run: | cd test-fixtures/ [ ! -e newest_tag.txt ] || rm newest_tag.txt + - name: Script to check if branch exists to not commit again run: |- - branch=$(curl -X GET -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/mozilla-mobile/firefox-ios/branches?per_page=100 | jq -r '.[].name | select(contains("update-spm-new-rust-component-tag-${{ env.version }}"))') + branch=$(curl -X GET -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/mozilla-mobile/firefox-ios/branches?per_page=100 | jq -r '.[].name | select(contains("update-spm-new-rust-component-tag-${{ env.rust_version }}"))') echo $branch if [ -z "$branch" ]; then echo "BRANCH_CREATED=false" >> $GITHUB_ENV; else echo "BRANCH_CREATED=true" >> $GITHUB_ENV;fi + + - name: Determine PR Version Number + id: versioning + run: | + # This step is used to determine the next version number for the PR title + # The output includes debugging for the piped commands that generate + # the version number and the last line is the version number itself + + output=$(bash test-fixtures/ci/get-next-pr-version) + echo "$output" + next_version=$(echo "$output" | tail -n 1) # get the last line of the output + echo "Next Firefox iOS version is: v${next_version}" + echo "next_app_version=${next_version}" >> $GITHUB_ENV + - name: Update rust-component version if: env.BRANCH_CREATED == 'false' run: |- git diff - git diff --quiet || (git add firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved firefox-ios/Client.xcodeproj/project.pbxproj) + git diff --quiet || (git add firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved firefox-ios/Client.xcodeproj/project.pbxproj) + - name: Create Pull Request uses: peter-evans/create-pull-request@v3 if: env.BRANCH_CREATED == 'false' with: - commit-message: Auto update SPM with latest rust-component release ${{ env.version }} + commit-message: Auto update SPM with latest rust-component release ${{ env.rust_version }} author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> committer: GitHub - title: Refactor [vXXX] Auto update SPM with latest rust-component ${{ env.version }} - branch: update-spm-new-rust-component-tag-${{ env.version }} + title: Refactor [v${{ env.next_app_version }}] Auto update SPM with latest rust-component ${{ env.rust_version }} + branch: update-spm-new-rust-component-tag-${{ env.rust_version }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/firefox-ios-import-strings.yml b/.github/workflows/firefox-ios-import-strings.yml index e35cf8eae2f9..a8f7503989de 100644 --- a/.github/workflows/firefox-ios-import-strings.yml +++ b/.github/workflows/firefox-ios-import-strings.yml @@ -22,12 +22,28 @@ jobs: persist-credentials: false token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.inputs.branchName }} + - name: Select Xcode ${{ matrix.xcode }} run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + + - name: Determine PR Version Number + id: versioning + run: | + # This step is used to determine the next version number for the PR title + # The output includes debugging for the piped commands that generate + # the version number and the last line is the version number itself + + output=$(bash test-fixtures/ci/get-next-pr-version) + echo "$output" + next_version=$(echo "$output" | tail -n 1) # get the last line of the output + echo "Next version is: v${next_version}" + echo "next_version=${next_version}" >> $GITHUB_ENV + - name: Get PR info run: | current_date=$(date +"%Y-%m-%d") @@ -36,7 +52,7 @@ jobs: echo "current_date=$current_date" >> $GITHUB_ENV if [[ $current_branch == 'main' ]]; then echo "branch_name=string-import-$current_date" >> $GITHUB_ENV - echo "pr_title=Localize [ver] String import $current_date" >> $GITHUB_ENV + echo "pr_title=Localize [v${{ env.next_version }}] String import $current_date" >> $GITHUB_ENV echo "pr_body=This automated PR imports string changes" >> $GITHUB_ENV else # version: v105.0 -> v105 @@ -45,12 +61,15 @@ jobs: echo "pr_title=Localize [$version] String import $current_date" >> $GITHUB_ENV echo "pr_body=This automated PR imports string changes into branch '$current_branch'" >> $GITHUB_ENV fi + - name: Run script to import strings run: sh ./bootstrap.sh --importLocales + - name: Update new strings run: |- git diff || (git add firefox-ios/Shared/*/*.lproj/* firefox-ios/Shared/*.lproj/* firefox-ios/WidgetKit/*.lproj/* firefox-ios/Client/*/*.lproj/* firefox-ios/Client/*.lproj/*) git restore firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved + - name: Create Pull Request uses: peter-evans/create-pull-request@v5 with: @@ -58,6 +77,6 @@ jobs: committer: GitHub token: ${{ secrets.GITHUB_TOKEN }} commit-message: ${{ env.pr_title }} - title: ${{ env.pr_title }} + title: "Localize [v${{ env.next_version }}] String import ${{ env.current_date }}" branch: ${{ env.branch_name }} body: ${{ env.pr_body }} diff --git a/.github/workflows/firefox-ios-update-effective-tld-names-file.yml b/.github/workflows/firefox-ios-update-effective-tld-names-file.yml index 3a0f8b385067..8a9eba01c71f 100644 --- a/.github/workflows/firefox-ios-update-effective-tld-names-file.yml +++ b/.github/workflows/firefox-ios-update-effective-tld-names-file.yml @@ -43,14 +43,28 @@ jobs: echo "create_pr=true" >> $GITHUB_ENV cp tmp.dat firefox-ios/Shared/effective_tld_names.dat echo "branch_name=refactor-update-effective-tld-names" >> $GITHUB_ENV - echo "pr_title=Refactor [vXXX] Update effective_tld_names file $current_date" >> $GITHUB_ENV echo "pr_body=This automated PR updates the effective_tld_names file" >> $GITHUB_ENV rm tmp.dat fi + + - name: Determine PR Version Number + id: versioning + run: | + # This step is used to determine the next version number for the PR title + # The output includes debugging for the piped commands that generate + # the version number and the last line is the version number itself + + output=$(bash test-fixtures/ci/get-next-pr-version) + echo "$output" + next_version=$(echo "$output" | tail -n 1) # get the last line of the output + echo "Next version is: v${next_version}" + echo "next_version=${next_version}" >> $GITHUB_ENV + - name: Add Modified File to PR if: ${{ env.create_pr }} run: |- git diff || (git add firefox-ios/Shared/effective_tld_names.dat) + - name: Create Pull Request if: ${{ env.create_pr }} uses: peter-evans/create-pull-request@v3 @@ -59,6 +73,6 @@ jobs: committer: GitHub token: ${{ secrets.GITHUB_TOKEN }} commit-message: ${{ env.pr_title }} - title: ${{ env.pr_title }} + title: "Refactor [v${{ env.next_version }}] Update effective_tld_names file ${{ env.current_date }}" branch: ${{ env.branch_name }} body: ${{ env.pr_body }} diff --git a/.github/workflows/firefox-ios-update-uri.yml b/.github/workflows/firefox-ios-update-uri.yml new file mode 100644 index 000000000000..8ed617db2353 --- /dev/null +++ b/.github/workflows/firefox-ios-update-uri.yml @@ -0,0 +1,76 @@ +name: Update URIs and Create PR +on: + schedule: + - cron: '0 0 1 * *' # Runs at 00:00 on the first day of every month + workflow_dispatch: + inputs: + branchName: + description: 'Branch used as target for automation' + required: true + default: 'main' +jobs: + build: + runs-on: ubuntu-latest # using ubuntu as a lightweight environment + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ github.event.inputs.branchName }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # include pyarrow for pandas 3.0 compatibility + pip install pandas pyarrow requests + + - name: Run URI update script + run: python test-fixtures/ci/uri_update.py + + - name: Check for changes + id: git-check + run: | + if git diff --quiet; then + echo "No changes detected, skipping PR creation." + echo "changes_detected=false" >> $GITHUB_ENV + else + echo "Changes detected, proceeding to create PR." + git diff + git config --global user.name 'github-actions[bot]' + git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit --allow-empty -m "empty commit" # add empty commit so when squashed the history will show PR title + git add firefox-ios/Shared/Extensions/URLExtensions.swift + echo "changes_detected=true" >> $GITHUB_ENV + datetime=$(date +%Y%m%d%H%M%S) + echo "date=$datetime" >> $GITHUB_ENV + fi + + - name: Determine PR Version Number + id: versioning + run: | + # This step is used to determine the next version number for the PR title + # The output includes debugging for the piped commands that generate + # the version number and the last line is the version number itself + + output=$(bash test-fixtures/ci/get-next-pr-version) + echo "$output" + next_version=$(echo "$output" | tail -n 1) # get the last line of the output + echo "Next version is: v${next_version}" + echo "next_version=${next_version}" >> $GITHUB_ENV + + - name: Create Pull Request + if: env.changes_detected == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Automated URI update on ${{ github.event.inputs.branchName || 'main' }} ${{ env.date }} + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + committer: GitHub + title: Refactor [v${{ env.next_version }}] Automated URI update on ${{ github.event.inputs.branchName || 'main' }} ${{ env.date }} + branch: update-uri-schemes-${{ env.date }} + body: This automated PR updates the URIs. diff --git a/BrowserKit/Sources/WebEngine/EngineSession.swift b/BrowserKit/Sources/WebEngine/EngineSession.swift index f25eba4fe589..f5be2b45bbc0 100644 --- a/BrowserKit/Sources/WebEngine/EngineSession.swift +++ b/BrowserKit/Sources/WebEngine/EngineSession.swift @@ -7,6 +7,7 @@ import Foundation /// Protocol representing a single engine session. In browsers usually a session corresponds to a tab. public protocol EngineSession { var delegate: EngineSessionDelegate? { get set } + var findInPageDelegate: FindInPageHelperDelegate? { get set } /// The engine received a request to load a URL. /// - Parameter url: The URL string that is requested @@ -24,9 +25,18 @@ public protocol EngineSession { /// Navigates forward in the history of this session. func goForward() - /// Scroll the session to the top + /// Scroll the session to the top. func scrollToTop() + /// Find a text selection in this session. + /// - Parameters: + /// - text: The text to search + /// - function: The functionality the find in page should search with + func findInPage(text: String, function: FindInPageFunction) + + /// Called whenever the find in page session should be ended. + func findInPageDone() + /// Navigates to the specified index in the history of this session. The current index of /// this session's history will be updated but the items within it will be unchanged. /// Invalid index values are ignored. diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift new file mode 100644 index 000000000000..19518c9ad0de --- /dev/null +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Common +import Foundation +import WebKit + +public enum FindInPageFunction: String { + /// Find all the occurences of this text in the page + case find + + /// Find the next occurence of this text in the page + case findNext + + /// Find the previous occurence of this text in the page + case findPrevious +} + +public protocol FindInPageHelperDelegate: AnyObject { + func findInPageHelper(didUpdateCurrentResult currentResult: Int) + func findInPageHelper(didUpdateTotalResults totalResults: Int) +} + +class FindInPageContentScript: WKContentScript { + weak var delegate: FindInPageHelperDelegate? + private var logger: Logger + + init(logger: Logger = DefaultLogger.shared) { + self.logger = logger + } + + class func name() -> String { + return "FindInPage" + } + + func scriptMessageHandlerNames() -> [String] { + return ["findInPageHandler"] + } + + func userContentController( + didReceiveMessage message: Any + ) { + guard let parameters = message as? [String: Int] else { + // TODO: FXIOS-6463 - Integrate message handler check + logger.log("FindInPage.js sent wrong type of message", + level: .warning, + category: .webview) + return + } + + if let currentResult = parameters["currentResult"] { + delegate?.findInPageHelper(didUpdateCurrentResult: currentResult) + } + + if let totalResults = parameters["totalResults"] { + delegate?.findInPageHelper(didUpdateTotalResults: totalResults) + } + } +} diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift index c392e341ad50..20606c083c38 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift @@ -12,10 +12,7 @@ protocol WKContentScript { func scriptMessageHandlerNames() -> [String] - func userContentController( - _ userContentController: WKUserContentController, - didReceiveScriptMessage message: WKScriptMessage - ) + func userContentController(didReceiveMessage message: Any) func prepareForDeinit() } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift index be2dcf3199b2..ed8d26359e3f 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift @@ -7,6 +7,8 @@ import WebKit /// Manager used to add and remove scripts inside a WKEngineSession protocol WKContentScriptManager: WKScriptMessageHandler { + var scripts: [String: WKContentScript] { get } + func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) @@ -19,12 +21,12 @@ protocol WKContentScriptManager: WKScriptMessageHandler { } class DefaultContentScriptManager: NSObject, WKContentScriptManager { - var scripts = [String: WKContentScript]() + private(set) var scripts = [String: WKContentScript]() func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { - // If a script already exists on a tab, skip adding this duplicate. + // If a script already exists on a session, skip adding this duplicate. guard scripts[name] == nil else { return } scripts[name] = script @@ -58,7 +60,7 @@ class DefaultContentScriptManager: NSObject, WKContentScriptManager { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { for script in scripts.values where script.scriptMessageHandlerNames().contains(message.name) { - script.userContentController(userContentController, didReceiveScriptMessage: message) + script.userContentController(didReceiveMessage: message.body) return } } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index ddce0e223236..cce854a80988 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -12,6 +12,14 @@ class WKEngineSession: NSObject, WKNavigationDelegate, WKEngineWebViewDelegate { weak var delegate: EngineSessionDelegate? + weak var findInPageDelegate: FindInPageHelperDelegate? { + didSet { + let script = contentScriptManager.scripts[FindInPageContentScript.name()] + guard let findInPage = script as? FindInPageContentScript else { return } + findInPage.delegate = findInPageDelegate + } + } + private(set) var webView: WKEngineWebView private var logger: Logger var sessionData: WKEngineSessionData @@ -45,6 +53,7 @@ class WKEngineSession: NSObject, webView.navigationDelegate = self webView.delegate = self userScriptManager.injectUserScriptsIntoWebView(webView) + addContentScripts() } // TODO: FXIOS-7903 #17648 no return from this load(url:), we need a way to recordNavigationInTab @@ -116,6 +125,15 @@ class WKEngineSession: NSObject, webView.engineScrollView.setContentOffset(CGPoint.zero, animated: true) } + func findInPage(text: String, function: FindInPageFunction) { + let sanitizedInput = text.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + webView.evaluateJavascriptInDefaultContentWorld("__firefox__.\(function.rawValue)(\"\(sanitizedInput)\")") + } + + func findInPageDone() { + webView.evaluateJavascriptInDefaultContentWorld("__firefox__.findDone()") + } + func goToHistory(index: Int) { // TODO: FXIOS-7907 #17651 Handle goToHistoryIndex in WKEngineSession (equivalent to goToBackForwardListItem) } @@ -221,6 +239,14 @@ class WKEngineSession: NSObject, } } + // MARK: - Content scripts + + private func addContentScripts() { + contentScriptManager.addContentScript(FindInPageContentScript(), + name: FindInPageContentScript.name(), + forSession: self) + } + // MARK: - WKUIDelegate func webView(_ webView: WKWebView, diff --git a/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift b/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift new file mode 100644 index 000000000000..2b6675d94761 --- /dev/null +++ b/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift @@ -0,0 +1,78 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import XCTest +@testable import WebEngine + +final class FindInPageContentScriptTests: XCTestCase { + private var findInPageDelegate: MockFindInPageHelperDelegate! + + override func setUp() { + super.setUp() + findInPageDelegate = MockFindInPageHelperDelegate() + } + + override func tearDown() { + super.tearDown() + findInPageDelegate = nil + } + + func testDidReceiveMessageGivenEmptyMessageThenNoDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + + subject.userContentController(didReceiveMessage: []) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenStringMessageThenNoDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + + subject.userContentController(didReceiveMessage: ["": ""]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenCurrentResultMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let currentResult = 1 + + subject.userContentController(didReceiveMessage: ["currentResult": currentResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + XCTAssertEqual(findInPageDelegate.savedCurrentResult, currentResult) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenTotalResultMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let totalResult = 10 + + subject.userContentController(didReceiveMessage: ["totalResults": totalResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 1) + XCTAssertEqual(findInPageDelegate.savedTotalResults, totalResult) + } + + func testDidReceiveMessageGivenTotalAndCurrentResultsMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let totalResult = 15 + let currentResult = 20 + + subject.userContentController(didReceiveMessage: ["totalResults": totalResult, "currentResult": currentResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + XCTAssertEqual(findInPageDelegate.savedCurrentResult, currentResult) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 1) + XCTAssertEqual(findInPageDelegate.savedTotalResults, totalResult) + } +} diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift new file mode 100644 index 000000000000..f4d42b22c7d7 --- /dev/null +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +@testable import WebEngine + +class MockFindInPageHelperDelegate: FindInPageHelperDelegate { + var didUpdateCurrentResultCalled = 0 + var didUpdateTotalResultsCalled = 0 + var savedCurrentResult = 0 + var savedTotalResults = 0 + + func findInPageHelper(didUpdateCurrentResult currentResult: Int) { + savedCurrentResult = currentResult + didUpdateCurrentResultCalled += 1 + } + + func findInPageHelper(didUpdateTotalResults totalResults: Int) { + savedTotalResults = totalResults + didUpdateTotalResultsCalled += 1 + } +} diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift index 7c15a6b2a7b1..14edad32fb1c 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift @@ -3,7 +3,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Foundation -import WebKit @testable import WebEngine class MockWKContentScript: WKContentScript { @@ -20,8 +19,7 @@ class MockWKContentScript: WKContentScript { return ["MockWKContentScriptHandler"] } - func userContentController(_ userContentController: WKUserContentController, - didReceiveScriptMessage message: WKScriptMessage) { + func userContentController(didReceiveMessage message: Any) { userContentControllerCalled += 1 } diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift index 161ce93fa6ba..249db89909f4 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift @@ -7,20 +7,28 @@ import WebKit @testable import WebEngine class MockWKContentScriptManager: NSObject, WKContentScriptManager { + var scripts = [String: WKContentScript]() var addContentScriptCalled = 0 var addContentScriptToPageCalled = 0 var uninstallCalled = 0 var userContentControllerCalled = 0 + var savedContentScriptNames = [String]() + var savedContentScriptPageNames = [String]() + func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + scripts[name] = script + savedContentScriptNames.append(name) addContentScriptCalled += 1 } func addContentScriptToPage(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + scripts[name] = script + savedContentScriptPageNames.append(name) addContentScriptToPageCalled += 1 } diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift index 4fdc2741f238..d7adcc6c09de 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift @@ -36,6 +36,8 @@ class MockWKEngineWebView: UIView, WKEngineWebView { var removeFromSuperviewCalled = 0 var addObserverCalled = 0 var removeObserverCalled = 0 + var evaluateJavaScriptCalled = 0 + var savedJavaScript: String? var loadFileReadAccessURL: URL? @@ -105,4 +107,12 @@ class MockWKEngineWebView: UIView, WKEngineWebView { forKeyPath keyPath: String) { removeObserverCalled += 1 } + + func evaluateJavaScript(_ javaScript: String, + in frame: WKFrameInfo?, + in contentWorld: WKContentWorld, + completionHandler: ((Result) -> Void)?) { + evaluateJavaScriptCalled += 1 + savedJavaScript = javaScript + } } diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift index 9998ba6ab2a9..3146644e3c88 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift @@ -11,6 +11,7 @@ final class WKEngineSessionTests: XCTestCase { private var contentScriptManager: MockWKContentScriptManager! private var userScriptManager: MockWKUserScriptManager! private var engineSessionDelegate: MockEngineSessionDelegate! + private var findInPageDelegate: MockFindInPageHelperDelegate! override func setUp() { super.setUp() @@ -19,6 +20,7 @@ final class WKEngineSessionTests: XCTestCase { contentScriptManager = MockWKContentScriptManager() userScriptManager = MockWKUserScriptManager() engineSessionDelegate = MockEngineSessionDelegate() + findInPageDelegate = MockFindInPageHelperDelegate() } override func tearDown() { @@ -28,6 +30,7 @@ final class WKEngineSessionTests: XCTestCase { contentScriptManager = nil userScriptManager = nil engineSessionDelegate = nil + findInPageDelegate = nil } // MARK: Load URL @@ -134,6 +137,80 @@ final class WKEngineSessionTests: XCTestCase { prepareForTearDown(subject!) } + // MARK: Find in page + + func testFindInPageTextGivenFindAllThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"Banana\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenFindNextThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .findNext) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findNext(\"Banana\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenFindPreviousThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .findPrevious) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findPrevious(\"Banana\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenMaliciousAlertCodeThenIsSanitized() { + let subject = createSubject() + let maliciousTextWithAlert = "'; alert('Malicious code injected!'); '" + subject?.findInPage(text: maliciousTextWithAlert, function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + let result = "__firefox__.find(\"\'; alert(\'Malicious code injected!\'); \'\")" + XCTAssertEqual(webViewProvider.webView.savedJavaScript, result) + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenMaliciousBrokenJsStringCodeThenIsSanitized() { + let subject = createSubject() + let maliciousText = "; maliciousFunction(); " + subject?.findInPage(text: maliciousText, function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"; maliciousFunction(); \")") + prepareForTearDown(subject!) + } + + func testFindInPageDoneThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPageDone() + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findDone()") + prepareForTearDown(subject!) + } + + func testFindInPageDelegateIsSetProperly() { + let subject = createSubject() + + subject?.findInPageDelegate = findInPageDelegate + let script = contentScriptManager.scripts[FindInPageContentScript.name()] as! FindInPageContentScript + script.userContentController(didReceiveMessage: ["currentResult": 10]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + prepareForTearDown(subject!) + } + // MARK: Reload func testReloadThenCallsReloadFromOrigin() { @@ -407,6 +484,15 @@ final class WKEngineSessionTests: XCTestCase { // MARK: Content script manager + func testContentScriptGivenInitContentScriptsThenAreAddedAtInit() { + let subject = createSubject() + + XCTAssertEqual(contentScriptManager.addContentScriptCalled, 1) + XCTAssertEqual(contentScriptManager.savedContentScriptNames.count, 1) + XCTAssertEqual(contentScriptManager.savedContentScriptNames[0], FindInPageContentScript.name()) + prepareForTearDown(subject!) + } + func testContentScriptWhenCloseCalledThenUninstallIsCalled() { let subject = createSubject() diff --git a/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj b/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj index 6ed157d23f0c..de37815dbf61 100644 --- a/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj +++ b/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj @@ -37,6 +37,12 @@ 8A46021E2B0FE50D00FFD17F /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A46021D2B0FE50D00FFD17F /* UIView+Extension.swift */; }; 8A4602202B0FE52F00FFD17F /* UISearchbar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A46021F2B0FE52F00FFD17F /* UISearchbar+Extension.swift */; }; 8A4602222B0FE55300FFD17F /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4602212B0FE55300FFD17F /* UIViewController+Extension.swift */; }; + 8ADF72C62B73DB9700530E7A /* MainFrameAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */; }; + 8ADF72C72B73DB9700530E7A /* AllFramesAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */; }; + 8ADF72C82B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */; }; + 8ADF72C92B73DB9700530E7A /* AllFramesAtDocumentEnd.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */; }; + 8ADF72CA2B73DB9700530E7A /* MainFrameAtDocumentEnd.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */; }; + 8ADF72CD2B73DE6A00530E7A /* FindInPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */; }; 8AEBDD712B69A8C300FE9192 /* AppLaunchUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEBDD702B69A8C300FE9192 /* AppLaunchUtil.swift */; }; /* End PBXBuildFile section */ @@ -73,6 +79,12 @@ 8A46021D2B0FE50D00FFD17F /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 8A46021F2B0FE52F00FFD17F /* UISearchbar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchbar+Extension.swift"; sourceTree = ""; }; 8A4602212B0FE55300FFD17F /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; + 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MainFrameAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = AllFramesAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = WebcompatAllFramesAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = AllFramesAtDocumentEnd.js; sourceTree = ""; }; + 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MainFrameAtDocumentEnd.js; sourceTree = ""; }; + 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInPage.swift; sourceTree = ""; }; 8AEBDD702B69A8C300FE9192 /* AppLaunchUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchUtil.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -91,6 +103,7 @@ 8A0F44692B56EDC000438589 /* Engine */ = { isa = PBXGroup; children = ( + 8ADF72C02B73D6F600530E7A /* Scripts */, 8A0F446A2B56EDC900438589 /* EngineProvider.swift */, ); path = Engine; @@ -206,6 +219,7 @@ 8A4602152B0FE42300FFD17F /* Components */ = { isa = PBXGroup; children = ( + 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */, 8A4602162B0FE43A00FFD17F /* BrowserSearchBar.swift */, 8A4602182B0FE45200FFD17F /* BrowserToolbar.swift */, ); @@ -231,6 +245,18 @@ path = Browser; sourceTree = ""; }; + 8ADF72C02B73D6F600530E7A /* Scripts */ = { + isa = PBXGroup; + children = ( + 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */, + 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */, + 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */, + 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */, + 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */, + ); + path = Scripts; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -292,8 +318,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8ADF72C92B73DB9700530E7A /* AllFramesAtDocumentEnd.js in Resources */, + 8ADF72C82B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js in Resources */, 8A4601F12B0FE20700FFD17F /* LaunchScreen.storyboard in Resources */, 8A4601EE2B0FE20700FFD17F /* Assets.xcassets in Resources */, + 8ADF72C62B73DB9700530E7A /* MainFrameAtDocumentStart.js in Resources */, + 8ADF72C72B73DB9700530E7A /* AllFramesAtDocumentStart.js in Resources */, + 8ADF72CA2B73DB9700530E7A /* MainFrameAtDocumentEnd.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -321,6 +352,7 @@ 8A0F44852B56FFFC00438589 /* SearchTerm.swift in Sources */, 8A3DB32F2B5AD3D400F89705 /* SettingsCell.swift in Sources */, 8A0F44792B56FD6E00438589 /* SuggestionCellViewModel.swift in Sources */, + 8ADF72CD2B73DE6A00530E7A /* FindInPage.swift in Sources */, 8A3DB32D2B5AD3C700F89705 /* SettingsViewController.swift in Sources */, 8A4602192B0FE45200FFD17F /* BrowserToolbar.swift in Sources */, 8AEBDD712B69A8C300FE9192 /* AppLaunchUtil.swift in Sources */, diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json new file mode 100644 index 000000000000..4e1aac97fc56 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chevronDownLarge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf new file mode 100644 index 000000000000..ad97547171e0 Binary files /dev/null and b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf differ diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/Contents.json b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/Contents.json new file mode 100644 index 000000000000..fc3e4b9e2501 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chevronUpLarge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/chevronUpLarge.pdf b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/chevronUpLarge.pdf new file mode 100644 index 000000000000..237fc56b88e9 Binary files /dev/null and b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/chevronUpLarge.pdf differ diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/Contents.json b/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/Contents.json new file mode 100644 index 000000000000..3fc07befde46 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "crossMedium.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/crossMedium.pdf b/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/crossMedium.pdf new file mode 100644 index 000000000000..a5b98bb59ea4 Binary files /dev/null and b/SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/crossMedium.pdf differ diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentEnd.js b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentEnd.js new file mode 100644 index 000000000000..513e399ba4ab --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentEnd.js @@ -0,0 +1 @@ +(()=>{"use strict";window.__firefox__||Object.defineProperty(window,"__firefox__",{enumerable:!1,configurable:!1,writable:!1,value:{userScripts:{},includeOnce:function(e,t){return!!__firefox__.userScripts[e]||(__firefox__.userScripts[e]=!0,"function"==typeof t&&t(),!1)}}})})(),(()=>{function e(){let e={url:document.location.href,urls:t(),cookies:n()};webkit.messageHandlers.adsMessageHandler.postMessage(e)}function t(){let e=[],t=document.getElementsByTagName("a");for(let n of t)n.href&&e.push(n.href);return e}function n(){let e=document.cookie.split("; "),t=[];return e.forEach((e=>{var[n,...o]=e.split("=");o=o.join("="),t.push({name:n,value:o})})),t}const o=t=>{switch(t.type){case"load":e();break;case"pageshow":t.persisted&&e();break;default:console.log("Event:",t.type)}};["pageshow","load"].forEach((e=>window.addEventListener(e,o)))})(),(()=>{"use strict";window.__firefox__.includeOnce("ContextMenu",(function(){window.addEventListener("touchstart",(function(e){var t=e.target,n=t.closest("a"),o=t.closest("img");if(n||o){var s={};s.touchX=e.changedTouches[0].pageX-window.scrollX,s.touchY=e.changedTouches[0].pageY-window.scrollY,n&&(s.link=n.href,s.title=n.textContent),o&&(s.image=o.src,s.title=s.title||o.title,s.alt=o.alt),(s.link||s.image)&&webkit.messageHandlers.contextMenuMessageHandler.postMessage(s)}}),!0)}))})(),(()=>{"use strict";window.__firefox__.includeOnce("FocusHelper",(function(){const e=e=>{const t=e.type,n=e.target.nodeName;("INPUT"===n||"TEXTAREA"===n||e.target.isContentEditable)&&((e=>{if("INPUT"!==e.nodeName)return!1;const t=e.type.toUpperCase();return"BUTTON"==t||"SUBMIT"==t||"FILE"==t})(e.target)||webkit.messageHandlers.focusHelper.postMessage({eventType:t,elementType:n}))},t={capture:!0,passive:!0},n=window.document.body;["focus","blur"].forEach((o=>{n.addEventListener(o,e,t)}))}))})(),(()=>{"use strict";window.__firefox__.includeOnce("PrintHandler",(function(){window.print=function(){webkit.messageHandlers.printHandler.postMessage({})}}))})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js new file mode 100644 index 000000000000..69d3f26801c5 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={919:()=>{webkit.messageHandlers.trackingProtectionStats&&function(){let e=!0;Object.defineProperty(window.__firefox__,"TrackingProtectionStats",{enumerable:!1,configurable:!1,writable:!1,value:{}}),Object.defineProperty(window.__firefox__.TrackingProtectionStats,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(t,n){n===APP_ID_TOKEN&&t!==e&&(e=t,c(t))}});let t=new Array,n=null;function r(r){if(e){try{if(document.location.host===new URL(r).host)return}catch(e){}r&&t.push(r),n||(n=setTimeout((()=>{n=null,t.length<1||(webkit.messageHandlers.trackingProtectionStats.postMessage({urls:t}),t=new Array)}),200))}}function i(){[].slice.apply(document.scripts).forEach((function(e){r(e.src)})),[].slice.apply(document.images).forEach((function(e){r(e.src)})),[].slice.apply(document.getElementsByTagName("iframe")).forEach((function(e){r(e.src)}))}let o=null,s=null,a=null,l=null,u=null;function c(e){if(!e)return window.removeEventListener("load",i,!1),void(o&&(XMLHttpRequest.prototype.open=o,XMLHttpRequest.prototype.send=s,window.fetch=a,u.disconnect(),o=s=l=u=null));if(!o){window.addEventListener("load",i,!1),o||(o=XMLHttpRequest.prototype.open,s=XMLHttpRequest.prototype.send);var t=new WeakMap;new WeakMap,XMLHttpRequest.prototype.open=function(e,n){return t.set(this,n),o.apply(this,arguments)},XMLHttpRequest.prototype.send=function(e){return r(t.get(this)),s.apply(this,arguments)},a||(a=window.fetch),window.fetch=function(e,t){return"string"==typeof e?r(e):e instanceof Request&&r(e.url),a.apply(window,arguments)},l||(l=Object.getOwnPropertyDescriptor(Image.prototype,"src")),delete Image.prototype.src,Object.defineProperty(Image.prototype,"src",{get:function(){return l.get.call(this)},set:function(e){r(this.src),l.set.call(this,e)}}),u=new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){if("SCRIPT"===e.tagName&&e.src)r(e.src);else if("IMG"===e.tagName&&e.src)r(e.src);else if("IFRAME"===e.tagName&&e.src){if("about:blank"===e.src)return;r(e.src)}else"LINK"===e.tagName&&e.href&&r(e.href)}))}))})),u.observe(document.documentElement,{childList:!0,subtree:!0})}}c(!0)}()}},t={};window.__firefox__||Object.defineProperty(window,"__firefox__",{enumerable:!1,configurable:!1,writable:!1,value:{userScripts:{},includeOnce:function(e,t){return!!__firefox__.userScripts[e]||(__firefox__.userScripts[e]=!0,"function"==typeof t&&t(),!1)}}}),void 0===window.__firefox__.download&&Object.defineProperty(window.__firefox__,"download",{enumerable:!1,configurable:!1,writable:!1,value:function(e,t){if(t===APP_ID_TOKEN){if(e.startsWith("blob:")){var n=new XMLHttpRequest;return n.open("GET",e,!0),n.responseType="blob",n.onload=function(){if(200===this.status){var t=this.response;!function(e,t){var n=new FileReader;n.onloadend=function(){t(this.result.split(",")[1])},n.readAsDataURL(e)}(t,(function(n){webkit.messageHandlers.downloadManager.postMessage({url:e,mimeType:t.type,size:t.size,base64String:n})}))}},void n.send()}var r=document.createElement("a");r.href=e,r.dispatchEvent(new MouseEvent("click"))}}}),window.__firefox__.includeOnce("LoginsHelper",(function(){function e(e){}var t={_getRandomId:function(){return Math.round(Math.random()*(Number.MAX_VALUE-Number.MIN_VALUE)+Number.MIN_VALUE).toString()},_messages:["RemoteLogins:loginsFound"],_requests:{},_takeRequest:function(e){var t=e,n=this._requests[t.requestId];return this._requests[t.requestId]=void 0,n},_sendRequest:function(e,t){var n=this._getRandomId();t.requestId=n,webkit.messageHandlers.loginsManagerMessageHandler.postMessage(t);var r=this;return new Promise((function(t,i){e.promise={resolve:t,reject:i},r._requests[n]=e}))},receiveMessage:function(e){var t=this._takeRequest(e);switch(e.name){case"RemoteLogins:loginsFound":t.promise.resolve({form:t.form,loginsFound:e.logins});break;case"RemoteLogins:loginsAutoCompleted":t.promise.resolve(e.logins)}},_asyncFindLogins:function(e,t){var i=this._getFormFields(e,!1);if(!i[0]||!i[1])return Promise.reject("No logins found");i[0].addEventListener("blur",r);var o=n._getPasswordOrigin(e.ownerDocument.documentURI),s=n._getActionOrigin(e);if(null==s)return Promise.reject("Action origin is null");var a={form:e},l={type:"request",formOrigin:o,actionOrigin:s};return this._sendRequest(a,l)},loginsFound:function(e,t){this._fillForm(e,!0,!1,!1,!1,t)},onUsernameInput:function(t){var n=t.target;if(n.ownerDocument instanceof HTMLDocument&&this._isUsernameFieldType(n)){var r=n.form;if(r&&n.value){t.type;var[i,o,s]=this._getFormFields(r,!1);if(i==n&&o){var a=this;this._asyncFindLogins(r,{showMasterPassword:!1}).then((function(e){a._fillForm(e.form,!0,!0,!0,!0,e.loginsFound)})).then(null,e)}}}},_getPasswordFields:function(e,t){for(var n=[],r=0;r3?(n.length,null):n},_isUsernameFieldType:function(e){return e instanceof HTMLInputElement&&!!class{static inputTypeIsCompatibleWithUsername(e){const t=e.getAttribute("type")?.toLowerCase()||e.type;return["text","email","url","tel","number","search"].includes(t)||t?.includes("user")}static elementAttrsMatchRegex(e,t){if(t.test(e.id)||t.test(e.name)||t.test(e.className))return!0;const n=e.getAttribute("placeholder");return n&&t.test(n)}static hasLabelMatchingRegex(e,t){return t.test(e.labels?.[0]?.textContent)}}.inputTypeIsCompatibleWithUsername(e)},_getFormFields:function(e,t){var n,r,i=null,o=this._getPasswordFields(e,t);if(!o)return[null,null,null];for(var s=o[0].index-1;s>=0;s--){var a=e.elements[s];if(this._isUsernameFieldType(a)){i=a;break}}if(!t||1==o.length)return[i,o[0].element,null];var l=o[0].element.value,u=o[1].element.value,c=o[2]?o[2].element.value:null;if(3==o.length)if(l==u&&u==c)r=o[0].element,n=null;else if(l==u)r=o[0].element,n=o[2].element;else if(u==c)n=o[0].element,r=o[2].element;else{if(l!=c)return[null,null,null];r=o[0].element,n=o[1].element}else l==u?(r=o[0].element,n=null):(n=o[0].element,r=o[1].element);return[i,r,n]},_isAutocompleteDisabled:function(e){return!(!e||!e.hasAttribute("autocomplete")||"off"!=e.getAttribute("autocomplete").toLowerCase())},_onFormSubmit:function(e){var t=e.ownerDocument,r=t.defaultView,i=n._getPasswordOrigin(t.documentURI);if(i){var o=n._getActionOrigin(e),s=this._getFormFields(e,!0),a=s[0],l=s[1],u=s[2];if(null!=l){this._isAutocompleteDisabled(e)||this._isAutocompleteDisabled(a)||this._isAutocompleteDisabled(l)||this._isAutocompleteDisabled(u);var c=a?{name:a.name,value:a.value}:null,d={name:l.name,value:l.value};u&&(u.name,u.value),r.opener&&r.opener.top,webkit.messageHandlers.loginsManagerMessageHandler.postMessage({type:"submit",hostname:i,username:c.value,usernameField:c.name,password:d.value,passwordField:d.name,formSubmitUrl:o})}}},_fillForm:function(e,t,n,r,i,o){var s=this._getFormFields(e,!1),l=s[0],u=s[1];if(null==u)return[!1,o];if(u.disabled||u.readOnly)return[!1,o];var c=Number.MAX_VALUE,d=Number.MAX_VALUE;l&&l.maxLength>=0&&(c=l.maxLength),u.maxLength>=0&&(d=u.maxLength),o=function(e,t){var n,r,i;if(null==e)throw new TypeError("Array is null or not defined");var o=Object(e),s=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(arguments.length>1&&(n=e),r=new Array(s),i=0;i{var e={327:(e,t,n)=>{const{makeUrlAbsolute:r,parseUrl:o}=n(898);function i(e){return e.replace(/www[a-zA-Z0-9]*\./,"").replace(".co.",".").split(".").slice(0,-1).join(" ")}function l(e){return(t,n)=>{let r,o=0;for(let n=0;no&&(o=i,r=l(t))}}if(!r&&e.defaultValue&&(r=e.defaultValue(n)),r){if(e.processors)for(const t of e.processors)r=t(r,n);return r.trim&&(r=r.trim()),r}}}const a={description:{rules:[['meta[property="og:description"]',e=>e.getAttribute("content")],['meta[name="description" i]',e=>e.getAttribute("content")]]},icon:{rules:[['link[rel="apple-touch-icon"]',e=>e.getAttribute("href")],['link[rel="apple-touch-icon-precomposed"]',e=>e.getAttribute("href")],['link[rel="icon" i]',e=>e.getAttribute("href")],['link[rel="fluid-icon"]',e=>e.getAttribute("href")],['link[rel="shortcut icon"]',e=>e.getAttribute("href")],['link[rel="Shortcut Icon"]',e=>e.getAttribute("href")],['link[rel="mask-icon"]',e=>e.getAttribute("href")]],scorers:[(e,t)=>{const n=e.getAttribute("sizes");if(n){const e=n.match(/\d+/g);if(e)return e[0]}}],defaultValue:e=>"favicon.ico",processors:[(e,t)=>r(t.url,e)]},image:{rules:[['meta[property="og:image:secure_url"]',e=>e.getAttribute("content")],['meta[property="og:image:url"]',e=>e.getAttribute("content")],['meta[property="og:image"]',e=>e.getAttribute("content")],['meta[name="twitter:image"]',e=>e.getAttribute("content")],['meta[property="twitter:image"]',e=>e.getAttribute("content")],['meta[name="thumbnail"]',e=>e.getAttribute("content")]],processors:[(e,t)=>r(t.url,e)]},keywords:{rules:[['meta[name="keywords" i]',e=>e.getAttribute("content")]],processors:[(e,t)=>e.split(",").map((e=>e.trim()))]},title:{rules:[['meta[property="og:title"]',e=>e.getAttribute("content")],['meta[name="twitter:title"]',e=>e.getAttribute("content")],['meta[property="twitter:title"]',e=>e.getAttribute("content")],['meta[name="hdl"]',e=>e.getAttribute("content")],["title",e=>e.text]]},language:{rules:[["html[lang]",e=>e.getAttribute("lang")],['meta[name="language" i]',e=>e.getAttribute("content")]],processors:[(e,t)=>e.split("-")[0]]},type:{rules:[['meta[property="og:type"]',e=>e.getAttribute("content")]]},url:{rules:[["a.amp-canurl",e=>e.getAttribute("href")],['link[rel="canonical"]',e=>e.getAttribute("href")],['meta[property="og:url"]',e=>e.getAttribute("content")]],defaultValue:e=>e.url,processors:[(e,t)=>r(t.url,e)]},provider:{rules:[['meta[property="og:site_name"]',e=>e.getAttribute("content")]],defaultValue:e=>i(o(e.url))}};e.exports={buildRuleSet:l,getMetadata:function(e,t,n){const r={},o={url:t},i=n||a;return Object.keys(i).map((t=>{const n=l(i[t]);r[t]=n(e,o)})),r},getProvider:i,metadataRuleSets:a}},898:(e,t,n)=>{if(void 0!==n.g.URL)e.exports={makeUrlAbsolute:(e,t)=>new URL(t,e).href,parseUrl:e=>new URL(e).host};else{const t=n(327);e.exports={makeUrlAbsolute:(e,n)=>null===t.parse(n).host?t.resolve(e,n):n,parseUrl:e=>t.parse(e).hostname}}}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";Object.defineProperty(window.__firefox__,"searchQueryForField",{enumerable:!1,configurable:!1,writable:!1,value:function(){var e=document.activeElement;if("input"!==e.tagName.toLowerCase())return null;var t=e.form;if(!t||"get"!=t.method.toLowerCase())return null;var n=t.getElementsByTagName("input"),r=(n=Array.prototype.slice.call(n,0)).map((function(t){return t.name==e.name?[t.name,"{searchTerms}"].join("="):[t.name,t.value].map(encodeURIComponent).join("=")})),o=t.getElementsByTagName("select"),i=(o=Array.prototype.slice.call(o,0)).map((function(e){return[e.name,e.options[e.selectedIndex].value].map(encodeURIComponent).join("=")}));return r=r.concat(i),t.action?[t.action,r.join("&")].join("?"):null}})})(),(()=>{"use strict";const e=document.getElementById(APP_ID_TOKEN+"__firefox__visitOnce");null!=e&&e.addEventListener("click",(function(e){e.preventDefault(),webkit.messageHandlers.errorPageHelperMessageManager.postMessage({type:"certVisitOnce"})}))})(),(()=>{"use strict";const e="__firefox__find-highlight",t="__firefox__find-highlight-active",n=`.${e} {\n color: #000;\n background-color: #ffde49;\n border-radius: 1px;\n box-shadow: 0 0 0 2px #ffde49;\n transition: all 100ms ease 100ms;\n}\n.${e}.${t} {\n background-color: #f19750;\n box-shadow: 0 0 0 4px #f19750,0 1px 3px 3px rgba(0,0,0,.75);\n}`;var r="",o=null,i=null,l=-1,a=document.createElement("span");a.className=e;var c=document.createElement("style");function u(){i&&(l=(l+i.length+1)%i.length,f())}function s(){if(!i)return;let e,t=i;for(let n=0,r=t.length;n0){let e=u.substring(s,c.index);f.appendChild(document.createTextNode(e))}let n=a.cloneNode(!1);if(n.textContent=t,f.appendChild(n),r.push(n),s=e.lastIndex,d=!0,r.length>500){o=!0;break}}if(d){if(s{"use strict";const e=n(327);Object.defineProperty(window.__firefox__,"metadata",{enumerable:!1,configurable:!1,writable:!1,value:Object.freeze(new function(){this.getMetadata=function(){let t=e.getMetadata(document,document.URL);if(t.url=document.URL,!location.pathname.startsWith("/amp/"))return t;const n=document.querySelector("link[rel='canonical']");if(!n?.href)return t;try{new URL(n.href,location).protocol.match(/^https?:$/)&&(t.url=n.href)}catch(e){}return t}})})})()})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js new file mode 100644 index 000000000000..19f3822ab334 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js @@ -0,0 +1,2 @@ +/*! For license information please see MainFrameAtDocumentStart.js.LICENSE.txt */ +(()=>{var e={893:e=>{var t={unlikelyCandidates:/-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,okMaybeItsACandidate:/and|article|body|column|content|main|shadow/i};function i(e){return(!e.style||"none"!=e.style.display)&&!e.hasAttribute("hidden")&&(!e.hasAttribute("aria-hidden")||"true"!=e.getAttribute("aria-hidden")||e.className&&e.className.indexOf&&-1!==e.className.indexOf("fallback-image"))}e.exports=function(e,n={}){"function"==typeof n&&(n={visibilityChecker:n});var a={minScore:20,minContentLength:140,visibilityChecker:i};n=Object.assign(a,n);var r=e.querySelectorAll("p, pre, article"),o=e.querySelectorAll("div > br");if(o.length){var s=new Set(r);[].forEach.call(o,(function(e){s.add(e.parentNode)})),r=Array.from(s)}var l=0;return[].some.call(r,(function(e){if(!n.visibilityChecker(e))return!1;var i=e.className+" "+e.id;if(t.unlikelyCandidates.test(i)&&!t.okMaybeItsACandidate.test(i))return!1;if(e.matches("li p"))return!1;var a=e.textContent.trim().length;return!(an.minScore}))}},174:e=>{function t(e,t){if(t&&t.documentElement)e=t,t=arguments[2];else if(!e||!e.documentElement)throw new Error("First argument to Readability constructor should be a document object.");if(t=t||{},this._doc=e,this._docJSDOMParser=this._doc.firstChild.__JSDOMParser__,this._articleTitle=null,this._articleByline=null,this._articleDir=null,this._articleSiteName=null,this._attempts=[],this._debug=!!t.debug,this._maxElemsToParse=t.maxElemsToParse||this.DEFAULT_MAX_ELEMS_TO_PARSE,this._nbTopCandidates=t.nbTopCandidates||this.DEFAULT_N_TOP_CANDIDATES,this._charThreshold=t.charThreshold||this.DEFAULT_CHAR_THRESHOLD,this._classesToPreserve=this.CLASSES_TO_PRESERVE.concat(t.classesToPreserve||[]),this._keepClasses=!!t.keepClasses,this._serializer=t.serializer||function(e){return e.innerHTML},this._disableJSONLD=!!t.disableJSONLD,this._allowedVideoRegex=t.allowedVideoRegex||this.REGEXPS.videos,this._flags=this.FLAG_STRIP_UNLIKELYS|this.FLAG_WEIGHT_CLASSES|this.FLAG_CLEAN_CONDITIONALLY,this._debug){let e=function(e){if(e.nodeType==e.TEXT_NODE)return`${e.nodeName} ("${e.textContent}")`;let t=Array.from(e.attributes||[],(function(e){return`${e.name}="${e.value}"`})).join(" ");return`<${e.localName} ${t}>`};this.log=function(){if("undefined"!=typeof console){let t=Array.from(arguments,(t=>t&&t.nodeType==this.ELEMENT_NODE?e(t):t));t.unshift("Reader: (Readability)"),console.log.apply(console,t)}else if("undefined"!=typeof dump){var t=Array.prototype.map.call(arguments,(function(t){return t&&t.nodeName?e(t):t})).join(" ");dump("Reader: (Readability) "+t+"\n")}}}else this.log=function(){}}t.prototype={FLAG_STRIP_UNLIKELYS:1,FLAG_WEIGHT_CLASSES:2,FLAG_CLEAN_CONDITIONALLY:4,ELEMENT_NODE:1,TEXT_NODE:3,DEFAULT_MAX_ELEMS_TO_PARSE:0,DEFAULT_N_TOP_CANDIDATES:5,DEFAULT_TAGS_TO_SCORE:"section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),DEFAULT_CHAR_THRESHOLD:500,REGEXPS:{unlikelyCandidates:/-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,okMaybeItsACandidate:/and|article|body|column|content|main|shadow/i,positive:/article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,negative:/-ad-|hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,extraneous:/print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,byline:/byline|author|dateline|writtenby|p-author/i,replaceFonts:/<(\/?)font[^>]*>/gi,normalize:/\s{2,}/g,videos:/\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,shareElements:/(\b|_)(share|sharedaddy)(\b|_)/i,nextLink:/(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,prevLink:/(prev|earl|old|new|<|«)/i,tokenize:/\W+/g,whitespace:/^\s*$/,hasContent:/\S$/,hashUrl:/^#.+/,srcsetUrl:/(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g,b64DataUrl:/^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i,jsonLdArticleTypes:/^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/},UNLIKELY_ROLES:["menu","menubar","complementary","navigation","alert","alertdialog","dialog"],DIV_TO_P_ELEMS:new Set(["BLOCKQUOTE","DL","DIV","IMG","OL","P","PRE","TABLE","UL"]),ALTER_TO_DIV_EXCEPTIONS:["DIV","ARTICLE","SECTION","P"],PRESENTATIONAL_ATTRIBUTES:["align","background","bgcolor","border","cellpadding","cellspacing","frame","hspace","rules","style","valign","vspace"],DEPRECATED_SIZE_ATTRIBUTE_ELEMS:["TABLE","TH","TD","HR","PRE"],PHRASING_ELEMS:["ABBR","AUDIO","B","BDO","BR","BUTTON","CITE","CODE","DATA","DATALIST","DFN","EM","EMBED","I","IMG","INPUT","KBD","LABEL","MARK","MATH","METER","NOSCRIPT","OBJECT","OUTPUT","PROGRESS","Q","RUBY","SAMP","SCRIPT","SELECT","SMALL","SPAN","STRONG","SUB","SUP","TEXTAREA","TIME","VAR","WBR"],CLASSES_TO_PRESERVE:["page"],HTML_ESCAPE_MAP:{lt:"<",gt:">",amp:"&",quot:'"',apos:"'"},_postProcessContent:function(e){this._fixRelativeUris(e),this._simplifyNestedElements(e),this._keepClasses||this._cleanClasses(e)},_removeNodes:function(e,t){if(this._docJSDOMParser&&e._isLiveNodeList)throw new Error("Do not pass live node lists to _removeNodes");for(var i=e.length-1;i>=0;i--){var n=e[i],a=n.parentNode;a&&(t&&!t.call(this,n,i,e)||a.removeChild(n))}},_replaceNodeTags:function(e,t){if(this._docJSDOMParser&&e._isLiveNodeList)throw new Error("Do not pass live node lists to _replaceNodeTags");for(const i of e)this._setNodeTag(i,t)},_forEachNode:function(e,t){Array.prototype.forEach.call(e,t,this)},_findNode:function(e,t){return Array.prototype.find.call(e,t,this)},_someNode:function(e,t){return Array.prototype.some.call(e,t,this)},_everyNode:function(e,t){return Array.prototype.every.call(e,t,this)},_concatNodeLists:function(){var e=Array.prototype.slice,t=e.call(arguments),i=t.map((function(t){return e.call(t)}));return Array.prototype.concat.apply([],i)},_getAllNodesWithTag:function(e,t){return e.querySelectorAll?e.querySelectorAll(t.join(",")):[].concat.apply([],t.map((function(t){var i=e.getElementsByTagName(t);return Array.isArray(i)?i:Array.from(i)})))},_cleanClasses:function(e){var t=this._classesToPreserve,i=(e.getAttribute("class")||"").split(/\s+/).filter((function(e){return-1!=t.indexOf(e)})).join(" ");for(i?e.setAttribute("class",i):e.removeAttribute("class"),e=e.firstElementChild;e;e=e.nextElementSibling)this._cleanClasses(e)},_fixRelativeUris:function(e){var t=this._doc.baseURI,i=this._doc.documentURI;function n(e){if(t==i&&"#"==e.charAt(0))return e;try{return new URL(e,t).href}catch(e){}return e}var a=this._getAllNodesWithTag(e,["a"]);this._forEachNode(a,(function(e){var t=e.getAttribute("href");if(t)if(0===t.indexOf("javascript:"))if(1===e.childNodes.length&&e.childNodes[0].nodeType===this.TEXT_NODE){var i=this._doc.createTextNode(e.textContent);e.parentNode.replaceChild(i,e)}else{for(var a=this._doc.createElement("span");e.firstChild;)a.appendChild(e.firstChild);e.parentNode.replaceChild(a,e)}else e.setAttribute("href",n(t))}));var r=this._getAllNodesWithTag(e,["img","picture","figure","video","audio","source"]);this._forEachNode(r,(function(e){var t=e.getAttribute("src"),i=e.getAttribute("poster"),a=e.getAttribute("srcset");if(t&&e.setAttribute("src",n(t)),i&&e.setAttribute("poster",n(i)),a){var r=a.replace(this.REGEXPS.srcsetUrl,(function(e,t,i,a){return n(t)+(i||"")+a}));e.setAttribute("srcset",r)}}))},_simplifyNestedElements:function(e){for(var t=e;t;){if(t.parentNode&&["DIV","SECTION"].includes(t.tagName)&&(!t.id||!t.id.startsWith("readability"))){if(this._isElementWithoutContent(t)){t=this._removeAndGetNext(t);continue}if(this._hasSingleTagInsideElement(t,"DIV")||this._hasSingleTagInsideElement(t,"SECTION")){for(var i=t.children[0],n=0;n»] /.test(t))n=/ [\\\/>»] /.test(t),a(t=i.replace(/(.*)[\|\-\\\/>»] .*/gi,"$1"))<3&&(t=i.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi,"$1"));else if(-1!==t.indexOf(": ")){var r=this._concatNodeLists(e.getElementsByTagName("h1"),e.getElementsByTagName("h2")),o=t.trim();this._someNode(r,(function(e){return e.textContent.trim()===o}))||(a(t=i.substring(i.lastIndexOf(":")+1))<3?t=i.substring(i.indexOf(":")+1):a(i.substr(0,i.indexOf(":")))>5&&(t=i))}else if(t.length>150||t.length<15){var s=e.getElementsByTagName("h1");1===s.length&&(t=this._getInnerText(s[0]))}var l=a(t=t.trim().replace(this.REGEXPS.normalize," "));return l<=4&&(!n||l!=a(i.replace(/[\|\-\\\/>»]+/g,""))-1)&&(t=i),t},_prepDocument:function(){var e=this._doc;this._removeNodes(this._getAllNodesWithTag(e,["style"])),e.body&&this._replaceBrs(e.body),this._replaceNodeTags(this._getAllNodesWithTag(e,["font"]),"SPAN")},_nextNode:function(e){for(var t=e;t&&t.nodeType!=this.ELEMENT_NODE&&this.REGEXPS.whitespace.test(t.textContent);)t=t.nextSibling;return t},_replaceBrs:function(e){this._forEachNode(this._getAllNodesWithTag(e,["br"]),(function(e){for(var t=e.nextSibling,i=!1;(t=this._nextNode(t))&&"BR"==t.tagName;){i=!0;var n=t.nextSibling;t.parentNode.removeChild(t),t=n}if(i){var a=this._doc.createElement("p");for(e.parentNode.replaceChild(a,e),t=a.nextSibling;t;){if("BR"==t.tagName){var r=this._nextNode(t.nextSibling);if(r&&"BR"==r.tagName)break}if(!this._isPhrasingContent(t))break;var o=t.nextSibling;a.appendChild(t),t=o}for(;a.lastChild&&this._isWhitespace(a.lastChild);)a.removeChild(a.lastChild);"P"===a.parentNode.tagName&&this._setNodeTag(a.parentNode,"DIV")}}))},_setNodeTag:function(e,t){if(this.log("_setNodeTag",e,t),this._docJSDOMParser)return e.localName=t.toLowerCase(),e.tagName=t.toUpperCase(),e;for(var i=e.ownerDocument.createElement(t);e.firstChild;)i.appendChild(e.firstChild);e.parentNode.replaceChild(i,e),e.readability&&(i.readability=e.readability);for(var n=0;n!i.includes(e))).join(" ").length/n.join(" ").length:0},_checkByline:function(e,t){if(this._articleByline)return!1;if(void 0!==e.getAttribute)var i=e.getAttribute("rel"),n=e.getAttribute("itemprop");return!(!("author"===i||n&&-1!==n.indexOf("author")||this.REGEXPS.byline.test(t))||!this._isValidByline(e.textContent)||(this._articleByline=e.textContent.trim(),0))},_getNodeAncestors:function(e,t){t=t||0;for(var i=0,n=[];e.parentNode&&(n.push(e.parentNode),!t||++i!==t);)e=e.parentNode;return n},_grabArticle:function(e){this.log("**** grabArticle ****");var t=this._doc,i=null!==e;if(!(e=e||this._doc.body))return this.log("No body found in document. Abort."),null;for(var n=e.innerHTML;;){this.log("Starting grabArticle loop");var a=this._flagIsActive(this.FLAG_STRIP_UNLIKELYS),r=[],o=this._doc.documentElement;let V=!0;for(;o;){"HTML"===o.tagName&&(this._articleLang=o.getAttribute("lang"));var s=o.className+" "+o.id;if(this._isProbablyVisible(o))if("true"!=o.getAttribute("aria-modal")||"dialog"!=o.getAttribute("role"))if(this._checkByline(o,s))o=this._removeAndGetNext(o);else if(V&&this._headerDuplicatesTitle(o))this.log("Removing header: ",o.textContent.trim(),this._articleTitle.trim()),V=!1,o=this._removeAndGetNext(o);else{if(a){if(this.REGEXPS.unlikelyCandidates.test(s)&&!this.REGEXPS.okMaybeItsACandidate.test(s)&&!this._hasAncestorTag(o,"table")&&!this._hasAncestorTag(o,"code")&&"BODY"!==o.tagName&&"A"!==o.tagName){this.log("Removing unlikely candidate - "+s),o=this._removeAndGetNext(o);continue}if(this.UNLIKELY_ROLES.includes(o.getAttribute("role"))){this.log("Removing content with role "+o.getAttribute("role")+" - "+s),o=this._removeAndGetNext(o);continue}}if("DIV"!==o.tagName&&"SECTION"!==o.tagName&&"HEADER"!==o.tagName&&"H1"!==o.tagName&&"H2"!==o.tagName&&"H3"!==o.tagName&&"H4"!==o.tagName&&"H5"!==o.tagName&&"H6"!==o.tagName||!this._isElementWithoutContent(o)){if(-1!==this.DEFAULT_TAGS_TO_SCORE.indexOf(o.tagName)&&r.push(o),"DIV"===o.tagName){for(var l=null,c=o.firstChild;c;){var d=c.nextSibling;if(this._isPhrasingContent(c))null!==l?l.appendChild(c):this._isWhitespace(c)||(l=t.createElement("p"),o.replaceChild(l,c),l.appendChild(c));else if(null!==l){for(;l.lastChild&&this._isWhitespace(l.lastChild);)l.removeChild(l.lastChild);l=null}c=d}if(this._hasSingleTagInsideElement(o,"P")&&this._getLinkDensity(o)<.25){var h=o.children[0];o.parentNode.replaceChild(h,o),o=h,r.push(o)}else this._hasChildBlockElement(o)||(o=this._setNodeTag(o,"P"),r.push(o))}o=this._getNextNode(o)}else o=this._removeAndGetNext(o)}else o=this._removeAndGetNext(o);else this.log("Removing hidden node - "+s),o=this._removeAndGetNext(o)}var u=[];this._forEachNode(r,(function(e){if(e.parentNode&&void 0!==e.parentNode.tagName){var t=this._getInnerText(e);if(!(t.length<25)){var i=this._getNodeAncestors(e,5);if(0!==i.length){var n=0;n+=1,n+=t.split(",").length,n+=Math.min(Math.floor(t.length/100),3),this._forEachNode(i,(function(e,t){if(e.tagName&&e.parentNode&&void 0!==e.parentNode.tagName){if(void 0===e.readability&&(this._initializeNode(e),u.push(e)),0===t)var i=1;else i=1===t?2:3*t;e.readability.contentScore+=n/i}}))}}}}));for(var m=[],g=0,f=u.length;gN.readability.contentScore){m.splice(b,0,p),m.length>this._nbTopCandidates&&m.pop();break}}}var E,y=m[0]||null,T=!1;if(null===y||"BODY"===y.tagName){for(y=t.createElement("DIV"),T=!0;e.firstChild;)this.log("Moving child out:",e.firstChild),y.appendChild(e.firstChild);e.appendChild(y),this._initializeNode(y)}else if(y){for(var A=[],v=1;v=.75&&A.push(this._getNodeAncestors(m[v]));if(A.length>=3)for(E=y.parentNode;"BODY"!==E.tagName;){for(var S=0,C=0;C=3){y=E;break}E=E.parentNode}y.readability||this._initializeNode(y),E=y.parentNode;for(var x=y.readability.contentScore,L=x/3;"BODY"!==E.tagName;)if(E.readability){var w=E.readability.contentScore;if(wx){y=E;break}x=E.readability.contentScore,E=E.parentNode}else E=E.parentNode;for(E=y.parentNode;"BODY"!=E.tagName&&1==E.children.length;)E=(y=E).parentNode;y.readability||this._initializeNode(y)}var R=t.createElement("DIV");i&&(R.id="readability-content");for(var I=Math.max(10,.2*y.readability.contentScore),D=(E=y.parentNode).children,M=0,O=D.length;M=I)P=!0;else if("P"===k.nodeName){var B=this._getLinkDensity(k),U=this._getInnerText(k),G=U.length;(G>80&&B<.25||G<80&&G>0&&0===B&&-1!==U.search(/\.( |$)/))&&(P=!0)}}P&&(this.log("Appending node:",k),-1===this.ALTER_TO_DIV_EXCEPTIONS.indexOf(k.nodeName)&&(this.log("Altering sibling:",k,"to div."),k=this._setNodeTag(k,"DIV")),R.appendChild(k),D=E.children,M-=1,O-=1)}if(this._debug&&this.log("Article content pre-prep: "+R.innerHTML),this._prepArticle(R),this._debug&&this.log("Article content post-prep: "+R.innerHTML),T)y.id="readability-page-1",y.className="page";else{var F=t.createElement("DIV");for(F.id="readability-page-1",F.className="page";R.firstChild;)F.appendChild(R.firstChild);R.appendChild(F)}this._debug&&this.log("Article content after paging: "+R.innerHTML);var W=!0,z=this._getInnerText(R,!0).length;if(z0&&e.length<100},_unescapeHtmlEntities:function(e){if(!e)return e;var t=this.HTML_ESCAPE_MAP;return e.replace(/&(quot|amp|apos|lt|gt);/g,(function(e,i){return t[i]})).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi,(function(e,t,i){var n=parseInt(t||i,t?16:10);return String.fromCharCode(n)}))},_getJSONLD:function(e){var t,i=this._getAllNodesWithTag(e,["script"]);return this._forEachNode(i,(function(e){if(!t&&"application/ld+json"===e.getAttribute("type"))try{var i=e.textContent.replace(/^\s*\s*$/g,""),n=JSON.parse(i);if(!n["@context"]||!n["@context"].match(/^https?\:\/\/schema\.org$/))return;if(!n["@type"]&&Array.isArray(n["@graph"])&&(n=n["@graph"].find((function(e){return(e["@type"]||"").match(this.REGEXPS.jsonLdArticleTypes)}))),!n||!n["@type"]||!n["@type"].match(this.REGEXPS.jsonLdArticleTypes))return;if(t={},"string"==typeof n.name&&"string"==typeof n.headline&&n.name!==n.headline){var a=this._getArticleTitle(),r=this._textSimilarity(n.name,a)>.75,o=this._textSimilarity(n.headline,a)>.75;t.title=o&&!r?n.headline:n.name}else"string"==typeof n.name?t.title=n.name.trim():"string"==typeof n.headline&&(t.title=n.headline.trim());return n.author&&("string"==typeof n.author.name?t.byline=n.author.name.trim():Array.isArray(n.author)&&n.author[0]&&"string"==typeof n.author[0].name&&(t.byline=n.author.filter((function(e){return e&&"string"==typeof e.name})).map((function(e){return e.name.trim()})).join(", "))),"string"==typeof n.description&&(t.excerpt=n.description.trim()),void(n.publisher&&"string"==typeof n.publisher.name&&(t.siteName=n.publisher.name.trim()))}catch(e){this.log(e.message)}})),t||{}},_getArticleMetadata:function(e){var t={},i={},n=this._doc.getElementsByTagName("meta"),a=/\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi,r=/^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i;return this._forEachNode(n,(function(e){var t=e.getAttribute("name"),n=e.getAttribute("property"),o=e.getAttribute("content");if(o){var s=null,l=null;n&&(s=n.match(a))&&(l=s[0].toLowerCase().replace(/\s/g,""),i[l]=o.trim()),!s&&t&&r.test(t)&&(l=t,o&&(l=l.toLowerCase().replace(/\s/g,"").replace(/\./g,":"),i[l]=o.trim()))}})),t.title=e.title||i["dc:title"]||i["dcterm:title"]||i["og:title"]||i["weibo:article:title"]||i["weibo:webpage:title"]||i.title||i["twitter:title"],t.title||(t.title=this._getArticleTitle()),t.byline=e.byline||i["dc:creator"]||i["dcterm:creator"]||i.author,t.excerpt=e.excerpt||i["dc:description"]||i["dcterm:description"]||i["og:description"]||i["weibo:article:description"]||i["weibo:webpage:description"]||i.description||i["twitter:description"],t.siteName=e.siteName||i["og:site_name"],t.title=this._unescapeHtmlEntities(t.title),t.byline=this._unescapeHtmlEntities(t.byline),t.excerpt=this._unescapeHtmlEntities(t.excerpt),t.siteName=this._unescapeHtmlEntities(t.siteName),t},_isSingleImage:function(e){return"IMG"===e.tagName||1===e.children.length&&""===e.textContent.trim()&&this._isSingleImage(e.children[0])},_unwrapNoscriptImages:function(e){var t=Array.from(e.getElementsByTagName("img"));this._forEachNode(t,(function(e){for(var t=0;t0&&a>i)return!1;if(e.parentNode.tagName===t&&(!n||n(e.parentNode)))return!0;e=e.parentNode,a++}return!1},_getRowAndColumnCount:function(e){for(var t=0,i=0,n=e.getElementsByTagName("tr"),a=0;a0)n._readabilityDataTable=!0;else if(["col","colgroup","tfoot","thead","th"].some((function(e){return!!n.getElementsByTagName(e)[0]})))this.log("Data table because found data-y descendant"),n._readabilityDataTable=!0;else if(n.getElementsByTagName("table")[0])n._readabilityDataTable=!1;else{var r=this._getRowAndColumnCount(n);r.rows>=10||r.columns>4?n._readabilityDataTable=!0:n._readabilityDataTable=r.rows*r.columns>10}}else n._readabilityDataTable=!1;else n._readabilityDataTable=!1}},_fixLazyImages:function(e){this._forEachNode(this._getAllNodesWithTag(e,["img","picture","figure"]),(function(e){if(e.src&&this.REGEXPS.b64DataUrl.test(e.src)){if("image/svg+xml"===this.REGEXPS.b64DataUrl.exec(e.src)[1])return;for(var t=!1,i=0;in+=this._getInnerText(e,!0).length)),n/i},_cleanConditionally:function(e,t){this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)&&this._removeNodes(this._getAllNodesWithTag(e,[t]),(function(e){var i=function(e){return e._readabilityDataTable},n="ul"===t||"ol"===t;if(!n){var a=0,r=this._getAllNodesWithTag(e,["ul","ol"]);this._forEachNode(r,(e=>a+=this._getInnerText(e).length)),n=a/this._getInnerText(e).length>.9}if("table"===t&&i(e))return!1;if(this._hasAncestorTag(e,"table",-1,i))return!1;if(this._hasAncestorTag(e,"code"))return!1;var o=this._getClassWeight(e);if(this.log("Cleaning Conditionally",e),o+0<0)return!0;if(this._getCharCount(e,",")<10){for(var s=e.getElementsByTagName("p").length,l=e.getElementsByTagName("img").length,c=e.getElementsByTagName("li").length-100,d=e.getElementsByTagName("input").length,h=this._getTextDensity(e,["h1","h2","h3","h4","h5","h6"]),u=0,m=this._getAllNodesWithTag(e,["object","embed","iframe"]),g=0;g1&&s/l<.5&&!this._hasAncestorTag(e,"figure")||!n&&c>s||d>Math.floor(s/3)||!n&&h<.9&&_<25&&(0===l||l>2)&&!this._hasAncestorTag(e,"figure")||!n&&o<25&&p>.2||o>=25&&p>.5||1===u&&_<75||u>1;if(n&&b){for(var N=0;N1)return b;if(l==e.getElementsByTagName("li").length)return!1}return b}return!1}))},_cleanMatchedNodes:function(e,t){for(var i=this._getNextNode(e,!0),n=this._getNextNode(e);n&&n!=i;)n=t.call(this,n,n.className+" "+n.id)?this._removeAndGetNext(n):this._getNextNode(n)},_cleanHeaders:function(e){let t=this._getAllNodesWithTag(e,["h1","h2"]);this._removeNodes(t,(function(e){let t=this._getClassWeight(e)<0;return t&&this.log("Removing header with low class weight:",e),t}))},_headerDuplicatesTitle:function(e){if("H1"!=e.tagName&&"H2"!=e.tagName)return!1;var t=this._getInnerText(e,!1);return this.log("Evaluating similarity of header:",t,this._articleTitle),this._textSimilarity(this._articleTitle,t)>.75},_flagIsActive:function(e){return(this._flags&e)>0},_removeFlag:function(e){this._flags=this._flags&~e},_isProbablyVisible:function(e){return(!e.style||"none"!=e.style.display)&&!e.hasAttribute("hidden")&&(!e.hasAttribute("aria-hidden")||"true"!=e.getAttribute("aria-hidden")||e.className&&e.className.indexOf&&-1!==e.className.indexOf("fallback-image"))},parse:function(){if(this._maxElemsToParse>0){var e=this._doc.getElementsByTagName("*").length;if(e>this._maxElemsToParse)throw new Error("Aborting parsing document; "+e+" elements found")}this._unwrapNoscriptImages(this._doc);var t=this._disableJSONLD?{}:this._getJSONLD(this._doc);this._removeScripts(this._doc),this._prepDocument();var i=this._getArticleMetadata(t);this._articleTitle=i.title;var n=this._grabArticle();if(!n)return null;if(this.log("Grabbed: "+n.innerHTML),this._postProcessContent(n),!i.excerpt){var a=n.getElementsByTagName("p");a.length>0&&(i.excerpt=a[0].textContent.trim())}var r=n.textContent;return{title:this._articleTitle,byline:i.byline||this._articleByline,dir:this._articleDir,lang:this._articleLang,content:this._serializer(n),textContent:r,length:r.length,excerpt:i.excerpt,siteName:i.siteName||this._articleSiteName}}},e.exports=t},107:(e,t,i)=>{var n=i(174),a=i(893);e.exports={Readability:n,isProbablyReaderable:a}},856:function(e){e.exports=function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:i,getPrototypeOf:n,getOwnPropertyDescriptor:a}=Object;let{freeze:r,seal:o,create:s}=Object,{apply:l,construct:c}="undefined"!=typeof Reflect&&Reflect;r||(r=function(e){return e}),o||(o=function(e){return e}),l||(l=function(e,t,i){return e.apply(t,i)}),c||(c=function(e,t){return new e(...t)});const d=T(Array.prototype.forEach),h=T(Array.prototype.pop),u=T(Array.prototype.push),m=T(String.prototype.toLowerCase),g=T(String.prototype.toString),f=T(String.prototype.match),p=T(String.prototype.replace),_=T(String.prototype.indexOf),b=T(String.prototype.trim),N=T(RegExp.prototype.test),E=(y=TypeError,function(){for(var e=arguments.length,t=new Array(e),i=0;i1?i-1:0),a=1;a2&&void 0!==arguments[2]?arguments[2]:m;t&&t(e,null);let r=n.length;for(;r--;){let t=n[r];if("string"==typeof t){const e=a(t);e!==t&&(i(n)||(n[r]=e),t=e)}e[t]=!0}return e}function v(t){const i=s(null);for(const[n,r]of e(t))void 0!==a(t,n)&&(i[n]=r);return i}function S(e,t){for(;null!==e;){const i=a(e,t);if(i){if(i.get)return T(i.get);if("function"==typeof i.value)return T(i.value)}e=n(e)}return function(e){return console.warn("fallback value for",e),null}}const C=r(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),x=r(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),L=r(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),w=r(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),R=r(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),I=r(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),D=r(["#text"]),M=r(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),O=r(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),k=r(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),P=r(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),H=o(/\{\{[\w\W]*|[\w\W]*\}\}/gm),B=o(/<%[\w\W]*|[\w\W]*%>/gm),U=o(/\${[\w\W]*}/gm),G=o(/^data-[\-\w.\u00B7-\uFFFF]/),F=o(/^aria-[\-\w]+$/),W=o(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),z=o(/^(?:\w+script|data):/i),j=o(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),V=o(/^html$/i);var X=Object.freeze({__proto__:null,MUSTACHE_EXPR:H,ERB_EXPR:B,TMPLIT_EXPR:U,DATA_ATTR:G,ARIA_ATTR:F,IS_ALLOWED_URI:W,IS_SCRIPT_OR_DATA:z,ATTR_WHITESPACE:j,DOCTYPE_NAME:V});const Y=function(){return"undefined"==typeof window?null:window},$=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let i=null;const n="data-tt-policy-suffix";t&&t.hasAttribute(n)&&(i=t.getAttribute(n));const a="dompurify"+(i?"#"+i:"");try{return e.createPolicy(a,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};return function t(){let i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Y();const n=e=>t(e);if(n.version="3.0.6",n.removed=[],!i||!i.document||9!==i.document.nodeType)return n.isSupported=!1,n;let{document:a}=i;const o=a,l=o.currentScript,{DocumentFragment:c,HTMLTemplateElement:y,Node:T,Element:H,NodeFilter:B,NamedNodeMap:U=i.NamedNodeMap||i.MozNamedAttrMap,HTMLFormElement:G,DOMParser:F,trustedTypes:z}=i,j=H.prototype,q=S(j,"cloneNode"),K=S(j,"nextSibling"),J=S(j,"childNodes"),Z=S(j,"parentNode");if("function"==typeof y){const e=a.createElement("template");e.content&&e.content.ownerDocument&&(a=e.content.ownerDocument)}let Q,ee="";const{implementation:te,createNodeIterator:ie,createDocumentFragment:ne,getElementsByTagName:ae}=a,{importNode:re}=o;let oe={};n.isSupported="function"==typeof e&&"function"==typeof Z&&te&&void 0!==te.createHTMLDocument;const{MUSTACHE_EXPR:se,ERB_EXPR:le,TMPLIT_EXPR:ce,DATA_ATTR:de,ARIA_ATTR:he,IS_SCRIPT_OR_DATA:ue,ATTR_WHITESPACE:me}=X;let{IS_ALLOWED_URI:ge}=X,fe=null;const pe=A({},[...C,...x,...L,...R,...D]);let _e=null;const be=A({},[...M,...O,...k,...P]);let Ne=Object.seal(s(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ee=null,ye=null,Te=!0,Ae=!0,ve=!1,Se=!0,Ce=!1,xe=!1,Le=!1,we=!1,Re=!1,Ie=!1,De=!1,Me=!0,Oe=!1;const ke="user-content-";let Pe=!0,He=!1,Be={},Ue=null;const Ge=A({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Fe=null;const We=A({},["audio","video","img","source","image","track"]);let ze=null;const je=A({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",Xe="http://www.w3.org/2000/svg",Ye="http://www.w3.org/1999/xhtml";let $e=Ye,qe=!1,Ke=null;const Je=A({},[Ve,Xe,Ye],g);let Ze=null;const Qe=["application/xhtml+xml","text/html"],et="text/html";let tt=null,it=null;const nt=a.createElement("form"),at=function(e){return e instanceof RegExp||e instanceof Function},rt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!it||it!==e){if(e&&"object"==typeof e||(e={}),e=v(e),Ze=Ze=-1===Qe.indexOf(e.PARSER_MEDIA_TYPE)?et:e.PARSER_MEDIA_TYPE,tt="application/xhtml+xml"===Ze?g:m,fe="ALLOWED_TAGS"in e?A({},e.ALLOWED_TAGS,tt):pe,_e="ALLOWED_ATTR"in e?A({},e.ALLOWED_ATTR,tt):be,Ke="ALLOWED_NAMESPACES"in e?A({},e.ALLOWED_NAMESPACES,g):Je,ze="ADD_URI_SAFE_ATTR"in e?A(v(je),e.ADD_URI_SAFE_ATTR,tt):je,Fe="ADD_DATA_URI_TAGS"in e?A(v(We),e.ADD_DATA_URI_TAGS,tt):We,Ue="FORBID_CONTENTS"in e?A({},e.FORBID_CONTENTS,tt):Ge,Ee="FORBID_TAGS"in e?A({},e.FORBID_TAGS,tt):{},ye="FORBID_ATTR"in e?A({},e.FORBID_ATTR,tt):{},Be="USE_PROFILES"in e&&e.USE_PROFILES,Te=!1!==e.ALLOW_ARIA_ATTR,Ae=!1!==e.ALLOW_DATA_ATTR,ve=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Se=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Ce=e.SAFE_FOR_TEMPLATES||!1,xe=e.WHOLE_DOCUMENT||!1,Re=e.RETURN_DOM||!1,Ie=e.RETURN_DOM_FRAGMENT||!1,De=e.RETURN_TRUSTED_TYPE||!1,we=e.FORCE_BODY||!1,Me=!1!==e.SANITIZE_DOM,Oe=e.SANITIZE_NAMED_PROPS||!1,Pe=!1!==e.KEEP_CONTENT,He=e.IN_PLACE||!1,ge=e.ALLOWED_URI_REGEXP||W,$e=e.NAMESPACE||Ye,Ne=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&at(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ne.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&at(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ne.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ne.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Ce&&(Ae=!1),Ie&&(Re=!0),Be&&(fe=A({},[...D]),_e=[],!0===Be.html&&(A(fe,C),A(_e,M)),!0===Be.svg&&(A(fe,x),A(_e,O),A(_e,P)),!0===Be.svgFilters&&(A(fe,L),A(_e,O),A(_e,P)),!0===Be.mathMl&&(A(fe,R),A(_e,k),A(_e,P))),e.ADD_TAGS&&(fe===pe&&(fe=v(fe)),A(fe,e.ADD_TAGS,tt)),e.ADD_ATTR&&(_e===be&&(_e=v(_e)),A(_e,e.ADD_ATTR,tt)),e.ADD_URI_SAFE_ATTR&&A(ze,e.ADD_URI_SAFE_ATTR,tt),e.FORBID_CONTENTS&&(Ue===Ge&&(Ue=v(Ue)),A(Ue,e.FORBID_CONTENTS,tt)),Pe&&(fe["#text"]=!0),xe&&A(fe,["html","head","body"]),fe.table&&(A(fe,["tbody"]),delete Ee.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw E('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw E('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');Q=e.TRUSTED_TYPES_POLICY,ee=Q.createHTML("")}else void 0===Q&&(Q=$(z,l)),null!==Q&&"string"==typeof ee&&(ee=Q.createHTML(""));r&&r(e),it=e}},ot=A({},["mi","mo","mn","ms","mtext"]),st=A({},["foreignobject","desc","title","annotation-xml"]),lt=A({},["title","style","font","a","script"]),ct=A({},x);A(ct,L),A(ct,w);const dt=A({},R);A(dt,I);const ht=function(e){let t=Z(e);t&&t.tagName||(t={namespaceURI:$e,tagName:"template"});const i=m(e.tagName),n=m(t.tagName);return!!Ke[e.namespaceURI]&&(e.namespaceURI===Xe?t.namespaceURI===Ye?"svg"===i:t.namespaceURI===Ve?"svg"===i&&("annotation-xml"===n||ot[n]):Boolean(ct[i]):e.namespaceURI===Ve?t.namespaceURI===Ye?"math"===i:t.namespaceURI===Xe?"math"===i&&st[n]:Boolean(dt[i]):e.namespaceURI===Ye?!(t.namespaceURI===Xe&&!st[n])&&!(t.namespaceURI===Ve&&!ot[n])&&!dt[i]&&(lt[i]||!ct[i]):!("application/xhtml+xml"!==Ze||!Ke[e.namespaceURI]))},ut=function(e){u(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},mt=function(e,t){try{u(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){u(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!_e[e])if(Re||Ie)try{ut(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},gt=function(e){let t=null,i=null;if(we)e=""+e;else{const t=f(e,/^[\r\n\t ]+/);i=t&&t[0]}"application/xhtml+xml"===Ze&&$e===Ye&&(e=''+e+"");const n=Q?Q.createHTML(e):e;if($e===Ye)try{t=(new F).parseFromString(n,Ze)}catch(e){}if(!t||!t.documentElement){t=te.createDocument($e,"template",null);try{t.documentElement.innerHTML=qe?ee:n}catch(e){}}const r=t.body||t.documentElement;return e&&i&&r.insertBefore(a.createTextNode(i),r.childNodes[0]||null),$e===Ye?ae.call(t,xe?"html":"body")[0]:xe?t.documentElement:r},ft=function(e){return ie.call(e.ownerDocument||e,e,B.SHOW_ELEMENT|B.SHOW_COMMENT|B.SHOW_TEXT,null)},pt=function(e){return e instanceof G&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof U)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},_t=function(e){return"function"==typeof T&&e instanceof T},bt=function(e,t,i){oe[e]&&d(oe[e],(e=>{e.call(n,t,i,it)}))},Nt=function(e){let t=null;if(bt("beforeSanitizeElements",e,null),pt(e))return ut(e),!0;const i=tt(e.nodeName);if(bt("uponSanitizeElement",e,{tagName:i,allowedTags:fe}),e.hasChildNodes()&&!_t(e.firstElementChild)&&N(/<[/\w]/g,e.innerHTML)&&N(/<[/\w]/g,e.textContent))return ut(e),!0;if(!fe[i]||Ee[i]){if(!Ee[i]&&yt(i)){if(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,i))return!1;if(Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(i))return!1}if(Pe&&!Ue[i]){const t=Z(e)||e.parentNode,i=J(e)||e.childNodes;if(i&&t)for(let n=i.length-1;n>=0;--n)t.insertBefore(q(i[n],!0),K(e))}return ut(e),!0}return e instanceof H&&!ht(e)?(ut(e),!0):"noscript"!==i&&"noembed"!==i&&"noframes"!==i||!N(/<\/no(script|embed|frames)/i,e.innerHTML)?(Ce&&3===e.nodeType&&(t=e.textContent,d([se,le,ce],(e=>{t=p(t,e," ")})),e.textContent!==t&&(u(n.removed,{element:e.cloneNode()}),e.textContent=t)),bt("afterSanitizeElements",e,null),!1):(ut(e),!0)},Et=function(e,t,i){if(Me&&("id"===t||"name"===t)&&(i in a||i in nt))return!1;if(Ae&&!ye[t]&&N(de,t));else if(Te&&N(he,t));else if(!_e[t]||ye[t]){if(!(yt(e)&&(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,e)||Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(e))&&(Ne.attributeNameCheck instanceof RegExp&&N(Ne.attributeNameCheck,t)||Ne.attributeNameCheck instanceof Function&&Ne.attributeNameCheck(t))||"is"===t&&Ne.allowCustomizedBuiltInElements&&(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,i)||Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(i))))return!1}else if(ze[t]);else if(N(ge,p(i,me,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==_(i,"data:")||!Fe[e])if(ve&&!N(ue,p(i,me,"")));else if(i)return!1;return!0},yt=function(e){return e.indexOf("-")>0},Tt=function(e){bt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const i={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:_e};let a=t.length;for(;a--;){const r=t[a],{name:o,namespaceURI:s,value:l}=r,c=tt(o);let u="value"===o?l:b(l);if(i.attrName=c,i.attrValue=u,i.keepAttr=!0,i.forceKeepAttr=void 0,bt("uponSanitizeAttribute",e,i),u=i.attrValue,i.forceKeepAttr)continue;if(mt(o,e),!i.keepAttr)continue;if(!Se&&N(/\/>/i,u)){mt(o,e);continue}Ce&&d([se,le,ce],(e=>{u=p(u,e," ")}));const m=tt(e.nodeName);if(Et(m,c,u)){if(!Oe||"id"!==c&&"name"!==c||(mt(o,e),u=ke+u),Q&&"object"==typeof z&&"function"==typeof z.getAttributeType)if(s);else switch(z.getAttributeType(m,c)){case"TrustedHTML":u=Q.createHTML(u);break;case"TrustedScriptURL":u=Q.createScriptURL(u)}try{s?e.setAttributeNS(s,o,u):e.setAttribute(o,u),h(n.removed)}catch(e){}}}bt("afterSanitizeAttributes",e,null)},At=function e(t){let i=null;const n=ft(t);for(bt("beforeSanitizeShadowDOM",t,null);i=n.nextNode();)bt("uponSanitizeShadowNode",i,null),Nt(i)||(i.content instanceof c&&e(i.content),Tt(i));bt("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,a=null,r=null,s=null;if(qe=!e,qe&&(e="\x3c!--\x3e"),"string"!=typeof e&&!_t(e)){if("function"!=typeof e.toString)throw E("toString is not a function");if("string"!=typeof(e=e.toString()))throw E("dirty is not a string, aborting")}if(!n.isSupported)return e;if(Le||rt(t),n.removed=[],"string"==typeof e&&(He=!1),He){if(e.nodeName){const t=tt(e.nodeName);if(!fe[t]||Ee[t])throw E("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof T)i=gt("\x3c!----\x3e"),a=i.ownerDocument.importNode(e,!0),1===a.nodeType&&"BODY"===a.nodeName||"HTML"===a.nodeName?i=a:i.appendChild(a);else{if(!Re&&!Ce&&!xe&&-1===e.indexOf("<"))return Q&&De?Q.createHTML(e):e;if(i=gt(e),!i)return Re?null:De?ee:""}i&&we&&ut(i.firstChild);const l=ft(He?e:i);for(;r=l.nextNode();)Nt(r)||(r.content instanceof c&&At(r.content),Tt(r));if(He)return e;if(Re){if(Ie)for(s=ne.call(i.ownerDocument);i.firstChild;)s.appendChild(i.firstChild);else s=i;return(_e.shadowroot||_e.shadowrootmode)&&(s=re.call(o,s,!0)),s}let h=xe?i.outerHTML:i.innerHTML;return xe&&fe["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&N(V,i.ownerDocument.doctype.name)&&(h="\n"+h),Ce&&d([se,le,ce],(e=>{h=p(h,e," ")})),Q&&De?Q.createHTML(h):h},n.setConfig=function(){rt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Le=!0},n.clearConfig=function(){it=null,Le=!1},n.isValidAttribute=function(e,t,i){it||rt({});const n=tt(e),a=tt(t);return Et(n,a,i)},n.addHook=function(e,t){"function"==typeof t&&(oe[e]=oe[e]||[],u(oe[e],t))},n.removeHook=function(e){if(oe[e])return h(oe[e])},n.removeHooks=function(e){oe[e]&&(oe[e]=[])},n.removeAllHooks=function(){oe={}},n}()}()}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n].call(r.exports,r,r.exports,i),r.exports}(()=>{"use strict";Object.defineProperty(window.__firefox__,"NightMode",{enumerable:!1,configurable:!1,writable:!1,value:{enabled:!1}});const e="brightness(80%) invert(100%) hue-rotate(180deg)",t=`html {\n -webkit-filter: hue-rotate(180deg) invert(100%) !important;\n}\niframe,img,video {\n -webkit-filter: ${e} !important;\n}`;var i;function n(){return i||((i=document.createElement("style")).type="text/css",i.appendChild(document.createTextNode(t)),i)}function a(t){t.querySelectorAll('[style*="background"]').forEach((function(t){(t.style.backgroundImage||"").startsWith("url")&&function(t){o.push(t),t.__firefox__NightMode_originalFilter=t.style.webkitFilter,t.style.webkitFilter=e}(t)}))}function r(e){e.style.webkitFilter=e.__firefox__NightMode_originalFilter,delete e.__firefox__NightMode_originalFilter}var o=null,s=new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){e.nodeType===Node.ELEMENT_NODE&&a(e)}))}))}));Object.defineProperty(window.__firefox__.NightMode,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(e){if(e!==window.__firefox__.NightMode.enabled){window.__firefox__.NightMode.enabled=e;var t=n();if(e)return o=[],document.documentElement.appendChild(t),a(document),void s.observe(document.documentElement,{childList:!0,subtree:!0});s.disconnect(),o.forEach(r);var i=t.parentNode;i&&i.removeChild(t),o=null,"rgba(0, 0, 0, 0)"===getComputedStyle(document.documentElement)["background-color"]&&(document.documentElement.style.backgroundColor="#fff")}}}),window.addEventListener("DOMContentLoaded",(function(){window.__firefox__.NightMode.setEnabled(window.__firefox__.NightMode.enabled)}))})(),(()=>{"use strict";Object.defineProperty(window.__firefox__,"NoImageMode",{enumerable:!1,configurable:!1,writable:!1,value:{enabled:!1}});const e="__firefox__NoImageMode";Object.defineProperty(window.__firefox__.NoImageMode,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(t){if(t!==window.__firefox__.NoImageMode.enabled)if(window.__firefox__.NoImageMode.enabled=t,t)!function(){var t="*{background-image:none !important;}img,iframe{visibility:hidden !important;}",i=document.getElementById(e);if(i)i.innerHTML=t;else{var n=document.createElement("style");n.type="text/css",n.id=e,n.appendChild(document.createTextNode(t)),document.documentElement.appendChild(n)}}();else{var i=document.getElementById(e);i&&i.remove(),[].slice.apply(document.getElementsByTagName("img")).forEach((function(e){var t=e.src;e.src="",e.src=t})),[].slice.apply(document.querySelectorAll('[style*="background"]')).forEach((function(e){var t=e.style.backgroundImage;e.style.backgroundImage="none",e.style.backgroundImage=t})),[].slice.apply(document.styleSheets).forEach((function(e){[].slice.apply(e.rules||[]).forEach((function(e){var t=e.style;if(t){var i=t.backgroundImage;t.backgroundImage=i}}))}))}}}),window.addEventListener("DOMContentLoaded",(function(){window.__firefox__.NoImageMode.setEnabled(window.__firefox__.NoImageMode.enabled)}))})(),(()=>{"use strict";var e=null,t=null;const n=/^http:\/\/localhost:\d+\/reader-mode\/page/;function a(e){t&&t.theme&&document.body.classList.remove(t.theme),e&&e.theme&&document.body.classList.add(e.theme),t&&t.fontSize&&document.body.classList.remove("font-size"+t.fontSize),e&&e.fontSize&&document.body.classList.add("font-size"+e.fontSize),t&&t.fontType&&document.body.classList.remove(t.fontType),e&&e.fontType&&document.body.classList.add(e.fontType),t=e}function r(e){return"string"!=typeof e?"":e.replace(/\&/g,"&").replace(/\/g,">").replace(/\"/g,""").replace(/\'/g,"'")}Object.defineProperty(window.__firefox__,"reader",{enumerable:!1,configurable:!1,writable:!1,value:Object.freeze({checkReadability:function(){setTimeout((function(){if(document.location.href.match(n))webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Active"});else{if(("http:"===document.location.protocol||"https:"===document.location.protocol)&&"/"!==document.location.pathname){if(e&&e.content)return webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Available"}),void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderContentParsed",Value:e});var{Readability:t}=i(107),a={spec:document.location.href,host:document.location.host,prePath:document.location.protocol+"//"+document.location.host,scheme:document.location.protocol.substr(0,document.location.protocol.indexOf(":")),pathBase:document.location.protocol+"//"+document.location.host+location.pathname.substr(0,location.pathname.lastIndexOf("/")+1)},o=(new XMLSerializer).serializeToString(document);if(o.indexOf("-1)return void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"});const n=i(856).sanitize(o,{WHOLE_DOCUMENT:!0});var s=new t(a,(new DOMParser).parseFromString(n,"text/html"),{debug:!1});return(e=s.parse())?(e.title=r(e.title),e.byline=r(e.byline),webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:null!==e?"Available":"Unavailable"}),void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderContentParsed",Value:e})):void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"})}webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"})}}),100)},readerize:function(){return e},setStyle:a})}),window.addEventListener("load",(function(e){document.location.href.match(n)&&(a(JSON.parse(document.body.getAttribute("data-readerStyle"))),function(){var e=document.getElementById("reader-message");e&&(e.style.display="none");var t=document.getElementById("reader-header");t&&(t.style.display="block");var i=document.getElementById("reader-content");i&&(i.style.display="block")}(),function(){var e=document.getElementById("reader-content");if(e)for(var t=window.innerWidth,i=e.offsetWidth,n=t+"px !important",a=function(e){e._originalWidth||(e._originalWidth=e.offsetWidth);var a=e._originalWidth;a.55*t&&(a=t);var r=Math.max((i-t)/2,(i-a)/2)+"px !important",o="max-width: "+n+";width: "+a+"px !important;margin-left: "+r+";margin-right: "+r+";";e.style.cssText=o},r=document.querySelectorAll(".content p > img:only-child, .content p > a:only-child > img:only-child, .content .wp-caption img, .content figure img"),o=r.length;--o>=0;){var s=r[o];s.width>0?a(s):s.onload=function(){a(s)}}}())})),window.addEventListener("pageshow",(function(e){document.location.href.match(n)&&webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderPageEvent",Value:"PageShow"})}))})()})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js new file mode 100644 index 000000000000..34092295e7ff --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js @@ -0,0 +1 @@ +(()=>{var e=!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled),t=void 0!==HTMLVideoElement.prototype.webkitEnterFullscreen;e||!t||/mobile/i.test(navigator.userAgent)||(HTMLElement.prototype.requestFullscreen=function(){if(void 0!==this.webkitRequestFullscreen)return this.webkitRequestFullscreen(),!0;if(void 0!==this.webkitEnterFullscreen)return this.webkitEnterFullscreen(),!0;var e=this.querySelector("video");return void 0!==e&&(e.webkitEnterFullscreen(),!0)},Object.defineProperty(document,"fullscreenEnabled",{get:function(){return!0}}),Object.defineProperty(document.documentElement,"fullscreenEnabled",{get:function(){return!0}}))})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift b/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift index 2a3a24a07c1d..ab9a640a744c 100644 --- a/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift @@ -9,10 +9,16 @@ protocol NavigationDelegate: AnyObject { func onURLChange(url: String) func onLoadingStateChange(loading: Bool) func onNavigationStateChange(canGoBack: Bool, canGoForward: Bool) + + func onFindInPage(selected: String) + func onFindInPage(currentResult: Int) + func onFindInPage(totalResults: Int) } // Holds different type of browser views, communicating through protocols with them -class BrowserViewController: UIViewController, EngineSessionDelegate { +class BrowserViewController: UIViewController, + EngineSessionDelegate, + FindInPageHelperDelegate { weak var navigationDelegate: NavigationDelegate? private lazy var progressView: UIProgressView = .build { _ in } private var engineSession: EngineSession! @@ -27,6 +33,8 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { self.engineView = engineProvider.view self.urlFormatter = urlFormatter super.init(nibName: nil, bundle: nil) + + engineSession.findInPageDelegate = self } required init?(coder: NSCoder) { @@ -98,6 +106,14 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { engineSession.scrollToTop() } + func findInPage(text: String, function: FindInPageFunction) { + engineSession.findInPage(text: text, function: function) + } + + func findInPageDone() { + engineSession.findInPageDone() + } + // MARK: - Search func loadUrlOrSearch(_ searchTerm: SearchTerm) { @@ -145,10 +161,20 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { // MARK: - EngineSessionDelegate Menu items func findInPage(with selection: String) { - // FXIOS-8087: Handle find in page in WebEngine + navigationDelegate?.onFindInPage(selected: selection) } func search(with selection: String) { loadUrlOrSearch(SearchTerm(term: selection)) } + + // MARK: - FindInPageHelperDelegate + + func findInPageHelper(didUpdateCurrentResult currentResult: Int) { + navigationDelegate?.onFindInPage(currentResult: currentResult) + } + + func findInPageHelper(didUpdateTotalResults totalResults: Int) { + navigationDelegate?.onFindInPage(totalResults: totalResults) + } } diff --git a/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift b/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift new file mode 100644 index 000000000000..b0d5287888d7 --- /dev/null +++ b/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift @@ -0,0 +1,191 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Common +import UIKit + +protocol FindInPageBarDelegate: AnyObject { + func findInPage(_ findInPage: FindInPageBar, textChanged text: String) + func findInPage(_ findInPage: FindInPageBar, findPreviousWithText text: String) + func findInPage(_ findInPage: FindInPageBar, findNextWithText text: String) + func findInPageDidPressClose(_ findInPage: FindInPageBar) +} + +class FindInPageBar: UIView, UITextFieldDelegate, ThemeApplicable { + private struct UX { + static let fontSize: CGFloat = 16 + static let totalResultsMax = 500 + } + + weak var delegate: FindInPageBarDelegate? + + private lazy var topBorder: UIView = .build() + + private lazy var searchText: UITextField = .build { textField in + textField.addTarget(self, action: #selector(self.didTextChange), for: .editingChanged) + textField.font = DefaultDynamicFontHelper.preferredFont(withTextStyle: .callout, size: UX.fontSize) + textField.setContentHuggingPriority(.defaultLow, for: .horizontal) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.adjustsFontForContentSizeCategory = true + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.inputAssistantItem.leadingBarButtonGroups = [] + textField.inputAssistantItem.trailingBarButtonGroups = [] + textField.enablesReturnKeyAutomatically = true + textField.returnKeyType = .search + textField.delegate = self + } + + private lazy var matchCountView: UILabel = .build { label in + label.font = DefaultDynamicFontHelper.preferredFont(withTextStyle: .callout, size: UX.fontSize) + label.isHidden = true + label.setContentHuggingPriority(.defaultHigh, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + label.adjustsFontForContentSizeCategory = true + } + + private lazy var previousButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Large.chevronUp), for: .normal) + button.addTarget(self, action: #selector(self.didFindPrevious), for: .touchUpInside) + } + + private lazy var nextButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Large.chevronDown), for: .normal) + button.addTarget(self, action: #selector(self.didFindNext), for: .touchUpInside) + } + + private lazy var closeButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Medium.cross), for: .normal) + button.addTarget(self, action: #selector(self.didPressClose), for: .touchUpInside) + } + + var currentResult = 0 { + didSet { + if totalResults > UX.totalResultsMax { + matchCountView.text = "\(currentResult)/\(UX.totalResultsMax)+" + } else { + matchCountView.text = "\(currentResult)/\(totalResults)" + } + } + } + + var totalResults = 0 { + didSet { + if totalResults > UX.totalResultsMax { + matchCountView.text = "\(currentResult)/\(UX.totalResultsMax)+" + } else { + matchCountView.text = "\(currentResult)/\(totalResults)" + } + previousButton.isEnabled = totalResults > 1 + nextButton.isEnabled = previousButton.isEnabled + } + } + + var text: String? { + get { + return searchText.text + } + + set { + searchText.text = newValue + didTextChange(searchText) + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .white + + addSubviews(searchText, matchCountView, previousButton, nextButton, closeButton, topBorder) + + NSLayoutConstraint.activate([ + searchText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + searchText.topAnchor.constraint(equalTo: topAnchor), + searchText.bottomAnchor.constraint(equalTo: bottomAnchor), + + matchCountView.leadingAnchor.constraint(equalTo: searchText.trailingAnchor), + matchCountView.centerYAnchor.constraint(equalTo: centerYAnchor), + + previousButton.leadingAnchor.constraint(equalTo: matchCountView.trailingAnchor), + previousButton.widthAnchor.constraint(equalTo: heightAnchor), + previousButton.heightAnchor.constraint(equalTo: heightAnchor), + previousButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + nextButton.leadingAnchor.constraint(equalTo: previousButton.trailingAnchor), + nextButton.widthAnchor.constraint(equalTo: heightAnchor), + nextButton.heightAnchor.constraint(equalTo: heightAnchor), + nextButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + closeButton.leadingAnchor.constraint(equalTo: nextButton.trailingAnchor), + closeButton.widthAnchor.constraint(equalTo: heightAnchor), + closeButton.heightAnchor.constraint(equalTo: heightAnchor), + closeButton.trailingAnchor.constraint(equalTo: trailingAnchor), + closeButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + topBorder.heightAnchor.constraint(equalToConstant: 1), + topBorder.leadingAnchor.constraint(equalTo: leadingAnchor), + topBorder.trailingAnchor.constraint(equalTo: trailingAnchor), + topBorder.topAnchor.constraint(equalTo: topAnchor) + ]) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + searchText.becomeFirstResponder() + return super.becomeFirstResponder() + } + + @objc + private func didFindPrevious(_ sender: UIButton) { + delegate?.findInPage(self, findPreviousWithText: searchText.text ?? "") + } + + @objc + private func didFindNext(_ sender: UIButton) { + delegate?.findInPage(self, findNextWithText: searchText.text ?? "") + } + + @objc + private func didTextChange(_ sender: UITextField) { + matchCountView.isHidden = searchText.text?.trimmingCharacters(in: .whitespaces).isEmpty ?? true + delegate?.findInPage(self, textChanged: searchText.text ?? "") + } + + @objc + private func didPressClose(_ sender: UIButton) { + delegate?.findInPageDidPressClose(self) + } + + // MARK: - Theme Applicable + + func applyTheme(theme: Theme) { + let colors = theme.colors + topBorder.backgroundColor = colors.borderPrimary + searchText.textColor = theme.type == .light ? colors.textPrimary : colors.textInverted + matchCountView.textColor = colors.actionSecondary + previousButton.setTitleColor(colors.iconPrimary, for: .normal) + nextButton.setTitleColor(colors.iconPrimary, for: .normal) + closeButton.setTitleColor(colors.iconPrimary, for: .normal) + } + + // MARK: - UITextFieldDelegate + + // Keyboard with a .search returnKeyType doesn't dismiss when return pressed. Handle this manually. + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if string == "\n" { + textField.resignFirstResponder() + return false + } + return true + } +} diff --git a/SampleBrowser/SampleBrowser/UI/RootViewController.swift b/SampleBrowser/SampleBrowser/UI/RootViewController.swift index 0dcb6bc109f5..6ddde93686b5 100644 --- a/SampleBrowser/SampleBrowser/UI/RootViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/RootViewController.swift @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/ +import Common import UIKit // Holds toolbar, search bar, search and browser VCs @@ -11,7 +12,8 @@ class RootViewController: UIViewController, SearchBarDelegate, SearchSuggestionDelegate, MenuDelegate, - SettingsDelegate { + SettingsDelegate, + FindInPageBarDelegate { private lazy var toolbar: BrowserToolbar = .build { _ in } private lazy var searchBar: BrowserSearchBar = .build { _ in } private lazy var statusBarFiller: UIView = .build { view in @@ -20,6 +22,7 @@ class RootViewController: UIViewController, private var browserVC: BrowserViewController private var searchVC: SearchViewController + private var findInPageBar: FindInPageBar? // MARK: - Init @@ -148,6 +151,18 @@ class RootViewController: UIViewController, searchBar.setSearchBarText(url) } + func onFindInPage(selected: String) { + showFindInPage() + } + + func onFindInPage(currentResult: Int) { + findInPageBar?.currentResult = currentResult + } + + func onFindInPage(totalResults: Int) { + findInPageBar?.totalResults = totalResults + } + // MARK: - SearchBarDelegate func searchSuggestions(searchTerm: String) { @@ -191,4 +206,41 @@ class RootViewController: UIViewController, func scrollToTop() { browserVC.scrollToTop() } + + func showFindInPage() { + let findInPageBar = FindInPageBar() + findInPageBar.translatesAutoresizingMaskIntoConstraints = false + findInPageBar.delegate = self + self.findInPageBar = findInPageBar + + view.addSubview(findInPageBar) + + NSLayoutConstraint.activate([ + findInPageBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + findInPageBar.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + findInPageBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + findInPageBar.heightAnchor.constraint(equalToConstant: 46) + ]) + } + + // MARK: - FindInPageBarDelegate + + func findInPage(_ findInPage: FindInPageBar, textChanged text: String) { + browserVC.findInPage(text: text, function: .find) + } + + func findInPage(_ findInPage: FindInPageBar, findPreviousWithText text: String) { + browserVC.findInPage(text: text, function: .findPrevious) + } + + func findInPage(_ findInPage: FindInPageBar, findNextWithText text: String) { + browserVC.findInPage(text: text, function: .findNext) + } + + func findInPageDidPressClose(_ findInPage: FindInPageBar) { + browserVC.findInPageDone() + findInPageBar?.endEditing(true) + findInPageBar?.removeFromSuperview() + findInPageBar = nil + } } diff --git a/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift b/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift index ce844667521f..62d566cec4eb 100644 --- a/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift +++ b/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift @@ -6,4 +6,5 @@ import Foundation protocol SettingsDelegate: AnyObject { func scrollToTop() + func showFindInPage() } diff --git a/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift b/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift index 9cc7b57a4944..6a2d83d9f505 100644 --- a/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift @@ -84,7 +84,7 @@ class SettingsViewController: UIViewController, UITableViewDelegate { case .contentBlocking: break // TODO: FXIOS-8088 - Handle content blocking in WebEngine case .findInPage: - break // TODO: FXIOS-8087 - Handle find in page in WebEngine + delegate?.showFindInPage() case .scrollingToTop: delegate?.scrollToTop() case .zoom: diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 7421682814e8..4087da359a92 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -165,7 +165,6 @@ 21B548992B1E7FDF00DC1DF8 /* InactiveTabsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B548982B1E7FDF00DC1DF8 /* InactiveTabsManagerTests.swift */; }; 21BFEEF52A040EF40033048D /* TabMigrationUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BFEEF42A040EF40033048D /* TabMigrationUtility.swift */; }; 21BFEEF82A05A0370033048D /* TabMigrationUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21BFEEF62A05A0310033048D /* TabMigrationUtilityTests.swift */; }; - 21D0F62129B91BF500022292 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D0F62029B91BF500022292 /* ToastView.swift */; }; 21D151262AFC28960062D891 /* TabManagerMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D151252AFC28960062D891 /* TabManagerMiddleware.swift */; }; 21D7B60628077CA5003F7E94 /* LibraryViewController+ToolbarActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D7B60528077CA5003F7E94 /* LibraryViewController+ToolbarActions.swift */; }; 21D8843F2A7959D000AF144C /* HomePageSettingViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D8843E2A7959D000AF144C /* HomePageSettingViewControllerTests.swift */; }; @@ -192,6 +191,7 @@ 23D57E6E25ED6F2700883FAD /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D57E6D25ED6F2700883FAD /* SearchViewController.swift */; }; 23ED80FF25C89C9800D0E9D5 /* DefaultBrowserOnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ED80FE25C89C9800D0E9D5 /* DefaultBrowserOnboardingViewController.swift */; }; 253648E12B2111C100D5C2C5 /* SearchViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253648E02B2111C100D5C2C5 /* SearchViewControllerTests.swift */; }; + 254B760A2B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 254B76092B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift */; }; 274A36CC239EB99400A21587 /* LibraryPanelContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274A36CB239EB99400A21587 /* LibraryPanelContextMenu.swift */; }; 274A36CE239EB9EC00A21587 /* LibraryViewController+LibraryPanelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274A36CD239EB9EC00A21587 /* LibraryViewController+LibraryPanelDelegate.swift */; }; 2816F0001B33E05400522243 /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2816EFFF1B33E05400522243 /* UIConstants.swift */; }; @@ -970,6 +970,7 @@ BD57D9A729D4C42B00039394 /* ZoomLevelStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD57D9A629D4C42B00039394 /* ZoomLevelStoreTests.swift */; }; BD6B361E2B3C2511005E5345 /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6B361D2B3C2511005E5345 /* CircularProgressView.swift */; }; BD6CC84229CDDA3400546A5D /* ZoomLevelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6CC84129CDDA3400546A5D /* ZoomLevelStore.swift */; }; + C2200A6A2B7D148C00DC062A /* ContentBlockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2200A692B7D148C00DC062A /* ContentBlockerTests.swift */; }; C22753402A3C9E1300B9C0D1 /* WebsiteDataManagementViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C227533F2A3C9E1300B9C0D1 /* WebsiteDataManagementViewModel.swift */; }; C2296FCC2A601C190046ECA6 /* IntensityVisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2296FCB2A601C190046ECA6 /* IntensityVisualEffectView.swift */; }; C23889DF2A4EFCE500429673 /* ShareExtensionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23889DE2A4EFCE500429673 /* ShareExtensionCoordinator.swift */; }; @@ -2315,7 +2316,6 @@ 21B548982B1E7FDF00DC1DF8 /* InactiveTabsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InactiveTabsManagerTests.swift; sourceTree = ""; }; 21BFEEF42A040EF40033048D /* TabMigrationUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabMigrationUtility.swift; sourceTree = ""; }; 21BFEEF62A05A0310033048D /* TabMigrationUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabMigrationUtilityTests.swift; sourceTree = ""; }; - 21D0F62029B91BF500022292 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; 21D151252AFC28960062D891 /* TabManagerMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagerMiddleware.swift; sourceTree = ""; }; 21D7B60528077CA5003F7E94 /* LibraryViewController+ToolbarActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibraryViewController+ToolbarActions.swift"; sourceTree = ""; }; 21D8843E2A7959D000AF144C /* HomePageSettingViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageSettingViewControllerTests.swift; sourceTree = ""; }; @@ -2366,6 +2366,7 @@ 24DA4827BEDC3FC4334F5505 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Search.strings; sourceTree = ""; }; 253648E02B2111C100D5C2C5 /* SearchViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewControllerTests.swift; sourceTree = ""; }; 25404EFD8E59C9EBCF4AFBBE /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gd; path = gd.lproj/Menu.strings; sourceTree = ""; }; + 254B76092B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbusFirefoxSuggestFeatureLayer.swift; sourceTree = ""; }; 255B49C5B2976844285FB746 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/ErrorPages.strings; sourceTree = ""; }; 258D4CEDBF09798787FB9370 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Storage.strings; sourceTree = ""; }; 25B045CB969DA3F112A3F6A6 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/LoginManager.strings; sourceTree = ""; }; @@ -3344,6 +3345,9 @@ 434A2ED628CF4BD2006D3DD0 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/ToolbarLocation.strings; sourceTree = ""; }; 434AD05F2B023DFB00760E89 /* rm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = rm; path = rm.lproj/Shopping.strings; sourceTree = ""; }; 434AE5202A24BECB0079F5B4 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/DisplayCard.strings; sourceTree = ""; }; + 434B67C32B7BF1FC00FC512B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/CredentialProvider.strings; sourceTree = ""; }; + 434B67C42B7BF1FC00FC512B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/FirefoxLogins.strings; sourceTree = ""; }; + 434B67C52B7BF1FC00FC512B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/LoginsHelper.strings; sourceTree = ""; }; 434B69B82B399D9100D77EA7 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/ContextualHints.strings"; sourceTree = ""; }; 434B69B92B399D9200D77EA7 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/CredentialProvider.strings"; sourceTree = ""; }; 434B69BA2B399D9200D77EA7 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Credentials.strings"; sourceTree = ""; }; @@ -3603,6 +3607,7 @@ 4364EAA02B1DE9FB003A1240 /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = ""; }; 4365AEE82B14AFB700A572D7 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/FirefoxHomepage.strings; sourceTree = ""; }; 4366119829E426B10012DBC7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = "fr.lproj/Edit Card.strings"; sourceTree = ""; }; + 436630302B7BEDF00076848B /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/TabToolbar.strings; sourceTree = ""; }; 436637282AEFC99F00518776 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Shopping.strings"; sourceTree = ""; }; 4366A0AE29DAEDF500DA8329 /* rm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = rm; path = rm.lproj/Alert.strings; sourceTree = ""; }; 4366A0AF29DAEDF500DA8329 /* rm */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = rm; path = rm.lproj/BiometricAuthentication.strings; sourceTree = ""; }; @@ -4048,6 +4053,7 @@ 4397EB162AC1A2A900EB6952 /* hy-AM */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hy-AM"; path = "hy-AM.lproj/TabLocation.strings"; sourceTree = ""; }; 4398CDB82AC1A2FF00C9AA9E /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Share.strings; sourceTree = ""; }; 4398CDB92AC1A2FF00C9AA9E /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/TabLocation.strings; sourceTree = ""; }; + 4399EF912B7BEFC600E091BE /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/TabToolbar.strings; sourceTree = ""; }; 439A0C6E29FFD5DA0084BD94 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Notification.strings; sourceTree = ""; }; 439A0C6F29FFD5DB0084BD94 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/ZoomPageBar.strings; sourceTree = ""; }; 439A220E29F69A0C00F120EE /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Notification.strings; sourceTree = ""; }; @@ -4873,6 +4879,7 @@ 43F816BF2B3062FC0099E15F /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Credentials.strings"; sourceTree = ""; }; 43F816C02B3062FC0099E15F /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/FirefoxLogins.strings"; sourceTree = ""; }; 43F816C12B3062FC0099E15F /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/LoginsHelper.strings"; sourceTree = ""; }; + 43F848F02B7BF07600B6BB44 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/TabToolbar.strings; sourceTree = ""; }; 43F86F6B2AC1A41900EDC5F0 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Share.strings; sourceTree = ""; }; 43F86F6C2AC1A41900EDC5F0 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/TabLocation.strings; sourceTree = ""; }; 43F8734828B39E780047E9CD /* oc */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = oc; path = oc.lproj/BookmarkPanel.strings; sourceTree = ""; }; @@ -4911,6 +4918,7 @@ 43FAA4DA28CF4A8200EFE5B3 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/JumpBackIn.strings; sourceTree = ""; }; 43FAA4DB28CF4A8200EFE5B3 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/ToolbarLocation.strings; sourceTree = ""; }; 43FAD31929E4263300332F94 /* es-AR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-AR"; path = "es-AR.lproj/Edit Card.strings"; sourceTree = ""; }; + 43FAD8FA2B7BF13100E3474B /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/FirefoxHomepage.strings; sourceTree = ""; }; 43FB409E2A0BCB650006C10B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/FirefoxSync.strings; sourceTree = ""; }; 43FB409F2A0BCB650006C10B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Upgrade.strings; sourceTree = ""; }; 43FBF67F2ACADCF400F86E9E /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Share.strings; sourceTree = ""; }; @@ -6350,6 +6358,7 @@ C1C54E119D804703437D116C /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/3DTouchActions.strings; sourceTree = ""; }; C1DF4F71AB33BAB7B199F361 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/PrivateBrowsing.strings; sourceTree = ""; }; C1F24EFFB6E9B114849A4DB3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/PrivateBrowsing.strings; sourceTree = ""; }; + C2200A692B7D148C00DC062A /* ContentBlockerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerTests.swift; sourceTree = ""; }; C227533F2A3C9E1300B9C0D1 /* WebsiteDataManagementViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteDataManagementViewModel.swift; sourceTree = ""; }; C2296FCB2A601C190046ECA6 /* IntensityVisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntensityVisualEffectView.swift; sourceTree = ""; }; C23889DE2A4EFCE500429673 /* ShareExtensionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionCoordinator.swift; sourceTree = ""; }; @@ -8779,7 +8788,6 @@ 43D16B8129831E6A009F8279 /* CreditCardInputField.swift */, 43D16B7929831C7F009F8279 /* CreditCardAutofillToggle.swift */, E174963F2994302D0096900A /* PreferredFont.swift */, - 21D0F62029B91BF500022292 /* ToastView.swift */, ); path = ViewComponents; sourceTree = ""; @@ -10159,6 +10167,7 @@ isa = PBXGroup; children = ( DFFC9AD02A681FA0002A6AAD /* NimbusFakespotFeatureLayer.swift */, + 254B76092B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift */, C81B78A3280752A20000C15F /* NimbusFeatureFlagLayer.swift */, C87D8B7F2818333F00A6307D /* NimbusManager.swift */, C825E9822832A425006CB811 /* NimbusSearchBarLayer.swift */, @@ -11488,6 +11497,7 @@ 8A28C627291028870078A81A /* CanRemoveQuickActionBookmarkTests.swift */, F84B21D91A090F8100AAB793 /* ClientTests.swift */, 8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */, + C2200A692B7D148C00DC062A /* ContentBlockerTests.swift */, C889D7CD2858C4B500121E1D /* ContextMenuHelperTests.swift */, 8A93F86329D37314004159D9 /* Coordinators */, 43B658D729CE249D00C9EF08 /* CreditCard */, @@ -13919,6 +13929,7 @@ 0BA02DB22942605600C92603 /* FormAutofillHelper.swift in Sources */, B26ADF852B339ED000C6E127 /* AddressAutofillSetting.swift in Sources */, 8A19ACB82A329128001C2147 /* PrivacyPolicySetting.swift in Sources */, + 254B760A2B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift in Sources */, E68AEDB01B18F81A00133D99 /* SwipeAnimator.swift in Sources */, 1DDE3DB32AC34E1E0039363B /* TabCell.swift in Sources */, 3BF56D271CDBBE1F00AC4D75 /* SimpleToast.swift in Sources */, @@ -14105,7 +14116,6 @@ C8F457AA1F1FDD9B000CB895 /* BrowserViewController+KeyCommands.swift in Sources */, 21357F2F294237D8004BF9FD /* RemoteTabsClientAndTabsDataSource.swift in Sources */, 59A68FD5260B8D520F890F4A /* ReaderPanel.swift in Sources */, - 21D0F62129B91BF500022292 /* ToastView.swift in Sources */, C849E46126B9C39B00260F0B /* EnhancedTrackingProtectionVC.swift in Sources */, E40FAB0C1A7ABB77009CB80D /* WebServer.swift in Sources */, 59A68D66379CFA85C4EAF00B /* TwoLineImageOverlayCell.swift in Sources */, @@ -14226,6 +14236,7 @@ F98CB66E2A4123F1005F38E9 /* EnhancedTrackingProtectionMenuVMTests.swift in Sources */, DF8C6DD72A52EED1007FAAF2 /* ClientSyncManagerTests.swift in Sources */, C2D1A1112A67E73D00205DCC /* BookmarksCoordinatorTests.swift in Sources */, + C2200A6A2B7D148C00DC062A /* ContentBlockerTests.swift in Sources */, C869916328918C36007ACC5C /* WallpaperNetworkingModuleTests.swift in Sources */, B640467E29B9B58200C5C7B6 /* TabLocationViewTests.swift in Sources */, 8ABA9C8D28931223002C0077 /* MockDispatchQueue.swift in Sources */, @@ -15566,6 +15577,7 @@ 43D6F6F22B554C0100328E70 /* rm */, 4364E8092B554C3B00601C45 /* si */, 43D3C0152B5E832600EF55BA /* be */, + 43FAD8FA2B7BF13100E3474B /* ro */, ); name = FirefoxHomepage.strings; sourceTree = ""; @@ -16442,6 +16454,7 @@ 43D3C0142B5E832600EF55BA /* be */, 438B41BA2B5E865F00BA2E52 /* ko */, 4378F2C82B5E879900D89B0A /* pt-PT */, + 434B67C32B7BF1FC00FC512B /* tt */, ); name = CredentialProvider.strings; sourceTree = ""; @@ -16556,6 +16569,7 @@ 43D3C0162B5E832600EF55BA /* be */, 438B41BC2B5E865F00BA2E52 /* ko */, 4378F2C92B5E879900D89B0A /* pt-PT */, + 434B67C42B7BF1FC00FC512B /* tt */, ); name = FirefoxLogins.strings; sourceTree = ""; @@ -16614,6 +16628,7 @@ 43D3C0172B5E832600EF55BA /* be */, 438B41BD2B5E865F00BA2E52 /* ko */, 4378F2CA2B5E879900D89B0A /* pt-PT */, + 434B67C52B7BF1FC00FC512B /* tt */, ); name = LoginsHelper.strings; sourceTree = ""; @@ -16665,6 +16680,9 @@ 430148D52B5E8854000C4D41 /* th */, 433204162B68010A00ECE7AC /* bs */, 43CEB45A2B680459000F85A9 /* pt-PT */, + 436630302B7BEDF00076848B /* da */, + 4399EF912B7BEFC600E091BE /* kab */, + 43F848F02B7BF07600B6BB44 /* nb */, ); name = TabToolbar.strings; sourceTree = ""; @@ -21018,7 +21036,7 @@ repositoryURL = "https://github.com/mozilla/rust-components-swift.git"; requirement = { kind = exactVersion; - version = 124.0.20240210050349; + version = 124.0.20240215050333; }; }; 435C85EE2788F4D00072B526 /* XCRemoteSwiftPackageReference "glean-swift" */ = { diff --git a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bde1544e2b66..3dfdc5f9d5e9 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mozilla/rust-components-swift.git", "state" : { - "revision" : "3f536993f42697b087d0481e9f87d6c9b13dcccc", - "version" : "124.0.20240210050349" + "revision" : "dc8e209f5a191ab1fa8c8f5e91f4b6014e8459a8", + "version" : "124.0.20240215050333" } }, { diff --git a/firefox-ios/Client/AccessoryViewProvider.swift b/firefox-ios/Client/AccessoryViewProvider.swift index cbac6a1d2c5d..5b9c55f6990f 100644 --- a/firefox-ios/Client/AccessoryViewProvider.swift +++ b/firefox-ios/Client/AccessoryViewProvider.swift @@ -119,12 +119,12 @@ class AccessoryViewProvider: UIView, Themeable { private lazy var loginAutofillView: AutofillAccessoryViewButtonItem = { let accessoryView = AutofillAccessoryViewButtonItem( image: UIImage(named: StandardImageIdentifiers.Large.login), - labelText: .Settings.Passwords.UseSavedLoginFromKeyboard, + labelText: .PasswordAutofill.UseSavedPasswordFromKeyboard, tappedAction: { [weak self] in self?.tappedLoginsButton() }) accessoryView.accessibilityTraits = .button - accessoryView.accessibilityLabel = .Settings.Passwords.UseSavedLoginFromKeyboard + accessoryView.accessibilityLabel = .PasswordAutofill.UseSavedPasswordFromKeyboard return accessoryView }() @@ -188,18 +188,18 @@ class AccessoryViewProvider: UIView, Themeable { setupSpacer(trailingFixedSpacer, width: UX.fixedTrailingSpacerWidth) toolbar.items = [ + currentAccessoryView, + flexibleSpacer, previousButton, nextButton, fixedSpacer, - currentAccessoryView, - flexibleSpacer, doneButton ].compactMap { $0 } toolbar.accessibilityElements = [ + currentAccessoryView?.customView, previousButton, nextButton, - currentAccessoryView?.customView, doneButton ].compactMap { $0 } diff --git a/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs b/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs index a3a27bcc1dcf..b78e47198d26 100644 --- a/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs +++ b/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs @@ -16,7 +16,8 @@ const IOS_DEFAULT_PREFERENCES = { "extensions.formautofill.creditCards.supported": "detect", "browser.search.region": "US", "extensions.formautofill.creditCards.supportedCountries": "US,CA,GB,FR,DE", - "extensions.formautofill.addresses.enabled": false, + "extensions.formautofill.addresses.enabled": true, + "extensions.formautofill.addresses.experiments.enabled": false, // TODO(FXCM-765): fetch this value from swift "extensions.formautofill.addresses.capture.enabled": false, "extensions.formautofill.addresses.supportedCountries": "", "extensions.formautofill.creditCards.enabled": true, diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs index bae0a588a627..88fb2c29f875 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs @@ -33,16 +33,16 @@ const AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF = "extensions.formautofill.creditCards.ignoreAutocompleteOff"; const AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF = "extensions.formautofill.addresses.ignoreAutocompleteOff"; -const ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL = +const ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF = "extensions.formautofill.heuristics.captureOnFormRemoval"; -const ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION = +const ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF = "extensions.formautofill.heuristics.captureOnPageNavigation"; export const FormAutofill = { ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF, - ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL, - ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION, + ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF, + ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF, AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF, @@ -98,18 +98,6 @@ export const FormAutofill = { FormAutofill._creditCardAutofillSupportedCountries ); }, - /** - * Determines if the address autofill feature is available to use in the browser. - * If the feature is not available, then there are no user facing ways to enable it. - * - * @returns {boolean} `true` if address autofill is available - */ - get isAutofillAddressesAvailable() { - return this._isSupportedRegion( - FormAutofill._isAutofillAddressesAvailable, - FormAutofill._addressAutofillSupportedCountries - ); - }, /** * Determines if the user has enabled or disabled credit card autofill. * @@ -256,12 +244,12 @@ XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "captureOnFormRemoval", - ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL + ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "captureOnPageNavigation", - ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION + ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, @@ -272,6 +260,12 @@ XPCOMUtils.defineLazyPreferenceGetter( val => val?.split(",").filter(v => !!v) ); +XPCOMUtils.defineLazyPreferenceGetter( + FormAutofill, + "isAutofillAddressesAvailable", + "extensions.formautofill.addresses.experiments.enabled" +); + ChromeUtils.defineLazyGetter(FormAutofill, "countries", () => AddressMetaDataLoader.getCountries() ); diff --git a/firefox-ios/Client/ContentBlocker/ContentBlocker.swift b/firefox-ios/Client/ContentBlocker/ContentBlocker.swift index 6d7d7f3e8842..ccbc79c2a3ef 100644 --- a/firefox-ios/Client/ContentBlocker/ContentBlocker.swift +++ b/firefox-ios/Client/ContentBlocker/ContentBlocker.swift @@ -311,22 +311,30 @@ extension ContentBlocker { func compileListsNotInStore(completion: @escaping () -> Void) { let blocklists = BlocklistFileName.allCases.map { $0.filename } - let deferreds: [Deferred] = blocklists.map { filename in - let result = Deferred() + let dispatchGroup = DispatchGroup() + blocklists.forEach { filename in + dispatchGroup.enter() ruleStore?.lookUpContentRuleList(forIdentifier: filename) { [weak self] contentRuleList, error in if contentRuleList != nil { - result.fill(()) + dispatchGroup.leave() return } self?.loadJsonFromBundle(forResource: filename) { jsonString in var str = jsonString guard let self, - let range = str.range(of: "]", options: String.CompareOptions.backwards) else { return } + let range = str.range(of: "]", options: String.CompareOptions.backwards) + else { + dispatchGroup.leave() + return + } str = str.replacingCharacters(in: range, with: self.safelistAsJSON() + "]") self.ruleStore?.compileContentRuleList( forIdentifier: filename, encodedContentRuleList: str ) { rule, error in + defer { + dispatchGroup.leave() + } guard error == nil else { self.logger.log( "Content blocker errored with: \(String(describing: error))", @@ -345,15 +353,12 @@ extension ContentBlocker { assert(rule != nil) return } - - result.fill(()) } } } - return result } - all(deferreds).uponQueue(.main) { _ in + dispatchGroup.notify(queue: .main) { completion() } } diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift index 88f909582b83..e1b65db707e2 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift @@ -654,8 +654,10 @@ class BrowserCoordinator: BaseCoordinator, guard uuid == windowUUID else { return } // Additional cleanup performed when the current iPad window is closed. // This is necessary in order to ensure the BVC and other memory is freed correctly. - browserViewController.contentContainer.subviews.forEach { $0.removeFromSuperview() } - browserViewController.removeFromParent() + + // TODO: Revisit for [FXIOS-8064]. Disabled temporarily to avoid potential KVO crash in WebKit. (FXIOS-8416) + // browserViewController.contentContainer.subviews.forEach { $0.removeFromSuperview() } + // browserViewController.removeFromParent() case .libraryOpened: // Auto-close library panel if it was opened in another iPad window. [FXIOS-8095] guard uuid != windowUUID else { return } diff --git a/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardTableViewController.swift b/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardTableViewController.swift index 3300bddcdf5a..cd0c08ad5c8e 100644 --- a/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardTableViewController.swift +++ b/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardTableViewController.swift @@ -30,7 +30,6 @@ class CreditCardTableViewController: UIViewController, Themeable { var lastSelectedIndex: IndexPath? // MARK: View - var toastView: UIHostingController private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) @@ -63,10 +62,6 @@ class CreditCardTableViewController: UIViewController, Themeable { self.themeManager = themeManager self.notificationCenter = notificationCenter - let toastView = ToastView(messageType: .removedCard, - isShowing: true) - self.toastView = UIHostingController(rootView: toastView) - super.init(nibName: nil, bundle: nil) } @@ -104,22 +99,6 @@ class CreditCardTableViewController: UIViewController, Themeable { view.bringSubviewToFront(tableView) } - private func setupToastView() { - guard let toast = toastView.view else { return } - toast.translatesAutoresizingMaskIntoConstraints = false - - addChild(toastView) - view.addSubview(toast) - - NSLayoutConstraint.activate([ - toast.leadingAnchor.constraint(equalTo: view.leadingAnchor), - toast.bottomAnchor.constraint(equalTo: view.bottomAnchor), - toast.trailingAnchor.constraint(equalTo: view.trailingAnchor), - toast.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - ]) - toastView.view.backgroundColor = .clear - } - func applyTheme() { let theme = themeManager.currentTheme view.backgroundColor = theme.colors.layer1 diff --git a/firefox-ios/Client/Frontend/Autofill/CreditCard/ViewComponents/ToastView.swift b/firefox-ios/Client/Frontend/Autofill/CreditCard/ViewComponents/ToastView.swift deleted file mode 100644 index ce2897370514..000000000000 --- a/firefox-ios/Client/Frontend/Autofill/CreditCard/ViewComponents/ToastView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/ - -import Foundation -import SwiftUI - -struct ToastView: View { - var textColor: Color = .white - var backgroundColor: Color = .blue - var messageType: CreditCardModifiedStatus - @State var isShowing = false - - var toast: some View { - VStack { - Spacer() - Text(messageType.message) - .frame(minWidth: 0, maxWidth: .infinity) - .padding() - .background(backgroundColor) - .foregroundColor(textColor) - } - } - - var body: some View { - withAnimation(.default) { - VStack { - } - .toast(toastView: toast, isShowing: $isShowing) - .transition(AnyTransition.move(edge: .bottom)) - } - } -} - -struct ToastModifier: ViewModifier { - @Binding var isShowing: Bool - let toastView: T - var duration: TimeInterval = 3 - - func body(content: Content) -> some View { - ZStack { - content - if isShowing { - toastView - .onAppear(perform: hideToast) - } - } - } - - private func hideToast() { - DispatchQueue.main.asyncAfter(deadline: .now() + duration) { - withAnimation { - isShowing.toggle() - } - } - } -} - -extension View { - func toast(toastView: T, - isShowing: Binding) -> some View { - modifier(ToastModifier(isShowing: isShowing, toastView: toastView)) - } -} diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/GeneralBrowserAction.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/GeneralBrowserAction.swift index e234c6a7f876..ebeeb61f3ef2 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/GeneralBrowserAction.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/GeneralBrowserAction.swift @@ -7,11 +7,14 @@ import Redux enum GeneralBrowserAction: Action { case showToast(ToastTypeContext) + case showOverlay(KeyboardContext) var windowUUID: UUID { switch self { case .showToast(let context as ActionContext): return context.windowUUID + case .showOverlay(let context as ActionContext): + return context.windowUUID } } } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+UIDropInteractionDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+UIDropInteractionDelegate.swift index 9f786052f3e1..675890a2c74f 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+UIDropInteractionDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+UIDropInteractionDelegate.swift @@ -27,7 +27,7 @@ extension BrowserViewController: UIDropInteractionDelegate { TelemetryWrapper.recordEvent(category: .action, method: .drop, object: .url, value: .browser) _ = session.loadObjects(ofClass: URL.self) { urls in - guard let url = urls.first else { return } + guard let draggedUrl = urls.first, let url = URIFixup.getURL(draggedUrl.absoluteString) else { return } self.finishEditingAndSubmit(url, visitType: VisitType.typed, forTab: tab) } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index b38e12d04c97..7b271fc3dfb2 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -12,6 +12,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { var showDataClearanceFlow: Bool var fakespotState: FakespotState var toast: ToastType? + var showOverlay: Bool init(appState: AppState, uuid: WindowUUID) { guard let bvcState = store.state.screenState( @@ -28,6 +29,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { showDataClearanceFlow: bvcState.showDataClearanceFlow, fakespotState: bvcState.fakespotState, toast: bvcState.toast, + showOverlay: bvcState.showOverlay, windowUUID: bvcState.windowUUID) } @@ -38,6 +40,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { showDataClearanceFlow: false, fakespotState: FakespotState(windowUUID: windowUUID), toast: nil, + showOverlay: false, windowUUID: windowUUID) } @@ -47,6 +50,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { showDataClearanceFlow: Bool, fakespotState: FakespotState, toast: ToastType? = nil, + showOverlay: Bool = false, windowUUID: WindowUUID ) { self.searchScreenState = searchScreenState @@ -55,6 +59,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { self.fakespotState = fakespotState self.toast = toast self.windowUUID = windowUUID + self.showOverlay = showOverlay } static let reducer: Reducer = { state, action in @@ -96,6 +101,15 @@ struct BrowserViewControllerState: ScreenState, Equatable { fakespotState: state.fakespotState, toast: toastType, windowUUID: state.windowUUID) + case GeneralBrowserAction.showOverlay(let context): + let showOverlay = context.showOverlay + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + usePrivateHomepage: state.usePrivateHomepage, + showDataClearanceFlow: state.showDataClearanceFlow, + fakespotState: state.fakespotState, + showOverlay: showOverlay, + windowUUID: state.windowUUID) default: return state } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index 04ff8e33f304..8fd78b27fd9f 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -546,6 +546,10 @@ class BrowserViewController: UIViewController, if let toast = state.toast { self.showToastType(toast: toast) } + + if state.showOverlay == true { + overlayManager.openNewTab(url: nil, newTabSettings: newTabSettings) + } } } @@ -2017,7 +2021,10 @@ class BrowserViewController: UIViewController, guard let tab = tabManager.selectedTab else { return } if isPreferSwitchToOpenTabOverDuplicateFeatureEnabled, - let tab = tabManager.tabs.reversed().first(where: { $0.url == url && $0.isPrivate == tab.isPrivate }) { + let tab = tabManager.tabs.reversed().first(where: { + // URL for reading mode comes encoded and we need a separate case to check if it's equal with the tab url + ($0.url == url || $0.url == url.safeEncodedUrl) && $0.isPrivate == tab.isPrivate + }) { tabManager.selectTab(tab) } else { // Handle keyboard shortcuts from homepage with url selection @@ -2288,7 +2295,10 @@ extension BrowserViewController: HomePanelDelegate { guard let tab = tabManager.selectedTab else { return } if isPreferSwitchToOpenTabOverDuplicateFeatureEnabled, - let tab = tabManager.tabs.reversed().first(where: { $0.url == url && $0.isPrivate == tab.isPrivate }) { + let tab = tabManager.tabs.reversed().first(where: { + // URL for reading mode comes encoded and we need a separate case to check if it's equal with the tab url + ($0.url == url || $0.url == url.safeEncodedUrl) && $0.isPrivate == tab.isPrivate + }) { tabManager.selectTab(tab) } else { if isGoogleTopSite { diff --git a/firefox-ios/Client/Frontend/Browser/SearchViewController.swift b/firefox-ios/Client/Frontend/Browser/SearchViewController.swift index c6aeb632d58f..22366f11549d 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchViewController.swift @@ -242,11 +242,19 @@ class SearchViewController: SiteTableViewController, profile.firefoxSuggest?.interruptReader() let tempSearchQuery = searchQuery + let providers = [.amp, .wikipedia] + .filter { NimbusFirefoxSuggestFeatureLayer().isSuggestionProviderAvailable($0) } + .filter { + switch $0 { + case .amp: includeSponsored + case .wikipedia: includeNonSponsored + default: false + } + } return Task { [weak self] in guard let suggestions = try? await self?.profile.firefoxSuggest?.query( tempSearchQuery, - includeSponsored: includeSponsored, - includeNonSponsored: includeNonSponsored + providers: providers ) else { return } await MainActor.run { guard let self, self.searchQuery == tempSearchQuery else { return } @@ -1003,7 +1011,7 @@ class SearchViewController: SiteTableViewController, case SearchListSection.remoteTabs.rawValue: return hasFirefoxSuggestions case SearchListSection.searchSuggestions.rawValue: - return viewModel.isPrivate ? + return viewModel.isPrivate ? model.shouldShowPrivateModeSearchSuggestions : model.shouldShowSearchSuggestions default: diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift index db033bd2e782..abe8f5c27ce1 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift @@ -73,6 +73,14 @@ class ToastTypeContext: ActionContext { } } +class KeyboardContext: ActionContext { + let showOverlay: Bool + init(showOverlay: Bool, windowUUID: WindowUUID) { + self.showOverlay = showOverlay + super.init(windowUUID: windowUUID) + } +} + class RefreshTabContext: ActionContext { let tabDisplayModel: TabDisplayModel init(tabDisplayModel: TabDisplayModel, windowUUID: WindowUUID) { diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift index 2a8340278b8d..f8ebeacc6e5c 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift @@ -317,6 +317,13 @@ class LegacyGridTabViewController: UIViewController, self.tabManager.cleanupClosedTabs(recentlyClosedTabs, previous: previousTab, isPrivate: isPrivateState) + + presentToastForClosingAllTabs( + recentlyClosedTabs: recentlyClosedTabs, + previousTabUUID: previousTabUUID, + isPrivate: isPrivateState + ) + TelemetryWrapper.recordEvent( category: .action, method: .tap, @@ -334,6 +341,17 @@ class LegacyGridTabViewController: UIViewController, } } + private func presentToastForClosingAllTabs(recentlyClosedTabs: [Tab], previousTabUUID: String, isPrivate: Bool) { + self.presentUndoToast(tabsCount: recentlyClosedTabs.count) { [weak self] undoButtonPressed in + guard undoButtonPressed else { return } + self?.tabManager.undoCloseAllTabsLegacy( + recentlyClosedTabs: recentlyClosedTabs, + previousTabUUID: previousTabUUID, + isPrivate: isPrivate + ) + } + } + func closeTabsTrayHelper() { if tabDisplayManager.isPrivate { emptyPrivateTabsView.isHidden = !privateTabsAreEmpty diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift index 919e8c0a6a1e..be418e6bca78 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift @@ -58,7 +58,7 @@ class TabManagerMiddleware { self.closeAllTabs(state: state, uuid: uuid) case TabPanelAction.undoCloseAllTabs: - self.tabManager(for: uuid).undoCloseAllTabs() + self.undoCloseAllTabs(uuid: uuid) case TabPanelAction.selectTab(let context): let tabUUID = context.tabUUID @@ -217,6 +217,7 @@ class TabManagerMiddleware { let model = getTabsDisplayModel(for: isPrivate, shouldScrollToTab: true, uuid: uuid) store.dispatch(TabPanelAction.refreshTab(RefreshTabContext(tabDisplayModel: model, windowUUID: uuid))) store.dispatch(TabTrayAction.dismissTabTray(uuid.context)) + store.dispatch(GeneralBrowserAction.showOverlay(KeyboardContext(showOverlay: true, windowUUID: uuid))) } /// Move tab on `TabManager` array to support drag and drop @@ -298,27 +299,33 @@ class TabManagerMiddleware { let tabManager = tabManager(for: uuid) guard let tabsState = state.screenState(TabsPanelState.self, for: .tabsPanel, window: nil) else { return } Task { - let count = tabManager.tabs.count + let normalCount = tabManager.normalTabs.count + let privateCount = tabManager.privateTabs.count await tabManager.removeAllTabs(isPrivateMode: tabsState.isPrivateMode) ensureMainThread { [self] in let model = getTabsDisplayModel(for: tabsState.isPrivateMode, shouldScrollToTab: false, uuid: uuid) let context = RefreshTabContext(tabDisplayModel: model, windowUUID: uuid) store.dispatch(TabPanelAction.refreshTab(context)) - store.dispatch(TabTrayAction.dismissTabTray(uuid.context)) - store.dispatch(GeneralBrowserAction.showToast(ToastTypeContext(toastType: .allTabs(count: count), - windowUUID: uuid))) + if tabsState.isPrivateMode { + store.dispatch(TabPanelAction.showToast(ToastTypeContext(toastType: .allTabs(count: privateCount), + windowUUID: uuid))) + } else { + store.dispatch(TabTrayAction.dismissTabTray(uuid.context)) + store.dispatch(GeneralBrowserAction.showToast(ToastTypeContext(toastType: .allTabs(count: normalCount), + windowUUID: uuid))) + } } } } - /// Handles undo close all tabs. Adds back all tabs depending on mode - /// - /// - Parameter isPrivateMode: if private mode is active or not - private func undoCloseAllTabs(isPrivateMode: Bool, uuid: WindowUUID) { - // TODO: FXIOS-7978 Handle Undo close all tabs + private func undoCloseAllTabs(uuid: WindowUUID) { let tabManager = tabManager(for: uuid) tabManager.undoCloseAllTabs() + + // The private tab panel is the only panel that stays open after a close all tabs action + let model = getTabsDisplayModel(for: true, shouldScrollToTab: false, uuid: uuid) + store.dispatch(TabPanelAction.refreshTab(RefreshTabContext(tabDisplayModel: model, windowUUID: uuid))) } // MARK: - Inactive tabs helper diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabDisplayPanel.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabDisplayPanel.swift index 9b7a51ac8a67..94e9d75c551e 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabDisplayPanel.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabDisplayPanel.swift @@ -54,6 +54,7 @@ class TabDisplayPanel: UIViewController, override func viewDidLoad() { super.viewDidLoad() + view.accessibilityLabel = .TabTrayViewAccessibilityLabel setupView() listenForThemeChange(view) applyTheme() diff --git a/firefox-ios/Client/Frontend/Fakespot/FakespotViewModel.swift b/firefox-ios/Client/Frontend/Fakespot/FakespotViewModel.swift index 2b4d0fa5486f..0cd2049d8174 100644 --- a/firefox-ios/Client/Frontend/Fakespot/FakespotViewModel.swift +++ b/firefox-ios/Client/Frontend/Fakespot/FakespotViewModel.swift @@ -360,6 +360,7 @@ class FakespotViewModel { func toggleAdsEnabled() { prefs.setBool(!areAdsEnabled, forKey: PrefsKeys.Shopping2023EnableAds) FakespotUtils().addSettingTelemetry() + recordAdsToggleTelemetry() // Make sure the view updates with the new ads setting onStateChange?() } @@ -656,4 +657,15 @@ class FakespotViewModel { extras: [TelemetryWrapper.ExtraKey.size.rawValue: state.rawValue] ) } + + func recordAdsToggleTelemetry() { + TelemetryWrapper.recordEvent( + category: .action, + method: .tap, + object: .shoppingAdsSettingToggle, + extras: [ + TelemetryWrapper.ExtraKey.Shopping.adsSettingToggle.rawValue: areAdsEnabled + ] + ) + } } diff --git a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanelViewModel.swift b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanelViewModel.swift index 109111eab101..10caca9a621c 100644 --- a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanelViewModel.swift +++ b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanelViewModel.swift @@ -104,28 +104,6 @@ class BookmarksPanelViewModel { self.bookmarkFolder = mobileFolder self.bookmarkNodes = mobileFolder.fxChildren ?? [] - if let mobileBookmarks = mobileFolder.fxChildren, !mobileBookmarks.isEmpty { - TelemetryWrapper.recordEvent(category: .information, - method: .view, - object: .mobileBookmarks, - value: .doesHaveMobileBookmarks) - - let mobileBookmarksExtra = [ - TelemetryWrapper.EventExtraKey.mobileBookmarksQuantity.rawValue: Int64(mobileBookmarks.count) - ] - - TelemetryWrapper.recordEvent(category: .information, - method: .view, - object: .mobileBookmarks, - value: .mobileBookmarksCount, - extras: mobileBookmarksExtra) - } else { - TelemetryWrapper.recordEvent(category: .information, - method: .view, - object: .mobileBookmarks, - value: .doesNotHaveMobileBookmarks) - } - let desktopFolder = LocalDesktopFolder() self.bookmarkNodes.insert(desktopFolder, at: 0) diff --git a/firefox-ios/Client/Frontend/Strings.swift b/firefox-ios/Client/Frontend/Strings.swift index 5adafaabac7d..3e2b1add1472 100644 --- a/firefox-ios/Client/Frontend/Strings.swift +++ b/firefox-ios/Client/Frontend/Strings.swift @@ -1788,11 +1788,6 @@ extension String { tableName: nil, value: "Use your fingerprint to access passwords now.", comment: "Touch ID prompt subtitle when accessing logins and passwords") - public static let UseSavedLoginFromKeyboard = MZLocalizedString( - key: "", // Settings.Passwords.Autofill.UseSavedLoginFromKeyboard.v124 - tableName: "Settings", - value: "Use saved login", - comment: "When a user is in the process of loggin in, and has at least one saved password, we show this label inside the keyboard hint. This indicates to the user that there are stored logins available for use on this pending authentication.") } public struct Sync { @@ -1868,12 +1863,12 @@ extension String { value: "Address bar - Firefox Suggest", comment: "In the Search page of the Settings menu, the title for the Firefox Suggest settings section.") public static let ShowNonSponsoredSuggestionsTitle = MZLocalizedString( - key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Title.v124", + key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Title.v124.v2", tableName: "Settings", value: "Suggestions from the Web", - comment: "In the Search page of the Settings menu, the title for setting to enable Suggestions from Firefox.") + comment: "In the Search page of the Settings menu, the title for setting to enable Suggestions from the web in Firefox.") public static let ShowNonSponsoredSuggestionsDescription = MZLocalizedString( - key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Description.v124", + key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Description.v124.v2", tableName: "Settings", value: "Get suggestions from %@ related to your search", comment: "In the Search page of the Settings menu, the description for the setting to enable Suggestions from Firefox. Placeholder is for the app name - Firefox.") @@ -5758,6 +5753,32 @@ extension String { comment: "Warning message shown when you try to enable or use native AutoFill without a device passcode setup") } +// MARK: - Password autofill +extension String { + public struct PasswordAutofill { + public static let UseSavedPasswordFromKeyboard = MZLocalizedString( + key: "PasswordAutofill.UseSavedPasswordFromKeyboard.v124", + tableName: "PasswordAutofill", + value: "Use saved password", + comment: "Displayed inside the keyboard hint when a user is entering their login credentials and has at least one saved password. Indicates that there are stored passwords available for use in filling out the login form.") + public static let UseSavedPasswordFromHeader = MZLocalizedString( + key: "PasswordAutofill.UseSavedPasswordFromHeader.v124", + tableName: "PasswordAutofill", + value: "Use saved password?", + comment: "This label is used in the password list screen header as a question, prompting the user if they want to use a saved password for logging in.") + public static let ManagePasswordsButton = MZLocalizedString( + key: "PasswordAutofill.ManagePasswordsButton.v124", + tableName: "PasswordAutofill", + value: "Manage passwords", + comment: "This label is used for a button in the password list screen allowing users to manage their saved passwords. It's meant to direct users to where they can add, remove, or edit their saved passwords.") + public static let SignInWithSavedPassword = MZLocalizedString( + key: "PasswordAutofill.SignInWithSavedPassword.v124", + tableName: "PasswordAutofill", + value: "You’ll sign into %@", + comment: "This phrase is used as a subtitle in the header of password list screen, indicating to the user that they will be logging into a specific website (represented by %@) using a saved password. It's providing clarity on which website the saved credentials apply to.") + } +} + // MARK: - v35 Strings extension String { public static let FirefoxHomeJumpBackInSectionTitle = MZLocalizedString( @@ -6053,6 +6074,16 @@ extension String { tableName: "Settings", value: "Show Suggestions in Private Browsing", comment: "Label for toggle. Explains that in private browsing mode, the search suggestions which appears at the top of the search bar, can be toggled on or off. Located in the Private Session section in the Search page in the Settings menu.") + public static let ShowNonSponsoredSuggestionsTitle = MZLocalizedString( + key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Title.v124", + tableName: "Settings", + value: "Suggestions from %@", + comment: "In the Search page of the Settings menu, the title for setting to enable Suggestions from Firefox. Placeholder is for the app name - Firefox.") + public static let ShowNonSponsoredSuggestionsDescription = MZLocalizedString( + key: "Settings.Search.Suggest.ShowNonSponsoredSuggestions.Description.v124", + tableName: "Settings", + value: "Get suggestions from the web related to your search", + comment: "In the Search page of the Settings menu, the description for the setting to enable Suggestions from Firefox.") } } } diff --git a/firefox-ios/Client/Frontend/Toolbar+URLBar/ShareButton.swift b/firefox-ios/Client/Frontend/Toolbar+URLBar/ShareButton.swift index 6edf66aea74d..4e4c1f6c7b1b 100644 --- a/firefox-ios/Client/Frontend/Toolbar+URLBar/ShareButton.swift +++ b/firefox-ios/Client/Frontend/Toolbar+URLBar/ShareButton.swift @@ -51,10 +51,10 @@ class ShareButton: UIButton { extension ShareButton: ThemeApplicable { func applyTheme(theme: Theme) { - selectedTintColor = theme.colors.iconSecondary + selectedTintColor = theme.colors.iconDisabled disabledTintColor = theme.colors.iconDisabled - unselectedTintColor = theme.colors.iconDisabled - tintColor = isEnabled ? selectedTintColor : disabledTintColor + unselectedTintColor = theme.colors.iconSecondary + tintColor = isEnabled ? unselectedTintColor : disabledTintColor imageView?.tintColor = tintColor } } diff --git a/firefox-ios/Client/Frontend/Toolbar+URLBar/TabLocationView.swift b/firefox-ios/Client/Frontend/Toolbar+URLBar/TabLocationView.swift index c134be284864..0aa9791bb337 100644 --- a/firefox-ios/Client/Frontend/Toolbar+URLBar/TabLocationView.swift +++ b/firefox-ios/Client/Frontend/Toolbar+URLBar/TabLocationView.swift @@ -65,9 +65,9 @@ class TabLocationView: UIView, FeatureFlaggable { didSet { hideButtons() updateTextWithURL() - hideTrackingProtectionButton() shareButton.isHidden = !(shouldEnableShareButtonFeature && isValidHttpUrlProtocol(url)) setNeedsUpdateConstraints() + showTrackingProtectionButton(for: url) } } @@ -396,7 +396,9 @@ class TabLocationView: UIView, FeatureFlaggable { func showTrackingProtectionButton(for url: URL?) { ensureMainThread { let isValidHttpUrlProtocol = self.isValidHttpUrlProtocol(url) - if isValidHttpUrlProtocol, self.trackingProtectionButton.isHidden { + let isReaderModeURL = url?.isReaderModeURL ?? false + let isFxHomeUrl = url?.isFxHomeUrl ?? false + if !isFxHomeUrl, !isReaderModeURL, isValidHttpUrlProtocol, self.trackingProtectionButton.isHidden { self.trackingProtectionButton.transform = UX.trackingProtectionxOffset self.trackingProtectionButton.alpha = 0 self.trackingProtectionButton.isHidden = false @@ -405,7 +407,7 @@ class TabLocationView: UIView, FeatureFlaggable { self.trackingProtectionButton.transform = .identity } } - self.trackingProtectionButton.isHidden = !isValidHttpUrlProtocol + self.trackingProtectionButton.isHidden = !isValidHttpUrlProtocol || isReaderModeURL || isFxHomeUrl } } @@ -456,7 +458,9 @@ private extension TabLocationView { readerModeButton.isHidden = shoppingButton.isHidden ? newReaderModeState == .unavailable : true // When the user turns on the reader mode we need to hide the trackingProtectionButton (according to 16400), // we will hide it once the newReaderModeState == .active - self.trackingProtectionButton.isHidden = newReaderModeState == .active + if newReaderModeState == .active { + self.trackingProtectionButton.isHidden = true + } if wasHidden != readerModeButton.isHidden { UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: nil) diff --git a/firefox-ios/Client/Nimbus/NimbusFirefoxSuggestFeatureLayer.swift b/firefox-ios/Client/Nimbus/NimbusFirefoxSuggestFeatureLayer.swift new file mode 100644 index 000000000000..3edbe2f4a2d3 --- /dev/null +++ b/firefox-ios/Client/Nimbus/NimbusFirefoxSuggestFeatureLayer.swift @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +import MozillaAppServices + +protocol NimbusFirefoxSuggestFeatureLayerProtocol { + var config: [SuggestionType: Bool] { get } + func isSuggestionProviderAvailable(_ provider: SuggestionProvider) -> Bool +} + +/// A translation layer for the `firefoxSuggestFeature.fml` +/// Responsible for creating a model for suggestion information available in the fml. +class NimbusFirefoxSuggestFeatureLayer: NimbusFirefoxSuggestFeatureLayerProtocol { + let nimbus: FxNimbus + + var config: [SuggestionType: Bool] { + nimbus.features.firefoxSuggestFeature.value().availableSuggestionsTypes + } + + init(nimbus: FxNimbus = .shared) { + self.nimbus = nimbus + } + + func isSuggestionProviderAvailable(_ provider: SuggestionProvider) -> Bool { + return switch provider { + case .amp: config[.amp] ?? false + case .wikipedia: config[.wikipedia] ?? false + default: false + } + } +} diff --git a/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift b/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift index 8be510d081b0..e81b398867af 100644 --- a/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift +++ b/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift @@ -748,9 +748,7 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler completion: { buttonPressed in // Handles undo to Close tabs if buttonPressed { - self.reAddTabs(tabsToAdd: recentlyClosedTabs, - previousTabUUID: previousTabUUID) - NotificationCenter.default.post(name: .DidTapUndoCloseAllTabToast, object: nil) + self.undoCloseAllTabsLegacy(recentlyClosedTabs: recentlyClosedTabs, previousTabUUID: previousTabUUID) } else { // Finish clean up for recently close tabs DispatchQueue.global().async { [unowned self] in @@ -765,6 +763,16 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler delegates.forEach { $0.get()?.tabManagerDidRemoveAllTabs(self, toast: toast) } } + /// Restore recently closed tabs when tab tray refactor is disabled + func undoCloseAllTabsLegacy(recentlyClosedTabs: [Tab], previousTabUUID: String, isPrivate: Bool = false) { + self.reAddTabs( + tabsToAdd: recentlyClosedTabs, + previousTabUUID: previousTabUUID, + isPrivate: isPrivate + ) + NotificationCenter.default.post(name: .DidTapUndoCloseAllTabToast, object: nil) + } + func tabDidSetScreenshot(_ tab: Tab, hasHomeScreenshot: Bool) {} // MARK: - Private @@ -821,13 +829,15 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler } } - private func reAddTabs(tabsToAdd: [Tab], previousTabUUID: String) { + private func reAddTabs(tabsToAdd: [Tab], previousTabUUID: String, isPrivate: Bool = false) { tabs.append(contentsOf: tabsToAdd) let tabToSelect = tabs.first(where: { $0.tabUUID == previousTabUUID }) let currentlySelectedTab = selectedTab if let tabToSelect = tabToSelect, let currentlySelectedTab = currentlySelectedTab { - // remove currently selected tab - removeTabs([currentlySelectedTab]) + // remove tab only in normal mode because we don't create a new tab after users closes all tabs in private mode + if !isPrivate { + removeTabs([currentlySelectedTab]) + } // select previous tab selectTab(tabToSelect, previous: nil) } diff --git a/firefox-ios/Client/TabManagement/TabManager.swift b/firefox-ios/Client/TabManagement/TabManager.swift index 9ba6d6f1f6c6..fc16c57b6f50 100644 --- a/firefox-ios/Client/TabManagement/TabManager.swift +++ b/firefox-ios/Client/TabManagement/TabManager.swift @@ -58,6 +58,8 @@ protocol TabManager: AnyObject { func makeToastFromRecentlyClosedUrls(_ recentlyClosedTabs: [Tab], isPrivate: Bool, previousTabUUID: String) + func undoCloseAllTabsLegacy(recentlyClosedTabs: [Tab], previousTabUUID: String, isPrivate: Bool) + @discardableResult func addTab(_ request: URLRequest!, configuration: WKWebViewConfiguration!, diff --git a/firefox-ios/Client/Telemetry/AppStartupTelemetry.swift b/firefox-ios/Client/Telemetry/AppStartupTelemetry.swift index e9c172efa597..5684035587bb 100644 --- a/firefox-ios/Client/Telemetry/AppStartupTelemetry.swift +++ b/firefox-ios/Client/Telemetry/AppStartupTelemetry.swift @@ -17,12 +17,22 @@ final class AppStartupTelemetry { // MARK: Logic public func sendStartupTelemetry() { + queryCreditCards() + queryLogins() + queryBookmarks() + } + + // MARK: Credit Cards + func queryCreditCards() { _ = profile.autofill.reopenIfClosed() profile.autofill.listCreditCards(completion: { cards, error in guard let cards = cards, error == nil else { return } self.sendCreditCardsSavedAllTelemetry(numberOfSavedCreditCards: cards.count) }) + } + // MARK: Logins + func queryLogins() { let searchController = UISearchController() let loginsViewModel = PasswordManagerViewModel( profile: profile, @@ -37,6 +47,24 @@ final class AppStartupTelemetry { } } + // MARK: Bookmarks + func queryBookmarks() { + profile.places + .getBookmarksTree(rootGUID: BookmarkRoots.MobileFolderGUID, recursive: false) + .uponQueue(.main) { result in + guard let mobileFolder = result.successValue as? BookmarkFolderData else { + return + } + + if let mobileBookmarks = mobileFolder.fxChildren, !mobileBookmarks.isEmpty { + self.sendDoesHaveMobileBookmarksTelemetry() + self.sendMobileBookmarksCountTelemetry(bookmarksCount: Int64(mobileBookmarks.count)) + } else { + self.sendDoesntHaveMobileBookmarksTelemetry() + } + } + } + // MARK: Telemetry Events private func sendLoginsSavedAllTelemetry(numberOfSavedLogins: Int) { let savedLoginsExtra = [TelemetryWrapper.EventExtraKey.loginsQuantity.rawValue: Int64(numberOfSavedLogins)] @@ -55,4 +83,30 @@ final class AppStartupTelemetry { object: .creditCardSavedAll, extras: savedCardsExtra) } + + private func sendDoesHaveMobileBookmarksTelemetry() { + TelemetryWrapper.recordEvent(category: .information, + method: .view, + object: .mobileBookmarks, + value: .doesHaveMobileBookmarks) + } + + private func sendDoesntHaveMobileBookmarksTelemetry() { + TelemetryWrapper.recordEvent(category: .information, + method: .view, + object: .mobileBookmarks, + value: .doesNotHaveMobileBookmarks) + } + + private func sendMobileBookmarksCountTelemetry(bookmarksCount: Int64) { + let mobileBookmarksExtra = [ + TelemetryWrapper.EventExtraKey.mobileBookmarksQuantity.rawValue: bookmarksCount + ] + + TelemetryWrapper.recordEvent(category: .information, + method: .view, + object: .mobileBookmarks, + value: .mobileBookmarksCount, + extras: mobileBookmarksExtra) + } } diff --git a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift index 3aa62565f2a5..86011827d7e2 100644 --- a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift +++ b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift @@ -333,6 +333,7 @@ extension TelemetryWrapper { case downloadLinkButton = "download-link-button" case downloadNowButton = "download-now-button" case downloadsPanel = "downloads-panel" + // MARK: Fakespot case shoppingButton = "shopping-button" case shoppingBottomSheet = "shopping-bottom-sheet" case shoppingProductPageVisits = "product_page_visits" @@ -354,6 +355,7 @@ extension TelemetryWrapper { case shoppingComponentOptedOut = "shopping-component-opted-out" case shoppingUserHasOnboarded = "shopping-user-has-onboarded" case shoppingAdsOptedOut = "shopping-ads-opted-out" + case shoppingAdsSettingToggle = "shopping-ads-setting-toggle" case keyCommand = "key-command" case locationBar = "location-bar" case messaging = "messaging" @@ -728,6 +730,7 @@ extension TelemetryWrapper { case interactionWithALink = "interaction-with-a-link" case swipingTheSurfaceHandle = "swiping-the-surface-handle" case optingOutOfTheFeature = "opting-out-of-the-feature" + case adsSettingToggle = "ads-setting-toggle" case closeButton = "close-button" case isNimbusDisabled = "is-nimbus-disabled" case isComponentOptedOut = "is-component-opted-out" @@ -1263,6 +1266,19 @@ extension TelemetryWrapper { GleanMetrics.Shopping.surfaceReanalyzeClicked.record() case (.action, .tap, .shoppingProductBackInStockButton, _, _): GleanMetrics.Shopping.surfaceReactivatedButtonClicked.record() + case(.action, .tap, .shoppingAdsSettingToggle, _, let extras): + if let isEnabled = extras?[EventExtraKey.Shopping.adsSettingToggle.rawValue] + as? Bool { + let isEnabledExtra = GleanMetrics.Shopping.SurfaceAdsSettingToggledExtra(isEnabled: isEnabled) + GleanMetrics.Shopping.surfaceAdsSettingToggled.record(isEnabledExtra) + } else { + recordUninstrumentedMetrics( + category: category, + method: method, + object: object, + value: value, + extras: extras) + } case (.action, .navigate, .shoppingBottomSheet, _, _): GleanMetrics.Shopping.surfaceNoReviewReliabilityAvailable.record() case (.action, .view, .shoppingSurfaceStaleAnalysisShown, _, _): diff --git a/firefox-ios/Client/metrics.yaml b/firefox-ios/Client/metrics.yaml index 58e370b1b11f..2ebf74a18d56 100755 --- a/firefox-ios/Client/metrics.yaml +++ b/firefox-ios/Client/metrics.yaml @@ -991,6 +991,23 @@ shopping: - fx-ios-data-stewards@mozilla.com expires: "2024-06-01" + surface_ads_setting_toggled: + type: event + description: | + Whether the user has opted in for ads + extra_keys: + is_enabled: + type: boolean + description: | + Is enabled when the user has opted in for ads + bugs: + - https://github.com/mozilla-mobile/firefox-ios/issues/17303 + data_reviews: + - https://github.com/mozilla-mobile/firefox-ios/pull/17681 + notification_emails: + - fx-ios-data-stewards@mozilla.com + expires: "2024-06-01" + shopping.settings: nimbus_disabled_shopping: type: boolean diff --git a/firefox-ios/Shared/Extensions/URLExtensions.swift b/firefox-ios/Shared/Extensions/URLExtensions.swift index 4eb6ca55fd12..52e36aa043da 100644 --- a/firefox-ios/Shared/Extensions/URLExtensions.swift +++ b/firefox-ios/Shared/Extensions/URLExtensions.swift @@ -7,99 +7,101 @@ import Common import WebEngine // The list of permanent URI schemes has been taken from http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml -private let permanentURISchemes = ["aaa", - "aaas", - "about", - "acap", - "acct", - "cap", - "cid", - "coap", - "coaps", - "crid", - "data", - "dav", - "dict", - "dns", - "dtn", - "example", - "file", - "ftp", - "geo", - "go", - "gopher", - "h323", - "http", - "https", - "iax", - "icap", - "im", - "imap", - "info", - "ipn", - "ipp", - "ipps", - "iris", - "iris.beep", - "iris.lwz", - "iris.xpc", - "iris.xpcs", - "jabber", - "ldap", - "leaptofrogans", - "mailto", - "mid", - "msrp", - "msrps", - "mtqp", - "mupdate", - "news", - "nfs", - "ni", - "nih", - "nntp", - "opaquelocktoken", - "pkcs11", - "pop", - "pres", - "reload", - "rtsp", - "rtsps", - "rtspu", - "service", - "session", - "shttp", - "sieve", - "sip", - "sips", - "sms", - "snmp", - "soap.beep", - "soap.beeps", - "stun", - "stuns", - "tag", - "tel", - "telnet", - "tftp", - "thismessage", - "tip", - "tn3270", - "turn", - "turns", - "tv", - "urn", - "vemmi", - "vnc", - "ws", - "wss", - "xcon", - "xcon-userid", - "xmlrpc.beep", - "xmlrpc.beeps", - "xmpp", - "z39.50r", - "z39.50s"] +private let permanentURISchemes = [ + "aaa", + "aaas", + "about", + "acap", + "acct", + "cap", + "cid", + "coap", + "coaps", + "crid", + "data", + "dav", + "dict", + "dns", + "dtn", + "example", + "file", + "ftp", + "geo", + "go", + "gopher", + "h323", + "http", + "https", + "iax", + "icap", + "im", + "imap", + "info", + "ipn", + "ipp", + "ipps", + "iris", + "iris.beep", + "iris.lwz", + "iris.xpc", + "iris.xpcs", + "jabber", + "ldap", + "leaptofrogans", + "mailto", + "mid", + "msrp", + "msrps", + "mtqp", + "mupdate", + "news", + "nfs", + "ni", + "nih", + "nntp", + "opaquelocktoken", + "pkcs11", + "pop", + "pres", + "reload", + "rtsp", + "rtsps", + "rtspu", + "service", + "session", + "shttp", + "sieve", + "sip", + "sips", + "sms", + "snmp", + "soap.beep", + "soap.beeps", + "stun", + "stuns", + "tag", + "tel", + "telnet", + "tftp", + "thismessage", + "tip", + "tn3270", + "turn", + "turns", + "tv", + "urn", + "vemmi", + "vnc", + "ws", + "wss", + "xcon", + "xcon-userid", + "xmlrpc.beep", + "xmlrpc.beeps", + "xmpp", + "z39.50r", + "z39.50s", +] extension URL { public func withQueryParams(_ params: [URLQueryItem]) -> URL { diff --git a/firefox-ios/Shared/Supporting Files/co.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/co.lproj/Onboarding.strings index f4197c3a2030..21987e623d8b 100644 --- a/firefox-ios/Shared/Supporting Files/co.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/co.lproj/Onboarding.strings @@ -16,24 +16,51 @@ /* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ "Onboarding.Customization.Intro.Continue.Action.v123" = "Persunalizà %@"; +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Definite u tema è a barra d’attrezzi per ch’elli currispondenu à u vostru stilu di navigazione."; + /* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ "Onboarding.Customization.Intro.Skip.Action.v123" = "Principià a navigazione"; +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ vi dà u cuntrollu"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Arregistrà è cuntinuà"; + /* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ "Onboarding.Customization.Theme.Dark.Action.v123" = "Scuru"; +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Fighjate u Web cù u lume chì vi piace."; + /* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ "Onboarding.Customization.Theme.Light.Action.v123" = "Chjaru"; /* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ "Onboarding.Customization.Theme.Skip.Action.v123" = "Ignurà"; +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Autumaticu (sistema)"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Sceglie un tema"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ "Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Inghjò"; +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Arregistrà è principià a navigazione"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Mantinite e vostre ricerche accant’à voi."; + /* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ "Onboarding.Customization.Toolbar.Skip.Action.v123" = "Ignurà"; +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Sciglite a piazza di a barra d’attrezzi"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ "Onboarding.Customization.Toolbar.Top.Action.v123" = "Insù"; @@ -64,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Quandu site cunnessi cù a sincrunizazione attivata, a vostra sicurità hè rinfurzata. %@ cifra e vostre parolle d’intesa, e vostre indette, è ancu di più."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ cifra e vostre parolle d’intesa, e vostre indette, è ancu di più quandu vo impiegate a sincrunizazione."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Cunnettesi"; diff --git a/firefox-ios/Shared/Supporting Files/co.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/co.lproj/Shopping.strings index 01228690c4b7..53fe9bbb824d 100644 --- a/firefox-ios/Shared/Supporting Files/co.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/co.lproj/Shopping.strings @@ -121,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "A selezzione di « Sì, pruvallu » vole dì chì vo accettate quell’elementi di %@ :"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "A selezzione di « Sì, pruvallu » vole dì chì vo accettate st’elementi :"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Fighjate quantu sò degni di cunfidenza l‘avisi di prudutti nant’à %1$@ prima di cumprà. U verificadore d’avisu, una funzione esperimentale da %2$@, hè integratu direttamente in u navigatore."; diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/Settings.strings index a512c64af4f8..4a4429a0ffb2 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/Settings.strings @@ -23,7 +23,7 @@ "CreditCard.Settings.RememberThisCard.v112" = "Eisiau cofio'r cerdyn hwn?"; /* When a user is in the process or has finished making a purchase with a card not saved in Firefox's list of stored cards, we ask the user if they would like to save this card for future purchases. This string is a title string of the overall message that asks the user if they would like Firefox to remember the card that is being used. */ -"CreditCard.Settings.RememberThisCard.v122" = "Cadw'r cerdyn hwn yn ddiogel?"; +"CreditCard.Settings.RememberThisCard.v122" = "Cadw’r cerdyn hwn yn ddiogel?"; /* When a user is in the process or has finished making a purchase with a remembered card, and if the credit card information doesn't match the contents of the stored information of that card, we show this string. We ask this user if they would like Firefox update the staled information of that credit card. */ "CreditCard.Settings.UpdateThisCard.v112" = "Diweddaru'r cerdyn hwn?"; diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/Shopping.strings index 6d598f458648..eba1342432ba 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/Shopping.strings @@ -62,7 +62,7 @@ "Shopping.InfoCard.FakespotDoesNotAnalyzeReviews.Title.v120" = "Methu Gwirio’r Adolygiadau Hyn"; /* Description text for an information card used in the review checker section. This message is displayed when the reviews for a product are not yet available but are expected to be provided within the next 24 hours. It serves to inform users of the short wait for reviews and encourages them to return soon for the updated information. */ -"Shopping.InfoCard.InfoComingSoon.Description.v121" = "Dylai fod gennym dadansoddiad wedi’i ddiweddaru o fewn 24 awr. Dewch nôl i weld."; +"Shopping.InfoCard.InfoComingSoon.Description.v121" = "Dylai fod gennym wybodaeth am adolygiadau'r cynnyrch hwn o fewn 24 awr. Dewch nôl."; /* Title for an information card that is displayed in the review checker section when certain details about a product or feature are not currently available but are expected to be provided soon. The message should imply that the user can look forward to receiving more information shortly. */ "Shopping.InfoCard.InfoComingSoon.Title.v121" = "Gwybodaeth ar ei Ffordd"; diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/SnackBar.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/SnackBar.strings index c81c160b7ad9..8f74e21ed4b7 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/SnackBar.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/SnackBar.strings @@ -2,7 +2,7 @@ "CreditCard.SnackBar.RemovedCardLabel.v112" = "Cerdyn wedi'i dynnu"; /* Label text that gets presented as a confirmation at the bottom of screen when credit card information gets saved successfully */ -"CreditCard.SnackBar.SavedCardLabel.v112" = "Cerdyn newydd wedi'i gadw"; +"CreditCard.SnackBar.SavedCardLabel.v112" = "Cerdyn Newydd wedi'i Gadw"; /* Label text that gets presented as a confirmation at the bottom of screen when credit card information gets updated successfully */ "CreditCard.SnackBar.UpdatedCardLabel.v112" = "Wedi diweddaru manylion cerdyn"; diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/TabLocation.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/TabLocation.strings index 970443f561af..ddfd2c5ca600 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/TabLocation.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/TabLocation.strings @@ -2,7 +2,7 @@ "TabLocation.ETP.Off.NotSecure.A11y.Label.v119" = "Nid yw'r cysylltiad yn ddiogel. Mae Diogelwch Uwch rhag Tracio wedi'i ddiffodd."; /* Accessibility label for the security icon in url bar */ -"TabLocation.ETP.Off.Secure.A11y.Label.v119" = "Cysylltiad diogel. Mae Diogelwch Uwch rhag Tracio wedi'i ddiffodd."; +"TabLocation.ETP.Off.Secure.A11y.Label.v119" = "Cysylltiad diogel. Mae Diogelwch Uwch Rhag Tracio wedi'i ddiffodd."; /* Accessibility label for the security icon in url bar */ "TabLocation.ETP.On.NotSecure.A11y.Label.v119" = "Nid yw’r cysylltiad yn ddiogel"; @@ -11,10 +11,10 @@ "TabLocation.ETP.On.Secure.A11y.Label.v119" = "Cysylltiadau diogel"; /* Accessibility label for the lock / tracking protection button on the URL bar */ -"TabLocation.LockButton.AccessibilityLabel.v122" = "Diogelwch rhag Tracio"; +"TabLocation.LockButton.AccessibilityLabel.v122" = "Diogelwch Rhag Tracio"; /* Large content title for the lock button. This title is displayed when accessible font sizes are enabled */ -"TabLocation.LockButton.LargeContentTitle.v122" = "Diogelwch rhag Tracio"; +"TabLocation.LockButton.LargeContentTitle.v122" = "Diogelwch Rhag Tracio"; /* Accessibility label for the share button in url bar */ "TabLocation.Share.A11y.Label.v119" = "Rhannwch y dudalen hon"; @@ -23,7 +23,7 @@ "TabLocation.ShareButton.AccessibilityLabel.v122" = "Rhannu"; /* Accessibility label for the shopping button in url bar */ -"TabLocation.Shopping.A11y.Label.v120" = "Gwiriwr Adolygiadau"; +"TabLocation.Shopping.A11y.Label.v120" = "Gwirydd Adolygiadau"; /* Large content title for the tabs button. The argument is the number of open tabs or an infinity symbol. This title is displayed when using accessible font sizes is enabled. */ "TabsButton.Accessibility.LargeContentTitle.v122" = "Dangos Tabiau: %@"; diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/UpdateCard.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/UpdateCard.strings index be03fa14b164..09095962947b 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/UpdateCard.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/UpdateCard.strings @@ -5,7 +5,7 @@ "CreditCard.UpdateCard.MainTitle.v115" = "Diweddaru'r cerdyn hwn?"; /* This value is used as the title for the update card page */ -"CreditCard.UpdateCard.MainTitle.v122" = "Diweddaru cerdyn?"; +"CreditCard.UpdateCard.MainTitle.v122" = "Diweddaru'r cerdyn?"; /* This value is used as the title for the Manage Cards button from the update credit card page */ "CreditCard.UpdateCard.ManageCardsButtonTitle.v115" = "Rheoli cardiau"; diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/BiometricAuthentication.strings b/firefox-ios/Shared/Supporting Files/da.lproj/BiometricAuthentication.strings index d6626188286a..7adaecf55b90 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/BiometricAuthentication.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/BiometricAuthentication.strings @@ -1,6 +1,9 @@ /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen. */ "Biometry.Screen.UniversalAuthenticationReason.v115" = "Autentificer dig for at se adgangskoder."; +/* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ +"Biometry.Screen.UniversalAuthenticationReason.v122" = "Autentificer dig for at få adgang til dine gemte adgangskoder og betalingsmetoder."; + /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ "Biometry.Screen.UniversalAuthenticationReasonV2.v116" = "Autentificer dig for at få adgang til dine gemte logins og krypterede kort."; diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/FirefoxSync.strings b/firefox-ios/Shared/Supporting Files/da.lproj/FirefoxSync.strings index 97d20c086849..c98ebb352972 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/FirefoxSync.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/FirefoxSync.strings @@ -1,3 +1,6 @@ +/* Toggle for address autofill syncing setting */ +"FirefoxSync.AddressAutofillEngine.v124" = "Adresser"; + /* Toggle for credit cards syncing setting */ "FirefoxSync.CreditCardsEngine.v115" = "Betalingskort"; diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/da.lproj/Onboarding.strings index 3f2259f08da7..5cdbc020b2e2 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Skift din standardbrowser"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Tilpas %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Indstil temaet og værktøjslinjen til at passe din stil."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Gå i gang"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ lader dig bestemme"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Gem og fortsæt"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Mørk"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Se nettet i det bedste lys."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Lys"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Spring over"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Samme som systemet"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Vælg et tema"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Bund"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Gem og gå i gang"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Hold søgning indenfor rækkevidde."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Spring over"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Vælg placeringen af værktøjslinjen"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Top"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Uafhængig. Non-profit. For det fælles bedste."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Du er mere sikker, når du er logget ind og har synkroniseret dine data. %@ krypterer dine adgangskoder, bogmærker med mere."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ krypterer dine adgangskoder, bogmærker med mere, når du er synkroniseret."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Log ind"; diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/da.lproj/Settings.strings index 784ce8abef1c..e005f4e65679 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/Settings.strings @@ -55,6 +55,9 @@ /* Settings section title for the old Firefox account */ "FxA.FirefoxAccount.v119" = "Konto"; +/* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ +"Settings.AddressAutofill.Title.v124" = "Autofyld adresser"; + /* Label used as an item in Settings screen. When touched, it will take user to credit card settings page to that will allows to add or modify saved credit cards to allow for autofill in a webpage. */ "Settings.AutofillCreditCard.Title.v122" = "Betalingsmetoder"; @@ -88,6 +91,12 @@ /* Accessibility label for default search engine setting. */ "Settings.Search.Accessibility.DefaultSearchEngine.v121" = "Standardsøgetjeneste"; +/* Accessibility label for Learn more about Firefox Suggest. */ +"Settings.Search.Accessibility.LearnAboutSuggestions.v124" = "Læs mere om Firefox-forslag"; + +/* Footer for for the `default search engine` settings section in the Search Settings page, which explains in more details what the `Show Search Suggestions` setting includes. */ +"Settings.Search.DefaultSearchEngine.Footer.v122" = "Resultater fra søgninger, historik, bogmærker og mere"; + /* Title for the `default search engine` settings section in the Search page in the Settings menu. */ "Settings.Search.DefaultSearchEngine.Title.v121" = "Standardsøgetjeneste"; @@ -106,3 +115,21 @@ /* Label for the `show search suggestions` setting, in the Search Settings page. */ "Settings.Search.ShowSuggestions.v121" = "Vis søgeforslag"; +/* In the Search page of the Settings menu, the title for the Firefox Suggest settings section. */ +"Settings.Search.Suggest.AddressBarSetting.Title.v124" = "Adressefelt - Firefox-forslag"; + +/* In the search page of the Settings menu, the title for the link to the SUMO Page about Firefox Suggest. */ +"Settings.Search.Suggest.LearnAboutSuggestions.v124" = "Læs mere om Firefox-forslag"; + +/* In the Search page of the Settings menu, the description for the setting to enable Suggestions from Firefox. */ +"Settings.Search.Suggest.ShowNonSponsoredSuggestions.Description.v124" = "Få forslag fra nettet relateret til din søgning"; + +/* In the Search page of the Settings menu, the title for setting to enable Suggestions from Firefox. Placeholder is for the app name - Firefox. */ +"Settings.Search.Suggest.ShowNonSponsoredSuggestions.Title.v124" = "Forslag fra %@"; + +/* In the Search page of the Settings menu, the description for the setting to enable Suggestions from sponsors. Placeholder is for the app name - Firefox. */ +"Settings.Search.Suggest.ShowSponsoredSuggestions.Description.v124" = "Støt %@ med lejlighedsvise sponsorede forslag"; + +/* In the Search page of the Settings menu, the title for the setting to enable Suggestions from sponsors. */ +"Settings.Search.Suggest.ShowSponsoredSuggestions.Title.v124" = "Forslag fra sponsorer"; + diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/da.lproj/Shopping.strings index ba11ae2bcc5e..b7253d3e5467 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/Shopping.strings @@ -100,6 +100,9 @@ /* Title for info card when the product is in analysis mode */ "Shopping.InfoCard.ProgressAnalysis.Title.v120" = "Kontrollerer kvaliteten af anmeldelser"; +/* Title for info card when the product is in analysis mode. The placeholder represents the percentage of the analysis progress, ranging between 1 and 100. */ +"Shopping.InfoCard.ProgressAnalysis.Title.v123" = "Kontrollerer kvaliteten af anmeldelser (%@)"; + /* This description appears beneath the confirmation title on the information card to inform the user that their report regarding the product stock status has been received and is being processed. It serves to set the expectation that the review information will be updated within 24 hours and invites the user to revisit the product page for updates. */ "Shopping.InfoCard.ReportSubmittedByCurrentUser.Description.v121" = "Vi burde have oplysninger om dette produkts anmeldelser klar indenfor 24 timer. Tjek igen senere."; @@ -118,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "Ved at vælge \"Ja, prøv det\" accepterer du følgende fra %@:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "Ved at vælge \"Ja, prøv det\" accepterer du følgende:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Se, hvor pålidelige anmeldelserne af produkter på %1$@ er, før du handler. Verificering af anmeldelser, en eksperimentel funktion fra %2$@, er indbygget i browseren."; @@ -136,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Privatlivspolitik"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "%@s privatlivserklæring"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Ikke nu"; @@ -145,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Betingelser for brug"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "%@s betingelser for brug"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Karakter %@"; @@ -199,6 +211,12 @@ /* Accessibility label for the down chevron icon used to expand or show the details of the Settings Card within the shopping product review bottom sheet. */ "Shopping.SettingsCard.Expand.AccessibilityLabel.v120" = "Vis kortet \"Indstillinger\""; +/* Accessibility hint for the recommended products label and switch, grouped together. When the group is selected in VoiceOver mode, the hint is read to help the user understand what action can be performed. */ +"Shopping.SettingsCard.Expand.GroupedRecommendedProductsAndSwitch.AccessibilityHint.v123" = "Dobbelt-tryk for at slå indstilling til/fra."; + +/* Accessibility label for the recommended products label and switch, grouped together. The first placeholder is for the recommended products label, and the second placeholder is for the state of the switch: On/Off. */ +"Shopping.SettingsCard.Expand.GroupedRecommendedProductsAndSwitch.AccessibilityLabel.v123" = "%1$@, knap-kontakt, %2$@."; + /* Action title of the footer underneath the Settings Card displayed in the shopping review quality bottom sheet. The first parameter will be replaced by the Fakespot app name and the second parameter by the company name of Mozilla. */ "Shopping.SettingsCard.Footer.Action.v120" = "Verificering af anmeldelser er leveret af %1$@ fra %2$@"; @@ -208,6 +226,12 @@ /* Label of the switch from settings card displayed in the shopping review quality bottom sheet. The placeholder will be replaced with the app name. */ "Shopping.SettingsCard.RecommendedProducts.Label.v120" = "Vis produkter anbefalet af %@"; +/* Toggled Off accessibility switch value from Settings Card within the shopping product review bottom sheet. */ +"Shopping.SettingsCard.SwitchValueOff.AccessibilityLabel.v123" = "Fra"; + +/* Toggled On accessibility value, from Settings Card within the shopping product review bottom sheet. */ +"Shopping.SettingsCard.SwitchValueOn.AccessibilityLabel.v123" = "Til"; + /* Label of the button from settings card displayed in the shopping review quality bottom sheet. */ "Shopping.SettingsCard.TurnOffButton.Title.v120" = "Slå verificering af anmeldelser fra"; diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/TabToolbar.strings b/firefox-ios/Shared/Supporting Files/da.lproj/TabToolbar.strings new file mode 100644 index 000000000000..af552f3ae51f --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/da.lproj/TabToolbar.strings @@ -0,0 +1,3 @@ +/* Accessibility label for the tab toolbar fire button in private mode, used to provide users a way to end and delete their private session data. */ +"TabToolbar.Accessibility.DataClearance.v122" = "Rydning af data"; + diff --git a/firefox-ios/Shared/Supporting Files/eo.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/eo.lproj/Onboarding.strings index 89581192709e..e871c1d9d960 100644 --- a/firefox-ios/Shared/Supporting Files/eo.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/eo.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Ŝanĝi vian norman retumilon"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Personecigi %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Elektu vian etoson kaj ilaron por ke ili kongruu kun via maniero retumi."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Komenci retumi"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ transdonas al vi la regadon"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Konservi kaj daŭrigi"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Malhela"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Vidu la reton per la plej bona lumo."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Hela"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Ignori"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Aŭtomata (sistema)"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Elektu etoson"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Malsupre"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Konservi kaj komenci retumi"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Havu viajn serĉojn ĉemane."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Ignori"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Elektu lokon por la ilaro"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Supre"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Sendependa. Neprofitcela. Por ĉiam."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Komencinte seancon kaj spegulinte, vi estas pli sekura. %@ ĉifras viajn pasvortojn, legosignojn kaj pli."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "Dum spegulado %@ ĉifras viajn pasvortojn, legosignojn kaj pli."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Komenci seancon"; diff --git a/firefox-ios/Shared/Supporting Files/eo.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/eo.lproj/Shopping.strings index 3a8c9eb169e5..0bb7ee00b7ef 100644 --- a/firefox-ios/Shared/Supporting Files/eo.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/eo.lproj/Shopping.strings @@ -121,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "Se vi elektas “Jes, provi ĝin” vi akceptas la jenon el %@:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "Se vi elektas “Jes, provi ĝin” vi akceptas la jenon:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Vidu kiel fidindaj estas la recenzoj en %1$@ antaŭ ol aĉeti. La kontrolilo de recenzoj, eksperimenta trajto de %2$@, estas integrita en la retumilo."; @@ -139,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Politiko pri privateco"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "Rimarko de %@ pri privateco"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Ne nun"; @@ -148,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Kondiĉoj de uzo"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "Kondiĉoj de uzo de %@"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Noto %@"; diff --git a/firefox-ios/Shared/Supporting Files/eu.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/eu.lproj/Onboarding.strings index 4f9fd7357da4..672965ce4176 100644 --- a/firefox-ios/Shared/Supporting Files/eu.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/eu.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Aldatu zure nabigatzaile lehenetsia"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Pertsonalizatu %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Ezarri itxura eta tresna-barrak zure nabigatzeko estilo propioarekin bat."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Hasi nabigatzen"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@(e)k agintea ematen dizu"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Gorde eta jarraitu"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Iluna"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Ikusi weba modurik argienean."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Argia"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Saltatu"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Sistemaren automatikoa"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Hautatu itxura"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Behean"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Gorde eta hasi nabigatzen"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Mantendu bilaketak esku-eskura."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Saltatu"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Hautatu tresna-barraren kokapena"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Goian"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Independentea. Irabazi-asmorik gabea. Denen onurarako."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Saioa hasita eta sinkronizatuta zaudenean, seguruago zaude. %@(e)k\nzure pasahitzak, laster-markak eta gehiago zifratzen du."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "Sinkronizatzean, zure pasahitzak, laster-markak eta gehiago zifratzen ditu %@(e)k."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Hasi saioa"; diff --git a/firefox-ios/Shared/Supporting Files/eu.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/eu.lproj/Shopping.strings index 406bd0de50a9..6d2657603481 100644 --- a/firefox-ios/Shared/Supporting Files/eu.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/eu.lproj/Shopping.strings @@ -121,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "\"Bai, probatu\" hautatuz gero, ondorengoa onartzen duzu %@(e)tik:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "\"Bai, probatu\" hautatuz gero, ondorengoak onartzen dituzu:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Erosi aurretik, ikusi %1$@(e)ko produktuen balorazioak zenbateraino diren fidagarriak. Balorazioen egiaztatzailea %2$@(e)n eginbide esperimentala da eta nabigatzailean integratuta dago."; @@ -139,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Pribatutasun-politika"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "%@(r)en pribatutasun-oharra"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Une honetan ez"; @@ -148,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Erabilera-baldintzak"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "%@(r)en erabilera-baldintzak"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Eman nota %@(r)i"; diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/it.lproj/Shopping.strings index bb75a0858291..652598c16de3 100644 --- a/firefox-ios/Shared/Supporting Files/it.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/it.lproj/Shopping.strings @@ -2,10 +2,10 @@ "ContextualHints.Shopping.NotOptedIn.v120" = "Prima di acquistare, scopri se puoi fidarti delle recensioni di questo prodotto."; /* Contextual hints are little popups that appear for the users informing them of new features. This one is a call to action for the popup describing the Shopping feature. It indicates that a user can go directly to the Shopping feature by tapping the text of the action. */ -"ContextualHints.Shopping.NotOptedInAction.v120" = "Prova verifica recensioni"; +"ContextualHints.Shopping.NotOptedInAction.v120" = "Prova Verifica recensioni"; /* Contextual hints are little popups that appear for the users informing them of new features. This is a call to action for the popup that appears after the user has opted in for the Shopping feature. It indicates that a user can directly open the review checker by tapping the text of the action. */ -"ContextualHints.Shopping.OptedInAction.v120" = "Apri verifica recensioni"; +"ContextualHints.Shopping.OptedInAction.v120" = "Apri Verifica recensioni"; /* Contextual hints are little popups that appear for the users informing them of new features. This one appears after the user has opted in and informs him if he wants use the review checker by tapping the Shopping button. */ "ContextualHints.Shopping.OptedInBody.v120" = "Queste recensioni sono affidabili? Controlla ora per vedere la valutazione rettificata."; @@ -233,13 +233,13 @@ "Shopping.SettingsCard.SwitchValueOn.AccessibilityLabel.v123" = "Attiva"; /* Label of the button from settings card displayed in the shopping review quality bottom sheet. */ -"Shopping.SettingsCard.TurnOffButton.Title.v120" = "Disattiva verifica recensioni"; +"Shopping.SettingsCard.TurnOffButton.Title.v120" = "Disattiva Verifica recensioni"; /* Beta label for the header of the Shopping Experience (Fakespot) sheet */ "Shopping.Sheet.Beta.Title.v120" = "BETA"; /* Accessibility label for close button that dismisses the Shopping Experience (Fakespot) sheet. */ -"Shopping.Sheet.Close.AccessibilityLabel.v121" = "Chiudi verifica recensioni"; +"Shopping.Sheet.Close.AccessibilityLabel.v121" = "Chiudi Verifica recensioni"; /* Label for the header of the Shopping Experience (Fakespot) sheet */ "Shopping.Sheet.Title.v120" = "Verifica recensioni"; diff --git a/firefox-ios/Shared/Supporting Files/ja.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/ja.lproj/Onboarding.strings index 46fb940f17fe..f41e04c23979 100644 --- a/firefox-ios/Shared/Supporting Files/ja.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/ja.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "デフォルトブラウザーを切り替えましょう"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "%@ をカスタマイズ"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "あなたののブラウジングスタイルに合わせてテーマとツールバーを設定しましょう。"; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "ブラウジングを開始"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ でプライバシー保護"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "保存して続ける"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Dark"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "最適な明るさでウェブをご覧ください。"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Light"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "スキップ"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "システムテーマ"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "テーマの選択"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "画面下部"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "保存してブラウジングを開始"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "手元で検索。"; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "スキップ"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "ツールバーの配置を選択"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "画面上部"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "健全な独立系で非営利。"; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "ログインして同期すると、%@ がユーザーのパスワードやブックマークなどを暗号化して、より安全に使用できます。"; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ は同期時にパスワードやブックマークなどを暗号化します。"; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "ログイン"; diff --git a/firefox-ios/Shared/Supporting Files/ja.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/ja.lproj/Shopping.strings index 611aef9b768a..593eb839dad5 100644 --- a/firefox-ios/Shared/Supporting Files/ja.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/ja.lproj/Shopping.strings @@ -121,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "「はい、試します」を選択すると、%@ の以下の内容に同意したものとみなされます:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "「はい、試します」を選択すると、これらの項目に同意したものとみなされます:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "購入する前に、%1$@ で製品レビューの信頼性を確認してください。 %2$@ の実験的な機能であるレビュー チェッカーはブラウザーに直接組み込まれています。"; @@ -139,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "プライバシーポリシー"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "%@ のプライバシー通知"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "後で"; @@ -148,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "利用規約"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "%@ の利用規約"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "グレード %@"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/BiometricAuthentication.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/BiometricAuthentication.strings index f27bbad89f96..59134adcf476 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/BiometricAuthentication.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/BiometricAuthentication.strings @@ -1,6 +1,9 @@ /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen. */ "Biometry.Screen.UniversalAuthenticationReason.v115" = "Ttwasnetmen steb i unekcum ɣer wawalen uffiren."; +/* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ +"Biometry.Screen.UniversalAuthenticationReason.v122" = "Sesteb i wakken ad tkecmeḍ ɣer wawalen-ik uffiren yettwaskelsen d tarrayin n uxelleṣ."; + /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ "Biometry.Screen.UniversalAuthenticationReasonV2.v116" = "Sesteb i unekcum ɣer inekcam yettwaskelsen d tkarḍiwin yettwawgelhen."; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/CredentialProvider.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/CredentialProvider.strings index 8f732e7360b4..06783c8b3f14 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/CredentialProvider.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/CredentialProvider.strings @@ -1,3 +1,6 @@ +/* Label shown when there are no logins saved in the passwords list */ +"LoginsList.NoLoginsFound.Title.v122" = "Ulac awalen uffiren i yettwaskelsen"; + /* Label displayed when a user searches for an item, and no matches can be found against the search query */ "LoginsList.NoMatchingResult.Title.v122" = "Ulac awalen uffiren i yettwafen"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/FirefoxHomepage.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/FirefoxHomepage.strings index 3ab3ee5757e4..3f341b902755 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/FirefoxHomepage.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/FirefoxHomepage.strings @@ -1,3 +1,6 @@ +/* When the user ends their private session, they are returned to the private mode homepage, and a toastbar popups confirming that their data has been erased. This is the label for that toast. */ +"FirefoxHomepage.FeltDeletion.Link.v122" = "Isefka n tunigin tusligt ttwasefḍen"; + /* The link for the card that educates users about how private mode works. The link redirects to an external site for more information. The card shows up on the homepage when in the new privacy mode. */ "FirefoxHomepage.FeltPrivacyUI.Link.v122" = "Anwa i izemren ad iwali armud-iw?"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/Onboarding.strings index 79fc561b6453..1ff0a2ebd6b1 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/Onboarding.strings @@ -16,6 +16,9 @@ /* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ "Onboarding.Customization.Intro.Continue.Action.v123" = "Sagen %@"; +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Sbadu asentel-ik akked ufeggag n yifecka almend n tunigin-ik."; + /* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ "Onboarding.Customization.Intro.Skip.Action.v123" = "Bdu tunigin"; @@ -28,6 +31,9 @@ /* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ "Onboarding.Customization.Theme.Dark.Action.v123" = "Aberkan"; +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Wali web s tafat taceεlalt i tebɣiḍ."; + /* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ "Onboarding.Customization.Theme.Light.Action.v123" = "Aceɛlal"; @@ -37,12 +43,24 @@ /* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ "Onboarding.Customization.Theme.System.Action.v123" = "Asentel n unagraw - auto"; +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Fren asentel"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ "Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Adda"; +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Sekles, tebduḍ tunigin"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Eǧǧ inadiyen-ik ɣef wafus."; + /* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ "Onboarding.Customization.Toolbar.Skip.Action.v123" = "Suref"; +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Fren adeg deg ufeggag n yifecka"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ "Onboarding.Customization.Toolbar.Top.Action.v123" = "Afella"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/Settings.strings index 27143860d647..fa6c9c646dc3 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/Settings.strings @@ -7,6 +7,9 @@ /* Title label for when there are no credit cards shown in credit card list in autofill settings screen. %@ is the product name and should not be altered. */ "CreditCard.Settings.EmptyListTitle.v112" = "Sekles tikaṛḍiwin n usmad deg %@"; +/* Title label for when there are no credit cards shown in credit card list in autofill settings screen. %@ is the product name and should not be altered. */ +"CreditCard.Settings.EmptyListTitle.v122" = "Sekles tikarḍiwin deg %@"; + /* Accessibility label for a credit card list item in autofill settings screen. The first parameter is the credit card issuer (e.g. Visa). The second parameter is is the name of the credit card holder. The third parameter is the last 4 digits of the credit card. The fourth parameter is the card's expiration date. */ "CreditCard.Settings.ListItemA11y.v118" = "Takarḍa %1$@, teffe-d i %2$@, adtfakk deg %3$@, ad temmet deg %4$@"; @@ -52,6 +55,9 @@ /* Settings section title for the old Firefox account */ "FxA.FirefoxAccount.v119" = "Amiḍan"; +/* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ +"Settings.AddressAutofill.Title.v124" = "Ččar tansiwin s wudem awurman"; + /* Label used as an item in Settings screen. When touched, it will take user to credit card settings page to that will allows to add or modify saved credit cards to allow for autofill in a webpage. */ "Settings.AutofillCreditCard.Title.v122" = "Tarrayin n uxelleṣ"; @@ -88,12 +94,18 @@ /* Accessibility label for Learn more about Firefox Suggest. */ "Settings.Search.Accessibility.LearnAboutSuggestions.v124" = "Issin ugar ɣef Firefox Suggest"; +/* Footer for for the `default search engine` settings section in the Search Settings page, which explains in more details what the `Show Search Suggestions` setting includes. */ +"Settings.Search.DefaultSearchEngine.Footer.v122" = "Igmaḍ n yinadiyen, n uzray, n tecraḍ n yisebtar d wayen niḍen"; + /* Title for the `default search engine` settings section in the Search page in the Settings menu. */ "Settings.Search.DefaultSearchEngine.Title.v121" = "Amsedday n unadi amezwer"; /* Navigation title for search page in the Settings menu. */ "Settings.Search.PageTitle.v121" = "Nadi"; +/* Label for toggle. Explains that in private browsing mode, the search suggestions which appears at the top of the search bar, can be toggled on or off. Located in the Private Session section in the Search page in the Settings menu. */ +"Settings.Search.PrivateSession.Setting.v122" = "Sken isumar n tunigin tusligt"; + /* Title for the `Private Browsing` settings section in the Search page in the Settings menu. */ "Settings.Search.PrivateSession.Title.v122" = "Tunigin tusligt"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/Shopping.strings index 1bb60107ad39..58b161663ed5 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/Shopping.strings @@ -79,6 +79,9 @@ /* Title for info card when the product is in analysis mode */ "Shopping.InfoCard.ProgressAnalysis.Title.v120" = "Adenqed n tɣara n yilɣa"; +/* Title for info card when the product is in analysis mode. The placeholder represents the percentage of the analysis progress, ranging between 1 and 100. */ +"Shopping.InfoCard.ProgressAnalysis.Title.v123" = "Adenqed n tɣara n yilɣa (%@)"; + /* This title is displayed on the information card as a confirmation message after a user reports that a previously out-of-stock product is now available. It's meant to acknowledge the user's contribution and encourage community engagement by letting them know their report has been successfully submitted. */ "Shopping.InfoCard.ReportSubmittedByCurrentUser.Title.v121" = "Tanemmirt ɣef tuzna n uneqqis!"; @@ -103,12 +106,18 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Tasertit n tbaḍnit"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "Tasertit n tbaḍnit n %@"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Mačči tura"; /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Tiwtilin n useqdec"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "Tiwtilin n useqdec n %@"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Tazmilt %@"; @@ -136,6 +145,9 @@ /* The title of the learn more button from How we determine review quality card displayed in the shopping review quality bottom sheet. The placeholder will be replaced with the Fakespot app name. */ "Shopping.ReviewQualityCard.LearnMoreButton.Title.v120" = "Issin ugar ɣef wamek %@ yettguccul taɣara n wulɣu"; +/* Mixed reviews label from How we determine review quality card displayed in the shopping review quality bottom sheet. */ +"Shopping.ReviewQualityCard.MixedReviews.Label.v120" = "Nettwali iwellihen sdukklen iwellihen inaflasen d yiwellihen arinaflasen"; + /* Accessibility label for the up chevron icon used to collapse or minimize the Settings Card within the shopping product review bottom sheet. */ "Shopping.SettingsCard.Collapse.AccessibilityLabel.v120" = "Fneẓ takarḍa n yiɣewwaren"; diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/TabToolbar.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/TabToolbar.strings new file mode 100644 index 000000000000..d903fd0014bf --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/TabToolbar.strings @@ -0,0 +1,3 @@ +/* Accessibility label for the tab toolbar fire button in private mode, used to provide users a way to end and delete their private session data. */ +"TabToolbar.Accessibility.DataClearance.v122" = "Asfaḍ n yisefka"; + diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/FirefoxSync.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/FirefoxSync.strings index e6c8fb06c2a6..3372865f0b8f 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/FirefoxSync.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/FirefoxSync.strings @@ -1,3 +1,6 @@ +/* Toggle for address autofill syncing setting */ +"FirefoxSync.AddressAutofillEngine.v124" = "Adresser"; + /* Toggle for credit cards syncing setting */ "FirefoxSync.CreditCardsEngine.v115" = "Betalingskort"; diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/Onboarding.strings index 26b85b2373c6..d1d76a3cfb4c 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Endre standard nettleser"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Tilpass %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Still inn temaet og verktøylinjen for å matche din unike nettleserstil."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Begynn å surfe"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ gir deg kontroll"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Lagre og fortsett"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Mørk"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Se nettet i det beste lyset."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Lyst"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Hopp over"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Automatisk (system)"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Velg et tema"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Bunn"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Lagre og begynn å surfe"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Hold søk innen rekkevidde."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Hopp over"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Velg plassering for verktøylinjen"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Topp"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Uavhengig. Ideell. For alltid."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Når du er inlogget og synkronisert, er du tryggere. %@ krypterer passordene, bokmerkene og mer."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ krypterer passordene, bokmerkene og mer når du er synkronisert."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Logg inn"; diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/Settings.strings index 53b525a14b72..087ca20c7b15 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/Settings.strings @@ -55,6 +55,9 @@ /* Settings section title for the old Firefox account */ "FxA.FirefoxAccount.v119" = "Konto"; +/* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ +"Settings.AddressAutofill.Title.v124" = "Autoutfyll adresser"; + /* Label used as an item in Settings screen. When touched, it will take user to credit card settings page to that will allows to add or modify saved credit cards to allow for autofill in a webpage. */ "Settings.AutofillCreditCard.Title.v122" = "Betalingsmetoder"; @@ -88,6 +91,12 @@ /* Accessibility label for default search engine setting. */ "Settings.Search.Accessibility.DefaultSearchEngine.v121" = "Standard søkemotor"; +/* Accessibility label for Learn more about Firefox Suggest. */ +"Settings.Search.Accessibility.LearnAboutSuggestions.v124" = "Les mer om Firefox-forslag"; + +/* Footer for for the `default search engine` settings section in the Search Settings page, which explains in more details what the `Show Search Suggestions` setting includes. */ +"Settings.Search.DefaultSearchEngine.Footer.v122" = "Resultater fra søk, historikk, bokmerker og mer"; + /* Title for the `default search engine` settings section in the Search page in the Settings menu. */ "Settings.Search.DefaultSearchEngine.Title.v121" = "Standard søkemotor"; @@ -106,3 +115,21 @@ /* Label for the `show search suggestions` setting, in the Search Settings page. */ "Settings.Search.ShowSuggestions.v121" = "Vis søkeforslag"; +/* In the Search page of the Settings menu, the title for the Firefox Suggest settings section. */ +"Settings.Search.Suggest.AddressBarSetting.Title.v124" = "Adresselinje - Firefox-forslag"; + +/* In the search page of the Settings menu, the title for the link to the SUMO Page about Firefox Suggest. */ +"Settings.Search.Suggest.LearnAboutSuggestions.v124" = "Les mer om Firefox-forslag"; + +/* In the Search page of the Settings menu, the description for the setting to enable Suggestions from Firefox. */ +"Settings.Search.Suggest.ShowNonSponsoredSuggestions.Description.v124" = "Få forslag fra nettet relatert til søket ditt"; + +/* In the Search page of the Settings menu, the title for setting to enable Suggestions from Firefox. Placeholder is for the app name - Firefox. */ +"Settings.Search.Suggest.ShowNonSponsoredSuggestions.Title.v124" = "Forslag fra %@"; + +/* In the Search page of the Settings menu, the description for the setting to enable Suggestions from sponsors. Placeholder is for the app name - Firefox. */ +"Settings.Search.Suggest.ShowSponsoredSuggestions.Description.v124" = "Støtt %@ med sporadiske sponsede forslag"; + +/* In the Search page of the Settings menu, the title for the setting to enable Suggestions from sponsors. */ +"Settings.Search.Suggest.ShowSponsoredSuggestions.Title.v124" = "Forslag fra sponsorer"; + diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/Shopping.strings index 0a0b5a4bc4bf..be2a075809ed 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/Shopping.strings @@ -100,6 +100,9 @@ /* Title for info card when the product is in analysis mode */ "Shopping.InfoCard.ProgressAnalysis.Title.v120" = "Kontrollerer kvaliteten på vurderingen"; +/* Title for info card when the product is in analysis mode. The placeholder represents the percentage of the analysis progress, ranging between 1 and 100. */ +"Shopping.InfoCard.ProgressAnalysis.Title.v123" = "Kontrollerer kvaliteten på vurderingen (%@)"; + /* This description appears beneath the confirmation title on the information card to inform the user that their report regarding the product stock status has been received and is being processed. It serves to set the expectation that the review information will be updated within 24 hours and invites the user to revisit the product page for updates. */ "Shopping.InfoCard.ReportSubmittedByCurrentUser.Description.v121" = "Vi bør ha informasjon om dette produktets anmeldelser innen 24 timer. Sjekk igjen senere."; @@ -118,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "Ved å velge «Ja, prøv det» godtar du følgende fra %@:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "Ved å velge «Ja, prøv det» godtar du følgende:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Se hvor pålitelige produktvurderinger er på %1$@ før du handler. Vurderingskontrollør, en eksperimentell funksjon fra %2$@, er innebygd rett i nettleseren."; @@ -136,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Personvernbestemmelser"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "%@s personvernerklæring"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Ikke nå"; @@ -145,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Brukervilkår"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "%@s brukervilkår"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Karakter %@"; @@ -199,6 +211,12 @@ /* Accessibility label for the down chevron icon used to expand or show the details of the Settings Card within the shopping product review bottom sheet. */ "Shopping.SettingsCard.Expand.AccessibilityLabel.v120" = "Utvid «Innstillinger»-kortet"; +/* Accessibility hint for the recommended products label and switch, grouped together. When the group is selected in VoiceOver mode, the hint is read to help the user understand what action can be performed. */ +"Shopping.SettingsCard.Expand.GroupedRecommendedProductsAndSwitch.AccessibilityHint.v123" = "Dobbelttrykk for å veksle innstilling."; + +/* Accessibility label for the recommended products label and switch, grouped together. The first placeholder is for the recommended products label, and the second placeholder is for the state of the switch: On/Off. */ +"Shopping.SettingsCard.Expand.GroupedRecommendedProductsAndSwitch.AccessibilityLabel.v123" = "%1$@, bytteknapp, %2$@."; + /* Action title of the footer underneath the Settings Card displayed in the shopping review quality bottom sheet. The first parameter will be replaced by the Fakespot app name and the second parameter by the company name of Mozilla. */ "Shopping.SettingsCard.Footer.Action.v120" = "Vurderingskontrolløren drives av %1$@ av %2$@"; @@ -208,6 +226,12 @@ /* Label of the switch from settings card displayed in the shopping review quality bottom sheet. The placeholder will be replaced with the app name. */ "Shopping.SettingsCard.RecommendedProducts.Label.v120" = "Vis produkter anbefalt av %@"; +/* Toggled Off accessibility switch value from Settings Card within the shopping product review bottom sheet. */ +"Shopping.SettingsCard.SwitchValueOff.AccessibilityLabel.v123" = "Av"; + +/* Toggled On accessibility value, from Settings Card within the shopping product review bottom sheet. */ +"Shopping.SettingsCard.SwitchValueOn.AccessibilityLabel.v123" = "På"; + /* Label of the button from settings card displayed in the shopping review quality bottom sheet. */ "Shopping.SettingsCard.TurnOffButton.Title.v120" = "Slå av vurderingskontrolløren"; diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/TabToolbar.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/TabToolbar.strings new file mode 100644 index 000000000000..dc4d7c077c3b --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/TabToolbar.strings @@ -0,0 +1,3 @@ +/* Accessibility label for the tab toolbar fire button in private mode, used to provide users a way to end and delete their private session data. */ +"TabToolbar.Accessibility.DataClearance.v122" = "Datarensing"; + diff --git a/firefox-ios/Shared/Supporting Files/pl.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/pl.lproj/Onboarding.strings index 41ee512f5099..4a96e64abe99 100644 --- a/firefox-ios/Shared/Supporting Files/pl.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/pl.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Przełącz domyślną przeglądarkę"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Dostosuj przeglądarkę %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Ustaw motyw i pasek narzędzi tak, by pasowały do Twojego własnego stylu przeglądania."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Zacznij przeglądać Internet"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ daje większą kontrolę"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Zachowaj i kontynuuj"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Ciemny"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Oglądaj sieć w najlepszym świetle."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Jasny"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Pomiń"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Systemowy"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Wybierz motyw"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Na dole"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Zachowaj i zacznij przeglądać Internet"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Trzymaj wyszukiwania zawsze pod ręką."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Pomiń"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Wybierz położenie paska narzędzi"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Na górze"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Niezależny. Nie dla zysku. Na dobre."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Zalogowanie i zsynchronizowanie zwiększa Twoje bezpieczeństwo. %@ szyfruje hasła, zakładki i nie tylko."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ szyfruje Twoje hasła, zakładki i inne dane podczas synchronizacji."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "Zaloguj się"; diff --git a/firefox-ios/Shared/Supporting Files/rm.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/rm.lproj/Onboarding.strings index 2a127dee11d9..063bd6aefd65 100644 --- a/firefox-ios/Shared/Supporting Files/rm.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/rm.lproj/Onboarding.strings @@ -13,6 +13,57 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Mida tes navigatur standard"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "Persunalisar %@"; + +/* String used to describe the description label of the customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Intro.Description.v123" = "Tscherna il design e configurescha la trav d’utensils uschia che tut correspunda a tes stil unic da navigar."; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Cumenzar a navigar"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ ta dat la controlla"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Memorisar e cuntinuar"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Stgir"; + +/* String used to describe the description label of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Description.v123" = "Contempla il web en la meglra glisch."; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Cler"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Sursiglir"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to system theme from the available choices. */ +"Onboarding.Customization.Theme.System.Action.v123" = "Tenor il sistem"; + +/* String used to describe the title of the theme customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Theme.Title.v123" = "Tscherna in design"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Sut"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Memorisar e cumenzar a navigar"; + +/* String used to describe the description label of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Description.v123" = "Hajas tias tschertgas adina per mauns."; + +/* String used to describe the option to skip the toolbar customization in Firefox Onboarding screens and start browisg in the app. */ +"Onboarding.Customization.Toolbar.Skip.Action.v123" = "Sursiglir"; + +/* String used to describe the title of the toolbar customization onboarding page in our Onboarding screens. */ +"Onboarding.Customization.Toolbar.Title.v123" = "Tscherna la posiziun per la trav d’utensils"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ +"Onboarding.Customization.Toolbar.Top.Action.v123" = "Sura"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Independent. Senza finamira da profit. Per il bain da tuts."; @@ -40,6 +91,9 @@ /* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ "Onboarding.Sync.Description.v120" = "Sche ti es annunzià e sincroniseschas tias datas, es ti pli segir. %@ criptescha tes pleds-clav, segnapaginas e dapli."; +/* String used to describes the description of what Firefox is on the Sync onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Sync.Description.v123" = "%@ criptescha tes pleds-clav, segnapaginas e dapli cura che ti sincroniseschas tias datas."; + /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Sync.SignIn.Action.v114" = "S'annunziar"; diff --git a/firefox-ios/Shared/Supporting Files/rm.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/rm.lproj/Shopping.strings index 5fba02721324..abfe37a1535e 100644 --- a/firefox-ios/Shared/Supporting Files/rm.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/rm.lproj/Shopping.strings @@ -121,6 +121,9 @@ /* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replaced by the Fakespot app name. After the colon, what appears are two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ "Shopping.OptInCard.Disclaimer.Text.v120" = "Cun tscherner «Gea, empruvar», acceptas ti ils suandants puncts da %@:"; +/* Text for the disclaimer that appears underneath the rating image of the Shopping Experience Opt In onboarding Card (Fakespot). After the colon, there will be two links, each on their own line. The first link is to a Privacy policy. The second link is to Terms of use. */ +"Shopping.OptInCard.Disclaimer.Text.v123" = "Cun tscherner «Gea, empruvar» acceptas ti las suandantas cundiziuns:"; + /* Label for the first paragraph of the Shopping Experience Opt In onboarding Card (Fakespot). The first parameter will be the website the user is coming from when viewing this screen (default Amazon). The second parameter will be replaced by the app name. This string is almost identical with 'Shopping.OptInCard.FirstParagraph.Description', but without Best Buy and Walmart websites, which are not available in many locales. */ "Shopping.OptInCard.FirstParagraph.AmazonOnly.Description.v122" = "Ve a savair quant fidablas che las recensiuns dals products èn sin %1$@ avant che ti als cumpras. La verificaziun da recensiuns, ina funcziun experimentala da %2$@, è integrada directamain en il navigatur."; @@ -139,6 +142,9 @@ /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Directivas per la protecziun da datas"; +/* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Firefox app name. */ +"Shopping.OptInCard.PrivacyPolicy.Button.Title.v123" = "Las directivas davart la protecziun da datas da %@"; + /* Text for the secondary button of the Shopping Experience Opt In onboarding Card (Fakespot) */ "Shopping.OptInCard.SecondaryButton.Title.v120" = "Betg ussa"; @@ -148,6 +154,9 @@ /* Show Firefox Browser Terms of Use page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.TermsOfUse.Button.Title.v120" = "Cundiziuns d'utilisaziun"; +/* Show Fakespot Terms of Use page in the Shopping Experience Opt In onboarding Card (Fakespot). The parameter will be replace by the Fakespot name. */ +"Shopping.OptInCard.TermsOfUse.Button.Title.v123" = "Las cundiziuns d’utilisaziun da %@"; + /* Accessibility label for the Grade labels used in 'How we determine review quality' card and 'How reliable are these reviews' card displayed in the shopping review quality bottom sheet. The placeholder will be replaced by a grade letter (e.g. A). The grading system contains letters from A-F. */ "Shopping.ReliabilityScore.Grade.A11y.Label.v120" = "Nota %@"; diff --git a/firefox-ios/Shared/Supporting Files/ro.lproj/FirefoxHomepage.strings b/firefox-ios/Shared/Supporting Files/ro.lproj/FirefoxHomepage.strings new file mode 100644 index 000000000000..ce283eaeae4b --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/ro.lproj/FirefoxHomepage.strings @@ -0,0 +1,3 @@ +/* The title for the card that educates users about how private mode works. The card shows up on the homepage when in the new privacy mode. */ +"FirefoxHomepage.FeltPrivacyUI.Title.v122" = "Nu lăsa urme pe acest dispozitiv"; + diff --git a/firefox-ios/Shared/Supporting Files/ro.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/ro.lproj/Onboarding.strings index 4efbcc89a515..1d956935fac9 100644 --- a/firefox-ios/Shared/Supporting Files/ro.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/ro.lproj/Onboarding.strings @@ -1,3 +1,3 @@ -/* String used to describes the title of what Firefox is on the welcome onboarding page for current version in our Onboarding screens. Placeholder is for the app name. */ -"Onboarding.Welcome.Title.TreatementA.v114" = "Desemnează %@ drept browserul implicit"; +/* String used to describes the title of what Firefox is on the welcome onboarding page for current version in our Onboarding screens. */ +"Onboarding.Welcome.Title.TreatementA.v120" = "Ne place să te protejăm"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Alert.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Alert.strings index 073e0fdf423a..f23858cd9ebf 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Alert.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Alert.strings @@ -7,6 +7,9 @@ /* Title label for the dialog box that gets presented as a confirmation to ask user if they would like to remove the saved credit card */ "CreditCard.SnackBar.RemoveCardTitle.v112" = "Бу карта бетерелсенме?"; +/* Title label for the dialog box that gets presented as a confirmation to ask user if they would like to remove the saved credit card */ +"CreditCard.SnackBar.RemoveCardTitle.v122" = "Карта бетерелсенме?"; + /* Button text to dismiss the dialog box that gets presented as a confirmation to to remove card and perform the operation of removing the credit card. */ "CreditCard.SnackBar.RemovedCardButton.v112" = "Бетерү"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Alerts.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Alerts.strings index 4930b290d1bc..225c63ffa948 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Alerts.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Alerts.strings @@ -1,3 +1,12 @@ +/* When tapping the fire icon in private mode, an alert comes up asking to confirm if you want to delete all browsing data and end your private session. This is the cancel action for the alert, cancelling ending your session. */ +"Alerts.FeltDeletion.Button.Cancel.v122" = "Баш тарту"; + +/* When tapping the fire icon in private mode, an alert comes up asking to confirm if you want to delete all browsing data and end your private session. This is the affirmative action for the alert, confirming that you do want to do that. */ +"Alerts.FeltDeletion.Button.Confirm.v122" = "Сессия мәгълүматларын бетерү"; + +/* When tapping the fire icon in private mode, an alert comes up asking to confirm if you want to delete all browsing data and end your private session. This is the title for the alert. */ +"Alerts.FeltDeletion.Title.v122" = "Хосусый гизү сессиягез тәмамлансынмы?"; + /* The title for the negative action of the restore tabs pop-up alert. This alert shows when opening up Firefox after it crashed, and will reject the action of restoring tabs. */ "Alerts.RestoreTabs.Button.No.v109" = "Юк"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/BiometricAuthentication.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/BiometricAuthentication.strings index c05660a7c437..09c34ccef38f 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/BiometricAuthentication.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/BiometricAuthentication.strings @@ -1,6 +1,9 @@ /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen. */ "Biometry.Screen.UniversalAuthenticationReason.v115" = "Серсүзләргә ирешү өчен аутентификация үтегез."; +/* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ +"Biometry.Screen.UniversalAuthenticationReason.v122" = "Сакланган серсүзләрегезгә һәм түләү ысулларыгызга ирешү өчен аутентификация үтегез."; + /* Biometric authentication is when the system prompts users for Face ID or fingerprint before accessing protected information. This string asks the user to enter their device passcode to access the protected screen for logins and encrypted cards. */ "Biometry.Screen.UniversalAuthenticationReasonV2.v116" = "Сакланган логиннарыгызга һәм шифрланган карталарга ирешү өчен аутентификация үтегез."; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/CredentialProvider.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/CredentialProvider.strings new file mode 100644 index 000000000000..4fc07d86dc9f --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/CredentialProvider.strings @@ -0,0 +1,9 @@ +/* Label shown when there are no logins saved in the passwords list */ +"LoginsList.NoLoginsFound.Title.v122" = "Сакланган серсүзләр юк"; + +/* Label displayed when a user searches for an item, and no matches can be found against the search query */ +"LoginsList.NoMatchingResult.Title.v122" = "Серсүзләр табылмады"; + +/* Placeholder text for search field in the credential provider list */ +"LoginsList.Search.Placeholder.v122" = "Серсүзләрдә эзләү"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxLogins.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxLogins.strings new file mode 100644 index 000000000000..86e182c649ea --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxLogins.strings @@ -0,0 +1,18 @@ +/* Prompt for saving the username in the Save Logins prompt. */ +"LoginsHelper.PromptSaveLogin.Title.v122" = "Кулланучы исеме саклансынмы?"; + +/* Prompt for saving a password in the Save Logins prompt. */ +"LoginsHelper.PromptSavePassword.Title.v122" = "Серсүз саклансынмы?"; + +/* Prompt for updating the password in the Update Password prompt. */ +"LoginsHelper.PromptUpdateLogin.Title.OneArg.v122" = "Серсүз яңартылсынмы?"; + +/* Prompt for updating a password in the Update Password prompt. */ +"LoginsHelper.PromptUpdateLogin.Title.TwoArg.v122" = "Серсүз яңартылсынмы?"; + +/* Placeholder text for search box in logins list view. */ +"LoginsList.LoginsListSearchPlaceholder.v122" = "Серсүзләрдә эзләү"; + +/* Title for the list of logins saved by the app */ +"LoginsList.Title.v122" = "САКЛАНГАН СЕРСҮЗЛӘР"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxSync.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxSync.strings index 4fa494cabda2..c8188dcab9a0 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxSync.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/FirefoxSync.strings @@ -1,3 +1,12 @@ +/* Toggle for address autofill syncing setting */ +"FirefoxSync.AddressAutofillEngine.v124" = "Адреслар"; + /* Toggle for credit cards syncing setting */ "FirefoxSync.CreditCardsEngine.v115" = "Кредит карталары"; +/* Toggle for credit cards syncing setting */ +"FirefoxSync.CreditCardsEngine.v122" = "Түләү ысуллары"; + +/* Toggle passwords syncing setting, in the Settings > Sync Data menu of the app. */ +"Sync.LoginsEngine.Title.v122" = "Серсүзләр"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/LoginsHelper.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/LoginsHelper.strings new file mode 100644 index 000000000000..2cfbd091a591 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/LoginsHelper.strings @@ -0,0 +1,9 @@ +/* Button to not save the user's password in the logins helper */ +"LoginsHelper.DontSave.Button.v122" = "Хәзер түгел"; + +/* Button to not update the user's password in the logins helper */ +"LoginsHelper.DontUpdate.Button.v122" = "Хәзер түгел"; + +/* Button to save the user's password */ +"LoginsHelper.SaveLogin.Button.v122" = "Саклау"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings index 7542750cd532..189150e9fe43 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings @@ -13,6 +13,33 @@ /* The title on the Default Browser Popup, which is a card with instructions telling the user how to set Firefox as their default browser. */ "DefaultBrowserPopup.Title.v114" = "Башка браузерны төп браузер итү"; +/* String used to describe the option to continue to the next onboarding card in Firefox Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Continue.Action.v123" = "%@ браузерын көйләү"; + +/* String used to describe the option to skip the customization cards in Firefox Onboarding screens and start browsing. */ +"Onboarding.Customization.Intro.Skip.Action.v123" = "Гизүне башлау"; + +/* String used to describe the title of the customization onboarding page in our Onboarding screens. Placeholder is for the app name. */ +"Onboarding.Customization.Intro.Title.v123" = "%@ идарәне Сезгә тапшыра"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Continue.Action.v123" = "Саклау һәм дәвам итү"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to dark theme from the available choices. */ +"Onboarding.Customization.Theme.Dark.Action.v123" = "Караңгы"; + +/* On the theme customization onboarding card, the string used to describe the option to set the theme to light theme from the available choices. */ +"Onboarding.Customization.Theme.Light.Action.v123" = "Ачык"; + +/* String used to describe the option to skip the theme customization in Firefox Onboarding screens. */ +"Onboarding.Customization.Theme.Skip.Action.v123" = "Калдырып тору"; + +/* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ +"Onboarding.Customization.Toolbar.Bottom.Action.v123" = "Аста"; + +/* String used to describe the option to save set preferences and leave onboarding to start browsing in the app. */ +"Onboarding.Customization.Toolbar.Continue.Action.v123" = "Саклау һәм гизүне башлау"; + /* String used to describes what Firefox is on the first onboarding page in our Onboarding screens. Indie means small independant. */ "Onboarding.IntroDescriptionPart1.v114" = "Бәйсез. Коммерциягә нигезләнмәгән. Яхшы ниятле."; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/RememberCard.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/RememberCard.strings index 509d5f7e23a9..10c916ae1127 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/RememberCard.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/RememberCard.strings @@ -4,6 +4,9 @@ /* This value is used as the title for the Yes button in the remember credit card page */ "CreditCard.RememberCard.MainButtonTitle.v115" = "Әйе"; +/* This value is used as the title for the Yes button in the remember credit card page */ +"CreditCard.RememberCard.MainButtonTitle.v122" = "Саклау"; + /* This value is used as the title for the remember credit card page */ "CreditCard.RememberCard.MainTitle.v115" = "Бу карта саклансынмы?"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Shopping.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Shopping.strings index c52fcdce9399..f0d715ea8bb1 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Shopping.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Shopping.strings @@ -64,6 +64,15 @@ /* Title for info card when no information is available at the moment */ "Shopping.InfoCard.NoInfoAvailableRightNow.Title.v120" = "Хәзерге моментта мәгълүмат юк"; +/* This title is displayed on the information card as a confirmation message after a user reports that a previously out-of-stock product is now available. It's meant to acknowledge the user's contribution and encourage community engagement by letting them know their report has been successfully submitted. */ +"Shopping.InfoCard.ReportSubmittedByCurrentUser.Title.v121" = "Белдерүегез өчен рәхмәт!"; + +/* Label for the Learn more button in the Shopping Experience Opt In onboarding Card (Fakespot) */ +"Shopping.OptInCard.LearnMoreButtonTitle.Title.v120" = "Күбрәк белү"; + +/* Text for the main button of the Shopping Experience Opt In onboarding Card (Fakespot) */ +"Shopping.OptInCard.MainButton.Title.v120" = "Әйе, кулланып карау"; + /* Show Firefox Browser Privacy Policy page from the Privacy section in the Shopping Experience Opt In onboarding Card (Fakespot). See https://www.mozilla.org/privacy/firefox/ */ "Shopping.OptInCard.PrivacyPolicy.Button.Title.v120" = "Хосусыйлык сәясәте"; diff --git a/firefox-ios/Shared/cy.lproj/Localizable.strings b/firefox-ios/Shared/cy.lproj/Localizable.strings index 42856fac44ec..76de57b2c1c7 100644 --- a/firefox-ios/Shared/cy.lproj/Localizable.strings +++ b/firefox-ios/Shared/cy.lproj/Localizable.strings @@ -47,7 +47,7 @@ "Add Tab" = "Ychwanegu Tab"; /* Name for button adding current article to reading list in reader mode */ -"Add to Reading List" = "Ychwanegu i'r Rhestr Ddarllen"; +"Add to Reading List" = "Ychwanegu at y Rhestr Ddarllen"; /* Accessibility message e.g. spoken by VoiceOver after the current page gets added to the Reading List using the Reader View button, e.g. by long-pressing it or by its accessibility custom action. */ "Added page to Reading List" = "Ychwanegwyd tudalen i'r Rhestr Ddarllen"; @@ -221,7 +221,7 @@ "Could not load page." = "Methu llwytho'r dudalen."; /* Description for the new ETP mode i.e. standard vs strict */ -"CoverSheet.v24.ETP.Description" = "Mae Diogelwch rhag Tracio Uwch mewnol yn helpu i atal hysbysebion rhag eich dilyn. Trowch Llym ymlaen i rwystro rhagor o dracwyr, hysbysebion a llamlenni."; +"CoverSheet.v24.ETP.Description" = "Mae Diogelwch Rhag Tracio Uwch mewnol yn helpu i atal hysbysebion rhag eich dilyn. Trowch Llym ymlaen i rwystro rhagor o dracwyr, hysbysebion a llamlenni."; /* Text for the new ETP settings button */ "CoverSheet.v24.ETP.Settings.Button" = "Mynd i'r Gosodiadau"; @@ -962,7 +962,7 @@ "ReaderMode.Available.VoiceOverAnnouncement" = "Modd Darllen ar Gael"; /* Name for Mark as read button in reader mode */ -"ReaderModeBar.MarkAsRead.v106" = "Marcio wedi Darllen"; +"ReaderModeBar.MarkAsRead.v106" = "Marcio fel wedi ei Darllen"; /* Name for Mark as unread button in reader mode */ "ReaderModeBar.MarkAsUnread.v106" = "Marcio fel Heb ei Darllen"; diff --git a/firefox-ios/Shared/tt.lproj/LoginManager.strings b/firefox-ios/Shared/tt.lproj/LoginManager.strings index f49e9071a9f6..6f429f2a1559 100644 --- a/firefox-ios/Shared/tt.lproj/LoginManager.strings +++ b/firefox-ios/Shared/tt.lproj/LoginManager.strings @@ -16,6 +16,21 @@ /* Label for the button used to delete the current login. */ "Delete" = "Бетерү"; +/* Prompt option for cancelling out of deletion */ +"DeleteLoginAlert.DeleteButton.Cancel.v122" = "Баш тарту"; + +/* Label for the button used to delete the current login. */ +"DeleteLoginAlert.DeleteButton.Title.v122" = "Бетерү"; + +/* Prompt message warning the user that deleting non-synced logins will permanently remove them, when they attempt to do so */ +"DeleteLoginAlert.Message.Local.v122" = "Сез бу гамәлне кире кайтара алмыйсыз."; + +/* Prompt message warning the user that deleted logins will remove logins from all connected devices */ +"DeleteLoginAlert.Message.Synced.v122" = "Бу адым серсүзне барлык синхронланган җиһазларыгыздан бетерүгә китерәчәк."; + +/* Title for the prompt that appears when the user deletes a login. */ +"DeleteLoginsAlert.Title.v122" = "Серсүз бетерелсенме?"; + /* Label for the button used to deselect all logins. */ "Deselect All" = "Берсен дә сайламау"; @@ -37,6 +52,9 @@ /* Label displayed when no logins are found after searching. */ "No logins found" = "Логиннар табылмады"; +/* Label displayed when no logins are found after searching. */ +"NoLoginsFound.Title.v122" = "Серсүзләр табылмады"; + /* Open and Fill website text selection menu item */ "Open & Fill" = "Aчу һәм тутыру"; diff --git a/firefox-ios/Storage/Rust/RustFirefoxSuggest.swift b/firefox-ios/Storage/Rust/RustFirefoxSuggest.swift index 470cfab613e4..a198baeb6a1b 100644 --- a/firefox-ios/Storage/Rust/RustFirefoxSuggest.swift +++ b/firefox-ios/Storage/Rust/RustFirefoxSuggest.swift @@ -12,8 +12,7 @@ public protocol RustFirefoxSuggestActor: Actor { /// Searches the store for matching suggestions. func query( _ keyword: String, - includeSponsored: Bool, - includeNonSponsored: Bool + providers: [SuggestionProvider] ) async throws -> [RustFirefoxSuggestion] /// Interrupts any ongoing queries for suggestions. @@ -39,16 +38,8 @@ public actor RustFirefoxSuggest: RustFirefoxSuggestActor { public func query( _ keyword: String, - includeSponsored: Bool, - includeNonSponsored: Bool + providers: [SuggestionProvider] ) async throws -> [RustFirefoxSuggestion] { - var providers = [SuggestionProvider]() - if includeSponsored { - providers.append(.amp) - } - if includeNonSponsored { - providers.append(.wikipedia) - } return try store.query(query: SuggestionQuery( keyword: keyword, providers: providers diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/ContentBlockerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/ContentBlockerTests.swift new file mode 100644 index 000000000000..4b6caa5963bd --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/ContentBlockerTests.swift @@ -0,0 +1,29 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import XCTest +@testable import Client + +final class ContentBlockerTests: XCTestCase { + override func setUp() { + super.setUp() + ensureAllRulesAreRemovedFromStore() + } + + private func ensureAllRulesAreRemovedFromStore() { + let expectation = XCTestExpectation() + ContentBlocker.shared.removeAllRulesInStore { + expectation.fulfill() + } + wait(for: [expectation]) + } + + func testCompileListsNotInStore_callsCompletionHandlerSuccessfully() { + let expectation = XCTestExpectation() + ContentBlocker.shared.compileListsNotInStore { + expectation.fulfill() + } + wait(for: [expectation]) + } +} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift index 65bd96caab61..53e1c585abb0 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift @@ -131,6 +131,8 @@ class MockTabManager: TabManager { isPrivate: Bool, previousTabUUID: String) {} + func undoCloseAllTabsLegacy(recentlyClosedTabs: [Client.Tab], previousTabUUID: String, isPrivate: Bool) {} + @discardableResult func addTab(_ request: URLRequest!, configuration: WKWebViewConfiguration!, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchViewControllerTests.swift index f0a00729418a..1011629d7433 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchViewControllerTests.swift @@ -14,11 +14,10 @@ actor MockRustFirefoxSuggest: RustFirefoxSuggestActor { } func query( _ keyword: String, - includeSponsored: Bool, - includeNonSponsored: Bool + providers: [SuggestionProvider] ) async throws -> [RustFirefoxSuggestion] { var suggestions = [RustFirefoxSuggestion]() - if includeSponsored { + if providers.contains(.amp) { suggestions.append(RustFirefoxSuggestion( title: "Mozilla", url: URL(string: "https://mozilla.org")!, @@ -26,7 +25,7 @@ actor MockRustFirefoxSuggest: RustFirefoxSuggestActor { iconImage: nil )) } - if includeNonSponsored { + if providers.contains(.wikipedia) { suggestions.append(RustFirefoxSuggestion( title: "California", url: URL(string: "https://wikipedia.org/California")!, @@ -86,23 +85,48 @@ class SearchViewControllerTest: XCTestCase { profile = nil } - func testFirefoxSuggestionReturnsSponsoredAndNonSponsored() async throws { + func testFirefoxSuggestionReturnsNoSuggestions() async throws { + FxNimbus.shared.features.firefoxSuggestFeature.with(initializer: { _, _ in + FirefoxSuggestFeature(availableSuggestionsTypes: [.amp: false, .wikipedia: false]) + }) + + engines.shouldShowFirefoxSuggestions = false + engines.shouldShowSponsoredSuggestions = false + await searchViewController.loadFirefoxSuggestions()?.value + XCTAssertEqual(searchViewController.firefoxSuggestions.count, 0) + + // Providers set to false, so regardless of the prefs, we shouldn't see anything engines.shouldShowFirefoxSuggestions = true engines.shouldShowSponsoredSuggestions = true await searchViewController.loadFirefoxSuggestions()?.value - - XCTAssertEqual(searchViewController.firefoxSuggestions.count, 2) + XCTAssertEqual(searchViewController.firefoxSuggestions.count, 0) } - func testFirefoxSuggestionReturnsNoSuggestions() async throws { + func testFirefoxSuggestionReturnsNoSuggestionsWhenSuggestionSettingsFalse() async throws { + FxNimbus.shared.features.firefoxSuggestFeature.with(initializer: { _, _ in + FirefoxSuggestFeature(availableSuggestionsTypes: [.amp: true, .wikipedia: true]) + }) + engines.shouldShowFirefoxSuggestions = false engines.shouldShowSponsoredSuggestions = false await searchViewController.loadFirefoxSuggestions()?.value XCTAssertEqual(searchViewController.firefoxSuggestions.count, 0) } + func testFirefoxSuggestionReturnsSponsoredAndNonSponsored() async throws { + FxNimbus.shared.features.firefoxSuggestFeature.with(initializer: { _, _ in + FirefoxSuggestFeature(availableSuggestionsTypes: [.amp: true, .wikipedia: true]) + }) + + engines.shouldShowFirefoxSuggestions = true + engines.shouldShowSponsoredSuggestions = true + await searchViewController.loadFirefoxSuggestions()?.value + + XCTAssertEqual(searchViewController.firefoxSuggestions.count, 2) + } + func testHistoryAndBookmarksAreFilteredWhenShowSponsoredSuggestionsIsTrue() { - profile.prefs.setBool(true, forKey: PrefsKeys.SearchSettings.showFirefoxSponsoredSuggestions) + engines.shouldShowSponsoredSuggestions = true let data = ArrayCursor(data: [ Site(url: "https://example.com?mfadid=adm", title: "Test1"), Site(url: "https://example.com", title: "Test2"), Site(url: "https://example.com?a=b&c=d", title: "Test3")]) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift index ec09d11d9945..04d84f00b44c 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift @@ -616,6 +616,14 @@ class TelemetryWrapperTests: XCTestCase { testEventMetricRecordingSuccess(metric: GleanMetrics.Shopping.surfaceStaleAnalysisShown) } + func test_shoppingAdsSettingToggle_GleanIsCalled() { + let isEnabled = TelemetryWrapper.EventExtraKey.Shopping.adsSettingToggle.rawValue + let extras = [isEnabled: true] + TelemetryWrapper.recordEvent(category: .action, method: .tap, object: .shoppingAdsSettingToggle, extras: extras) + + testEventMetricRecordingSuccess(metric: GleanMetrics.Shopping.surfaceAdsSettingToggled) + } + func test_shoppingNimbusDisabled_GleanIsCalled() { TelemetryWrapper.recordEvent( category: .information, diff --git a/firefox-ios/firefox-ios-tests/Tests/Smoketest1.xctestplan b/firefox-ios/firefox-ios-tests/Tests/Smoketest1.xctestplan index 954cb8a5d2cd..23ee9dea34f4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/Smoketest1.xctestplan +++ b/firefox-ios/firefox-ios-tests/Tests/Smoketest1.xctestplan @@ -54,6 +54,7 @@ "BrowsingPDFTests", "ClipBoardTests\/testClipboard()", "CreditCardsTests\/testAccessingTheCreditCardsSection()", + "CreditCardsTests\/testCreditCardsAutofill()", "CreditCardsTests\/testDeleteButtonFromEditCard()", "CreditCardsTests\/testEditSavedCardsUI()", "CreditCardsTests\/testManageCreditCardsOption()", diff --git a/firefox-ios/firefox-ios-tests/Tests/Smoketest3.xctestplan b/firefox-ios/firefox-ios-tests/Tests/Smoketest3.xctestplan index 68a130c01738..45d81d0f2116 100644 --- a/firefox-ios/firefox-ios-tests/Tests/Smoketest3.xctestplan +++ b/firefox-ios/firefox-ios-tests/Tests/Smoketest3.xctestplan @@ -22,6 +22,7 @@ "BrowsingPDFTests", "ClipBoardTests", "CreditCardsTests\/testAutofillCreditCardsToggleOnOoff()", + "CreditCardsTests\/testCreditCardsAutofill()", "CreditCardsTests\/testManageCreditCardsOption()", "DataManagementTests", "DatabaseFixtureTest", diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/CreditCardsTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/CreditCardsTests.swift index e2d5adf75258..c3c3a1f35647 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/CreditCardsTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/CreditCardsTests.swift @@ -89,31 +89,7 @@ class CreditCardsTests: BaseTestCase { // https://testrail.stage.mozaws.net/index.php?/cases/view/2306972 func testManageCreditCardsOption() { - // Access any website with a credit card form and tap on the credit card number/ credit card name - navigator.nowAt(NewTabScreen) - navigator.goto(CreditCardsSettings) - unlockLoginsView() - mozWaitForElementToExist(app.staticTexts[creditCardsStaticTexts.AutoFillCreditCard.autoFillCreditCards]) - let saveAndFillPaymentMethodsSwitch = app.switches[creditCardsStaticTexts.AutoFillCreditCard.saveAutofillCards] - if saveAndFillPaymentMethodsSwitch.value! as! String == "0" { - saveAndFillPaymentMethodsSwitch.tap() - } - app.buttons[creditCardsStaticTexts.AutoFillCreditCard.addCard].tap() - addCreditCard(name: "Test", cardNumber: "2720994326581252", expirationDate: "0540") - navigator.goto(NewTabScreen) - navigator.openURL("https://checkout.stripe.dev/preview") - waitUntilPageLoad() - app.swipeUp() - let cardNumber = app.webViews["contentView"].webViews.textFields["Card number"] - mozWaitForElementToExist(cardNumber) - cardNumber.tapOnApp() - // Use saved card prompt is displayed - mozWaitForElementToExist(app.buttons[useSavedCard]) - // Expand the prompt - app.buttons[useSavedCard].tap() - unlockLoginsView() - mozWaitForElementToExist(app.staticTexts["Use saved card"]) - mozWaitForElementToExist(app.buttons[manageCards]) + addCreditCardAndReachAutofillWebsite() // Tap on the "Manage credit cards" option app.buttons[manageCards].tap() unlockLoginsView() @@ -177,6 +153,56 @@ class CreditCardsTests: BaseTestCase { mozWaitForElementToExist(app.buttons[useSavedCard]) } + // https://testrail.stage.mozaws.net/index.php?/cases/view/2306971 + func testCreditCardsAutofill() { + addCreditCardAndReachAutofillWebsite() + // Select the saved credit card + mozWaitForElementToExist(app.scrollViews.otherElements.tables.staticTexts["Test"]) + var attempts = 4 + while app.scrollViews.otherElements.tables.staticTexts["Test"].isHittable && attempts > 0 { + app.scrollViews.otherElements.tables.cells.firstMatch.tapOnApp() + attempts -= 1 + } + if app.staticTexts["TEST CARDS"].exists { + app.staticTexts["TEST CARDS"].tap() + } + // The credit card's number and name are imported correctly on the designated fields + let contentView = app.webViews["contentView"].webViews.textFields + XCTAssertEqual(contentView["Card number"].value! as! String, "2720 9943 2658 1252") + XCTAssertEqual(contentView["Expiration"].value! as! String, "05 / 40") + XCTAssertEqual(contentView["Full name on card"].value! as! String, "Test") + XCTAssertEqual(contentView["CVC"].value! as! String, "CVC") + XCTAssertEqual(contentView["ZIP"].value! as! String, "ZIP") + } + + private func addCreditCardAndReachAutofillWebsite() { + // Access any website with a credit card form and tap on the credit card number/ credit card name + navigator.nowAt(NewTabScreen) + navigator.goto(CreditCardsSettings) + unlockLoginsView() + mozWaitForElementToExist(app.staticTexts[creditCardsStaticTexts.AutoFillCreditCard.autoFillCreditCards]) + let saveAndFillPaymentMethodsSwitch = app.switches[creditCardsStaticTexts.AutoFillCreditCard.saveAutofillCards] + if saveAndFillPaymentMethodsSwitch.value! as! String == "0" { + saveAndFillPaymentMethodsSwitch.tap() + } + app.buttons[creditCardsStaticTexts.AutoFillCreditCard.addCard].tap() + addCreditCard(name: "Test", cardNumber: "2720994326581252", expirationDate: "0540") + navigator.goto(NewTabScreen) + navigator.openURL("https://checkout.stripe.dev/preview") + waitUntilPageLoad() + app.swipeUp() + let cardNumber = app.webViews["contentView"].webViews.textFields["Card number"] + mozWaitForElementToExist(cardNumber) + cardNumber.tapOnApp() + // Use saved card prompt is displayed + mozWaitForElementToExist(app.buttons[useSavedCard]) + // Expand the prompt + app.buttons[useSavedCard].tap() + unlockLoginsView() + mozWaitForElementToExist(app.staticTexts["Use saved card"]) + mozWaitForElementToExist(app.buttons[manageCards]) + } + private func addCardAndReachViewCardPage() { navigator.nowAt(NewTabScreen) navigator.goto(CreditCardsSettings) diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift index a2fa0abfc911..eb4e79b32601 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift @@ -72,8 +72,8 @@ class HistoryTests: BaseTestCase { navigator.nowAt(NewTabScreen) navigator.goto(TabTray) navigator.performAction(Action.ToggleSyncMode) - mozWaitForElementToExist(app.tables.cells.staticTexts["Firefox Sync"]) - XCTAssertTrue(app.tables.buttons["Sync and Save Data"].exists, "Sign in button does not appear") + mozWaitForElementToExist(app.otherElements.staticTexts["Firefox Sync"]) + XCTAssertTrue(app.otherElements.buttons["Sync and Save Data"].exists, "Sign in button does not appear") } // https://testrail.stage.mozaws.net/index.php?/cases/view/2307487 diff --git a/firefox-ios/nimbus-features/firefoxSuggestFeature.yaml b/firefox-ios/nimbus-features/firefoxSuggestFeature.yaml index bea34c27f0ec..9108e8b7393e 100644 --- a/firefox-ios/nimbus-features/firefoxSuggestFeature.yaml +++ b/firefox-ios/nimbus-features/firefoxSuggestFeature.yaml @@ -12,7 +12,25 @@ features: additional Search settings. type: Boolean default: false + available-suggestions-types: + description: > + A map of suggestion types to booleans that indicate whether or not the + provider should return suggestions of those types. + type: Map + default: + amp: true + wikipedia: true + defaults: - channel: developer value: status: true + +enums: + SuggestionType: + description: The type of a Firefox Suggest search suggestion. + variants: + amp: + description: A Firefox Suggestion from adMarketplace. + wikipedia: + description: A Firefox Suggestion for a Wikipedia page. diff --git a/firefox-ios/nimbus-features/tabTrayRefactorFeature.yaml b/firefox-ios/nimbus-features/tabTrayRefactorFeature.yaml index 425cd7cf5f86..f8d3fc813994 100644 --- a/firefox-ios/nimbus-features/tabTrayRefactorFeature.yaml +++ b/firefox-ios/nimbus-features/tabTrayRefactorFeature.yaml @@ -12,7 +12,7 @@ features: defaults: - channel: beta value: - enabled: false + enabled: true - channel: developer value: - enabled: false + enabled: true diff --git a/test-fixtures/ci/get-next-pr-version b/test-fixtures/ci/get-next-pr-version new file mode 100755 index 000000000000..6e0f506a036e --- /dev/null +++ b/test-fixtures/ci/get-next-pr-version @@ -0,0 +1,108 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Script Description: +# This script is designed to fetch the next version number for a pull request based on the highest version +# number found in the branch names of a given Git repository. It examines branches prefixed with 'release/v', +# identifies the highest numerical version, increments it by one, and outputs the next version number for use +# in automated processes, such as GitHub Actions. +# +# Usage: +# Execute the script directly in a Bash shell. The script prints the next version number to standard output. +# PLEASE NOTE: The output includes a debugging check for each command in the pipeline, +# and the last line will always be the version number. +# To get the version number, process the output of the script with 'tail -n 1'. +# +# Example for version number: new_version=$(bash get-next-pr-version | tail -n 1) +# Example for full output with debugging info: +# output=$(bash get-next-pr-version) +# echo "$output" +# new_version=$(echo "$output" | tail -n 1) +# +# Example Full Output: +# GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0) +# Copyright (C) 2022 Free Software Foundation, Inc. +# License GPLv3+: GNU GPL version 3 or later +# +# This is free software; you are free to change and redistribute it. +# There is NO WARRANTY, to the extent permitted by law. +# *************************************** +# *** Bash Version: 5.2.26(1)-release *** +# *************************************** +# Command git ls-remote exited with code 0 +# Command grep 'refs/heads/release/v' exited with code 0 +# Command awk -F'/' exited with code 0 +# Command sort -Vr exited with code 0 +# Command head -n 1 (first grep) exited with code 0 +# Command grep -o '[0-9]\+' exited with code 0 +# Command head -n 1 (second head) exited with code 0 +# Command xargs exited with code 0 +# Current version: 123 +# 124 +# +# Requirements: +# - Bash version 3.0 or higher due to the usage of indexed arrays, redirection of read files, and process substitution. +# - Access to Git and the repository from which to fetch version numbers. + +bash --version + +# Get Bash version string +bash_version="Bash Version: ${BASH_VERSION[0]}" + +# Calculate the length of the version string plus padding and asterisks +len=${#bash_version} +total_length=$((len + 8)) # Adding 6 for spaces and asterisks + +# Generate a line of asterisks of the required length +line=$(printf '%*s' "$total_length" | tr ' ' '*') + +# Output +echo "$line" +echo "*** $bash_version ***" +echo "$line" + +# Check for Bash version 3.0 or above +if [ "${BASH_VERSINFO[0]}" -lt 3 ]; then + echo "Error: Bash version 3.0 or higher is required to run this script." + exit 1 +fi + +# Run pipeline and redirect the output to a temporary file +temp_file=$(mktemp) +git ls-remote --heads origin \ + | grep 'refs/heads/release/v' \ + | awk -F'/' '{print $NF}' \ + | sort -Vr \ + | head -n 1 \ + | grep -o '[0-9]\+' \ + | head -n 1 \ + | xargs > "${temp_file}" + +# Check the pipeline status +return_codes=("${PIPESTATUS[@]}") + +# Read the version from the temporary file +my_version=$(<"${temp_file}") + +# Cleanup +rm "${temp_file}" + +commands=("git ls-remote" "grep 'refs/heads/release/v'" "awk -F'/'" "sort -Vr" "head -n 1 (first grep)" "grep -o '[0-9]\+'" "head -n 1 (second head)" "xargs") +# Check each command for failure +for i in "${!return_codes[@]}"; do + echo "Command ${commands[$i]} exited with code ${return_codes[$i]}" + if [ "${return_codes[$i]}" -ne 0 ]; then + # Github Actions that use the branch name as a build parameter for the version number + # shouldn't fail if this script fails. Don't exit with a non-zero status here. + echo "Failure in pipeline at command index: $i with exit code ${return_codes[$i]}" + fi +done + +# Echo the version captured +echo "Current version: ${my_version}" +next_version=$((my_version + 1)) + +# DO NOT MODIFY THE OUTPUT FORMAT +# THIS IS USED BY GITHUB ACTIONS +echo "${next_version}" diff --git a/test-fixtures/ci/uri_update.py b/test-fixtures/ci/uri_update.py new file mode 100644 index 000000000000..be61fee4e57e --- /dev/null +++ b/test-fixtures/ci/uri_update.py @@ -0,0 +1,197 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +""" +This script updates the URI schemes in the iOS Swift file based on the latest CSV data from IANA. +It fetches the CSV, parses it for permanent URI schemes, compares them with the existing ones in the Swift file, +and updates the Swift file if there are new or removed URIs. + +Usage: + python uri_update.py +""" + + +import io +import logging +import re +import sys + +import pandas as pd # pandas 3.0 will require pyarrow as a dependency +import requests +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + + +CONFIG = { + "URI_WEBSITE": "https://www.iana.org/assignments/uri-schemes/uri-schemes-1.csv", + "IOS_URI_PATH": "firefox-ios/Shared/Extensions/", + "IOS_URIS_FILE": "URLExtensions.swift", + "RETRIES": 2, + "BACKOFF_FACTOR": 0.3, + "STATUS_FORCELIST": [500, 502, 503, 504], +} + + +# use exponential backoff because why not +def get_uri_csv(url): + """ + Fetches the URI CSV from the specified URL with retry logic. + + Parameters: + - url (str): The URL to fetch the CSV from. + + Returns: + - str: The content of the CSV file. + """ + session = requests.Session() + retries = Retry( + total=CONFIG["RETRIES"], + backoff_factor=CONFIG["BACKOFF_FACTOR"], + status_forcelist=CONFIG["STATUS_FORCELIST"], + ) + session.mount("http://", HTTPAdapter(max_retries=retries)) + session.mount("https://", HTTPAdapter(max_retries=retries)) + + try: + response = session.get(url) + response.raise_for_status() + return response.content.decode("utf-8") + except Exception as e: + logging.error("Failed to get CSV: %r", e) + sys.exit(1) + + +def parse_uri_csv(api_response_csv): + """ + Parses the given CSV content for permanent URI schemes. + + Parameters: + - api_response_csv (str): The CSV content returned from the api as a string. + + Returns: + - list: A list of permanent URI schemes. + """ + df = pd.read_csv(io.StringIO(api_response_csv)) + filtered_df = df[ + (df["Status"] == "Permanent") + & (~df["URI Scheme"].str.contains("OBSOLETE", na=False)) + ] + return filtered_df["URI Scheme"].dropna().sort_values().tolist() + + +def update_swift_file(new_urischemes, swift_file_path): + """ + Updates the Swift file with new URI schemes. + + Parameters: + - new_urischemes (list): A list of new URI schemes to update in the Swift file. + - swift_file_path (str): The file path of the Swift file to update. + + """ + try: + start_index, end_index = find_uris_section_indices(swift_file_path) + + with open(swift_file_path, "r") as file: + lines = file.readlines() + + new_lines = [f' "{scheme}",\n' for scheme in new_urischemes] + # end_index must be non-inclusive to avoid removing ']' when there are fewer URIs in the new list + updated_lines = lines[:start_index] + new_lines + lines[end_index - 1 :] + + with open(swift_file_path, "w") as file: + file.writelines(updated_lines) + except Exception as e: + logging.error("Failed to update the Swift file: %r", e) + sys.exit(1) + + +def find_uris_section_indices(swift_file_path): + """ + Finds the start and end indices of the URI schemes section in the Swift file. + + Parameters: + - swift_file_path (str): The file path of the Swift file. + + Returns: + - tuple: A tuple containing the start and end line numbers of the URI schemes section. + """ + start_index = -1 + end_index = -1 + + try: + with open(swift_file_path, "r") as file: + # Use enumerate to iterate with 1-based index + for i, line in enumerate(file, start=1): + if "private let permanentURISchemes" in line and start_index == -1: + start_index = i + continue + if start_index != -1 and "]" in line: + end_index = i + break + except Exception as e: + logging.error("An error occurred while reading %r: %r", swift_file_path, e) + sys.exit(1) + + if start_index == -1 or end_index == -1: + logging.error("Could not find 'permanentURISchemes' array in the Swift file.") + sys.exit(1) + return start_index, end_index + + +def extract_current_uris(swift_file_path): + """ + Extracts the current URIs from the Swift file. + + Parameters: + - swift_file_path (str): The file path of the Swift file. + + Returns: + - list: A list of URIs extracted from the Swift file. + """ + # start/end use 1-based index for true line number + start_line, end_line = find_uris_section_indices(swift_file_path) + uris = [] + try: + with open(swift_file_path, "r") as file: + # use enumerate and start i at 1 for 1-based index + for i, line in enumerate(file, start=1): + if start_line <= i <= end_line: + # Extract URI between the first pair of double quotes + match = re.search(r'"([^"]+)"', line) + if match: + uris.append(match.group(1)) + elif i == end_line: + break + except Exception as e: + logging.error("An error occurred while reading %r: %r", swift_file_path, e) + sys.exit(1) + return uris + + +def main(): + logging.info("Current CONFIG for task:\n%r", CONFIG) + csv_url = CONFIG["URI_WEBSITE"] + swift_file_path = "%s/%s" % (CONFIG["IOS_URI_PATH"], CONFIG["IOS_URIS_FILE"]) + + try: + uri_response_csv = get_uri_csv(csv_url) + permanent_urischemes = parse_uri_csv(uri_response_csv) + current_uris = extract_current_uris(swift_file_path) + uri_diff = [uri for uri in permanent_urischemes if uri not in set(current_uris)] + + if uri_diff: + logging.info(f"Updating URIs with diff: {uri_diff}") + update_swift_file(permanent_urischemes, swift_file_path) + else: + logging.info("No update needed. File contains latest permanent URISchemes.") + except Exception as e: + logging.error("Error occurred: %r", e) + sys.exit(1) + + +if __name__ == "__main__": + # config logging here to avoid collisions during imports + logging.basicConfig( + level=logging.INFO, format="%(asctime)s-%(levelname)s: %(message)s" + ) + main() diff --git a/test-fixtures/generate-metrics.sh b/test-fixtures/generate-metrics.sh index 85ee9dac7177..cd9950c3dc83 100755 --- a/test-fixtures/generate-metrics.sh +++ b/test-fixtures/generate-metrics.sh @@ -4,8 +4,8 @@ set -e BUILD_LOG_FILE="$1" TYPE_LOG_FILE="$2" -THRESHOLD_UNIT_TEST=15 -THRESHOLD_XCUITEST=15 +THRESHOLD_UNIT_TEST=13 +THRESHOLD_XCUITEST=13 WARNING_COUNT=$(grep -E -v "SourcePackages/checkouts" "$BUILD_LOG_FILE" | grep -E "(^|:)[0-9]+:[0-9]+:|warning:|ld: warning:|:0: warning:|fatal|===" | uniq | wc -l)