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