Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: safari/ext/content.js

Issue 5670052883857408: Fixed properties defined on prototype in Safari background page proxy (Closed)
Left Patch Set: Adressed comments Created Nov. 29, 2013, 10:29 a.m.
Right Patch Set: Rebased and fixed issue with inheritance Created March 6, 2014, 3:04 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 })();
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld