Index: safari/ext/content.js |
=================================================================== |
rename from safari/content.js |
rename to safari/ext/content.js |
--- a/safari/content.js |
+++ b/safari/ext/content.js |
@@ -1,6 +1,6 @@ |
/* |
* This file is part of Adblock Plus <http://adblockplus.org/>, |
- * Copyright (C) 2006-2013 Eyeo GmbH |
+ * Copyright (C) 2006-2014 Eyeo GmbH |
* |
* Adblock Plus is free software: you can redistribute it and/or modify |
* it under the terms of the GNU General Public License version 3 as |
@@ -17,19 +17,135 @@ |
(function() |
{ |
- safari.self.tab.dispatchMessage("loading", document.location.href); |
+ // the safari object is missing in frames created from javascript: URLs. |
+ // So we have to fallback to the safari object from the parent frame. |
+ if (!("safari" in window)) |
+ window.safari = window.parent.safari; |
- /* Background page proxy */ |
- var proxy = { |
+ /* Intialization */ |
+ |
+ var beforeLoadEvent = document.createEvent("Event"); |
+ beforeLoadEvent.initEvent("beforeload"); |
+ |
+ var isTopLevel = window == window.top; |
+ var isPrerendered = document.visibilityState == "prerender"; |
+ |
+ var documentInfo = safari.self.tab.canLoad( |
+ beforeLoadEvent, |
+ { |
+ category: "loading", |
+ url: document.location.href, |
+ referrer: document.referrer, |
+ isTopLevel: isTopLevel, |
+ isPrerendered: isPrerendered |
+ } |
+ ); |
+ |
+ if (isTopLevel && isPrerendered) |
+ { |
+ var onVisibilitychange = function() |
+ { |
+ safari.self.tab.dispatchMessage("replaced", {pageId: documentInfo.pageId}); |
+ document.removeEventListener("visibilitychange", onVisibilitychange); |
+ }; |
+ document.addEventListener("visibilitychange", onVisibilitychange); |
+ } |
+ |
+ |
+ /* Web requests */ |
+ |
+ document.addEventListener("beforeload", function(event) |
+ { |
+ var url = relativeToAbsoluteUrl(event.url); |
+ |
+ // we don't block non-HTTP requests anyway, so we can bail out |
+ // without asking the background page. This is even necessary |
+ // because passing large data (like a photo encoded as data: URL) |
+ // to the background page, freezes Safari. |
+ if (!/^https?:/.test(url)) |
+ return; |
+ |
+ var type; |
+ switch(event.target.localName) |
+ { |
+ case "frame": |
+ case "iframe": |
+ type = "sub_frame"; |
+ break; |
+ case "img": |
+ type = "image"; |
+ break; |
+ case "object": |
+ case "embed": |
+ type = "object"; |
+ break; |
+ case "script": |
+ type = "script"; |
+ break; |
+ case "link": |
+ if (/\bstylesheet\b/i.test(event.target.rel)) |
+ { |
+ type = "stylesheet"; |
+ break; |
+ } |
+ default: |
+ type = "other"; |
+ } |
+ |
+ if (!safari.self.tab.canLoad( |
+ event, { |
+ category: "webRequest", |
+ url: url, |
+ type: type, |
+ pageId: documentInfo.pageId, |
+ frameId: documentInfo.frameId |
+ } |
+ )) |
+ { |
+ event.preventDefault(); |
+ |
+ // Safari doesn't dispatch an "error" event when preventing an element |
+ // from loading by cancelling the "beforeload" event. So we have to |
+ // dispatch it manually. Otherwise element collapsing wouldn't work. |
+ if (type != "sub_frame") |
+ { |
+ setTimeout(function() |
+ { |
+ var evt = document.createEvent("Event"); |
+ evt.initEvent("error"); |
+ event.target.dispatchEvent(evt); |
+ }, 0); |
+ } |
+ } |
+ }, true); |
+ |
+ |
+ /* Context menus */ |
+ |
+ document.addEventListener("contextmenu", function(event) |
+ { |
+ var element = event.srcElement; |
+ safari.self.tab.setContextMenuEventUserInfo(event, { |
+ pageId: documentInfo.pageId, |
+ srcUrl: ("src" in element) ? element.src : null, |
+ tagName: element.localName |
+ }); |
+ }); |
+ |
+ |
+ /* Background page */ |
+ |
+ var backgroundPageProxy = { |
objects: [], |
callbacks: [], |
send: function(message) |
{ |
- var evt = document.createEvent("Event"); |
- evt.initEvent("beforeload"); |
- return safari.self.tab.canLoad(evt, {type: "proxy", payload: message}); |
+ message.category = "proxy"; |
+ message.pageId = documentInfo.pageId; |
+ |
+ return safari.self.tab.canLoad(beforeLoadEvent, message); |
}, |
checkResult: function(result) |
{ |
@@ -43,66 +159,57 @@ |
}, |
serialize: function(obj, memo) |
{ |
- var objectId = this.objects.indexOf(obj); |
- if (objectId != -1) |
- return {type: "hosted", objectId: objectId}; |
+ if (typeof obj == "object" && obj != null || typeof obj == "function") |
+ { |
+ if ("__proxyObjectId" in obj) |
+ return {type: "hosted", objectId: obj.__proxyObjectId}; |
- if (typeof obj == "function") |
- { |
- var callbackId = this.callbacks.indexOf(obj); |
+ if (typeof obj == "function") |
+ { |
+ var callbackId; |
+ if ("__proxyCallbackId" in obj) |
+ callbackId = obj.__proxyCallbackId; |
+ else |
+ { |
+ callbackId = this.callbacks.push(obj) - 1; |
+ Object.defineProperty(obj, "__proxyCallbackId", {value: callbackId}); |
+ } |
- if (callbackId == -1) |
- { |
- callbackId = this.callbacks.push(obj) - 1; |
- |
- safari.self.addEventListener("message", function(event) |
- { |
- if (event.name == "proxyCallback") |
- if (event.message.callbackId == callbackId) |
- obj.apply( |
- this.getObject(event.message.contextId), |
- this.deserializeSequence(event.message.args) |
- ); |
- }.bind(this)); |
+ return {type: "callback", callbackId: callbackId, frameId: documentInfo.frameId}; |
} |
- return {type: "callback", callbackId: callbackId}; |
- } |
+ if (obj.constructor != Date && obj.constructor != RegExp) |
+ { |
+ if (!memo) |
+ memo = {specs: [], objects: []}; |
- if (typeof obj == "object" && |
- obj != null && |
- obj.constructor != Date && |
- obj.constructor != RegExp) |
- { |
- if (!memo) |
- memo = {specs: [], objects: []}; |
+ var idx = memo.objects.indexOf(obj); |
+ if (idx != -1) |
+ return memo.specs[idx]; |
- var idx = memo.objects.indexOf(obj); |
- if (idx != -1) |
- return memo.specs[idx]; |
+ var spec = {}; |
+ memo.specs.push(spec); |
+ memo.objects.push(obj); |
- var spec = {}; |
- memo.specs.push(spec); |
- memo.objects.push(obj); |
+ if (obj.constructor == Array) |
+ { |
+ spec.type = "array"; |
+ spec.items = []; |
- if (obj.constructor == Array) |
- { |
- spec.type = "array"; |
- spec.items = []; |
+ for (var i = 0; i < obj.length; i++) |
+ spec.items.push(this.serialize(obj[i], memo)); |
+ } |
+ else |
+ { |
+ spec.type = "object"; |
+ spec.properties = {}; |
- for (var i = 0; i < obj.length; i++) |
- spec.items.push(this.serialize(obj[i], memo)); |
+ for (var k in obj) |
+ spec.properties[k] = this.serialize(obj[k], memo); |
+ } |
+ |
+ return spec; |
} |
- else |
- { |
- spec.type = "object"; |
- spec.properties = {}; |
- |
- for (var k in obj) |
- spec.properties[k] = this.serialize(obj[k], memo); |
- } |
- |
- return spec; |
} |
return {type: "value", value: obj}; |
@@ -143,10 +250,6 @@ |
return this.deserializeSequence(spec.items, array, memo); |
} |
}, |
- getObjectId: function(obj) |
- { |
- return this.objects.indexOf(obj); |
- }, |
getProperty: function(objectId, property) |
{ |
return this.deserializeResult( |
@@ -164,7 +267,7 @@ |
return { |
get: function() |
{ |
- return proxy.getProperty(proxy.getObjectId(this), property); |
+ return proxy.getProperty(this.__proxyObjectId, property); |
}, |
set: function(value) |
{ |
@@ -172,7 +275,7 @@ |
proxy.send( |
{ |
type: "setProperty", |
- objectId: proxy.getObjectId(this), |
+ objectId: this.__proxyObjectId, |
property: property, |
value: proxy.serialize(value) |
}) |
@@ -192,7 +295,7 @@ |
{ |
type: "callFunction", |
functionId: objectId, |
- contextId: proxy.getObjectId(this), |
+ contextId: this.__proxyObjectId, |
args: Array.prototype.map.call( |
arguments, |
proxy.serialize.bind(proxy) |
@@ -201,7 +304,15 @@ |
); |
}; |
}, |
- getObject: function(objectId) { |
+ handleCallback: function(message) |
+ { |
+ this.callbacks[message.callbackId].apply( |
+ this.getObject(message.contextId), |
+ this.deserializeSequence(message.args) |
+ ); |
+ }, |
+ getObject: function(objectId) |
+ { |
var objectInfo = this.send({ |
type: "inspectObject", |
objectId: objectId |
@@ -218,24 +329,27 @@ |
obj = {}; |
this.objects[objectId] = obj; |
+ Object.defineProperty(obj, "__proxyObjectId", {value: objectId}); |
} |
- var ignored = []; |
+ var excluded = []; |
+ var included = []; |
if ("prototypeOf" in objectInfo) |
{ |
var prototype = window[objectInfo.prototypeOf].prototype; |
- ignored = Object.getOwnPropertyNames(prototype); |
- ignored.splice(ignored.indexOf("constructor"), 1); |
+ excluded = Object.getOwnPropertyNames(prototype); |
+ included = ["constructor"]; |
obj.__proto__ = prototype; |
} |
else |
{ |
if (objectInfo.isFunction) |
- ignored = Object.getOwnPropertyNames(function() {}); |
- else |
- ignored = []; |
+ { |
+ excluded = Object.getOwnPropertyNames(function() {}); |
+ included = ["prototype"]; |
+ } |
if ("prototypeId" in objectInfo) |
obj.__proto__ = this.getObject(objectInfo.prototypeId); |
@@ -244,65 +358,64 @@ |
} |
for (var property in objectInfo.properties) |
- if (ignored.indexOf(property) == -1) |
- Object.defineProperty(obj, property, this.createProperty( |
- property, objectInfo.properties[property].enumerable |
- )); |
+ { |
+ if (excluded.indexOf(property) == -1 || included.indexOf(property) != -1) |
+ { |
+ var desc = Object.getOwnPropertyDescriptor(obj, property); |
- if (objectInfo.isFunction) |
- obj.prototype = this.getProperty(objectId, "prototype"); |
+ if (!desc || desc.configurable) |
+ { |
+ Object.defineProperty(obj, property, this.createProperty( |
+ property, objectInfo.properties[property].enumerable |
+ )); |
+ } |
+ else if (desc.writable) |
+ obj[property] = this.getProperty(objectId, property); |
+ } |
+ } |
return obj; |
} |
}; |
- |
- /* Web request blocking */ |
- |
- document.addEventListener("beforeload", function(event) |
- { |
- var type; |
- |
- switch(event.target.nodeName) |
+ ext.backgroundPage = { |
+ sendMessage: function(message, responseCallback) |
{ |
- case "FRAME": |
- case "IFRAME": |
- type = "frame"; |
- break; |
- case "IMG": |
- type = "image"; |
- break; |
- case "OBJECT": |
- case "EMBED": |
- type = "object"; |
- break; |
- case "SCRIPT": |
- type = "script"; |
- break; |
- case "LINK": |
- if (/(^|\s)stylesheet($|\s)/i.test(event.target.rel)) |
- { |
- type = "stylesheet"; |
- break; |
- } |
- default: |
- type = "other"; |
+ messageProxy.sendMessage(message, responseCallback, documentInfo); |
+ }, |
+ getWindow: function() |
+ { |
+ return backgroundPageProxy.getObject(0); |
} |
- |
- if (!safari.self.tab.canLoad(event, {type: "webRequest", payload: {url: event.url, type: type}})) |
- event.preventDefault(); |
- }, true); |
- |
- |
- /* API */ |
- |
- ext.backgroundPage = { |
- _eventTarget: safari.self, |
- _messageDispatcher: safari.self.tab, |
- |
- sendMessage: sendMessage, |
- getWindow: function() { return proxy.getObject(0); } |
}; |
- ext.onMessage = new MessageEventTarget(safari.self); |
+ |
+ /* Message processing */ |
+ |
+ var messageProxy = new ext._MessageProxy(safari.self.tab); |
+ |
+ safari.self.addEventListener("message", function(event) |
+ { |
+ if (event.message.pageId == documentInfo.pageId) |
+ { |
+ if (event.name == "request") |
+ { |
+ messageProxy.handleRequest(event.message, {}); |
+ return; |
+ } |
+ |
+ if (event.message.frameId == documentInfo.frameId) |
+ { |
+ switch (event.name) |
+ { |
+ case "response": |
+ messageProxy.handleResponse(event.message); |
+ break; |
+ case "proxyCallback": |
+ backgroundPageProxy.handleCallback(event.message); |
+ break; |
+ } |
+ } |
+ } |
+ }); |
})(); |