OLD | NEW |
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-2014 Eyeo GmbH | 3 * Copyright (C) 2006-2014 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 // the safari object is missing in frames created from javascript: URLs. | 20 // the safari object is missing in frames created from javascript: URLs. |
21 // So we have to fallback to the safari object from the parent frame. | 21 // So we have to fallback to the safari object from the parent frame. |
22 if (!("safari" in window)) | 22 if (!("safari" in window)) |
23 window.safari = window.parent.safari; | 23 window.safari = window.parent.safari; |
24 | 24 |
25 if (window == window.top) | 25 |
26 safari.self.tab.dispatchMessage("loading"); | 26 /* Intialization */ |
| 27 |
| 28 var beforeLoadEvent = document.createEvent("Event"); |
| 29 beforeLoadEvent.initEvent("beforeload"); |
| 30 |
| 31 var isTopLevel = window == window.top; |
| 32 var isPrerendered = document.visibilityState == "prerender"; |
| 33 |
| 34 var documentInfo = safari.self.tab.canLoad( |
| 35 beforeLoadEvent, |
| 36 { |
| 37 category: "loading", |
| 38 url: document.location.href, |
| 39 referrer: document.referrer, |
| 40 isTopLevel: isTopLevel, |
| 41 isPrerendered: isPrerendered |
| 42 } |
| 43 ); |
| 44 |
| 45 if (isTopLevel && isPrerendered) |
| 46 { |
| 47 var onVisibilitychange = function() |
| 48 { |
| 49 safari.self.tab.dispatchMessage("replaced", {pageId: documentInfo.pageId})
; |
| 50 document.removeEventListener("visibilitychange", onVisibilitychange); |
| 51 }; |
| 52 document.addEventListener("visibilitychange", onVisibilitychange); |
| 53 } |
27 | 54 |
28 | 55 |
29 /* Events */ | 56 /* Web requests */ |
30 | 57 |
31 var ContentMessageEventTarget = function() | 58 document.addEventListener("beforeload", function(event) |
32 { | 59 { |
33 MessageEventTarget.call(this, safari.self); | 60 // we don't block non-HTTP requests anyway, so we can bail out |
34 }; | 61 // without asking the background page. This is even necessary |
35 ContentMessageEventTarget.prototype = { | 62 // because passing large data (like a photo encoded as data: URL) |
36 __proto__: MessageEventTarget.prototype, | 63 // to the background page, freezes Safari. |
37 _getResponseDispatcher: function(event) | 64 if (!/^https?:/.test(event.url)) |
| 65 return; |
| 66 |
| 67 var type; |
| 68 switch(event.target.localName) |
38 { | 69 { |
39 return event.target.tab; | 70 case "frame": |
40 }, | 71 case "iframe": |
41 _getSenderDetails: function(event) | 72 type = "sub_frame"; |
| 73 break; |
| 74 case "img": |
| 75 type = "image"; |
| 76 break; |
| 77 case "object": |
| 78 case "embed": |
| 79 type = "object"; |
| 80 break; |
| 81 case "script": |
| 82 type = "script"; |
| 83 break; |
| 84 case "link": |
| 85 if (/\bstylesheet\b/i.test(event.target.rel)) |
| 86 { |
| 87 type = "stylesheet"; |
| 88 break; |
| 89 } |
| 90 default: |
| 91 type = "other"; |
| 92 } |
| 93 |
| 94 if (!safari.self.tab.canLoad( |
| 95 event, { |
| 96 category: "webRequest", |
| 97 url: event.url, |
| 98 type: type, |
| 99 pageId: documentInfo.pageId, |
| 100 frameId: documentInfo.frameId |
| 101 } |
| 102 )) |
42 { | 103 { |
43 return {}; | 104 event.preventDefault(); |
| 105 |
| 106 // Safari doesn't dispatch an "error" event when preventing an element |
| 107 // from loading by cancelling the "beforeload" event. So we have to |
| 108 // dispatch it manually. Otherwise element collapsing wouldn't work. |
| 109 if (type != "sub_frame") |
| 110 { |
| 111 var evt = document.createEvent("Event"); |
| 112 evt.initEvent("error"); |
| 113 event.target.dispatchEvent(evt); |
| 114 } |
44 } | 115 } |
45 }; | 116 }, true); |
46 | 117 |
47 | 118 |
48 /* Background page proxy */ | 119 /* Context menus */ |
49 var proxy = { | 120 |
| 121 document.addEventListener("contextmenu", function(event) |
| 122 { |
| 123 var element = event.srcElement; |
| 124 safari.self.tab.setContextMenuEventUserInfo(event, { |
| 125 pageId: documentInfo.pageId, |
| 126 srcUrl: ("src" in element) ? element.src : null, |
| 127 tagName: element.localName |
| 128 }); |
| 129 }); |
| 130 |
| 131 |
| 132 /* Background page */ |
| 133 |
| 134 var backgroundPageProxy = { |
50 objects: [], | 135 objects: [], |
51 callbacks: [], | 136 callbacks: [], |
52 | 137 |
53 send: function(message) | 138 send: function(message) |
54 { | 139 { |
55 var evt = document.createEvent("Event"); | 140 message.category = "proxy"; |
56 evt.initEvent("beforeload"); | 141 message.pageId = documentInfo.pageId; |
57 return safari.self.tab.canLoad(evt, {type: "proxy", payload: message}); | 142 |
| 143 return safari.self.tab.canLoad(beforeLoadEvent, message); |
58 }, | 144 }, |
59 checkResult: function(result) | 145 checkResult: function(result) |
60 { | 146 { |
61 if (!result.succeed) | 147 if (!result.succeed) |
62 throw result.error; | 148 throw result.error; |
63 }, | 149 }, |
64 deserializeResult: function(result) | 150 deserializeResult: function(result) |
65 { | 151 { |
66 this.checkResult(result); | 152 this.checkResult(result); |
67 return this.deserialize(result.result); | 153 return this.deserialize(result.result); |
68 }, | 154 }, |
69 serialize: function(obj, memo) | 155 serialize: function(obj, memo) |
70 { | 156 { |
71 var objectId = this.objects.indexOf(obj); | 157 var objectId = this.objects.indexOf(obj); |
72 if (objectId != -1) | 158 if (objectId != -1) |
73 return {type: "hosted", objectId: objectId}; | 159 return {type: "hosted", objectId: objectId}; |
74 | 160 |
75 if (typeof obj == "function") | 161 if (typeof obj == "function") |
76 { | 162 { |
77 var callbackId = this.callbacks.indexOf(obj); | 163 var callbackId = this.callbacks.indexOf(obj); |
78 | |
79 if (callbackId == -1) | 164 if (callbackId == -1) |
80 { | |
81 callbackId = this.callbacks.push(obj) - 1; | 165 callbackId = this.callbacks.push(obj) - 1; |
82 | 166 |
83 safari.self.addEventListener("message", function(event) | 167 return {type: "callback", callbackId: callbackId, frameId: documentInfo.
frameId}; |
84 { | |
85 if (event.name == "proxyCallback") | |
86 if (event.message.callbackId == callbackId) | |
87 obj.apply( | |
88 this.getObject(event.message.contextId), | |
89 this.deserializeSequence(event.message.args) | |
90 ); | |
91 }.bind(this)); | |
92 } | |
93 | |
94 return {type: "callback", callbackId: callbackId}; | |
95 } | 168 } |
96 | 169 |
97 if (typeof obj == "object" && | 170 if (typeof obj == "object" && |
98 obj != null && | 171 obj != null && |
99 obj.constructor != Date && | 172 obj.constructor != Date && |
100 obj.constructor != RegExp) | 173 obj.constructor != RegExp) |
101 { | 174 { |
102 if (!memo) | 175 if (!memo) |
103 memo = {specs: [], objects: []}; | 176 memo = {specs: [], objects: []}; |
104 | 177 |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
219 functionId: objectId, | 292 functionId: objectId, |
220 contextId: proxy.getObjectId(this), | 293 contextId: proxy.getObjectId(this), |
221 args: Array.prototype.map.call( | 294 args: Array.prototype.map.call( |
222 arguments, | 295 arguments, |
223 proxy.serialize.bind(proxy) | 296 proxy.serialize.bind(proxy) |
224 ) | 297 ) |
225 }) | 298 }) |
226 ); | 299 ); |
227 }; | 300 }; |
228 }, | 301 }, |
229 getObject: function(objectId) { | 302 handleCallback: function(message) |
| 303 { |
| 304 this.callbacks[message.callbackId].apply( |
| 305 this.getObject(message.contextId), |
| 306 this.deserializeSequence(message.args) |
| 307 ); |
| 308 }, |
| 309 getObject: function(objectId) |
| 310 { |
230 var objectInfo = this.send({ | 311 var objectInfo = this.send({ |
231 type: "inspectObject", | 312 type: "inspectObject", |
232 objectId: objectId | 313 objectId: objectId |
233 }); | 314 }); |
234 | 315 |
235 var obj = this.objects[objectId]; | 316 var obj = this.objects[objectId]; |
236 if (obj) | 317 if (obj) |
237 Object.getOwnPropertyNames(obj).forEach(function(prop) { delete obj[prop
]; }); | 318 Object.getOwnPropertyNames(obj).forEach(function(prop) { delete obj[prop
]; }); |
238 else | 319 else |
239 { | 320 { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
274 property, objectInfo.properties[property].enumerable | 355 property, objectInfo.properties[property].enumerable |
275 )); | 356 )); |
276 | 357 |
277 if (objectInfo.isFunction) | 358 if (objectInfo.isFunction) |
278 obj.prototype = this.getProperty(objectId, "prototype"); | 359 obj.prototype = this.getProperty(objectId, "prototype"); |
279 | 360 |
280 return obj; | 361 return obj; |
281 } | 362 } |
282 }; | 363 }; |
283 | 364 |
| 365 ext.backgroundPage = { |
| 366 sendMessage: function(message, responseCallback) |
| 367 { |
| 368 messageProxy.sendMessage(message, responseCallback, documentInfo); |
| 369 }, |
| 370 getWindow: function() |
| 371 { |
| 372 return backgroundPageProxy.getObject(0); |
| 373 } |
| 374 }; |
| 375 |
284 | 376 |
285 /* Web request blocking */ | 377 /* Message processing */ |
286 | 378 |
287 document.addEventListener("beforeload", function(event) | 379 var messageProxy = new ext._MessageProxy(safari.self.tab); |
| 380 |
| 381 safari.self.addEventListener("message", function(event) |
288 { | 382 { |
289 // we don't block non-HTTP requests anyway, so we can bail out | 383 if (event.message.pageId == documentInfo.pageId) |
290 // without asking the background page. This is even necessary | 384 { |
291 // because passing large data (like a photo encoded as data: URL) | 385 if (event.name == "request") |
292 // to the background page, freezes Safari. | 386 { |
293 if (!/^https?:/.test(event.url)) | 387 messageProxy.handleRequest(event.message, {}); |
294 return; | 388 return; |
| 389 } |
295 | 390 |
296 var type; | 391 if (event.message.frameId == documentInfo.frameId) |
297 | 392 { |
298 switch(event.target.localName) | 393 switch (event.name) |
299 { | |
300 case "frame": | |
301 case "iframe": | |
302 type = "sub_frame"; | |
303 break; | |
304 case "img": | |
305 type = "image"; | |
306 break; | |
307 case "object": | |
308 case "embed": | |
309 type = "object"; | |
310 break; | |
311 case "script": | |
312 type = "script"; | |
313 break; | |
314 case "link": | |
315 if (/\bstylesheet\b/i.test(event.target.rel)) | |
316 { | 394 { |
317 type = "stylesheet"; | 395 case "response": |
318 break; | 396 messageProxy.handleResponse(event.message); |
319 } | 397 break; |
320 default: | 398 case "proxyCallback": |
321 type = "other"; | 399 backgroundPageProxy.handleCallback(event.message); |
322 } | 400 break; |
323 | |
324 if (!safari.self.tab.canLoad( | |
325 event, { | |
326 type: "webRequest", | |
327 payload: { | |
328 url: event.url, | |
329 type: type, | |
330 documentUrl: document.location.href, | |
331 isTopLevel: window == window.top | |
332 } | 401 } |
333 } | 402 } |
334 )) | |
335 { | |
336 event.preventDefault(); | |
337 | |
338 // Safari doesn't dispatch an "error" event when preventing an element | |
339 // from loading by cancelling the "beforeload" event. So we have to | |
340 // dispatch it manually. Otherwise element collapsing wouldn't work. | |
341 if (type != "sub_frame") | |
342 { | |
343 var evt = document.createEvent("Event"); | |
344 evt.initEvent("error"); | |
345 event.target.dispatchEvent(evt); | |
346 } | |
347 } | 403 } |
348 }, true); | 404 }); |
349 | |
350 | |
351 /* API */ | |
352 | |
353 ext.backgroundPage = { | |
354 sendMessage: function(message, responseCallback) | |
355 { | |
356 _sendMessage( | |
357 message, responseCallback, | |
358 safari.self.tab, safari.self, | |
359 { | |
360 documentUrl: document.location.href, | |
361 isTopLevel: window == window.top | |
362 } | |
363 ); | |
364 }, | |
365 getWindow: function() | |
366 { | |
367 return proxy.getObject(0); | |
368 } | |
369 }; | |
370 | |
371 ext.onMessage = new ContentMessageEventTarget(); | |
372 | |
373 | |
374 // Safari does not pass the element which the context menu is refering to | |
375 // so we need to add it to the event's user info. | |
376 document.addEventListener("contextmenu", function(event) | |
377 { | |
378 var element = event.srcElement; | |
379 safari.self.tab.setContextMenuEventUserInfo(event, { | |
380 srcUrl: ("src" in element) ? element.src : null, | |
381 tagName: element.localName | |
382 }); | |
383 }, false); | |
384 })(); | 405 })(); |
OLD | NEW |