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

Powered by Google App Engine
This is Rietveld