434 lines
16 KiB
JavaScript
434 lines
16 KiB
JavaScript
/**
|
|
The MIT License
|
|
|
|
Copyright (c) 2010 Daniel Park (http://metaweb.com, http://postmessage.freebaseapps.com)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
**/
|
|
var NO_JQUERY = {};
|
|
(function(window, $, undefined) {
|
|
|
|
if (!("console" in window)) {
|
|
var c = window.console = {};
|
|
c.log = c.warn = c.error = c.debug = function(){};
|
|
}
|
|
|
|
if ($ === NO_JQUERY) {
|
|
// jQuery is optional
|
|
$ = {
|
|
fn: {},
|
|
extend: function() {
|
|
var a = arguments[0];
|
|
for (var i=1,len=arguments.length; i<len; i++) {
|
|
var b = arguments[i];
|
|
for (var prop in b) {
|
|
a[prop] = b[prop];
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
};
|
|
}
|
|
|
|
$.fn.pm = function() {
|
|
console.log("usage: \nto send: $.pm(options)\nto receive: $.pm.bind(type, fn, [origin])");
|
|
return this;
|
|
};
|
|
|
|
// send postmessage
|
|
$.pm = window.pm = function(options) {
|
|
pm.send(options);
|
|
};
|
|
|
|
// bind postmessage handler
|
|
$.pm.bind = window.pm.bind = function(type, fn, origin, hash, async_reply) {
|
|
pm.bind(type, fn, origin, hash, async_reply === true);
|
|
};
|
|
|
|
// unbind postmessage handler
|
|
$.pm.unbind = window.pm.unbind = function(type, fn) {
|
|
pm.unbind(type, fn);
|
|
};
|
|
|
|
// default postmessage origin on bind
|
|
$.pm.origin = window.pm.origin = null;
|
|
|
|
// default postmessage polling if using location hash to pass postmessages
|
|
$.pm.poll = window.pm.poll = 200;
|
|
|
|
var pm = {
|
|
|
|
send: function(options) {
|
|
var o = $.extend({}, pm.defaults, options),
|
|
target = o.target;
|
|
if (!o.target) {
|
|
console.warn("postmessage target window required");
|
|
return;
|
|
}
|
|
if (!o.type) {
|
|
console.warn("postmessage type required");
|
|
return;
|
|
}
|
|
var msg = {data:o.data, type:o.type};
|
|
if (o.success) {
|
|
msg.callback = pm._callback(o.success);
|
|
}
|
|
if (o.error) {
|
|
msg.errback = pm._callback(o.error);
|
|
}
|
|
if (("postMessage" in target) && !o.hash) {
|
|
pm._bind();
|
|
target.postMessage(JSON.stringify(msg), o.origin || '*');
|
|
}
|
|
else {
|
|
pm.hash._bind();
|
|
pm.hash.send(o, msg);
|
|
}
|
|
},
|
|
|
|
bind: function(type, fn, origin, hash, async_reply) {
|
|
pm._replyBind ( type, fn, origin, hash, async_reply );
|
|
},
|
|
|
|
_replyBind: function(type, fn, origin, hash, isCallback) {
|
|
if (("postMessage" in window) && !hash) {
|
|
pm._bind();
|
|
}
|
|
else {
|
|
pm.hash._bind();
|
|
}
|
|
var l = pm.data("listeners.postmessage");
|
|
if (!l) {
|
|
l = {};
|
|
pm.data("listeners.postmessage", l);
|
|
}
|
|
var fns = l[type];
|
|
if (!fns) {
|
|
fns = [];
|
|
l[type] = fns;
|
|
}
|
|
fns.push({fn:fn, callback: isCallback, origin:origin || $.pm.origin});
|
|
},
|
|
|
|
unbind: function(type, fn) {
|
|
var l = pm.data("listeners.postmessage");
|
|
if (l) {
|
|
if (type) {
|
|
if (fn) {
|
|
// remove specific listener
|
|
var fns = l[type];
|
|
if (fns) {
|
|
var m = [];
|
|
for (var i=0,len=fns.length; i<len; i++) {
|
|
var o = fns[i];
|
|
if (o.fn !== fn) {
|
|
m.push(o);
|
|
}
|
|
}
|
|
l[type] = m;
|
|
}
|
|
}
|
|
else {
|
|
// remove all listeners by type
|
|
delete l[type];
|
|
}
|
|
}
|
|
else {
|
|
// unbind all listeners of all type
|
|
for (var i in l) {
|
|
delete l[i];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
data: function(k, v) {
|
|
if (v === undefined) {
|
|
return pm._data[k];
|
|
}
|
|
pm._data[k] = v;
|
|
return v;
|
|
},
|
|
|
|
_data: {},
|
|
|
|
_CHARS: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''),
|
|
|
|
_random: function() {
|
|
var r = [];
|
|
for (var i=0; i<32; i++) {
|
|
r[i] = pm._CHARS[0 | Math.random() * 32];
|
|
};
|
|
return r.join("");
|
|
},
|
|
|
|
_callback: function(fn) {
|
|
var cbs = pm.data("callbacks.postmessage");
|
|
if (!cbs) {
|
|
cbs = {};
|
|
pm.data("callbacks.postmessage", cbs);
|
|
}
|
|
var r = pm._random();
|
|
cbs[r] = fn;
|
|
return r;
|
|
},
|
|
|
|
_bind: function() {
|
|
// are we already listening to message events on this w?
|
|
if (!pm.data("listening.postmessage")) {
|
|
if (window.addEventListener) {
|
|
window.addEventListener("message", pm._dispatch, false);
|
|
}
|
|
else if (window.attachEvent) {
|
|
window.attachEvent("onmessage", pm._dispatch);
|
|
}
|
|
pm.data("listening.postmessage", 1);
|
|
}
|
|
},
|
|
|
|
_dispatch: function(e) {
|
|
//console.log("$.pm.dispatch", e, this);
|
|
try {
|
|
var msg = JSON.parse(e.data);
|
|
}
|
|
catch (ex) {
|
|
//console.warn("postmessage data invalid json: ", ex); //message wasn't meant for pm
|
|
return;
|
|
}
|
|
if (!msg.type) {
|
|
//console.warn("postmessage message type required"); //message wasn't meant for pm
|
|
return;
|
|
}
|
|
var cbs = pm.data("callbacks.postmessage") || {},
|
|
cb = cbs[msg.type];
|
|
if (cb) {
|
|
cb(msg.data);
|
|
}
|
|
else {
|
|
var l = pm.data("listeners.postmessage") || {};
|
|
var fns = l[msg.type] || [];
|
|
for (var i=0,len=fns.length; i<len; i++) {
|
|
var o = fns[i];
|
|
if (o.origin && o.origin !== '*' && e.origin !== o.origin) {
|
|
console.warn("postmessage message origin mismatch", e.origin, o.origin);
|
|
if (msg.errback) {
|
|
// notify post message errback
|
|
var error = {
|
|
message: "postmessage origin mismatch",
|
|
origin: [e.origin, o.origin]
|
|
};
|
|
pm.send({target:e.source, data:error, type:msg.errback});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
function sendReply ( data ) {
|
|
if (msg.callback) {
|
|
pm.send({target:e.source, data:data, type:msg.callback});
|
|
}
|
|
}
|
|
|
|
try {
|
|
if ( o.callback ) {
|
|
o.fn(msg.data, sendReply, e);
|
|
} else {
|
|
sendReply ( o.fn(msg.data, e) );
|
|
}
|
|
}
|
|
catch (ex) {
|
|
if (msg.errback) {
|
|
// notify post message errback
|
|
pm.send({target:e.source, data:ex, type:msg.errback});
|
|
} else {
|
|
throw ex;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
// location hash polling
|
|
pm.hash = {
|
|
|
|
send: function(options, msg) {
|
|
//console.log("hash.send", target_window, options, msg);
|
|
var target_window = options.target,
|
|
target_url = options.url;
|
|
if (!target_url) {
|
|
console.warn("postmessage target window url is required");
|
|
return;
|
|
}
|
|
target_url = pm.hash._url(target_url);
|
|
var source_window,
|
|
source_url = pm.hash._url(window.location.href);
|
|
if (window == target_window.parent) {
|
|
source_window = "parent";
|
|
}
|
|
else {
|
|
try {
|
|
for (var i=0,len=parent.frames.length; i<len; i++) {
|
|
var f = parent.frames[i];
|
|
if (f == window) {
|
|
source_window = i;
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
catch(ex) {
|
|
// Opera: security error trying to access parent.frames x-origin
|
|
// juse use window.name
|
|
source_window = window.name;
|
|
}
|
|
}
|
|
if (source_window == null) {
|
|
console.warn("postmessage windows must be direct parent/child windows and the child must be available through the parent window.frames list");
|
|
return;
|
|
}
|
|
var hashmessage = {
|
|
"x-requested-with": "postmessage",
|
|
source: {
|
|
name: source_window,
|
|
url: source_url
|
|
},
|
|
postmessage: msg
|
|
};
|
|
var hash_id = "#x-postmessage-id=" + pm._random();
|
|
target_window.location = target_url + hash_id + encodeURIComponent(JSON.stringify(hashmessage));
|
|
},
|
|
|
|
_regex: /^\#x\-postmessage\-id\=(\w{32})/,
|
|
|
|
_regex_len: "#x-postmessage-id=".length + 32,
|
|
|
|
_bind: function() {
|
|
// are we already listening to message events on this w?
|
|
if (!pm.data("polling.postmessage")) {
|
|
setInterval(function() {
|
|
var hash = "" + window.location.hash,
|
|
m = pm.hash._regex.exec(hash);
|
|
if (m) {
|
|
var id = m[1];
|
|
if (pm.hash._last !== id) {
|
|
pm.hash._last = id;
|
|
pm.hash._dispatch(hash.substring(pm.hash._regex_len));
|
|
}
|
|
}
|
|
}, $.pm.poll || 200);
|
|
pm.data("polling.postmessage", 1);
|
|
}
|
|
},
|
|
|
|
_dispatch: function(hash) {
|
|
if (!hash) {
|
|
return;
|
|
}
|
|
try {
|
|
hash = JSON.parse(decodeURIComponent(hash));
|
|
if (!(hash['x-requested-with'] === 'postmessage' &&
|
|
hash.source && hash.source.name != null && hash.source.url && hash.postmessage)) {
|
|
// ignore since hash could've come from somewhere else
|
|
return;
|
|
}
|
|
}
|
|
catch (ex) {
|
|
// ignore since hash could've come from somewhere else
|
|
return;
|
|
}
|
|
var msg = hash.postmessage,
|
|
cbs = pm.data("callbacks.postmessage") || {},
|
|
cb = cbs[msg.type];
|
|
if (cb) {
|
|
cb(msg.data);
|
|
}
|
|
else {
|
|
var source_window;
|
|
if (hash.source.name === "parent") {
|
|
source_window = window.parent;
|
|
}
|
|
else {
|
|
source_window = window.frames[hash.source.name];
|
|
}
|
|
var l = pm.data("listeners.postmessage") || {};
|
|
var fns = l[msg.type] || [];
|
|
for (var i=0,len=fns.length; i<len; i++) {
|
|
var o = fns[i];
|
|
if (o.origin) {
|
|
var origin = /https?\:\/\/[^\/]*/.exec(hash.source.url)[0];
|
|
if (o.origin !== '*' && origin !== o.origin) {
|
|
console.warn("postmessage message origin mismatch", origin, o.origin);
|
|
if (msg.errback) {
|
|
// notify post message errback
|
|
var error = {
|
|
message: "postmessage origin mismatch",
|
|
origin: [origin, o.origin]
|
|
};
|
|
pm.send({target:source_window, data:error, type:msg.errback, hash:true, url:hash.source.url});
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
function sendReply ( data ) {
|
|
if (msg.callback) {
|
|
pm.send({target:source_window, data:data, type:msg.callback, hash:true, url:hash.source.url});
|
|
}
|
|
}
|
|
|
|
try {
|
|
if ( o.callback ) {
|
|
o.fn(msg.data, sendReply);
|
|
} else {
|
|
sendReply ( o.fn(msg.data) );
|
|
}
|
|
}
|
|
catch (ex) {
|
|
if (msg.errback) {
|
|
// notify post message errback
|
|
pm.send({target:source_window, data:ex, type:msg.errback, hash:true, url:hash.source.url});
|
|
} else {
|
|
throw ex;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
},
|
|
|
|
_url: function(url) {
|
|
// url minus hash part
|
|
return (""+url).replace(/#.*$/, "");
|
|
}
|
|
|
|
};
|
|
|
|
$.extend(pm, {
|
|
defaults: {
|
|
target: null, /* target window (required) */
|
|
url: null, /* target window url (required if no window.postMessage or hash == true) */
|
|
type: null, /* message type (required) */
|
|
data: null, /* message data (required) */
|
|
success: null, /* success callback (optional) */
|
|
error: null, /* error callback (optional) */
|
|
origin: "*", /* postmessage origin (optional) */
|
|
hash: false /* use location hash for message passing (optional) */
|
|
}
|
|
});
|
|
|
|
})(this, typeof jQuery === "undefined" ? NO_JQUERY : jQuery);
|