| 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; |
| + } |
| + } |
| + } |
| + }); |
| })(); |