LEFT | RIGHT |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 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 /* Context menus */ |
| 21 |
| 22 var contextMenuItems = new ext.PageMap(); |
| 23 |
| 24 var ContextMenus = function(page) |
| 25 { |
| 26 this._page = page; |
| 27 }; |
| 28 ContextMenus.prototype = { |
| 29 create: function(item) |
| 30 { |
| 31 var items = contextMenuItems.get(this._page); |
| 32 if (!items) |
| 33 contextMenuItems.set(this._page, items = []); |
| 34 |
| 35 items.push(item); |
| 36 }, |
| 37 removeAll: function() |
| 38 { |
| 39 contextMenuItems.delete(this._page); |
| 40 } |
| 41 }; |
| 42 |
| 43 safari.application.addEventListener("contextmenu", function(event) |
| 44 { |
| 45 if (!event.userInfo) |
| 46 return; |
| 47 |
| 48 var pageId = event.userInfo.pageId; |
| 49 if (!pageId) |
| 50 return; |
| 51 |
| 52 var page = pages[event.userInfo.pageId]; |
| 53 var items = contextMenuItems.get(page); |
| 54 if (!items) |
| 55 return; |
| 56 |
| 57 var context = event.userInfo.tagName; |
| 58 if (context == "img") |
| 59 context = "image"; |
| 60 |
| 61 for (var i = 0; i < items.length; i++) |
| 62 { |
| 63 // Supported contexts are: all, audio, image, video |
| 64 var menuItem = items[i]; |
| 65 if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(co
ntext) == -1) |
| 66 continue; |
| 67 |
| 68 event.contextMenu.appendContextMenuItem(i, menuItem.title); |
| 69 } |
| 70 }); |
| 71 |
| 72 safari.application.addEventListener("command", function(event) |
| 73 { |
| 74 var page = pages[event.userInfo.pageId]; |
| 75 var items = contextMenuItems.get(page); |
| 76 |
| 77 items[event.command].onclick(page); |
| 78 }); |
| 79 |
| 80 |
| 81 /* Browser actions */ |
| 82 |
| 83 var toolbarItemProperties = {}; |
| 84 |
| 85 var getToolbarItemForWindow = function(win) |
| 86 { |
| 87 for (var i = 0; i < safari.extension.toolbarItems.length; i++) |
| 88 { |
| 89 var toolbarItem = safari.extension.toolbarItems[i]; |
| 90 |
| 91 if (toolbarItem.browserWindow == win) |
| 92 return toolbarItem; |
| 93 } |
| 94 |
| 95 return null; |
| 96 }; |
| 97 |
| 98 var updateToolbarItemForPage = function(page, win) { |
| 99 var toolbarItem = getToolbarItemForWindow(win); |
| 100 if (!toolbarItem) |
| 101 return; |
| 102 |
| 103 for (var name in toolbarItemProperties) |
| 104 { |
| 105 var property = toolbarItemProperties[name]; |
| 106 |
| 107 if (page && property.pages.has(page)) |
| 108 toolbarItem[name] = property.pages.get(page); |
| 109 else |
| 110 toolbarItem[name] = property.global; |
| 111 } |
| 112 }; |
| 113 |
| 114 var BrowserAction = function(page) |
| 115 { |
| 116 this._page = page; |
| 117 }; |
| 118 BrowserAction.prototype = { |
| 119 _set: function(name, value) |
| 120 { |
| 121 var toolbarItem = getToolbarItemForWindow(this._page._tab.browserWindow); |
| 122 if (!toolbarItem) |
| 123 return; |
| 124 |
| 125 var property = toolbarItemProperties[name]; |
| 126 if (!property) |
| 127 property = toolbarItemProperties[name] = { |
| 128 pages: new ext.PageMap(), |
| 129 global: toolbarItem[name] |
| 130 }; |
| 131 |
| 132 property.pages.set(this._page, value); |
| 133 |
| 134 if (isPageActive(this._page)) |
| 135 toolbarItem[name] = value; |
| 136 }, |
| 137 setIcon: function(path) |
| 138 { |
| 139 this._set("image", safari.extension.baseURI + path.replace("$size", "16"))
; |
| 140 }, |
| 141 setBadge: function(badge) |
| 142 { |
| 143 if (!badge) |
| 144 this._set("badge", 0); |
| 145 else if ("number" in badge) |
| 146 this._set("badge", badge.number); |
| 147 } |
| 148 }; |
| 149 |
| 150 safari.application.addEventListener("activate", function(event) |
| 151 { |
| 152 // this event is also dispatched on windows that got focused. But we |
| 153 // are only interested in tabs, which became active in their window. |
| 154 if (!(event.target instanceof SafariBrowserTab)) |
| 155 return; |
| 156 |
| 157 // update the toolbar item for the page visible in the tab that just |
| 158 // became active. If we can't find that page (e.g. when a page was |
| 159 // opened in a new tab, and our content script didn't run yet), the |
| 160 // toolbar item of the window, is reset to its intial configuration. |
| 161 updateToolbarItemForPage(event.target._visiblePage, event.target.browserWind
ow); |
| 162 }, true); |
| 163 |
| 164 |
20 /* Pages */ | 165 /* Pages */ |
21 | 166 |
22 var pages = Object.create(null); | 167 var pages = Object.create(null); |
23 var pageCounter = 0; | 168 var pageCounter = 0; |
24 | 169 |
| 170 var Frame = function(url, parent) |
| 171 { |
| 172 this._urlString = url; |
| 173 this._urlObj = null; |
| 174 |
| 175 this.parent = parent; |
| 176 } |
| 177 Frame.prototype = { |
| 178 get url() |
| 179 { |
| 180 // On Safari 6 and before, the URL constuctor doesn't exist. |
| 181 // The "urls" module provides a polifill, but it might not |
| 182 // be loaded yet. So we have to lazily parse URLs. |
| 183 if (!this._urlObj) |
| 184 { |
| 185 this._urlObj = new URL(this._urlString); |
| 186 this._urlString = null; |
| 187 } |
| 188 |
| 189 return this._urlObj; |
| 190 } |
| 191 }; |
| 192 |
25 var Page = function(id, tab, url) | 193 var Page = function(id, tab, url) |
26 { | 194 { |
27 this._id = id; | 195 this._id = id; |
28 this._tab = tab; | 196 this._tab = tab; |
29 this._frames = [{url: new URL(url), parent: null}]; | 197 this._frames = [new Frame(url, null)]; |
30 | 198 |
31 if (tab.page) | 199 if (tab.page) |
32 this._messageProxy = new ext._MessageProxy(tab.page); | 200 this._messageProxy = new ext._MessageProxy(tab.page); |
33 else | 201 else |
34 // while the new tab page is shown on Safari 7, the 'page' property | 202 // 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 | 203 // of the tab is undefined, and we can't send messages to that page |
36 this._messageProxy = { | 204 this._messageProxy = { |
37 handleRequest: function() {}, | 205 handleRequest: function() {}, |
38 handleResponse: function() {}, | 206 handleResponse: function() {}, |
39 sendMessage: function() {} | 207 sendMessage: function() {} |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 for (var id in tab._pages) | 249 for (var id in tab._pages) |
82 { | 250 { |
83 if (id != page._id) | 251 if (id != page._id) |
84 forgetPage(id); | 252 forgetPage(id); |
85 } | 253 } |
86 | 254 |
87 if (isPageActive(page)) | 255 if (isPageActive(page)) |
88 updateToolbarItemForPage(page, tab.browserWindow); | 256 updateToolbarItemForPage(page, tab.browserWindow); |
89 }; | 257 }; |
90 | 258 |
| 259 var addPage = function(tab, url, prerendered) |
| 260 { |
| 261 var pageId = ++pageCounter; |
| 262 |
| 263 if (!('_pages' in tab)) |
| 264 tab._pages = Object.create(null); |
| 265 |
| 266 var page = new Page(pageId, tab, url); |
| 267 pages[pageId] = tab._pages[pageId] = page; |
| 268 |
| 269 // When a new page is shown, forget the previous page associated |
| 270 // with its tab, and reset the toolbar item if necessary. |
| 271 // Note that it wouldn't be sufficient to do that when the old |
| 272 // page is unloading, because Safari dispatches window.onunload |
| 273 // only when reloading the page or following links, but not when |
| 274 // you enter a new URL in the address bar. |
| 275 if (!prerendered) |
| 276 replacePage(page); |
| 277 |
| 278 return pageId; |
| 279 }; |
| 280 |
91 ext.pages = { | 281 ext.pages = { |
92 open: function(url, callback) | 282 open: function(url, callback) |
93 { | 283 { |
94 var tab = safari.application.activeBrowserWindow.openTab(); | 284 var tab = safari.application.activeBrowserWindow.openTab(); |
95 tab.url = url; | 285 tab.url = url; |
96 | 286 |
97 if (callback) | 287 if (callback) |
98 { | 288 { |
99 var onLoading = function(page) | 289 var onLoading = function(page) |
100 { | 290 { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
138 return; | 328 return; |
139 | 329 |
140 // when a tab is closed, forget the previous page associated with that | 330 // when a tab is closed, forget the previous page associated with that |
141 // tab. Note that it wouldn't be sufficient do that when the old page | 331 // tab. Note that it wouldn't be sufficient do that when the old page |
142 // is unloading, because Safari dispatches window.onunload only when | 332 // is unloading, because Safari dispatches window.onunload only when |
143 // reloading the page or following links, but not when closing the tab. | 333 // reloading the page or following links, but not when closing the tab. |
144 for (var id in event.target._pages) | 334 for (var id in event.target._pages) |
145 forgetPage(id); | 335 forgetPage(id); |
146 }, true); | 336 }, true); |
147 | 337 |
148 | 338 // We generally rely on content scripts to report new pages, |
149 /* Browser actions */ | 339 // since Safari's extension API doesn't consider pre-rendered |
150 | 340 // pages. However, when the extension initializes we have to |
151 var toolbarItemProperties = {}; | 341 // use Safari's extension API to detect existing tabs. |
152 | 342 safari.application.browserWindows.forEach(function(win) |
153 var getToolbarItemForWindow = function(win) | 343 { |
154 { | 344 for (var i = 0; i < win.tabs.length; i++) |
155 for (var i = 0; i < safari.extension.toolbarItems.length; i++) | 345 { |
156 { | 346 var tab = win.tabs[i]; |
157 var toolbarItem = safari.extension.toolbarItems[i]; | 347 var url = tab.url; |
158 | 348 |
159 if (toolbarItem.browserWindow == win) | 349 // For the new tab page the url property is undefined. |
160 return toolbarItem; | 350 if (url) |
161 } | 351 addPage(tab, url, false); |
162 | 352 } |
163 return null; | |
164 }; | |
165 | |
166 var updateToolbarItemForPage = function(page, win) { | |
167 var toolbarItem = getToolbarItemForWindow(win); | |
168 if (!toolbarItem) | |
169 return; | |
170 | |
171 for (var name in toolbarItemProperties) | |
172 { | |
173 var property = toolbarItemProperties[name]; | |
174 | |
175 if (page && property.pages.has(page)) | |
176 toolbarItem[name] = property.pages.get(page); | |
177 else | |
178 toolbarItem[name] = property.global; | |
179 } | |
180 }; | |
181 | |
182 var BrowserAction = function(page) | |
183 { | |
184 this._page = page; | |
185 }; | |
186 BrowserAction.prototype = { | |
187 _set: function(name, value) | |
188 { | |
189 var toolbarItem = getToolbarItemForWindow(this._page._tab.browserWindow); | |
190 if (!toolbarItem) | |
191 return; | |
192 | |
193 var property = toolbarItemProperties[name]; | |
194 if (!property) | |
195 property = toolbarItemProperties[name] = { | |
196 pages: new ext.PageMap(), | |
197 global: toolbarItem[name] | |
198 }; | |
199 | |
200 property.pages.set(this._page, value); | |
201 | |
202 if (isPageActive(this._page)) | |
203 toolbarItem[name] = value; | |
204 }, | |
205 setIcon: function(path) | |
206 { | |
207 this._set("image", safari.extension.baseURI + path.replace("$size", "16"))
; | |
208 }, | |
209 setBadge: function(badge) | |
210 { | |
211 if (!badge) | |
212 this._set("badge", 0); | |
213 else if ("number" in badge) | |
214 this._set("badge", badge.number); | |
215 } | |
216 }; | |
217 | |
218 safari.application.addEventListener("activate", function(event) | |
219 { | |
220 // this event is also dispatched on windows that got focused. But we | |
221 // are only interested in tabs, which became active in their window. | |
222 if (!(event.target instanceof SafariBrowserTab)) | |
223 return; | |
224 | |
225 // update the toolbar item for the page visible in the tab that just | |
226 // became active. If we can't find that page (e.g. when a page was | |
227 // opened in a new tab, and our content script didn't run yet), the | |
228 // toolbar item of the window, is reset to its intial configuration. | |
229 updateToolbarItemForPage(event.target._visiblePage, event.target.browserWind
ow); | |
230 }, true); | |
231 | |
232 | |
233 /* Context menus */ | |
234 | |
235 var contextMenuItems = new ext.PageMap(); | |
236 | |
237 var ContextMenus = function(page) | |
238 { | |
239 this._page = page; | |
240 }; | |
241 ContextMenus.prototype = { | |
242 create: function(item) | |
243 { | |
244 var items = contextMenuItems.get(this._page); | |
245 if (!items) | |
246 contextMenuItems.set(this._page, items = []); | |
247 | |
248 items.push(item); | |
249 }, | |
250 removeAll: function() | |
251 { | |
252 contextMenuItems.delete(this._page); | |
253 } | |
254 }; | |
255 | |
256 safari.application.addEventListener("contextmenu", function(event) | |
257 { | |
258 if (!event.userInfo) | |
259 return; | |
260 | |
261 var pageId = event.userInfo.pageId; | |
262 if (!pageId) | |
263 return; | |
264 | |
265 var page = pages[event.userInfo.pageId]; | |
266 var items = contextMenuItems.get(page); | |
267 if (!items) | |
268 return; | |
269 | |
270 var context = event.userInfo.tagName; | |
271 if (context == "img") | |
272 context = "image"; | |
273 | |
274 for (var i = 0; i < items.length; i++) | |
275 { | |
276 // Supported contexts are: all, audio, image, video | |
277 var menuItem = items[i]; | |
278 if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(co
ntext) == -1) | |
279 continue; | |
280 | |
281 event.contextMenu.appendContextMenuItem(i, menuItem.title); | |
282 } | |
283 }); | |
284 | |
285 safari.application.addEventListener("command", function(event) | |
286 { | |
287 var page = pages[event.userInfo.pageId]; | |
288 var items = contextMenuItems.get(page); | |
289 | |
290 items[event.command].onclick(page); | |
291 }); | 353 }); |
292 | 354 |
293 | 355 |
294 /* Web requests */ | 356 /* Web requests */ |
295 | 357 |
296 ext.webRequest = { | 358 ext.webRequest = { |
297 onBeforeRequest: new ext._EventTarget(), | 359 onBeforeRequest: new ext._EventTarget(), |
298 handlerBehaviorChanged: function() {} | 360 handlerBehaviorChanged: function() {}, |
299 }; | 361 indistinguishableTypes: [["OTHER", "FONT"]] |
300 | |
301 | |
302 /* Background page */ | |
303 | |
304 ext.backgroundPage = { | |
305 getWindow: function() | |
306 { | |
307 return window; | |
308 } | |
309 }; | 362 }; |
310 | 363 |
311 | 364 |
312 /* Background page proxy (for access from content scripts) */ | 365 /* Background page proxy (for access from content scripts) */ |
313 | 366 |
314 var backgroundPageProxy = { | 367 var backgroundPageProxy = { |
315 cache: new ext.PageMap(), | 368 cache: new ext.PageMap(), |
316 | 369 |
317 registerObject: function(obj, objects) | 370 registerObject: function(obj, objects) |
318 { | 371 { |
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
527 { | 580 { |
528 case "loading": | 581 case "loading": |
529 var tab = event.target; | 582 var tab = event.target; |
530 var message = event.message; | 583 var message = event.message; |
531 | 584 |
532 var pageId; | 585 var pageId; |
533 var frameId; | 586 var frameId; |
534 | 587 |
535 if (message.isTopLevel) | 588 if (message.isTopLevel) |
536 { | 589 { |
537 pageId = ++pageCounter; | 590 pageId = addPage(tab, message.url, message.isPrerendered); |
538 frameId = 0; | 591 frameId = 0; |
539 | 592 |
540 if (!('_pages' in tab)) | 593 ext.pages.onLoading._dispatch(pages[pageId]); |
541 tab._pages = Object.create(null); | |
542 | |
543 var page = new Page(pageId, tab, message.url); | |
544 pages[pageId] = tab._pages[pageId] = page; | |
545 | |
546 // when a new page is shown, forget the previous page associated | |
547 // with its tab, and reset the toolbar item if necessary. | |
548 // Note that it wouldn't be sufficient to do that when the old | |
549 // page is unloading, because Safari dispatches window.onunload | |
550 // only when reloading the page or following links, but not when | |
551 // you enter a new URL in the address bar. | |
552 if (!message.isPrerendered) | |
553 replacePage(page); | |
554 | |
555 ext.pages.onLoading._dispatch(page); | |
556 } | 594 } |
557 else | 595 else |
558 { | 596 { |
559 var page; | 597 var page; |
560 var parentFrame; | 598 var parentFrame; |
561 | 599 |
562 var lastPageId; | 600 var lastPageId; |
563 var lastPage; | 601 var lastPage; |
564 var lastPageTopLevelFrame; | 602 var lastPageTopLevelFrame; |
565 | 603 |
(...skipping 28 matching lines...) Expand all Loading... |
594 // if we can't find the parent frame and its page, fall back to | 632 // if we can't find the parent frame and its page, fall back to |
595 // the page most recently loaded in the tab and its top level fram
e | 633 // the page most recently loaded in the tab and its top level fram
e |
596 if (!page) | 634 if (!page) |
597 { | 635 { |
598 pageId = lastPageId; | 636 pageId = lastPageId; |
599 page = lastPage; | 637 page = lastPage; |
600 parentFrame = lastPageTopLevelFrame; | 638 parentFrame = lastPageTopLevelFrame; |
601 } | 639 } |
602 | 640 |
603 frameId = page._frames.length; | 641 frameId = page._frames.length; |
604 page._frames.push({url: new URL(message.url), parent: parentFrame}
); | 642 page._frames.push(new Frame(message.url, parentFrame)); |
605 } | 643 } |
606 | |
607 event.message = {pageId: pageId, frameId: frameId}; | 644 event.message = {pageId: pageId, frameId: frameId}; |
608 break; | 645 break; |
609 case "webRequest": | 646 case "webRequest": |
610 var page = pages[event.message.pageId]; | 647 var page = pages[event.message.pageId]; |
611 var frame = page._frames[event.message.frameId]; | 648 var frame = page._frames[event.message.frameId]; |
612 | 649 |
613 var results = ext.webRequest.onBeforeRequest._dispatch( | 650 var results = ext.webRequest.onBeforeRequest._dispatch( |
614 new URL(event.message.url, frame.url), | 651 new URL(event.message.url, frame.url), |
615 event.message.type, page, frame | 652 event.message.type, page, frame |
616 ); | 653 ); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
682 { | 719 { |
683 delete safari.extension.settings[key]; | 720 delete safari.extension.settings[key]; |
684 | 721 |
685 if (callback) | 722 if (callback) |
686 setTimeout(callback, 0); | 723 setTimeout(callback, 0); |
687 }, | 724 }, |
688 onChanged: new ext._EventTarget(), | 725 onChanged: new ext._EventTarget(), |
689 | 726 |
690 // Preferences were previously encoded as JSON for compatibility | 727 // Preferences were previously encoded as JSON for compatibility |
691 // with localStorage, which has been used on Chrome. | 728 // with localStorage, which has been used on Chrome. |
692 migratePrefs: function(prefs) | 729 migratePrefs: function(hooks) |
693 { | 730 { |
694 var settings = safari.extension.settings; | 731 var settings = safari.extension.settings; |
695 | 732 |
696 for (var key in settings) | 733 for (var key in settings) |
697 { | 734 { |
698 if (key in prefs) | 735 var item = hooks.map(key, settings[key]); |
| 736 |
| 737 if (item) |
699 { | 738 { |
700 try | |
701 { | |
702 settings["pref:" + key] = JSON.parse(settings[key]); | |
703 } | |
704 catch (e) | |
705 { | |
706 } | |
707 | |
708 delete settings[key]; | 739 delete settings[key]; |
| 740 settings[item.key] = item.value; |
709 } | 741 } |
710 } | 742 } |
| 743 |
| 744 hooks.done(); |
711 }, | 745 }, |
712 | 746 |
713 // While moving away from the FileSystem API on Chrome the data structure | 747 // While moving away from the FileSystem API on Chrome the data structure |
714 // for files on Safari changed as well, in order to keep thing consistent. | 748 // for files on Safari changed as well, in order to keep thing consistent. |
715 migrateFiles: function(callback) | 749 migrateFiles: function(callback) |
716 { | 750 { |
717 var settings = safari.extension.settings; | 751 var settings = safari.extension.settings; |
718 | 752 |
719 for (var key in settings) | 753 for (var key in settings) |
720 { | 754 { |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
772 tab.activate(); | 806 tab.activate(); |
773 if (callback) | 807 if (callback) |
774 callback(page); | 808 callback(page); |
775 return; | 809 return; |
776 } | 810 } |
777 } | 811 } |
778 | 812 |
779 ext.pages.open(optionsUrl, callback); | 813 ext.pages.open(optionsUrl, callback); |
780 }; | 814 }; |
781 })(); | 815 })(); |
LEFT | RIGHT |