Android intent links for WebVR.Rocks (for Gecko View)
/* global ga, performance, YT */
(function () {
// Toggle this variable to output to the console debug GA messages.
var gaDebug = false;
function initGA (id, opts) {
(function (c, v, a, n) {
c.GoogleAnalyticsObject = n;
c[n] = c[n] || function () {
(c[n].q = c[n].q || []).push(arguments);
};
c[n].l = 1 * new Date();
var s = v.createElement('script');
s.async = true;
s.src = a;
s.addEventListener('load', enableDebug);
var m = v.getElementsByTagName('script')[0];
m.parentNode.insertBefore(s, m);
})(window, document, 'https://www.google-analytics.com/analytics.js', 'ga');
function enableDebug () {
if (!gaDebug) {
return;
}
window.ga = console.info.bind(console);
}
enableDebug();
ga('create', id, opts);
ga('set', 'forceSSL', true);
ga('send', 'pageview');
}
function initGAEvents () {
window.addEventListener('load', function () {
setTimeout(function () {
var t = performance.timing;
// Credit to https://github.com/addyosmani/timing.js/blob/c58c164/timing.js#L67-L88:
// Total time from start to load.
gaSendPageTiming('loadTime', t.loadEventEnd - t.fetchStart);
// Time spent constructing the DOM tree.
gaSendPageTiming('domReadyTime', t.domComplete - t.domInteractive);
// Time consumed preparing the new page.
gaSendPageTiming('readyStart', t.fetchStart - t.navigationStart);
// Time spent during redirection.
gaSendPageTiming('redirectTime', t.redirectEnd - t.redirectStart);
// AppCache.
gaSendPageTiming('appcacheTime', t.domainLookupStart - t.fetchStart);
// Time spent unloading documents.
gaSendPageTiming('unloadEventTime', t.unloadEventEnd - t.unloadEventStart);
// DNS query time.
gaSendPageTiming('lookupDomainTime', t.domainLookupEnd - t.domainLookupStart);
// TCP connection time.
gaSendPageTiming('connectTime', t.connectEnd - t.connectStart);
// Time spent during the request.
gaSendPageTiming('requestTime', t.responseEnd - t.requestStart);
// Request to completion of the DOM loading.
gaSendPageTiming('initDomTreeTime', t.domInteractive - t.responseEnd);
// Load event time.
gaSendPageTiming('loadEventTime', t.loadEventEnd - t.loadEventStart);
});
});
var gaSendTiming = function (timingCategory, timingLabel) {
return function (timingVar, timeEnd) {
if (typeof timeEnd === 'undefined') {
timeEnd = performance.now();
}
ga('send', {
hitType: 'timing',
timingCategory: timingCategory,
timingVar: timingVar,
timingValue: timeEnd,
timingLabel: timingLabel
});
};
};
var gaSendPageTiming = gaSendTiming('page');
ga('send', 'event', 'pageload.title', document.title);
ga('send', 'event', 'pageload.location', window.location.href);
ga('send', 'event', 'pageload.pathname', window.location.pathname);
ga('send', 'event', 'pageload.querystring', window.location.search);
ga('send', 'event', 'pageload.hash', window.location.hash);
ga('send', 'event', 'supports.getVRDisplays', 'getVRDisplays' in navigator);
ga('send', 'event', 'supports.getVRDevices', 'getVRDevices' in navigator);
ga('send', 'event', 'libs.aframe', 'AFRAME' in window ? (window.AFRAME.version || '<unknown>') : 'null');
ga('send', 'event', 'libs.three', 'THREE' in window ? (window.THREE.REVISION || '<unknown>') : 'null');
ga('send', 'event', 'libs.webvrpolyfill', 'WebVRConfig' in window || 'WebVRPolyfill' in window ? (window.WebVRPolyfill && window.WebVRPolyfill.version || '<unknown>') : 'null');
ga('send', 'event', 'libs.webvrplus', 'WEBVRPLUS' in window ? (window.WEBVRPLUS.version || '<unknown>') : 'null');
var getDeviceNames = function (devices) {
var names = (devices || []).map(function (device) {
return device ? (device.displayName || device.deviceName || '<unknown>') : '<unknown>';
});
return JSON.stringify(names);
};
var getPresentationStates = function (devices) {
var states = (devices || []).map(function (device) {
return device.isPresenting;
});
return JSON.stringify(states);
};
var fsElement;
var fsEvent;
if (document.body.requestFullscreen) {
fsElement = 'fullscreenElement';
fsEvent = 'fullscreenchange';
} else if (document.body.mozRequestFullScreen) {
fsElement = 'mozFullScreenElement';
fsEvent = 'mozfullscreenchange';
} else if (document.body.webkitRequestFullscreen) {
fsElement = 'webkitFullscreenElement';
fsEvent = 'webkitfullscreenchange';
} else if (document.body.msRequestFullscreen) {
fsElement = 'msFullscreenElement';
fsEvent = 'MSFullscreenChange';
}
document.addEventListener(fsEvent, function () {
var isFs = document[fsElement] instanceof HTMLElement;
ga('send', 'event', 'modechange.fullscreen', isFs);
if (navigator.getVRDevices) {
// NOTE: With the old API, we unfortunately cannot discern between
// entering/exiting fullscreen or VR mode - that is, whether
// `requestFullscreen({vrDisplay: display})` or
// `requestFullscreen(canvas)` was called.
var devices = [{isPresenting: isFs}];
ga('send', 'event', 'modechange.vr', getPresentationStates(devices));
}
});
if (navigator.getVRDisplays) {
navigator.getVRDisplays().then(function (devices) {
ga('send', 'event', 'pageload.getVRDisplays', getDeviceNames(devices));
window.addEventListener('vrdisplaypresentchange', function () {
ga('send', 'event', 'modechange.vr', getPresentationStates(devices));
});
});
} else if (navigator.getVRDevices) {
navigator.getVRDevices().then(function (devices) {
ga('send', 'event', 'pageload.getVRDevices', getDeviceNames(devices));
});
}
document.documentElement.addEventListener('click', function (e) {
var el = e.target.closest && e.target.closest('a, button') || e.target;
if (!el) { return; }
if (el.href) {
ga('send', 'event', 'click.link', el.href);
} else {
ga('send', 'event', 'click.button', el.id ? '#' + el.id : el.textContent);
}
});
window.addEventListener('appinstalled', function () {
var numVisits = storage.get('visits');
ga('send', 'event', 'app.installed.href', window.location.href);
ga('send', 'event', 'app.installed.visits', typeof numVisits === 'undefined' ? '<unknown>' : numVisits);
ga('send', 'event', 'app.installed.installs', storage.increment('installs'));
});
}
initGA('UA-86987247-1', {alwaysSendReferrer: true});
initGAEvents();
var requireScript = (function () {
var script = document.createElement('script');
function injectScript (package, version, file) {
package = package || '';
version = version || '';
file = file || '';
var src = (url || '').trim();
if (src.indexOf('//') === 0 ||
src.indexOf('https://') === 0 ||
src.indexOf('http://') === 0) {
src = url;
} else {
if (package.split('@').split('#').indexOf('/') > -1) {
// TODO: Handle GitHub file URLs.
} else {
var url = package;
var package = url.split('@')[0] || '';
var version = url.split('/')[0] || '';
var file = url.substring(0, url.indexOf('/', 0)) || '';
src = 'https://unpkg.com/${package}';
if (version) {
src += '@' + version;
}
if (file) {
src += '/' + file;
}
}
}
script.async = script.defer = true;
script.src = src;
document.head.appendChild(script);
return script;
}
return script;
});
var html = document.documentElement;
var hash = window.location.hash.substr(1);
var debug = false;
var storage = {
_cache: {},
has: function (key) {
try {
return key in localStorage;
} catch (e) {}
},
get: function (key, defaultValue) {
try {
if (!(key in localStorage)) {
return defaultValue;
}
return JSON.parse(localStorage[key]);
} catch (e) {}
},
remove: function (key) {
try {
delete localStorage[key];
} catch (e) {}
},
set: function (key, value) {
try {
localStorage[key] = JSON.stringify(value);
} catch (e) {}
},
increment: function (key, opts) {
if (opts && opts.once && key in storage._cache) {
return storage._cache[key];
}
var count = 0;
if (storage.has(key)) {
count = parseInt(storage.get(key), 10) || 0;
}
storage.set(key, ++count);
storage._cache[key] = count;
return count;
}
};
var handleHashchange = function () {
hash = window.location.hash.substr(1);
if (hash) {
html.setAttribute('data-hash', hash);
}
};
var handleLoad = function () {
handleHashchange();
var numVisits = storage.increment('visits');
html.dataset.newbie = storage.get('videos:what_is_webvr:plays', 0) <= 2;
if (storage.has('debug_ui')) {
debug = true;
html.dataset.debug = '';
} else {
debug = false;
delete html.dataset.debug;
}
var supportsTouch = 'ontouchstart' in window;
html.dataset.supportsTouch = supportsTouch;
var openDialogues = {};
// TODO: Hide dismissable notifications (on all pages).
// Remove unnecessary notifications on homepage.
if (html.matches && html.matches('[data-layout~="home"]')) {
Array.prototype.forEach.call(document.querySelectorAll('#notifications .message[data-pinned="true"]'), function (el) {
el.parentNode.removeChild(el);
});
}
html.addEventListener('click', function (e) {
var el = e.target;
var downloadBtnEl = el.closest('[data-download-id]') || el.querySelector('[data-download-id]');
if (downloadBtnEl) {
downloadBtnEl.closest(':not(#download)').downloadIsNew = 'false';
downloadBtnEl.closest('#download').dataset.downloadIsNew = 'false';
storage.increment('downloads:' +
downloadBtnEl.dataset.downloadName + ':' +
downloadBtnEl.dataset.downloadId);
}
var dropdown = el.closest && el.closest('.dropdown-with-children');
if (dropdown && supportsTouch) {
if (openDialogues[dropdown] === true) {
openDialogues[dropdown] = false;
} else {
openDialogues[dropdown] = true;
}
dropdown.setAttribute('aria-expanded', openDialogues[dropdown]);
return;
}
Object.keys(openDialogues).forEach(function (dialogueEl) {
dialogueEl.setAttribute('aria-expanded', 'false');
});
openDialogues = {};
var notification = el.closest && el.closest('.message-dismissable');
if (notification) {
if (notification.getAttribute('aria-expanded') === 'true') {
notification.setAttribute('aria-expanded', 'false');
storage.set('notifications:' + notification.id, 'hidden');
} else {
notification.setAttribute('aria-expanded', 'true');
storage.set('notifications:' + notification.id, 'visible');
}
}
});
var latestDownloadBtn = document.querySelector('#download > .button[data-download-name]');
if (latestDownloadBtn) {
var storageDownloadKey = 'downloads:' +
latestDownloadBtn.dataset.downloadName + ':' +
latestDownloadBtn.dataset.downloadId;
if (!storage.has(storageDownloadKey)) {
latestDownloadBtn.dataset.downloadIsNew = 'true';
var btnParentEl = latestDownloadBtn.closest('#download');
if (btnParentEl) {
btnParentEl.dataset.downloadIsNew = 'true';
}
}
}
initYT('Le8pTXQqM3s');
initIntentLinks();
};
var initIntentLinks = function () {
var linkEls = document.querySelectorAll('[data-layout~="demos"] #demos a[data-href]');
Array.prototype.forEach.call(linkEls, function (el) {
var url = new URL(el.getAttribute('data-href'));
var newHref = 'intent://' + url.origin + url.pathname + (url.query || '') +
'#Intent;scheme=' + url.protocol.replace(':', '') +
'package=org.mozilla.geckoviewvr;' +
'S.browser_fallback_url=' + encodeURIComponent(window.location.href);
el.setAttribute('href', newHref);
});
window.onerror = function (msg, url, lineNo, columnNo, error) {
// ... handle error ...
document.body.innerHTML += (msg + url + lineNo + columnNo + error);
return false;
};
};
var initYT = function (id) {
// YouTube Player helper.
// Reference: https://developers.google.com/youtube/iframe_api_reference
var tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
window.onYouTubeIframeAPIReady = function () {
player = new YT.Player('yt_' + id, {
height: '390',
width: '640',
videoId: id,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
};
var onPlayerReady = function (event) {
if (debug) {
console.log('YouTube Video "%s" ready', id);
}
};
var playPauseCounter = 0;
var onPlayerStateChange = function (event) {
if (debug) {
console.log('YouTube Video "%s" changed state', event.data);
}
if (event.data === YT.PlayerState.ENDED) {
if (debug) {
console.log('YouTube Video "%s" was played and ended');
}
storage.increment('videos:what_is_webvr:plays', {once: true});
return;
}
if (event.data === YT.PlayerState.PLAYING ||
event.data === YT.PlayerState.PAUSED) {
playPauseCounter++;
}
if (playPauseCounter >= 4) {
if (debug) {
console.log('YouTube Video "%s" was paused/played a few times');
}
storage.increment('videos:what_is_webvr:plays', {once: true});
}
};
};
window.addEventListener('hashchange', handleHashchange);
window.addEventListener('load', handleLoad);
})();