Slide 1

Slide 1 text

Building Safari app extensions Kuba Suder @kuba_suder • h.ps:/ /mackuba.eu

Slide 2

Slide 2 text

A li%le bit of history...

Slide 3

Slide 3 text

Safari Extensions ~ Safari 5 (2010)

Slide 4

Slide 4 text

Safari Extensions Gallery

Slide 5

Slide 5 text

Extension Builder

Slide 6

Slide 6 text

(Old) Safari Extensions Pros: • you can use web technologies (HTML/JS) • no ObjC/Swi> knowledge required • easy to port from Chrome/Firefox

Slide 7

Slide 7 text

(Old) Safari Extensions Cons: • you have to use web technologies ! • no way to make " # • no analy5cs? $ • the review process is an absolute nightmare %

Slide 8

Slide 8 text

2015: Content Blockers

Slide 9

Slide 9 text

Content Blockers on the Mac

Slide 10

Slide 10 text

2016: Safari App Extensions

Slide 11

Slide 11 text

Safari App Extensions Pros: • built in Xcode/Swi1 ❤ (or ObjC if you have to :) • can be distributed via Mac App Store • updated together with the app • normal app review process " • can be paid ##

Slide 12

Slide 12 text

Safari App Extensions Cons: • no way to reuse code/skills from other browsers • needs to be contained in an app

Slide 13

Slide 13 text

Building an extension

Slide 14

Slide 14 text

Building an extension • Info.plist • basic metadata • configura7on in NSExtension

Slide 15

Slide 15 text

Building an extension • Main object: SFSafariExtensionHandler • handle toolbar bu3on callbacks • handle context menu callbacks • handle messages from the JavaScript side • whatever else you need running in the background

Slide 16

Slide 16 text

Injec&ng stylesheets • SFSafariStyleSheet key • can be applied only to selected sites/pages

Slide 17

Slide 17 text

Injec&ng scripts • SFSafariContentScript key • can be applied only to selected sites/pages • runs in the opened website's environment • safari.extension - communicate with Safari and your app • can access assets from the app bundle

Slide 18

Slide 18 text

Communica)ng with the script JS: safari.self.addEventListener('message', function() { … }); safari.extension.dispatchMessage('newItemsLoaded'); App: func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]!) { page.dispatchMessageToScript(withName: "reload", userInfo: nil) }

Slide 19

Slide 19 text

Adding a toolbar bu.on

Slide 20

Slide 20 text

Adding a toolbar bu.on • SFSafariToolbarItem key & class • icon as a template image • PDF (!), 19×19, simple black outline • 10.1: can be changed at runDme • can be enabled/disabled • can have a badge

Slide 21

Slide 21 text

Responding to bu.on clicks Op#on 1: no UI • get a call toolbarItemClicked(in:) Op#on 2: popover • na$ve Cocoa view + SFSafariExtensionViewController • scales automa$cally, must use AutoLayout • might behave in weird ways (layers, transparency, anima$ons)

Slide 22

Slide 22 text

Context menu items

Slide 23

Slide 23 text

Context menu items • SFSafariContextMenu key • contextMenuItemSelected(withCommand:in:userInfo:) • valida>on - can be shown or hidden depending on the context • you can add mul>ple items

Slide 24

Slide 24 text

Content blocking • separate extension target • users see two extensions, can enable them separately:

Slide 25

Slide 25 text

Content blocking • Safari extension can manage the content blocker (Safari 10.1) SFContentBlockerManager.getStateOfContentBlocker( withIdentifier: "eu.mackuba.BannerHunterMac.SafariExtension", completionHandler: { state, error in … } ) SFContentBlockerManager.reloadContentBlocker(withIdentifier: "…") { … }

Slide 26

Slide 26 text

Managing the extension SFSafariExtensionManager.getStateOfSafariExtension( withIdentifier: "eu.mackuba.BannerHunterMac.ReportSitePopup", completionHandler: { state, error in … } )

Slide 27

Slide 27 text

Site access permissions • declare access level: None / Some / All • access needed to read URL :( SFSafariWebsiteAccess Level Some Allowed Domains *.facebook.com

Slide 28

Slide 28 text

Site access permissions (different message in 10.1?)

Slide 29

Slide 29 text

Communica)ng with the app • enable App Groups • project capabili3es • NOT on developer.apple.com (unlike iOS apps)

Slide 30

Slide 30 text

Communica)ng with the app • shared data folder let appGroupId = "KL585M628D.eu.mackuba.BannerHunter" let fileManager = FileManager.defaultManager() let container = fileManager.containerURL( forSecurityApplicationGroupIdentifier: appGroupId ) • located in: ~/Library/Group Containers

Slide 31

Slide 31 text

Communica)ng with the app • shared user defaults container let defaults = UserDefaults(suiteName: appGroupId) • no support for "defaults write …" ! • edit .plist file in the Group Container • killall cfprefsd • plz file a radar ;)

Slide 32

Slide 32 text

Communica)ng with the app (10.1) SFSafariApplication.dispatchMessage( withName: "startSync", toExtensionWithIdentifier: "eu.mackuba.BannerHunterMac.Extension", userInfo: ["userId": 567] ) func messageReceivedFromContainingApp( withName messageName: String, userInfo: [String : Any]? = nil) { startSync() }

Slide 33

Slide 33 text

Pro %ps • Develop → Allow Unsigned Extensions • console.log- and NSLog-based debugging ! • disable app sandbox during development • use a framework to share code • extension is disabled by default aDer installaEon • for non-MAS apps, app needs to be started once

Slide 34

Slide 34 text

Pro %ps • opening Safari preferences window: SFSafariApplication.showPreferencesForExtension( withIdentifier: "eu.mackuba.BannerHunterMac.ReportSitePopup" )

Slide 35

Slide 35 text

Great success! !

Slide 36

Slide 36 text

… Profit! !! !

Slide 37

Slide 37 text

Links Safari App Extension Programming Guide: h8ps:/ /developer.apple.com/library/content/documenta@on/ NetworkingInternetWeb/Conceptual/SafariAppExtension_PG/ API reference: h,ps:/ /developer.apple.com/reference/safariservices

Slide 38

Slide 38 text

Thank you :)