Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 /* | 1 /* |
2 * This file is part of Adblock Plus <http://adblockplus.org/>, | 2 * This file is part of Adblock Plus <http://adblockplus.org/>, |
3 * Copyright (C) 2006-2013 Eyeo GmbH | 3 * Copyright (C) 2006-2013 Eyeo GmbH |
4 * | 4 * |
5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
8 * | 8 * |
9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
16 */ | 16 */ |
17 | 17 |
18 (function() | 18 (function() |
19 { | 19 { |
20 safari.self.tab.dispatchMessage("loading", document.location.href); | 20 // the safari object is missing in frames created from javascript: URLs. |
21 | 21 // So we have to fallback to the safari object from the parent frame. |
22 | 22 if (!("safari" in window)) |
23 /* Background page proxy */ | 23 window.safari = window.parent.safari; |
24 | |
25 | |
26 /* Intialization */ | |
24 | 27 |
25 var beforeLoadEvent = document.createEvent("Event"); | 28 var beforeLoadEvent = document.createEvent("Event"); |
26 beforeLoadEvent.initEvent("beforeload"); | 29 beforeLoadEvent.initEvent("beforeload"); |
27 | 30 |
28 var proxy = { | 31 var isTopLevel = window == window.top; |
29 objects: [], | 32 var isPrerendered = document.visibilityState == "prerender"; |
30 callbacks: [], | 33 |
31 | 34 var documentInfo = safari.self.tab.canLoad( |
32 send: function(message) | 35 beforeLoadEvent, |
33 { | 36 { |
34 return safari.self.tab.canLoad(beforeLoadEvent, {type: "proxy", payload: m essage}); | 37 category: "loading", |
35 }, | 38 url: document.location.href, |
36 checkResult: function(result) | 39 referrer: document.referrer, |
37 { | 40 isTopLevel: isTopLevel, |
38 if (!result.succeed) | 41 isPrerendered: isPrerendered |
39 throw result.error; | 42 } |
40 }, | 43 ); |
41 deserializeResult: function(result) | 44 |
42 { | 45 if (isTopLevel && isPrerendered) |
43 this.checkResult(result); | 46 { |
44 return this.deserialize(result.result); | 47 var onVisibilitychange = function() |
45 }, | 48 { |
46 serialize: function(obj, memo) | 49 safari.self.tab.dispatchMessage("replaced", {pageId: documentInfo.pageId}) ; |
47 { | 50 document.removeEventListener("visibilitychange", onVisibilitychange); |
48 var objectId = this.getObjectId(obj); | 51 }; |
49 if (objectId != -1) | 52 document.addEventListener("visibilitychange", onVisibilitychange); |
50 return {type: "hosted", objectId: objectId}; | 53 } |
51 | 54 |
52 if (typeof obj == "function") | 55 |
53 { | 56 /* Web requests */ |
54 var callbackId = this.callbacks.indexOf(obj); | |
55 | |
56 if (callbackId == -1) | |
57 { | |
58 callbackId = this.callbacks.push(obj) - 1; | |
59 | |
60 if (callbackId == 0) | |
Wladimir Palant
2014/01/15 16:21:01
This is non-obvious and needs a short explanation
| |
61 { | |
62 safari.self.addEventListener("message", function(event) | |
63 { | |
64 if (event.name == "proxyCallback") | |
65 { | |
66 this.callbacks[event.message.callbackId].apply( | |
67 this.getObject(event.message.contextId), | |
68 this.deserializeSequence(event.message.args) | |
69 ); | |
70 } | |
71 }.bind(this)); | |
72 } | |
73 } | |
74 | |
75 return {type: "callback", callbackId: callbackId}; | |
76 } | |
77 | |
78 if (typeof obj == "object" && | |
79 obj != null && | |
80 obj.constructor != Date && | |
81 obj.constructor != RegExp) | |
82 { | |
83 if (!memo) | |
84 memo = {specs: [], objects: []}; | |
85 | |
86 var idx = memo.objects.indexOf(obj); | |
87 if (idx != -1) | |
88 return memo.specs[idx]; | |
89 | |
90 var spec = {}; | |
91 memo.specs.push(spec); | |
92 memo.objects.push(obj); | |
93 | |
94 if (obj.constructor == Array) | |
95 { | |
96 spec.type = "array"; | |
97 spec.items = []; | |
98 | |
99 for (var i = 0; i < obj.length; i++) | |
100 spec.items.push(this.serialize(obj[i], memo)); | |
101 } | |
102 else | |
103 { | |
104 spec.type = "object"; | |
105 spec.properties = {}; | |
106 | |
107 for (var k in obj) | |
108 spec.properties[k] = this.serialize(obj[k], memo); | |
109 } | |
110 | |
111 return spec; | |
112 } | |
113 | |
114 return {type: "value", value: obj}; | |
115 }, | |
116 deserializeSequence: function(specs, array, memo) | |
117 { | |
118 if (!array) | |
119 array = []; | |
120 | |
121 if (!memo) | |
122 memo = {specs: [], arrays: []}; | |
123 | |
124 for (var i = 0; i < specs.length; i++) | |
125 array.push(this.deserialize(specs[i], memo)); | |
126 | |
127 return array; | |
128 }, | |
129 deserialize: function(spec, memo) | |
130 { | |
131 switch (spec.type) | |
132 { | |
133 case "value": | |
134 return spec.value; | |
135 case "object": | |
136 return this.getObject(spec.objectId); | |
137 case "array": | |
138 if (!memo) | |
139 memo = {specs: [], arrays: []}; | |
140 | |
141 var idx = memo.specs.indexOf(spec); | |
142 if (idx != -1) | |
143 return memo.arrays[idx]; | |
144 | |
145 var array = []; | |
146 memo.specs.push(spec); | |
147 memo.arrays.push(array); | |
148 | |
149 return this.deserializeSequence(spec.items, array, memo); | |
150 } | |
151 }, | |
152 getObjectId: function(obj) | |
153 { | |
154 return this.objects.indexOf(obj); | |
155 }, | |
156 getProperty: function(objectId, property) | |
157 { | |
158 return this.deserializeResult( | |
159 this.send( | |
160 { | |
161 type: "getProperty", | |
162 objectId: objectId, | |
163 property: property | |
164 }) | |
165 ); | |
166 }, | |
167 createProperty: function(property, enumerable) | |
168 { | |
169 var proxy = this; | |
170 return { | |
171 get: function() | |
172 { | |
173 return proxy.getProperty(proxy.getObjectId(this), property); | |
174 }, | |
175 set: function(value) | |
176 { | |
177 proxy.checkResult( | |
178 proxy.send( | |
179 { | |
180 type: "setProperty", | |
181 objectId: proxy.getObjectId(this), | |
182 property: property, | |
183 value: proxy.serialize(value) | |
184 }) | |
185 ); | |
186 }, | |
187 enumerable: enumerable, | |
188 configurable: true | |
189 }; | |
190 }, | |
191 createFunction: function(objectId) | |
192 { | |
193 var proxy = this; | |
194 return function() | |
195 { | |
196 return proxy.deserializeResult( | |
197 proxy.send( | |
198 { | |
199 type: "callFunction", | |
200 functionId: objectId, | |
201 contextId: proxy.getObjectId(this), | |
202 args: Array.prototype.map.call( | |
203 arguments, | |
204 proxy.serialize.bind(proxy) | |
205 ) | |
206 }) | |
207 ); | |
208 }; | |
209 }, | |
210 getObject: function(objectId) | |
211 { | |
212 var objectInfo = this.send({ | |
213 type: "inspectObject", | |
214 objectId: objectId | |
215 }); | |
216 | |
217 var obj = this.objects[objectId]; | |
218 if (obj) | |
219 Object.getOwnPropertyNames(obj).forEach(function(prop) { delete obj[prop ]; }); | |
220 else | |
221 { | |
222 if (objectInfo.isFunction) | |
223 obj = this.createFunction(objectId); | |
224 else | |
225 obj = {}; | |
226 | |
227 this.objects[objectId] = obj; | |
228 } | |
229 | |
230 var ignored = []; | |
231 if ("prototypeOf" in objectInfo) | |
232 { | |
233 var prototype = window[objectInfo.prototypeOf].prototype; | |
234 | |
235 ignored = Object.getOwnPropertyNames(prototype); | |
236 ignored.splice(ignored.indexOf("constructor"), 1); | |
Wladimir Palant
2014/01/15 16:21:01
The assumption here is that ignored.indexOf() will
| |
237 | |
238 obj.__proto__ = prototype; | |
239 } | |
240 else | |
241 { | |
242 if (objectInfo.isFunction) | |
243 { | |
244 ignored = Object.getOwnPropertyNames(function() {}); | |
245 ignored.splice(ignored.indexOf("prototype"), 1); | |
246 } | |
247 else | |
248 ignored = []; | |
249 | |
250 if ("prototypeId" in objectInfo) | |
251 obj.__proto__ = this.getObject(objectInfo.prototypeId); | |
252 else | |
253 obj.__proto__ = null; | |
254 } | |
255 | |
256 for (var property in objectInfo.properties) | |
257 { | |
258 if (ignored.indexOf(property) != -1) | |
259 continue; | |
260 | |
261 if (!Object.hasOwnProperty(obj, property) || Object.getOwnPropertyDescri ptor(obj, property).configurable) | |
262 { | |
263 Object.defineProperty(obj, property, this.createProperty( | |
264 property, objectInfo.properties[property].enumerable | |
265 )); | |
266 } | |
267 else | |
268 obj[property] = this.getProperty(objectId, property); | |
269 } | |
270 | |
271 return obj; | |
272 } | |
273 }; | |
274 | |
275 | |
276 /* Web request blocking */ | |
277 | 57 |
278 document.addEventListener("beforeload", function(event) | 58 document.addEventListener("beforeload", function(event) |
279 { | 59 { |
280 var type; | 60 var type; |
281 | |
282 switch(event.target.localName) | 61 switch(event.target.localName) |
283 { | 62 { |
284 case "frame": | 63 case "frame": |
285 case "iframe": | 64 case "iframe": |
286 type = "frame"; | 65 type = "sub_frame"; |
287 break; | 66 break; |
288 case "img": | 67 case "img": |
289 type = "image"; | 68 type = "image"; |
290 break; | 69 break; |
291 case "object": | 70 case "object": |
292 case "embed": | 71 case "embed": |
293 type = "object"; | 72 type = "object"; |
294 break; | 73 break; |
295 case "script": | 74 case "script": |
296 type = "script"; | 75 type = "script"; |
297 break; | 76 break; |
298 case "link": | 77 case "link": |
299 if (/\bstylesheet\b/i.test(event.target.rel)) | 78 if (/\bstylesheet\b/i.test(event.target.rel)) |
300 { | 79 { |
301 type = "stylesheet"; | 80 type = "stylesheet"; |
302 break; | 81 break; |
303 } | 82 } |
304 default: | 83 default: |
305 type = "other"; | 84 type = "other"; |
306 } | 85 } |
307 | 86 |
308 if (!safari.self.tab.canLoad(event, {type: "webRequest", payload: {url: even t.url, type: type}})) | 87 if (!safari.self.tab.canLoad( |
88 event, { | |
89 category: "webRequest", | |
90 url: event.url, | |
91 type: type, | |
92 pageId: documentInfo.pageId, | |
93 frameId: documentInfo.frameId | |
94 } | |
95 )) | |
96 { | |
309 event.preventDefault(); | 97 event.preventDefault(); |
98 | |
99 // Safari doesn't dispatch an "error" event when preventing an element | |
100 // from loading by cancelling the "beforeload" event. So we have to | |
101 // dispatch it manually. Otherwise element collapsing wouldn't work. | |
102 if (type != "sub_frame") | |
103 { | |
104 var evt = document.createEvent("Event"); | |
105 evt.initEvent(type == "error"); | |
106 event.target.dispatchEvent(evt); | |
107 } | |
108 } | |
310 }, true); | 109 }, true); |
311 | 110 |
312 | 111 |
313 /* API */ | 112 /* Context menus */ |
113 | |
114 document.addEventListener("contextmenu", function(event) | |
115 { | |
116 var element = event.srcElement; | |
117 safari.self.tab.setContextMenuEventUserInfo(event, { | |
118 pageId: documentInfo.pageId, | |
119 srcUrl: ("src" in element) ? element.src : null, | |
120 tagName: element.localName | |
121 }); | |
122 }); | |
123 | |
124 | |
125 /* Background page */ | |
126 | |
127 var backgroundPageProxy = { | |
128 objects: [], | |
129 callbacks: [], | |
130 | |
131 send: function(message) | |
132 { | |
133 message.category = "proxy"; | |
134 message.pageId = documentInfo.pageId; | |
135 | |
136 return safari.self.tab.canLoad(beforeLoadEvent, message); | |
137 }, | |
138 checkResult: function(result) | |
139 { | |
140 if (!result.succeed) | |
141 throw result.error; | |
142 }, | |
143 deserializeResult: function(result) | |
144 { | |
145 this.checkResult(result); | |
146 return this.deserialize(result.result); | |
147 }, | |
148 serialize: function(obj, memo) | |
149 { | |
150 if (typeof obj == "object" && obj != null || typeof obj == "function") | |
151 { | |
152 if ("__proxyObjectId" in obj) | |
153 return {type: "hosted", objectId: obj.__proxyObjectId}; | |
154 | |
155 if (typeof obj == "function") | |
156 { | |
157 var callbackId; | |
158 if ("__proxyCallbackId" in obj) | |
159 callbackId = obj.__proxyCallbackId; | |
160 else | |
161 { | |
162 callbackId = this.callbacks.push(obj) - 1; | |
163 Object.defineProperty(obj, "__proxyCallbackId", {value: callbackId}) ; | |
164 } | |
165 | |
166 return {type: "callback", callbackId: callbackId, frameId: documentInf o.frameId}; | |
167 } | |
168 | |
169 if (obj.constructor != Date && obj.constructor != RegExp) | |
170 { | |
171 if (!memo) | |
172 memo = {specs: [], objects: []}; | |
173 | |
174 var idx = memo.objects.indexOf(obj); | |
175 if (idx != -1) | |
176 return memo.specs[idx]; | |
177 | |
178 var spec = {}; | |
179 memo.specs.push(spec); | |
180 memo.objects.push(obj); | |
181 | |
182 if (obj.constructor == Array) | |
183 { | |
184 spec.type = "array"; | |
185 spec.items = []; | |
186 | |
187 for (var i = 0; i < obj.length; i++) | |
188 spec.items.push(this.serialize(obj[i], memo)); | |
189 } | |
190 else | |
191 { | |
192 spec.type = "object"; | |
193 spec.properties = {}; | |
194 | |
195 for (var k in obj) | |
196 spec.properties[k] = this.serialize(obj[k], memo); | |
197 } | |
198 | |
199 return spec; | |
200 } | |
201 } | |
202 | |
203 return {type: "value", value: obj}; | |
204 }, | |
205 deserializeSequence: function(specs, array, memo) | |
206 { | |
207 if (!array) | |
208 array = []; | |
209 | |
210 if (!memo) | |
211 memo = {specs: [], arrays: []}; | |
212 | |
213 for (var i = 0; i < specs.length; i++) | |
214 array.push(this.deserialize(specs[i], memo)); | |
215 | |
216 return array; | |
217 }, | |
218 deserialize: function(spec, memo) | |
219 { | |
220 switch (spec.type) | |
221 { | |
222 case "value": | |
223 return spec.value; | |
224 case "object": | |
225 return this.getObject(spec.objectId); | |
226 case "array": | |
227 if (!memo) | |
228 memo = {specs: [], arrays: []}; | |
229 | |
230 var idx = memo.specs.indexOf(spec); | |
231 if (idx != -1) | |
232 return memo.arrays[idx]; | |
233 | |
234 var array = []; | |
235 memo.specs.push(spec); | |
236 memo.arrays.push(array); | |
237 | |
238 return this.deserializeSequence(spec.items, array, memo); | |
239 } | |
240 }, | |
241 getProperty: function(objectId, property) | |
242 { | |
243 return this.deserializeResult( | |
244 this.send( | |
245 { | |
246 type: "getProperty", | |
247 objectId: objectId, | |
248 property: property | |
249 }) | |
250 ); | |
251 }, | |
252 createProperty: function(property, enumerable) | |
253 { | |
254 var proxy = this; | |
255 return { | |
256 get: function() | |
257 { | |
258 return proxy.getProperty(this.__proxyObjectId, property); | |
259 }, | |
260 set: function(value) | |
261 { | |
262 proxy.checkResult( | |
263 proxy.send( | |
264 { | |
265 type: "setProperty", | |
266 objectId: this.__proxyObjectId, | |
267 property: property, | |
268 value: proxy.serialize(value) | |
269 }) | |
270 ); | |
271 }, | |
272 enumerable: enumerable, | |
273 configurable: true | |
274 }; | |
275 }, | |
276 createFunction: function(objectId) | |
277 { | |
278 var proxy = this; | |
279 return function() | |
280 { | |
281 return proxy.deserializeResult( | |
282 proxy.send( | |
283 { | |
284 type: "callFunction", | |
285 functionId: objectId, | |
286 contextId: this.__proxyObjectId, | |
287 args: Array.prototype.map.call( | |
288 arguments, | |
289 proxy.serialize.bind(proxy) | |
290 ) | |
291 }) | |
292 ); | |
293 }; | |
294 }, | |
295 handleCallback: function(message) | |
296 { | |
297 this.callbacks[message.callbackId].apply( | |
298 this.getObject(message.contextId), | |
299 this.deserializeSequence(message.args) | |
300 ); | |
301 }, | |
302 getObject: function(objectId) | |
303 { | |
304 var objectInfo = this.send({ | |
305 type: "inspectObject", | |
306 objectId: objectId | |
307 }); | |
308 | |
309 var obj = this.objects[objectId]; | |
310 if (obj) | |
311 Object.getOwnPropertyNames(obj).forEach(function(prop) { delete obj[prop ]; }); | |
312 else | |
313 { | |
314 if (objectInfo.isFunction) | |
315 obj = this.createFunction(objectId); | |
316 else | |
317 obj = {}; | |
318 | |
319 this.objects[objectId] = obj; | |
320 Object.defineProperty(obj, "__proxyObjectId", {value: objectId}); | |
321 } | |
322 | |
323 var excluded = []; | |
324 var included = []; | |
325 if ("prototypeOf" in objectInfo) | |
326 { | |
327 var prototype = window[objectInfo.prototypeOf].prototype; | |
328 | |
329 excluded = Object.getOwnPropertyNames(prototype); | |
330 included = ["constructor"]; | |
331 | |
332 obj.__proto__ = prototype; | |
333 } | |
334 else | |
335 { | |
336 if (objectInfo.isFunction) | |
337 { | |
338 excluded = Object.getOwnPropertyNames(function() {}); | |
339 included = ["prototype"]; | |
340 } | |
341 | |
342 if ("prototypeId" in objectInfo) | |
343 obj.__proto__ = this.getObject(objectInfo.prototypeId); | |
344 else | |
345 obj.__proto__ = null; | |
346 } | |
347 | |
348 for (var property in objectInfo.properties) | |
349 { | |
350 if (excluded.indexOf(property) == -1 || included.indexOf(property) != -1 ) | |
351 { | |
352 var desc = Object.getOwnPropertyDescriptor(obj, property); | |
353 | |
354 if (!desc || desc.configurable) | |
355 { | |
356 Object.defineProperty(obj, property, this.createProperty( | |
357 property, objectInfo.properties[property].enumerable | |
358 )); | |
359 } | |
360 else if (desc.writable) | |
361 obj[property] = this.getProperty(objectId, property); | |
362 } | |
363 } | |
364 | |
365 return obj; | |
366 } | |
367 }; | |
314 | 368 |
315 ext.backgroundPage = { | 369 ext.backgroundPage = { |
316 _eventTarget: safari.self, | 370 sendMessage: function(message, responseCallback) |
317 _messageDispatcher: safari.self.tab, | 371 { |
318 | 372 messageProxy.sendMessage(message, responseCallback, documentInfo); |
319 sendMessage: sendMessage, | 373 }, |
320 getWindow: function() { return proxy.getObject(0); } | 374 getWindow: function() |
375 { | |
376 return backgroundPageProxy.getObject(0); | |
377 } | |
321 }; | 378 }; |
322 | 379 |
323 ext.onMessage = new MessageEventTarget(safari.self); | 380 |
381 /* Message processing */ | |
382 | |
383 var messageProxy = new ext._MessageProxy(safari.self.tab); | |
384 | |
385 safari.self.addEventListener("message", function(event) | |
386 { | |
387 if (event.message.pageId == documentInfo.pageId) | |
388 { | |
389 if (event.name == "request") | |
390 { | |
391 messageProxy.handleRequest(event.message, {}); | |
392 return; | |
393 } | |
394 | |
395 if (event.message.frameId == documentInfo.frameId) | |
396 { | |
397 switch (event.name) | |
398 { | |
399 case "response": | |
400 messageProxy.handleResponse(event.message); | |
401 break; | |
402 case "proxyCallback": | |
403 backgroundPageProxy.handleCallback(event.message); | |
404 break; | |
405 } | |
406 } | |
407 } | |
408 }); | |
324 })(); | 409 })(); |
LEFT | RIGHT |