| 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) |