Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: safari/background.js

Issue 16067002: Added Safari Support (Closed)
Patch Set: Created Oct. 21, 2013, 8:11 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« chrome/background.js ('K') | « popupBlocker.js ('k') | safari/common.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: safari/background.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/safari/background.js
@@ -0,0 +1,427 @@
+(function() {
+ /* Tabs */
+
+ var TabEventTarget = function() {
+ WrappedEventTarget.apply(this, arguments);
+ };
+ TabEventTarget.prototype = {
+ __proto__: WrappedEventTarget.prototype,
+ _wrapListener: function(listener) {
+ return function(event) {
+ listener(new Tab(event.target));
+ };
+ }
+ };
+
+ Tab = function(tab) {
+ this._tab = tab;
+
+ this._eventTarget = tab;
+ this._messageDispatcher = tab.page;
+
+ this.url = tab.url;
+
+ this.onBeforeNavigate = new TabEventTarget(tab, "beforeNavigate", false);
+ this.onCompleted = new TabEventTarget(tab, "navigate", false);
+ this.onActivated = new TabEventTarget(tab, "activate", false);
+ this.onRemoved = new TabEventTarget(tab, "close", false);
+ };
+ Tab.prototype = {
+ close: function() {
+ this._tab.close();
+ },
+ activate: function() {
+ this._tab.activate();
+ },
+ sendMessage: sendMessage,
+ pageAction: {
+ // there are no page actions in safari, so we use toolbar items instead
+ setIcon: function(path) {
+ safari.extension.toolbarItems[0].image = safari.extension.baseURI + path;
+ },
+ setTitle: function(title) {
+ safari.extension.toolbarItems[0].toolTip = title;
+ },
+
+ // toolbar items in safari can"t get hidden
+ hide: function() {},
+ show: function() {}
+ }
+ };
+
+ TabMap = function() {
+ this._tabs = [];
+ this._values = [];
+
+ this._onClosed = this._onClosed.bind(this);
+ };
+ TabMap.prototype = {
+ get: function(tab) {
+ var idx;
+
+ if (!tab || (idx = this._tabs.indexOf(tab._tab)) == -1)
+ return null;
+
+ return this._values[idx];
+ },
+ set: function(tab, value) {
+ var idx = this._tabs.indexOf(tab._tab);
+
+ if (idx != -1)
+ this._values[idx] = value;
+ else {
+ this._tabs.push(tab._tab);
+ this._values.push(value);
+
+ tab._tab.addEventListener("close", this._onClosed, false);
+ }
+ },
+ has: function(tab) {
+ return this._tabs.indexOf(tab._tab) != -1;
+ },
+ clear: function() {
+ while (this._tabs.length > 0)
+ this._delete(this._tabs[0]);
+ },
+ _delete: function(tab) {
+ var idx = this._tabs.indexOf(tab);
+
+ if (idx != -1) {
+ this._tabs.splice(idx, 1);
+ this._values.splice(idx, 1);
+
+ tab.removeEventListener("close", this._onClosed, false);
+ }
+ },
+ _onClosed: function(event) {
+ this._delete(event.target);
+ }
+ };
+ TabMap.prototype["delete"] = function(tab) {
+ this._delete(tab._tab);
+ };
+
+
+ /* Windows */
+
+ Window = function(win) {
+ this._win = win;
+ this.visible = win.visible;
+ }
+ Window.prototype = {
+ 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));
+ }
+ };
+
+ if (safari.extension.globalPage.contentWindow == window) {
+ /* Background page proxy */
+
+ var proxy = {
+ tabs: [],
+ objects: [],
+
+ registerObject: function(obj, objects) {
+ var objectId = objects.indexOf(obj);
+
+ if (objectId == -1)
+ objectId = objects.push(obj) - 1;
+
+ return objectId;
+ },
+ serializeSequence: function(sequence, objects, memo) {
+ if (!memo)
+ memo = {specs: [], arrays: []};
+
+ var items = [];
+ for (var i = 0; i < sequence.length; i++)
+ items.push(this.serialize(sequence[i], objects, memo));
+
+ return items;
+ },
+ serialize: function(obj, objects, memo) {
+ if (typeof obj == "function")
+ return {type: "function", objectId: this.registerObject(obj, objects)};
+
+ if (typeof obj == "object" && obj != null) {
+ if (obj.constructor == Array) {
+ if (!memo)
+ memo = {specs: [], arrays: []};
+
+ var idx = memo.arrays.indexOf(obj);
+ if (idx != -1)
+ return memo.specs[idx];
+
+ var spec = {type: "array"};
+ memo.specs.push(spec);
+ memo.arrays.push(obj);
+
+ spec.items = this.serializeSequence(obj, objects, memo);
+ return spec;
+ }
+
+ if (obj.constructor != Date)
+ if (obj.constructor != RegExp)
+ return {type: "object", objectId: this.registerObject(obj, objects)};
+ }
+
+ return {type: "value", value: obj};
+ },
+ createCallback: function(callbackId, tab) {
+ var proxy = this;
+
+ return function() {
+ var idx = proxy.tabs.indexOf(tab);
+
+ if (idx != -1) {
+ var objects = proxy.objects[idx];
+
+ tab.page.dispatchMessage("proxyCallback", {
+ callbackId: callbackId,
+ contextId: proxy.registerObject(this, objects),
+ args: proxy.serializeSequence(arguments, objects)
+ });
+ }
+ };
+ },
+ deserialize: function(spec, objects, tab, memo) {
+ switch (spec.type) {
+ case "value":
+ return spec.value;
+ case "hosted":
+ return objects[spec.objectId];
+ case "callback":
+ return this.createCallback(spec.callbackId, tab);
+ case "object":
+ case "array":
+ if (!memo)
+ memo = {specs: [], objects: []};
+
+ var idx = memo.specs.indexOf(spec);
+ if (idx != -1)
+ return memo.objects[idx];
+
+ var obj;
+ if (spec.type == "array")
+ obj = [];
+ else
+ obj = {};
+
+ memo.specs.push(spec);
+ memo.objects.push(obj);
+
+ if (spec.type == "array")
+ for (var i = 0; i < spec.items.length; i++)
+ obj.push(this.deserialize(spec.items[i], objects, tab, memo));
+ else
+ for (var k in spec.properties)
+ obj[k] = this.deserialize(spec.properties[k], objects, tab, memo);
+
+ return obj;
+ }
+ },
+ createObjectCache: function(tab) {
+ var objects = [window];
+
+ this.tabs.push(tab);
+ this.objects.push(objects);
+
+ tab.addEventListener("close", function() {
+ 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);
+
+ return objects;
+ },
+ fail: function(error) {
+ if (error instanceof Error)
+ error = error.message;
+ return {succeed: false, error: error};
+ },
+ _handleMessage: function(message, tab) {
+ var objects = this.getObjectCache(tab);
+
+ switch (message.type) {
+ case "getProperty":
+ var obj = objects[message.objectId];
+
+ try {
+ var value = obj[message.property];
+ } catch (e) {
+ return this.fail(e);
+ }
+
+ return {succeed: true, result: this.serialize(value, objects)};
+ case "setProperty":
+ var obj = objects[message.objectId];
+ var value = this.deserialize(message.value, objects, tab);
+
+ try {
+ obj[message.property] = value;
+ } catch (e) {
+ return this.fail(e);
+ }
+
+ return {succeed: true};
+ case "callFunction":
+ var func = objects[message.functionId];
+ var context = objects[message.contextId];
+
+ var args = [];
+ for (var i = 0; i < message.args.length; i++)
+ args.push(this.deserialize(message.args[i], objects, tab));
+
+ try {
+ var result = func.apply(context, args);
+ } catch (e) {
+ return this.fail(e);
+ }
+
+ return {succeed: true, result: this.serialize(result, objects)};
+ case "inspectObject":
+ var obj = objects[message.objectId];
+
+ var prototype = Object.getPrototypeOf(obj);
+ var prototypeId;
+ if (prototype != null)
+ prototypeId = this.registerObject(prototype, objects);
+ else
+ prototypeId = null;
+
+ var properties = {};
+ Object.getOwnPropertyNames(obj).forEach(function(prop) {
+ if (obj != Object.prototype || prop == "constructor")
+ properties[prop] = {
+ enumerable: Object.getOwnPropertyDescriptor(obj, prop).enumerable
+ };
+ });
+
+ return {prototypeId: prototypeId, properties: properties};
+ }
+ }
+ };
+
+
+ /* Web request blocking */
+
+ ext.webRequest = {
+ onBeforeRequest: {
+ _listeners: [],
+ _urlPatterns: [],
+
+ _handleMessage: function(message, tab) {
+ tab = new Tab(tab);
+
+ for (var i = 0; i < this._listeners.length; i++) {
+ var regex = this._urlPatterns[i];
+
+ if (!regex || regex.test(message))
+ if (this._listeners[i](message.url, message.type, tab, 0, -1) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+ addListener: function(listener, urls) {
+ var regex;
+
+ if (urls)
+ regex = new RegExp("^(?:" + urls.map(function(url) {
+ return url.split("*").map(function(s) {
+ return s.replace(/([.?+^$[\]\\(){}|-])/g, "\\$1");
+ }).join(".*");
+ }).join("|") + ")($|[?#])");
+
+ this._listeners.push(listener);
+ this._urlPatterns.push(regex);
+ },
+ removeListener: function(listener) {
+ var idx = this._listeners.indexOf(listener);
+
+ if (idx != -1) {
+ this._listeners.splice(idx, 1);
+ this._urlPatterns.splice(idx, 1);
+ }
+ }
+ },
+ handlerBehaviorChanged: function() {}
+ };
+
+
+ /* Synchronous messaging */
+
+ safari.application.addEventListener("message", function(event) {
+ if (event.name == "canLoad") {
+ var handler;
+
+ switch (event.message.type) {
+ case "proxy":
+ handler = proxy;
+ break;
+ case "webRequest":
+ handler = ext.webRequest.onBeforeRequest;
+ break;
+ }
+
+ event.message = handler._handleMessage(event.message.payload, event.target);
+ }
+ }, true);
+ }
+
+
+ /* API */
+
+ 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.tabs = {
+ onBeforeNavigate: new TabEventTarget(safari.application, "beforeNavigate", true),
+ onCompleted: new TabEventTarget(safari.application, "navigate", true),
+ onActivated: new TabEventTarget(safari.application, "activate", true),
+ onRemoved: new TabEventTarget(safari.application, "close", true)
+ };
+
+ ext.backgroundPage = {
+ getWindow: function() {
+ return safari.extension.globalPage.contentWindow;
+ }
+ };
+
+ ext.onMessage = new MessageEventTarget(safari.application);
+})();
« chrome/background.js ('K') | « popupBlocker.js ('k') | safari/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld