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

Side by Side Diff: safari/ext/background.js

Issue 5088751004942336: Issue 370 - Right-clicked element is removed independent of created filter (Closed)
Patch Set: Rebase to rev 3c9cea80c481 Created July 18, 2014, 8:54 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 | « qunit/tests/versionComparator.js ('k') | safari/ext/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
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-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 /* Tabs */ 20 /* Pages */
21 21
22 var TabEventTarget = function() 22 var pages = {__proto__: null};
23 { 23 var pageCounter = 0;
24 WrappedEventTarget.apply(this, arguments); 24
25 }; 25 var Page = function(id, tab, url)
26 TabEventTarget.prototype = { 26 {
27 __proto__: WrappedEventTarget.prototype, 27 this._id = id;
28 _wrapListener: function(listener) 28 this._tab = tab;
29 { 29 this._frames = [{url: url, parent: null}];
30 return function(event) 30
31 if (tab.page)
32 this._messageProxy = new ext._MessageProxy(tab.page);
33 else
34 // while the new tab page is shown on Safari 7, the 'page' property
35 // of the tab is undefined, and we can't send messages to that page
36 this._messageProxy = {
37 handleRequest: function() {},
38 handleResponse: function() {},
39 sendMessage: function() {}
40 };
41
42 this.browserAction = new BrowserAction(this);
43 this.contextMenus = new ContextMenus(this);
44 };
45 Page.prototype = {
46 get url()
47 {
48 return this._frames[0].url;
49 },
50 activate: function()
51 {
52 this._tab.activate();
53 },
54 sendMessage: function(message, responseCallback)
55 {
56 this._messageProxy.sendMessage(message, responseCallback, {pageId: this._i d});
57 }
58 };
59
60 var isPageActive = function(page)
61 {
62 var tab = page._tab;
63 return tab == tab.browserWindow.activeTab && page == tab._visiblePage;
64 };
65
66 var forgetPage = function(id)
67 {
68 ext._removeFromAllPageMaps(id);
69
70 delete pages[id]._tab._pages[id];
71 delete pages[id];
72 };
73
74 var replacePage = function(page)
75 {
76 var tab = page._tab;
77 tab._visiblePage = page;
78
79 for (var id in tab._pages)
80 {
81 if (id != page._id)
82 forgetPage(id);
83 }
84
85 if (isPageActive(page))
86 updateToolbarItemForPage(page, tab.browserWindow);
87 };
88
89 ext.pages = {
90 open: function(url, callback)
91 {
92 var tab = safari.application.activeBrowserWindow.openTab();
93 tab.url = url;
94
95 if (callback)
31 { 96 {
32 if (event.target instanceof SafariBrowserTab) 97 var onLoading = function(page)
33 listener(new Tab(event.target));
34 };
35 }
36 };
37
38 var LoadingTabEventTarget = function(target)
39 {
40 WrappedEventTarget.call(this, target, "message", false);
41 };
42 LoadingTabEventTarget.prototype = {
43 __proto__: WrappedEventTarget.prototype,
44 _wrapListener: function(listener)
45 {
46 return function (event)
47 {
48 if (event.name == "loading" && event.message == event.target.url)
49 listener(new Tab(event.target));
50 };
51 }
52 };
53
54 Tab = function(tab)
55 {
56 this._tab = tab;
57
58 this._eventTarget = tab;
59 this._messageDispatcher = tab.page;
60
61 this.onLoading = new LoadingTabEventTarget(tab);
62 this.onCompleted = new TabEventTarget(tab, "navigate", false);
63 this.onActivated = new TabEventTarget(tab, "activate", false);
64 this.onRemoved = new TabEventTarget(tab, "close", false);
65 };
66 Tab.prototype = {
67 get url()
68 {
69 return this._tab.url;
70 },
71 close: function()
72 {
73 this._tab.close();
74 },
75 activate: function()
76 {
77 this._tab.activate();
78 },
79 sendMessage: sendMessage,
80 browserAction: {
81 setIcon: function(path)
82 {
83 safari.extension.toolbarItems[0].image = safari.extension.baseURI + path ;
84 },
85 setTitle: function(title)
86 {
87 safari.extension.toolbarItems[0].toolTip = title;
88 },
89 setBadge: function(badge)
90 {
91 if (!badge)
92 safari.extension.toolbarItems[0].badge = 0;
93 else if ("number" in badge)
94 safari.extension.toolbarItems[0].badge = badge.number;
95 }
96 }
97 };
98
99 TabMap = function()
100 {
101 this._tabs = [];
102 this._values = [];
103
104 this._onClosed = this._onClosed.bind(this);
105 };
106 TabMap.prototype =
107 {
108 get: function(tab) {
109 var idx;
110
111 if (!tab || (idx = this._tabs.indexOf(tab._tab)) == -1)
112 return null;
113
114 return this._values[idx];
115 },
116 set: function(tab, value)
117 {
118 var idx = this._tabs.indexOf(tab._tab);
119
120 if (idx != -1)
121 this._values[idx] = value;
122 else
123 {
124 this._tabs.push(tab._tab);
125 this._values.push(value);
126
127 tab._tab.addEventListener("close", this._onClosed, false);
128 }
129 },
130 has: function(tab)
131 {
132 return this._tabs.indexOf(tab._tab) != -1;
133 },
134 clear: function()
135 {
136 while (this._tabs.length > 0)
137 this._delete(this._tabs[0]);
138 },
139 _delete: function(tab)
140 {
141 var idx = this._tabs.indexOf(tab);
142
143 if (idx != -1)
144 {
145 this._tabs.splice(idx, 1);
146 this._values.splice(idx, 1);
147
148 tab.removeEventListener("close", this._onClosed, false);
149 }
150 },
151 _onClosed: function(event)
152 {
153 this._delete(event.target);
154 }
155 };
156 TabMap.prototype["delete"] = function(tab)
157 {
158 this._delete(tab._tab);
159 };
160
161
162 /* Windows */
163
164 Window = function(win)
165 {
166 this._win = win;
167 }
168 Window.prototype = {
169 get visible()
170 {
171 return this._win.visible;
172 },
173 getAllTabs: function(callback)
174 {
175 callback(this._win.tabs.map(function(tab) { return new Tab(tab); }));
176 },
177 getActiveTab: function(callback)
178 {
179 callback(new Tab(this._win.activeTab));
180 },
181 openTab: function(url, callback)
182 {
183 var tab = this._win.openTab();
184 tab.url = url;
185
186 if (callback)
187 callback(new Tab(tab));
188 }
189 };
190
191 if (safari.extension.globalPage.contentWindow == window)
192 {
193 /* Background page proxy */
194
195 var proxy = {
196 tabs: [],
197 objects: [],
198
199 registerObject: function(obj, objects)
200 {
201 var objectId = objects.indexOf(obj);
202
203 if (objectId == -1)
204 objectId = objects.push(obj) - 1;
205
206 return objectId;
207 },
208 serializeSequence: function(sequence, objects, memo)
209 {
210 if (!memo)
211 memo = {specs: [], arrays: []};
212
213 var items = [];
214 for (var i = 0; i < sequence.length; i++)
215 items.push(this.serialize(sequence[i], objects, memo));
216
217 return items;
218 },
219 serialize: function(obj, objects, memo)
220 {
221 if (typeof obj == "object" && obj != null || typeof obj == "function")
222 { 98 {
223 if (obj.constructor == Array) 99 if (page._tab == tab)
224 { 100 {
225 if (!memo) 101 ext.pages.onLoading.removeListener(onLoading);
226 memo = {specs: [], arrays: []}; 102 callback(page);
227
228 var idx = memo.arrays.indexOf(obj);
229 if (idx != -1)
230 return memo.specs[idx];
231
232 var spec = {type: "array"};
233 memo.specs.push(spec);
234 memo.arrays.push(obj);
235
236 spec.items = this.serializeSequence(obj, objects, memo);
237 return spec;
238 }
239
240 if (obj.constructor != Date && obj.constructor != RegExp)
241 return {type: "object", objectId: this.registerObject(obj, objects)} ;
242 }
243
244 return {type: "value", value: obj};
245 },
246 createCallback: function(callbackId, tab)
247 {
248 var proxy = this;
249
250 return function()
251 {
252 var idx = proxy.tabs.indexOf(tab);
253
254 if (idx != -1) {
255 var objects = proxy.objects[idx];
256
257 tab.page.dispatchMessage("proxyCallback",
258 {
259 callbackId: callbackId,
260 contextId: proxy.registerObject(this, objects),
261 args: proxy.serializeSequence(arguments, objects)
262 });
263 } 103 }
264 }; 104 };
265 }, 105 ext.pages.onLoading.addListener(onLoading);
266 deserialize: function(spec, objects, tab, memo) 106 }
107 },
108 query: function(info, callback)
109 {
110 var matchedPages = [];
111
112 for (var id in pages)
267 { 113 {
268 switch (spec.type) 114 var page = pages[id];
115 var win = page._tab.browserWindow;
116
117 if ("active" in info && info.active != isPageActive(page))
118 continue;
119 if ("lastFocusedWindow" in info && info.lastFocusedWindow != (win == saf ari.application.activeBrowserWindow))
120 continue;
121
122 matchedPages.push(page);
123 };
124
125 callback(matchedPages);
126 },
127 onLoading: new ext._EventTarget()
128 };
129
130 safari.application.addEventListener("close", function(event)
131 {
132 // this event is dispatched on closing windows and tabs. However when a
133 // window is closed, it is first dispatched on each tab in the window and
134 // then on the window itself. But we are only interested in closed tabs.
135 if (!(event.target instanceof SafariBrowserTab))
136 return;
137
138 // when a tab is closed, forget the previous page associated with that
139 // tab. Note that it wouldn't be sufficient do that when the old page
140 // is unloading, because Safari dispatches window.onunload only when
141 // reloading the page or following links, but not when closing the tab.
142 for (var id in event.target._pages)
143 forgetPage(id);
144 }, true);
145
146
147 /* Browser actions */
148
149 var toolbarItemProperties = {};
150
151 var getToolbarItemForWindow = function(win)
152 {
153 for (var i = 0; i < safari.extension.toolbarItems.length; i++)
154 {
155 var toolbarItem = safari.extension.toolbarItems[i];
156
157 if (toolbarItem.browserWindow == win)
158 return toolbarItem;
159 }
160
161 return null;
162 };
163
164 var updateToolbarItemForPage = function(page, win) {
165 var toolbarItem = getToolbarItemForWindow(win);
166 if (!toolbarItem)
167 return;
168
169 for (var name in toolbarItemProperties)
170 {
171 var property = toolbarItemProperties[name];
172
173 if (page && property.pages.has(page))
174 toolbarItem[name] = property.pages.get(page);
175 else
176 toolbarItem[name] = property.global;
177 }
178 };
179
180 var BrowserAction = function(page)
181 {
182 this._page = page;
183 };
184 BrowserAction.prototype = {
185 _set: function(name, value)
186 {
187 var toolbarItem = getToolbarItemForWindow(this._page._tab.browserWindow);
188 if (!toolbarItem)
189 return;
190
191 var property = toolbarItemProperties[name];
192 if (!property)
193 property = toolbarItemProperties[name] = {
194 pages: new ext.PageMap(),
195 global: toolbarItem[name]
196 };
197
198 property.pages.set(this._page, value);
199
200 if (isPageActive(this._page))
201 toolbarItem[name] = value;
202 },
203 setIcon: function(path)
204 {
205 this._set("image", safari.extension.baseURI + path.replace("$size", "16")) ;
206 },
207 setBadge: function(badge)
208 {
209 if (!badge)
210 this._set("badge", 0);
211 else if ("number" in badge)
212 this._set("badge", badge.number);
213 }
214 };
215
216 safari.application.addEventListener("activate", function(event)
217 {
218 // this event is also dispatched on windows that got focused. But we
219 // are only interested in tabs, which became active in their window.
220 if (!(event.target instanceof SafariBrowserTab))
221 return;
222
223 // update the toolbar item for the page visible in the tab that just
224 // became active. If we can't find that page (e.g. when a page was
225 // opened in a new tab, and our content script didn't run yet), the
226 // toolbar item of the window, is reset to its intial configuration.
227 updateToolbarItemForPage(event.target._visiblePage, event.target.browserWind ow);
228 }, true);
229
230
231 /* Context menus */
232
233 var contextMenuItems = new ext.PageMap();
234
235 var ContextMenus = function(page)
236 {
237 this._page = page;
238 };
239 ContextMenus.prototype = {
240 create: function(item)
241 {
242 var items = contextMenuItems.get(this._page);
243 if (!items)
244 contextMenuItems.set(this._page, items = []);
245
246 items.push(item);
247 },
248 removeAll: function()
249 {
250 contextMenuItems.delete(this._page);
251 }
252 };
253
254 safari.application.addEventListener("contextmenu", function(event)
255 {
256 var pageId = event.userInfo.pageId;
257 if (!pageId)
258 return;
259
260 var page = pages[event.userInfo.pageId];
261 var items = contextMenuItems.get(page);
262 if (!items)
263 return;
264
265 var context = event.userInfo.tagName;
266 if (context == "img")
267 context = "image";
268 if (!event.userInfo.srcUrl)
269 context = null;
270
271 for (var i = 0; i < items.length; i++)
272 {
273 // Supported contexts are: all, audio, image, video
274 var menuItem = items[i];
275 if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(co ntext) == -1)
276 continue;
277
278 event.contextMenu.appendContextMenuItem(i, menuItem.title);
279 }
280 });
281
282 safari.application.addEventListener("command", function(event)
283 {
284 var page = pages[event.userInfo.pageId];
285 var items = contextMenuItems.get(page);
286
287 items[event.command].onclick(event.userInfo.srcUrl, page);
288 });
289
290
291 /* Web requests */
292
293 ext.webRequest = {
294 onBeforeRequest: new ext._EventTarget(true),
295 handlerBehaviorChanged: function() {}
296 };
297
298
299 /* Background page */
300
301 ext.backgroundPage = {
302 getWindow: function()
303 {
304 return window;
305 }
306 };
307
308
309 /* Background page proxy (for access from content scripts) */
310
311 var backgroundPageProxy = {
312 cache: new ext.PageMap(),
313
314 registerObject: function(obj, objects)
315 {
316 var objectId = objects.indexOf(obj);
317
318 if (objectId == -1)
319 objectId = objects.push(obj) - 1;
320
321 return objectId;
322 },
323 serializeSequence: function(sequence, objects, memo)
324 {
325 if (!memo)
326 memo = {specs: [], arrays: []};
327
328 var items = [];
329 for (var i = 0; i < sequence.length; i++)
330 items.push(this.serialize(sequence[i], objects, memo));
331
332 return items;
333 },
334 serialize: function(obj, objects, memo)
335 {
336 if (typeof obj == "object" && obj != null || typeof obj == "function")
337 {
338 if (obj.constructor == Array)
269 { 339 {
270 case "value": 340 if (!memo)
271 return spec.value; 341 memo = {specs: [], arrays: []};
272 case "hosted": 342
273 return objects[spec.objectId]; 343 var idx = memo.arrays.indexOf(obj);
274 case "callback": 344 if (idx != -1)
275 return this.createCallback(spec.callbackId, tab); 345 return memo.specs[idx];
276 case "object": 346
277 case "array": 347 var spec = {type: "array"};
278 if (!memo) 348 memo.specs.push(spec);
279 memo = {specs: [], objects: []}; 349 memo.arrays.push(obj);
280 350
281 var idx = memo.specs.indexOf(spec); 351 spec.items = this.serializeSequence(obj, objects, memo);
282 if (idx != -1) 352 return spec;
283 return memo.objects[idx]; 353 }
284 354
285 var obj; 355 if (obj.constructor != Date && obj.constructor != RegExp)
286 if (spec.type == "array") 356 return {type: "object", objectId: this.registerObject(obj, objects)};
287 obj = []; 357 }
358
359 return {type: "value", value: obj};
360 },
361 createCallback: function(callbackId, pageId, frameId)
362 {
363 var proxy = this;
364
365 return function()
366 {
367 var page = pages[pageId];
368 if (!page)
369 return;
370
371 var objects = proxy.cache.get(page);
372 if (!objects)
373 return;
374
375 page._tab.page.dispatchMessage("proxyCallback",
376 {
377 pageId: pageId,
378 frameId: frameId,
379 callbackId: callbackId,
380 contextId: proxy.registerObject(this, objects),
381 args: proxy.serializeSequence(arguments, objects)
382 });
383 };
384 },
385 deserialize: function(spec, objects, pageId, memo)
386 {
387 switch (spec.type)
388 {
389 case "value":
390 return spec.value;
391 case "hosted":
392 return objects[spec.objectId];
393 case "callback":
394 return this.createCallback(spec.callbackId, pageId, spec.frameId);
395 case "object":
396 case "array":
397 if (!memo)
398 memo = {specs: [], objects: []};
399
400 var idx = memo.specs.indexOf(spec);
401 if (idx != -1)
402 return memo.objects[idx];
403
404 var obj;
405 if (spec.type == "array")
406 obj = [];
407 else
408 obj = {};
409
410 memo.specs.push(spec);
411 memo.objects.push(obj);
412
413 if (spec.type == "array")
414 for (var i = 0; i < spec.items.length; i++)
415 obj.push(this.deserialize(spec.items[i], objects, pageId, memo));
416 else
417 for (var k in spec.properties)
418 obj[k] = this.deserialize(spec.properties[k], objects, pageId, mem o);
419
420 return obj;
421 }
422 },
423 getObjectCache: function(page)
424 {
425 var objects = this.cache.get(page);
426 if (!objects)
427 {
428 objects = [window];
429 this.cache.set(page, objects);
430 }
431 return objects;
432 },
433 fail: function(error)
434 {
435 if (error instanceof Error)
436 error = error.message;
437 return {succeed: false, error: error};
438 },
439 handleMessage: function(message)
440 {
441 var objects = this.getObjectCache(pages[message.pageId]);
442
443 switch (message.type)
444 {
445 case "getProperty":
446 var obj = objects[message.objectId];
447
448 try
449 {
450 var value = obj[message.property];
451 }
452 catch (e)
453 {
454 return this.fail(e);
455 }
456
457 return {succeed: true, result: this.serialize(value, objects)};
458 case "setProperty":
459 var obj = objects[message.objectId];
460 var value = this.deserialize(message.value, objects, message.pageId);
461
462 try
463 {
464 obj[message.property] = value;
465 }
466 catch (e)
467 {
468 return this.fail(e);
469 }
470
471 return {succeed: true};
472 case "callFunction":
473 var func = objects[message.functionId];
474 var context = objects[message.contextId];
475
476 var args = [];
477 for (var i = 0; i < message.args.length; i++)
478 args.push(this.deserialize(message.args[i], objects, message.pageId) );
479
480 try
481 {
482 var result = func.apply(context, args);
483 }
484 catch (e)
485 {
486 return this.fail(e);
487 }
488
489 return {succeed: true, result: this.serialize(result, objects)};
490 case "inspectObject":
491 var obj = objects[message.objectId];
492 var objectInfo = {properties: {}, isFunction: typeof obj == "function" };
493
494 Object.getOwnPropertyNames(obj).forEach(function(prop)
495 {
496 objectInfo.properties[prop] = {
497 enumerable: Object.prototype.propertyIsEnumerable.call(obj, prop)
498 };
499 });
500
501 if (obj.__proto__)
502 objectInfo.prototypeId = this.registerObject(obj.__proto__, objects) ;
503
504 if (obj == Object.prototype)
505 objectInfo.prototypeOf = "Object";
506 if (obj == Function.prototype)
507 objectInfo.prototypeOf = "Function";
508
509 return objectInfo;
510 }
511 }
512 };
513
514
515 /* Message processing */
516
517 safari.application.addEventListener("message", function(event)
518 {
519 switch (event.name)
520 {
521 case "canLoad":
522 switch (event.message.category)
523 {
524 case "loading":
525 var tab = event.target;
526 var message = event.message;
527
528 var pageId;
529 var frameId;
530
531 if (message.isTopLevel)
532 {
533 pageId = ++pageCounter;
534 frameId = 0;
535
536 if (!('_pages' in tab))
537 tab._pages = {__proto__: null};
538
539 var page = new Page(pageId, tab, message.url);
540 pages[pageId] = tab._pages[pageId] = page;
541
542 // when a new page is shown, forget the previous page associated
543 // with its tab, and reset the toolbar item if necessary.
544 // Note that it wouldn't be sufficient to do that when the old
545 // page is unloading, because Safari dispatches window.onunload
546 // only when reloading the page or following links, but not when
547 // you enter a new URL in the address bar.
548 if (!message.isPrerendered)
549 replacePage(page);
550
551 ext.pages.onLoading._dispatch(page);
552 }
288 else 553 else
289 obj = {};
290
291 memo.specs.push(spec);
292 memo.objects.push(obj);
293
294 if (spec.type == "array")
295 for (var i = 0; i < spec.items.length; i++)
296 obj.push(this.deserialize(spec.items[i], objects, tab, memo));
297 else
298 for (var k in spec.properties)
299 obj[k] = this.deserialize(spec.properties[k], objects, tab, memo );
300
301 return obj;
302 }
303 },
304 createObjectCache: function(tab)
305 {
306 var objects = [window];
307
308 this.tabs.push(tab);
309 this.objects.push(objects);
310
311 tab.addEventListener("close", function()
312 {
313 var idx = this.tabs.indexOf(tab);
314
315 if (idx != -1)
316 {
317 this.tabs.splice(idx, 1);
318 this.objects.splice(idx, 1);
319 }
320 }.bind(this));
321
322 return objects;
323 },
324 getObjectCache: function(tab)
325 {
326 var idx = this.tabs.indexOf(tab);
327 var objects;
328
329 if (idx != -1)
330 objects = this.objects[idx];
331 else
332 objects = this.objects[idx] = this.createObjectCache(tab);
333
334 return objects;
335 },
336 fail: function(error)
337 {
338 if (error instanceof Error)
339 error = error.message;
340 return {succeed: false, error: error};
341 },
342 _handleMessage: function(message, tab)
343 {
344 var objects = this.getObjectCache(tab);
345
346 switch (message.type)
347 {
348 case "getProperty":
349 var obj = objects[message.objectId];
350
351 try
352 { 554 {
353 var value = obj[message.property]; 555 var page;
556 var parentFrame;
557
558 var lastPageId;
559 var lastPage;
560 var lastPageTopLevelFrame;
561
562 // find the parent frame and its page for this sub frame,
563 // by matching its referrer with the URL of frames previously
564 // loaded in the same tab. If there is more than one match,
565 // the most recent loaded page and frame is preferred.
566 for (var curPageId in tab._pages)
567 {
568 var curPage = pages[curPageId];
569
570 for (var i = 0; i < curPage._frames.length; i++)
571 {
572 var curFrame = curPage._frames[i];
573
574 if (curFrame.url == message.referrer)
575 {
576 pageId = curPageId;
577 page = curPage;
578 parentFrame = curFrame;
579 }
580
581 if (i == 0)
582 {
583 lastPageId = curPageId;
584 lastPage = curPage;
585 lastPageTopLevelFrame = curFrame;
586 }
587 }
588 }
589
590 // if we can't find the parent frame and its page, fall back to
591 // the page most recently loaded in the tab and its top level fram e
592 if (!page)
593 {
594 pageId = lastPageId;
595 page = lastPage;
596 parentFrame = lastPageTopLevelFrame;
597 }
598
599 frameId = page._frames.length;
600 page._frames.push({url: message.url, parent: parentFrame});
354 } 601 }
355 catch (e) 602
356 { 603 event.message = {pageId: pageId, frameId: frameId};
357 return this.fail(e);
358 }
359
360 return {succeed: true, result: this.serialize(value, objects)};
361 case "setProperty":
362 var obj = objects[message.objectId];
363 var value = this.deserialize(message.value, objects, tab);
364
365 try
366 {
367 obj[message.property] = value;
368 }
369 catch (e)
370 {
371 return this.fail(e);
372 }
373
374 return {succeed: true};
375 case "callFunction":
376 var func = objects[message.functionId];
377 var context = objects[message.contextId];
378
379 var args = [];
380 for (var i = 0; i < message.args.length; i++)
381 args.push(this.deserialize(message.args[i], objects, tab));
382
383 try
384 {
385 var result = func.apply(context, args);
386 }
387 catch (e)
388 {
389 return this.fail(e);
390 }
391
392 return {succeed: true, result: this.serialize(result, objects)};
393 case "inspectObject":
394 var obj = objects[message.objectId];
395 var objectInfo = {properties: {}, isFunction: typeof obj == "functio n"};
396
397 Object.getOwnPropertyNames(obj).forEach(function(prop)
398 {
399 objectInfo.properties[prop] = {
400 enumerable: Object.prototype.propertyIsEnumerable.call(obj, prop )
401 };
402 });
403
404 if (obj.__proto__)
405 objectInfo.prototypeId = this.registerObject(obj.__proto__, object s);
406
407 if (obj == Object.prototype)
408 objectInfo.prototypeOf = "Object";
409 if (obj == Function.prototype)
410 objectInfo.prototypeOf = "Function";
411
412 return objectInfo;
413 }
414 }
415 };
416
417
418 /* Web request blocking */
419
420 ext.webRequest = {
421 onBeforeRequest: {
422 _listeners: [],
423 _urlPatterns: [],
424
425 _handleMessage: function(message, tab)
426 {
427 tab = new Tab(tab);
428
429 for (var i = 0; i < this._listeners.length; i++)
430 {
431 var regex = this._urlPatterns[i];
432
433 if ((!regex || regex.test(message.url)) && this._listeners[i](messag e.url, message.type, tab, 0, -1) === false)
434 return false;
435 }
436
437 return true;
438 },
439 addListener: function(listener, urls)
440 {
441 var regex;
442
443 if (urls)
444 regex = new RegExp("^(?:" + urls.map(function(url)
445 {
446 return url.split("*").map(function(s)
447 {
448 return s.replace(/([.?+^$[\]\\(){}|-])/g, "\\$1");
449 }).join(".*");
450 }).join("|") + ")($|[?#])");
451
452 this._listeners.push(listener);
453 this._urlPatterns.push(regex);
454 },
455 removeListener: function(listener)
456 {
457 var idx = this._listeners.indexOf(listener);
458
459 if (idx != -1)
460 {
461 this._listeners.splice(idx, 1);
462 this._urlPatterns.splice(idx, 1);
463 }
464 }
465 },
466 handlerBehaviorChanged: function() {}
467 };
468
469
470 /* Synchronous messaging */
471
472 safari.application.addEventListener("message", function(event)
473 {
474 if (event.name == "canLoad")
475 {
476 var handler;
477
478 switch (event.message.type)
479 {
480 case "proxy":
481 handler = proxy;
482 break; 604 break;
483 case "webRequest": 605 case "webRequest":
484 handler = ext.webRequest.onBeforeRequest; 606 var page = pages[event.message.pageId];
607
608 event.message = ext.webRequest.onBeforeRequest._dispatch(
609 event.message.url,
610 event.message.type,
611 page,
612 page._frames[event.message.frameId]
613 );
614 break;
615 case "proxy":
616 event.message = backgroundPageProxy.handleMessage(event.message);
485 break; 617 break;
486 } 618 }
487 619 break;
488 event.message = handler._handleMessage(event.message.payload, event.targ et); 620 case "request":
489 } 621 var page = pages[event.message.pageId];
490 }, true); 622 var sender = {page: page, frame: page._frames[event.message.frameId]};
491 } 623 page._messageProxy.handleRequest(event.message, sender);
492 624 break;
493 625 case "response":
494 /* API */ 626 pages[event.message.pageId]._messageProxy.handleResponse(event.message);
495 627 break;
496 ext.windows = { 628 case "replaced":
497 getAll: function(callback) 629 // when a prerendered page is shown, forget the previous page
498 { 630 // associated with its tab, and reset the toolbar item if necessary.
499 callback(safari.application.browserWindows.map(function(win) 631 // Note that it wouldn't be sufficient to do that when the old
500 { 632 // page is unloading, because Safari dispatches window.onunload
501 return new Window(win); 633 // only when reloading the page or following links, but not when
502 })); 634 // the current page is replaced with a prerendered page.
503 }, 635 replacePage(pages[event.message.pageId]);
504 getLastFocused: function(callback) 636 break;
505 { 637 }
506 callback(new Window(safari.application.activeBrowserWindow)); 638 });
507 } 639
508 }; 640
509 641 /* Storage */
510 ext.tabs = { 642
511 onLoading: new LoadingTabEventTarget(safari.application), 643 ext.storage = safari.extension.settings;
512 onCompleted: new TabEventTarget(safari.application, "navigate", true),
513 onActivated: new TabEventTarget(safari.application, "activate", true),
514 onRemoved: new TabEventTarget(safari.application, "close", true)
515 };
516
517 ext.backgroundPage = {
518 getWindow: function()
519 {
520 return safari.extension.globalPage.contentWindow;
521 }
522 };
523
524 ext.onMessage = new MessageEventTarget(safari.application);
525
526 // TODO: Implement context menu
527 ext.contextMenus = {
528 create: function(title, contexts, onclick) {},
529 removeAll: function(callback) {}
530 };
531
532 // Safari will load the bubble once, and then show it everytime the icon is
533 // clicked. While Chrome loads it everytime you click the icon. So in order to
534 // force the same behavior in Safari, we are going to reload the page of the
535 // bubble everytime it is shown.
536 if (safari.extension.globalPage.contentWindow != window)
537 safari.application.addEventListener("popover", function()
538 {
539 document.documentElement.style.display = "none";
540 document.location.reload();
541 }, true);
542 })(); 644 })();
OLDNEW
« no previous file with comments | « qunit/tests/versionComparator.js ('k') | safari/ext/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld