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

Side by Side Diff: safari/background.js

Issue 16067002: Added Safari Support (Closed)
Patch Set: Addressed comments Created Nov. 12, 2013, 10:28 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <http://adblockplus.org/>,
3 * Copyright (C) 2006-2013 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 (function()
19 {
20 /* Tabs */
21
22 var TabEventTarget = function()
23 {
24 WrappedEventTarget.apply(this, arguments);
25 };
26 TabEventTarget.prototype = {
27 __proto__: WrappedEventTarget.prototype,
28 _wrapListener: function(listener)
29 {
30 return function(event)
31 {
32 listener(new Tab(event.target));
33 };
34 }
35 };
36
37 Tab = function(tab)
38 {
39 this._tab = tab;
40
41 this._eventTarget = tab;
42 this._messageDispatcher = tab.page;
43
44 this.url = tab.url;
45
46 this.onBeforeNavigate = new TabEventTarget(tab, "beforeNavigate", false);
47 this.onCompleted = new TabEventTarget(tab, "navigate", false);
48 this.onActivated = new TabEventTarget(tab, "activate", false);
49 this.onRemoved = new TabEventTarget(tab, "close", false);
50 };
51 Tab.prototype = {
52 close: function()
53 {
54 this._tab.close();
55 },
56 activate: function()
57 {
58 this._tab.activate();
59 },
60 sendMessage: sendMessage,
61 pageAction: {
62 // there are no page actions in safari, so we use toolbar items instead
63 setIcon: function(path)
64 {
65 safari.extension.toolbarItems[0].image = safari.extension.baseURI + path ;
66 },
67 setTitle: function(title)
68 {
69 safari.extension.toolbarItems[0].toolTip = title;
70 },
71
72 // toolbar items in safari can"t get hidden
73 hide: function() {},
74 show: function() {}
75 }
76 };
77
78 TabMap = function()
79 {
80 this._tabs = [];
81 this._values = [];
82
83 this._onClosed = this._onClosed.bind(this);
84 };
85 TabMap.prototype =
86 {
87 get: function(tab) {
88 var idx;
89
90 if (!tab || (idx = this._tabs.indexOf(tab._tab)) == -1)
91 return null;
92
93 return this._values[idx];
94 },
95 set: function(tab, value)
96 {
97 var idx = this._tabs.indexOf(tab._tab);
98
99 if (idx != -1)
100 this._values[idx] = value;
101 else
102 {
103 this._tabs.push(tab._tab);
104 this._values.push(value);
105
106 tab._tab.addEventListener("close", this._onClosed, false);
107 }
108 },
109 has: function(tab)
110 {
111 return this._tabs.indexOf(tab._tab) != -1;
112 },
113 clear: function()
114 {
115 while (this._tabs.length > 0)
116 this._delete(this._tabs[0]);
117 },
118 _delete: function(tab)
119 {
120 var idx = this._tabs.indexOf(tab);
121
122 if (idx != -1)
123 {
124 this._tabs.splice(idx, 1);
125 this._values.splice(idx, 1);
126
127 tab.removeEventListener("close", this._onClosed, false);
128 }
129 },
130 _onClosed: function(event)
131 {
132 this._delete(event.target);
133 }
134 };
135 TabMap.prototype["delete"] = function(tab)
136 {
137 this._delete(tab._tab);
138 };
139
140
141 /* Windows */
142
143 Window = function(win)
144 {
145 this._win = win;
146 this.visible = win.visible;
147 }
148 Window.prototype = {
149 getAllTabs: function(callback)
150 {
151 callback(this._win.tabs.map(function(tab) { return new Tab(tab); }));
152 },
153 getActiveTab: function(callback)
154 {
155 callback(new Tab(this._win.activeTab));
156 },
157 openTab: function(url, callback)
158 {
159 var tab = this._win.openTab();
160 tab.url = url;
161
162 if (callback)
163 callback(new Tab(tab));
164 }
165 };
166
167 if (safari.extension.globalPage.contentWindow == window)
168 {
169 /* Background page proxy */
170
171 var proxy = {
172 tabs: [],
173 objects: [],
174
175 registerObject: function(obj, objects)
176 {
177 var objectId = objects.indexOf(obj);
178
179 if (objectId == -1)
180 objectId = objects.push(obj) - 1;
181
182 return objectId;
183 },
184 serializeSequence: function(sequence, objects, memo)
185 {
186 if (!memo)
187 memo = {specs: [], arrays: []};
188
189 var items = [];
190 for (var i = 0; i < sequence.length; i++)
191 items.push(this.serialize(sequence[i], objects, memo));
192
193 return items;
194 },
195 serialize: function(obj, objects, memo)
196 {
197 if (typeof obj == "object" && obj != null || typeof obj == "function")
198 {
199 if (obj.constructor == Array)
200 {
201 if (!memo)
202 memo = {specs: [], arrays: []};
203
204 var idx = memo.arrays.indexOf(obj);
205 if (idx != -1)
206 return memo.specs[idx];
207
208 var spec = {type: "array"};
209 memo.specs.push(spec);
210 memo.arrays.push(obj);
211
212 spec.items = this.serializeSequence(obj, objects, memo);
213 return spec;
214 }
215
216 if (obj.constructor != Date && obj.constructor != RegExp)
217 return {type: "object", objectId: this.registerObject(obj, objects)} ;
218 }
219
220 return {type: "value", value: obj};
221 },
222 createCallback: function(callbackId, tab)
223 {
224 var proxy = this;
225
226 return function()
227 {
228 var idx = proxy.tabs.indexOf(tab);
229
230 if (idx != -1) {
231 var objects = proxy.objects[idx];
232
233 tab.page.dispatchMessage("proxyCallback",
234 {
235 callbackId: callbackId,
236 contextId: proxy.registerObject(this, objects),
237 args: proxy.serializeSequence(arguments, objects)
238 });
239 }
240 };
241 },
242 deserialize: function(spec, objects, tab, memo)
243 {
244 switch (spec.type)
245 {
246 case "value":
247 return spec.value;
248 case "hosted":
249 return objects[spec.objectId];
250 case "callback":
251 return this.createCallback(spec.callbackId, tab);
252 case "object":
253 case "array":
254 if (!memo)
255 memo = {specs: [], objects: []};
256
257 var idx = memo.specs.indexOf(spec);
258 if (idx != -1)
259 return memo.objects[idx];
260
261 var obj;
262 if (spec.type == "array")
263 obj = [];
264 else
265 obj = {};
266
267 memo.specs.push(spec);
268 memo.objects.push(obj);
269
270 if (spec.type == "array")
271 for (var i = 0; i < spec.items.length; i++)
272 obj.push(this.deserialize(spec.items[i], objects, tab, memo));
273 else
274 for (var k in spec.properties)
275 obj[k] = this.deserialize(spec.properties[k], objects, tab, memo );
276
277 return obj;
278 }
279 },
280 createObjectCache: function(tab)
281 {
282 var objects = [window];
283
284 this.tabs.push(tab);
285 this.objects.push(objects);
286
287 tab.addEventListener("close", function()
288 {
289 var idx = this.tabs.indexOf(tab);
290
291 if (idx != -1)
292 {
293 this.tabs.splice(idx, 1);
294 this.objects.splice(idx, 1);
295 }
296 }.bind(this));
297
298 return objects;
299 },
300 getObjectCache: function(tab)
301 {
302 var idx = this.tabs.indexOf(tab);
303 var objects;
304
305 if (idx != -1)
306 objects = this.objects[idx];
307 else
308 objects = this.objects[idx] = this.createObjectCache(tab);
309
310 return objects;
311 },
312 fail: function(error)
313 {
314 if (error instanceof Error)
315 error = error.message;
316 return {succeed: false, error: error};
317 },
318 _handleMessage: function(message, tab)
319 {
320 var objects = this.getObjectCache(tab);
321
322 switch (message.type)
323 {
324 case "getProperty":
325 var obj = objects[message.objectId];
326
327 try
328 {
329 var value = obj[message.property];
330 }
331 catch (e)
332 {
333 return this.fail(e);
334 }
335
336 return {succeed: true, result: this.serialize(value, objects)};
337 case "setProperty":
338 var obj = objects[message.objectId];
339 var value = this.deserialize(message.value, objects, tab);
340
341 try
342 {
343 obj[message.property] = value;
344 }
345 catch (e)
346 {
347 return this.fail(e);
348 }
349
350 return {succeed: true};
351 case "callFunction":
352 var func = objects[message.functionId];
353 var context = objects[message.contextId];
354
355 var args = [];
356 for (var i = 0; i < message.args.length; i++)
357 args.push(this.deserialize(message.args[i], objects, tab));
358
359 try
360 {
361 var result = func.apply(context, args);
362 }
363 catch (e)
364 {
365 return this.fail(e);
366 }
367
368 return {succeed: true, result: this.serialize(result, objects)};
369 case "inspectObject":
370 var obj = objects[message.objectId];
371 var objectInfo = {properties: {}, isFunction: typeof obj == "functio n"};
372
373 Object.getOwnPropertyNames(obj).forEach(function(prop)
374 {
375 objectInfo.properties[prop] = {
376 enumerable: Object.prototype.propertyIsEnumerable.call(obj, prop )
377 };
378 });
379
380 if (obj.__proto__)
381 objectInfo.prototypeId = this.registerObject(obj.__proto__, object s);
382
383 if (obj == Object.prototype)
384 objectInfo.prototypeOf = "Object";
385 if (obj == Function.prototype)
386 objectInfo.prototypeOf = "Function";
387
388 return objectInfo;
389 }
390 }
391 };
392
393
394 /* Web request blocking */
395
396 ext.webRequest = {
397 onBeforeRequest: {
398 _listeners: [],
399 _urlPatterns: [],
400
401 _handleMessage: function(message, tab)
402 {
403 tab = new Tab(tab);
404
405 for (var i = 0; i < this._listeners.length; i++)
406 {
407 var regex = this._urlPatterns[i];
408
409 if (!regex || regex.test(message))
410 if (this._listeners[i](message.url, message.type, tab, 0, -1) === fa lse)
411 return false;
412 }
413
414 return true;
415 },
416 addListener: function(listener, urls)
417 {
418 var regex;
419
420 if (urls)
421 regex = new RegExp("^(?:" + urls.map(function(url)
422 {
423 return url.split("*").map(function(s)
424 {
425 return s.replace(/([.?+^$[\]\\(){}|-])/g, "\\$1");
426 }).join(".*");
427 }).join("|") + ")($|[?#])");
428
429 this._listeners.push(listener);
430 this._urlPatterns.push(regex);
431 },
432 removeListener: function(listener)
433 {
434 var idx = this._listeners.indexOf(listener);
435
436 if (idx != -1)
437 {
438 this._listeners.splice(idx, 1);
439 this._urlPatterns.splice(idx, 1);
440 }
441 }
442 },
443 handlerBehaviorChanged: function() {}
444 };
445
446
447 /* Synchronous messaging */
448
449 safari.application.addEventListener("message", function(event)
450 {
451 if (event.name == "canLoad")
452 {
453 var handler;
454
455 switch (event.message.type)
456 {
457 case "proxy":
458 handler = proxy;
459 break;
460 case "webRequest":
461 handler = ext.webRequest.onBeforeRequest;
462 break;
463 }
464
465 event.message = handler._handleMessage(event.message.payload, event.targ et);
466 }
467 }, true);
468 }
469
470
471 /* API */
472
473 ext.windows = {
474 getAll: function(callback)
475 {
476 callback(safari.application.browserWindows.map(function(win)
477 {
478 return new Window(win);
479 }));
480 },
481 getLastFocused: function(callback)
482 {
483 callback(new Window(safari.application.activeBrowserWindow));
484 }
485 };
486
487 ext.tabs = {
488 onBeforeNavigate: new TabEventTarget(safari.application, "beforeNavigate", t rue),
489 onCompleted: new TabEventTarget(safari.application, "navigate", true),
490 onActivated: new TabEventTarget(safari.application, "activate", true),
491 onRemoved: new TabEventTarget(safari.application, "close", true)
492 };
493
494 ext.backgroundPage = {
495 getWindow: function()
496 {
497 return safari.extension.globalPage.contentWindow;
498 }
499 };
500
501 ext.onMessage = new MessageEventTarget(safari.application);
502 })();
OLDNEW
« background.js ('K') | « popupBlocker.js ('k') | safari/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld