WordPress Landing Pages - Version 1.9.0

Version Description

  • New preview views in landing page edit screens
  • Temporarily disabling geolocation services
Download this release

Release Info

Developer DavidWells
Plugin Icon 128x128 WordPress Landing Pages
Version 1.9.0
Comparing to
See all releases

Code changes from version 1.8.8 to 1.9.0

Files changed (51) hide show
  1. classes/class.activation.php +53 -4
  2. css/admin-style.css +15 -0
  3. js/admin/admin.post-edit.js +5 -2
  4. js/libraries/easyXDM.debug.js +1 -1
  5. js/libraries/jquery.zoomer.js +436 -0
  6. landing-pages.php +46 -48
  7. libraries/class-tgm-plugin-activation.php +3497 -2048
  8. libraries/shareme/sharrre/jquery.sharrre-1.3.3.min.js +1 -7
  9. modules/module.ab-testing.php +72 -146
  10. modules/module.admin-menus.php +5 -2
  11. modules/module.ajax-setup.php +1 -1
  12. modules/module.customizer.php +1 -1
  13. modules/module.install.php +12 -12
  14. modules/module.javascript-admin.php +1 -1
  15. modules/module.javascript-frontend.php +15 -6
  16. modules/module.metaboxes.php +64 -36
  17. modules/module.post-type.php +0 -10
  18. modules/module.store.php +1 -1
  19. modules/module.welcome.php +28 -9
  20. package.json +45 -0
  21. phpunit.xml.dist +8 -0
  22. readme.txt +5 -1
  23. shared/assets/assets.loader.class.php +1 -0
  24. shared/assets/js/frontend/analytics-src/analytics.forms.js +2 -2
  25. shared/assets/js/frontend/analytics-src/analytics.page.js +1 -0
  26. shared/assets/js/frontend/analytics/inboundAnalytics.js +3 -2
  27. shared/assets/js/frontend/analytics/inboundAnalytics.min.js +2 -2
  28. shared/classes/class.feedback.php +1 -1
  29. shared/classes/class.form.php +188 -50
  30. shared/classes/class.inbound-forms.akismet.php +0 -2
  31. shared/classes/class.lead-storage.php +713 -714
  32. shared/classes/class.licensing.php +11 -10
  33. shared/classes/class.load-shared.php +1 -1
  34. shared/classes/class.magic.php +2 -2
  35. shared/classes/class.master-license.php +4 -4
  36. shared/classes/class.menu.php +5 -7
  37. shared/classes/class.menus.adminbar.php +2 -4
  38. shared/classes/class.welcome.php +3 -3
  39. shared/shortcodes/css/form-cpt.css +9 -4
  40. shared/shortcodes/css/select2.png +0 -0
  41. shared/shortcodes/css/shortcodes.css +4 -3
  42. shared/shortcodes/inbound-shortcodes.php +28 -95
  43. shared/shortcodes/js/form-cpt.js +105 -74
  44. shared/shortcodes/shortcodes/forms.php +85 -77
  45. templates/countdown-lander/config.php +1 -1
  46. templates/countdown-lander/index.php +6 -6
  47. tests/codeception/_bootstrap.php +5 -1
  48. tests/codeception/acceptance.suite.yml +2 -2
  49. tests/codeception/acceptance/LoginCept.php +28 -0
  50. tests/codeception/acceptance/StatisticsCept.php +23 -31
  51. tests/phpunit/bootstrap.php +0 -15
classes/class.activation.php CHANGED
@@ -10,6 +10,31 @@ class Landing_Pages_Activation {
10
static $version_leads;
11
static $version_lpah;
12
13
public static function activate() {
14
self::load_static_vars();
15
self::run_version_checks();
@@ -41,7 +66,7 @@ class Landing_Pages_Activation {
41
42
/* Activate shared components */
43
self::activate_shared();
44
-
45
/* Run additional actions */
46
do_action( 'activate_landing_pages' );
47
@@ -230,14 +255,38 @@ class Landing_Pages_Activation {
230
}
231
232
}
233
}
234
235
/* Add Activation Hook */
236
register_activation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'activate' ) );
237
register_deactivation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'deactivate' ) );
238
239
-
240
- /* Add listener for uncompleted upgrade routines */
241
- add_action( 'admin_init' , array( 'Landing_Pages_Activation' , 'run_upgrade_routine_checks' ) );
242
243
}
10
static $version_leads;
11
static $version_lpah;
12
13
+ /**
14
+ * Initiate class
15
+ */
16
+ public function __construct() {
17
+ self::load_hooks();
18
+ }
19
+
20
+ /**
21
+ * load supporting hooks and filters
22
+ */
23
+ public static function load_hooks() {
24
+ if (!is_admin()) {
25
+ return;
26
+ }
27
+
28
+ /* Add listener for unset permalinks */
29
+ add_action('admin_notices', array( __CLASS__ , 'permastruct_check' ) );
30
+
31
+ /** add listener for permlaink flush command */
32
+ add_action('admin_init', array( __CLASS__ , 'flush_permalinks' ) , 11 );
33
+
34
+ /* Add listener for uncompleted upgrade routines */
35
+ add_action( 'admin_init' , array( 'Landing_Pages_Activation' , 'run_upgrade_routine_checks' ) );
36
+ }
37
+
38
public static function activate() {
39
self::load_static_vars();
40
self::run_version_checks();
66
67
/* Activate shared components */
68
self::activate_shared();
69
+
70
/* Run additional actions */
71
do_action( 'activate_landing_pages' );
72
255
}
256
257
}
258
+
259
+ /**
260
+ * flush permalinks
261
+ */
262
+ public static function flush_permalinks() {
263
+
264
+ if ( !get_option( 'lp_activate_rewrite_check' ) ) {
265
+ return;
266
+ }
267
+
268
+ flush_rewrite_rules( true );
269
+ delete_option( 'lp_activate_rewrite_check' );
270
+ }
271
+
272
+ /**
273
+ * check for 'default' permalinks and warn
274
+ */
275
+ public static function permastruct_check() {
276
+ if ( '' == get_option( 'permalink_structure' ) ) {
277
+ ?>
278
+ <div class="error">
279
+ <p><?php _e( 'Landing Pages plugin requires you to use a non default permlaink structure. Please head into your pemalink settings and choose an option besides \'default\'.' , 'landing-pages'); ?></p>
280
+ </div>
281
+ <?php
282
+ }
283
+ }
284
}
285
286
/* Add Activation Hook */
287
register_activation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'activate' ) );
288
register_deactivation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'deactivate' ) );
289
290
+ new Landing_Pages_Activation;
291
292
}
css/admin-style.css CHANGED
@@ -13,6 +13,9 @@
13
margin-top:30px;
14
z-index:999999;
15
}
16
17
tr#leads {
18
display: table-row !important;
@@ -61,11 +64,23 @@ display: none;
61
#setting-error-tgmpa p {
62
text-decoration: bold;
63
}
64
#setting-error-tgmpa p:nth-last-child(2), #setting-error-tgmpa a, #setting-error-tgmpa strong {
65
text-decoration: normal;
66
}
67
#setting-error-tgmpa p:nth-last-child(2) a:first-child{
68
padding-left: 0px;
69
}
70
#setting-error-tgmpa p:nth-last-child(2) a {
71
padding-right: 10px;
13
margin-top:30px;
14
z-index:999999;
15
}
16
+ #setting-error-tgmpa a:-webkit-any-link {
17
+ text-decoration: none;
18
+ }
19
20
tr#leads {
21
display: table-row !important;
64
#setting-error-tgmpa p {
65
text-decoration: bold;
66
}
67
+ .appearance_page_install-inbound-plugins .inbound-install-notice {
68
+ display: none;
69
+ }
70
#setting-error-tgmpa p:nth-last-child(2), #setting-error-tgmpa a, #setting-error-tgmpa strong {
71
text-decoration: normal;
72
}
73
+ #setting-error-tgmpa em:last-of-type {
74
+ display: inline-block;
75
+ }
76
+ .inbound-and {
77
+ display: block;
78
+ margin-top: 10px;
79
+ font-size: 15px;
80
+ }
81
#setting-error-tgmpa p:nth-last-child(2) a:first-child{
82
padding-left: 0px;
83
+ text-decoration: none;
84
}
85
#setting-error-tgmpa p:nth-last-child(2) a {
86
padding-right: 10px;
js/admin/admin.post-edit.js CHANGED
@@ -13,6 +13,9 @@ jQuery(document).ready(function($) {
13
}
14
});
15
16
// Filter Styling
17
jQuery('#template-filter li').first().addClass('button-primary');
18
// filter items when filter link is clicked
@@ -323,8 +326,8 @@ jQuery(document).ready(function($) {
323
//jQuery(this).attr('rel', all.hex);
324
325
jQuery(this).parent().find(".lp-success-message").remove();
326
- jQuery(this).parent().find(".new-save-lp").show();
327
- jQuery(this).parent().find(".new-save-lp-frontend").show();
328
//jQuery(this).attr('value', all.hex);
329
});
330
});
13
}
14
});
15
16
+ var width = jQuery("#lp-thumbnail-sidebar-preview").width();
17
+ jQuery('#zoomer').zoomer({ width: width, height: 225, zoom: 0.3, tranformOrigin: '0 43px', });
18
+
19
// Filter Styling
20
jQuery('#template-filter li').first().addClass('button-primary');
21
// filter items when filter link is clicked
326
//jQuery(this).attr('rel', all.hex);
327
328
jQuery(this).parent().find(".lp-success-message").remove();
329
+ //jQuery(this).parent().find(".new-save-lp").show();
330
+ //jQuery(this).parent().find(".new-save-lp-frontend").show();
331
//jQuery(this).attr('value', all.hex);
332
});
333
});
js/libraries/easyXDM.debug.js CHANGED
@@ -1 +1 @@
1
- /**
2
* easyXDM
3
* http://easyxdm.net/
4
* Copyright(c) 2009-2011, √ėyvind Sean Kinsey, oyvind@kinsey.no.
5
*
6
* Permission is hereby granted, free of charge, to any person obtaining a copy
7
* of this software and associated documentation files (the "Software"), to deal
8
* in the Software without restriction, including without limitation the rights
9
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
* copies of the Software, and to permit persons to whom the Software is
11
* furnished to do so, subject to the following conditions:
12
*
13
* The above copyright notice and this permission notice shall be included in
14
* all copies or substantial portions of the Software.
15
*
16
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
* THE SOFTWARE.
23
*/
24
var t = typeof object[property];
25
return t == 'function' ||
26
(!!(t == 'object' && object[property])) ||
27
t == 'unknown';
28
return !!(typeof(object[property]) == 'object' && object[property]);
29
return Object.prototype.toString.call(o) === '[object Array]';
30
try {
31
var activeX = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
32
flashVersion = Array.prototype.slice.call(activeX.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
33
HAS_FLASH_THROTTLED_BUG = parseInt(flashVersion[0], 10) > 9 && parseInt(flashVersion[1], 10) > 0;
34
activeX = null;
35
return true;
36
}
37
catch (notSupportedException) {
38
return false;
39
}
40
* Cross Browser implementation for adding and removing event listeners.
41
*/
42
on = function(target, type, listener){
43
_trace("adding listener " + type);
44
target.addEventListener(type, listener, false);
45
};
46
un = function(target, type, listener){
47
_trace("removing listener " + type);
48
target.removeEventListener(type, listener, false);
49
};
50
on = function(object, sEvent, fpNotify){
51
_trace("adding listener " + sEvent);
52
object.attachEvent("on" + sEvent, fpNotify);
53
};
54
un = function(object, sEvent, fpNotify){
55
_trace("removing listener " + sEvent);
56
object.detachEvent("on" + sEvent, fpNotify);
57
};
58
throw new Error("Browser not supported");
59
* Cross Browser implementation of DOMContentLoaded.
60
*/
61
// If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
62
// 'interactive' (HTML5 specs, recent WebKit builds) states.
63
// https://bugs.webkit.org/show_bug.cgi?id=45119
64
readyState = document.readyState;
65
domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
66
// If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
67
// when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
68
// We only need a body to add elements to, so the existence of document.body is enough for us.
69
domIsReady = !!document.body;
70
if (domIsReady) {
71
return;
72
}
73
domIsReady = true;
74
_trace("firing dom_onReady");
75
for (var i = 0; i < domReadyQueue.length; i++) {
76
domReadyQueue[i]();
77
}
78
domReadyQueue.length = 0;
79
if (isHostMethod(window, "addEventListener")) {
80
on(document, "DOMContentLoaded", dom_onReady);
81
}
82
else {
83
on(document, "readystatechange", function(){
84
if (document.readyState == "complete") {
85
dom_onReady();
86
}
87
});
88
if (document.documentElement.doScroll && window === top) {
89
var doScrollCheck = function(){
90
if (domIsReady) {
91
return;
92
}
93
// http://javascript.nwbox.com/IEContentLoaded/
94
try {
95
document.documentElement.doScroll("left");
96
}
97
catch (e) {
98
setTimeout(doScrollCheck, 1);
99
return;
100
}
101
dom_onReady();
102
};
103
doScrollCheck();
104
}
105
}
106
107
// A fallback to window.onload, that will always work
108
on(window, "load", dom_onReady);
109
* This will add a function to the queue of functions to be run once the DOM reaches a ready state.
110
* If functions are added after this event then they will be executed immediately.
111
* @param {function} fn The function to add
112
* @param {Object} scope An optional scope for the function to be called with.
113
*/
114
if (domIsReady) {
115
fn.call(scope);
116
return;
117
}
118
domReadyQueue.push(function(){
119
fn.call(scope);
120
});
121
* Returns an instance of easyXDM from the parent window with
122
* respect to the namespace.
123
*
124
* @return An instance of easyXDM (in the parent window)
125
*/
126
var obj = parent;
127
if (namespace !== "") {
128
for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
129
if (!obj) {
130
throw new Error(ii.slice(0, i + 1).join('.') + ' is not an object');
131
}
132
obj = obj[ii[i]];
133
}
134
}
135
if (!obj || !obj.easyXDM) {
136
throw new Error('Could not find easyXDM in parent.' + namespace);
137
}
138
return obj.easyXDM;
139
* Removes easyXDM variable from the global scope. It also returns control
140
* of the easyXDM variable to whatever code used it before.
141
*
142
* @param {String} ns A string representation of an object that will hold
143
* an instance of easyXDM.
144
* @return An instance of easyXDM
145
*/
146
if (typeof ns != "string" || !ns) {
147
throw new Error('namespace must be a non-empty string');
148
}
149
_trace("Settings namespace to '" + ns + "'");
150
151
window.easyXDM = _easyXDM;
152
namespace = ns;
153
if (namespace) {
154
IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
155
}
156
return easyXDM;
157
* Methods for working with URLs
158
*/
159
* Get the domain name from a url.
160
* @param {String} url The url to extract the domain from.
161
* @return The domain part of the url.
162
* @type {String}
163
*/
164
if (!url) {
165
throw new Error("url is undefined or empty");
166
}
167
return url.match(reURI)[3];
168
* Get the port for a given URL, or "" if none
169
* @param {String} url The url to extract the port from.
170
* @return The port part of the url.
171
* @type {String}
172
*/
173
if (!url) {
174
throw new Error("url is undefined or empty");
175
}
176
return url.match(reURI)[4] || "";
177
* Returns a string containing the schema, domain and if present the port
178
* @param {String} url The url to extract the location from
179
* @return {String} The location part of the url
180
*/
181
if (!url) {
182
throw new Error("url is undefined or empty");
183
}
184
if (/^file/.test(url)) {
185
throw new Error("The file:// protocol is not supported");
186
}
187
var m = url.toLowerCase().match(reURI);
188
var proto = m[2], domain = m[3], port = m[4] || "";
189
if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
190
port = "";
191
}
192
return proto + "//" + domain + port;
193
* Resolves a relative url into an absolute one.
194
* @param {String} url The path to resolve.
195
* @return {String} The resolved url.
196
*/
197
if (!url) {
198
throw new Error("url is undefined or empty");
199
}
200
201
// replace all // except the one in proto with /
202
url = url.replace(reDoubleSlash, "$1/");
203
204
// If the url is a valid url we do nothing
205
if (!url.match(/^(http||https):\/\//)) {
206
// If this is a relative path
207
var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
208
if (path.substring(path.length - 1) !== "/") {
209
path = path.substring(0, path.lastIndexOf("/") + 1);
210
}
211
212
url = location.protocol + "//" + location.host + path + url;
213
}
214
215
// reduce all 'xyz/../' to just ''
216
while (reParent.test(url)) {
217
url = url.replace(reParent, "");
218
}
219
220
_trace("resolved url '" + url + "'");
221
return url;
222
* Appends the parameters to the given url.<br/>
223
* The base url can contain existing query parameters.
224
* @param {String} url The base url.
225
* @param {Object} parameters The parameters to add.
226
* @return {String} A new valid url with the parameters appended.
227
*/
228
if (!parameters) {
229
throw new Error("parameters is undefined or null");
230
}
231
232
var hash = "", indexOf = url.indexOf("#");
233
if (indexOf !== -1) {
234
hash = url.substring(indexOf);
235
url = url.substring(0, indexOf);
236
}
237
var q = [];
238
for (var key in parameters) {
239
if (parameters.hasOwnProperty(key)) {
240
q.push(key + "=" + encodeURIComponent(parameters[key]));
241
}
242
}
243
return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
244
input = input.substring(1).split("&");
245
var data = {}, pair, i = input.length;
246
while (i--) {
247
pair = input[i].split("=");
248
data[pair[0]] = decodeURIComponent(pair[1]);
249
}
250
return data;
251
* Helper methods
252
*/
253
* Helper for checking if a variable/property is undefined
254
* @param {Object} v The variable to test
255
* @return {Boolean} True if the passed variable is undefined
256
*/
257
return typeof v === "undefined";
258
* A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
259
* @return {JSON} A valid JSON conforming object, or null if not found.
260
*/
261
var cached = {};
262
var obj = {
263
a: [1, 2, 3]
264
}, json = "{\"a\":[1,2,3]}";
265
266
if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
267
// this is a working JSON instance
268
return JSON;
269
}
270
if (Object.toJSON) {
271
if (Object.toJSON(obj).replace((/\s/g), "") === json) {
272
// this is a working stringify method
273
cached.stringify = Object.toJSON;
274
}
275
}
276
277
if (typeof String.prototype.evalJSON === "function") {
278
obj = json.evalJSON();
279
if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
280
// this is a working parse method
281
cached.parse = function(str){
282
return str.evalJSON();
283
};
284
}
285
}
286
287
if (cached.stringify && cached.parse) {
288
// Only memoize the result if we have valid instance
289
getJSON = function(){
290
return cached;
291
};
292
return cached;
293
}
294
return null;
295
* Applies properties from the source object to the target object.<br/>
296
* @param {Object} target The target of the properties.
297
* @param {Object} source The source of the properties.
298
* @param {Boolean} noOverwrite Set to True to only set non-existing properties.
299
*/
300
var member;
301
for (var prop in source) {
302
if (source.hasOwnProperty(prop)) {
303
if (prop in destination) {
304
member = source[prop];
305
if (typeof member === "object") {
306
apply(destination[prop], member, noOverwrite);
307
}
308
else if (!noOverwrite) {
309
destination[prop] = source[prop];
310
}
311
}
312
else {
313
destination[prop] = source[prop];
314
}
315
}
316
}
317
return destination;
318
var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
319
input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
320
HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
321
document.body.removeChild(form);
322
_trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG);
323
* Creates a frame and appends it to the DOM.
324
* @param config {object} This object can have the following properties
325
* <ul>
326
* <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
327
* <li> {object} attr The attributes that should be set on the frame.</li>
328
* <li> {DOMElement} container Its parent element (Optional).</li>
329
* <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
330
* </ul>
331
* @return The frames DOMElement
332
* @type DOMElement
333
*/
334
_trace("creating frame: " + config.props.src);
335
if (undef(HAS_NAME_PROPERTY_BUG)) {
336
testForNamePropertyBug();
337
}
338
var frame;
339
// This is to work around the problems in IE6/7 with setting the name property.
340
// Internally this is set as 'submitName' instead when using 'iframe.name = ...'
341
// This is not required by easyXDM itself, but is to facilitate other use cases
342
if (HAS_NAME_PROPERTY_BUG) {
343
frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
344
}
345
else {
346
frame = document.createElement("IFRAME");
347
frame.name = config.props.name;
348
}
349
350
frame.id = frame.name = config.props.name;
351
delete config.props.name;
352
353
if (config.onLoad) {
354
on(frame, "load", config.onLoad);
355
}
356
357
if (typeof config.container == "string") {
358
config.container = document.getElementById(config.container);
359
}
360
361
if (!config.container) {
362
// This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
363
apply(frame.style, {
364
position: "absolute",
365
top: "-2000px"
366
});
367
config.container = document.body;
368
}
369
370
// HACK for some reason, IE needs the source set
371
// after the frame has been appended into the DOM
372
// so remove the src, and set it afterwards
373
var src = config.props.src;
374
delete config.props.src;
375
376
// transfer properties to the frame
377
apply(frame, config.props);
378
379
frame.border = frame.frameBorder = 0;
380
frame.allowTransparency = true;
381
config.container.appendChild(frame);
382
383
// HACK see above
384
frame.src = src;
385
config.props.src = src;
386
387
return frame;
388
* Check whether a domain is allowed using an Access Control List.
389
* The ACL can contain * and ? as wildcards, or can be regular expressions.
390
* If regular expressions they need to begin with ^ and end with $.
391
* @param {Array/String} acl The list of allowed domains
392
* @param {String} domain The domain to test.
393
* @return {Boolean} True if the domain is allowed, false if not.
394
*/
395
// normalize into an array
396
if (typeof acl == "string") {
397
acl = [acl];
398
}
399
var re, i = acl.length;
400
while (i--) {
401
re = acl[i];
402
re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "quot;));
403
if (re.test(domain)) {
404
return true;
405
}
406
}
407
return false;
408
* Functions related to stacks
409
*/
410
* Prepares an array of stack-elements suitable for the current configuration
411
* @param {Object} config The Transports configuration. See easyXDM.Socket for more.
412
* @return {Array} An array of stack-elements with the TransportElement at index 0.
413
*/
414
var protocol = config.protocol, stackEls;
415
config.isHost = config.isHost || undef(query.xdm_p);
416
useHash = config.hash || false;
417
_trace("preparing transport stack");
418
419
if (!config.props) {
420
config.props = {};
421
}
422
if (!config.isHost) {
423
_trace("using parameters from query");
424
config.channel = query.xdm_c;
425
config.secret = query.xdm_s;
426
config.remote = query.xdm_e;
427
protocol = query.xdm_p;
428
if (config.acl && !checkAcl(config.acl, config.remote)) {
429
throw new Error("Access denied for " + config.remote);
430
}
431
}
432
else {
433
config.remote = resolveUrl(config.remote);
434
config.channel = config.channel || "default" + channelId++;
435
config.secret = Math.random().toString(16).substring(2);
436
if (undef(protocol)) {
437
if (getLocation(location.href) == getLocation(config.remote)) {
438
/*
439
* Both documents has the same origin, lets use direct access.
440
*/
441
protocol = "4";
442
}
443
else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
444
/*
445
* This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
446
*/
447
protocol = "1";
448
}
449
else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
450
/*
451
* The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
452
*/
453
protocol = "6";
454
}
455
else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
456
/*
457
* This is supported in Gecko (Firefox 1+)
458
*/
459
protocol = "5";
460
}
461
else if (config.remoteHelper) {
462
/*
463
* This is supported in all browsers that retains the value of window.name when
464
* navigating from one domain to another, and where parent.frames[foo] can be used
465
* to get access to a frame from the same domain
466
*/
467
config.remoteHelper = resolveUrl(config.remoteHelper);
468
protocol = "2";
469
}
470
else {
471
/*
472
* This is supported in all browsers where [window].location is writable for all
473
* The resize event will be used if resize is supported and the iframe is not put
474
* into a container, else polling will be used.
475
*/
476
protocol = "0";
477
}
478
_trace("selecting protocol: " + protocol);
479
}
480
else {
481
_trace("using protocol: " + protocol);
482
}
483
}
484
config.protocol = protocol; // for conditional branching
485
switch (protocol) {
486
case "0":// 0 = HashTransport
487
apply(config, {
488
interval: 100,
489
delay: 2000,
490
useResize: true,
491
useParent: false,
492
usePolling: false
493
}, true);
494
if (config.isHost) {
495
if (!config.local) {
496
_trace("looking for image to use as local");
497
// If no local is set then we need to find an image hosted on the current domain
498
var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
499
var i = images.length;
500
while (i--) {
501
image = images[i];
502
if (image.src.substring(0, domain.length) === domain) {
503
config.local = image.src;
504
break;
505
}
506
}
507
if (!config.local) {
508
_trace("no image found, defaulting to using the window");
509
// If no local was set, and we are unable to find a suitable file, then we resort to using the current window
510
config.local = window;
511
}
512
}
513
514
var parameters = {
515
xdm_c: config.channel,
516
xdm_p: 0
517
};
518
519
if (config.local === window) {
520
// We are using the current window to listen to
521
config.usePolling = true;
522
config.useParent = true;
523
config.local = location.protocol + "//" + location.host + location.pathname + location.search;
524
parameters.xdm_e = config.local;
525
parameters.xdm_pa = 1; // use parent
526
}
527
else {
528
parameters.xdm_e = resolveUrl(config.local);
529
}
530
531
if (config.container) {
532
config.useResize = false;
533
parameters.xdm_po = 1; // use polling
534
}
535
config.remote = appendQueryParameters(config.remote, parameters);
536
}
537
else {
538
apply(config, {
539
channel: query.xdm_c,
540
remote: query.xdm_e,
541
useParent: !undef(query.xdm_pa),
542
usePolling: !undef(query.xdm_po),
543
useResize: config.useParent ? false : config.useResize
544
});
545
}
546
stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
547
encode: true,
548
maxLength: 4000 - config.remote.length
549
}), new easyXDM.stack.VerifyBehavior({
550
initiate: config.isHost
551
})];
552
break;
553
case "1":
554
stackEls = [new easyXDM.stack.PostMessageTransport(config)];
555
break;
556
case "2":
557
stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
558
initiate: config.isHost
559
})];
560
break;
561
case "3":
562
stackEls = [new easyXDM.stack.NixTransport(config)];
563
break;
564
case "4":
565
stackEls = [new easyXDM.stack.SameOriginTransport(config)];
566
break;
567
case "5":
568
stackEls = [new easyXDM.stack.FrameElementTransport(config)];
569
break;
570
case "6":
571
if (!flashVersion) {
572
hasFlash();
573
}
574
stackEls = [new easyXDM.stack.FlashTransport(config)];
575
break;
576
}
577
// this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
578
stackEls.push(new easyXDM.stack.QueueBehavior({
579
lazy: config.lazy,
580
remove: true
581
}));
582
return stackEls;
583
* Chains all the separate stack elements into a single usable stack.<br/>
584
* If an element is missing a necessary method then it will have a pass-through method applied.
585
* @param {Array} stackElements An array of stack elements to be linked.
586
* @return {easyXDM.stack.StackElement} The last element in the chain.
587
*/
588
var stackEl, defaults = {
589
incoming: function(message, origin){
590
this.up.incoming(message, origin);
591
},
592
outgoing: function(message, recipient){
593
this.down.outgoing(message, recipient);
594
},
595
callback: function(success){
596
this.up.callback(success);
597
},
598
init: function(){
599
this.down.init();
600
},
601
destroy: function(){
602
this.down.destroy();
603
}
604
};
605
for (var i = 0, len = stackElements.length; i < len; i++) {
606
stackEl = stackElements[i];
607
apply(stackEl, defaults, true);
608
if (i !== 0) {
609
stackEl.down = stackElements[i - 1];
610
}
611
if (i !== len - 1) {
612
stackEl.up = stackElements[i + 1];
613
}
614
}
615
return stackEl;
616
* This will remove a stackelement from its stack while leaving the stack functional.
617
* @param {Object} element The elment to remove from the stack.
618
*/
619
element.up.down = element.down;
620
element.down.up = element.up;
621
element.up = element.down = null;
622
* Export the main object and any other methods applicable
623
*/
624
* @class easyXDM
625
* A javascript library providing cross-browser, cross-domain messaging/RPC.
626
* @version 2.4.15.118
627
* @singleton
628
*/
629
/**
630
* The version of the library
631
* @type {string}
632
*/
633
version: "2.4.15.118",
634
/**
635
* This is a map containing all the query parameters passed to the document.
636
* All the values has been decoded using decodeURIComponent.
637
* @type {object}
638
*/
639
query: query,
640
/**
641
* @private
642
*/
643
stack: {},
644
/**
645
* Applies properties from the source object to the target object.<br/>
646
* @param {object} target The target of the properties.
647
* @param {object} source The source of the properties.
648
* @param {boolean} noOverwrite Set to True to only set non-existing properties.
649
*/
650
apply: apply,
651
652
/**
653
* A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
654
* @return {JSON} A valid JSON conforming object, or null if not found.
655
*/
656
getJSONObject: getJSON,
657
/**
658
* This will add a function to the queue of functions to be run once the DOM reaches a ready state.
659
* If functions are added after this event then they will be executed immediately.
660
* @param {function} fn The function to add
661
* @param {object} scope An optional scope for the function to be called with.
662
*/
663
whenReady: whenReady,
664
/**
665
* Removes easyXDM variable from the global scope. It also returns control
666
* of the easyXDM variable to whatever code used it before.
667
*
668
* @param {String} ns A string representation of an object that will hold
669
* an instance of easyXDM.
670
* @return An instance of easyXDM
671
*/
672
noConflict: noConflict
673
checkAcl: checkAcl,
674
getDomainName: getDomainName,
675
getLocation: getLocation,
676
appendQueryParameters: appendQueryParameters
677
_deferred: [],
678
flush: function(){
679
this.trace("... deferred messages ...");
680
for (var i = 0, len = this._deferred.length; i < len; i++) {
681
this.trace(this._deferred[i]);
682
}
683
this._deferred.length = 0;
684
this.trace("... end of deferred messages ...");
685
},
686
getTime: function(){
687
var d = new Date(), h = d.getHours() + "", m = d.getMinutes() + "", s = d.getSeconds() + "", ms = d.getMilliseconds() + "", zeros = "000";
688
if (h.length == 1) {
689
h = "0" + h;
690
}
691
if (m.length == 1) {
692
m = "0" + m;
693
}
694
if (s.length == 1) {
695
s = "0" + s;
696
}
697
ms = zeros.substring(ms.length) + ms;
698
return h + ":" + m + ":" + s + "." + ms;
699
},
700
/**
701
* Logs the message to console.log if available
702
* @param {String} msg The message to log
703
*/
704
log: function(msg){
705
// Uses memoizing to cache the implementation
706
if (!isHostObject(window, "console") || undef(console.log)) {
707
/**
708
* Sets log to be an empty function since we have no output available
709
* @ignore
710
*/
711
this.log = emptyFn;
712
}
713
else {
714
/**
715
* Sets log to be a wrapper around console.log
716
* @ignore
717
* @param {String} msg
718
*/
719
this.log = function(msg){
720
console.log(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ": " + msg);
721
};
722
}
723
this.log(msg);
724
},
725
/**
726
* Will try to trace the given message either to a DOMElement with the id "log",
727
* or by using console.info.
728
* @param {String} msg The message to trace
729
*/
730
trace: function(msg){
731
// Uses memoizing to cache the implementation
732
if (!domIsReady) {
733
if (this._deferred.length === 0) {
734
easyXDM.whenReady(debug.flush, debug);
735
}
736
this._deferred.push(msg);
737
this.log(msg);
738
}
739
else {
740
var el = document.getElementById("log");
741
// is there a log element present?
742
if (el) {
743
/**
744
* Sets trace to be a function that outputs the messages to the DOMElement with id "log"
745
* @ignore
746
* @param {String} msg
747
*/
748
this.trace = function(msg){
749
try {
750
el.appendChild(document.createElement("div")).appendChild(document.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
751
el.scrollTop = el.scrollHeight;
752
}
753
catch (e) {
754
//In case we are unloading
755
}
756
};
757
}
758
else if (isHostObject(window, "console") && !undef(console.info)) {
759
/**
760
* Sets trace to be a wrapper around console.info
761
* @ignore
762
* @param {String} msg
763
*/
764
this.trace = function(msg){
765
console.info(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg);
766
};
767
}
768
else {
769
/**
770
* Create log window
771
* @ignore
772
*/
773
var domain = location.host, windowname = domain.replace(/\[-.:]/g, "") + "easyxdm_log", logWin;
774
try {
775
logWin = window.open("", windowname, "width=800,height=200,status=0,navigation=0,scrollbars=1");
776
}
777
catch (e) {
778
}
779
if (logWin) {
780
var doc = logWin.document;
781
el = doc.getElementById("log");
782
if (!el) {
783
doc.write("<html><head><title>easyXDM log " + domain + "</title></head>");
784
doc.write("<body><div id=\"log\"></div></body></html>");
785
doc.close();
786
el = doc.getElementById("log");
787
}
788
this.trace = function(msg){
789
try {
790
el.appendChild(doc.createElement("div")).appendChild(doc.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
791
el.scrollTop = el.scrollHeight;
792
}
793
catch (e) {
794
//In case we are unloading
795
}
796
};
797
this.trace("---- new logger at " + location.href);
798
}
799
800
if (!el) {
801
// We are unable to use any logging
802
this.trace = emptyFn;
803
}
804
}
805
this.trace(msg);
806
}
807
},
808
/**
809
* Creates a method usable for tracing.
810
* @param {String} name The name the messages should be marked with
811
* @return {Function} A function that accepts a single string as argument.
812
*/
813
getTracer: function(name){
814
return function(msg){
815
debug.trace(name + ": " + msg);
816
};
817
}
818
* @class easyXDM.DomHelper
819
* Contains methods for dealing with the DOM
820
* @singleton
821
*/
822
/**
823
* Provides a consistent interface for adding eventhandlers
824
* @param {Object} target The target to add the event to
825
* @param {String} type The name of the event
826
* @param {Function} listener The listener
827
*/
828
on: on,
829
/**
830
* Provides a consistent interface for removing eventhandlers
831
* @param {Object} target The target to remove the event from
832
* @param {String} type The name of the event
833
* @param {Function} listener The listener
834
*/
835
un: un,
836
/**
837
* Checks for the presence of the JSON object.
838
* If it is not present it will use the supplied path to load the JSON2 library.
839
* This should be called in the documents head right after the easyXDM script tag.
840
* http://json.org/json2.js
841
* @param {String} path A valid path to json2.js
842
*/
843
requiresJSON: function(path){
844
if (!isHostObject(window, "JSON")) {
845
debug.log("loading external JSON");
846
// we need to encode the < in order to avoid an illegal token error
847
// when the script is inlined in a document.
848
document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
849
}
850
else {
851
debug.log("native JSON found");
852
}
853
}
854
// The map containing the stored functions
855
var _map = {};
856
857
/**
858
* @class easyXDM.Fn
859
* This contains methods related to function handling, such as storing callbacks.
860
* @singleton
861
* @namespace easyXDM
862
*/
863
easyXDM.Fn = {
864
/**
865
* Stores a function using the given name for reference
866
* @param {String} name The name that the function should be referred by
867
* @param {Function} fn The function to store
868
* @namespace easyXDM.fn
869
*/
870
set: function(name, fn){
871
this._trace("storing function " + name);
872
_map[name] = fn;
873
},
874
/**
875
* Retrieves the function referred to by the given name
876
* @param {String} name The name of the function to retrieve
877
* @param {Boolean} del If the function should be deleted after retrieval
878
* @return {Function} The stored function
879
* @namespace easyXDM.fn
880
*/
881
get: function(name, del){
882
this._trace("retrieving function " + name);
883
var fn = _map[name];
884
if (!fn) {
885
this._trace(name + " not found");
886
}
887
888
if (del) {
889
delete _map[name];
890
}
891
return fn;
892
}
893
};
894
895
easyXDM.Fn._trace = debug.getTracer("easyXDM.Fn");
896
* @class easyXDM.Socket
897
* This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
898
* The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
899
* Internally different stacks will be used depending on the browsers features and the available parameters.
900
* <h2>How to set up</h2>
901
* Setting up the provider:
902
* <pre><code>
903
* var socket = new easyXDM.Socket({
904
* &nbsp; local: "name.html",
905
* &nbsp; onReady: function(){
906
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
907
* &nbsp; &nbsp; socket.postMessage("foo-message");
908
* &nbsp; },
909
* &nbsp; onMessage: function(message, origin) {
910
* &nbsp;&nbsp; alert("received " + message + " from " + origin);
911
* &nbsp; }
912
* });
913
* </code></pre>
914
* Setting up the consumer:
915
* <pre><code>
916
* var socket = new easyXDM.Socket({
917
* &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
918
* &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
919
* &nbsp; onReady: function(){
920
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
921
* &nbsp; &nbsp; socket.postMessage("foo-message");
922
* &nbsp; },
923
* &nbsp; onMessage: function(message, origin) {
924
* &nbsp;&nbsp; alert("received " + message + " from " + origin);
925
* &nbsp; }
926
* });
927
* </code></pre>
928
* If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
929
* and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
930
* @namespace easyXDM
931
* @constructor
932
* @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
933
* @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
934
* @cfg {String} remote (Consumer only) The url to the providers document.
935
* @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
936
* @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
937
* @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
938
* @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
939
* @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
940
* @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
941
* @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
942
* @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
943
* This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
944
* If none of the patterns match an Error will be thrown.
945
* @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
946
* Properties such as 'name' and 'src' will be overrided. Optional.
947
*/
948
var trace = debug.getTracer("easyXDM.Socket");
949
trace("constructor");
950
951
// create the stack
952
var stack = chainStack(prepareTransportStack(config).concat([{
953
incoming: function(message, origin){
954
config.onMessage(message, origin);
955
},
956
callback: function(success){
957
if (config.onReady) {
958
config.onReady(success);
959
}
960
}
961
}])), recipient = getLocation(config.remote);
962
963
// set the origin
964
this.origin = getLocation(config.remote);
965
/**
966
* Initiates the destruction of the stack.
967
*/
968
this.destroy = function(){
969
stack.destroy();
970
};
971
972
/**
973
* Posts a message to the remote end of the channel
974
* @param {String} message The message to send
975
*/
976
this.postMessage = function(message){
977
stack.outgoing(message, recipient);
978
};
979
980
stack.init();
981
* @class easyXDM.Rpc
982
* Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
983
* of methods to be called from the remote end.<br/>
984
* The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
985
* This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
986
* <h2>How to set up</h2>
987
* <pre><code>
988
* var rpc = new easyXDM.Rpc({
989
* &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
990
* &nbsp; remote: "http:&#47;&#47;remotedomain/...",
991
* &nbsp; onReady: function(){
992
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
993
* &nbsp; &nbsp; rpc.foo(...
994
* &nbsp; }
995
* },{
996
* &nbsp; local: {..},
997
* &nbsp; remote: {..}
998
* });
999
* </code></pre>
1000
*
1001
* <h2>Exposing functions (procedures)</h2>
1002
* <pre><code>
1003
* var rpc = new easyXDM.Rpc({
1004
* &nbsp; ...
1005
* },{
1006
* &nbsp; local: {
1007
* &nbsp; &nbsp; nameOfMethod: {
1008
* &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
1009
* &nbsp; &nbsp; &nbsp; &nbsp; ...
1010
* &nbsp; &nbsp; &nbsp; }
1011
* &nbsp; &nbsp; },
1012
* &nbsp; &nbsp; &#47;&#47; with shorthand notation
1013
* &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
1014
* &nbsp; &nbsp; }
1015
* &nbsp; },
1016
* &nbsp; remote: {...}
1017
* });
1018
* </code></pre>
1019
* The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
1020
* To send a successfull result back you can use
1021
* <pre><code>
1022
* return foo;
1023
* </pre></code>
1024
* or
1025
* <pre><code>
1026
* success(foo);
1027
* </pre></code>
1028
* To return an error you can use
1029
* <pre><code>
1030
* throw new Error("foo error");
1031
* </code></pre>
1032
* or
1033
* <pre><code>
1034
* error("foo error");
1035
* </code></pre>
1036
*
1037
* <h2>Defining remotely exposed methods (procedures/notifications)</h2>
1038
* The definition of the remote end is quite similar:
1039
* <pre><code>
1040
* var rpc = new easyXDM.Rpc({
1041
* &nbsp; ...
1042
* },{
1043
* &nbsp; local: {...},
1044
* &nbsp; remote: {
1045
* &nbsp; &nbsp; nameOfMethod: {}
1046
* &nbsp; }
1047
* });
1048
* </code></pre>
1049
* To call a remote method use
1050
* <pre><code>
1051
* rpc.nameOfMethod("arg1", "arg2", function(value) {
1052
* &nbsp; alert("success: " + value);
1053
* }, function(message) {
1054
* &nbsp; alert("error: " + message + );
1055
* });
1056
* </code></pre>
1057
* Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
1058
* When called with no callback a JSON-RPC 2.0 notification will be executed.
1059
* Be aware that you will not be notified of any errors with this method.
1060
* <br/>
1061
* <h2>Specifying a custom serializer</h2>
1062
* If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
1063
* then you can specify a custom serializer using <code>serializer: foo</code>
1064
* <pre><code>
1065
* var rpc = new easyXDM.Rpc({
1066
* &nbsp; ...
1067
* },{
1068
* &nbsp; local: {...},
1069
* &nbsp; remote: {...},
1070
* &nbsp; serializer : {
1071
* &nbsp; &nbsp; parse: function(string){ ... },
1072
* &nbsp; &nbsp; stringify: function(object) {...}
1073
* &nbsp; }
1074
* });
1075
* </code></pre>
1076
* If <code>serializer</code> is set then the class will not attempt to use the native implementation.
1077
* @namespace easyXDM
1078
* @constructor
1079
* @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
1080
* @param {Object} jsonRpcConfig The description of the interface to implement.
1081
*/
1082
var trace = debug.getTracer("easyXDM.Rpc");
1083
trace("constructor");
1084
1085
// expand shorthand notation
1086
if (jsonRpcConfig.local) {
1087
for (var method in jsonRpcConfig.local) {
1088
if (jsonRpcConfig.local.hasOwnProperty(method)) {
1089
var member = jsonRpcConfig.local[method];
1090
if (typeof member === "function") {
1091
jsonRpcConfig.local[method] = {
1092
method: member
1093
};
1094
}
1095
}
1096
}
1097
}
1098
// create the stack
1099
var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
1100
callback: function(success){
1101
if (config.onReady) {
1102
config.onReady(success);
1103
}
1104
}
1105
}]));
1106
// set the origin
1107
this.origin = getLocation(config.remote);
1108
1109
/**
1110
* Initiates the destruction of the stack.
1111
*/
1112
this.destroy = function(){
1113
stack.destroy();
1114
};
1115
1116
stack.init();
1117
* @class easyXDM.stack.SameOriginTransport
1118
* SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
1119
* This can be useful for testing and for when the main application supports both internal and external sources.
1120
* @namespace easyXDM.stack
1121
* @constructor
1122
* @param {Object} config The transports configuration.
1123
* @cfg {String} remote The remote document to communicate with.
1124
*/
1125
var trace = debug.getTracer("easyXDM.stack.SameOriginTransport");
1126
trace("constructor");
1127
var pub, frame, send, targetOrigin;
1128
1129
return (pub = {
1130
outgoing: function(message, domain, fn){
1131
send(message);
1132
if (fn) {
1133
fn();
1134
}
1135
},
1136
destroy: function(){
1137
trace("destroy");
1138
if (frame) {
1139
frame.parentNode.removeChild(frame);
1140
frame = null;
1141
}
1142
},
1143
onDOMReady: function(){
1144
trace("init");
1145
targetOrigin = getLocation(config.remote);
1146
1147
if (config.isHost) {
1148
// set up the iframe
1149
apply(config.props, {
1150
src: appendQueryParameters(config.remote, {
1151
xdm_e: location.protocol + "//" + location.host + location.pathname,
1152
xdm_c: config.channel,
1153
xdm_p: 4 // 4 = SameOriginTransport
1154
}),
1155
name: IFRAME_PREFIX + config.channel + "_provider"
1156
});
1157
frame = createFrame(config);
1158
easyXDM.Fn.set(config.channel, function(sendFn){
1159
send = sendFn;
1160
setTimeout(function(){
1161
pub.up.callback(true);
1162
}, 0);
1163
return function(msg){
1164
pub.up.incoming(msg, targetOrigin);
1165
};
1166
});
1167
}
1168
else {
1169
send = getParentObject().Fn.get(config.channel, true)(function(msg){
1170
pub.up.incoming(msg, targetOrigin);
1171
});
1172
setTimeout(function(){
1173
pub.up.callback(true);
1174
}, 0);
1175
}
1176
},
1177
init: function(){
1178
whenReady(pub.onDOMReady, pub);
1179
}
1180
});
1181
* @class easyXDM.stack.FlashTransport
1182
* FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
1183
* @namespace easyXDM.stack
1184
* @constructor
1185
* @param {Object} config The transports configuration.
1186
* @cfg {String} remote The remote domain to communicate with.
1187
* @cfg {String} secret the pre-shared secret used to secure the communication.
1188
* @cfg {String} swf The path to the swf file
1189
* @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden.
1190
* @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed
1191
*/
1192
var trace = debug.getTracer("easyXDM.stack.FlashTransport");
1193
trace("constructor");
1194
if (!config.swf) {
1195
throw new Error("Path to easyxdm.swf is missing");
1196
}
1197
var pub, // the public interface
1198
frame, send, targetOrigin, swf, swfContainer;
1199
1200
function onMessage(message, origin){
1201
setTimeout(function(){
1202
trace("received message");
1203
pub.up.incoming(message, targetOrigin);
1204
}, 0);
1205
}
1206
1207
/**
1208
* This method adds the SWF to the DOM and prepares the initialization of the channel
1209
*/
1210
function addSwf(domain){
1211
trace("creating factory with SWF from " + domain);
1212
// the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
1213
var url = config.swf + "?host=" + config.isHost;
1214
var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
1215
1216
// prepare the init function that will fire once the swf is ready
1217
easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){
1218
easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild;
1219
var queue = easyXDM.stack.FlashTransport[domain].queue;
1220
for (var i = 0; i < queue.length; i++) {
1221
queue[i]();
1222
}
1223
queue.length = 0;
1224
});
1225
1226
if (config.swfContainer) {
1227
swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer;
1228
}
1229
else {
1230
// create the container that will hold the swf
1231
swfContainer = document.createElement('div');
1232
1233
// http://bugs.adobe.com/jira/browse/FP-4796
1234
// http://tech.groups.yahoo.com/group/flexcoders/message/162365
1235
// https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc
1236
apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? {
1237
height: "20px",
1238
width: "20px",
1239
position: "fixed",
1240
right: 0,
1241
top: 0
1242
} : {
1243
height: "1px",
1244
width: "1px",
1245
position: "absolute",
1246
overflow: "hidden",
1247
right: 0,
1248
top: 0
1249
});
1250
document.body.appendChild(swfContainer);
1251
}
1252
1253
// create the object/embed
1254
var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace;
1255
flashVars += "&log=true";
1256
swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
1257
"<param name='allowScriptAccess' value='always'></param>" +
1258
"<param name='wmode' value='transparent'>" +
1259
"<param name='movie' value='" +
1260
url +
1261
"'></param>" +
1262
"<param name='flashvars' value='" +
1263
flashVars +
1264
"'></param>" +
1265
"<embed type='application/x-shockwave-flash' FlashVars='" +
1266
flashVars +
1267
"' allowScriptAccess='always' wmode='transparent' src='" +
1268
url +
1269
"' height='1' width='1'></embed>" +
1270
"</object>";
1271
}
1272
1273
return (pub = {
1274
outgoing: function(message, domain, fn){
1275
swf.postMessage(config.channel, message.toString());
1276
if (fn) {
1277
fn();
1278
}
1279
},
1280
destroy: function(){
1281
trace("destroy");
1282
try {
1283
swf.destroyChannel(config.channel);
1284
}
1285
catch (e) {
1286
}
1287
swf = null;
1288
if (frame) {
1289
frame.parentNode.removeChild(frame);
1290
frame = null;
1291
}
1292
},
1293
onDOMReady: function(){
1294
trace("init");
1295
1296
targetOrigin = config.remote;
1297
1298
// Prepare the code that will be run after the swf has been intialized
1299
easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
1300
setTimeout(function(){
1301
trace("firing onReady");
1302
pub.up.callback(true);
1303
});
1304
});
1305
1306
// set up the omMessage handler
1307
easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
1308
1309
config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths
1310
var swfdomain = getDomainName(config.swf);
1311
var fn = function(){
1312
// set init to true in case the fn was called was invoked from a separate instance
1313
easyXDM.stack.FlashTransport[swfdomain].init = true;
1314
swf = easyXDM.stack.FlashTransport[swfdomain].swf;
1315
// create the channel
1316
swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
1317
1318
if (config.isHost) {
1319
// if Flash is going to be throttled and we want to avoid this
1320
if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) {
1321
apply(config.props, {
1322
position: "fixed",
1323
right: 0,
1324
top: 0,
1325
height: "20px",
1326
width: "20px"
1327
});
1328
}
1329
// set up the iframe
1330
apply(config.props, {
1331
src: appendQueryParameters(config.remote, {
1332
xdm_e: getLocation(location.href),
1333
xdm_c: config.channel,
1334
xdm_p: 6, // 6 = FlashTransport
1335
xdm_s: config.secret
1336
}),
1337
name: IFRAME_PREFIX + config.channel + "_provider"
1338
});
1339
frame = createFrame(config);
1340
}
1341
};
1342
1343
if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) {
1344
// if the swf is in place and we are the consumer
1345
fn();
1346
}
1347
else {
1348
// if the swf does not yet exist
1349
if (!easyXDM.stack.FlashTransport[swfdomain]) {
1350
// add the queue to hold the init fn's
1351
easyXDM.stack.FlashTransport[swfdomain] = {
1352
queue: [fn]
1353
};
1354
addSwf(swfdomain);
1355
}
1356
else {
1357
easyXDM.stack.FlashTransport[swfdomain].queue.push(fn);
1358
}
1359
}
1360
},
1361
init: function(){
1362
whenReady(pub.onDOMReady, pub);
1363
}
1364
});
1365
* @class easyXDM.stack.PostMessageTransport
1366
* PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
1367
* <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
1368
* <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
1369
* @namespace easyXDM.stack
1370
* @constructor
1371
* @param {Object} config The transports configuration.
1372
* @cfg {String} remote The remote domain to communicate with.
1373
*/
1374
var trace = debug.getTracer("easyXDM.stack.PostMessageTransport");
1375
trace("constructor");
1376
var pub, // the public interface
1377
frame, // the remote frame, if any
1378
callerWindow, // the window that we will call with
1379
targetOrigin; // the domain to communicate with
1380
/**
1381
* Resolves the origin from the event object
1382
* @private
1383
* @param {Object} event The messageevent
1384
* @return {String} The scheme, host and port of the origin
1385
*/
1386
function _getOrigin(event){
1387
if (event.origin) {
1388
// This is the HTML5 property
1389
return getLocation(event.origin);
1390
}
1391
if (event.uri) {
1392
// From earlier implementations
1393
return getLocation(event.uri);
1394
}
1395
if (event.domain) {
1396
// This is the last option and will fail if the
1397
// origin is not using the same schema as we are
1398
return location.protocol + "//" + event.domain;
1399
}
1400
throw "Unable to retrieve the origin of the event";
1401
}
1402
1403
/**
1404
* This is the main implementation for the onMessage event.<br/>
1405
* It checks the validity of the origin and passes the message on if appropriate.
1406
* @private
1407
* @param {Object} event The messageevent
1408
*/
1409
function _window_onMessage(event){
1410
var origin = _getOrigin(event);
1411
trace("received message '" + event.data + "' from " + origin);
1412
if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
1413
pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
1414
}
1415
}
1416
1417
return (pub = {
1418
outgoing: function(message, domain, fn){
1419
callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
1420
if (fn) {
1421
fn();
1422
}
1423
},
1424
destroy: function(){
1425
trace("destroy");
1426
un(window, "message", _window_onMessage);
1427
if (frame) {
1428
callerWindow = null;
1429
frame.parentNode.removeChild(frame);
1430
frame = null;
1431
}
1432
},
1433
onDOMReady: function(){
1434
trace("init");
1435
targetOrigin = getLocation(config.remote);
1436
if (config.isHost) {
1437
// add the event handler for listening
1438
var waitForReady = function(event){
1439
if (event.data == config.channel + "-ready") {
1440
trace("firing onReady");
1441
// replace the eventlistener
1442
callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
1443
un(window, "message", waitForReady);
1444
on(window, "message", _window_onMessage);
1445
setTimeout(function(){
1446
pub.up.callback(true);
1447
}, 0);
1448
}
1449
};
1450
on(window, "message", waitForReady);
1451
1452
// set up the iframe
1453
apply(config.props, {
1454
src: appendQueryParameters(config.remote, {
1455
xdm_e: getLocation(location.href),
1456
xdm_c: config.channel,
1457
xdm_p: 1 // 1 = PostMessage
1458
}),
1459
name: IFRAME_PREFIX + config.channel + "_provider"
1460
});
1461
frame = createFrame(config);
1462
}
1463
else {
1464
// add the event handler for listening
1465
on(window, "message", _window_onMessage);
1466
callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
1467
callerWindow.postMessage(config.channel + "-ready", targetOrigin);
1468
1469
setTimeout(function(){
1470
pub.up.callback(true);
1471
}, 0);
1472
}
1473
},
1474
init: function(){
1475
whenReady(pub.onDOMReady, pub);
1476
}
1477
});
1478
* @class easyXDM.stack.FrameElementTransport
1479
* FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
1480
* Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
1481
* @namespace easyXDM.stack
1482
* @constructor
1483
* @param {Object} config The transports configuration.
1484
* @cfg {String} remote The remote document to communicate with.
1485
*/
1486
var trace = debug.getTracer("easyXDM.stack.FrameElementTransport");
1487
trace("constructor");
1488
var pub, frame, send, targetOrigin;
1489
1490
return (pub = {
1491
outgoing: function(message, domain, fn){
1492
send.call(this, message);
1493
if (fn) {
1494
fn();
1495
}
1496
},
1497
destroy: function(){
1498
trace("destroy");
1499
if (frame) {
1500
frame.parentNode.removeChild(frame);
1501
frame = null;
1502
}
1503
},
1504
onDOMReady: function(){
1505
trace("init");
1506
targetOrigin = getLocation(config.remote);
1507
1508
if (config.isHost) {
1509
// set up the iframe
1510
apply(config.props, {
1511
src: appendQueryParameters(config.remote, {
1512
xdm_e: getLocation(location.href),
1513
xdm_c: config.channel,
1514
xdm_p: 5 // 5 = FrameElementTransport
1515
}),
1516
name: IFRAME_PREFIX + config.channel + "_provider"
1517
});
1518
frame = createFrame(config);
1519
frame.fn = function(sendFn){
1520
delete frame.fn;
1521
send = sendFn;
1522
setTimeout(function(){
1523
pub.up.callback(true);
1524
}, 0);
1525
// remove the function so that it cannot be used to overwrite the send function later on
1526
return function(msg){
1527
pub.up.incoming(msg, targetOrigin);
1528
};
1529
};
1530
}
1531
else {
1532
// This is to mitigate origin-spoofing
1533
if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
1534
window.top.location = query.xdm_e;
1535
}
1536
send = window.frameElement.fn(function(msg){
1537
pub.up.incoming(msg, targetOrigin);
1538
});
1539
pub.up.callback(true);
1540
}
1541
},
1542
init: function(){
1543
whenReady(pub.onDOMReady, pub);
1544
}
1545
});
1546
* @class easyXDM.stack.NameTransport
1547
* NameTransport uses the window.name property to relay data.
1548
* The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
1549
* and the <code>remoteHelper</code> parameter needs to be set on the consumer.
1550
* @constructor
1551
* @param {Object} config The transports configuration.
1552
* @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
1553
* @namespace easyXDM.stack
1554
*/
1555
var trace = debug.getTracer("easyXDM.stack.NameTransport");
1556
trace("constructor");
1557
if (config.isHost && undef(config.remoteHelper)) {
1558
trace("missing remoteHelper");
1559
throw new Error("missing remoteHelper");
1560
}
1561
1562
var pub; // the public interface
1563
var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
1564
1565
function _sendMessage(message){
1566
var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
1567
trace("sending message " + message);
1568
trace("navigating to '" + url + "'");
1569
callerWindow.contentWindow.sendMessage(message, url);
1570
}
1571
1572
function _onReady(){
1573
if (isHost) {
1574
if (++readyCount === 2 || !isHost) {
1575
pub.up.callback(true);
1576
}
1577
}
1578
else {
1579
_sendMessage("ready");
1580
trace("calling onReady");
1581
pub.up.callback(true);
1582
}
1583
}
1584
1585
function _onMessage(message){
1586
trace("received message " + message);
1587
pub.up.incoming(message, remoteOrigin);
1588
}
1589
1590
function _onLoad(){
1591
if (callback) {
1592
setTimeout(function(){
1593
callback(true);
1594
}, 0);
1595
}
1596
}
1597
1598
return (pub = {
1599
outgoing: function(message, domain, fn){
1600
callback = fn;
1601
_sendMessage(message);
1602
},
1603
destroy: function(){
1604
trace("destroy");
1605
callerWindow.parentNode.removeChild(callerWindow);
1606
callerWindow = null;
1607
if (isHost) {
1608
remoteWindow.parentNode.removeChild(remoteWindow);
1609
remoteWindow = null;
1610
}
1611
},
1612
onDOMReady: function(){
1613
trace("init");
1614
isHost = config.isHost;
1615
readyCount = 0;
1616
remoteOrigin = getLocation(config.remote);
1617
config.local = resolveUrl(config.local);
1618
1619
if (isHost) {
1620
// Register the callback
1621
easyXDM.Fn.set(config.channel, function(message){
1622
trace("received initial message " + message);
1623
if (isHost && message === "ready") {
1624
// Replace the handler
1625
easyXDM.Fn.set(config.channel, _onMessage);
1626
_onReady();
1627
}
1628
});
1629
1630
// Set up the frame that points to the remote instance
1631
remoteUrl = appendQueryParameters(config.remote, {
1632
xdm_e: config.local,
1633
xdm_c: config.channel,
1634
xdm_p: 2
1635
});
1636
apply(config.props, {
1637
src: remoteUrl + '#' + config.channel,
1638
name: IFRAME_PREFIX + config.channel + "_provider"
1639
});
1640
remoteWindow = createFrame(config);
1641
}
1642
else {
1643
config.remoteHelper = config.remote;
1644
easyXDM.Fn.set(config.channel, _onMessage);
1645
}
1646
// Set up the iframe that will be used for the transport
1647
1648
callerWindow = createFrame({
1649
props: {
1650
src: config.local + "#_4" + config.channel
1651
},
1652
onLoad: function onLoad(){
1653
// Remove the handler
1654
var w = callerWindow || this;
1655
un(w, "load", onLoad);
1656
easyXDM.Fn.set(config.channel + "_load", _onLoad);
1657
(function test(){
1658
if (typeof w.contentWindow.sendMessage == "function") {
1659
_onReady();
1660
}
1661
else {
1662
setTimeout(test, 50);
1663
}
1664
}());
1665
}
1666
});
1667
},
1668
init: function(){
1669
whenReady(pub.onDOMReady, pub);
1670
}
1671
});
1672
* @class easyXDM.stack.HashTransport
1673
* HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
1674
* <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
1675
* @namespace easyXDM.stack
1676
* @constructor
1677
* @param {Object} config The transports configuration.
1678
* @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
1679
* @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
1680
* @cfg {Number} interval The interval used when polling for messages.
1681
*/
1682
var trace = debug.getTracer("easyXDM.stack.HashTransport");
1683
trace("constructor");
1684
var pub;
1685
var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
1686
var useParent, _remoteOrigin;
1687
1688
function _sendMessage(message){
1689
trace("sending message '" + (_msgNr + 1) + " " + message + "' to " + _remoteOrigin);
1690
if (!_callerWindow) {
1691
trace("no caller window");
1692
return;
1693
}
1694
var url = config.remote + "#" + (_msgNr++) + "_" + message;
1695
((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
1696
}
1697
1698
function _handleHash(hash){
1699
_lastMsg = hash;
1700
trace("received message '" + _lastMsg + "' from " + _remoteOrigin);
1701
pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
1702
}
1703
1704
/**
1705
* Checks location.hash for a new message and relays this to the receiver.
1706
* @private
1707
*/
1708
function _pollHash(){
1709
if (!_listenerWindow) {
1710
return;
1711
}
1712
var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
1713
if (indexOf != -1) {
1714
hash = href.substring(indexOf);
1715
}
1716
if (hash && hash != _lastMsg) {
1717
trace("poll: new message");
1718
_handleHash(hash);
1719
}
1720
}
1721
1722
function _attachListeners(){
1723
trace("starting polling");
1724
_timer = setInterval(_pollHash, pollInterval);
1725
}
1726
1727
return (pub = {
1728
outgoing: function(message, domain){
1729
_sendMessage(message);
1730
},
1731
destroy: function(){
1732
window.clearInterval(_timer);
1733
if (isHost || !useParent) {
1734
_callerWindow.parentNode.removeChild(_callerWindow);
1735
}
1736
_callerWindow = null;
1737
},
1738
onDOMReady: function(){
1739
isHost = config.isHost;
1740
pollInterval = config.interval;
1741
_lastMsg = "#" + config.channel;
1742
_msgNr = 0;
1743
useParent = config.useParent;
1744
_remoteOrigin = getLocation(config.remote);
1745
if (isHost) {
1746
config.props = {
1747
src: config.remote,
1748
name: IFRAME_PREFIX + config.channel + "_provider"
1749
};
1750
if (useParent) {
1751
config.onLoad = function(){
1752
_listenerWindow = window;
1753
_attachListeners();
1754
pub.up.callback(true);
1755
};
1756
}
1757
else {
1758
var tries = 0, max = config.delay / 50;
1759
(function getRef(){
1760
if (++tries > max) {
1761
trace("unable to get reference to _listenerWindow, giving up");
1762
throw new Error("Unable to reference listenerwindow");
1763
}
1764
try {
1765
_listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
1766
}
1767
catch (ex) {
1768
}
1769
if (_listenerWindow) {
1770
_attachListeners();
1771
trace("got a reference to _listenerWindow");
1772
pub.up.callback(true);
1773
}
1774
else {
1775
setTimeout(getRef, 50);
1776
}
1777
}());
1778
}
1779
_callerWindow = createFrame(config);
1780
}
1781
else {
1782
_listenerWindow = window;
1783
_attachListeners();
1784
if (useParent) {
1785
_callerWindow = parent;
1786
pub.up.callback(true);
1787
}
1788
else {
1789
apply(config, {
1790
props: {
1791
src: config.remote + "#" + config.channel + new Date(),
1792
name: IFRAME_PREFIX + config.channel + "_consumer"
1793
},
1794
onLoad: function(){
1795
pub.up.callback(true);
1796
}
1797
});
1798
_callerWindow = createFrame(config);
1799
}
1800
}
1801
},
1802
init: function(){
1803
whenReady(pub.onDOMReady, pub);
1804
}
1805
});
1806
* @class easyXDM.stack.ReliableBehavior
1807
* This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
1808
* @namespace easyXDM.stack
1809
* @constructor
1810
* @param {Object} config The behaviors configuration.
1811
*/
1812
var trace = debug.getTracer("easyXDM.stack.ReliableBehavior");
1813
trace("constructor");
1814
var pub, // the public interface
1815
callback; // the callback to execute when we have a confirmed success/failure
1816
var idOut = 0, idIn = 0, currentMessage = "";
1817
1818
return (pub = {
1819
incoming: function(message, origin){
1820
trace("incoming: " + message);
1821
var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
1822
message = message.substring(indexOf + 1);
1823
1824
if (ack[0] == idOut) {
1825
trace("message delivered");
1826
currentMessage = "";
1827
if (callback) {
1828
callback(true);
1829
}
1830
}
1831
if (message.length > 0) {
1832
trace("sending ack, and passing on " + message);
1833
pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
1834
if (idIn != ack[1]) {
1835
idIn = ack[1];
1836
pub.up.incoming(message, origin);
1837
}
1838
}
1839
1840
},
1841
outgoing: function(message, origin, fn){
1842
currentMessage = message;
1843
callback = fn;
1844
pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
1845
}
1846
});
1847
* @class easyXDM.stack.QueueBehavior
1848
* This is a behavior that enables queueing of messages. <br/>
1849
* It will buffer incoming messages and dispach these as fast as the underlying transport allows.
1850
* This will also fragment/defragment messages so that the outgoing message is never bigger than the
1851
* set length.
1852
* @namespace easyXDM.stack
1853
* @constructor
1854
* @param {Object} config The behaviors configuration. Optional.
1855
* @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
1856
*/
1857
var trace = debug.getTracer("easyXDM.stack.QueueBehavior");
1858
trace("constructor");
1859
var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
1860
1861
function dispatch(){
1862
if (config.remove && queue.length === 0) {
1863
trace("removing myself from the stack");
1864
removeFromStack(pub);
1865
return;
1866
}
1867
if (waiting || queue.length === 0 || destroying) {
1868
return;
1869
}
1870
trace("dispatching from queue");
1871
waiting = true;
1872
var message = queue.shift();
1873
1874
pub.down.outgoing(message.data, message.origin, function(success){
1875
waiting = false;
1876
if (message.callback) {
1877
setTimeout(function(){
1878
message.callback(success);
1879
}, 0);
1880
}
1881
dispatch();
1882
});
1883
}
1884
return (pub = {
1885
init: function(){
1886
if (undef(config)) {
1887
config = {};
1888
}
1889
if (config.maxLength) {
1890
maxLength = config.maxLength;
1891
doFragment = true;
1892
}
1893
if (config.lazy) {
1894
lazy = true;
1895
}
1896
else {
1897
pub.down.init();
1898
}
1899
},
1900
callback: function(success){
1901
waiting = false;
1902
var up = pub.up; // in case dispatch calls removeFromStack
1903
dispatch();
1904
up.callback(success);
1905
},
1906
incoming: function(message, origin){
1907
if (doFragment) {
1908
var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
1909
incoming += message.substring(indexOf + 1);
1910
if (seq === 0) {
1911
trace("received the last fragment");
1912
if (config.encode) {
1913
incoming = decodeURIComponent(incoming);
1914
}
1915
pub.up.incoming(incoming, origin);
1916
incoming = "";
1917
}
1918
else {
1919
trace("waiting for more fragments, seq=" + message);
1920
}
1921
}
1922
else {
1923
pub.up.incoming(message, origin);
1924
}
1925
},
1926
outgoing: function(message, origin, fn){
1927
if (config.encode) {
1928
message = encodeURIComponent(message);
1929
}
1930
var fragments = [], fragment;
1931
if (doFragment) {
1932
// fragment into chunks
1933
while (message.length !== 0) {
1934
fragment = message.substring(0, maxLength);
1935
message = message.substring(fragment.length);
1936
fragments.push(fragment);
1937
}
1938
// enqueue the chunks
1939
while ((fragment = fragments.shift())) {
1940
trace("enqueuing");
1941
queue.push({
1942
data: fragments.length + "_" + fragment,
1943
origin: origin,
1944
callback: fragments.length === 0 ? fn : null
1945
});
1946
}
1947
}
1948
else {
1949
queue.push({
1950
data: message,
1951
origin: origin,
1952
callback: fn
1953
});
1954
}
1955
if (lazy) {
1956
pub.down.init();
1957
}
1958
else {
1959
dispatch();
1960
}
1961
},
1962
destroy: function(){
1963
trace("destroy");
1964
destroying = true;
1965
pub.down.destroy();
1966
}
1967
});
1968
* @class easyXDM.stack.VerifyBehavior
1969
* This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
1970
* and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
1971
* @namespace easyXDM.stack
1972
* @constructor
1973
* @param {Object} config The behaviors configuration.
1974
* @cfg {Boolean} initiate If the verification should be initiated from this end.
1975
*/
1976
var trace = debug.getTracer("easyXDM.stack.VerifyBehavior");
1977
trace("constructor");
1978
if (undef(config.initiate)) {
1979
throw new Error("settings.initiate is not set");
1980
}
1981
var pub, mySecret, theirSecret, verified = false;
1982
1983
function startVerification(){
1984
trace("requesting verification");
1985
mySecret = Math.random().toString(16).substring(2);
1986
pub.down.outgoing(mySecret);
1987
}
1988
1989
return (pub = {
1990
incoming: function(message, origin){
1991
var indexOf = message.indexOf("_");
1992
if (indexOf === -1) {
1993
if (message === mySecret) {
1994
trace("verified, calling callback");
1995
pub.up.callback(true);
1996
}
1997
else if (!theirSecret) {
1998
trace("returning secret");
1999
theirSecret = message;
2000
if (!config.initiate) {
2001
startVerification();
2002
}
2003
pub.down.outgoing(message);
2004
}
2005
}
2006
else {
2007
if (message.substring(0, indexOf) === theirSecret) {
2008
pub.up.incoming(message.substring(indexOf + 1), origin);
2009
}
2010
}
2011
},
2012
outgoing: function(message, origin, fn){
2013
pub.down.outgoing(mySecret + "_" + message, origin, fn);
2014
},
2015
callback: function(success){
2016
if (config.initiate) {
2017
startVerification();
2018
}
2019
}
2020
});
2021
* @class easyXDM.stack.RpcBehavior
2022
* This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
2023
* Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
2024
* @namespace easyXDM.stack
2025
* @constructor
2026
* @param {Object} proxy The object to apply the methods to.
2027
* @param {Object} config The definition of the local and remote interface to implement.
2028
* @cfg {Object} local The local interface to expose.
2029
* @cfg {Object} remote The remote methods to expose through the proxy.
2030
* @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
2031
*/
2032
var trace = debug.getTracer("easyXDM.stack.RpcBehavior");
2033
var pub, serializer = config.serializer || getJSON();
2034
var _callbackCounter = 0, _callbacks = {};
2035
2036
/**
2037
* Serializes and sends the message
2038
* @private
2039
* @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
2040
*/
2041
function _send(data){
2042
data.jsonrpc = "2.0";
2043
pub.down.outgoing(serializer.stringify(data));
2044
}
2045
2046
/**
2047
* Creates a method that implements the given definition
2048
* @private
2049
* @param {Object} The method configuration
2050
* @param {String} method The name of the method
2051
* @return {Function} A stub capable of proxying the requested method call
2052
*/
2053
function _createMethod(definition, method){
2054
var slice = Array.prototype.slice;
2055
2056
trace("creating method " + method);
2057
return function(){
2058
trace("executing method " + method);
2059
var l = arguments.length, callback, message = {
2060
method: method
2061
};
2062
2063
if (l > 0 && typeof arguments[l - 1] === "function") {
2064
//with callback, procedure
2065
if (l > 1 && typeof arguments[l - 2] === "function") {
2066
// two callbacks, success and error
2067
callback = {
2068
success: arguments[l - 2],
2069
error: arguments[l - 1]
2070
};
2071
message.params = slice.call(arguments, 0, l - 2);
2072
}
2073
else {
2074
// single callback, success
2075
callback = {
2076
success: arguments[l - 1]
2077
};
2078
message.params = slice.call(arguments, 0, l - 1);
2079
}
2080
_callbacks["" + (++_callbackCounter)] = callback;
2081
message.id = _callbackCounter;
2082
}
2083
else {
2084
// no callbacks, a notification
2085
message.params = slice.call(arguments, 0);
2086
}
2087
if (definition.namedParams && message.params.length === 1) {
2088
message.params = message.params[0];
2089
}
2090
// Send the method request
2091
_send(message);
2092
};
2093
}
2094
2095
/**
2096
* Executes the exposed method
2097
* @private
2098
* @param {String} method The name of the method
2099
* @param {Number} id The callback id to use
2100
* @param {Function} method The exposed implementation
2101
* @param {Array} params The parameters supplied by the remote end
2102
*/
2103
function _executeMethod(method, id, fn, params){
2104
if (!fn) {
2105
trace("requested to execute non-existent procedure " + method);
2106
if (id) {
2107
_send({
2108
id: id,
2109
error: {
2110
code: -32601,
2111
message: "Procedure not found."
2112
}
2113
});
2114
}
2115
return;
2116
}
2117
2118
trace("requested to execute procedure " + method);
2119
var success, error;
2120
if (id) {
2121
success = function(result){
2122
success = emptyFn;
2123
_send({
2124
id: id,
2125
result: result
2126
});
2127
};
2128
error = function(message, data){
2129
error = emptyFn;
2130
var msg = {
2131
id: id,
2132
error: {
2133
code: -32099,
2134
message: message
2135
}
2136
};
2137
if (data) {
2138
msg.error.data = data;
2139
}
2140
_send(msg);
2141
};
2142
}
2143
else {
2144
success = error = emptyFn;
2145
}
2146
// Call local method
2147
if (!isArray(params)) {
2148
params = [params];
2149
}
2150
try {
2151
var result = fn.method.apply(fn.scope, params.concat([success, error]));
2152
if (!undef(result)) {
2153
success(result);
2154
}
2155
}
2156
catch (ex1) {
2157
error(ex1.message);
2158
}
2159
}
2160
2161
return (pub = {
2162
incoming: function(message, origin){
2163
var data = serializer.parse(message);
2164
if (data.method) {
2165
trace("received request to execute method " + data.method + (data.id ? (" using callback id " + data.id) : ""));
2166
// A method call from the remote end
2167
if (config.handle) {
2168
config.handle(data, _send);
2169
}
2170
else {
2171
_executeMethod(data.method, data.id, config.local[data.method], data.params);
2172
}
2173
}
2174
else {
2175
trace("received return value destined to callback with id " + data.id);
2176
// A method response from the other end
2177
var callback = _callbacks[data.id];
2178
if (data.error) {
2179
if (callback.error) {
2180
callback.error(data.error);
2181
}
2182
else {
2183
trace("unhandled error returned.");
2184
}
2185
}
2186
else if (callback.success) {
2187
callback.success(data.result);
2188
}
2189
delete _callbacks[data.id];
2190
}
2191
},
2192
init: function(){
2193
trace("init");
2194
if (config.remote) {
2195
trace("creating stubs");
2196
// Implement the remote sides exposed methods
2197
for (var method in config.remote) {
2198
if (config.remote.hasOwnProperty(method)) {
2199
proxy[method] = _createMethod(config.remote[method], method);
2200
}
2201
}
2202
}
2203
pub.down.init();
2204
},
2205
destroy: function(){
2206
trace("destroy");
2207
for (var method in config.remote) {
2208
if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
2209
delete proxy[method];
2210
}
2211
}
2212
pub.down.destroy();
2213
}
2214
});
2215
var t = typeof object[property];
2216
return t == 'function' ||
2217
(!!(t == 'object' && object[property])) ||
2218
t == 'unknown';
2219
return !!(typeof(object[property]) == 'object' && object[property]);
2220
return Object.prototype.toString.call(o) === '[object Array]';
2221
var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash";
2222
if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") {
2223
// adapted from the swfobject code
2224
var description = navigator.plugins[name].description;
2225
if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) {
2226
flashVersion = description.match(/\d+/g);
2227
}
2228
}
2229
if (!flashVersion) {
2230
var flash;
2231
try {
2232
flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
2233
flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
2234
flash = null;
2235
}
2236
catch (notSupportedException) {
2237
}
2238
}
2239
if (!flashVersion) {
2240
return false;
2241
}
2242
var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10);
2243
HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0;
2244
return true;
2245
* Cross Browser implementation for adding and removing event listeners.
2246
*/
2247
on = function(target, type, listener){
2248
target.addEventListener(type, listener, false);
2249
};
2250
un = function(target, type, listener){
2251
target.removeEventListener(type, listener, false);
2252
};
2253
on = function(object, sEvent, fpNotify){
2254
object.attachEvent("on" + sEvent, fpNotify);
2255
};
2256
un = function(object, sEvent, fpNotify){
2257
object.detachEvent("on" + sEvent, fpNotify);
2258
};
2259
throw new Error("Browser not supported");
2260
* Cross Browser implementation of DOMContentLoaded.
2261
*/
2262
// If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
2263
// 'interactive' (HTML5 specs, recent WebKit builds) states.
2264
// https://bugs.webkit.org/show_bug.cgi?id=45119
2265
readyState = document.readyState;
2266
domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
2267
// If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
2268
// when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
2269
// We only need a body to add elements to, so the existence of document.body is enough for us.
2270
domIsReady = !!document.body;
2271
if (domIsReady) {
2272
return;
2273
}
2274
domIsReady = true;
2275
for (var i = 0; i < domReadyQueue.length; i++) {
2276
domReadyQueue[i]();
2277
}
2278
domReadyQueue.length = 0;
2279
if (isHostMethod(window, "addEventListener")) {
2280
on(document, "DOMContentLoaded", dom_onReady);
2281
}
2282
else {
2283
on(document, "readystatechange", function(){
2284
if (document.readyState == "complete") {
2285
dom_onReady();
2286
}
2287
});
2288
if (document.documentElement.doScroll && window === top) {
2289
var doScrollCheck = function(){
2290
if (domIsReady) {
2291
return;
2292
}
2293
// http://javascript.nwbox.com/IEContentLoaded/
2294
try {
2295
document.documentElement.doScroll("left");
2296
}
2297
catch (e) {
2298
setTimeout(doScrollCheck, 1);
2299
return;
2300
}
2301
dom_onReady();
2302
};
2303
doScrollCheck();
2304
}
2305
}
2306
// A fallback to window.onload, that will always work
2307
on(window, "load", dom_onReady);
2308
* This will add a function to the queue of functions to be run once the DOM reaches a ready state.
2309
* If functions are added after this event then they will be executed immediately.
2310
* @param {function} fn The function to add
2311
* @param {Object} scope An optional scope for the function to be called with.
2312
*/
2313
if (domIsReady) {
2314
fn.call(scope);
2315
return;
2316
}
2317
domReadyQueue.push(function(){
2318
fn.call(scope);
2319
});
2320
* Returns an instance of easyXDM from the parent window with
2321
* respect to the namespace.
2322
*
2323
* @return An instance of easyXDM (in the parent window)
2324
*/
2325
var obj = parent;
2326
if (namespace !== "") {
2327
for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
2328
obj = obj[ii[i]];
2329
}
2330
}
2331
return obj.easyXDM;
2332
* Removes easyXDM variable from the global scope. It also returns control
2333
* of the easyXDM variable to whatever code used it before.
2334
*
2335
* @param {String} ns A string representation of an object that will hold
2336
* an instance of easyXDM.
2337
* @return An instance of easyXDM
2338
*/
2339
window.easyXDM = _easyXDM;
2340
namespace = ns;
2341
if (namespace) {
2342
IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
2343
}
2344
return easyXDM;
2345
* Methods for working with URLs
2346
*/
2347
* Get the domain name from a url.
2348
* @param {String} url The url to extract the domain from.
2349
* @return The domain part of the url.
2350
* @type {String}
2351
*/
2352
return url.match(reURI)[3];
2353
* Get the port for a given URL, or "" if none
2354
* @param {String} url The url to extract the port from.
2355
* @return The port part of the url.
2356
* @type {String}
2357
*/
2358
return url.match(reURI)[4] || "";
2359
* Returns a string containing the schema, domain and if present the port
2360
* @param {String} url The url to extract the location from
2361
* @return {String} The location part of the url
2362
*/
2363
var m = url.toLowerCase().match(reURI);
2364
var proto = m[2], domain = m[3], port = m[4] || "";
2365
if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
2366
port = "";
2367
}
2368
return proto + "//" + domain + port;
2369
* Resolves a relative url into an absolute one.
2370
* @param {String} url The path to resolve.
2371
* @return {String} The resolved url.
2372
*/
2373
// replace all // except the one in proto with /
2374
url = url.replace(reDoubleSlash, "$1/");
2375
// If the url is a valid url we do nothing
2376
if (!url.match(/^(http||https):\/\//)) {
2377
// If this is a relative path
2378
var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
2379
if (path.substring(path.length - 1) !== "/") {
2380
path = path.substring(0, path.lastIndexOf("/") + 1);
2381
}
2382
url = location.protocol + "//" + location.host + path + url;
2383
}
2384
// reduce all 'xyz/../' to just ''
2385
while (reParent.test(url)) {
2386
url = url.replace(reParent, "");
2387
}
2388
return url;
2389
* Appends the parameters to the given url.<br/>
2390
* The base url can contain existing query parameters.
2391
* @param {String} url The base url.
2392
* @param {Object} parameters The parameters to add.
2393
* @return {String} A new valid url with the parameters appended.
2394
*/
2395
var hash = "", indexOf = url.indexOf("#");
2396
if (indexOf !== -1) {
2397
hash = url.substring(indexOf);
2398
url = url.substring(0, indexOf);
2399
}
2400
var q = [];
2401
for (var key in parameters) {
2402
if (parameters.hasOwnProperty(key)) {
2403
q.push(key + "=" + encodeURIComponent(parameters[key]));
2404
}
2405
}
2406
return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
2407
input = input.substring(1).split("&");
2408
var data = {}, pair, i = input.length;
2409
while (i--) {
2410
pair = input[i].split("=");
2411
data[pair[0]] = decodeURIComponent(pair[1]);
2412
}
2413
return data;
2414
* Helper methods
2415
*/
2416
* Helper for checking if a variable/property is undefined
2417
* @param {Object} v The variable to test
2418
* @return {Boolean} True if the passed variable is undefined
2419
*/
2420
return typeof v === "undefined";
2421
* A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
2422
* @return {JSON} A valid JSON conforming object, or null if not found.
2423
*/
2424
var cached = {};
2425
var obj = {
2426
a: [1, 2, 3]
2427
}, json = "{\"a\":[1,2,3]}";
2428
if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
2429
// this is a working JSON instance
2430
return JSON;
2431
}
2432
if (Object.toJSON) {
2433
if (Object.toJSON(obj).replace((/\s/g), "") === json) {
2434
// this is a working stringify method
2435
cached.stringify = Object.toJSON;
2436
}
2437
}
2438
if (typeof String.prototype.evalJSON === "function") {
2439
obj = json.evalJSON();
2440
if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
2441
// this is a working parse method
2442
cached.parse = function(str){
2443
return str.evalJSON();
2444
};
2445
}
2446
}
2447
if (cached.stringify && cached.parse) {
2448
// Only memoize the result if we have valid instance
2449
getJSON = function(){
2450
return cached;
2451
};
2452
return cached;
2453
}
2454
return null;
2455
* Applies properties from the source object to the target object.<br/>
2456
* @param {Object} target The target of the properties.
2457
* @param {Object} source The source of the properties.
2458
* @param {Boolean} noOverwrite Set to True to only set non-existing properties.
2459
*/
2460
var member;
2461
for (var prop in source) {
2462
if (source.hasOwnProperty(prop)) {
2463
if (prop in destination) {
2464
member = source[prop];
2465
if (typeof member === "object") {
2466
apply(destination[prop], member, noOverwrite);
2467
}
2468
else if (!noOverwrite) {
2469
destination[prop] = source[prop];
2470
}
2471
}
2472
else {
2473
destination[prop] = source[prop];
2474
}
2475
}
2476
}
2477
return destination;
2478
var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
2479
input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
2480
HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
2481
document.body.removeChild(form);
2482
* Creates a frame and appends it to the DOM.
2483
* @param config {object} This object can have the following properties
2484
* <ul>
2485
* <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
2486
* <li> {object} attr The attributes that should be set on the frame.</li>
2487
* <li> {DOMElement} container Its parent element (Optional).</li>
2488
* <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
2489
* </ul>
2490
* @return The frames DOMElement
2491
* @type DOMElement
2492
*/
2493
if (undef(HAS_NAME_PROPERTY_BUG)) {
2494
testForNamePropertyBug();
2495
}
2496
var frame;
2497
// This is to work around the problems in IE6/7 with setting the name property.
2498
// Internally this is set as 'submitName' instead when using 'iframe.name = ...'
2499
// This is not required by easyXDM itself, but is to facilitate other use cases
2500
if (HAS_NAME_PROPERTY_BUG) {
2501
frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
2502
}
2503
else {
2504
frame = document.createElement("IFRAME");
2505
frame.name = config.props.name;
2506
}
2507
frame.id = frame.name = config.props.name;
2508
delete config.props.name;
2509
if (typeof config.container == "string") {
2510
config.container = document.getElementById(config.container);
2511
}
2512
if (!config.container) {
2513
// This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
2514
apply(frame.style, {
2515
position: "absolute",
2516
top: "-2000px",
2517
// Avoid potential horizontal scrollbar
2518
left: "0px"
2519
});
2520
config.container = document.body;
2521
}
2522
// HACK: IE cannot have the src attribute set when the frame is appended
2523
// into the container, so we set it to "javascript:false" as a
2524
// placeholder for now. If we left the src undefined, it would
2525
// instead default to "about:blank", which causes SSL mixed-content
2526
// warnings in IE6 when on an SSL parent page.
2527
var src = config.props.src;
2528
config.props.src = "javascript:false";
2529
// transfer properties to the frame
2530
apply(frame, config.props);
2531
frame.border = frame.frameBorder = 0;
2532
frame.allowTransparency = true;
2533
config.container.appendChild(frame);
2534
if (config.onLoad) {
2535
on(frame, "load", config.onLoad);
2536
}
2537
// set the frame URL to the proper value (we previously set it to
2538
// "javascript:false" to work around the IE issue mentioned above)
2539
if(config.usePost) {
2540
var form = config.container.appendChild(document.createElement('form')), input;
2541
form.target = frame.name;
2542
form.action = src;
2543
form.method = 'POST';
2544
if (typeof(config.usePost) === 'object') {
2545
for (var i in config.usePost) {
2546
if (config.usePost.hasOwnProperty(i)) {
2547
if (HAS_NAME_PROPERTY_BUG) {
2548
input = document.createElement('<input name="' + i + '"/>');
2549
} else {
2550
input = document.createElement("INPUT");
2551
input.name = i;
2552
}
2553
input.value = config.usePost[i];
2554
form.appendChild(input);
2555
}
2556
}
2557
}
2558
form.submit();
2559
form.parentNode.removeChild(form);
2560
} else {
2561
frame.src = src;
2562
}
2563
config.props.src = src;
2564
return frame;
2565
* Check whether a domain is allowed using an Access Control List.
2566
* The ACL can contain * and ? as wildcards, or can be regular expressions.
2567
* If regular expressions they need to begin with ^ and end with $.
2568
* @param {Array/String} acl The list of allowed domains
2569
* @param {String} domain The domain to test.
2570
* @return {Boolean} True if the domain is allowed, false if not.
2571
*/
2572
// normalize into an array
2573
if (typeof acl == "string") {
2574
acl = [acl];
2575
}
2576
var re, i = acl.length;
2577
while (i--) {
2578
re = acl[i];
2579
re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "quot;));
2580
if (re.test(domain)) {
2581
return true;
2582
}
2583
}
2584
return false;
2585
* Functions related to stacks
2586
*/
2587
* Prepares an array of stack-elements suitable for the current configuration
2588
* @param {Object} config The Transports configuration. See easyXDM.Socket for more.
2589
* @return {Array} An array of stack-elements with the TransportElement at index 0.
2590
*/
2591
var protocol = config.protocol, stackEls;
2592
config.isHost = config.isHost || undef(query.xdm_p);
2593
useHash = config.hash || false;
2594
if (!config.props) {
2595
config.props = {};
2596
}
2597
if (!config.isHost) {
2598
config.channel = query.xdm_c.replace(/["'<>\\]/g, "");
2599
config.secret = query.xdm_s;
2600
config.remote = query.xdm_e.replace(/["'<>\\]/g, "");
2601
;
2602
protocol = query.xdm_p;
2603
if (config.acl && !checkAcl(config.acl, config.remote)) {
2604
throw new Error("Access denied for " + config.remote);
2605
}
2606
}
2607
else {
2608
config.remote = resolveUrl(config.remote);
2609
config.channel = config.channel || "default" + channelId++;
2610
config.secret = Math.random().toString(16).substring(2);
2611
if (undef(protocol)) {
2612
if (getLocation(location.href) == getLocation(config.remote)) {
2613
/*
2614
* Both documents has the same origin, lets use direct access.
2615
*/
2616
protocol = "4";
2617
}
2618
else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
2619
/*
2620
* This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
2621
*/
2622
protocol = "1";
2623
}
2624
else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
2625
/*
2626
* The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
2627
*/
2628
protocol = "6";
2629
}
2630
else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
2631
/*
2632
* This is supported in Gecko (Firefox 1+)
2633
*/
2634
protocol = "5";
2635
}
2636
else if (config.remoteHelper) {
2637
/*
2638
* This is supported in all browsers that retains the value of window.name when
2639
* navigating from one domain to another, and where parent.frames[foo] can be used
2640
* to get access to a frame from the same domain
2641
*/
2642
protocol = "2";
2643
}
2644
else {
2645
/*
2646
* This is supported in all browsers where [window].location is writable for all
2647
* The resize event will be used if resize is supported and the iframe is not put
2648
* into a container, else polling will be used.
2649
*/
2650
protocol = "0";
2651
}
2652
}
2653
}
2654
config.protocol = protocol; // for conditional branching
2655
switch (protocol) {
2656
case "0":// 0 = HashTransport
2657
apply(config, {
2658
interval: 100,
2659
delay: 2000,
2660
useResize: true,
2661
useParent: false,
2662
usePolling: false
2663
}, true);
2664
if (config.isHost) {
2665
if (!config.local) {
2666
// If no local is set then we need to find an image hosted on the current domain
2667
var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
2668
var i = images.length;
2669
while (i--) {
2670
image = images[i];
2671
if (image.src.substring(0, domain.length) === domain) {
2672
config.local = image.src;
2673
break;
2674
}
2675
}
2676
if (!config.local) {
2677
// If no local was set, and we are unable to find a suitable file, then we resort to using the current window
2678
config.local = window;
2679
}
2680
}
2681
var parameters = {
2682
xdm_c: config.channel,
2683
xdm_p: 0
2684
};
2685
if (config.local === window) {
2686
// We are using the current window to listen to
2687
config.usePolling = true;
2688
config.useParent = true;
2689
config.local = location.protocol + "//" + location.host + location.pathname + location.search;
2690
parameters.xdm_e = config.local;
2691
parameters.xdm_pa = 1; // use parent
2692
}
2693
else {
2694
parameters.xdm_e = resolveUrl(config.local);
2695
}
2696
if (config.container) {
2697
config.useResize = false;
2698
parameters.xdm_po = 1; // use polling
2699
}
2700
config.remote = appendQueryParameters(config.remote, parameters);
2701
}
2702
else {
2703
apply(config, {
2704
channel: query.xdm_c,
2705
remote: query.xdm_e,
2706
useParent: !undef(query.xdm_pa),
2707
usePolling: !undef(query.xdm_po),
2708
useResize: config.useParent ? false : config.useResize
2709
});
2710
}
2711
stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({