Skip to content

Commit a4d5986

Browse files
committed
Trigger search result ad clicked event on iOS
1 parent d834bf2 commit a4d5986

File tree

6 files changed

+88
-35
lines changed

6 files changed

+88
-35
lines changed

ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,15 @@ extension BrowserViewController: WKNavigationDelegate {
330330
tab?.loadRequest(modifiedRequest)
331331
return (.cancel, preferences)
332332
}
333-
334-
tab?.braveSearchResultAdManager = BraveSearchResultAdManager(url: requestURL, rewards: rewards, isPrivateBrowsing: isPrivateBrowsing)
333+
334+
if let braveSearchResultAdManager = tab?.braveSearchResultAdManager,
335+
braveSearchResultAdManager.isSearchResultAdClickedURL(requestURL),
336+
navigationAction.navigationType == .linkActivated {
337+
braveSearchResultAdManager.maybeTriggerSearchResultAdClickedEvent(requestURL)
338+
tab?.braveSearchResultAdManager = nil
339+
} else {
340+
tab?.braveSearchResultAdManager = BraveSearchResultAdManager(url: requestURL, rewards: rewards, isPrivateBrowsing: isPrivateBrowsing)
341+
}
335342

336343
// We fetch cookies to determine if backup search was enabled on the website.
337344
let profile = self.profile

ios/brave-ios/Sources/Brave/Frontend/Browser/Search/BraveSearchResultAdManager.swift

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,67 @@ import BraveCore
77

88
// A helper class to handle Brave Search Result Ads.
99
class BraveSearchResultAdManager: NSObject {
10+
private let searchResultAdClickedUrlPath = "/a/redirect"
11+
12+
private let placementId = "placement_id"
13+
1014
private let rewards: BraveRewards
1115

16+
private var searchResultAds = [String: BraveAds.SearchResultAdInfo]()
17+
1218
init?(url: URL, rewards: BraveRewards, isPrivateBrowsing: Bool) {
1319
if !BraveAds.shouldSupportSearchResultAds() ||
14-
!BraveSearchManager.isValidURL(url) ||
15-
isPrivateBrowsing ||
16-
rewards.isEnabled {
20+
!BraveSearchManager.isValidURL(url) ||
21+
isPrivateBrowsing ||
22+
rewards.isEnabled {
1723
return nil
1824
}
1925

2026
self.rewards = rewards
2127
}
2228

23-
func triggerSearchResultAdViewedEvent(_ searchResultAdInfo: BraveAds.SearchResultAdInfo) {
24-
rewards.ads.triggerSearchResultAdEvent(
25-
searchResultAdInfo,
26-
eventType: .viewed,
27-
completion: { _ in })
29+
func isSearchResultAdClickedURL(_ url: URL) -> Bool {
30+
return getPlacementID(url) != nil
31+
}
32+
33+
func triggerSearchResultAdViewedEvent(
34+
placementId: String,
35+
searchResultAd: BraveAds.SearchResultAdInfo) {
36+
searchResultAds[placementId] = searchResultAd
37+
38+
guard let searchResultAd = searchResultAds[placementId] else {
39+
return
40+
}
41+
42+
rewards.ads.triggerSearchResultAdEvent(
43+
searchResultAd,
44+
eventType: .viewed,
45+
completion: { _ in })
46+
}
47+
48+
func maybeTriggerSearchResultAdClickedEvent(_ url: URL) {
49+
guard let placementId = getPlacementID(url) else {
50+
return
51+
}
52+
53+
guard let searchResultAd = searchResultAds[placementId] else {
54+
return
55+
}
56+
57+
rewards.ads.triggerSearchResultAdEvent(
58+
searchResultAd,
59+
eventType: .clicked,
60+
completion: { _ in })
61+
}
62+
63+
private func getPlacementID(_ url: URL) -> String? {
64+
if !BraveSearchManager.isValidURL(url) ||
65+
url.path != searchResultAdClickedUrlPath {
66+
return nil
67+
}
68+
guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else {
69+
return nil
70+
}
71+
return queryItems.first(where: { $0.name == placementId })?.value
2872
}
2973
}

ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/BraveSearchResultAdScriptHandler.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
// License, v. 2.0. If a copy of the MPL was not distributed with this
44
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
55

6-
import WebKit
76
import BraveCore
7+
import BraveShared
88
import os.log
9+
import WebKit
910

1011
class BraveSearchResultAdScriptHandler: TabContentScript {
1112
private struct SearchResultAdResponse: Decodable {
@@ -29,16 +30,14 @@ class BraveSearchResultAdScriptHandler: TabContentScript {
2930

3031
fileprivate weak var tab: Tab?
3132

32-
private static let secondsInDay = 24 * 60 * 60
33-
3433
init(tab: Tab) {
3534
self.tab = tab
3635
}
3736

3837
static let scriptName = "BraveSearchResultAdScript"
3938
static let scriptId = UUID().uuidString
4039
static let messageHandlerName = "\(scriptName)_\(messageUUID)"
41-
static let scriptSandbox: WKContentWorld = .page
40+
static let scriptSandbox: WKContentWorld = .defaultClient
4241
static let userScript: WKUserScript? = {
4342
guard var script = loadUserScript(named: scriptName) else {
4443
return nil
@@ -74,8 +73,8 @@ class BraveSearchResultAdScriptHandler: TabContentScript {
7473
let messageData = try? JSONSerialization.data(withJSONObject: message.body, options: []),
7574
let searchResultAds = try? JSONDecoder().decode(SearchResultAdResponse.self, from: messageData)
7675
else {
77-
Logger.module.error("Failed to parse search result ads response")
78-
return
76+
Logger.module.error("Failed to parse search result ads response")
77+
return
7978
}
8079

8180
processSearchResultAds(searchResultAds, braveSearchResultAdManager: braveSearchResultAdManager)
@@ -95,11 +94,11 @@ class BraveSearchResultAdScriptHandler: TabContentScript {
9594
var conversion: BraveAds.ConversionInfo?
9695
if let conversionUrlPatternValue = ad.conversionUrlPatternValue,
9796
let conversionObservationWindowValue = ad.conversionObservationWindowValue {
98-
let timeInterval = TimeInterval(conversionObservationWindowValue * BraveSearchResultAdScriptHandler.secondsInDay)
97+
let timeInterval = TimeInterval(conversionObservationWindowValue) * 1.days
9998
conversion = .init(
100-
urlPattern: conversionUrlPatternValue,
101-
verifiableAdvertiserPublicKeyBase64: ad.conversionAdvertiserPublicKeyValue,
102-
observationWindow: Date(timeIntervalSince1970: timeInterval)
99+
urlPattern: conversionUrlPatternValue,
100+
verifiableAdvertiserPublicKeyBase64: ad.conversionAdvertiserPublicKeyValue,
101+
observationWindow: Date(timeIntervalSince1970: timeInterval)
103102
)
104103
}
105104

@@ -117,7 +116,8 @@ class BraveSearchResultAdScriptHandler: TabContentScript {
117116
conversion: conversion
118117
)
119118

120-
braveSearchResultAdManager.triggerSearchResultAdViewedEvent(searchResultAd)
119+
braveSearchResultAdManager.triggerSearchResultAdViewedEvent(
120+
placementId: ad.placementId, searchResultAd:searchResultAd)
121121
}
122122
}
123123
}

ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/DomainSpecific/Paged/BraveSearchResultAdScript.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Copyright 2024 The Brave Authors. All rights reserved.
1+
// Copyright (c) 2024 The Brave Authors. All rights reserved.
22
// This Source Code Form is subject to the terms of the Mozilla Public
3-
// License, v. 2.0. If a copy of the MPL was not distributed with this
4-
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
3+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
4+
// You can obtain one at https://mozilla.org/MPL/2.0/.
55

66
'use strict';
77

@@ -14,8 +14,10 @@ window.__firefox__.includeOnce('BraveSearchResultAdScript', function($) {
1414
});
1515

1616
let getJsonLdCreatives = () => {
17-
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
18-
const jsonLdList = Array.from(scripts).map(script => JSON.parse(script.textContent));
17+
const scripts =
18+
document.querySelectorAll('script[type="application/ld+json"]');
19+
const jsonLdList =
20+
Array.from(scripts).map(script => JSON.parse(script.textContent));
1921

2022
if (!jsonLdList) {
2123
return [];
@@ -32,8 +34,10 @@ window.__firefox__.includeOnce('BraveSearchResultAdScript', function($) {
3234
'data-description': 'description',
3335
'data-rewards-value': 'rewardsValue',
3436
'data-conversion-url-pattern-value': 'conversionUrlPatternValue',
35-
'data-conversion-advertiser-public-key-value': 'conversionAdvertiserPublicKeyValue',
36-
'data-conversion-observation-window-value': 'conversionObservationWindowValue'
37+
'data-conversion-advertiser-public-key-value':
38+
'conversionAdvertiserPublicKeyValue',
39+
'data-conversion-observation-window-value':
40+
'conversionObservationWindowValue'
3741
};
3842

3943
let jsonLdCreatives = [];

ios/browser/api/ads/brave_ads.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ OBJC_EXPORT
6060
/// disabled.
6161
+ (BOOL)shouldAlwaysRunService;
6262

63-
/// Returns |true| if search result ads are supported.
63+
/// Returns `true` if search result ads are supported.
6464
+ (BOOL)shouldSupportSearchResultAds;
6565

6666
/// Whether or not Brave Ads is enabled and the user should receive
@@ -122,8 +122,7 @@ OBJC_EXPORT
122122
(BraveAdsPromotedContentAdEventType)eventType
123123
completion:(void (^)(BOOL success))completion;
124124

125-
- (void)triggerSearchResultAdEvent:
126-
(BraveAdsSearchResultAdInfo*)searchResultAdInfo
125+
- (void)triggerSearchResultAdEvent:(BraveAdsSearchResultAdInfo*)searchResultAd
127126
eventType:(BraveAdsSearchResultAdEventType)eventType
128127
completion:(void (^)(BOOL success))completion;
129128

ios/browser/api/ads/brave_ads.mm

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,16 +1733,15 @@ - (void)triggerPromotedContentAdEvent:(NSString*)placementId
17331733
}));
17341734
}
17351735

1736-
- (void)triggerSearchResultAdEvent:
1737-
(BraveAdsSearchResultAdInfo*)searchResultAdInfo
1736+
- (void)triggerSearchResultAdEvent:(BraveAdsSearchResultAdInfo*)searchResultAd
17381737
eventType:(BraveAdsSearchResultAdEventType)eventType
17391738
completion:(void (^)(BOOL success))completion {
1740-
if (![self isServiceRunning] || !searchResultAdInfo) {
1739+
if (![self isServiceRunning] || !searchResultAd) {
17411740
return;
17421741
}
17431742

17441743
ads->TriggerSearchResultAdEvent(
1745-
searchResultAdInfo.cppObjPtr,
1744+
searchResultAd.cppObjPtr,
17461745
static_cast<brave_ads::mojom::SearchResultAdEventType>(eventType),
17471746
base::BindOnce(^(const bool success) {
17481747
completion(success);

0 commit comments

Comments
 (0)