Index: safari/ext/content.js |
=================================================================== |
--- a/safari/ext/content.js |
+++ b/safari/ext/content.js |
@@ -18,7 +18,7 @@ |
(function() |
{ |
if (window == window.top) |
- safari.self.tab.dispatchMessage("loading"); |
+ safari.self.tab.dispatchMessage("loading", {isDocumentHidden: document.hidden}); |
/* Events */ |
@@ -36,6 +36,14 @@ |
_getSenderDetails: function(event) |
{ |
return {}; |
+ }, |
+ _ignoreIf: function(event) |
+ { |
+ // If the current page is a preloaded page in Safari 7.0, we have to |
+ // ignore all messages sent from the background page. The high-level code |
+ // doesn't know anything about preloaded pages and expects messages to be |
+ // handled only by the current visible page. |
+ return document.hidden && event.message.isTabVisible; |
} |
}; |
@@ -279,11 +287,10 @@ |
/* Web request blocking */ |
- document.addEventListener("beforeload", function(event) |
+ var canLoad = function(event) |
{ |
var type; |
- |
- switch(event.target.localName) |
+ switch (event.target.localName) |
{ |
case "frame": |
case "iframe": |
@@ -309,17 +316,24 @@ |
type = "other"; |
} |
- if (!safari.self.tab.canLoad( |
+ return safari.self.tab.canLoad( |
event, { |
type: "webRequest", |
payload: { |
url: event.url, |
type: type, |
documentUrl: document.location.href, |
- isTopLevel: window == window.top |
+ isTopLevel: window == window.top, |
+ isDocumentHidden: document.hidden |
} |
} |
- )) |
+ ); |
+ }; |
+ |
+ document.addEventListener("beforeload", function(event) |
+ { |
+ var result; |
+ if (event.target._blocked || (result = canLoad(event)) == "blocked") |
{ |
event.preventDefault(); |
@@ -327,9 +341,11 @@ |
// element from loading by cancelling the "beforeload" event. So we have |
// to dispatch it manually. Otherwise element collapsing wouldn't work. |
var evt = document.createEvent("Event"); |
- evt.initEvent(type == "sub_frame" ? "load" : "error"); |
+ evt.initEvent(/^i?frame$/.test(event.target.localName) ? "load" : "error"); |
event.target.dispatchEvent(evt); |
} |
+ else if (result == "deferred") |
+ deferredBeforeLoadEvents.push(event); |
}, true); |
@@ -339,11 +355,25 @@ |
sendMessage: function(message, responseCallback) |
{ |
_sendMessage( |
- message, responseCallback, |
- safari.self.tab, safari.self, |
+ // message payload |
+ message, |
+ // response callback |
+ function(response) |
+ { |
+ if (response.deferred) |
+ deferredMessages.push([message, responseCallback]); |
+ else if (responseCallback) |
+ responseCallback(response.payload); |
+ }, |
+ // message dispatcher |
+ safari.self.tab, |
+ // response event target |
+ safari.self, |
+ // extra data |
{ |
documentUrl: document.location.href, |
- isTopLevel: window == window.top |
+ isTopLevel: window == window.top, |
+ isDocumentHidden: document.hidden |
} |
); |
}, |
@@ -356,6 +386,60 @@ |
ext.onMessage = new ContentMessageEventTarget(); |
+ // Starting with Safari 7.0 content scripts also run in preloaded pages. |
+ // However our high-level code doesn't know anything about preloaded pages |
+ // and expects only one page running in each tab. So we have to defer |
+ // all requests to the background page, until the preloaded page is shown. |
+ |
+ var deferredMessages = []; |
+ var deferredBeforeLoadEvents = []; |
+ |
+ document.addEventListener("visibilitychange", function() |
+ { |
+ if (document.hidden) |
+ return; |
+ |
+ // notify background page, that it can catch up on onLoading events |
+ if (window == window.top) |
+ safari.self.tab.dispatchMessage("show"); |
+ |
+ // catch up on deferred messages |
+ for (var i = 0; i < deferredMessages.length; i++) |
+ ext.backgroundPage.sendMessage.apply(null, deferredMessages[i]); |
+ deferredMessages = []; |
+ |
+ // catch up on deferred resource blocking |
+ for (var i = 0; i < deferredBeforeLoadEvents.length; i++) |
+ { |
+ var result = canLoad(deferredBeforeLoadEvents[i]); |
+ if (result == "blocked") |
+ { |
+ // if at least one script was supposed to be blocked, |
+ // we have to reload the preloaded page. Executed scripts |
+ // won't undo themselves by removing them from the DOM |
+ var element = deferredBeforeLoadEvents[i].target; |
+ if (element.localName == "script") |
+ { |
+ document.documentElement.style.display = "none"; |
+ document.location.reload(); |
+ return; |
+ } |
+ |
+ // other resources that were supposed to be blocked are replaced |
+ // with an identical clone, in order to trigger "beforeload" again |
+ if (element.parentNode) |
+ { |
+ var clone = element.cloneNode(true); |
+ clone._blocked = true; // remember that this should be blocked, |
+ // to avoid checking twice |
+ element.parentNode.replaceChild(clone, element); |
+ } |
+ } |
+ } |
+ deferredBeforeLoadEvents = []; |
+ }); |
+ |
+ |
// Safari does not pass the element which the context menu is refering to |
// so we need to add it to the event's user info. |
document.addEventListener("contextmenu", function(event) |