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-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 |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 }, | 92 }, |
93 _removeTab: function(tab) | 93 _removeTab: function(tab) |
94 { | 94 { |
95 delete this._tabs[tab._id]; | 95 delete this._tabs[tab._id]; |
96 | 96 |
97 if (Object.keys(this._tabs).length == 0) | 97 if (Object.keys(this._tabs).length == 0) |
98 this.removeListener(this._sharedListener); | 98 this.removeListener(this._sharedListener); |
99 } | 99 } |
100 }; | 100 }; |
101 | 101 |
| 102 var BeforeNavigateTabEventTarget = function() |
| 103 { |
| 104 TabEventTarget.call(this, chrome.webNavigation.onBeforeNavigate); |
| 105 }; |
| 106 BeforeNavigateTabEventTarget.prototype = { |
| 107 __proto__: TabEventTarget.prototype, |
| 108 _wrapListener: function(listener) |
| 109 { |
| 110 return function(details) |
| 111 { |
| 112 if (details.frameId == 0) |
| 113 listener(new Tab({id: details.tabId, url: details.url})); |
| 114 }; |
| 115 } |
| 116 }; |
| 117 |
102 var LoadingTabEventTarget = function() | 118 var LoadingTabEventTarget = function() |
103 { | 119 { |
104 TabEventTarget.call(this, chrome.tabs.onUpdated); | 120 TabEventTarget.call(this, chrome.tabs.onUpdated); |
105 }; | 121 }; |
106 LoadingTabEventTarget.prototype = { | 122 LoadingTabEventTarget.prototype = { |
107 __proto__: TabEventTarget.prototype, | 123 __proto__: TabEventTarget.prototype, |
108 _wrapListener: function(listener) | 124 _wrapListener: function(listener) |
109 { | 125 { |
110 return function(id, info, tab) | 126 return function(id, info, tab) |
111 { | 127 { |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
154 TabEventTarget.call(this, chrome.tabs.onRemoved); | 170 TabEventTarget.call(this, chrome.tabs.onRemoved); |
155 }; | 171 }; |
156 RemovedTabEventTarget.prototype = { | 172 RemovedTabEventTarget.prototype = { |
157 __proto__: TabEventTarget.prototype, | 173 __proto__: TabEventTarget.prototype, |
158 _wrapListener: function(listener) | 174 _wrapListener: function(listener) |
159 { | 175 { |
160 return function(id) { listener(new Tab({id: id})); }; | 176 return function(id) { listener(new Tab({id: id})); }; |
161 } | 177 } |
162 }; | 178 }; |
163 | 179 |
164 var BeforeRequestEventTarget = function() | 180 var BackgroundMessageEventTarget = function() |
165 { | 181 { |
166 WrappedEventTarget.call(this, chrome.webRequest.onBeforeRequest); | 182 MessageEventTarget.call(this); |
167 }; | 183 } |
168 BeforeRequestEventTarget.prototype = { | 184 BackgroundMessageEventTarget.prototype = { |
169 __proto__: WrappedEventTarget.prototype, | 185 __proto__: MessageEventTarget.prototype, |
170 _wrapListener: function(listener) | 186 _wrapSender: function(sender) |
171 { | 187 { |
172 return function(details) | 188 var tab = new Tab(sender.tab); |
173 { | 189 return {tab: tab, frame: new Frame({url: sender.url, tab: tab})}; |
174 var tab = null; | |
175 | |
176 if (details.tabId != -1) | |
177 tab = new Tab({id: details.tabId}); | |
178 | |
179 return {cancel: listener( | |
180 details.url, | |
181 details.type, | |
182 tab, | |
183 details.frameId, | |
184 details.parentFrameId | |
185 ) === false}; | |
186 }; | |
187 }, | |
188 _prepareExtraArguments: function(urls) | |
189 { | |
190 return [urls ? {urls: urls} : {}, ["blocking"]]; | |
191 } | 190 } |
192 }; | 191 }; |
193 | 192 |
194 | 193 |
195 /* Tabs */ | 194 /* Tabs */ |
196 | 195 |
197 var sendMessage = chrome.tabs.sendMessage || chrome.tabs.sendRequest; | 196 var sendMessage = chrome.tabs.sendMessage || chrome.tabs.sendRequest; |
198 | 197 |
199 var BrowserAction = function(tabId) | 198 var BrowserAction = function(tabId) |
200 { | 199 { |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
234 tabId: this._tabId, | 233 tabId: this._tabId, |
235 text: badge.number.toString() | 234 text: badge.number.toString() |
236 }); | 235 }); |
237 } | 236 } |
238 } | 237 } |
239 }; | 238 }; |
240 | 239 |
241 Tab = function(tab) | 240 Tab = function(tab) |
242 { | 241 { |
243 this._id = tab.id; | 242 this._id = tab.id; |
| 243 this._url = tab.url; |
244 | 244 |
245 this.url = tab.url; | |
246 this.browserAction = new BrowserAction(tab.id); | 245 this.browserAction = new BrowserAction(tab.id); |
247 | 246 |
| 247 this.onBeforeNavigate = ext.tabs.onBeforeNavigate._bindToTab(this); |
248 this.onLoading = ext.tabs.onLoading._bindToTab(this); | 248 this.onLoading = ext.tabs.onLoading._bindToTab(this); |
249 this.onCompleted = ext.tabs.onCompleted._bindToTab(this); | 249 this.onCompleted = ext.tabs.onCompleted._bindToTab(this); |
250 this.onActivated = ext.tabs.onActivated._bindToTab(this); | 250 this.onActivated = ext.tabs.onActivated._bindToTab(this); |
251 this.onRemoved = ext.tabs.onRemoved._bindToTab(this); | 251 this.onRemoved = ext.tabs.onRemoved._bindToTab(this); |
252 }; | 252 }; |
253 Tab.prototype = { | 253 Tab.prototype = { |
| 254 get url() |
| 255 { |
| 256 // usually our Tab objects are created from chrome Tab objects, which |
| 257 // provide the url. So we can return the url given in the constructor. |
| 258 if (this._url != null) |
| 259 return this._url; |
| 260 |
| 261 // but sometimes we only have the id when we create a Tab object. |
| 262 // In that case we get the url from top frame of the tab, recorded by |
| 263 // the onBeforeRequest handler. |
| 264 var frames = framesOfTabs.get(this); |
| 265 if (frames) |
| 266 { |
| 267 var frame = frames[0]; |
| 268 if (frame) |
| 269 return frame.url; |
| 270 } |
| 271 }, |
254 close: function() | 272 close: function() |
255 { | 273 { |
256 chrome.tabs.remove(this._id); | 274 chrome.tabs.remove(this._id); |
257 }, | 275 }, |
258 activate: function() | 276 activate: function() |
259 { | 277 { |
260 chrome.tabs.update(this._id, {selected: true}); | 278 chrome.tabs.update(this._id, {selected: true}); |
261 }, | 279 }, |
262 sendMessage: function(message, responseCallback) | 280 sendMessage: function(message, responseCallback) |
263 { | 281 { |
264 sendMessage(this._id, message, responseCallback); | 282 sendMessage(this._id, message, responseCallback); |
265 } | 283 } |
266 }; | 284 }; |
267 | 285 |
268 TabMap = function() | 286 TabMap = function(deleteTabOnBeforeNavigate) |
269 { | 287 { |
270 this._map = {}; | 288 this._map = {}; |
271 this.delete = this.delete.bind(this); | 289 |
| 290 this._delete = this._delete.bind(this); |
| 291 this._deleteTabOnBeforeNavigate = deleteTabOnBeforeNavigate; |
272 }; | 292 }; |
273 TabMap.prototype = { | 293 TabMap.prototype = { |
274 get: function(tab) | 294 get: function(tab) |
275 { | 295 { |
276 return (this._map[tab._id] || {}).value; | 296 return (this._map[tab._id] || {}).value; |
277 }, | 297 }, |
278 set: function(tab, value) | 298 set: function(tab, value) |
279 { | 299 { |
280 if (!(tab._id in this._map)) | 300 if (!(tab._id in this._map)) |
281 tab.onRemoved.addListener(this.delete); | 301 { |
| 302 tab.onRemoved.addListener(this._delete); |
| 303 if (this._deleteTabOnBeforeNavigate) |
| 304 tab.onBeforeNavigate.addListener(this._delete); |
| 305 } |
282 | 306 |
283 this._map[tab._id] = {tab: tab, value: value}; | 307 this._map[tab._id] = {tab: tab, value: value}; |
284 }, | 308 }, |
285 has: function(tab) | 309 has: function(tab) |
286 { | 310 { |
287 return tab._id in this._map; | 311 return tab._id in this._map; |
288 }, | 312 }, |
289 clear: function() | 313 clear: function() |
290 { | 314 { |
291 for (var id in this._map) | 315 for (var id in this._map) |
292 this.delete(this._map[id].tab); | 316 this.delete(this._map[id].tab); |
| 317 }, |
| 318 _delete: function(tab) |
| 319 { |
| 320 // delay so that other event handlers can still lookup this tab |
| 321 setTimeout(this.delete.bind(this, tab), 0); |
293 } | 322 } |
294 }; | 323 }; |
295 TabMap.prototype["delete"] = function(tab) | 324 TabMap.prototype["delete"] = function(tab) |
296 { | 325 { |
297 delete this._map[tab._id]; | 326 delete this._map[tab._id]; |
298 tab.onRemoved.removeListener(this.delete); | 327 |
| 328 tab.onRemoved.removeListener(this._delete); |
| 329 tab.onBeforeNavigate.removeListener(this._delete); |
299 }; | 330 }; |
300 | 331 |
301 | 332 |
302 /* Windows */ | 333 /* Windows */ |
303 | 334 |
304 Window = function(win) | 335 Window = function(win) |
305 { | 336 { |
306 this._id = win.id; | 337 this._id = win.id; |
307 this.visible = win.status != "minimized"; | 338 this.visible = win.status != "minimized"; |
308 }; | 339 }; |
(...skipping 20 matching lines...) Expand all Loading... |
329 chrome.tabs.create(props); | 360 chrome.tabs.create(props); |
330 else | 361 else |
331 chrome.tabs.create(props, function(tab) | 362 chrome.tabs.create(props, function(tab) |
332 { | 363 { |
333 callback(new Tab(tab)); | 364 callback(new Tab(tab)); |
334 }); | 365 }); |
335 } | 366 } |
336 }; | 367 }; |
337 | 368 |
338 | 369 |
| 370 /* Frames */ |
| 371 |
| 372 var framesOfTabs = new TabMap(); |
| 373 |
| 374 Frame = function(params) |
| 375 { |
| 376 this._tab = params.tab; |
| 377 this._id = params.id; |
| 378 this._url = params.url; |
| 379 }; |
| 380 Frame.prototype = { |
| 381 get url() |
| 382 { |
| 383 if (this._url != null) |
| 384 return this._url; |
| 385 |
| 386 var frames = framesOfTabs.get(this._tab); |
| 387 if (frames) |
| 388 { |
| 389 var frame = frames[this._id]; |
| 390 if (frame) |
| 391 return frame.url; |
| 392 } |
| 393 }, |
| 394 get parent() |
| 395 { |
| 396 var frames = framesOfTabs.get(this._tab); |
| 397 if (frames) |
| 398 { |
| 399 var frame; |
| 400 if (this._id != null) |
| 401 frame = frames[this._id]; |
| 402 else |
| 403 { |
| 404 // the frame ID wasn't available when we created |
| 405 // the Frame object (e.g. for the onMessage event), |
| 406 // so we have to find the frame details by their URL. |
| 407 for (var frameId in frames) |
| 408 { |
| 409 if (frames[frameId].url == this._url) |
| 410 { |
| 411 frame = frames[frameId]; |
| 412 break; |
| 413 } |
| 414 } |
| 415 } |
| 416 |
| 417 if (frame && frame.parent != -1) |
| 418 return new Frame({id: frame.parent, tab: this._tab}); |
| 419 else |
| 420 return null; |
| 421 } |
| 422 } |
| 423 }; |
| 424 |
| 425 |
| 426 /* Web request blocking */ |
| 427 |
| 428 chrome.webRequest.onBeforeRequest.addListener(function(details) |
| 429 { |
| 430 // the high-level code isn't interested in requests that aren't related |
| 431 // to a tab and since those can only be handled in Chrome, we ignore |
| 432 // them here instead of in the browser independent high-level code. |
| 433 if (details.tabId == -1) |
| 434 return; |
| 435 |
| 436 var tab = new Tab({id: details.tabId}); |
| 437 var frames = framesOfTabs.get(tab); |
| 438 |
| 439 if (!frames) |
| 440 { |
| 441 frames = []; |
| 442 framesOfTabs.set(tab, frames); |
| 443 |
| 444 // assume that the first request belongs to the top frame. Chrome |
| 445 // may give the top frame the type "object" instead of "main_frame". |
| 446 // https://code.google.com/p/chromium/issues/detail?id=281711 |
| 447 if (frameId == 0) |
| 448 details.type = "main_frame"; |
| 449 } |
| 450 |
| 451 var frameId; |
| 452 if (details.type == "main_frame" || details.type == "sub_frame") |
| 453 { |
| 454 frameId = details.parentFrameId; |
| 455 frames[details.frameId] = {url: details.url, parent: frameId}; |
| 456 |
| 457 // the high-level code isn't interested in top frame requests and |
| 458 // since those can only be handled in Chrome, we ignore them here |
| 459 // instead of in the browser independent high-level code. |
| 460 if (details.type == "main_frame") |
| 461 return; |
| 462 } |
| 463 else |
| 464 frameId = details.frameId; |
| 465 |
| 466 // the high-level code relies on the frame. However in case the frame can't |
| 467 // be controlled by the extension or the extension was (re)loaded after the |
| 468 // frame was loaded, the frame is unknown and we have to ignore the request. |
| 469 if (!(frameId in frames)) |
| 470 return; |
| 471 |
| 472 var frame = new Frame({id: frameId, tab: tab}); |
| 473 |
| 474 for (var i = 0; i < ext.webRequest.onBeforeRequest._listeners.length; i++) |
| 475 { |
| 476 if (ext.webRequest.onBeforeRequest._listeners[i](details.url, details.type
, tab, frame) === false) |
| 477 return {cancel: true}; |
| 478 } |
| 479 }, {urls: ["<all_urls>"]}, ["blocking"]); |
| 480 |
| 481 |
339 /* API */ | 482 /* API */ |
340 | 483 |
341 ext.windows = { | 484 ext.windows = { |
342 getAll: function(callback) | 485 getAll: function(callback) |
343 { | 486 { |
344 chrome.windows.getAll(function(windows) | 487 chrome.windows.getAll(function(windows) |
345 { | 488 { |
346 callback(windows.map(function(win) | 489 callback(windows.map(function(win) |
347 { | 490 { |
348 return new Window(win); | 491 return new Window(win); |
349 })); | 492 })); |
350 }); | 493 }); |
351 }, | 494 }, |
352 getLastFocused: function(callback) | 495 getLastFocused: function(callback) |
353 { | 496 { |
354 chrome.windows.getLastFocused(function(win) | 497 chrome.windows.getLastFocused(function(win) |
355 { | 498 { |
356 callback(new Window(win)); | 499 callback(new Window(win)); |
357 }); | 500 }); |
358 } | 501 } |
359 }; | 502 }; |
360 | 503 |
361 ext.tabs = { | 504 ext.tabs = { |
| 505 onBeforeNavigate: new BeforeNavigateTabEventTarget(), |
362 onLoading: new LoadingTabEventTarget(), | 506 onLoading: new LoadingTabEventTarget(), |
363 onCompleted: new CompletedTabEventTarget(), | 507 onCompleted: new CompletedTabEventTarget(), |
364 onActivated: new ActivatedTabEventTarget(), | 508 onActivated: new ActivatedTabEventTarget(), |
365 onRemoved: new RemovedTabEventTarget() | 509 onRemoved: new RemovedTabEventTarget() |
366 }; | 510 }; |
367 | 511 |
368 ext.webRequest = { | 512 ext.webRequest = { |
369 onBeforeRequest: new BeforeRequestEventTarget(), | 513 onBeforeRequest: new SimpleEventTarget(), |
370 handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged | 514 handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged |
371 }; | 515 }; |
372 | 516 |
373 var contextMenu = []; | 517 var contextMenu = []; |
374 ext.contextMenus = { | 518 ext.contextMenus = { |
375 addMenuItem: function(title, contexts, onclick) | 519 addMenuItem: function(title, contexts, onclick) |
376 { | 520 { |
377 contextMenu.push({ | 521 contextMenu.push({ |
378 title: title, | 522 title: title, |
379 contexts: contexts, | 523 contexts: contexts, |
(...skipping 20 matching lines...) Expand all Loading... |
400 onclick: item.onclick | 544 onclick: item.onclick |
401 }); | 545 }); |
402 } | 546 } |
403 }); | 547 }); |
404 }, | 548 }, |
405 hideMenu: function() | 549 hideMenu: function() |
406 { | 550 { |
407 chrome.contextMenus.removeAll(); | 551 chrome.contextMenus.removeAll(); |
408 } | 552 } |
409 }; | 553 }; |
| 554 |
| 555 ext.onMessage = new BackgroundMessageEventTarget(); |
410 })(); | 556 })(); |
OLD | NEW |