Left: | ||
Right: |
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 indistinguishableTypes: [["OTHER", "FONT"]] | 361 indistinguishableTypes: [["OTHER", "FONT"]] |
300 }; | 362 }; |
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
518 { | 580 { |
519 case "loading": | 581 case "loading": |
520 var tab = event.target; | 582 var tab = event.target; |
521 var message = event.message; | 583 var message = event.message; |
522 | 584 |
523 var pageId; | 585 var pageId; |
524 var frameId; | 586 var frameId; |
525 | 587 |
526 if (message.isTopLevel) | 588 if (message.isTopLevel) |
527 { | 589 { |
528 pageId = ++pageCounter; | 590 pageId = addPage(tab, message.url, message.isPrerendered); |
529 frameId = 0; | 591 frameId = 0; |
530 | 592 |
531 if (!('_pages' in tab)) | 593 ext.pages.onLoading._dispatch(pages[pageId]); |
532 tab._pages = Object.create(null); | |
533 | |
534 var page = new Page(pageId, tab, message.url); | |
535 pages[pageId] = tab._pages[pageId] = page; | |
536 | |
537 // when a new page is shown, forget the previous page associated | |
538 // with its tab, and reset the toolbar item if necessary. | |
539 // Note that it wouldn't be sufficient to do that when the old | |
540 // page is unloading, because Safari dispatches window.onunload | |
541 // only when reloading the page or following links, but not when | |
542 // you enter a new URL in the address bar. | |
543 if (!message.isPrerendered) | |
544 replacePage(page); | |
545 | |
546 ext.pages.onLoading._dispatch(page); | |
547 } | 594 } |
548 else | 595 else |
549 { | 596 { |
550 var page; | 597 var page; |
551 var parentFrame; | 598 var parentFrame; |
552 | 599 |
553 var lastPageId; | 600 var lastPageId; |
554 var lastPage; | 601 var lastPage; |
555 var lastPageTopLevelFrame; | 602 var lastPageTopLevelFrame; |
556 | 603 |
(...skipping 28 matching lines...) Expand all Loading... | |
585 // 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 |
586 // 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 |
587 if (!page) | 634 if (!page) |
588 { | 635 { |
589 pageId = lastPageId; | 636 pageId = lastPageId; |
590 page = lastPage; | 637 page = lastPage; |
591 parentFrame = lastPageTopLevelFrame; | 638 parentFrame = lastPageTopLevelFrame; |
592 } | 639 } |
593 | 640 |
594 frameId = page._frames.length; | 641 frameId = page._frames.length; |
595 page._frames.push({url: new URL(message.url), parent: parentFrame} ); | 642 page._frames.push(new Frame(message.url, parentFrame)); |
596 } | 643 } |
597 | |
598 event.message = {pageId: pageId, frameId: frameId}; | 644 event.message = {pageId: pageId, frameId: frameId}; |
599 break; | 645 break; |
600 case "webRequest": | 646 case "webRequest": |
601 var page = pages[event.message.pageId]; | 647 var page = pages[event.message.pageId]; |
602 var frame = page._frames[event.message.frameId]; | 648 var frame = page._frames[event.message.frameId]; |
603 | 649 |
604 var results = ext.webRequest.onBeforeRequest._dispatch( | 650 var results = ext.webRequest.onBeforeRequest._dispatch( |
605 new URL(event.message.url, frame.url), | 651 new URL(event.message.url, frame.url), |
606 event.message.type, page, frame | 652 event.message.type, page, frame |
607 ); | 653 ); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
653 var items = {}; | 699 var items = {}; |
654 var settings = safari.extension.settings; | 700 var settings = safari.extension.settings; |
655 | 701 |
656 for (var i = 0; i < keys.length; i++) | 702 for (var i = 0; i < keys.length; i++) |
657 { | 703 { |
658 var key = keys[i]; | 704 var key = keys[i]; |
659 if (key in settings) | 705 if (key in settings) |
660 items[key] = settings[key]; | 706 items[key] = settings[key]; |
661 } | 707 } |
662 | 708 |
663 setTimeout(callback, 0, items); | 709 setTimeout(callback, 0, items); |
Wladimir Palant
2015/03/12 23:05:37
Did you verify that this works on all Safari versi
Sebastian Noack
2015/03/12 23:35:29
I saw this one coming. I checked MDN and made the
| |
664 }, | 710 }, |
665 set: function(key, value, callback) | 711 set: function(key, value, callback) |
666 { | 712 { |
667 safari.extension.settings[key] = value; | 713 safari.extension.settings[key] = value; |
668 | 714 |
669 if (callback) | 715 if (callback) |
670 setTimeout(callback, 0); | 716 setTimeout(callback, 0); |
671 }, | 717 }, |
672 remove: function(key, callback) | 718 remove: function(key, callback) |
673 { | 719 { |
674 delete safari.extension.settings[key]; | 720 delete safari.extension.settings[key]; |
675 | 721 |
676 if (callback) | 722 if (callback) |
677 setTimeout(callback, 0); | 723 setTimeout(callback, 0); |
678 }, | 724 }, |
679 onChanged: new ext._EventTarget(), | 725 onChanged: new ext._EventTarget(), |
680 | 726 |
681 // Preferences were previously encoded as JSON for compatibility | 727 // Preferences were previously encoded as JSON for compatibility |
682 // with localStorage, which has been used on Chrome. | 728 // with localStorage, which has been used on Chrome. |
683 migratePrefs: function(prefs) | 729 migratePrefs: function(hooks) |
684 { | 730 { |
685 var settings = safari.extension.settings; | 731 var settings = safari.extension.settings; |
686 | 732 |
687 for (var key in settings) | 733 for (var key in settings) |
688 { | 734 { |
689 if (key in prefs) | 735 var item = hooks.map(key, settings[key]); |
736 | |
737 if (item) | |
690 { | 738 { |
691 try | |
692 { | |
693 settings["pref:" + key] = JSON.parse(settings[key]); | |
694 } | |
695 catch (e) | |
696 { | |
697 } | |
698 | |
699 delete settings[key]; | 739 delete settings[key]; |
740 settings[item.key] = item.value; | |
700 } | 741 } |
701 } | 742 } |
743 | |
744 hooks.done(); | |
702 } | 745 } |
703 }; | 746 }; |
704 | 747 |
705 safari.extension.settings.addEventListener("change", function(event) | 748 safari.extension.settings.addEventListener("change", function(event) |
706 { | 749 { |
707 var changes = {}; | 750 var changes = {}; |
708 var change = changes[event.key] = {}; | 751 var change = changes[event.key] = {}; |
709 | 752 |
710 if (event.oldValue != null) | 753 if (event.oldValue != null) |
711 change.oldValue = event.oldValue; | 754 change.oldValue = event.oldValue; |
(...skipping 20 matching lines...) Expand all Loading... | |
732 tab.activate(); | 775 tab.activate(); |
733 if (callback) | 776 if (callback) |
734 callback(page); | 777 callback(page); |
735 return; | 778 return; |
736 } | 779 } |
737 } | 780 } |
738 | 781 |
739 ext.pages.open(optionsUrl, callback); | 782 ext.pages.open(optionsUrl, callback); |
740 }; | 783 }; |
741 })(); | 784 })(); |
LEFT | RIGHT |