swiftsafari-content-blocker

Content Blocker extension with a String instead of a file


I'm using the function NSItemProvider(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json") in a content blocker extension.

The thing is that all my rules are stored in a few dictionaries and, when I'm using this function, it's always because the rules have changed. I'm currently creating a String from these dictionaries that looks like "[{\"trigger\": {\"url-filter\": \"webiste.com\"},\"action\": {"\type\": \"css-display-none\",\"selector\":\".testContentBlocker\"}}]"and I have to transform it in a JSON file to finally be able to use it in the function written previously described.

Instead of having to put the String in a JSON file to be able to use it, could I do something simpler to use NSItemProvider()?


Solution

  • By loading the extension up in the debugger (and by using Hopper), you can see that NSItemProvider(contentsOfURL:) is simply registering to provide data from the file's contents with type public.json.

    (lldb) po attachment
    <NSItemProvider: 0x7fd4c250f2a0> {types = (
        "public.file-url",
        "public.json"
    )}
    

    It's roughly equivalent to this:

    // possible implementation of NSItemProvider.init(contentsOfURL:)
    convenience init?(contentsOfURL fileURL: NSURL!)
    {
        self.init(item: fileURL, typeIdentifier: (fileURL.fileURL ? kUTTypeFileURL : kUTTypeURL) as String)
    
        let type = UTTypeCreatePreferredIdentifierForTag(
            kUTTagClassFilenameExtension, fileURL.pathExtension!, nil)?.takeRetainedValue() as! String
    
        registerItemForTypeIdentifier(type) { completionHandler, expectedValueClass, options in
            let data = try! NSData(contentsOfURL: fileURL, options: .DataReadingMappedAlways)
            completionHandler(data, nil)
        }
    }
    

    So you can do this yourself, in memory:

    // get the data
    let data = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json")!)
    
    // put the data in an item provider
    let attachment = NSItemProvider(item: data, typeIdentifier: kUTTypeJSON as String)
    
    // send the item to Safari
    let item = NSExtensionItem()
    item.attachments = [attachment]
    context.completeRequestReturningItems([item], completionHandler: nil);
    

    If you want to provide content dynamically, you can use NSJSONSerialization to transform a dictionary into NSData at runtime.