Last active
August 29, 2015 14:04
-
-
Save andy12530/9dcf71ad59ce8ee48276 to your computer and use it in GitHub Desktop.
跨浏览器,用于iframe之间通信的基础组件
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
G.add('js/util/event/event_emitter.js', [], function (require, exports, module) { | |
// Events | |
// ----------------- | |
// Thanks to: | |
// - https://github.com/documentcloud/backbone/blob/master/backbone.js | |
// - https://github.com/joyent/node/blob/master/lib/events.js | |
// Regular expression used to split event strings | |
var eventSplitter = /\s+/ | |
// A module that can be mixed in to *any object* in order to provide it | |
// with custom events. You may bind with `on` or remove with `off` callback | |
// functions to an event; `trigger`-ing an event fires all callbacks in | |
// succession. | |
// | |
// var object = new Events(); | |
// object.on('expand', function(){ alert('expanded'); }); | |
// object.trigger('expand'); | |
// | |
function Events() { | |
} | |
// Bind one or more space separated events, `events`, to a `callback` | |
// function. Passing `"all"` will bind the callback to all events fired. | |
Events.prototype.on = function(events, callback, context) { | |
var cache, event, list | |
if (!callback) return this | |
cache = this.__events || (this.__events = {}) | |
events = events.split(eventSplitter) | |
while (event = events.shift()) { | |
list = cache[event] || (cache[event] = []) | |
list.push(callback, context) | |
} | |
return this | |
} | |
// Remove one or many callbacks. If `context` is null, removes all callbacks | |
// with that function. If `callback` is null, removes all callbacks for the | |
// event. If `events` is null, removes all bound callbacks for all events. | |
Events.prototype.off = function(events, callback, context) { | |
var cache, event, list, i | |
// No events, or removing *all* events. | |
if (!(cache = this.__events)) return this | |
if (!(events || callback || context)) { | |
delete this.__events | |
return this | |
} | |
events = events ? events.split(eventSplitter) : Object.keys(cache) | |
// Loop through the callback list, splicing where appropriate. | |
while (event = events.shift()) { | |
list = cache[event] | |
if (!list) continue | |
if (!(callback || context)) { | |
delete cache[event] | |
continue | |
} | |
for (i = list.length - 2; i >= 0; i -= 2) { | |
if (!(callback && list[i] !== callback || | |
context && list[i + 1] !== context)) { | |
list.splice(i, 2) | |
} | |
} | |
} | |
return this | |
} | |
// Trigger one or many events, firing all bound callbacks. Callbacks are | |
// passed the same arguments as `trigger` is, apart from the event name | |
// (unless you're listening on `"all"`, which will cause your callback to | |
// receive the true name of the event as the first argument). | |
Events.prototype.trigger = function(events) { | |
var cache, event, all, list, i, len, rest = [], args | |
if (!(cache = this.__events)) return this | |
events = events.split(eventSplitter) | |
// Fill up `rest` with the callback arguments. Since we're only copying | |
// the tail of `arguments`, a loop is much faster than Array#slice. | |
for (i = 1, len = arguments.length; i < len; i++) { | |
rest[i - 1] = arguments[i] | |
} | |
// For each event, walk through the list of callbacks twice, first to | |
// trigger the event, then to trigger any `"all"` callbacks. | |
while (event = events.shift()) { | |
// Copy callback lists to prevent modification. | |
if (all = cache.all) all = all.slice() | |
if (list = cache[event]) list = list.slice() | |
// Execute event callbacks. | |
if (list) { | |
for (i = 0, len = list.length; i < len; i += 2) { | |
list[i].apply(list[i + 1] || this, rest) | |
} | |
} | |
// Execute "all" callbacks. | |
if (all) { | |
args = [event].concat(rest) | |
for (i = 0, len = all.length; i < len; i += 2) { | |
all[i].apply(all[i + 1] || this, args) | |
} | |
} | |
} | |
return this | |
} | |
Events.prototype.emit = Events.prototype.trigger; | |
// Mix `Events` to object instance or Class function. | |
Events.mixTo = function(receiver) { | |
var proto = Events.prototype | |
for (var p in proto) { | |
if (proto.hasOwnProperty(p)) { | |
receiver[p] = proto[p] | |
} | |
} | |
}; | |
module.exports = Events; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
G.add('js/util/iframe/rpc3.js', ['js/util/event/event_emitter.js', 'jquery'], function(require, exports, module) { | |
"use strict"; | |
var $ = require('jquery'); | |
var channelCounter = 0; | |
var Events = require('js/util/event/event_emitter.js'); | |
var JSON = window.JSON ? window.JSON : { | |
stringify: GJ.jsonEncode, | |
parse: GJ.jsonDecode | |
}; | |
var emptyFn = function() {}; | |
var isFunction = function(obj) { | |
return typeof obj === 'function'; | |
}; | |
var RPC = function(config) { | |
var self = this; | |
config = config || {}; | |
config.isHost = config.remote ? true : false; | |
if(config.isHost && window.name) { | |
config.channel = 'RPC_CHANNEL_P' + (channelCounter++); | |
} else { | |
config.channel = 'RPC_CHANNEL_' + (channelCounter++); | |
} | |
if (window.postMessage || document.postMessage) { | |
config.protocol = "1"; | |
} else { | |
config.protocol = "2"; | |
} | |
var callbacks = {}; | |
var methods = config.method || {}; | |
var messageID = 1; | |
Events.mixTo(RPC.Transport.prototype); | |
var transport = new RPC.Transport(config); | |
var send = function(message) { | |
transport.send(message); | |
}; | |
transport.on('ready', function() { | |
if (isFunction(config.onReady)) { | |
setTimeout(function () { | |
config.onReady.call(self); | |
}, 0); | |
} | |
}); | |
transport.on('message', function(message) { | |
if (message.method) { // exec method | |
execMethod(message); | |
} else if (message.callbackId) { // exec callback | |
var callback = callbacks[message.callbackId]; | |
if (callback) { | |
callback(message.result); | |
} | |
} | |
}); | |
var Fn = function(method, params, callback) { | |
var message = { | |
jsonrpc: "2.0", | |
params: params, | |
method: method, | |
callbackId: messageID | |
}; | |
if (isFunction(params)) { | |
callback = params; | |
} | |
if (isFunction(callback)) { | |
callbacks[messageID] = callback; | |
} | |
messageID++; | |
setTimeout(function() { | |
send(message); | |
}, 0); | |
}; | |
Fn.set = function(fnName, method) { | |
methods[fnName] = method; | |
}; | |
Fn.destroy = function() { | |
messageID = 0; | |
methods = {}; | |
callbacks = {}; | |
transport.destroy(); | |
}; | |
function execMethod(message) { | |
var result; | |
var fn = methods[message.method]; | |
if (isFunction(fn)) { | |
try { | |
if (Object.prototype.toString.call(message.params) !== '[object Array]') { | |
message.params = [message.params]; | |
} | |
result = fn.apply({}, message.params); | |
} catch (ex) { | |
throw new Error("Exec function error: " + ex.message); | |
} | |
if (message.callbackId) { | |
send({ | |
callbackId: message.callbackId, | |
result: result | |
}); | |
} | |
} | |
} | |
transport.init(); | |
Fn.iframe = config.iframe; | |
return Fn; | |
}; | |
RPC.behavior = {}; | |
if (module) { | |
module.exports = RPC; | |
} | |
(function(Behavior) { | |
Behavior.FLAG = '__RPC__'; | |
Behavior.handleMessage = function(message) { | |
return Behavior.FLAG + JSON.stringify(message); | |
}; | |
Behavior.verify = function(message) { | |
var result = { | |
message: undefined, | |
trust: false | |
}; | |
if (message.indexOf(Behavior.FLAG) === 0) { | |
result.message = message.replace(Behavior.FLAG, ''); | |
result.trust = true; | |
} | |
return result; | |
}; | |
Behavior.navigator = function(config, transport) { | |
var pub = { | |
incoming: function(message) { | |
var verifyInfo = Behavior.verify(message); | |
if (true === verifyInfo.trust) { | |
message = verifyInfo.message; | |
} else { | |
return; | |
} | |
if (message === '"ready"') { | |
transport.emit('ready'); | |
if (config.isHost) { | |
pub.outgoing('ready'); | |
} | |
} else { | |
transport.emit('message', JSON.parse(message)); | |
} | |
}, | |
outgoing: function(message) { | |
message = Behavior.handleMessage(message); | |
if (config.isHost) { | |
window.navigator[config.channel + '_remote'](message); | |
} else { | |
window.navigator[config.channel + '_host'](message); | |
} | |
}, | |
init: function() { | |
if (config.isHost) { | |
window.navigator[config.channel + '_host'] = pub.incoming; | |
} else { | |
window.navigator[config.channel + '_remote'] = pub.incoming; | |
pub.outgoing('ready'); | |
} | |
} | |
}; | |
return pub; | |
}; | |
Behavior.postMessage = function(config, transport) { | |
var postFnHost; | |
var pub = { | |
incoming: function(message) { | |
if(message.channel === config.channel) { | |
transport.emit('message', message); | |
} | |
}, | |
outgoing: function(message) { | |
if (message === 'ready') { | |
message = { | |
channel: config.channel, | |
isReady: true | |
}; | |
} else { | |
message.channel = config.channel; | |
} | |
message = Behavior.handleMessage(message); | |
postFnHost.postMessage(message, "*"); | |
}, | |
init: function() { | |
GJ.addEvent(window, 'message', function(event) { | |
var message = event.data; | |
var verifyInfo = Behavior.verify(message); | |
if (true === verifyInfo.trust) { | |
message = verifyInfo.message; | |
} else { | |
return; | |
} | |
message = JSON.parse(message); | |
if (message.channel === config.channel) { | |
if (message.isReady) { | |
if (config.isHost) { | |
pub.outgoing('ready'); | |
} | |
transport.emit('ready'); | |
} else { | |
pub.incoming(message); | |
} | |
} | |
}); | |
if (config.isHost) { | |
var i = config.iframe; | |
postFnHost = ("postMessage" in i.contentWindow) ? i.contentWindow : i.contentWindow.document; | |
} else { | |
postFnHost = ("postMessage" in window.parent) ? window.parent : window.parent.document; | |
pub.outgoing('ready'); | |
} | |
} | |
}; | |
return pub; | |
}; | |
})(RPC.behavior); | |
(function(RPC) { | |
var extend = function(obj, ext, overwrite) { | |
for (var prop in ext) { | |
if (prop in obj) { | |
var member = ext[prop]; | |
if (typeof member === 'object') { | |
extend(obj[prop], member, overwrite); | |
} else if (overwrite) { | |
obj[prop] = ext[prop]; | |
} | |
} else { | |
obj[prop] = ext[prop]; | |
} | |
} | |
return obj; | |
}; | |
var createFrame = function(config) { | |
var iframe = null; | |
try { | |
iframe = document.createElement('<IFRAME name="'+ config.channel +'">'); | |
} catch (e) { | |
} | |
if (!iframe || iframe.nodeName !== "IFRAME") { | |
iframe = document.createElement("IFRAME"); | |
iframe.name = config.channel; | |
} | |
config.props = config.props || {}; | |
if (typeof config.container === "string") { | |
config.container = document.getElementById(config.container); | |
} | |
extend(iframe.style, config.props.style, true); | |
if (!config.container) { | |
// This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers. | |
extend(iframe.style, { | |
position: "absolute", | |
top: "-2000px", | |
// Avoid potential horizontal scrollbar | |
left: "0px" | |
}, true); | |
} | |
// HACK: IE cannot have the src attribute set when the frame is appended | |
// into the container, so we set it to "javascript:false" as a | |
// placeholder for now. If we left the src undefined, it would | |
// instead default to "about:blank", which causes SSL mixed-content | |
// warnings in IE6 when on an SSL parent page. | |
config.props.src = 'javascript:false'; | |
// transfer properties to the frame | |
extend(iframe, config.props, true); | |
iframe.border = iframe.frameBorder = 0; | |
iframe.allowTransparency = true; | |
if (config.container) { | |
config.container.appendChild(iframe); | |
} else { | |
config.container = document.body; | |
$('body').prepend(iframe); | |
} | |
// set the frame URL to the proper value (we previously set it to | |
// "javascript:false" to work around the IE issue mentioned above) | |
// if (config.onLoad) { | |
// $(iframe).on('load', config.onLoad); | |
// } | |
iframe.src = config.remote; | |
return iframe; | |
}; | |
RPC.Transport = function(config) { | |
var self = this, stack = []; | |
var messages = []; | |
Events.mixTo(this); | |
if(!config.isHost) { | |
config.channel = window.name; | |
} | |
switch (config.protocol) { | |
case "1": | |
stack = new RPC.behavior.postMessage(config, self); | |
break; | |
case "2": | |
stack = new RPC.behavior.navigator(config, self); | |
break; | |
} | |
config.onLoad = function() { | |
stack.init(); | |
}; | |
this.on('ready', function () { | |
self.send = function(message) { | |
stack.outgoing(message); | |
}; | |
for(var i = 0; i< messages.length; i++) { | |
self.send(messages[i]); | |
} | |
}); | |
this.init = function () { | |
if (config.isHost) { | |
config.iframe = createFrame(config); | |
} | |
config.onLoad(); | |
}; | |
this.send = function(message) { | |
messages.push(message); | |
}; | |
this.destroy = function () { | |
config.iframe.parentNode.removeChild(config.iframe); | |
}; | |
}; | |
})(RPC); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment