From 85bedcf9acf195e8e49777b1114cc301874879e9 Mon Sep 17 00:00:00 2001 From: Shane Tomlinson <stomlinson@mozilla.com> Date: Thu, 15 Dec 2011 14:01:40 +0000 Subject: [PATCH] Revert "Merge branch 'production_dir' into dev" This reverts commit e3f57a264cba19dc8ef78fc6377b52e2769d57f8, reversing changes made to f647f5520fad0750a8cfb51145d42fd759a15735. --- resources/static/include.js | 956 ++++++++++++++++++++++- resources/static/include_js/include.js | 955 ---------------------- resources/views/communication_iframe.ejs | 2 +- resources/views/dialog_layout.ejs | 5 +- resources/views/layout.ejs | 4 +- resources/views/relay.ejs | 3 +- scripts/compress.sh | 84 +- 7 files changed, 997 insertions(+), 1012 deletions(-) mode change 120000 => 100644 resources/static/include.js delete mode 100644 resources/static/include_js/include.js diff --git a/resources/static/include.js b/resources/static/include.js deleted file mode 120000 index a6aff07d3..000000000 --- a/resources/static/include.js +++ /dev/null @@ -1 +0,0 @@ -include_js/include.js \ No newline at end of file diff --git a/resources/static/include.js b/resources/static/include.js new file mode 100644 index 000000000..2909a6f79 --- /dev/null +++ b/resources/static/include.js @@ -0,0 +1,955 @@ +/** + * Uncompressed source can be found at https://browserid.org/include.orig.js + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +(function() { + // this is the file that the RP includes to shim in the + // navigator.id.getVerifiedEmail() function + "use strict"; + + // local embedded copy of jschannel: http://github.com/mozilla/jschannel + /** + * js_channel is a very lightweight abstraction on top of + * postMessage which defines message formats and semantics + * to support interactions more rich than just message passing + * js_channel supports: + * + query/response - traditional rpc + * + query/update/response - incremental async return of results + * to a query + * + notifications - fire and forget + * + error handling + * + * js_channel is based heavily on json-rpc, but is focused at the + * problem of inter-iframe RPC. + * + * Message types: + * There are 5 types of messages that can flow over this channel, + * and you may determine what type of message an object is by + * examining its parameters: + * 1. Requests + * + integer id + * + string method + * + (optional) any params + * 2. Callback Invocations (or just "Callbacks") + * + integer id + * + string callback + * + (optional) params + * 3. Error Responses (or just "Errors) + * + integer id + * + string error + * + (optional) string message + * 4. Responses + * + integer id + * + (optional) any result + * 5. Notifications + * + string method + * + (optional) any params + */ + var Channel = (function() { + "use strict"; + + // current transaction id, start out at a random *odd* number between 1 and a million + // There is one current transaction counter id per page, and it's shared between + // channel instances. That means of all messages posted from a single javascript + // evaluation context, we'll never have two with the same id. + var s_curTranId = Math.floor(Math.random()*1000001); + + // no two bound channels in the same javascript evaluation context may have the same origin, scope, and window. + // futher if two bound channels have the same window and scope, they may not have *overlapping* origins + // (either one or both support '*'). This restriction allows a single onMessage handler to efficiently + // route messages based on origin and scope. The s_boundChans maps origins to scopes, to message + // handlers. Request and Notification messages are routed using this table. + // Finally, channels are inserted into this table when built, and removed when destroyed. + var s_boundChans = { }; + + // add a channel to s_boundChans, throwing if a dup exists + function s_addBoundChan(win, origin, scope, handler) { + function hasWin(arr) { + for (var i = 0; i < arr.length; i++) if (arr[i].win === win) return true; + return false; + } + + // does she exist? + var exists = false; + + + if (origin === '*') { + // we must check all other origins, sadly. + for (var k in s_boundChans) { + if (!s_boundChans.hasOwnProperty(k)) continue; + if (k === '*') continue; + if (typeof s_boundChans[k][scope] === 'object') { + exists = hasWin(s_boundChans[k][scope]); + if (exists) break; + } + } + } else { + // we must check only '*' + if ((s_boundChans['*'] && s_boundChans['*'][scope])) { + exists = hasWin(s_boundChans['*'][scope]); + } + if (!exists && s_boundChans[origin] && s_boundChans[origin][scope]) + { + exists = hasWin(s_boundChans[origin][scope]); + } + } + if (exists) throw "A channel is already bound to the same window which overlaps with origin '"+ origin +"' and has scope '"+scope+"'"; + + if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { }; + if (typeof s_boundChans[origin][scope] != 'object') s_boundChans[origin][scope] = [ ]; + s_boundChans[origin][scope].push({win: win, handler: handler}); + } + + function s_removeBoundChan(win, origin, scope) { + var arr = s_boundChans[origin][scope]; + for (var i = 0; i < arr.length; i++) { + if (arr[i].win === win) { + arr.splice(i,1); + } + } + if (s_boundChans[origin][scope].length === 0) { + delete s_boundChans[origin][scope] + } + } + + function s_isArray(obj) { + if (Array.isArray) return Array.isArray(obj); + else { + return (obj.constructor.toString().indexOf("Array") != -1); + } + } + + // No two outstanding outbound messages may have the same id, period. Given that, a single table + // mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and + // Response messages. Entries are added to this table when requests are sent, and removed when + // responses are received. + var s_transIds = { }; + + // class singleton onMessage handler + // this function is registered once and all incoming messages route through here. This + // arrangement allows certain efficiencies, message data is only parsed once and dispatch + // is more efficient, especially for large numbers of simultaneous channels. + var s_onMessage = function(e) { + try { + var m = JSON.parse(e.data); + if (typeof m !== 'object' || m === null) throw "malformed"; + } catch(e) { + // just ignore any posted messages that do not consist of valid JSON + return; + } + + var w = e.source; + var o = e.origin; + var s, i, meth; + + if (typeof m.method === 'string') { + var ar = m.method.split('::'); + if (ar.length == 2) { + s = ar[0]; + meth = ar[1]; + } else { + meth = m.method; + } + } + + if (typeof m.id !== 'undefined') i = m.id; + + // w is message source window + // o is message origin + // m is parsed message + // s is message scope + // i is message id (or undefined) + // meth is unscoped method name + // ^^ based on these factors we can route the message + + // if it has a method it's either a notification or a request, + // route using s_boundChans + if (typeof meth === 'string') { + var delivered = false; + if (s_boundChans[o] && s_boundChans[o][s]) { + for (var i = 0; i < s_boundChans[o][s].length; i++) { + if (s_boundChans[o][s][i].win === w) { + s_boundChans[o][s][i].handler(o, meth, m); + delivered = true; + break; + } + } + } + + if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) { + for (var i = 0; i < s_boundChans['*'][s].length; i++) { + if (s_boundChans['*'][s][i].win === w) { + s_boundChans['*'][s][i].handler(o, meth, m); + break; + } + } + } + } + // otherwise it must have an id (or be poorly formed + else if (typeof i != 'undefined') { + if (s_transIds[i]) s_transIds[i](o, meth, m); + } + }; + + // Setup postMessage event listeners + if (window.addEventListener) window.addEventListener('message', s_onMessage, false); + else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage); + + /* a messaging channel is constructed from a window and an origin. + * the channel will assert that all messages received over the + * channel match the origin + * + * Arguments to Channel.build(cfg): + * + * cfg.window - the remote window with which we'll communicate + * cfg.origin - the expected origin of the remote window, may be '*' + * which matches any origin + * cfg.scope - the 'scope' of messages. a scope string that is + * prepended to message names. local and remote endpoints + * of a single channel must agree upon scope. Scope may + * not contain double colons ('::'). + * cfg.debugOutput - A boolean value. If true and window.console.log is + * a function, then debug strings will be emitted to that + * function. + * cfg.debugOutput - A boolean value. If true and window.console.log is + * a function, then debug strings will be emitted to that + * function. + * cfg.postMessageObserver - A function that will be passed two arguments, + * an origin and a message. It will be passed these immediately + * before messages are posted. + * cfg.gotMessageObserver - A function that will be passed two arguments, + * an origin and a message. It will be passed these arguments + * immediately after they pass scope and origin checks, but before + * they are processed. + * cfg.onReady - A function that will be invoked when a channel becomes "ready", + * this occurs once both sides of the channel have been + * instantiated and an application level handshake is exchanged. + * the onReady function will be passed a single argument which is + * the channel object that was returned from build(). + */ + return { + build: function(cfg) { + var debug = function(m) { + if (cfg.debugOutput && window.console && window.console.log) { + // try to stringify, if it doesn't work we'll let javascript's built in toString do its magic + try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { } + console.log("["+chanId+"] " + m); + } + } + + /* browser capabilities check */ + if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage"); + if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) { + throw("jschannel cannot run this browser, no JSON parsing/serialization"); + } + + /* basic argument validation */ + if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument"); + + if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument"); + + /* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same + * window... Not sure if we care to support that */ + if (window === cfg.window) throw("target window is same as present window -- not allowed"); + + // let's require that the client specify an origin. if we just assume '*' we'll be + // propagating unsafe practices. that would be lame. + var validOrigin = false; + if (typeof cfg.origin === 'string') { + var oMatch; + if (cfg.origin === "*") validOrigin = true; + // allow valid domains under http and https. Also, trim paths off otherwise valid origins. + else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9\.])+(?::\d+)?/))) { + cfg.origin = oMatch[0].toLowerCase(); + validOrigin = true; + } + } + + if (!validOrigin) throw ("Channel.build() called with an invalid origin"); + + if (typeof cfg.scope !== 'undefined') { + if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string'; + if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'" + } + + /* private variables */ + // generate a random and psuedo unique id for this channel + var chanId = (function () { + var text = ""; + var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length)); + return text; + })(); + + // registrations: mapping method names to call objects + var regTbl = { }; + // current oustanding sent requests + var outTbl = { }; + // current oustanding received requests + var inTbl = { }; + // are we ready yet? when false we will block outbound messages. + var ready = false; + var pendingQueue = [ ]; + + var createTransaction = function(id,origin,callbacks) { + var shouldDelayReturn = false; + var completed = false; + + return { + origin: origin, + invoke: function(cbName, v) { + // verify in table + if (!inTbl[id]) throw "attempting to invoke a callback of a nonexistent transaction: " + id; + // verify that the callback name is valid + var valid = false; + for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; } + if (!valid) throw "request supports no such callback '" + cbName + "'"; + + // send callback invocation + postMessage({ id: id, callback: cbName, params: v}); + }, + error: function(error, message) { + completed = true; + // verify in table + if (!inTbl[id]) throw "error called for nonexistent message: " + id; + + // remove transaction from table + delete inTbl[id]; + + // send error + postMessage({ id: id, error: error, message: message }); + }, + complete: function(v) { + completed = true; + // verify in table + if (!inTbl[id]) throw "complete called for nonexistent message: " + id; + // remove transaction from table + delete inTbl[id]; + // send complete + postMessage({ id: id, result: v }); + }, + delayReturn: function(delay) { + if (typeof delay === 'boolean') { + shouldDelayReturn = (delay === true); + } + return shouldDelayReturn; + }, + completed: function() { + return completed; + } + }; + } + + var setTransactionTimeout = function(transId, timeout, method) { + return window.setTimeout(function() { + if (outTbl[transId]) { + // XXX: what if client code raises an exception here? + var msg = "timeout (" + timeout + "ms) exceeded on method '" + method + "'"; + (1,outTbl[transId].error)("timeout_error", msg); + delete outTbl[transId]; + delete s_transIds[transId]; + } + }, timeout); + } + + var onMessage = function(origin, method, m) { + // if an observer was specified at allocation time, invoke it + if (typeof cfg.gotMessageObserver === 'function') { + // pass observer a clone of the object so that our + // manipulations are not visible (i.e. method unscoping). + // This is not particularly efficient, but then we expect + // that message observers are primarily for debugging anyway. + try { + cfg.gotMessageObserver(origin, m); + } catch (e) { + debug("gotMessageObserver() raised an exception: " + e.toString()); + } + } + + // now, what type of message is this? + if (m.id && method) { + // a request! do we have a registered handler for this request? + if (regTbl[method]) { + var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]); + inTbl[m.id] = { }; + try { + // callback handling. we'll magically create functions inside the parameter list for each + // callback + if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) { + for (var i = 0; i < m.callbacks.length; i++) { + var path = m.callbacks[i]; + var obj = m.params; + var pathItems = path.split('/'); + for (var j = 0; j < pathItems.length - 1; j++) { + var cp = pathItems[j]; + if (typeof obj[cp] !== 'object') obj[cp] = { }; + obj = obj[cp]; + } + obj[pathItems[pathItems.length - 1]] = (function() { + var cbName = path; + return function(params) { + return trans.invoke(cbName, params); + } + })(); + } + } + var resp = regTbl[method](trans, m.params); + if (!trans.delayReturn() && !trans.completed()) trans.complete(resp); + } catch(e) { + // automagic handling of exceptions: + var error = "runtime_error"; + var message = null; + // * if it's a string then it gets an error code of 'runtime_error' and string is the message + if (typeof e === 'string') { + message = e; + } else if (typeof e === 'object') { + // either an array or an object + // * if it's an array of length two, then array[0] is the code, array[1] is the error message + if (e && s_isArray(e) && e.length == 2) { + error = e[0]; + message = e[1]; + } + // * if it's an object then we'll look form error and message parameters + else if (typeof e.error === 'string') { + error = e.error; + if (!e.message) message = ""; + else if (typeof e.message === 'string') message = e.message; + else e = e.message; // let the stringify/toString message give us a reasonable verbose error string + } + } + + // message is *still* null, let's try harder + if (message === null) { + try { + message = JSON.stringify(e); + /* On MSIE8, this can result in 'out of memory', which + * leaves message undefined. */ + if (typeof(message) == 'undefined') + message = e.toString(); + } catch (e2) { + message = e.toString(); + } + } + + trans.error(error,message); + } + } + } else if (m.id && m.callback) { + if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback]) + { + debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")"); + } else { + // XXX: what if client code raises an exception here? + outTbl[m.id].callbacks[m.callback](m.params); + } + } else if (m.id) { + if (!outTbl[m.id]) { + debug("ignoring invalid response: " + m.id); + } else { + // XXX: what if client code raises an exception here? + if (m.error) { + (1,outTbl[m.id].error)(m.error, m.message); + } else { + if (m.result !== undefined) (1,outTbl[m.id].success)(m.result); + else (1,outTbl[m.id].success)(); + } + delete outTbl[m.id]; + delete s_transIds[m.id]; + } + } else if (method) { + // tis a notification. + if (regTbl[method]) { + // yep, there's a handler for that. + // transaction is null for notifications. + regTbl[method](null, m.params); + // if the client throws, we'll just let it bubble out + // what can we do? Also, here we'll ignore return values + } + } + } + + // now register our bound channel for msg routing + s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage); + + // scope method names based on cfg.scope specified when the Channel was instantiated + var scopeMethod = function(m) { + if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::"); + return m; + } + + // a small wrapper around postmessage whose primary function is to handle the + // case that clients start sending messages before the other end is "ready" + var postMessage = function(msg, force) { + if (!msg) throw "postMessage called with null message"; + + // delay posting if we're not ready yet. + var verb = (ready ? "post " : "queue "); + debug(verb + " message: " + JSON.stringify(msg)); + if (!force && !ready) { + pendingQueue.push(msg); + } else { + if (typeof cfg.postMessageObserver === 'function') { + try { + cfg.postMessageObserver(cfg.origin, msg); + } catch (e) { + debug("postMessageObserver() raised an exception: " + e.toString()); + } + } + + cfg.window.postMessage(JSON.stringify(msg), cfg.origin); + } + } + + var onReady = function(trans, type) { + debug('ready msg received'); + if (ready) throw "received ready message while in ready state. help!"; + + if (type === 'ping') { + chanId += '-R'; + } else { + chanId += '-L'; + } + + obj.unbind('__ready'); // now this handler isn't needed any more. + ready = true; + debug('ready msg accepted.'); + + if (type === 'ping') { + obj.notify({ method: '__ready', params: 'pong' }); + } + + // flush queue + while (pendingQueue.length) { + postMessage(pendingQueue.pop()); + } + + // invoke onReady observer if provided + if (typeof cfg.onReady === 'function') cfg.onReady(obj); + }; + + var obj = { + // tries to unbind a bound message handler. returns false if not possible + unbind: function (method) { + if (regTbl[method]) { + if (!(delete regTbl[method])) throw ("can't delete method: " + method); + return true; + } + return false; + }, + bind: function (method, cb) { + if (!method || typeof method !== 'string') throw "'method' argument to bind must be string"; + if (!cb || typeof cb !== 'function') throw "callback missing from bind params"; + + if (regTbl[method]) throw "method '"+method+"' is already bound!"; + regTbl[method] = cb; + return this; + }, + call: function(m) { + if (!m) throw 'missing arguments to call function'; + if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string"; + if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call"; + + // now it's time to support the 'callback' feature of jschannel. We'll traverse the argument + // object and pick out all of the functions that were passed as arguments. + var callbacks = { }; + var callbackNames = [ ]; + + var pruneFunctions = function (path, obj) { + if (typeof obj === 'object') { + for (var k in obj) { + if (!obj.hasOwnProperty(k)) continue; + var np = path + (path.length ? '/' : '') + k; + if (typeof obj[k] === 'function') { + callbacks[np] = obj[k]; + callbackNames.push(np); + delete obj[k]; + } else if (typeof obj[k] === 'object') { + pruneFunctions(np, obj[k]); + } + } + } + }; + pruneFunctions("", m.params); + + // build a 'request' message and send it + var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params }; + if (callbackNames.length) msg.callbacks = callbackNames; + + if (m.timeout) + // XXX: This function returns a timeout ID, but we don't do anything with it. + // We might want to keep track of it so we can cancel it using clearTimeout() + // when the transaction completes. + setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method)); + + // insert into the transaction table + outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success }; + s_transIds[s_curTranId] = onMessage; + + // increment current id + s_curTranId++; + + postMessage(msg); + }, + notify: function(m) { + if (!m) throw 'missing arguments to notify function'; + if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string"; + + // no need to go into any transaction table + postMessage({ method: scopeMethod(m.method), params: m.params }); + }, + destroy: function () { + s_removeBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : '')); + if (window.removeEventListener) window.removeEventListener('message', onMessage, false); + else if(window.detachEvent) window.detachEvent('onmessage', onMessage); + ready = false; + regTbl = { }; + inTbl = { }; + outTbl = { }; + cfg.origin = null; + pendingQueue = [ ]; + debug("channel destroyed"); + chanId = ""; + } + }; + + obj.bind('__ready', onReady); + setTimeout(function() { + postMessage({ method: scopeMethod('__ready'), params: "ping" }, true); + }, 0); + + return obj; + } + }; + })(); + + + var BrowserSupport = (function() { + var win = window, + nav = navigator, + reason; + + // For unit testing + function setTestEnv(newNav, newWindow) { + nav = newNav; + win = newWindow; + } + + function getInternetExplorerVersion() { + var rv = -1; // Return value assumes failure. + if (nav.appName == 'Microsoft Internet Explorer') { + var ua = nav.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat(RegExp.$1); + } + + return rv; + } + + function checkIE() { + var ieVersion = getInternetExplorerVersion(), + ieNosupport = ieVersion > -1 && ieVersion < 8; + + if(ieNosupport) { + return "IE_VERSION"; + } + } + + function explicitNosupport() { + return checkIE(); + } + + function checkLocalStorage() { + var localStorage = 'localStorage' in win && win['localStorage'] !== null; + if(!localStorage) { + return "LOCALSTORAGE"; + } + } + + function checkPostMessage() { + if(!win.postMessage) { + return "POSTMESSAGE"; + } + } + + function isSupported() { + reason = checkLocalStorage() || checkPostMessage() || explicitNosupport(); + + return !reason; + } + + function getNoSupportReason() { + return reason; + } + + return { + /** + * Set the test environment. + * @method setTestEnv + */ + setTestEnv: setTestEnv, + /** + * Check whether the current browser is supported + * @method isSupported + * @returns {boolean} + */ + isSupported: isSupported, + /** + * Called after isSupported, if isSupported returns false. Gets the reason + * why browser is not supported. + * @method getNoSupportReason + * @returns {string} + */ + getNoSupportReason: getNoSupportReason + }; + }()); + + + // this is for calls that are non-interactive + function _open_hidden_iframe(doc) { + var iframe = doc.createElement("iframe"); + iframe.style.display = "none"; + doc.body.appendChild(iframe); + iframe.src = ipServer + "/communication_iframe"; + return iframe; + } + + function _get_relayframe_id() { + var randomString = ''; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (var i=0; i < 8; i++) { + randomString += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return randomString; + } + + function _open_relayframe(framename) { + var doc = window.document; + var iframe = doc.createElement("iframe"); + iframe.setAttribute('name', framename); + iframe.setAttribute('src', ipServer + "/relay"); + iframe.style.display = "none"; + doc.body.appendChild(iframe); + + return iframe; + } + + function _open_window(url, name_suffix) { + url = url || "about:blank"; + // we open the window initially blank, and only after our relay frame has + // been constructed do we update the location. This is done because we + // must launch the window inside a click handler, but we should wait to + // start loading it until our relay iframe is instantiated and ready. + // see issue #287 & #286 + var window_name = "_mozid_signin"; + if (name_suffix) + window_name += "_" + name_suffix; + + var dialog = window.open( + url, + window_name, + isFennec ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=700,height=375"); + + dialog.focus(); + return dialog; + } + + function _attach_event(element, name, listener) { + if (element.addEventListener) { + element.addEventListener(name, listener, false); + } + else if(element.attachEvent) { + // IE < 9 + element.attachEvent('on' + name, listener); + } + } + + function _detatch_event(element, name, listener) { + if (element.removeEventListener) { + element.removeEventListener(name, listener, false); + } + else if(element.detachEvent) { + element.detachEvent(name, listener); + } + } + + /** + * The meat and potatoes of the verified email protocol + */ + + + if (!navigator.id) { + navigator.id = {}; + } + + if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) { + var ipServer = "https://browserid.org"; + var isFennec = navigator.userAgent.indexOf('Fennec/') != -1; + + var relay_chan, w, relay_iframe; + + // keep track of these so that we can re-use/re-focus an already open window. + navigator.id.get = function(callback, options) { + var doc = document; + function cleanup() { + // we no longer remove the relay chan and iframe + // since those live for as long as the page does + + if (w) { + w.close(); + w = null; + } + + _detatch_event(window, 'unload', cleanup); + } + + if (typeof callback !== 'function') throw "navigator.id.get() requires a callback argument"; + + if (options && options.silent) { + _noninteractiveCall('getPersistentAssertion', { }, function(rv) { + callback(rv); + }, function(e, msg) { + callback(null); + }); + } else { + if (w) { + // if there is already a window open, just focus the old window. + w.focus(); + return; + } + + if (!BrowserSupport.isSupported()) { + w = _open_window(ipServer + "/unsupported_dialog"); + return; + } + + // we now have the relay iframe and channel already + // we just message it. + relay_chan.call({ + method: "get", + params: options, + success: function(rv) { + if (callback) { + // return the string representation of the JWT, the client is responsible for unpacking it. + callback(rv); + } + cleanup(); + }, + error: function(code, msg) { + // XXX: we don't make the code and msg available to the user. + if (callback) callback(null); + cleanup(); + } + }); + + // open the window now that all else is ready + w = _open_window(ipServer + "/sign_in#" + relay_iframe.getAttribute('name')); + + // if the RP window closes, close the dialog as well. + _attach_event(window, 'unload', cleanup); + } + }; + + navigator.id.getVerifiedEmail = function (callback, options) { + if (options) { + throw "getVerifiedEmail doesn't accept options. use navigator.id.get() instead."; + } + navigator.id.get(callback); + }; + + navigator.id.logout = function(callback) { + _noninteractiveCall('logout', { }, function(rv) { + callback(rv); + }, function() { + callback(null); + }); + }; + + var _noninteractiveCall = function(method, args, onsuccess, onerror) { + var doc = window.document; + var ni_iframe = _open_hidden_iframe(doc); + + var chan = Channel.build({window: ni_iframe.contentWindow, origin: ipServer, scope: "mozid_ni"}); + + function cleanup() { + chan.destroy(); + chan = undefined; + doc.body.removeChild(ni_iframe); + } + + chan.call({ + method: method, + params: args, + success: function(rv) { + if (onsuccess) { + onsuccess(rv); + } + cleanup(); + }, + error: function(code, msg) { + if (onerror) onerror(code, msg); + cleanup(); + } + }); + }; + + // set up the relay iframe + var setup_relay_iframe = function() { + if (document.body) { + var frameid = _get_relayframe_id(); + // removed browserid_relay_ prefix, cause why? + relay_iframe = _open_relayframe(frameid); + relay_chan = Channel.build({ + window: relay_iframe.contentWindow, + origin: ipServer, + scope: "mozid" + }); + + } else { + window.setTimeout(setup_relay_iframe, 100); + } + }; + + // wait for onload + // _attach_event(window, 'load', setup_relay_iframe); + // that didn't work at fixing the issue, so reverting + window.setTimeout(setup_relay_iframe, 0); + + navigator.id._getVerifiedEmailIsShimmed = true; + } +}()); + diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js deleted file mode 100644 index fde3f20ff..000000000 --- a/resources/static/include_js/include.js +++ /dev/null @@ -1,955 +0,0 @@ -/** - * Uncompressed source can be found at https://browserid.org/build/include.js - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla BrowserID. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -(function() { - // this is the file that the RP includes to shim in the - // navigator.id.getVerifiedEmail() function - "use strict"; - - // local embedded copy of jschannel: http://github.com/mozilla/jschannel - /** - * js_channel is a very lightweight abstraction on top of - * postMessage which defines message formats and semantics - * to support interactions more rich than just message passing - * js_channel supports: - * + query/response - traditional rpc - * + query/update/response - incremental async return of results - * to a query - * + notifications - fire and forget - * + error handling - * - * js_channel is based heavily on json-rpc, but is focused at the - * problem of inter-iframe RPC. - * - * Message types: - * There are 5 types of messages that can flow over this channel, - * and you may determine what type of message an object is by - * examining its parameters: - * 1. Requests - * + integer id - * + string method - * + (optional) any params - * 2. Callback Invocations (or just "Callbacks") - * + integer id - * + string callback - * + (optional) params - * 3. Error Responses (or just "Errors) - * + integer id - * + string error - * + (optional) string message - * 4. Responses - * + integer id - * + (optional) any result - * 5. Notifications - * + string method - * + (optional) any params - */ - var Channel = (function() { - "use strict"; - - // current transaction id, start out at a random *odd* number between 1 and a million - // There is one current transaction counter id per page, and it's shared between - // channel instances. That means of all messages posted from a single javascript - // evaluation context, we'll never have two with the same id. - var s_curTranId = Math.floor(Math.random()*1000001); - - // no two bound channels in the same javascript evaluation context may have the same origin, scope, and window. - // futher if two bound channels have the same window and scope, they may not have *overlapping* origins - // (either one or both support '*'). This restriction allows a single onMessage handler to efficiently - // route messages based on origin and scope. The s_boundChans maps origins to scopes, to message - // handlers. Request and Notification messages are routed using this table. - // Finally, channels are inserted into this table when built, and removed when destroyed. - var s_boundChans = { }; - - // add a channel to s_boundChans, throwing if a dup exists - function s_addBoundChan(win, origin, scope, handler) { - function hasWin(arr) { - for (var i = 0; i < arr.length; i++) if (arr[i].win === win) return true; - return false; - } - - // does she exist? - var exists = false; - - - if (origin === '*') { - // we must check all other origins, sadly. - for (var k in s_boundChans) { - if (!s_boundChans.hasOwnProperty(k)) continue; - if (k === '*') continue; - if (typeof s_boundChans[k][scope] === 'object') { - exists = hasWin(s_boundChans[k][scope]); - if (exists) break; - } - } - } else { - // we must check only '*' - if ((s_boundChans['*'] && s_boundChans['*'][scope])) { - exists = hasWin(s_boundChans['*'][scope]); - } - if (!exists && s_boundChans[origin] && s_boundChans[origin][scope]) - { - exists = hasWin(s_boundChans[origin][scope]); - } - } - if (exists) throw "A channel is already bound to the same window which overlaps with origin '"+ origin +"' and has scope '"+scope+"'"; - - if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { }; - if (typeof s_boundChans[origin][scope] != 'object') s_boundChans[origin][scope] = [ ]; - s_boundChans[origin][scope].push({win: win, handler: handler}); - } - - function s_removeBoundChan(win, origin, scope) { - var arr = s_boundChans[origin][scope]; - for (var i = 0; i < arr.length; i++) { - if (arr[i].win === win) { - arr.splice(i,1); - } - } - if (s_boundChans[origin][scope].length === 0) { - delete s_boundChans[origin][scope] - } - } - - function s_isArray(obj) { - if (Array.isArray) return Array.isArray(obj); - else { - return (obj.constructor.toString().indexOf("Array") != -1); - } - } - - // No two outstanding outbound messages may have the same id, period. Given that, a single table - // mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and - // Response messages. Entries are added to this table when requests are sent, and removed when - // responses are received. - var s_transIds = { }; - - // class singleton onMessage handler - // this function is registered once and all incoming messages route through here. This - // arrangement allows certain efficiencies, message data is only parsed once and dispatch - // is more efficient, especially for large numbers of simultaneous channels. - var s_onMessage = function(e) { - try { - var m = JSON.parse(e.data); - if (typeof m !== 'object' || m === null) throw "malformed"; - } catch(e) { - // just ignore any posted messages that do not consist of valid JSON - return; - } - - var w = e.source; - var o = e.origin; - var s, i, meth; - - if (typeof m.method === 'string') { - var ar = m.method.split('::'); - if (ar.length == 2) { - s = ar[0]; - meth = ar[1]; - } else { - meth = m.method; - } - } - - if (typeof m.id !== 'undefined') i = m.id; - - // w is message source window - // o is message origin - // m is parsed message - // s is message scope - // i is message id (or undefined) - // meth is unscoped method name - // ^^ based on these factors we can route the message - - // if it has a method it's either a notification or a request, - // route using s_boundChans - if (typeof meth === 'string') { - var delivered = false; - if (s_boundChans[o] && s_boundChans[o][s]) { - for (var i = 0; i < s_boundChans[o][s].length; i++) { - if (s_boundChans[o][s][i].win === w) { - s_boundChans[o][s][i].handler(o, meth, m); - delivered = true; - break; - } - } - } - - if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) { - for (var i = 0; i < s_boundChans['*'][s].length; i++) { - if (s_boundChans['*'][s][i].win === w) { - s_boundChans['*'][s][i].handler(o, meth, m); - break; - } - } - } - } - // otherwise it must have an id (or be poorly formed - else if (typeof i != 'undefined') { - if (s_transIds[i]) s_transIds[i](o, meth, m); - } - }; - - // Setup postMessage event listeners - if (window.addEventListener) window.addEventListener('message', s_onMessage, false); - else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage); - - /* a messaging channel is constructed from a window and an origin. - * the channel will assert that all messages received over the - * channel match the origin - * - * Arguments to Channel.build(cfg): - * - * cfg.window - the remote window with which we'll communicate - * cfg.origin - the expected origin of the remote window, may be '*' - * which matches any origin - * cfg.scope - the 'scope' of messages. a scope string that is - * prepended to message names. local and remote endpoints - * of a single channel must agree upon scope. Scope may - * not contain double colons ('::'). - * cfg.debugOutput - A boolean value. If true and window.console.log is - * a function, then debug strings will be emitted to that - * function. - * cfg.debugOutput - A boolean value. If true and window.console.log is - * a function, then debug strings will be emitted to that - * function. - * cfg.postMessageObserver - A function that will be passed two arguments, - * an origin and a message. It will be passed these immediately - * before messages are posted. - * cfg.gotMessageObserver - A function that will be passed two arguments, - * an origin and a message. It will be passed these arguments - * immediately after they pass scope and origin checks, but before - * they are processed. - * cfg.onReady - A function that will be invoked when a channel becomes "ready", - * this occurs once both sides of the channel have been - * instantiated and an application level handshake is exchanged. - * the onReady function will be passed a single argument which is - * the channel object that was returned from build(). - */ - return { - build: function(cfg) { - var debug = function(m) { - if (cfg.debugOutput && window.console && window.console.log) { - // try to stringify, if it doesn't work we'll let javascript's built in toString do its magic - try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { } - console.log("["+chanId+"] " + m); - } - } - - /* browser capabilities check */ - if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage"); - if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) { - throw("jschannel cannot run this browser, no JSON parsing/serialization"); - } - - /* basic argument validation */ - if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument"); - - if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument"); - - /* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same - * window... Not sure if we care to support that */ - if (window === cfg.window) throw("target window is same as present window -- not allowed"); - - // let's require that the client specify an origin. if we just assume '*' we'll be - // propagating unsafe practices. that would be lame. - var validOrigin = false; - if (typeof cfg.origin === 'string') { - var oMatch; - if (cfg.origin === "*") validOrigin = true; - // allow valid domains under http and https. Also, trim paths off otherwise valid origins. - else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9\.])+(?::\d+)?/))) { - cfg.origin = oMatch[0].toLowerCase(); - validOrigin = true; - } - } - - if (!validOrigin) throw ("Channel.build() called with an invalid origin"); - - if (typeof cfg.scope !== 'undefined') { - if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string'; - if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'" - } - - /* private variables */ - // generate a random and psuedo unique id for this channel - var chanId = (function () { - var text = ""; - var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length)); - return text; - })(); - - // registrations: mapping method names to call objects - var regTbl = { }; - // current oustanding sent requests - var outTbl = { }; - // current oustanding received requests - var inTbl = { }; - // are we ready yet? when false we will block outbound messages. - var ready = false; - var pendingQueue = [ ]; - - var createTransaction = function(id,origin,callbacks) { - var shouldDelayReturn = false; - var completed = false; - - return { - origin: origin, - invoke: function(cbName, v) { - // verify in table - if (!inTbl[id]) throw "attempting to invoke a callback of a nonexistent transaction: " + id; - // verify that the callback name is valid - var valid = false; - for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; } - if (!valid) throw "request supports no such callback '" + cbName + "'"; - - // send callback invocation - postMessage({ id: id, callback: cbName, params: v}); - }, - error: function(error, message) { - completed = true; - // verify in table - if (!inTbl[id]) throw "error called for nonexistent message: " + id; - - // remove transaction from table - delete inTbl[id]; - - // send error - postMessage({ id: id, error: error, message: message }); - }, - complete: function(v) { - completed = true; - // verify in table - if (!inTbl[id]) throw "complete called for nonexistent message: " + id; - // remove transaction from table - delete inTbl[id]; - // send complete - postMessage({ id: id, result: v }); - }, - delayReturn: function(delay) { - if (typeof delay === 'boolean') { - shouldDelayReturn = (delay === true); - } - return shouldDelayReturn; - }, - completed: function() { - return completed; - } - }; - } - - var setTransactionTimeout = function(transId, timeout, method) { - return window.setTimeout(function() { - if (outTbl[transId]) { - // XXX: what if client code raises an exception here? - var msg = "timeout (" + timeout + "ms) exceeded on method '" + method + "'"; - (1,outTbl[transId].error)("timeout_error", msg); - delete outTbl[transId]; - delete s_transIds[transId]; - } - }, timeout); - } - - var onMessage = function(origin, method, m) { - // if an observer was specified at allocation time, invoke it - if (typeof cfg.gotMessageObserver === 'function') { - // pass observer a clone of the object so that our - // manipulations are not visible (i.e. method unscoping). - // This is not particularly efficient, but then we expect - // that message observers are primarily for debugging anyway. - try { - cfg.gotMessageObserver(origin, m); - } catch (e) { - debug("gotMessageObserver() raised an exception: " + e.toString()); - } - } - - // now, what type of message is this? - if (m.id && method) { - // a request! do we have a registered handler for this request? - if (regTbl[method]) { - var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]); - inTbl[m.id] = { }; - try { - // callback handling. we'll magically create functions inside the parameter list for each - // callback - if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) { - for (var i = 0; i < m.callbacks.length; i++) { - var path = m.callbacks[i]; - var obj = m.params; - var pathItems = path.split('/'); - for (var j = 0; j < pathItems.length - 1; j++) { - var cp = pathItems[j]; - if (typeof obj[cp] !== 'object') obj[cp] = { }; - obj = obj[cp]; - } - obj[pathItems[pathItems.length - 1]] = (function() { - var cbName = path; - return function(params) { - return trans.invoke(cbName, params); - } - })(); - } - } - var resp = regTbl[method](trans, m.params); - if (!trans.delayReturn() && !trans.completed()) trans.complete(resp); - } catch(e) { - // automagic handling of exceptions: - var error = "runtime_error"; - var message = null; - // * if it's a string then it gets an error code of 'runtime_error' and string is the message - if (typeof e === 'string') { - message = e; - } else if (typeof e === 'object') { - // either an array or an object - // * if it's an array of length two, then array[0] is the code, array[1] is the error message - if (e && s_isArray(e) && e.length == 2) { - error = e[0]; - message = e[1]; - } - // * if it's an object then we'll look form error and message parameters - else if (typeof e.error === 'string') { - error = e.error; - if (!e.message) message = ""; - else if (typeof e.message === 'string') message = e.message; - else e = e.message; // let the stringify/toString message give us a reasonable verbose error string - } - } - - // message is *still* null, let's try harder - if (message === null) { - try { - message = JSON.stringify(e); - /* On MSIE8, this can result in 'out of memory', which - * leaves message undefined. */ - if (typeof(message) == 'undefined') - message = e.toString(); - } catch (e2) { - message = e.toString(); - } - } - - trans.error(error,message); - } - } - } else if (m.id && m.callback) { - if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback]) - { - debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")"); - } else { - // XXX: what if client code raises an exception here? - outTbl[m.id].callbacks[m.callback](m.params); - } - } else if (m.id) { - if (!outTbl[m.id]) { - debug("ignoring invalid response: " + m.id); - } else { - // XXX: what if client code raises an exception here? - if (m.error) { - (1,outTbl[m.id].error)(m.error, m.message); - } else { - if (m.result !== undefined) (1,outTbl[m.id].success)(m.result); - else (1,outTbl[m.id].success)(); - } - delete outTbl[m.id]; - delete s_transIds[m.id]; - } - } else if (method) { - // tis a notification. - if (regTbl[method]) { - // yep, there's a handler for that. - // transaction is null for notifications. - regTbl[method](null, m.params); - // if the client throws, we'll just let it bubble out - // what can we do? Also, here we'll ignore return values - } - } - } - - // now register our bound channel for msg routing - s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage); - - // scope method names based on cfg.scope specified when the Channel was instantiated - var scopeMethod = function(m) { - if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::"); - return m; - } - - // a small wrapper around postmessage whose primary function is to handle the - // case that clients start sending messages before the other end is "ready" - var postMessage = function(msg, force) { - if (!msg) throw "postMessage called with null message"; - - // delay posting if we're not ready yet. - var verb = (ready ? "post " : "queue "); - debug(verb + " message: " + JSON.stringify(msg)); - if (!force && !ready) { - pendingQueue.push(msg); - } else { - if (typeof cfg.postMessageObserver === 'function') { - try { - cfg.postMessageObserver(cfg.origin, msg); - } catch (e) { - debug("postMessageObserver() raised an exception: " + e.toString()); - } - } - - cfg.window.postMessage(JSON.stringify(msg), cfg.origin); - } - } - - var onReady = function(trans, type) { - debug('ready msg received'); - if (ready) throw "received ready message while in ready state. help!"; - - if (type === 'ping') { - chanId += '-R'; - } else { - chanId += '-L'; - } - - obj.unbind('__ready'); // now this handler isn't needed any more. - ready = true; - debug('ready msg accepted.'); - - if (type === 'ping') { - obj.notify({ method: '__ready', params: 'pong' }); - } - - // flush queue - while (pendingQueue.length) { - postMessage(pendingQueue.pop()); - } - - // invoke onReady observer if provided - if (typeof cfg.onReady === 'function') cfg.onReady(obj); - }; - - var obj = { - // tries to unbind a bound message handler. returns false if not possible - unbind: function (method) { - if (regTbl[method]) { - if (!(delete regTbl[method])) throw ("can't delete method: " + method); - return true; - } - return false; - }, - bind: function (method, cb) { - if (!method || typeof method !== 'string') throw "'method' argument to bind must be string"; - if (!cb || typeof cb !== 'function') throw "callback missing from bind params"; - - if (regTbl[method]) throw "method '"+method+"' is already bound!"; - regTbl[method] = cb; - return this; - }, - call: function(m) { - if (!m) throw 'missing arguments to call function'; - if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string"; - if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call"; - - // now it's time to support the 'callback' feature of jschannel. We'll traverse the argument - // object and pick out all of the functions that were passed as arguments. - var callbacks = { }; - var callbackNames = [ ]; - - var pruneFunctions = function (path, obj) { - if (typeof obj === 'object') { - for (var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - var np = path + (path.length ? '/' : '') + k; - if (typeof obj[k] === 'function') { - callbacks[np] = obj[k]; - callbackNames.push(np); - delete obj[k]; - } else if (typeof obj[k] === 'object') { - pruneFunctions(np, obj[k]); - } - } - } - }; - pruneFunctions("", m.params); - - // build a 'request' message and send it - var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params }; - if (callbackNames.length) msg.callbacks = callbackNames; - - if (m.timeout) - // XXX: This function returns a timeout ID, but we don't do anything with it. - // We might want to keep track of it so we can cancel it using clearTimeout() - // when the transaction completes. - setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method)); - - // insert into the transaction table - outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success }; - s_transIds[s_curTranId] = onMessage; - - // increment current id - s_curTranId++; - - postMessage(msg); - }, - notify: function(m) { - if (!m) throw 'missing arguments to notify function'; - if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string"; - - // no need to go into any transaction table - postMessage({ method: scopeMethod(m.method), params: m.params }); - }, - destroy: function () { - s_removeBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : '')); - if (window.removeEventListener) window.removeEventListener('message', onMessage, false); - else if(window.detachEvent) window.detachEvent('onmessage', onMessage); - ready = false; - regTbl = { }; - inTbl = { }; - outTbl = { }; - cfg.origin = null; - pendingQueue = [ ]; - debug("channel destroyed"); - chanId = ""; - } - }; - - obj.bind('__ready', onReady); - setTimeout(function() { - postMessage({ method: scopeMethod('__ready'), params: "ping" }, true); - }, 0); - - return obj; - } - }; - })(); - - - var BrowserSupport = (function() { - var win = window, - nav = navigator, - reason; - - // For unit testing - function setTestEnv(newNav, newWindow) { - nav = newNav; - win = newWindow; - } - - function getInternetExplorerVersion() { - var rv = -1; // Return value assumes failure. - if (nav.appName == 'Microsoft Internet Explorer') { - var ua = nav.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - if (re.exec(ua) != null) - rv = parseFloat(RegExp.$1); - } - - return rv; - } - - function checkIE() { - var ieVersion = getInternetExplorerVersion(), - ieNosupport = ieVersion > -1 && ieVersion < 8; - - if(ieNosupport) { - return "IE_VERSION"; - } - } - - function explicitNosupport() { - return checkIE(); - } - - function checkLocalStorage() { - var localStorage = 'localStorage' in win && win['localStorage'] !== null; - if(!localStorage) { - return "LOCALSTORAGE"; - } - } - - function checkPostMessage() { - if(!win.postMessage) { - return "POSTMESSAGE"; - } - } - - function isSupported() { - reason = checkLocalStorage() || checkPostMessage() || explicitNosupport(); - - return !reason; - } - - function getNoSupportReason() { - return reason; - } - - return { - /** - * Set the test environment. - * @method setTestEnv - */ - setTestEnv: setTestEnv, - /** - * Check whether the current browser is supported - * @method isSupported - * @returns {boolean} - */ - isSupported: isSupported, - /** - * Called after isSupported, if isSupported returns false. Gets the reason - * why browser is not supported. - * @method getNoSupportReason - * @returns {string} - */ - getNoSupportReason: getNoSupportReason - }; - }()); - - - // this is for calls that are non-interactive - function _open_hidden_iframe(doc) { - var iframe = doc.createElement("iframe"); - iframe.style.display = "none"; - doc.body.appendChild(iframe); - iframe.src = ipServer + "/communication_iframe"; - return iframe; - } - - function _get_relayframe_id() { - var randomString = ''; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (var i=0; i < 8; i++) { - randomString += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return randomString; - } - - function _open_relayframe(framename) { - var doc = window.document; - var iframe = doc.createElement("iframe"); - iframe.setAttribute('name', framename); - iframe.setAttribute('src', ipServer + "/relay"); - iframe.style.display = "none"; - doc.body.appendChild(iframe); - - return iframe; - } - - function _open_window(url, name_suffix) { - url = url || "about:blank"; - // we open the window initially blank, and only after our relay frame has - // been constructed do we update the location. This is done because we - // must launch the window inside a click handler, but we should wait to - // start loading it until our relay iframe is instantiated and ready. - // see issue #287 & #286 - var window_name = "_mozid_signin"; - if (name_suffix) - window_name += "_" + name_suffix; - - var dialog = window.open( - url, - window_name, - isFennec ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=700,height=375"); - - dialog.focus(); - return dialog; - } - - function _attach_event(element, name, listener) { - if (element.addEventListener) { - element.addEventListener(name, listener, false); - } - else if(element.attachEvent) { - // IE < 9 - element.attachEvent('on' + name, listener); - } - } - - function _detatch_event(element, name, listener) { - if (element.removeEventListener) { - element.removeEventListener(name, listener, false); - } - else if(element.detachEvent) { - element.detachEvent(name, listener); - } - } - - /** - * The meat and potatoes of the verified email protocol - */ - - - if (!navigator.id) { - navigator.id = {}; - } - - if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) { - var ipServer = "https://browserid.org"; - var isFennec = navigator.userAgent.indexOf('Fennec/') != -1; - - var relay_chan, w, relay_iframe; - - // keep track of these so that we can re-use/re-focus an already open window. - navigator.id.get = function(callback, options) { - var doc = document; - function cleanup() { - // we no longer remove the relay chan and iframe - // since those live for as long as the page does - - if (w) { - w.close(); - w = null; - } - - _detatch_event(window, 'unload', cleanup); - } - - if (typeof callback !== 'function') throw "navigator.id.get() requires a callback argument"; - - if (options && options.silent) { - _noninteractiveCall('getPersistentAssertion', { }, function(rv) { - callback(rv); - }, function(e, msg) { - callback(null); - }); - } else { - if (w) { - // if there is already a window open, just focus the old window. - w.focus(); - return; - } - - if (!BrowserSupport.isSupported()) { - w = _open_window(ipServer + "/unsupported_dialog"); - return; - } - - // we now have the relay iframe and channel already - // we just message it. - relay_chan.call({ - method: "get", - params: options, - success: function(rv) { - if (callback) { - // return the string representation of the JWT, the client is responsible for unpacking it. - callback(rv); - } - cleanup(); - }, - error: function(code, msg) { - // XXX: we don't make the code and msg available to the user. - if (callback) callback(null); - cleanup(); - } - }); - - // open the window now that all else is ready - w = _open_window(ipServer + "/sign_in#" + relay_iframe.getAttribute('name')); - - // if the RP window closes, close the dialog as well. - _attach_event(window, 'unload', cleanup); - } - }; - - navigator.id.getVerifiedEmail = function (callback, options) { - if (options) { - throw "getVerifiedEmail doesn't accept options. use navigator.id.get() instead."; - } - navigator.id.get(callback); - }; - - navigator.id.logout = function(callback) { - _noninteractiveCall('logout', { }, function(rv) { - callback(rv); - }, function() { - callback(null); - }); - }; - - var _noninteractiveCall = function(method, args, onsuccess, onerror) { - var doc = window.document; - var ni_iframe = _open_hidden_iframe(doc); - - var chan = Channel.build({window: ni_iframe.contentWindow, origin: ipServer, scope: "mozid_ni"}); - - function cleanup() { - chan.destroy(); - chan = undefined; - doc.body.removeChild(ni_iframe); - } - - chan.call({ - method: method, - params: args, - success: function(rv) { - if (onsuccess) { - onsuccess(rv); - } - cleanup(); - }, - error: function(code, msg) { - if (onerror) onerror(code, msg); - cleanup(); - } - }); - }; - - // set up the relay iframe - var setup_relay_iframe = function() { - if (document.body) { - var frameid = _get_relayframe_id(); - // removed browserid_relay_ prefix, cause why? - relay_iframe = _open_relayframe(frameid); - relay_chan = Channel.build({ - window: relay_iframe.contentWindow, - origin: ipServer, - scope: "mozid" - }); - - } else { - window.setTimeout(setup_relay_iframe, 100); - } - }; - - // wait for onload - // _attach_event(window, 'load', setup_relay_iframe); - // that didn't work at fixing the issue, so reverting - window.setTimeout(setup_relay_iframe, 0); - - navigator.id._getVerifiedEmailIsShimmed = true; - } -}()); - diff --git a/resources/views/communication_iframe.ejs b/resources/views/communication_iframe.ejs index 09187498e..20f771f92 100644 --- a/resources/views/communication_iframe.ejs +++ b/resources/views/communication_iframe.ejs @@ -3,7 +3,7 @@ <head><title>non-interactive iframe</title> <meta charset="utf-8"> <% if(production) { %> - <script type="text/javascript" src="/production/communication_iframe.js"></script> + <script type="text/javascript" src="/communication_iframe/production.js"></script> <% } else { %> <script type="text/javascript" src="/lib/jquery-1.6.2.min.js"></script> <script type="text/javascript" src="/lib/jschannel.js"></script> diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs index dd7de0073..4cacf37b8 100644 --- a/resources/views/dialog_layout.ejs +++ b/resources/views/dialog_layout.ejs @@ -7,7 +7,7 @@ <script type="text/javascript" src="/lib/html5shim.js"></script> <![endif]--> <% if (production) { %> - <link href="/production/dialog.css" rel="stylesheet" type="text/css"> + <link href="/dialog/css/production.min.css" rel="stylesheet" type="text/css"> <% } else { %> <link href="/css/common.css" rel="stylesheet" type="text/css"> <link href="/dialog/css/popup.css" rel="stylesheet" type="text/css"> @@ -47,7 +47,8 @@ <% if (useJavascript !== false) { %> <% if (production) { %> - <script type="text/javascript" src="/production/dialog.js"></script> + <script type="text/javascript" src="/dialog/production.js"></script> + <% } else { %> <script type="text/javascript" src="/lib/jquery-1.6.2.min.js"></script> <script type="text/javascript" src="/lib/jschannel.js"></script> diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs index 24e8760bb..638478881 100644 --- a/resources/views/layout.ejs +++ b/resources/views/layout.ejs @@ -8,8 +8,8 @@ <![endif]--> <link href='https://fonts.googleapis.com/css?family=Droid+Serif:400,400italic,700,700italic' rel='stylesheet' type='text/css'> <% if (production) { %> - <link rel="stylesheet" type="text/css" href="/production/browserid.css"> - <script src="/production/browserid.js" type="text/javascript"></script> + <link rel="stylesheet" type="text/css" href="/css/browserid.min.css"> + <script src="/pages/lib.min.js" type="text/javascript"></script> <% } else { %> <link rel="stylesheet" href="/css/common.css" type="text/css" media="screen"> <link rel="stylesheet" href="/css/style.css" type="text/css" media="screen"> diff --git a/resources/views/relay.ejs b/resources/views/relay.ejs index dc8ff4d6b..629469c0d 100644 --- a/resources/views/relay.ejs +++ b/resources/views/relay.ejs @@ -7,7 +7,8 @@ <body> Relay iframe. Woohoo! <% if (production) { %> - <script type='text/javascript' src='/production/relay.js'></script> + <script type='text/javascript' src='https://browserid.org/relay/production.js'></script> + <% } else { %> <script type='text/javascript' src='https://browserid.org/lib/jschannel.js'></script> <script type='text/javascript' src='https://browserid.org/shared/browserid.js'></script> diff --git a/scripts/compress.sh b/scripts/compress.sh index 4d20d71c5..9f0eefc28 100755 --- a/scripts/compress.sh +++ b/scripts/compress.sh @@ -4,7 +4,6 @@ cd $(dirname "$0")/.. export PATH=$PWD/node_modules/.bin:$PATH - UGLIFY=`which uglifyjs 2> /dev/null` if [ ! -x "$UGLIFY" ]; then echo "uglifyjs not found in your path. Have you npm installed lately?" @@ -19,27 +18,15 @@ fi UGLIFYCSS=`pwd`'/node_modules/uglifycss/uglifycss' -#set up the path of where all build resources go. -BUILD_PATH=`pwd`'/resources/static/build' -if [ ! -x "$BUILD_PATH" ]; then - echo "****Creating build JS/CSS directory.****" - mkdir $BUILD_PATH -fi - -#set up the path of where all production resources go. -PRODUCTION_PATH=`pwd`'/resources/static/production' -if [ ! -x "$PRODUCTION_PATH" ]; then - echo "****Creating production JS/CSS directory.****" - mkdir $PRODUCTION_PATH -fi - set -e # exit on errors echo '' -echo '****Copy include.js****' +echo '****Compressing include.js****' echo '' + cd resources/static -cp include_js/include.js $BUILD_PATH/include.uncompressed.js +mv include.js include.orig.js +$UGLIFY include.orig.js > include.js echo '' echo '****Building dialog HTML, CSS, and JS****' @@ -50,48 +37,45 @@ echo '' cd dialog/views ../../../../scripts/create_templates.js -cp templates.js $BUILD_PATH/templates.js -cd ../.. +cd ../../ +cp shared/templates.js shared/templates.js.orig +cp dialog/views/templates.js shared/templates.js # produce the dialog js -cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/channel.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js - -# produce the dialog css -cat css/common.css dialog/css/popup.css dialog/css/m.css > $BUILD_PATH/dialog.uncompressed.css +cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js shared/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/channel.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/start.js > dialog/production.js # produce the non interactive frame js -cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js shared/javascript-extensions.js shared/browserid.js shared/storage.js shared/network.js shared/user.js communication_iframe/start.js > $BUILD_PATH/communication_iframe.uncompressed.js +cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js shared/javascript-extensions.js shared/browserid.js shared/storage.js shared/network.js shared/user.js communication_iframe/start.js > communication_iframe/production.js -# produce the relay js -cat lib/jschannel.js shared/browserid.js relay/relay.js relay/start.js > $BUILD_PATH/relay.uncompressed.js +cd communication_iframe +$UGLIFY < production.js > production.min.js +cp production.js production.uncompressed.js +mv production.min.js production.js -echo '' -echo '****Building BrowserID.org HTML, CSS, and JS****' -echo '' +cd ../dialog +$UGLIFY < production.js > production.min.js +cp production.js production.uncompressed.js +mv production.min.js production.js -#produce the main site js -cat lib/jquery-1.6.2.min.js lib/json2.js lib/underscore-min.js lib/ejs.js shared/javascript-extensions.js shared/browserid.js lib/dom-jquery.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/error-messages.js shared/storage.js shared/network.js shared/user.js shared/tooltip.js shared/validation.js shared/helpers.js pages/page_helpers.js pages/browserid.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/forgot.js pages/manage_account.js pages/signin.js pages/signup.js > $BUILD_PATH/browserid.uncompressed.js +cd css +cat ../../css/common.css popup.css m.css > production.css +$UGLIFYCSS production.css > production.min.css + +cd ../../relay +cat ../lib/jschannel.js ../shared/browserid.js relay.js start.js > production.js +$UGLIFY < production.js > production.min.js +mv production.min.js production.js -# produce the main site css -cat css/common.css css/style.css css/m.css > $BUILD_PATH/browserid.uncompressed.css echo '' -echo '****Compressing all JS, CSS****' +echo '****Building BrowserID.org HTML, CSS, and JS****' echo '' -cd $PRODUCTION_PATH -# minify the JS -$UGLIFY < $BUILD_PATH/include.uncompressed.js > include.js -$UGLIFY < $BUILD_PATH/dialog.uncompressed.js > dialog.js -$UGLIFY < $BUILD_PATH/communication_iframe.uncompressed.js > communication_iframe.js -$UGLIFY < $BUILD_PATH/relay.uncompressed.js > relay.js -$UGLIFY < $BUILD_PATH/browserid.uncompressed.js > browserid.js - -# minify the CSS -$UGLIFYCSS $BUILD_PATH/browserid.uncompressed.css > browserid.css -$UGLIFYCSS $BUILD_PATH/dialog.uncompressed.css > dialog.css - -# set up new simlink for include.js. How can this part be better? -cd .. -rm include.js -ln -s $PRODUCTION_PATH/include.js +cd ../pages +# re-minimize everything together +cat ../lib/jquery-1.6.2.min.js ../lib/json2.js ../lib/underscore-min.js ../lib/ejs.js ../shared/javascript-extensions.js ../shared/browserid.js ../lib/dom-jquery.js ../shared/templates.js ../shared/renderer.js ../shared/error-display.js ../shared/screens.js ../shared/error-messages.js ../shared/storage.js ../shared/network.js ../shared/user.js ../shared/tooltip.js ../shared/validation.js ../shared/helpers.js page_helpers.js browserid.js index.js add_email_address.js verify_email_address.js forgot.js manage_account.js signin.js signup.js > lib.js +$UGLIFY < lib.js > lib.min.js + +cd ../css +cat common.css style.css m.css > browserid.css +$UGLIFYCSS browserid.css > browserid.min.css -- GitLab