| Index: safari/ext/background.js |
| =================================================================== |
| --- a/safari/ext/background.js |
| +++ b/safari/ext/background.js |
| @@ -17,85 +17,35 @@ |
| (function() |
| { |
| - /* Events */ |
| + /* Pages */ |
| - var TabEventTarget = function() |
| + var pages = {__proto__: null}; |
| + var pageCounter = 0; |
| + |
| + var Page = function(id, tab, url, prerendered) |
| { |
| - WrappedEventTarget.apply(this, arguments); |
| - }; |
| - TabEventTarget.prototype = { |
| - __proto__: WrappedEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function(event) |
| - { |
| - if (event.target instanceof SafariBrowserTab) |
| - listener(new Tab(event.target)); |
| + this._id = id; |
| + this._tab = tab; |
| + this._frames = [{url: url, parent: null}]; |
| + this._prerendered = prerendered; |
| + |
| + if (tab.page) |
| + this._messageProxy = new ext._MessageProxy(tab.page); |
| + else |
| + // while the new tab page is shown on Safari 7, the 'page' property |
| + // of the tab is undefined, and we can't send messages to that page |
| + this._messageProxy = { |
| + handleRequest: function() {}, |
| + handleResponse: function() {}, |
| + sendMessage: function() {} |
| }; |
| - } |
| - }; |
| - |
| - var LoadingTabEventTarget = function(target) |
| - { |
| - WrappedEventTarget.call(this, target, "message", false); |
| - }; |
| - LoadingTabEventTarget.prototype = { |
| - __proto__: WrappedEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function (event) |
| - { |
| - if (event.name == "loading") |
| - listener(new Tab(event.target)); |
| - }; |
| - } |
| - }; |
| - |
| - var BackgroundMessageEventTarget = function() |
| - { |
| - MessageEventTarget.call(this, safari.application); |
| - }; |
| - BackgroundMessageEventTarget.prototype = { |
| - __proto__: MessageEventTarget.prototype, |
| - _getResponseDispatcher: function(event) |
| - { |
| - return event.target.page; |
| - }, |
| - _getSenderDetails: function(event) |
| - { |
| - return { |
| - tab: new Tab(event.target), |
| - frame: new Frame( |
| - event.message.documentUrl, |
| - event.message.isTopLevel, |
| - event.target |
| - ) |
| - }; |
| - } |
| - }; |
| - |
| - |
| - /* Tabs */ |
| - |
| - Tab = function(tab) |
| - { |
| - this._tab = tab; |
| this.browserAction = new BrowserAction(this); |
| - |
| - this.onLoading = new LoadingTabEventTarget(tab); |
| - this.onCompleted = new TabEventTarget(tab, "navigate", false); |
| - this.onActivated = new TabEventTarget(tab, "activate", false); |
| - this.onRemoved = new TabEventTarget(tab, "close", false); |
| }; |
| - Tab.prototype = { |
| + Page.prototype = { |
| get url() |
| { |
| - return this._tab.url; |
| - }, |
| - close: function() |
| - { |
| - this._tab.close(); |
| + return this._frames[0].url; |
| }, |
| activate: function() |
| { |
| @@ -103,106 +53,98 @@ |
| }, |
| sendMessage: function(message, responseCallback) |
| { |
| - _sendMessage( |
| - message, responseCallback, |
| - this._tab.page, this._tab |
| - ); |
| + this._messageProxy.sendMessage(message, responseCallback, {pageId: this._id}); |
| } |
| }; |
| - TabMap = function(deleteOnPageUnload) |
| + var isPageActive = function(page) |
| { |
| - this._data = []; |
| - this._deleteOnPageUnload = deleteOnPageUnload; |
| + return page._tab == page._tab.browserWindow.activeTab && !page._prerendered; |
| + }; |
| - this.delete = this.delete.bind(this); |
| - this._delete = this._delete.bind(this); |
| + var forgetPage = function(id) |
| + { |
| + ext._removeFromAllPageMaps(id); |
| + delete pages[id]; |
| }; |
| - TabMap.prototype = |
| + |
| + var replacePage = function(page) |
| { |
| - _indexOf: function(tab) |
| + for (var id in pages) |
| { |
| - for (var i = 0; i < this._data.length; i++) |
| - if (this._data[i].tab._tab == tab._tab) |
| - return i; |
| + if (id != page._id && pages[id]._tab == page._tab) |
| + forgetPage(id); |
| + } |
| - return -1; |
| - }, |
| - _delete: function(tab) |
| + if (isPageActive(page)) |
| + updateToolbarItemForPage(page); |
| + }; |
| + |
| + ext.pages = { |
| + open: function(url, callback) |
| { |
| - // delay so that other onClosed listeners can still look this tab up |
| - setTimeout(this.delete.bind(this, tab), 0); |
| - }, |
| - get: function(tab) { |
| - var idx; |
| + var tab = safari.application.activeBrowserWindow.openTab(); |
| + tab.url = url; |
| - if (!tab || (idx = this._indexOf(tab)) == -1) |
| - return null; |
| - |
| - return this._data[idx].value; |
| - }, |
| - set: function(tab, value) |
| - { |
| - var idx = this._indexOf(tab); |
| - |
| - if (idx != -1) |
| - this._data[idx].value = value; |
| - else |
| + if (callback) |
| { |
| - this._data.push({value: value, tab: tab}); |
| - |
| - tab.onRemoved.addListener(this._delete); |
| - if (this._deleteOnPageUnload) |
| - tab.onLoading.addListener(this.delete); |
| + var onLoading = function(page) |
| + { |
| + if (page._tab == tab) |
| + { |
| + ext.pages.onLoading.removeListener(onLoading); |
| + callback(page); |
| + } |
| + }; |
| + ext.pages.onLoading.addListener(onLoading); |
| } |
| }, |
| - has: function(tab) |
| + query: function(info, callback) |
| { |
| - return this._indexOf(tab) != -1; |
| + var matchedPages = []; |
| + |
| + for (var id in pages) |
| + { |
| + var page = pages[id]; |
| + var win = page._tab.browserWindow; |
| + |
| + if ("active" in info && info.active != isPageActive(page)) |
| + continue; |
| + if ("lastFocusedWindow" in info && info.lastFocusedWindow != (win == safari.application.activeBrowserWindow)) |
| + continue; |
| + |
| + matchedPages.push(page); |
| + }; |
| + |
| + callback(matchedPages); |
| }, |
| - clear: function() |
| - { |
| - while (this._data.length > 0) |
| - this.delete(this._data[0].tab); |
| - }, |
| - delete: function(tab) |
| - { |
| - var idx = this._indexOf(tab); |
| - |
| - if (idx != -1) |
| - { |
| - tab = this._data[idx].tab; |
| - this._data.splice(idx, 1); |
| - |
| - tab.onRemoved.removeListener(this._delete); |
| - tab.onLoading.removeListener(this.delete); |
| - } |
| - } |
| + onLoading: new ext._EventTarget() |
| }; |
| - ext.tabs = { |
| - onLoading: new LoadingTabEventTarget(safari.application), |
| - onCompleted: new TabEventTarget(safari.application, "navigate", true), |
| - onActivated: new TabEventTarget(safari.application, "activate", true), |
| - onRemoved: new TabEventTarget(safari.application, "close", true) |
| - }; |
| + safari.application.addEventListener("close", function(event) |
| + { |
| + // this event is dispatched on closing windows and tabs. However when a |
| + // window is closed, it is first dispatched on each tab in the window and |
| + // then on the window itself. But we are only interested in closed tabs. |
| + if (!(event.target instanceof SafariBrowserTab)) |
| + return; |
| + |
| + // when a tab is closed, forget the previous page associated with that |
| + // tab. Note that it wouldn't be sufficient do that when the old page |
| + // is unloading, because Safari dispatches window.onunload only when |
| + // reloading the page or following links, but not when closing the tab. |
| + for (var id in pages) |
| + { |
| + if (pages[id]._tab == event.target) |
| + forgetPage(id); |
| + } |
| + }, true); |
| /* Browser actions */ |
| var toolbarItemProperties = {}; |
| - var getToolbarItemProperty = function(name) |
| - { |
| - var property = toolbarItemProperties[name]; |
| - if (!property) |
| - { |
| - property = {tabs: new TabMap()}; |
| - toolbarItemProperties[name] = property; |
| - } |
| - return property; |
| - }; |
| - |
| var getToolbarItemForWindow = function(win) |
| { |
| for (var i = 0; i < safari.extension.toolbarItems.length; i++) |
| @@ -216,27 +158,44 @@ |
| return null; |
| }; |
| - var BrowserAction = function(tab) |
| + var updateToolbarItemForPage = function(page, win) { |
| + var toolbarItem = getToolbarItemForWindow(win || page._tab.browserWindow); |
| + if (!toolbarItem) |
| + return; |
| + |
| + for (var name in toolbarItemProperties) |
| + { |
| + var property = toolbarItemProperties[name]; |
| + |
| + if (page && property.pages.has(page)) |
| + toolbarItem[name] = property.pages.get(page); |
| + else |
| + toolbarItem[name] = property.global; |
| + } |
| + }; |
| + |
| + var BrowserAction = function(page) |
| { |
| - this._tab = tab; |
| + this._page = page; |
| }; |
| BrowserAction.prototype = { |
| _set: function(name, value) |
| { |
| - var currentWindow = this._tab._tab.browserWindow; |
| - var toolbarItem = getToolbarItemForWindow(currentWindow); |
| + var toolbarItem = getToolbarItemForWindow(this._page._tab.browserWindow); |
| + if (!toolbarItem) |
| + return; |
| - if (toolbarItem) |
| - { |
| - var property = getToolbarItemProperty(name); |
| - property.tabs.set(this._tab, value); |
| + var property = toolbarItemProperties[name]; |
| + if (!property) |
| + property = toolbarItemProperties[name] = { |
| + pages: new ext.PageMap(), |
| + global: toolbarItem[name] |
| + }; |
| - if (!("global" in property)) |
| - property.global = toolbarItem[name]; |
| + property.pages.set(this._page, value); |
| - if (this._tab._tab == currentWindow.activeTab) |
| - toolbarItem[name] = value; |
| - } |
| + if (isPageActive(this._page)) |
| + toolbarItem[name] = value; |
| }, |
| setIcon: function(path) |
| { |
| @@ -251,96 +210,121 @@ |
| } |
| }; |
| - ext.tabs.onActivated.addListener(function(tab) |
| + safari.application.addEventListener("activate", function(event) |
| { |
| - var toolbarItem = getToolbarItemForWindow(tab._tab.browserWindow); |
| - |
| - if (!toolbarItem) |
| + // this event is also dispatched on windows that got focused. But we |
| + // are only interested in tabs, which became active in their window. |
| + if (!(event.target instanceof SafariBrowserTab)) |
| return; |
| - for (var name in toolbarItemProperties) |
| + // update the toolbar item for the page visible in the tab that just |
| + // became active. If we can't find that page (e.g. when a page was |
| + // opened in a new tab, and our content script didn't run yet), the |
| + // toolbar item of the window, is reset to its intial configuration. |
| + var activePage = null; |
| + for (var id in pages) |
| { |
| - var property = toolbarItemProperties[name]; |
| + var page = pages[id]; |
| + if (page._tab == event.target && !page._prerendered) |
| + { |
| + activePage = page; |
| + break; |
| + } |
| + } |
| - if (property.tabs.has(tab)) |
| - toolbarItem[name] = property.tabs.get(tab); |
| - else |
| - toolbarItem[name] = property.global; |
| + updateToolbarItemForPage(activePage, event.target.browserWindow); |
| + }, true); |
| + |
| + |
| + /* Web requests */ |
| + |
| + ext.webRequest = { |
| + onBeforeRequest: new ext._EventTarget(true), |
| + handlerBehaviorChanged: function() {} |
| + }; |
| + |
| + |
| + /* Context menus */ |
| + |
| + var contextMenuItems = []; |
| + var isContextMenuHidden = true; |
| + |
| + ext.contextMenus = { |
| + addMenuItem: function(title, contexts, onclick) |
| + { |
| + contextMenuItems.push({ |
| + id: String(contextMenuItems.length), |
| + title: title, |
| + item: null, |
| + contexts: contexts, |
| + onclick: onclick |
| + }); |
| + this.showMenuItems(); |
| + }, |
| + removeMenuItems: function() |
| + { |
| + contextMenuItems = []; |
| + this.hideMenuItems(); |
| + }, |
| + showMenuItems: function() |
| + { |
| + isContextMenuHidden = false; |
| + }, |
| + hideMenuItems: function() |
| + { |
| + isContextMenuHidden = true; |
| + } |
| + }; |
| + |
| + safari.application.addEventListener("contextmenu", function(event) |
| + { |
| + if (isContextMenuHidden) |
| + return; |
| + |
| + var context = event.userInfo.tagName; |
| + if (context == "img") |
| + context = "image"; |
| + if (!event.userInfo.srcUrl) |
| + context = null; |
| + |
| + for (var i = 0; i < contextMenuItems.length; i++) |
| + { |
| + // Supported contexts are: all, audio, image, video |
| + var menuItem = contextMenuItems[i]; |
| + if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(context) == -1) |
| + continue; |
| + |
| + event.contextMenu.appendContextMenuItem(menuItem.id, menuItem.title); |
| } |
| }); |
| - ext.tabs.onLoading.addListener(function(tab) |
| + safari.application.addEventListener("command", function(event) |
| { |
| - var currentWindow = tab._tab.browserWindow; |
| - |
| - var toolbarItem; |
| - if (tab._tab == currentWindow.activeTab) |
| - toolbarItem = getToolbarItemForWindow(currentWindow); |
| - else |
| - toolbarItem = null; |
| - |
| - for (var name in toolbarItemProperties) |
| + for (var i = 0; i < contextMenuItems.length; i++) |
| { |
| - var property = toolbarItemProperties[name]; |
| - property.tabs.delete(tab); |
| - |
| - if (toolbarItem) |
| - toolbarItem[name] = property.global; |
| + if (contextMenuItems[i].id == event.command) |
| + { |
| + contextMenuItems[i].onclick(event.userInfo.srcUrl, pages[event.userInfo.pageId]); |
| + break; |
| + } |
| } |
| }); |
| - /* Windows */ |
| + /* Background page */ |
| - Window = function(win) |
| - { |
| - this._win = win; |
| - } |
| - Window.prototype = { |
| - get visible() |
| + ext.backgroundPage = { |
| + getWindow: function() |
| { |
| - return this._win.visible; |
| - }, |
| - getAllTabs: function(callback) |
| - { |
| - callback(this._win.tabs.map(function(tab) { return new Tab(tab); })); |
| - }, |
| - getActiveTab: function(callback) |
| - { |
| - callback(new Tab(this._win.activeTab)); |
| - }, |
| - openTab: function(url, callback) |
| - { |
| - var tab = this._win.openTab(); |
| - tab.url = url; |
| - |
| - if (callback) |
| - callback(new Tab(tab)); |
| + return window; |
| } |
| }; |
| - /* Frames */ |
| + /* Background page proxy (for access from content scripts) */ |
| - Frame = function(url, isTopLevel, tab) |
| - { |
| - this.url = url; |
| - |
| - // there is no way to discover frames with Safari's API. |
| - // so if this isn't the top level frame, assume that the parent is. |
| - // this is the best we can do for Safari. :( |
| - if (!isTopLevel) |
| - this.parent = new Frame(tab.url, true); |
| - else |
| - this.parent = null; |
| - }; |
| - |
| - |
| - /* Background page proxy */ |
| - |
| - var proxy = { |
| - tabs: [], |
| - objects: [], |
| + var backgroundPageProxy = { |
| + cache: new ext.PageMap(), |
| registerObject: function(obj, objects) |
| { |
| @@ -389,27 +373,31 @@ |
| return {type: "value", value: obj}; |
| }, |
| - createCallback: function(callbackId, tab) |
| + createCallback: function(callbackId, pageId, frameId) |
| { |
| var proxy = this; |
| return function() |
| { |
| - var idx = proxy.tabs.indexOf(tab); |
| + var page = pages[pageId]; |
| + if (!page) |
| + return; |
| - if (idx != -1) { |
| - var objects = proxy.objects[idx]; |
| + var objects = proxy.cache.get(page); |
| + if (!objects) |
| + return; |
| - tab.page.dispatchMessage("proxyCallback", |
| - { |
| - callbackId: callbackId, |
| - contextId: proxy.registerObject(this, objects), |
| - args: proxy.serializeSequence(arguments, objects) |
| - }); |
| - } |
| + page._tab.page.dispatchMessage("proxyCallback", |
| + { |
| + pageId: pageId, |
| + frameId: frameId, |
| + callbackId: callbackId, |
| + contextId: proxy.registerObject(this, objects), |
| + args: proxy.serializeSequence(arguments, objects) |
| + }); |
| }; |
| }, |
| - deserialize: function(spec, objects, tab, memo) |
| + deserialize: function(spec, objects, pageId, memo) |
| { |
| switch (spec.type) |
| { |
| @@ -418,7 +406,7 @@ |
| case "hosted": |
| return objects[spec.objectId]; |
| case "callback": |
| - return this.createCallback(spec.callbackId, tab); |
| + return this.createCallback(spec.callbackId, pageId, spec.frameId); |
| case "object": |
| case "array": |
| if (!memo) |
| @@ -439,44 +427,22 @@ |
| if (spec.type == "array") |
| for (var i = 0; i < spec.items.length; i++) |
| - obj.push(this.deserialize(spec.items[i], objects, tab, memo)); |
| + obj.push(this.deserialize(spec.items[i], objects, pageId, memo)); |
| else |
| for (var k in spec.properties) |
| - obj[k] = this.deserialize(spec.properties[k], objects, tab, memo); |
| + obj[k] = this.deserialize(spec.properties[k], objects, pageId, memo); |
| return obj; |
| } |
| }, |
| - createObjectCache: function(tab) |
| + getObjectCache: function(page) |
| { |
| - var objects = [window]; |
| - |
| - this.tabs.push(tab); |
| - this.objects.push(objects); |
| - |
| - tab.addEventListener("close", function() |
| + var objects = this.cache.get(page); |
| + if (!objects) |
| { |
| - var idx = this.tabs.indexOf(tab); |
| - |
| - if (idx != -1) |
| - { |
| - this.tabs.splice(idx, 1); |
| - this.objects.splice(idx, 1); |
| - } |
| - }.bind(this)); |
| - |
| - return objects; |
| - }, |
| - getObjectCache: function(tab) |
| - { |
| - var idx = this.tabs.indexOf(tab); |
| - var objects; |
| - |
| - if (idx != -1) |
| - objects = this.objects[idx]; |
| - else |
| - objects = this.objects[idx] = this.createObjectCache(tab); |
| - |
| + objects = [window]; |
| + this.cache.set(page, objects); |
| + } |
| return objects; |
| }, |
| fail: function(error) |
| @@ -485,9 +451,9 @@ |
| error = error.message; |
| return {succeed: false, error: error}; |
| }, |
| - _handleMessage: function(message, tab) |
| + handleMessage: function(message) |
| { |
| - var objects = this.getObjectCache(tab); |
| + var objects = this.getObjectCache(pages[message.pageId]); |
| switch (message.type) |
| { |
| @@ -506,7 +472,7 @@ |
| return {succeed: true, result: this.serialize(value, objects)}; |
| case "setProperty": |
| var obj = objects[message.objectId]; |
| - var value = this.deserialize(message.value, objects, tab); |
| + var value = this.deserialize(message.value, objects, message.pageId); |
| try |
| { |
| @@ -524,7 +490,7 @@ |
| var args = []; |
| for (var i = 0; i < message.args.length; i++) |
| - args.push(this.deserialize(message.args[i], objects, tab)); |
| + args.push(this.deserialize(message.args[i], objects, message.pageId)); |
| try |
| { |
| @@ -561,151 +527,140 @@ |
| }; |
| - /* Web request blocking */ |
| - |
| - ext.webRequest = { |
| - onBeforeRequest: { |
| - _listeners: [], |
| - |
| - _handleMessage: function(message, rawTab) |
| - { |
| - var tab = new Tab(rawTab); |
| - var frame = new Frame(message.documentUrl, message.isTopLevel, rawTab); |
| - |
| - for (var i = 0; i < this._listeners.length; i++) |
| - { |
| - if (this._listeners[i](message.url, message.type, tab, frame) === false) |
| - return false; |
| - } |
| - |
| - return true; |
| - }, |
| - addListener: function(listener) |
| - { |
| - this._listeners.push(listener); |
| - }, |
| - removeListener: function(listener) |
| - { |
| - var idx = this._listeners.indexOf(listener); |
| - if (idx != -1) |
| - this._listeners.splice(idx, 1); |
| - } |
| - }, |
| - handlerBehaviorChanged: function() {} |
| - }; |
| - |
| - |
| - /* Synchronous messaging */ |
| + /* Message processing */ |
| safari.application.addEventListener("message", function(event) |
| { |
| - if (event.name == "canLoad") |
| + switch (event.name) |
| { |
| - var handler; |
| + case "canLoad": |
| + switch (event.message.category) |
| + { |
| + case "loading": |
| + var pageId; |
| + var frameId; |
| - switch (event.message.type) |
| - { |
| - case "proxy": |
| - handler = proxy; |
| - break; |
| - case "webRequest": |
| - handler = ext.webRequest.onBeforeRequest; |
| - break; |
| - } |
| + if (event.message.isTopLevel) |
| + { |
| + pageId = ++pageCounter; |
| + frameId = 0; |
| - event.message = handler._handleMessage(event.message.payload, event.target); |
| + var isPrerendered = event.message.isPrerendered; |
| + var page = pages[pageId] = new Page( |
| + pageId, |
| + event.target, |
| + event.message.url, |
| + isPrerendered |
| + ); |
| + |
| + // when a new page is shown, forget the previous page associated |
| + // with its tab, and reset the toolbar item if necessary. |
| + // Note that it wouldn't be sufficient to do that when the old |
| + // page is unloading, because Safari dispatches window.onunload |
| + // only when reloading the page or following links, but not when |
| + // you enter a new URL in the address bar. |
| + if (!isPrerendered) |
| + replacePage(page); |
| + |
| + ext.pages.onLoading._dispatch(page); |
| + } |
| + else |
| + { |
| + var page; |
| + var parentFrame; |
| + |
| + var lastPageId; |
| + var lastPage; |
| + var lastPageTopLevelFrame; |
| + |
| + // find the parent frame and its page for this sub frame, |
| + // by matching its referrer with the URL of frames previously |
| + // loaded in the same tab. If there is more than one match, |
| + // the most recent loaded page and frame is preferred. |
| + for (var curPageId in pages) |
| + { |
| + var curPage = pages[curPageId]; |
| + if (curPage._tab != event.target) |
| + continue; |
| + |
| + for (var i = 0; i < curPage._frames.length; i++) |
| + { |
| + var curFrame = curPage._frames[i]; |
| + |
| + if (curFrame.url == event.message.referrer) |
| + { |
| + pageId = curPageId; |
| + page = curPage; |
| + parentFrame = curFrame; |
| + } |
| + |
| + if (i == 0) |
| + { |
| + lastPageId = curPageId; |
| + lastPage = curPage; |
| + lastPageTopLevelFrame = curFrame; |
| + } |
| + } |
| + } |
| + |
| + // if we can't find the parent frame and its page, fall back to |
| + // the page most recently loaded in the tab and its top level frame |
| + if (!page) |
| + { |
| + pageId = lastPageId; |
| + page = lastPage; |
| + parentFrame = lastPageTopLevelFrame; |
| + } |
| + |
| + frameId = page._frames.length; |
| + page._frames.push({ |
| + url: event.message.url, |
| + parent: parentFrame |
| + }); |
| + } |
| + |
| + event.message = {pageId: pageId, frameId: frameId}; |
| + break; |
| + case "webRequest": |
| + var page = pages[event.message.pageId]; |
| + |
| + event.message = ext.webRequest.onBeforeRequest._dispatch( |
| + event.message.url, |
| + event.message.type, |
| + page, |
| + page._frames[event.message.frameId] |
| + ); |
| + break; |
| + case "proxy": |
| + event.message = backgroundPageProxy.handleMessage(event.message); |
| + break; |
| + } |
| + break; |
| + case "request": |
| + var page = pages[event.message.pageId]; |
| + var sender = {page: page, frame: page._frames[event.message.frameId]}; |
| + page._messageProxy.handleRequest(event.message, sender); |
| + break; |
| + case "response": |
| + pages[event.message.pageId]._messageProxy.handleResponse(event.message); |
| + break; |
| + case "replaced": |
| + var page = pages[event.message.pageId]; |
| + page._prerendered = false; |
| + |
| + // when a prerendered page is shown, forget the previous page |
| + // associated with its tab, and reset the toolbar item if necessary. |
| + // Note that it wouldn't be sufficient to do that when the old |
| + // page is unloading, because Safari dispatches window.onunload |
| + // only when reloading the page or following links, but not when |
| + // the current page is replaced with a prerendered page. |
| + replacePage(page); |
| + break; |
| } |
| - }, true); |
| + }); |
| - /* API */ |
| + /* Storage */ |
| - ext.windows = { |
| - getAll: function(callback) |
| - { |
| - callback(safari.application.browserWindows.map(function(win) |
| - { |
| - return new Window(win); |
| - })); |
| - }, |
| - getLastFocused: function(callback) |
| - { |
| - callback(new Window(safari.application.activeBrowserWindow)); |
| - } |
| - }; |
| - |
| - ext.backgroundPage = { |
| - getWindow: function() |
| - { |
| - return safari.extension.globalPage.contentWindow; |
| - } |
| - }; |
| - |
| - ext.onMessage = new BackgroundMessageEventTarget(); |
| ext.storage = safari.extension.settings; |
| - |
| - var contextMenuItems = []; |
| - var isContextMenuHidden = true; |
| - ext.contextMenus = { |
| - addMenuItem: function(title, contexts, onclick) |
| - { |
| - contextMenuItems.push({ |
| - id: String(contextMenuItems.length), |
| - title: title, |
| - item: null, |
| - contexts: contexts, |
| - onclick: onclick |
| - }); |
| - this.showMenuItems(); |
| - }, |
| - removeMenuItems: function() |
| - { |
| - contextMenuItems = []; |
| - this.hideMenuItems(); |
| - }, |
| - showMenuItems: function() |
| - { |
| - isContextMenuHidden = false; |
| - }, |
| - hideMenuItems: function() |
| - { |
| - isContextMenuHidden = true; |
| - } |
| - }; |
| - |
| - // Create context menu items |
| - safari.application.addEventListener("contextmenu", function(event) |
| - { |
| - if (isContextMenuHidden) |
| - return; |
| - |
| - var context = event.userInfo.tagName; |
| - if (context == "img") |
| - context = "image"; |
| - if (!event.userInfo.srcUrl) |
| - context = null; |
| - |
| - for (var i = 0; i < contextMenuItems.length; i++) |
| - { |
| - // Supported contexts are: all, audio, image, video |
| - var menuItem = contextMenuItems[i]; |
| - if (menuItem.contexts.indexOf("all") == -1 && menuItem.contexts.indexOf(context) == -1) |
| - continue; |
| - |
| - event.contextMenu.appendContextMenuItem(menuItem.id, menuItem.title); |
| - } |
| - }, false); |
| - |
| - // Handle context menu item clicks |
| - safari.application.addEventListener("command", function(event) |
| - { |
| - for (var i = 0; i < contextMenuItems.length; i++) |
| - { |
| - if (contextMenuItems[i].id == event.command) |
| - { |
| - contextMenuItems[i].onclick(event.userInfo.srcUrl, new Tab(safari.application.activeBrowserWindow.activeTab)); |
| - break; |
| - } |
| - } |
| - }, false); |
| })(); |