mraid-webtester
Version:
MRAID Web Tester
881 lines (801 loc) • 29.6 kB
JavaScript
/*
* Copyright (c) 2012 The mraid-web-tester project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
(function(window) {
var mraid = window.mraid = {};
// CONSTANTS ///////////////////////////////////////////////////////////////
var VERSIONS = mraid.VERSIONS = {
UNKNOWN : '0.0',
V1 : '1.0',
V2 : '2.0'
};
var PLACEMENTS = mraid.PLACEMENTS = {
UNKNOWN : 'unknown',
INLINE : 'inline',
INTERSTITIAL : 'interstitial'
};
var ORIENTATIONS = mraid.ORIENTATIONS = {
NONE : 'none',
PORTRAIT : 'portrait',
LANDSCAPE : 'landscape'
};
var CLOSEPOSITIONS = mraid.CLOSEPOSITIONS = {
TOPLEFT : 'top-left',
TOPRIGHT : 'top-right',
TOPCENTER : 'top-center',
BOTTOMLEFT : 'bottom-left',
BOTTOMRIGHT : 'bottom-right',
BOTTOMCENTER: 'bottom-center',
CENTER : 'center'
};
var STATES = mraid.STATES = {
UNKNOWN :'unknown',
LOADING :'loading',
DEFAULT :'default',
RESIZED :'resized',
EXPANDED :'expanded',
HIDDEN :'hidden'
};
var EVENTS = mraid.EVENTS = {
INFO :'info',
ORIENTATIONCHANGE :'orientationChange',
READY :'ready',
ERROR :'error',
STATECHANGE :'stateChange',
VIEWABLECHANGE :'viewableChange',
SIZECHANGE :'sizeChange',
};
var FEATURES = mraid.FEATURES = {
SMS :'sms',
TEL :'tel',
CALENDAR :'calendar',
STOREPICTURE:'storePicture',
INLINEVIDEO :'inlineVideo'
};
// PRIVATE PROPERTIES (sdk controlled) //////////////////////////////////////////////////////
var state = STATES.UNKNOWN;
var placementType = PLACEMENTS.UNKNOWN;
var size = {
width:0,
height:0
};
var defaultPosition = {
x:0,
y:0,
width:0,
height:0
};
var currentPosition = {
x:0,
y:0,
width:0,
height:0
};
var maxSize = {
width:0,
height:0
};
var expandProperties = {
width:0,
height:0,
useCustomClose:false,
isModal:true
};
var resizeProperties = {
initialized: false,
validated: false,
width: 0,
height: 0,
customClosePosition: CLOSEPOSITIONS.TOPRIGHT,
offsetX: undefined,
offsetY: undefined,
allowOffscreen: true
};
var orientationProperties = {
allowOrientationChange: true,
forceOrientation: ORIENTATIONS.NONE
};
var supports = {
'sms':true,
'tel':true,
'email':true,
'calendar':true,
'storePicture':true,
'inlineVideo':true,
'orientation':true
};
var orientation = -1;
var mraidVersion = VERSIONS.UNKNOWN;
var screenSize = null;
var isViewable = false;
// PRIVATE PROPERTIES (internal) //////////////////////////////////////////////////////
var intervalID = null;
var dimensionValidators = {
x: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
}
return ret;
},
y: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
}
return ret;
},
width: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
height: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
}
};
var sizeValidators = {
width: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
height: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
}
};
var expandPropertyValidators = {
isModal: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
useCustomClose: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
width: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
height: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 0)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
allowOrientationChange: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
forceOrientation: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value in ORIENTATIONS)) {
ret.value = false;
ret.msg = 'not a valid option';
}
return ret;
}
};
var resizePropertyValidators = {
width: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 50)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
height: function (value) {
var ret = { 'value': true, 'msg': ''};
if (isNaN(value)) {
ret.value = false;
ret.msg = 'not a number';
} else if (!(value >= 50)) {
ret.value = false;
ret.msg = 'too small';
}
return ret;
},
offsetX: function (value) {
return {'value': (!isNaN(value)), 'msg': (!isNaN(value) ? '' : 'not a number')};
},
offsetY: function (value) {
return {'value': (!isNaN(value)), 'msg': (!isNaN(value) ? '' : 'not a number')};
},
allowOffscreen: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
customClosePosition: function (value) {
var ret = { 'value': true, 'msg': ''};
for (a in CLOSEPOSITIONS) {
if (value === CLOSEPOSITIONS[a]) {
return ret;;
}
}
ret.value = false;
ret.msg = 'not a valid option';
return ret;
},
initialized: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
validated: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(value === true || value === false)) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
}
};
var orientationPropertyValidators = {
allowOrientationChange: function (value) {
var ret = { 'value': true, 'msg': ''};
if (!(typeof value === 'boolean')) {
ret.value = false;
ret.msg = 'not a valid type';
}
return ret;
},
forceOrientation: function (value) {
var ret = { 'value': true, 'msg': ''};
for (a in ORIENTATIONS) {
if (value === ORIENTATIONS[a]) {
return ret;
}
}
ret.value = false;
ret.msg = 'not a valid option';
return ret;
}
};
var changeHandlers = {
version:function(val) {
mraidVersion = val;
},
placement:function(val){
placementType = val;
},
state:function(val) {
console.log('state listener. state='+state+':new='+val);
if (state == STATES.UNKNOWN && val != STATES.UNKNOWN) {
broadcastEvent(EVENTS.INFO, 'controller initialized');
}
if (state == STATES.LOADING && val != STATES.LOADING) {
mraid.signalReady();
} else {
broadcastEvent(EVENTS.INFO, 'setting state to ' + stringify(val));
state = val;
broadcastEvent(EVENTS.STATECHANGE, state);
}
},
size:function(val) {
broadcastEvent(EVENTS.INFO, 'setting size to ' + stringify(val));
size = val;
broadcastEvent(EVENTS.SIZECHANGE, size.width, size.height);
},
defaultPosition:function(val) {
broadcastEvent(EVENTS.INFO, 'setting default position to ' + stringify(val));
defaultPosition = val;
},
currentPosition:function(val) {
broadcastEvent(EVENTS.INFO, 'setting current position to ' + stringify(val));
currentPosition = val;
},
maxSize:function(val) {
broadcastEvent(EVENTS.INFO, 'setting maxSize to ' + stringify(val));
maxSize = val;
},
expandProperties:function(val) {
broadcastEvent(EVENTS.INFO, 'merging expandProperties with ' + stringify(val));
for (var i in val) {
expandProperties[i] = val[i];
}
},
resizeProperties:function(val) {
broadcastEvent(EVENTS.INFO, 'merging resizeProperties with ' + stringify(val));
for (var i in val) {
resizeProperties[i] = val[i];
}
},
supports:function(val) {
broadcastEvent(EVENTS.INFO, 'setting supports to ' + stringify(val));
supports = {};
for (var key in FEATURES) {
supports[FEATURES[key]] = contains(FEATURES[key], val);
}
},
orientation:function(val) {
broadcastEvent(EVENTS.INFO, 'setting orientation to ' + stringify(val));
orientation = val;
broadcastEvent(EVENTS.ORIENTATIONCHANGE, orientation);
},
screenSize:function(val) {
broadcastEvent(EVENTS.INFO, 'setting screenSize to ' + stringify(val));
screenSize = val;
broadcastEvent(EVENTS.SCREENCHANGE, screenSize.width, screenSize.height);
},
isViewable:function(val) {
broadcastEvent(EVENTS.INFO, 'setting isViewable to ' + stringify(val));
isViewable = val;
broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable);
},
orientationProperties:function(val) {
broadcastEvent(EVENTS.INFO, 'setting orientationProperties to ' + stringify(val));
for (var i in val) {
orientationProperties[i] = val[i];
}
}
};
var listeners = {};
var EventListeners = function(event) {
this.event = event;
this.count = 0;
var listeners = {};
this.add = function(func) {
var id = String(func);
if (!listeners[id]) {
listeners[id] = func;
this.count++;
if (this.count == 1) {
broadcastEvent(EVENTS.INFO, 'activating ' + event);
mraidview.activate(event);
}
}
};
this.remove = function(func) {
var id = String(func);
if (listeners[id]) {
listeners[id] = null;
delete listeners[id];
this.count--;
if (this.count == 0) {
broadcastEvent(EVENTS.INFO, 'deactivating ' + event);
mraidview.deactivate(event);
}
return true;
} else {
return false;
}
};
this.removeAll = function() { for (var id in listeners) this.remove(listeners[id]); };
this.broadcast = function(args) { for (var id in listeners) listeners[id].apply({}, args); };
this.toString = function() {
var out = [event,':'];
for (var id in listeners) out.push('|',id,'|');
return out.join('');
};
};
// PRIVATE METHODS ////////////////////////////////////////////////////////////
console = window.console; /* This is necessary for 2-Part Ads; otherwise, console returns null. */
mraidview.addEventListener('change', function(properties) {
for (var property in properties) {
var handler = changeHandlers[property];
console.log('for property "' + property + '" typeof handler is: ' + typeof(handler));
handler(properties[property]);
}
});
mraidview.addEventListener('error', function(message, action) {
broadcastEvent(EVENTS.ERROR, message, action);
});
var clone = function(obj) {
var f = function() {};
f.prototype = obj;
return new f();
};
var stringify = function(obj) {
if (typeof obj == 'object') {
if (obj.push) {
var out = [];
for (var p = 0; p < obj.length; p++) {
out.push(obj[p]);
}
return '[' + out.join(',') + ']';
} else {
var out = [];
for (var p in obj) {
out.push('\''+p+'\':'+obj[p]);
}
return '{' + out.join(',') + '}';
}
} else {
return String(obj);
}
};
var valid = function(obj, validators, action, full) {
if (full) {
if (obj === undefined) {
broadcastEvent(EVENTS.ERROR, 'Required object missing.', action);
return false;
} else {
for (var i in validators) {
if (obj[i] === undefined) {
broadcastEvent(EVENTS.ERROR, 'Object missing required property ' + i, action);
return false;
}
}
}
}
for (var i in obj) {
if (!validators[i]) {
broadcastEvent(EVENTS.ERROR, 'Invalid property specified - ' + i + '.', action);
return false;
} else {
var result = validators[i](obj[i]);
if (!result.value) {
broadcastEvent(EVENTS.ERROR, 'Value of property ' + i + ' is ' + result.msg + '.', action);
return false;
}
}
}
return true;
};
var contains = function(value, array) {
for (var i in array) if (array[i] == value) return true;
return false;
};
var broadcastEvent = function() {
var args = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) args[i] = arguments[i];
var event = args.shift();
if (listeners[event]) listeners[event].broadcast(args);
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////////////
mraid.signalReady = function() {
/* introduced in MRAIDv1 */
broadcastEvent(EVENTS.INFO, 'START READY SIGNAL, setting state to ' + stringify(STATES.DEFAULT));
state = STATES.DEFAULT;
broadcastEvent(EVENTS.STATECHANGE, state);
broadcastEvent(EVENTS.INFO, 'ready event fired');
broadcastEvent(EVENTS.READY, 'ready event fired');
window.clearInterval(intervalID);
};
mraid.getVersion = function() {
/* introduced in MRAIDv1 */
return (mraidVersion);
};
mraid.info = function(message) {
/* not in MRAID - unique to mraid-web-tester */
broadcastEvent(EVENTS.INFO, message);
};
mraid.error = function(message) {
/* introduced in MRAIDv1 */
broadcastEvent(EVENTS.ERROR, message);
};
mraid.addEventListener = function(event, listener) {
/* introduced in MRAIDv1 */
if (!event || !listener) {
broadcastEvent(EVENTS.ERROR, 'Both event and listener are required.', 'addEventListener');
} else if (!contains(event, EVENTS)) {
broadcastEvent(EVENTS.ERROR, 'Unknown event: ' + event, 'addEventListener');
} else {
if (!listeners[event]) listeners[event] = new EventListeners(event);
listeners[event].add(listener);
}
};
mraid.removeEventListener = function(event, listener) {
/* introduced in MRAIDv1 */
if (!event) {
broadcastEvent(EVENTS.ERROR, 'Must specify an event.', 'removeEventListener');
} else {
if (!listener || (typeof(listeners[event]) === 'undefined' || !listeners[event].remove(listener))) {
broadcastEvent(EVENTS.ERROR, 'Listener not currently registered for event: ' + event, 'removeEventListener');
return;
} else {
listeners[event].removeAll();
}
if (listeners[event].count == 0) {
listeners[event] = null;
delete listeners[event];
}
}
};
mraid.getState = function() {
/* introduced in MRAIDv1 */
return state;
};
mraid.getPlacementType = function() {
/* introduced in MRAIDv1 */
return placementType;
};
mraid.isViewable = function() {
/* introduced in MRAIDv1 */
return isViewable;
};
mraid.open = function(URL) {
/* introduced in MRAIDv1 */
if (!URL) {
broadcastEvent(EVENTS.ERROR, 'URL is required.', 'open');
} else {
mraidview.open(URL);
}
};
mraid.expand = function(URL) {
if (placementType === PLACEMENTS.INLINE) {
mraidview.expand(URL);
}
};
/*mraid.expand = function(dimensions, URL) {
/* introduced in MRAIDv1 */
/*var bOverride = true;
if (dimensions === undefined) {
dimensions = {width:mraid.getMaxSize(bOverride).width, height:mraid.getMaxSize(bOverride).height, x:0, y:0};
}
broadcastEvent(EVENTS.INFO, 'expanding to ' + stringify(dimensions));
if (valid(dimensions, dimensionValidators, 'expand', true)) {
mraidview.expand(dimensions, URL);
}
};*/
mraid.getExpandProperties = function() {
/* introduced in MRAIDv1 */
var props = clone(expandProperties);
// if (parseFloat(mraidVersion, 10) < 2) {
// delete props.allowOrientationChange;
// delete props.forceOrientation;
// }
return props;
};
mraid.setExpandProperties = function(properties) {
/* introduced in MRAIDv1 */
if (valid(properties, expandPropertyValidators, 'setExpandProperties')) {
mraidview.setExpandProperties(properties);
}
};
mraid.close = function() {
/* introduced in MRAIDv1 */
mraidview.close();
};
mraid.useCustomClose = function(useCustomCloseIndicator) {
/* introduced in MRAIDv1 */
mraidview.useCustomClose(useCustomCloseIndicator);
};
mraid.resize = function() {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (resize)', 'resize');
} else {
if (placementType === PLACEMENTS.INLINE) {
/* Check if resizeProperties object has been initialized && validated */
if (!resizeProperties.initialized) {
broadcastEvent(EVENTS.ERROR, 'Could not resize because props not init', 'resize');
return false;
}
if (!resizeProperties.validated) {
broadcastEvent(EVENTS.ERROR, 'Could not resize because props not valid', 'resize');
return false;
}
mraidview.resize();
}
}
};
mraid.getResizeProperties = function() {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getResizeProperties)', 'getResizeProperties');
} else {
return clone(resizeProperties);
}
return (null);
};
mraid.setResizeProperties = function(properties) {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (setResizeProperties)', 'setResizeProperties');
} else {
/* Set flag so resize() will know an attempt has been made to reset the properties. */
resizeProperties.initialized = false;
/* Check for required properties. */
if (!properties.width && !resizeProperties.width) { // Is width either missing or 0?
broadcastEvent(EVENTS.ERROR, 'Could not resize because property width is missing', 'setResizeProperties');
return false;
}
if (!properties.height && !resizeProperties.height) { // Is height either missing or 0?
broadcastEvent(EVENTS.ERROR, 'Could not resize because property height is missing', 'setResizeProperties');
return false;
}
if (!properties.hasOwnProperty('offsetX') && resizeProperties.offsetX === undefined) { // Is offsetX missing?
broadcastEvent(EVENTS.ERROR, 'Could not resize because property offsetX is missing', 'setResizeProperties');
return false;
}
if (!properties.hasOwnProperty('offsetY') && resizeProperties.offsetY === undefined) { // Is offsetY missing?
broadcastEvent(EVENTS.ERROR, 'Could not resize because property offsetY is missing', 'setResizeProperties');
return false;
}
if (valid(properties, resizePropertyValidators, 'setResizeProperties')) {
mraidview.setResizeProperties(properties);
//resizeProperties.validated = true;
//resizeProperties.initialized = true;
}
}
};
mraid.getCurrentPosition = function() {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getCurrentPosition)', 'getCurrentPosition');
} else {
return clone(currentPosition);
}
return (null);
};
mraid.getSize = function() {
/* introduced in MRAIDv1, deprecated in MRAIDv2 */
var pos = clone(currentPosition);
return ({width:pos.width, height:pos.height});
};
mraid.getMaxSize = function(bOverride) {
/* introduced in MRAIDv2, bOverride is an mraid-web-tester extension */
if (!bOverride && parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getMaxSize)', 'getMaxSize');
} else {
return clone(maxSize);
}
return (null);
};
mraid.getDefaultPosition = function() {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getDefaultPosition)', 'getDefaultPosition');
} else {
return clone(defaultPosition);
}
return (null);
};
mraid.getScreenSize = function() {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getScreenSize)', 'getScreenSize');
} else {
return clone(screenSize);
}
return (null);
};
mraid.supports = function(feature) {
/* introduced in MRAIDv2 */
var bSupports = false;
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (supports)', 'supports');
} else {
bSupports = supports[feature];
}
return (bSupports);
};
mraid.storePicture = function(url) {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (storePicture)', 'storePicture');
} else {
if (!supports[FEATURES.STOREPICTURE]) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this client. (storePicture)', 'storePicture');
} else if (!url || typeof url !== 'string') {
broadcastEvent(EVENTS.ERROR, 'Valid url required. (storePicture)', 'storePicture');
} else {
mraidview.storePicture(url);
}
}
};
mraid.createCalendarEvent = function(params) {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (createCalendarEvent)', 'createCalendarEvent');
} else {
if (!supports[FEATURES.CALENDAR]) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this client. (createCalendarEvent)', 'createCalendarEvent');
} else if (!params || typeof params != 'object') {
broadcastEvent(EVENTS.ERROR, 'Valid params required.', 'createCalendarEvent');
} else {
mraidview.createCalendarEvent(params);
}
}
};
mraid.playVideo = function(url) {
/* introduced in MRAIDv2 */
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (playVideo)', 'playVideo');
} else {
if (supports[FEATURES.INLINEVIDEO]) {
broadcastEvent(EVENTS.INFO, 'Inline video is available but playVideo uses native player.');
}
if (!url || typeof url != 'string') {
broadcastEvent(EVENTS.ERROR, 'Valid url required.', 'playVideo');
} else {
mraidview.playVideo(url);
}
}
};
mraid.getOrientation = function() {
/* not in MRAID - unique to mraid-web-tester */
if (!supports[FEATURES.ORIENTATION]) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this client. (getOrientation)', 'getOrientation');
}
return orientation;
};
mraid.setOrientationProperties = function (properties) {
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (setOrientationProperties)', 'setOrientationProperties');
} else {
if (valid(properties, orientationPropertyValidators, 'setOrientationProperties')) {
mraidview.setOrientationProperties(properties);
}
}
};
mraid.getOrientationProperties = function () {
if (parseFloat(mraidVersion, 10) < 2) {
broadcastEvent(EVENTS.ERROR, 'Method not supported by this version. (getOrientationProperties)', 'getOrientationProperties');
} else {
return clone(orientationProperties);
}
return (null);
};
})(window);