Page 1
© 2016 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
App Frameworks #WWDC16
Session 214
Extending your Appwith Safari App Extensions
Brian Weinstein Safari EngineerDamian Kaleta Safari EngineerZach Li Safari Engineer
Page 2
CapabilitiesSafari App Extensions
Customize web pages
NEW
Page 3
CapabilitiesSafari App Extensions
Customize web pagesBlock loading of resources or elements
NEW
Page 4
CapabilitiesSafari App Extensions
Customize web pagesBlock loading of resources or elements Add toolbar buttons
NEW
Page 5
CapabilitiesSafari App Extensions
Customize web pagesBlock loading of resources or elements Add toolbar buttonsDisplay popovers
NEW
Page 6
CapabilitiesSafari App Extensions
Customize web pagesBlock loading of resources or elements Add toolbar buttonsDisplay popoversAdd items to context menus on webpages
NEW
Page 7
Benefits - Built Using App ExtensionsSafari App Extensions
Developed using Xcode
Page 8
Benefits - Built Using App ExtensionsSafari App Extensions
Developed using XcodeRun native code
Page 9
Benefits - Distributed with your appSafari App Extensions
Sold through the Mac App Store
Page 10
Benefits - Distributed with your appSafari App Extensions
Sold through the Mac App StoreSame version as your app
Page 11
TypesSafari App Extensions
Page 12
TypesSafari App Extensions
Content Blockers
Page 13
TypesSafari App Extensions
Content BlockersPage modification and communication with native code
Page 14
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 15
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 16
Content Blockers
Zach Li Safari Engineer
Page 17
and many more…
17
Page 18
Bring an iOS content blocker to macOSContent Blockers
Page 19
Bring an iOS content blocker to macOSContent Blockers
Safari Extensibility: Content Blocking and Shared Links WWDC 2015
Page 21
Dessert BlockerDemo
Page 23
New APIContent Blockers
Page 24
// SFContentBlockerManager
public class SFContentBlockerManager : NSObject { public class func getStateOfContentBlocker(identifier identifier: String,
completionHandler: (SFContentBlockerState?, NSError?) -> Void)
}
// SFContentBlockerState
public class SFContentBlockerState : NSObject {
public var isEnabled: Bool { get }
}
NEW
Page 25
// SFContentBlockerManager
public class SFContentBlockerManager : NSObject { public class func getStateOfContentBlocker(identifier identifier: String,
completionHandler: (SFContentBlockerState?, NSError?) -> Void)
}
// SFContentBlockerState
public class SFContentBlockerState : NSObject {
public var isEnabled: Bool { get }
}
NEW
Page 26
SFSafariServicesAvailable()NEWCheck Safari Services APIs Availability
Safari Services APIs will be available on:• macOS Sierra• OS X El Capitan with Safari 10 installed
Page 27
SFSafariServicesAvailable()NEWCheck Safari Services APIs Availability
Safari Services APIs will be available on:• macOS Sierra• OS X El Capitan with Safari 10 installed import SafariServices
if SFSafariServicesAvailable() {
SFContentBlockerManager.getStateOfContentBlocker(identifier:
"com.apple.DessertBlocker.DessertBlockerExtension", completionHandler: {
(state, error) in
// Check `state` to find if the content blocker is enabled.
})
} else {
// The API is not available, have a fallback behavior.
}
Page 28
SFSafariServicesAvailable()NEWCheck Safari Services APIs Availability
Safari Services APIs will be available on:• macOS Sierra• OS X El Capitan with Safari 10 installed import SafariServices
if SFSafariServicesAvailable() {
SFContentBlockerManager.getStateOfContentBlocker(identifier:
"com.apple.DessertBlocker.DessertBlockerExtension", completionHandler: {
(state, error) in
// Check `state` to find if the content blocker is enabled.
})
} else {
// The API is not available, have a fallback behavior.
}
Page 29
SFSafariServicesAvailable()NEWCheck Safari Services APIs Availability
Safari Services APIs will be available on:• macOS Sierra• OS X El Capitan with Safari 10 installed import SafariServices
if SFSafariServicesAvailable() {
SFContentBlockerManager.getStateOfContentBlocker(identifier:
"com.apple.DessertBlocker.DessertBlockerExtension", completionHandler: {
(state, error) in
// Check `state` to find if the content blocker is enabled.
})
} else {
// The API is not available, have a fallback behavior.
}
Page 30
SFSafariServicesAvailable()NEWCheck Safari Services APIs Availability
Safari Services APIs will be available on:• macOS Sierra• OS X El Capitan with Safari 10 installed import SafariServices
if SFSafariServicesAvailable() {
SFContentBlockerManager.getStateOfContentBlocker(identifier:
"com.apple.DessertBlocker.DessertBlockerExtension", completionHandler: {
(state, error) in
// Check `state` to find if the content blocker is enabled.
})
} else {
// The API is not available, have a fallback behavior.
}
Page 31
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 32
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 33
// Adding a style sheet - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariStyleSheet</key>
<array>
<dict>
<key>Style Sheet</key>
<string>style.css</string>
</dict>
</array>
</dict>
Page 34
// Adding a style sheet - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariStyleSheet</key>
<array>
<dict>
<key>Style Sheet</key>
<string>style.css</string>
</dict>
</array>
</dict>
Page 35
// Adding a style sheet - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariStyleSheet</key>
<array>
<dict>
<key>Style Sheet</key>
<string>style.css</string>
</dict>
</array>
</dict>
Page 36
// Adding a style sheet - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariStyleSheet</key>
<array>
<dict>
<key>Style Sheet</key>
<string>style.css</string>
</dict>
</array>
</dict>
Page 37
// Adding a content script - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariContentScript</key>
<array>
<dict>
<key>Script</key>
<string>replace.js</string>
</dict>
</array>
</dict>
Page 38
// Adding a content script - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariContentScript</key>
<array>
<dict>
<key>Script</key>
<string>replace.js</string>
</dict>
</array>
</dict>
Page 39
// Adding a content script - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariContentScript</key>
<array>
<dict>
<key>Script</key>
<string>replace.js</string>
</dict>
</array>
</dict>
Page 40
// Adding a content script - Info.plist
<key>NSExtension</key>
<dict>
<key>SFSafariContentScript</key>
<array>
<dict>
<key>Script</key>
<string>replace.js</string>
</dict>
</array>
</dict>
Page 41
Content Script ArchitecturePage Modification
Content ScriptExtension Process
SafariExtension Bundle www.apple.com
Page 42
Content Script ArchitecturePage Modification
safari.extension.dispatchMessage(“GetWordsAndReplacements");
Content ScriptExtension Process
SafariExtension Bundle www.apple.com
Page 43
// Receiving a message from a content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 44
// Receiving a message from a content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 45
// Receiving a message from a content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 46
// Receiving a message from a content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 47
// Sending a message back to the content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 48
// Sending a message back to the content script
import SafariServices
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String,
from page: SFSafariPage, userInfo: [NSObject : AnyObject]!) {
if (messageName == "GetWordsAndReplacements") {
page.dispatchMessageToScript(withName: "WordsAndReplacements",
userInfo: ["Bear": "🐻"]);
}
}
}
Page 49
Content Script ArchitecturePage Modification
Content ScriptExtension Process
SafariExtension Bundle www.apple.com
Page 50
Content Script ArchitecturePage Modification
Content ScriptExtension Process
SafariExtension Bundle www.apple.com
page.dispatchMessageToScript(withName: "WordsAndReplacements", userInfo: ["Bear": "🐻"]);
Page 51
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 52
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 53
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 54
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 55
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 56
// Listening for messages inside a content script
safari.self.addEventListener("message", messageHandler);
function messageHandler(event)
{
if (event.name === "WordsAndReplacements") {
var wordReplacementMap = event.message;
for (var wordToReplace in wordReplacementMap) {
replace(document.body, wordToReplace, wordReplacementMap[wordToReplace]);
}
}
}
Page 57
Website Access LevelSafari App Extensions
Page 58
Website Access LevelSafari App Extensions
<key>NSExtension</key>
<dict>
<key>SFSafariWebsiteAccess</key>
<dict>
<key>Level</key>
<string>All</string>
</dict>
</dict>
Page 59
Website Access LevelSafari App Extensions
<key>NSExtension</key>
<dict>
<key>SFSafariWebsiteAccess</key>
<dict>
<key>Level</key>
<string>Some</string>
<key>Allowed Domains</key>
<array>
<string>*.apple.com</string>
<string>*.webkit.org</string>
</array>
</dict>
</dict>
Page 61
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 62
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 63
Extending Safari's UI
Damian Kaleta Safari Engineer
Page 72
// Definition
// Toolbar Button
<key>NSExtension</key>
<dict>
<key>SFSafariToolbarItem</key>
<dict>
<key>Identifier</key>
<string>NotebookToolbarItem</string>
<key>Label</key>
<string>Show Snippets</string>
<key>Image</key>
<string>ToolbarItemIcon.pdf</string>
<key>Action</key>
<string>Command</string>
</dict>
</dict>
Page 73
// Definition
// Toolbar Button
<key>NSExtension</key>
<dict>
<key>SFSafariToolbarItem</key>
<dict>
<key>Identifier</key>
<string>NotebookToolbarItem</string>
<key>Label</key>
<string>Show Snippets</string>
<key>Image</key>
<string>ToolbarItemIcon.pdf</string>
<key>Action</key>
<string>Command</string>
</dict>
</dict>
Page 74
// Definition
// Toolbar Button
<key>NSExtension</key>
<dict>
<key>SFSafariToolbarItem</key>
<dict>
<key>Identifier</key>
<string>NotebookToolbarItem</string>
<key>Label</key>
<string>Show Snippets</string>
<key>Image</key>
<string>ToolbarItemIcon.pdf</string>
<key>Action</key>
<string>Command</string>
</dict>
</dict>
Page 75
All methods should be defined on SFSafariExtensionHandler
APIs
optional public func toolbarItemClicked(in window: SFSafariWindow)
optional public func validateToolbarItem(in window: SFSafariWindow,
validationHandler: (enabled: Bool, badgeText: String) -> Swift.Void)
Toolbar Button
Page 77
ArchitecturePopover
Extension Safari
Remote View
SFSafariExtensionHandler
popoverViewController
Page 78
// Definition
// Popover
<key>NSExtension</key>
<dict>
<key>SFSafariToolbarItem</key>
<dict>
<key>Identifier</key>
<string>NotebookToolbarItem</string>
<key>Label</key>
<string>Show Snippets</string>
<key>Image</key>
<string>ToolbarItemIcon.pdf</string>
<key>Action</key>
<string>Popover</string>
</dict>
</dict>
Page 79
// Definition
// Popover
<key>NSExtension</key>
<dict>
<key>SFSafariToolbarItem</key>
<dict>
<key>Identifier</key>
<string>NotebookToolbarItem</string>
<key>Label</key>
<string>Show Snippets</string>
<key>Image</key>
<string>ToolbarItemIcon.pdf</string>
<key>Action</key>
<string>Popover</string>
</dict>
</dict>
Page 80
All methods should be defined on SFSafariExtensionHandler
APIs
optional public func popoverWillShow(in window: SFSafariWindow)
optional public func popoverDidClose(in window: SFSafariWindow)
optional public func popoverViewController() -> SFSafariExtensionViewController
Popover
Page 81
Context Menu Items
Page 83
// Definition// Context Menu Items
<key>NSExtension</key>
<dict>
<key>SFSafariContextMenu</key>
<array>
<dict>
<key>Text</key>
<string>Add Snippet to Notebook</string>
<key>Command</key>
<string>Add</string>
</dict>
</array>
</dict>
Page 84
// Definition// Context Menu Items
<key>NSExtension</key>
<dict>
<key>SFSafariContextMenu</key>
<array>
<dict>
<key>Text</key>
<string>Add Snippet to Notebook</string>
<key>Command</key>
<string>Add</string>
</dict>
</array>
</dict>
Page 85
// Definition// Context Menu Items
<key>NSExtension</key>
<dict>
<key>SFSafariContextMenu</key>
<array>
<dict>
<key>Text</key>
<string>Add Snippet to Notebook</string>
<key>Command</key>
<string>Add</string>
</dict>
</array>
</dict>
Page 86
All methods are defined on SFSafariExtensionHandler
optional public func contextMenuItemSelected(withCommand command: String,
in page: SFSafariPage, userInfo: [NSObject : AnyObject]? = [:])
APIsContext Menu Items
Page 87
Setting Context Menu User InfoContext Menu Items
document.addEventListener("contextmenu", handleContextMenu, false);
function handleContextMenu(event)
{
var selectedText = window.getSelection().toString();
safari.extension.setContextMenuEventUserInfo(event,
{ "selectedText": selectedText });
}
Page 88
Extend Safari’s UIDemo
Page 89
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 90
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 91
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 92
TypesSafari App Extensions
Content BlockersPage modification and communication with native codeExtending Safari’s UI
Page 93
Summary
Based on App Extensions
Page 94
Summary
Based on App ExtensionsDistributed with your Mac app
Page 95
More Information
https://developer.apple.com/wwdc16/214
Page 96
Related Sessions
Creating Extensions for iOS and OS X, Part 1 WWDC 2014
Creating Extensions for iOS and OS X, Part 2 WWDC 2014
App Extension Best Practices WWDC 2015
Page 97
Labs
Safari and WebKit Lab Fort Mason Wednesday 4:30PM
Safari and WebKit Lab Frameworks Lab C Friday 4:30PM