Skip to content

Commit

Permalink
allow to register early scripts (#930)
Browse files Browse the repository at this point in the history
Task/Issue URL:  https://app.asana.com/0/0/1207981214658241/f
iOS PR:  duckduckgo/iOS#3199
macOS PR:  duckduckgo/macos-browser#3081
What kind of version bump will this require?: Major/Minor/Patch

**Description**: Allows to load early script before rules are compiled
  • Loading branch information
SabrinaTardio authored Aug 7, 2024
1 parent 92ecebf commit 1d50e24
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,17 @@ final public class UserContentController: WKUserContentController {
@MainActor
private let scriptMessageHandler = PermanentScriptMessageHandler()

/// if earlyAccessHandlers (WKScriptMessageHandlers) are provided they are installed without waiting for contentBlockingAssets to be loaded if.
@MainActor
public init<Pub, Content>(assetsPublisher: Pub, privacyConfigurationManager: PrivacyConfigurationManaging)
public init<Pub, Content>(assetsPublisher: Pub, privacyConfigurationManager: PrivacyConfigurationManaging, earlyAccessHandlers: [UserScript] = [])
where Pub: Publisher, Content: UserContentControllerNewContent, Pub.Output == Content, Pub.Failure == Never {

self.privacyConfigurationManager = privacyConfigurationManager
super.init()

// Install initial WKScriptMessageHandlers if any. Currently, no WKUserScript are provided at initialization.
installUserScripts([], handlers: earlyAccessHandlers)

assetsPublisherCancellable = assetsPublisher.sink { [weak self, selfDescr=self.debugDescription] content in
os_log(.debug, log: .contentBlocking, "\(selfDescr): 📚 received content blocking assets")
Task.detached { [weak self] in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class UserContentControllerTests: XCTestCase {
@MainActor
override func setUp() async throws {
_=WKUserContentController.swizzleContentRuleListsMethodsOnce
_=WKUserContentController.swizzleScriptMessageHandlerMethodsOnce
ucc = UserContentController(assetsPublisher: assetsSubject, privacyConfigurationManager: PrivacyConfigurationManagerMock())
ucc.delegate = self
}
Expand All @@ -69,6 +70,16 @@ final class UserContentControllerTests: XCTestCase {
}

// MARK: - Tests
@MainActor
func testWhenUserContentControllerInitialisedWithEarlyAccessScriptsThenHandlersAreRegistered() async throws {
let script1 = MockUserScript(messageNames: ["message1"])
let script2 = MockUserScript(messageNames: ["message2"])
ucc = UserContentController(assetsPublisher: assetsSubject, privacyConfigurationManager: PrivacyConfigurationManagerMock(), earlyAccessHandlers: [script1, script2])
ucc.delegate = self

XCTAssertTrue(ucc.registeredScriptHandlerNames.contains("message1"))
XCTAssertTrue(ucc.registeredScriptHandlerNames.contains("message2"))
}

@MainActor
func testWhenContentBlockingAssetsPublished_contentRuleListsAreInstalled() async throws {
Expand Down Expand Up @@ -250,6 +261,34 @@ extension WKUserContentController {
}
}

extension WKUserContentController {
private static let scriptHandlersKey = UnsafeRawPointer(bitPattern: "scriptHandlersKey".hashValue)!

private static var installedScriptHandlers: [(WKScriptMessageHandler, WKContentWorld, String)] {
get {
objc_getAssociatedObject(self, scriptHandlersKey) as? [(WKScriptMessageHandler, WKContentWorld, String)] ?? []
}
set {
objc_setAssociatedObject(self, scriptHandlersKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

static let swizzleScriptMessageHandlerMethodsOnce: Void = {
let originalAddMethod = class_getInstanceMethod(WKUserContentController.self, #selector(WKUserContentController.add(_:contentWorld:name:)))!
let swizzledAddMethod = class_getInstanceMethod(WKUserContentController.self, #selector(swizzled_add(_:contentWorld:name:)))!
method_exchangeImplementations(originalAddMethod, swizzledAddMethod)
}()

@objc dynamic private func swizzled_add(_ scriptMessageHandler: WKScriptMessageHandler, contentWorld: WKContentWorld, name: String) {
Self.installedScriptHandlers.append((scriptMessageHandler, contentWorld, name))
swizzled_add(scriptMessageHandler, contentWorld: contentWorld, name: name) // calling the original method
}

var registeredScriptHandlerNames: [String] {
return Self.installedScriptHandlers.map { $0.2 }
}
}

extension UserContentControllerTests: UserContentControllerDelegate {
func userContentController(_ userContentController: UserContentController, didInstallContentRuleLists contentRuleLists: [String: WKContentRuleList], userScripts: any UserScriptsProvider, updateEvent: ContentBlockerRulesManager.UpdateEvent) {
onAssetsInstalled?((contentRuleLists, userScripts, updateEvent))
Expand Down Expand Up @@ -305,3 +344,17 @@ class PrivacyConfigurationMock: PrivacyConfiguration {
func userEnabledProtection(forDomain: String) {}
func userDisabledProtection(forDomain: String) {}
}

class MockUserScript: NSObject, UserScript {
var source: String = "MockUserScript"
var injectionTime: WKUserScriptInjectionTime = .atDocumentEnd
var forMainFrameOnly: Bool = false
var messageNames: [String]

init(messageNames: [String]) {
self.messageNames = messageNames
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
}
}

0 comments on commit 1d50e24

Please sign in to comment.