Merge pull request #385 from danielpunkass/safari-extension
Safari extension fixes
This commit is contained in:
commit
c60da0fefc
@ -18,7 +18,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
|
|||||||
// to verify whether our code is installed.
|
// to verify whether our code is installed.
|
||||||
|
|
||||||
// I tried to use a NSMapTable from String to the closure directly, but Swift
|
// I tried to use a NSMapTable from String to the closure directly, but Swift
|
||||||
// complains that the object as to be a class type.
|
// complains that the object has to be a class type.
|
||||||
typealias ValidationHandler = (Bool, String) -> Void
|
typealias ValidationHandler = (Bool, String) -> Void
|
||||||
class ValidationWrapper {
|
class ValidationWrapper {
|
||||||
let validationHandler: ValidationHandler
|
let validationHandler: ValidationHandler
|
||||||
@ -30,6 +30,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
|
|||||||
|
|
||||||
// Maps from UUID to a validation wrapper
|
// Maps from UUID to a validation wrapper
|
||||||
static var gPingPongMap = Dictionary<String, ValidationWrapper>()
|
static var gPingPongMap = Dictionary<String, ValidationWrapper>()
|
||||||
|
static var validationQueue = DispatchQueue(label: "Toolbar Validation")
|
||||||
|
|
||||||
// Bottleneck for calling through to a validation handler we have saved, and removing it from the list.
|
// Bottleneck for calling through to a validation handler we have saved, and removing it from the list.
|
||||||
static func callValidationHandler(forHandlerID handlerID: String, withShouldValidate shouldValidate: Bool) {
|
static func callValidationHandler(forHandlerID handlerID: String, withShouldValidate shouldValidate: Bool) {
|
||||||
@ -39,8 +40,6 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationQueue = DispatchQueue(label: "Toolbar Validation")
|
|
||||||
|
|
||||||
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
|
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
|
||||||
if (messageName == "subscribeToFeed") {
|
if (messageName == "subscribeToFeed") {
|
||||||
if let feedURLString = userInfo?["url"] as? String {
|
if let feedURLString = userInfo?["url"] as? String {
|
||||||
@ -73,12 +72,25 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
|
|||||||
|
|
||||||
let uniqueValidationID = NSUUID().uuidString
|
let uniqueValidationID = NSUUID().uuidString
|
||||||
|
|
||||||
// Save it right away to eliminate any doubt of whether the handler gets deallocated while
|
SafariExtensionHandler.validationQueue.sync {
|
||||||
// we are waiting for a callback from the getActiveTab or getActivatePage methods below.
|
|
||||||
let validationWrapper = ValidationWrapper(validationHandler: validationHandler)
|
// Save it right away to eliminate any doubt of whether the handler gets deallocated while
|
||||||
SafariExtensionHandler.gPingPongMap[uniqueValidationID] = validationWrapper
|
// we are waiting for a callback from the getActiveTab or getActivatePage methods below.
|
||||||
|
let validationWrapper = ValidationWrapper(validationHandler: validationHandler)
|
||||||
|
SafariExtensionHandler.gPingPongMap[uniqueValidationID] = validationWrapper
|
||||||
|
|
||||||
|
// To avoid problems with validation handlers dispatched after we've, for example,
|
||||||
|
// switched to a new tab, we aggressively clear out the map of any pending validations,
|
||||||
|
// and focus only on the newest validation request we've been asked for.
|
||||||
|
for thisValidationID in SafariExtensionHandler.gPingPongMap.keys {
|
||||||
|
if thisValidationID != uniqueValidationID {
|
||||||
|
// Default to valid ... we'll know soon enough whether the latest state
|
||||||
|
// is actually still valid or not...
|
||||||
|
SafariExtensionHandler.callValidationHandler(forHandlerID: thisValidationID, withShouldValidate: true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
validationQueue.sync {
|
|
||||||
// See comments above where gPingPongMap is declared. Upon being asked to validate the
|
// See comments above where gPingPongMap is declared. Upon being asked to validate the
|
||||||
// toolbar icon for a specific page, we save the validationHandler and postpone calling
|
// toolbar icon for a specific page, we save the validationHandler and postpone calling
|
||||||
// it until we have either received a response from our installed JavaScript, or until
|
// it until we have either received a response from our installed JavaScript, or until
|
||||||
@ -101,7 +113,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
|
|||||||
// Capture the uniqueValidationID to ensure it doesn't change out from under us on a future call
|
// Capture the uniqueValidationID to ensure it doesn't change out from under us on a future call
|
||||||
activePage.dispatchMessageToScript(withName: "ping", userInfo: ["validationID": uniqueValidationID])
|
activePage.dispatchMessageToScript(withName: "ping", userInfo: ["validationID": uniqueValidationID])
|
||||||
|
|
||||||
let pongTimeoutInNanoseconds = Int(NSEC_PER_SEC / UInt64(1))
|
let pongTimeoutInNanoseconds = Int(Double(NSEC_PER_SEC) * 0.5)
|
||||||
let timeoutDeadline = DispatchTime.now() + DispatchTimeInterval.nanoseconds(pongTimeoutInNanoseconds)
|
let timeoutDeadline = DispatchTime.now() + DispatchTimeInterval.nanoseconds(pongTimeoutInNanoseconds)
|
||||||
DispatchQueue.main.asyncAfter(deadline: timeoutDeadline, execute: { [timedOutValidationID = uniqueValidationID] in
|
DispatchQueue.main.asyncAfter(deadline: timeoutDeadline, execute: { [timedOutValidationID = uniqueValidationID] in
|
||||||
SafariExtensionHandler.callValidationHandler(forHandlerID: timedOutValidationID, withShouldValidate:false)
|
SafariExtensionHandler.callValidationHandler(forHandlerID: timedOutValidationID, withShouldValidate:false)
|
||||||
|
@ -1,124 +1,118 @@
|
|||||||
// Prevent injecting the JavaScript in IFRAMES, and from acting before Safari is ready...
|
var thisPageLinkObjects = null;
|
||||||
if ((window.top === window) && (typeof safari != 'undefined') && (document.location != null)) {
|
|
||||||
document.addEventListener("DOMContentLoaded", function(event) {
|
|
||||||
if (window.top === window)
|
|
||||||
{
|
|
||||||
var thisPageLinkObjects = null;
|
|
||||||
|
|
||||||
// I convert the native "link" node into an object that I can pass out to the global page
|
// I convert the native "link" node into an object that I can pass out to the global page
|
||||||
function objectFromLink(theLink)
|
function objectFromLink(theLink) {
|
||||||
{
|
var linkObject = new Object();
|
||||||
var linkObject = new Object();
|
|
||||||
|
|
||||||
linkObject.href = theLink.href;
|
linkObject.href = theLink.href;
|
||||||
linkObject.type = theLink.type;
|
linkObject.type = theLink.type;
|
||||||
linkObject.title = theLink.title;
|
linkObject.title = theLink.title;
|
||||||
|
|
||||||
return linkObject;
|
return linkObject;
|
||||||
}
|
|
||||||
|
|
||||||
// Some sites will list feeds with inappropriate or at least less-than-ideal information
|
|
||||||
// in the MIME type attribute. We cover some edge cases here that allow to be passed through,
|
|
||||||
// where they will successfully open as "feed://" URLs in the browser.
|
|
||||||
function isValidFeedLink(theLink)
|
|
||||||
{
|
|
||||||
var isValid = false;
|
|
||||||
|
|
||||||
switch (theLink.type)
|
|
||||||
{
|
|
||||||
case "application/atom+xml":
|
|
||||||
case "application/x.atom+xml":
|
|
||||||
case "application/rss+xml":
|
|
||||||
// These types do not require other criteria.
|
|
||||||
isValid = (theLink.href != null);
|
|
||||||
|
|
||||||
case "text/xml":
|
|
||||||
case "application/rdf+xml":
|
|
||||||
// These types require a title that has "RSS" in it.
|
|
||||||
if (theLink.title && theLink.title.search(/RSS/i) != -1)
|
|
||||||
{
|
|
||||||
isValid = (theLink.href != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scanForSyndicationFeeds()
|
|
||||||
{
|
|
||||||
// In case we don't find any, we establish that we have at least tried by setting the
|
|
||||||
// variables to empty instead of null.
|
|
||||||
thisPageLinkObjects = []
|
|
||||||
|
|
||||||
thisPageLinks = document.getElementsByTagName("link");
|
|
||||||
|
|
||||||
for (thisLinkIndex = 0; thisLinkIndex < thisPageLinks.length; thisLinkIndex++)
|
|
||||||
{
|
|
||||||
var thisLink = thisPageLinks[thisLinkIndex];
|
|
||||||
var thisLinkRel = thisLink.getAttribute("rel");
|
|
||||||
if (thisLinkRel == "alternate")
|
|
||||||
{
|
|
||||||
if (isValidFeedLink(thisLink))
|
|
||||||
{
|
|
||||||
thisPageLinkObjects.push(objectFromLink(thisLink));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeToFeed(theFeed)
|
|
||||||
{
|
|
||||||
// Convert the URL to a feed:// scheme because Safari
|
|
||||||
// will refuse to load e.g. a feed that is listed merely
|
|
||||||
// as "text/xml". We do some preflighting of the link rel
|
|
||||||
// in the PageLoadEnd.js so we can be more confident it's a
|
|
||||||
// good feed: URL.
|
|
||||||
var feedURL = theFeed.href;
|
|
||||||
if (feedURL.match(/^http[s]?:\/\//))
|
|
||||||
{
|
|
||||||
feedURL = feedURL.replace(/^http[s]?:\/\//, "feed://");
|
|
||||||
}
|
|
||||||
else if (feedURL.match(/^feed:/) == false)
|
|
||||||
{
|
|
||||||
feedURL = "feed:" + feedURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
safari.extension.dispatchMessage("subscribeToFeed", { "url": feedURL });
|
|
||||||
}
|
|
||||||
|
|
||||||
safari.self.addEventListener("message", function(event)
|
|
||||||
{
|
|
||||||
if (event.name === "toolbarButtonClicked")
|
|
||||||
{
|
|
||||||
// Workaround Radar #31182842, in which residual copies of our
|
|
||||||
// app extension may remain loaded in context of pages in Safari,
|
|
||||||
// causing multiple responses to broadcast message about toolbar
|
|
||||||
// button being clicked. In the case of the "extra" injections,
|
|
||||||
// the document location is null, so we can avoid doing on anything.
|
|
||||||
if ((document.location != null) && (thisPageLinkObjects.length > 0))
|
|
||||||
{
|
|
||||||
feedToOpen = thisPageLinkObjects[0];
|
|
||||||
subscribeToFeed(feedToOpen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event.name === "ping")
|
|
||||||
{
|
|
||||||
// Just a hack to get the toolbar icon validation to work as expected.
|
|
||||||
// If we don't pong back, the extension knows we are not loaded in a page.
|
|
||||||
|
|
||||||
// There is a bug in Safari where the messageHandler is apparently held on to by Safari
|
|
||||||
// even after an extension is disabled. So an effort to "ping" an extension's scripts will
|
|
||||||
// succeed even if its been disabled and the page reloaded. Checking for the existance of
|
|
||||||
// document.location seems to ensure we have enough of a handle still on the document that
|
|
||||||
// we can do something useful with it.
|
|
||||||
var shouldValidate = (document.location != null) && (thisPageLinkObjects.length > 0);
|
|
||||||
|
|
||||||
// Pass back the same validationID we were handed so they can look up the correlated validationHandler
|
|
||||||
safari.extension.dispatchMessage("pong", { "validationID": event.message.validationID, "shouldValidate": shouldValidate });
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
scanForSyndicationFeeds();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some sites will list feeds with inappropriate or at least less-than-ideal information
|
||||||
|
// in the MIME type attribute. We cover some edge cases here that allow to be passed through,
|
||||||
|
// where they will successfully open as "feed://" URLs in the browser.
|
||||||
|
function isValidFeedLink(theLink) {
|
||||||
|
var isValid = false;
|
||||||
|
|
||||||
|
switch (theLink.type)
|
||||||
|
{
|
||||||
|
case "application/atom+xml":
|
||||||
|
case "application/x.atom+xml":
|
||||||
|
case "application/rss+xml":
|
||||||
|
// These types do not require other criteria.
|
||||||
|
isValid = (theLink.href != null);
|
||||||
|
|
||||||
|
case "text/xml":
|
||||||
|
case "application/rdf+xml":
|
||||||
|
// These types require a title that has "RSS" in it.
|
||||||
|
if (theLink.title && theLink.title.search(/RSS/i) != -1)
|
||||||
|
{
|
||||||
|
isValid = (theLink.href != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanForSyndicationFeeds() {
|
||||||
|
// In case we don't find any, we establish that we have at least tried by setting the
|
||||||
|
// variables to empty instead of null.
|
||||||
|
thisPageLinkObjects = []
|
||||||
|
|
||||||
|
thisPageLinks = document.getElementsByTagName("link");
|
||||||
|
|
||||||
|
for (thisLinkIndex = 0; thisLinkIndex < thisPageLinks.length; thisLinkIndex++)
|
||||||
|
{
|
||||||
|
var thisLink = thisPageLinks[thisLinkIndex];
|
||||||
|
var thisLinkRel = thisLink.getAttribute("rel");
|
||||||
|
if (thisLinkRel == "alternate")
|
||||||
|
{
|
||||||
|
if (isValidFeedLink(thisLink))
|
||||||
|
{
|
||||||
|
thisPageLinkObjects.push(objectFromLink(thisLink));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeToFeed(theFeed) {
|
||||||
|
// Convert the URL to a feed:// scheme because Safari
|
||||||
|
// will refuse to load e.g. a feed that is listed merely
|
||||||
|
// as "text/xml". We do some preflighting of the link rel
|
||||||
|
// in the PageLoadEnd.js so we can be more confident it's a
|
||||||
|
// good feed: URL.
|
||||||
|
var feedURL = theFeed.href;
|
||||||
|
if (feedURL.match(/^http[s]?:\/\//))
|
||||||
|
{
|
||||||
|
feedURL = feedURL.replace(/^http[s]?:\/\//, "feed://");
|
||||||
|
}
|
||||||
|
else if (feedURL.match(/^feed:/) == false)
|
||||||
|
{
|
||||||
|
feedURL = "feed:" + feedURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
safari.extension.dispatchMessage("subscribeToFeed", { "url": feedURL });
|
||||||
|
}
|
||||||
|
|
||||||
|
function messageHandler(event) {
|
||||||
|
if (event.name === "toolbarButtonClicked")
|
||||||
|
{
|
||||||
|
// Workaround Radar #31182842, in which residual copies of our
|
||||||
|
// app extension may remain loaded in context of pages in Safari,
|
||||||
|
// causing multiple responses to broadcast message about toolbar
|
||||||
|
// button being clicked. In the case of the "extra" injections,
|
||||||
|
// the document location is null, so we can avoid doing on anything.
|
||||||
|
if ((document.location != null) && (thisPageLinkObjects.length > 0))
|
||||||
|
{
|
||||||
|
feedToOpen = thisPageLinkObjects[0];
|
||||||
|
subscribeToFeed(feedToOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.name === "ping")
|
||||||
|
{
|
||||||
|
// Just a hack to get the toolbar icon validation to work as expected.
|
||||||
|
// If we don't pong back, the extension knows we are not loaded in a page.
|
||||||
|
|
||||||
|
// There is a bug in Safari where the messageHandler is apparently held on to by Safari
|
||||||
|
// even after an extension is disabled. So an effort to "ping" an extension's scripts will
|
||||||
|
// succeed even if its been disabled and the page reloaded. Checking for the existance of
|
||||||
|
// document.location seems to ensure we have enough of a handle still on the document that
|
||||||
|
// we can do something useful with it.
|
||||||
|
var shouldValidate = (document.location != null) && (thisPageLinkObjects.length > 0);
|
||||||
|
|
||||||
|
// Pass back the same validationID we were handed so they can look up the correlated validationHandler
|
||||||
|
safari.extension.dispatchMessage("pong", { "validationID": event.message.validationID, "shouldValidate": shouldValidate });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
// Prevent injecting the JavaScript in IFRAMES, and from acting before Safari is ready...
|
||||||
|
if ((window.top === window) && (typeof safari != 'undefined') && (document.location != null))
|
||||||
|
{
|
||||||
|
safari.self.addEventListener("message", messageHandler, false)
|
||||||
|
scanForSyndicationFeeds();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user