summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-07 02:23:03 +0000
committerajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-07 02:23:03 +0000
commit134acbf8b30f5797eed8bdb344f7425b706a8144 (patch)
treeb0710cbfefe282f5b8dac1aaf7e8f6555b31251e /remoting
parent5272ff3b41be1ff32138375791e8296095ab964d (diff)
downloadchromium_src-134acbf8b30f5797eed8bdb344f7425b706a8144.zip
chromium_src-134acbf8b30f5797eed8bdb344f7425b706a8144.tar.gz
chromium_src-134acbf8b30f5797eed8bdb344f7425b706a8144.tar.bz2
Remove the background page, and majorly refactor Javascript + Webapp.
This removes remoting_session.html, puts the "in-session" UI into the same page, uses classes to hide/display elements, and redefines what we mean by the <section> tag. Also takes a bunch of the javascript logic and makes classes out of it as well as detangling a bit of the UI manipulation from the behavior functions. BUG=none TEST=manual Review URL: http://codereview.chromium.org/7277020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91658 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/remoting.gyp8
-rw-r--r--remoting/webapp/me2mom/background.html9
-rw-r--r--remoting/webapp/me2mom/background.js22
-rw-r--r--remoting/webapp/me2mom/choice.css70
-rw-r--r--remoting/webapp/me2mom/choice.html94
-rw-r--r--remoting/webapp/me2mom/client_session.js320
-rw-r--r--remoting/webapp/me2mom/debug_log.js42
-rw-r--r--remoting/webapp/me2mom/manifest.json1
-rw-r--r--remoting/webapp/me2mom/oauth2.js307
-rw-r--r--remoting/webapp/me2mom/oauth2_callback.html3
-rw-r--r--remoting/webapp/me2mom/plugin_settings.js3
-rw-r--r--remoting/webapp/me2mom/remoting.js315
-rw-r--r--remoting/webapp/me2mom/remoting_session.css54
-rw-r--r--remoting/webapp/me2mom/remoting_session.html38
-rw-r--r--remoting/webapp/me2mom/remoting_session.js317
-rw-r--r--remoting/webapp/me2mom/storage.js96
-rw-r--r--remoting/webapp/me2mom/xhr.js154
17 files changed, 1041 insertions, 812 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 1442876..66d23fc 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -34,8 +34,6 @@
],
'remoting_it2me_files': [
'resources/chromoting128.png',
- 'webapp/me2mom/background.html',
- 'webapp/me2mom/background.js',
'webapp/me2mom/choice.css',
'webapp/me2mom/choice.html',
'webapp/me2mom/cs_oauth2_trampoline.js',
@@ -49,11 +47,9 @@
'webapp/me2mom/oauth2_callback.html',
'webapp/me2mom/plugin_settings.js',
'webapp/me2mom/remoting.js',
- 'webapp/me2mom/remoting_session.css',
- 'webapp/me2mom/remoting_session.html',
- 'webapp/me2mom/remoting_session.js',
+ 'webapp/me2mom/client_session.js',
'webapp/me2mom/spinner.gif',
- 'webapp/me2mom/storage.js',
+ 'webapp/me2mom/xhr.js',
],
},
diff --git a/remoting/webapp/me2mom/background.html b/remoting/webapp/me2mom/background.html
deleted file mode 100644
index b3570d3..0000000
--- a/remoting/webapp/me2mom/background.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!doctype html>
-<!--
-Copyright (c) 2011 The Chromium Authors. All rights reserved.
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<html>
- <script src="background.js"></script>
-</html>
diff --git a/remoting/webapp/me2mom/background.js b/remoting/webapp/me2mom/background.js
deleted file mode 100644
index 0da714b..0000000
--- a/remoting/webapp/me2mom/background.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var remoting = {};
-
-function setItem(key, value) {
- window.localStorage.setItem(key, value);
-}
-
-function getItem(key, defaultValue) {
- var result = window.localStorage.getItem(key);
- return (result != null) ? result : defaultValue;
-}
-
-function removeItem(key) {
- window.localStorage.removeItem(key);
-}
-
-function clearAll() {
- window.localStorage.clear();
-}
diff --git a/remoting/webapp/me2mom/choice.css b/remoting/webapp/me2mom/choice.css
index ab592f1..7efe829 100644
--- a/remoting/webapp/me2mom/choice.css
+++ b/remoting/webapp/me2mom/choice.css
@@ -9,7 +9,6 @@ a {
text-decoration: none;
}
-
body {
background: -webkit-gradient(radial, center center, 0, center center, 400,
from(rgb(254, 254, 254)),
@@ -50,11 +49,7 @@ button[disabled], .button[disabled]:hover {
-webkit-box-shadow: none;
}
-footer {
- font-size: 14px;
-}
-
-header h1 {
+h1 {
font-size: 24px;
font-weight: normal;
margin-left: 10px;
@@ -65,11 +60,6 @@ label {
font-weight: bold;
}
-section {
- margin-top: 17px;
- padding-bottom: 20px;
-}
-
/* Classes */
.access-code-digit-group {
@@ -101,6 +91,21 @@ section {
text-align: center;
}
+.choice-header {
+ font-size: 24px;
+ font-weight: normal;
+ margin-left: 10px;
+}
+
+.choice-footer {
+ font-size: 14px;
+}
+
+.choice-panel {
+ margin-top: 17px;
+ padding-bottom: 20px;
+}
+
.client-element {
/* Class used to denote client UI elements. */
}
@@ -116,6 +121,11 @@ section {
*/
}
+.hidden {
+ display: none;
+}
+
+
.host-element {
/* Class used to denote host UI elements. */
}
@@ -136,16 +146,12 @@ section {
*/
}
-.hidden {
- display: none;
-}
-
.waiting {
color: rgb(180, 180, 180);
}
/* Ids */
-#auth-panel {
+#control-panel {
border-bottom: 2px solid gray;
padding: 5px 10px;
}
@@ -172,7 +178,7 @@ section {
text-align: center;
}
-#main-panel {
+#choice-mode {
color: rgb(115, 115, 115);
font-size: 16px;
margin: 100px auto 0 auto;
@@ -184,8 +190,8 @@ section {
float: right;
}
-#email-status {
- margin-right: 0.5ex;
+#client-footer-text, #host-footer-text {
+ text-align: center;
}
#current-email {
@@ -200,18 +206,36 @@ section {
margin: 25px 0 10px 0;
}
+#email-status {
+ margin-right: 0.5ex;
+}
+
#icon {
height: 64px;
width: 64px;
}
+
#oauth2-entry {
width: 400px;
}
-#server-response {
- font-weight: bolder;
+#session-mode {
+ overflow: auto;
+ width: 100%;
+ -webkit-user-select: none;
}
-#client-footer, #host-footer {
- text-align: center;
+#session-status-msg {
+ /*
+ clear: both; forces the session-controls div to size appropriately for
+ session-buttons.
+ */
+ clear: both;
+ font-family: monospace;
+ font-size: small;
+ margin: 5px;
+}
+
+#server-response {
+ font-weight: bolder;
}
diff --git a/remoting/webapp/me2mom/choice.html b/remoting/webapp/me2mom/choice.html
index 1ca4c08..1b95111 100644
--- a/remoting/webapp/me2mom/choice.html
+++ b/remoting/webapp/me2mom/choice.html
@@ -14,53 +14,64 @@ found in the LICENSE file.
<link rel="stylesheet" href="debug_log.css" />
<link rel="stylesheet" href="main.css" />
<link rel="stylesheet" href="choice.css" />
+ <script src="client_session.js"></script>
<script src="debug_log.js"></script>
<script src="oauth2.js"></script>
<script src="plugin_settings.js"></script>
<script src="remoting.js"></script>
+ <script src="xhr.js"></script>
<title>Chromoting</title>
</head>
- <body>
- <div id="auth-panel">
- <span id="email-status" class="display-inline">
+ <body onLoad="remoting.init();">
+ <nav id="control-panel">
+ <span id="email-status">
<span class="auth-status-label">Email:</span>
- <span id="current-email" class="display-inline"></span>
+ <span id="current-email"></span>
</span> <!-- email-status -->
- <span id="oauth2-entry">
+ <span id="oauth2-entry" class="client-element host-element">
<form>
<span class="auth-status-label">OAuth2 Token:</span>
- <input id="oauth2-token-button" class="display-inline" type="button"
- onclick="remoting.oauth2.openOAuth2Window(); return false;"
+ <input id="oauth2-token-button" type="button"
+ onclick="remoting.oauth2.doAuthRedirect();"
value="Get OAuth2 Token&hellip;" />
- <input id="oauth2-clear-button" class="display-inline" type="button"
- onclick="clearOAuth2(); return false;" value="Revoke" />
+ <input id="oauth2-clear-button" type="button"
+ onclick="remoting.clearOAuth2();"
+ value="Revoke" />
</form>
</span> <!-- oauth2-entry -->
+ <span id="session-controls" class="in-session-element">
+ <form id="session-buttons">
+ <input id="scale-to-fit-toggle" type="button"
+ value="Scale to fit" onclick="remoting.toggleScaleToFit();"/>
+ </form>
+ <span id="session-status-message">Initializing...</span>
+ </span> <!-- session-controls -->
+
<span id="debug-enable">
<form>
- <input id="debug-log-toggle" class="display-inline" type="button"
- value="Debug Log" onclick="toggleDebugLog(); return false;"/>
+ <input id="debug-log-toggle" type="button" value="Debug Log"
+ onclick="remoting.toggleDebugLog();"/>
</form>
</span> <!-- debug-enable -->
- </div> <!-- auth-panel -->
+ </nav> <!-- control-panel -->
- <div id="main-panel" class="hidden">
- <header>
+ <section id="choice-mode" class="mode hidden host-element client-element">
+ <header class="choice-header">
<img id="icon" src="chromoting128.png">
- <h1 class="icon-label host-element display-inline">
+ <h1 class="icon-label host-element">
Chromoting&nbsp;&rsaquo;&nbsp;Share
</h1>
- <h1 class="icon-label client-element display-inline">
+ <h1 class="icon-label client-element">
Chromoting&nbsp;&rsaquo;&nbsp;Connect
</h1>
<img id="divider-top" src="dividertop.png">
</header>
- <section id="host-section" class="host-element">
+ <div id="host-panel" class="host-element">
<div id="unshared" class="mode">
<div class="description">
With Chromoting you can easily let another Chrome user see and
@@ -68,7 +79,7 @@ found in the LICENSE file.
</div>
<div class="centered-button">
<button id="share-button" class="auth-status-control"
- type="button" onclick="tryShare(); return false;">
+ type="button" onclick="remoting.tryShare();">
Share this computer
</button>
</div>
@@ -97,7 +108,7 @@ found in the LICENSE file.
Your desktop is currently being shared.
</div>
<div class="centered-button">
- <button type="button" onclick="cancelShare(); return false;">
+ <button type="button" onclick="remoting.cancelShare();">
Stop sharing
</button>
</div>
@@ -109,14 +120,14 @@ found in the LICENSE file.
</div>
<div class="centered-button">
<button type="button" class="auth-status-control"
- onclick="setHostMode('unshared'); return false;">
+ onclick="remoting.setHostMode('unshared');">
OK
</button>
</div>
</div> <!-- share-failed -->
- </section> <!-- host-section -->
+ </div> <!-- host-panel -->
- <section id="client-section" class="client-element">
+ <div id="client-panel" class="client-element">
<div id="unconnected" class="mode">
<div class="description">
Have the user whose computer you wish to access click
@@ -124,7 +135,7 @@ found in the LICENSE file.
their access code to you.
</div>
<div id="access-code-entry-row">
- <form action="" onsubmit="tryConnect(); return false;">
+ <form action="" onsubmit="remoting.tryConnect(); return false;">
<label class="auth-status-control" for="access-code-entry">
Access code
</label>
@@ -159,50 +170,53 @@ found in the LICENSE file.
</div>
<div class="centered-button">
<button type="button" class="auth-status-control"
- onclick="setClientMode('unconnected'); return false;">
+ onclick="remoting.setClientMode('unconnected');">
OK
</button>
</div>
</div> <!-- connect-failed -->
- </section> <!-- client-section -->
+ </div> <!-- client-panel -->
- <footer>
+ <footer class="choice-footer">
<img id="divider-bottom" src="dividerbottom.png">
- <div id="client-footer" class="client-element">
+ <div id="client-footer-text" class="client-element">
Click here to
<a class="switch-mode"
href="#"
- onclick="setGlobalModePersistent(remoting.HOST_MODE);
- return false;">share this computer</a>
+ onclick="remoting.setAppMode(remoting.AppMode.HOST);">
+ share this computer</a>
with another user.
- </div> <!-- client-footer -->
+ </div> <!-- client-footer-text -->
- <div id="host-footer" class="host-element">
+ <div id="host-footer-text" class="host-element">
Click here to <a class="switch-mode"
href="#"
- onclick="setGlobalModePersistent(remoting.CLIENT_MODE);
- return false;">
+ onclick="remoting.setAppMode(remoting.AppMode.CLIENT);">
access a shared computer</a>.
- </div> <!-- host-footer -->
+ </div> <!-- host-footer-text -->
<div id="waiting-footer">
<img src="spinner.gif">
<span class="waiting icon-label">waiting for connection&hellip;</span>
<button id="cancel-button"
- onclick="cancelPendingOperation();">
+ onclick="remoting.cancelPendingOperation();">
Cancel
</button>
</div> <!-- waiting-footer -->
</footer>
- </div> <!-- main-panel -->
+ </section> <!-- choice-mode -->
+
+ <section id="session-mode" class="mode in-session-element hidden">
+ </section> <!-- session-mode -->
- <div id="loading-panel">
+
+ <section id="loading-mode">
<em>Loading&hellip;</em>
- </div>
+ </section>
- <div id="plugin-wrapper">
+ <div id="host-plugin-container" class="hidden">
</div>
- <div id="debug-log">
+ <div id="debug-log" class="hidden">
</div>
</body>
</html>
diff --git a/remoting/webapp/me2mom/client_session.js b/remoting/webapp/me2mom/client_session.js
new file mode 100644
index 0000000..87102682
--- /dev/null
+++ b/remoting/webapp/me2mom/client_session.js
@@ -0,0 +1,320 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Session class that handles creation and teardown of a remoting session.
+ *
+ * This abstracts a <embed> element and controls the plugin which does the
+ * actual remoting work. There should be no UI code inside this class. It
+ * should be purely thought of as a controller of sorts.
+ */
+
+"use strict";
+
+var remoting = remoting || {};
+
+(function() {
+/**
+ * @param {string} hostJid The jid of the host to connect to.
+ * @param {string} hostPublicKey The base64 encoded version of the host's
+ * public key.
+ * @param {string} accessCode The access code for the IT2Me connection.
+ * @param {string} email The username for the talk network.
+ * @param {function(remoting.ClientSession.State):void} onStateChange
+ * The callback to invoke when the session changes state.
+ * @constructor
+ */
+remoting.ClientSession = function(hostJid, hostPublicKey, accessCode, email,
+ onStateChange) {
+ this.state = remoting.ClientSession.CREATED;
+
+ this.hostJid = hostJid;
+ this.hostPublicKey = hostPublicKey;
+ this.accessCode = accessCode;
+ this.email = email;
+ this.clientJid = '';
+ this.onStateChange = onStateChange;
+};
+
+/** @enum {number} */
+remoting.ClientSession.State = {
+ UNKNOWN: 0,
+ CREATED: 1,
+ BAD_PLUGIN_VERSION: 2,
+ UNKNOWN_PLUGIN_ERROR: 3,
+ CONNECTING: 4,
+ INITIALIZING: 5,
+ CONNECTED: 6,
+ CLOSED: 7,
+ CONNECTION_FAILED: 8
+};
+
+/**
+ * The current state of the session.
+ * @type {remoting.ClientSession.State}
+ */
+remoting.ClientSession.prototype.state = remoting.ClientSession.State.UNKNOWN;
+
+/**
+ * Chromoting session API version (for this javascript).
+ * This is compared with the plugin API version to verify that they are
+ * compatible.
+ *
+ * @const
+ */
+remoting.ClientSession.prototype.API_VERSION_ = 2;
+
+/**
+ * Server used to bridge into the Jabber network for establishing Jingle
+ * connections.
+ *
+ * @const
+ */
+remoting.ClientSession.prototype.HTTP_XMPP_PROXY_ =
+ 'https://chromoting-httpxmpp-oauth2-dev.corp.google.com';
+
+/**
+ * The oldest API version that we support.
+ * This will differ from the |API_VERSION_| if we maintain backward
+ * compatibility with older API versions.
+ *
+ * @const
+ */
+remoting.ClientSession.prototype.API_MIN_VERSION_ = 1;
+
+/**
+ * Callback to invoke when the state is changed.
+ *
+ * @type {function(remoting.ClientSession.State):void}
+ */
+remoting.ClientSession.prototype.onStateChange = null;
+
+/**
+ * Adds <embed> tag to |container| and readies the sesion object.
+ *
+ * @param {Element} container The element to add the plugin to.
+ * @param {string} oauth2AccessToken A valid OAuth2 access token.
+ * @return {void}
+ */
+remoting.ClientSession.prototype.createPluginAndConnect =
+ function(container, oauth2AccessToken) {
+ this.plugin = document.createElement('embed');
+ this.plugin.id = 'session-client-plugin';
+ this.plugin.src = 'about://none';
+ this.plugin.type = 'pepper-application/x-chromoting';
+ this.plugin.className = 'client-plugin';
+ container.appendChild(this.plugin);
+
+ if (!this.isPluginVersionSupported_(this.plugin)) {
+ // TODO(ajwong): Remove from parent.
+ delete this.plugin;
+ this.setState_(remoting.ClientSession.BAD_PLUGIN_VERSION);
+ return;
+ }
+
+ var that = this;
+ this.plugin.sendIq = function(msg) { that.sendIq_(msg); };
+ this.plugin.debugInfo = function(msg) {
+ remoting.debug.log('plugin: ' + msg);
+ };
+
+ // TODO(ajwong): Is it even worth having this class handle these events?
+ // Or would it be better to just allow users to pass in their own handlers
+ // and leave these blank by default?
+ this.plugin.connectionInfoUpdate = function() {
+ that.connectionInfoUpdateCallback();
+ };
+ this.plugin.desktopSizeUpdate = function() { that.onDesktopSizeChanged_(); };
+
+ // For IT2Me, we are pre-authorized so there is no login challenge.
+ this.plugin.loginChallenge = function () {};
+
+ // TODO(garykac): Clean exit if |connect| isn't a function.
+ if (typeof this.plugin.connect === 'function') {
+ this.registerConnection_(oauth2AccessToken);
+ } else {
+ remoting.debug.log('ERROR: remoting plugin not loaded');
+ this.setState_(remoting.ClientSession.UNKNOWN_PLUGIN_ERROR);
+ }
+};
+
+/**
+ * Sends an IQ stanza via the http xmpp proxy.
+ *
+ * @param {string} msg XML string of IQ stanza to send to server.
+ * @return {void}
+ */
+remoting.ClientSession.prototype.sendIq_ = function(msg) {
+ remoting.debug.log('Sending Iq: ' + msg);
+
+ // Extract the top level fields of the Iq packet.
+ // TODO(ajwong): Can the plugin just return these fields broken out.
+ var parser = new DOMParser();
+ var iqNode = parser.parseFromString(msg, 'text/xml').firstChild;
+ var serializer = new XMLSerializer();
+ var parameters = {
+ 'to': iqNode.getAttribute('to'),
+ 'payload_xml': serializer.serializeToString(iqNode.firstChild),
+ 'id': iqNode.getAttribute('id') || '1',
+ 'type': iqNode.getAttribute('type'),
+ 'host_jid': this.hostJid
+ };
+
+ remoting.xhr.post(this.HTTP_XMPP_PROXY_ + '/sendIq', function(xhr) {},
+ parameters, {}, true);
+};
+
+/**
+ * Executes a poll loop on the server for more IQ packet to feed to the plugin.
+ *
+ * @return {void}
+ */
+remoting.ClientSession.prototype.feedIq_ = function() {
+ var that = this;
+
+ var onIq = function(xhr) {
+ if (xhr.status == 200) {
+ remoting.debug.log('Receiving Iq: --' + xhr.responseText + '--');
+ that.plugin.onIq(xhr.responseText);
+ }
+ if (xhr.status == 200 || xhr.status == 204) {
+ // Poll again.
+ that.feedIq_();
+ } else {
+ remoting.debug.log('HttpXmpp gateway returned code: ' + xhr.status);
+ that.plugin.disconnect();
+ that.setState_(remoting.ClientSession.CONNECTION_FAILED);
+ }
+ }
+
+ remoting.xhr.get(this.HTTP_XMPP_PROXY_ + '/readIq', onIq,
+ {'host_jid': this.hostJid}, {}, true);
+};
+
+/**
+ * @param {Element} plugin The embed element for the plugin.
+ * @return {boolean}
+ */
+remoting.ClientSession.prototype.isPluginVersionSupported_ = function(plugin) {
+ return this.API_VERSION_ >= plugin.apiMinVersion &&
+ plugin.apiVersion >= this.API_MIN_VERSION_;
+};
+
+/**
+ * Registers a new connection with the HttpXmpp proxy.
+ *
+ * @param {string} oauth2AccessToken A valid OAuth2 access token.
+ * @return {void}
+ */
+remoting.ClientSession.prototype.registerConnection_ =
+ function(oauth2AccessToken) {
+ var parameters = {
+ 'host_jid': this.hostJid,
+ 'username': this.email,
+ 'password': oauth2AccessToken
+ };
+
+ var that = this;
+ var onRegistered = function(xhr) {
+ if (xhr.status != 200) {
+ remoting.debug.log('FailedToConnect: --' + xhr.responseText +
+ '-- (status=' + xhr.status + ')');
+ that.setState_(remoting.ClientSession.CONNECTION_FAILED);
+ return;
+ }
+
+ remoting.debug.log('Receiving Iq: --' + xhr.responseText + '--');
+ that.clientJid = xhr.responseText;
+
+ // TODO(ajwong): Remove old version support.
+ if (that.plugin.API_VERSION_ >= 2) {
+ that.plugin.connect(that.hostJid, that.hostPublicKey, that.clientJid,
+ that.accessCode);
+ } else {
+ that.plugin.connect(that.hostJid, that.clientJid, that.accessCode);
+ }
+ that.feedIq_();
+ };
+
+ remoting.xhr.post(this.HTTP_XMPP_PROXY_ + '/newConnection',
+ onRegistered, parameters, {}, true);
+};
+
+/**
+ * Callback that the plugin invokes to indicate that the connection
+ * status has changed.
+ */
+remoting.ClientSession.prototype.connectionInfoUpdateCallback = function () {
+ var state = this.plugin.status;
+
+ // TODO(ajwong): We're doing silly type translation here. Any way to avoid?
+ if (state == this.plugin.STATUS_UNKNOWN) {
+ this.setState_(remoting.ClientSession.State.UNKNOWN);
+ } else if (state == this.plugin.STATUS_CONNECTING) {
+ this.setState_(remoting.ClientSession.State.CONNECTING);
+ } else if (state == this.plugin.STATUS_INITIALIZING) {
+ this.setState_(remoting.ClientSession.State.INITIALIZING);
+ } else if (state == this.plugin.STATUS_CONNECTED) {
+ this.onDesktopSizeChanged_();
+ this.setState_(remoting.ClientSession.State.CONNECTED);
+ } else if (state == this.plugin.STATUS_CLOSED) {
+ this.setState_(remoting.ClientSession.State.CLOSED);
+ } else if (state == this.plugin.STATUS_FAILED) {
+ this.setState_(remoting.ClientSession.State.CONNECTION_FAILED);
+ }
+};
+
+/**
+ * @param {remoting.ClientSession.State} state The new state for the session.
+ * @return {void}
+ */
+remoting.ClientSession.prototype.setState_ = function(state) {
+ this.state = state;
+ if (this.onStateChange) {
+ this.onStateChange(this.state);
+ }
+};
+
+/**
+ * This is a callback that gets called when the desktop size contained in the
+ * the plugin has changed.
+ *
+ * @return {void}
+ */
+remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() {
+ var width = this.plugin.desktopWidth;
+ var height = this.plugin.desktopHeight;
+ remoting.debug.log('desktop size changed: ' + width + 'x' + height);
+ this.plugin.width = width;
+ this.plugin.height = height;
+};
+
+/**
+ * Informs the plugin that it should scale itself.
+ *
+ * @param {boolean} shouldScale If the plugin should scale itself.
+ * @return {void}
+ */
+remoting.ClientSession.prototype.toggleScaleToFit = function (shouldScale) {
+ this.plugin.setScaleToFit(shouldScale);
+};
+
+/**
+ * Returns an associative array with a set of stats for this connection.
+ *
+ * @return {Object}
+ */
+remoting.ClientSession.prototype.stats = function () {
+ return {
+ 'video_bandwidth': this.plugin.videoBandwidth,
+ 'capture_latency': this.plugin.videoCaptureLatency,
+ 'encode_latency': this.plugin.videoEncodeLatency,
+ 'decode_latency': this.plugin.videoDecodeLatency,
+ 'render_latency': this.plugin.videoRenderLatency,
+ 'roundtrip_latency': this.plugin.roundTripLatency
+ };
+};
+
+}());
diff --git a/remoting/webapp/me2mom/debug_log.js b/remoting/webapp/me2mom/debug_log.js
index a848b2d..2a1ad1c 100644
--- a/remoting/webapp/me2mom/debug_log.js
+++ b/remoting/webapp/me2mom/debug_log.js
@@ -2,41 +2,43 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Maximum numer of lines to record in the debug log.
-// Only the most recent <n> lines are displayed.
-var MAX_DEBUG_LOG_SIZE = 1000;
+/**
+ * @fileoverview
+ * Module to support logging debug messages.
+ */
-function toggleDebugLog() {
- debugLog = document.getElementById('debug-log');
- toggleButton = document.getElementById('debug-log-toggle');
+"use strict";
+var remoting = remoting || {};
- if (!debugLog.style.display || debugLog.style.display == 'none') {
- debugLog.style.display = 'block';
- toggleButton.value = 'Hide Debug Log';
- } else {
- debugLog.style.display = 'none';
- toggleButton.value = 'Show Debug Log';
- }
+(function() {
+
+/** @constructor */
+remoting.DebugLog = function(logElement) {
+ this.debugLog = logElement;
}
+// Maximum numer of lines to record in the debug log.
+// Only the most recent <n> lines are displayed.
+var MAX_DEBUG_LOG_SIZE = 1000;
+
/**
* Add the given message to the debug log.
*
* @param {string} message The debug info to add to the log.
*/
-function addToDebugLog(message) {
- var debugLog = document.getElementById('debug-log');
-
+remoting.DebugLog.prototype.log = function(message) {
// Remove lines from top if we've hit our max log size.
- if (debugLog.childNodes.length == MAX_DEBUG_LOG_SIZE) {
- debugLog.removeChild(debugLog.firstChild);
+ if (this.debugLog.childNodes.length == MAX_DEBUG_LOG_SIZE) {
+ this.debugLog.removeChild(this.debugLog.firstChild);
}
// Add the new <p> to the end of the debug log.
var p = document.createElement('p');
p.appendChild(document.createTextNode(message));
- debugLog.appendChild(p);
+ this.debugLog.appendChild(p);
// Scroll to bottom of div
- debugLog.scrollTop = debugLog.scrollHeight;
+ this.debugLog.scrollTop = this.debugLog.scrollHeight;
}
+
+}());
diff --git a/remoting/webapp/me2mom/manifest.json b/remoting/webapp/me2mom/manifest.json
index fd08a87..12a4392 100644
--- a/remoting/webapp/me2mom/manifest.json
+++ b/remoting/webapp/me2mom/manifest.json
@@ -7,7 +7,6 @@
"local_path": "choice.html"
}
},
- "background_page": "background.html",
"icons": {
"128": "chromoting128.png"
},
diff --git a/remoting/webapp/me2mom/oauth2.js b/remoting/webapp/me2mom/oauth2.js
index a8bf740..c7a0c72 100644
--- a/remoting/webapp/me2mom/oauth2.js
+++ b/remoting/webapp/me2mom/oauth2.js
@@ -2,161 +2,268 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Declare an OAuth2 class to handle retrieval/storage of an OAuth2 token.
-//
-// Ideally, this should implement the OAuth2 PostMessage flow to avoid needing
-// to copy and paste a code, but that does not support extension URL schemes
-// quite yet. Instead, we currently use the native app flow with an
-// authorization code that the user must cut/paste.
+/**
+ * @fileoverview
+ * OAuth2 class that handles retrieval/storage of an OAuth2 token.
+ *
+ * Uses a content script to trampoline the OAuth redirect page back into the
+ * extension context. This works around the lack of native support for
+ * chrome-extensions in OAuth2.
+ */
-var remoting = chrome.extension.getBackgroundPage().remoting;
+"use strict";
-function OAuth2() {
- this.OAUTH2_REFRESH_TOKEN_NAME = 'oauth2_refresh_token';
+var remoting = remoting || {};
- this.client_id = encodeURIComponent(
+(function() {
+/** @constructor */
+remoting.OAuth2 = function() {
+}
+
+// Constants representing keys used for storing persistent state.
+remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
+remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
+
+// Constants for parameters used in retrieving the OAuth2 credentials.
+remoting.OAuth2.prototype.CLIENT_ID_ =
'440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' +
- 'apps.googleusercontent.com');
- this.client_secret = encodeURIComponent('W2ieEsG-R1gIA4MMurGrgMc_');
- this.scope = encodeURIComponent(
+ 'apps.googleusercontent.com';
+remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_';
+remoting.OAuth2.prototype.SCOPE_ =
'https://www.googleapis.com/auth/chromoting ' +
'https://www.googleapis.com/auth/googletalk ' +
- 'https://www.googleapis.com/auth/userinfo#email');
- this.redirect_uri = encodeURIComponent(
+ 'https://www.googleapis.com/auth/userinfo#email';
+remoting.OAuth2.prototype.REDIRECT_URI_ =
'https://chromoting-httpxmpp-oauth2-dev.corp.google.com' +
- '/oauth2_trampoline');
-}
+ '/oauth2_trampoline';
+remoting.OAuth2.prototype.OAUTH2_TOKEN_ENDPOINT_ =
+ 'https://accounts.google.com/o/oauth2/token';
-OAuth2.prototype.isAuthenticated = function() {
- if(this.getRefreshToken()) {
+/** @return {boolean} */
+remoting.OAuth2.prototype.isAuthenticated = function() {
+ if (this.getRefreshToken()) {
return true;
}
return false;
}
-OAuth2.prototype.clear = function() {
- remoting.removeItem(this.OAUTH2_REFRESH_TOKEN_NAME);
- delete this.access_token;
- delete this.access_token_expiration;
+/**
+ * Removes all storage, and effectively unauthenticates the user.
+ *
+ * @return {void}
+ */
+remoting.OAuth2.prototype.clear = function() {
+ window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
+ this.clearAccessToken();
}
-OAuth2.prototype.setRefreshToken = function(token) {
- remoting.setItem(this.OAUTH2_REFRESH_TOKEN_NAME, token);
+/**
+ * @param {string} token The new refresh token.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.setRefreshToken = function(token) {
+ window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
+ this.clearAccessToken();
}
-OAuth2.prototype.getRefreshToken = function(token) {
- return remoting.getItem(this.OAUTH2_REFRESH_TOKEN_NAME);
+/** @return {string} */
+remoting.OAuth2.prototype.getRefreshToken = function() {
+ var value = window.localStorage.getItem(this.KEY_REFRESH_TOKEN_);
+ if (value) {
+ return unescape(value);
+ }
+ return value;
}
-OAuth2.prototype.setAccessToken = function(token, expiration) {
- this.access_token = token;
- // Offset by 30 seconds to account for RTT issues.
- // TODO(ajwong): See if this is necessary, or of the protocol already
- // accounts for RTT.
- this.access_token_expiration = expiration - 30000;
+/**
+ * @param {string} token The new access token.
+ * @param {number} expiration Expiration time in milliseconds since epoch.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.setAccessToken = function(token, expiration) {
+ var access_token = {'token': token, 'expiration': expiration};
+ window.localStorage.setItem(this.KEY_ACCESS_TOKEN_,
+ JSON.stringify(access_token));
}
-OAuth2.prototype.needsNewAccessToken = function() {
+/**
+ * Returns the current access token, setting it to a invalid value if none
+ * existed before.
+ *
+ * @return {{token: string, expiration: number}}
+ */
+remoting.OAuth2.prototype.getAccessTokenInternal_ = function() {
+ if (!window.localStorage.getItem(this.KEY_ACCESS_TOKEN_)) {
+ // Always be able to return structured data.
+ this.setAccessToken('', 0);
+ }
+ var accessToken =
+ JSON.parse(window.localStorage.getItem(this.KEY_ACCESS_TOKEN_));
+
+ if (!('token' in accessToken && 'expiration' in accessToken)) {
+ console.log("Invalid access token stored.");
+ return {'token': '', 'expiration': 0};
+ }
+
+ return accessToken;
+}
+
+/**
+ * Returns true if the access token is expired, or otherwise invalid.
+ *
+ * Will throw if !isAuthenticated().
+ *
+ * @return {boolean}
+ */
+remoting.OAuth2.prototype.needsNewAccessToken = function() {
if (!this.isAuthenticated()) {
throw "Not Authenticated.";
}
- if (!this.access_token) {
+ var access_token = this.getAccessTokenInternal_();
+ if (!access_token['token']) {
return true;
}
- if (Date.now() > this.access_token_expiration) {
+ if (Date.now() > access_token['expiration']) {
return true;
}
return false;
}
-OAuth2.prototype.getAccessToken = function() {
+/**
+ * Returns the current access token.
+ *
+ * Will throw if !isAuthenticated() or needsNewAccessToken().
+ *
+ * @return {{token: string, expiration: number}}
+ */
+remoting.OAuth2.prototype.getAccessToken = function() {
if (this.needsNewAccessToken()) {
throw "Access Token expired.";
}
- return this.access_token;
+ return this.getAccessTokenInternal_()['token'];
+}
+
+/** @return {void} */
+remoting.OAuth2.prototype.clearAccessToken = function() {
+ window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
+}
+
+/**
+ * Update state based on token response from the OAuth2 /token endpoint.
+ *
+ * @param {XMLHttpRequest} xhr The XHR object for this request.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.processTokenResponse_ = function(xhr) {
+ if (xhr.status == 200) {
+ var tokens = JSON.parse(xhr.responseText);
+ if ('refresh_token' in tokens) {
+ this.setRefreshToken(tokens['refresh_token']);
+ }
+
+ // Offset by 30 seconds to account for RTT issues.
+ // TODO(ajwong): See if this is necessary, or of the protocol already
+ // accounts for RTT.
+ this.setAccessToken(tokens['access_token'],
+ tokens['expires_in'] * 1000 + Date.now() - 30000);
+ } else {
+ console.log("Failed to get tokens. Status: " + xhr.status +
+ " response: " + xhr.responseText);
+ }
}
-OAuth2.prototype.refreshAccessToken = function(on_done) {
+/**
+ * Asynchronously retrieves a new access token from the server.
+ *
+ * Will throw if !isAuthenticated().
+ *
+ * @param {function(): void} onDone Callback to invoke on completion.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.refreshAccessToken = function(onDone) {
if (!this.isAuthenticated()) {
throw "Not Authenticated.";
}
- var xhr = new XMLHttpRequest();
- var that = this;
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) {
- return;
- }
- if (xhr.status == 200) {
- tokens = JSON.parse(xhr.responseText);
- that.setAccessToken(tokens['access_token'],
- tokens['expires_in'] * 1000 + Date.now());
- } else {
- console.log("Refresh access token failed. Status: " + xhr.status +
- " response: " + xhr.responseText);
- }
- on_done();
+
+ var parameters = {
+ 'client_id': this.CLIENT_ID_,
+ 'client_secret': this.CLIENT_SECRET_,
+ 'refresh_token': this.getRefreshToken(),
+ 'grant_type': 'refresh_token'
};
- xhr.open('POST', 'https://accounts.google.com/o/oauth2/token', true);
- xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
- var post_data = 'client_id=' + this.client_id
- + '&client_secret=' + this.client_secret
- + '&refresh_token=' + encodeURIComponent(this.getRefreshToken())
- + '&grant_type=refresh_token';
- xhr.send(post_data);
+
+ var that = this;
+ remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
+ function(xhr) {
+ that.processTokenResponse_(xhr);
+ onDone();
+ },
+ parameters);
}
-OAuth2.prototype.openOAuth2Window = function() {
- var GET_CODE_URL = 'https://accounts.google.com/o/oauth2/auth?'
- + 'client_id=' + this.client_id
- + '&redirect_uri=' + this.redirect_uri
- + '&scope=' + this.scope
- + '&response_type=code';
- window.open(GET_CODE_URL);
+/**
+ * Redirect page to get a new OAuth2 Refresh Token.
+ *
+ * @return {void}
+ */
+remoting.OAuth2.prototype.doAuthRedirect = function() {
+ var GET_CODE_URL = 'https://accounts.google.com/o/oauth2/auth?' +
+ remoting.xhr.urlencodeParamHash({
+ 'client_id': this.CLIENT_ID_,
+ 'redirect_uri': this.REDIRECT_URI_,
+ 'scope': this.SCOPE_,
+ 'response_type': 'code'
+ });
+ window.location.replace(GET_CODE_URL);
}
-OAuth2.prototype.exchangeCodeForToken = function(code, on_done) {
- var xhr = new XMLHttpRequest();
- var that = this;
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) {
- return;
- }
- if (xhr.status == 200) {
- tokens = JSON.parse(xhr.responseText);
- that.setRefreshToken(tokens['refresh_token']);
- that.setAccessToken(tokens['access_token'],
- tokens['expires_in'] + Date.now());
- } else {
- console.log("Code exchnage failed. Status: " + xhr.status +
- " response: " + xhr.responseText);
- }
- on_done();
+/**
+ * Asynchronously exchanges an authorization code for a refresh token.
+ *
+ * @param {string} code The new refresh token.
+ * @param {function():void} onDone Callback to invoke on completion.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) {
+ var parameters = {
+ 'client_id': this.CLIENT_ID_,
+ 'client_secret': this.CLIENT_SECRET_,
+ 'redirect_uri': this.REDIRECT_URI_,
+ 'code': code,
+ 'grant_type': 'authorization_code'
};
- xhr.open('POST', 'https://accounts.google.com/o/oauth2/token', true);
- xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
- var post_data = 'client_id=' + this.client_id
- + '&client_secret=' + this.client_secret
- + '&redirect_uri=' + this.redirect_uri
- + '&code=' + encodeURIComponent(code)
- + '&grant_type=authorization_code';
- xhr.send(post_data);
+
+ var that = this;
+ remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
+ function(xhr) {
+ that.processTokenResponse_(xhr);
+ onDone();
+ },
+ parameters);
}
-// Call myfunc with an access token as the only parameter.
-//
-// This will refresh the access token if necessary. If the access token
-// cannot be refreshed, an error is thrown.
-OAuth2.prototype.callWithToken = function(myfunc) {
+/**
+ * Call myfunc with an access token as the only parameter.
+ *
+ * This will refresh the access token if necessary. If the access token
+ * cannot be refreshed, an error is thrown.
+ *
+ * @param {function(string):void} myfunc Function to invoke with access token.
+ * @return {void}
+ */
+remoting.OAuth2.prototype.callWithToken = function(myfunc) {
+ var that = this;
if (remoting.oauth2.needsNewAccessToken()) {
remoting.oauth2.refreshAccessToken(function() {
if (remoting.oauth2.needsNewAccessToken()) {
// If we still need it, we're going to infinite loop.
throw "Unable to get access token.";
}
- myfunc(remoting.oauth2.getAccessToken());
+ myfunc(that.getAccessToken());
});
return;
}
- myfunc(remoting.oauth2.getAccessToken());
+ myfunc(this.getAccessToken());
}
+}());
diff --git a/remoting/webapp/me2mom/oauth2_callback.html b/remoting/webapp/me2mom/oauth2_callback.html
index 385751b..2b34bbd 100644
--- a/remoting/webapp/me2mom/oauth2_callback.html
+++ b/remoting/webapp/me2mom/oauth2_callback.html
@@ -8,6 +8,7 @@ found in the LICENSE file.
<html>
<head>
<script src="oauth2.js"></script>
+ <script src="xhr.js"></script>
</head>
<body>
<div id="error" style="display:none;">
@@ -22,7 +23,7 @@ found in the LICENSE file.
queryArgs[pair[0]] = pair[1];
}
if ('code' in queryArgs) {
- var oauth2 = new OAuth2();
+ var oauth2 = new remoting.OAuth2();
oauth2.exchangeCodeForToken(queryArgs['code'], function() {
window.location.replace(chrome.extension.getURL('choice.html'));
});
diff --git a/remoting/webapp/me2mom/plugin_settings.js b/remoting/webapp/me2mom/plugin_settings.js
index e8515ea..a863ef7 100644
--- a/remoting/webapp/me2mom/plugin_settings.js
+++ b/remoting/webapp/me2mom/plugin_settings.js
@@ -6,5 +6,6 @@
// Keeping all that cenetralized here allows us to use symlinks for the other
// files making for a faster compile/run cycle when only modifying HTML/JS.
-var remoting = chrome.extension.getBackgroundPage().remoting;
+var remoting = remoting || {};
+
remoting.PLUGIN_MIMETYPE='HOST_PLUGIN_MIMETYPE';
diff --git a/remoting/webapp/me2mom/remoting.js b/remoting/webapp/me2mom/remoting.js
index 75ce831..0508374 100644
--- a/remoting/webapp/me2mom/remoting.js
+++ b/remoting/webapp/me2mom/remoting.js
@@ -2,20 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(jamiewalch): strict mode causes the page to crash so it's disabled for
-// now. Reinstate this when the associated bug is fixed.
-// http://code.google.com/p/v8/issues/detail?id=1423
-//"use strict";
+var remoting = remoting || {};
+
+(function() {
+"use strict";
+
+/** @enum {string} */
+remoting.AppMode = {
+ CLIENT: 'client',
+ HOST: 'host',
+ IN_SESSION: 'in-session'
+};
-// TODO(ajwong): This seems like a bad idea to share the exact same object
-// with the background page. Why are we doing it like this?
-var remoting = chrome.extension.getBackgroundPage().remoting;
-remoting.CLIENT_MODE = 'client';
-remoting.HOST_MODE = 'host';
remoting.EMAIL = 'email';
remoting.HOST_PLUGIN_ID = 'host-plugin-id';
-window.addEventListener('load', init_, false);
+/**
+ * Whether or not the plugin should scale itself.
+ * @type {boolean}
+ */
+remoting.scaleToFit = false;
+
+// Constants representing keys used for storing persistent application state.
+var KEY_APP_MODE_ = 'remoting-app-mode';
+var KEY_EMAIL_ = 'remoting-email';
// Some constants for pretty-printing the access code.
var kSupportIdLen = 7;
@@ -28,11 +38,11 @@ function hasClass(element, cls) {
}
function retrieveEmail_(access_token) {
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) {
- return;
- }
+ var headers = {
+ 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
+ };
+
+ var onResponse = function(xhr) {
if (xhr.status != 200) {
// TODO(ajwong): Have a better way of showing an error.
window.alert("Unable to get e-mail");
@@ -43,13 +53,13 @@ function retrieveEmail_(access_token) {
setEmail(xhr.responseText.split('&')[0].split('=')[1]);
};
- xhr.open('GET', 'https://www.googleapis.com/userinfo/email', true);
- xhr.setRequestHeader('Authorization', 'OAuth ' + access_token);
- xhr.send(null);
+ // TODO(ajwong): Update to new v2 API.
+ remoting.xhr.get('https://www.googleapis.com/userinfo/email',
+ onResponse, '', headers);
}
function refreshEmail_() {
- if (!remoting.getItem(remoting.EMAIL) && remoting.oauth2.isAuthenticated()) {
+ if (!getEmail() && remoting.oauth2.isAuthenticated()) {
remoting.oauth2.callWithToken(retrieveEmail_);
}
}
@@ -66,13 +76,10 @@ function removeClass(element, cls) {
function showElement(element, visible) {
if (visible) {
- if (hasClass(element, 'display-inline')) {
- element.style.display = 'inline-block';
- } else {
- element.style.display = 'block';
- }
+ // Reset to default visibility.
+ removeClass(element, 'hidden');
} else {
- element.style.display = 'none';
+ addClass(element, 'hidden');
}
}
@@ -96,33 +103,35 @@ function updateAuthStatus_() {
showElementById('oauth2-token-button', !oauthValid);
showElementById('oauth2-clear-button', oauthValid);
- var loginName = remoting.getItem(remoting.EMAIL);
+ var loginName = getEmail();
if (loginName) {
document.getElementById('current-email').innerText = loginName;
}
var disableControls = !(loginName && oauthValid);
- var authPanel = document.getElementById('auth-panel');
+ var controlPanel = document.getElementById('control-panel');
+ // TODO(ajwong): Do this via a style, or remove if the new auth flow is
+ // implemented.
if (disableControls) {
- authPanel.style.backgroundColor = 'rgba(204, 0, 0, 0.15)';
+ controlPanel.style.backgroundColor = 'rgba(204, 0, 0, 0.15)';
} else {
- authPanel.style.backgroundColor = 'rgba(0, 204, 102, 0.15)';
+ controlPanel.style.backgroundColor = 'rgba(0, 204, 102, 0.15)';
}
updateControls_(disableControls);
}
-function initBackgroundFuncs_() {
- remoting.getItem = chrome.extension.getBackgroundPage().getItem;
- remoting.setItem = chrome.extension.getBackgroundPage().setItem;
- remoting.removeItem = chrome.extension.getBackgroundPage().removeItem;
- remoting.oauth2 = new OAuth2();
-}
-
function setEmail(value) {
- remoting.setItem(remoting.EMAIL, value);
+ window.localStorage.setItem(KEY_EMAIL_, value);
updateAuthStatus_();
}
+/**
+ * @return {string}
+ */
+function getEmail() {
+ return window.localStorage.getItem(KEY_EMAIL_);
+}
+
function exchangedCodeForToken_() {
if (!remoting.oauth2.isAuthenticated()) {
alert('Your OAuth2 token was invalid. Please try again.');
@@ -133,7 +142,7 @@ function exchangedCodeForToken_() {
});
}
-function clearOAuth2() {
+remoting.clearOAuth2 = function() {
remoting.oauth2.clear();
updateAuthStatus_();
}
@@ -145,55 +154,87 @@ function setMode_(mode, modes) {
}
}
-function init_() {
- initBackgroundFuncs_();
+remoting.toggleDebugLog = function() {
+ var debugLog = document.getElementById('debug-log');
+ var toggleButton = document.getElementById('debug-log-toggle');
+
+ if (!debugLog.style.display || debugLog.style.display == 'none') {
+ debugLog.style.display = 'block';
+ toggleButton.value = 'Hide Debug Log';
+ } else {
+ debugLog.style.display = 'none';
+ toggleButton.value = 'Show Debug Log';
+ }
+}
+
+remoting.init = function() {
+ // Create global objects.
+ remoting.oauth2 = new remoting.OAuth2();
+ remoting.debug = new remoting.DebugLog(document.getElementById('debug-log'));
+
updateAuthStatus_();
refreshEmail_();
setHostMode('unshared');
setClientMode('unconnected');
- setGlobalMode(remoting.getItem('startup-mode', remoting.HOST_MODE));
- addClass(document.getElementById('loading-panel'), 'hidden');
- removeClass(document.getElementById('main-panel'), 'hidden');
+ setGlobalMode(getAppStartupMode());
+ addClass(document.getElementById('loading-mode'), 'hidden');
+ removeClass(document.getElementById('choice-mode'), 'hidden');
}
function setGlobalMode(mode) {
var elementsToShow = [];
var elementsToHide = [];
var hostElements = document.getElementsByClassName('host-element');
+ hostElements = Array.prototype.slice.apply(hostElements);
var clientElements = document.getElementsByClassName('client-element');
- if (mode == remoting.HOST_MODE) {
- elementsToShow = hostElements;
- elementsToHide = clientElements;
- } else {
- elementsToShow = clientElements;
- elementsToHide = hostElements;
- }
- for (var i = 0; i < elementsToShow.length; ++i) {
- showElement(elementsToShow[i], true);
+ clientElements = Array.prototype.slice.apply(clientElements);
+ var inSessionElements =
+ document.getElementsByClassName('in-session-element');
+ inSessionElements = Array.prototype.slice.apply(inSessionElements);
+ if (mode == remoting.AppMode.HOST) {
+ elementsToShow = elementsToShow.concat(hostElements);
+ elementsToHide = elementsToHide.concat(clientElements, inSessionElements);
+ } else if (mode == remoting.AppMode.CLIENT) {
+ elementsToShow = elementsToShow.concat(clientElements);
+ elementsToHide = elementsToHide.concat(hostElements, inSessionElements);
+ } else if (mode == remoting.AppMode.IN_SESSION) {
+ elementsToShow = elementsToShow.concat(inSessionElements);
+ elementsToHide = elementsToHide.concat(hostElements, clientElements);
}
+
+ // Hide first and then show since an element may be in both lists.
for (var i = 0; i < elementsToHide.length; ++i) {
showElement(elementsToHide[i], false);
}
+ for (var i = 0; i < elementsToShow.length; ++i) {
+ showElement(elementsToShow[i], true);
+ }
showElement(document.getElementById('waiting-footer', false));
remoting.currentMode = mode;
}
function setGlobalModePersistent(mode) {
setGlobalMode(mode);
- remoting.setItem('startup-mode', mode);
+ // TODO(ajwong): Does it make sense for "in_session" to be a peer to "host"
+ // or "client mode"? I don't think so, but not sure how to restructure UI.
+ if (mode != remoting.AppMode.IN_SESSION) {
+ remoting.storage.setStartupMode(mode);
+ } else {
+ remoting.storage.setStartupMode(remoting.AppMode.CLIENT);
+ }
}
function setHostMode(mode) {
- var section = document.getElementById('host-section');
+ var section = document.getElementById('host-panel');
var modes = section.getElementsByClassName('mode');
- addToDebugLog('Host mode: ' + mode);
+ remoting.debug.log('Host mode: ' + mode);
setMode_(mode, modes);
}
function setClientMode(mode) {
- var section = document.getElementById('client-section');
+ var section = document.getElementById('client-panel');
var modes = section.getElementsByClassName('mode');
- addToDebugLog('Client mode: ' + mode);
+ remoting.debug.log('Client mode: ' + mode);
setMode_(mode, modes);
}
@@ -205,9 +246,9 @@ function showWaiting_() {
}
function tryShare() {
- addToDebugLog('Attempting to share...');
+ remoting.debug.log('Attempting to share...');
if (remoting.oauth2.needsNewAccessToken()) {
- addToDebugLog('Refreshing token...');
+ remoting.debug.log('Refreshing token...');
remoting.oauth2.refreshAccessToken(function() {
if (remoting.oauth2.needsNewAccessToken()) {
// If we still need it, we're going to infinite loop.
@@ -221,7 +262,7 @@ function tryShare() {
showWaiting_();
- var div = document.getElementById('plugin-wrapper');
+ var div = document.getElementById('host-plugin-container');
var plugin = document.createElement('embed');
plugin.setAttribute('type', remoting.PLUGIN_MIMETYPE);
plugin.setAttribute('hidden', 'true');
@@ -229,9 +270,10 @@ function tryShare() {
div.appendChild(plugin);
plugin.onStateChanged = onStateChanged_;
plugin.logDebugInfoCallback = debugInfoCallback_;
- plugin.connect(remoting.getItem(remoting.EMAIL),
+ plugin.connect(getEmail(),
'oauth2:' + remoting.oauth2.getAccessToken());
}
+remoting.tryShare = tryShare;
function onStateChanged_() {
var plugin = document.getElementById(remoting.HOST_PLUGIN_ID);
@@ -253,11 +295,11 @@ function onStateChanged_() {
} else if (state == plugin.CONNECTED) {
setHostMode('shared');
} else if (state == plugin.DISCONNECTED) {
- setGlobalMode(remoting.HOST_MODE);
+ setGlobalMode(remoting.AppMode.HOST);
setHostMode('unshared');
plugin.parentNode.removeChild(plugin);
} else {
- addToDebugLog('Unknown state -> ' + state);
+ remoting.debug.log('Unknown state -> ' + state);
}
}
@@ -266,26 +308,102 @@ function onStateChanged_() {
* is additional debug log info to display.
*/
function debugInfoCallback_(msg) {
- addToDebugLog('plugin: ' + msg);
+ remoting.debug.log('plugin: ' + msg);
}
function showShareError_(errorCode) {
var errorDiv = document.getElementById(errorCode);
errorDiv.style.display = 'block';
- addToDebugLog("Sharing error: " + errorCode);
+ remoting.debug.log("Sharing error: " + errorCode);
setHostMode('share-failed');
}
function cancelShare() {
- addToDebugLog('Canceling share...');
+ remoting.debug.log('Canceling share...');
var plugin = document.getElementById(remoting.HOST_PLUGIN_ID);
plugin.disconnect();
}
+remoting.cancelShare = cancelShare;
+
+/**
+ * Show a client message that stays on the screeen until the state changes.
+ *
+ * @param {string} message The message to display.
+ */
+function setClientStateMessage(message) {
+ var msg = document.getElementById('session-status-message');
+ msg.innerText = message;
+}
+
+function updateStatusBarStats() {
+ if (remoting.session.state != remoting.ClientSession.State.CONNECTED)
+ return;
+ var stats = remoting.session.stats();
+
+ var units = '';
+ var videoBandwidth = stats['video_bandwidth'];
+ if (videoBandwidth < 1024) {
+ units = 'Bps';
+ } else if (videoBandwidth < 1048576) {
+ units = 'KiBps';
+ videoBandwidth = videoBandwidth / 1024;
+ } else if (videoBandwidth < 1073741824) {
+ units = 'MiBps';
+ videoBandwidth = videoBandwidth / 1048576;
+ } else {
+ units = 'GiBps';
+ videoBandwidth = videoBandwidth / 1073741824;
+ }
+
+ setClientStateMessage(
+ 'Bandwidth: ' + videoBandwidth.toFixed(2) + units +
+ ', Capture: ' + stats['capture_latency'].toFixed(2) + 'ms' +
+ ', Encode: ' + stats['encode_latency'].toFixed(2) + 'ms' +
+ ', Decode: ' + stats['decode_latency'].toFixed(2) + 'ms' +
+ ', Render: ' + stats['render_latency'].toFixed(2) + 'ms' +
+ ', Latency: ' + stats['roundtrip_latency'].toFixed(2) + 'ms');
+
+ // Update the stats once per second.
+ window.setTimeout(updateStatusBarStats, 1000);
+}
+
+function onClientStateChange_(state) {
+ if (state == remoting.ClientSession.State.UNKNOWN) {
+ setClientStateMessage('Unknown');
+ } else if (state == remoting.ClientSession.State.CREATED) {
+ setClientStateMessage('Created');
+ } else if (state == remoting.ClientSession.State.BAD_PLUGIN_VERSION) {
+ setClientStateMessage('Incompatible Plugin Version');
+ } else if (state == remoting.ClientSession.State.UNKNOWN_PLUGIN_ERROR) {
+ setClientStateMessage('Unknown error with plugin.');
+ } else if (state == remoting.ClientSession.State.CONNECTING) {
+ setClientStateMessage('Connecting as ' + remoting.username);
+ } else if (state == remoting.ClientSession.State.INITIALIZING) {
+ setClientStateMessage('Initializing connection');
+ } else if (state == remoting.ClientSession.State.CONNECTED) {
+ updateStatusBarStats();
+ } else if (state == remoting.ClientSession.State.CLOSED) {
+ setClientStateMessage('Closed');
+ } else if (state == remoting.ClientSession.State.CONNECTION_FAILED) {
+ setClientStateMessage('Failed');
+ } else {
+ setClientStateMessage('Bad State!!');
+ }
+}
function startSession_() {
- addToDebugLog('Starting session...');
- remoting.username = remoting.getItem(remoting.EMAIL);
- document.location = 'remoting_session.html';
+ remoting.debug.log('Starting session...');
+ remoting.username = getEmail();
+ setGlobalMode(remoting.AppMode.IN_SESSION);
+ remoting.session =
+ new remoting.ClientSession(remoting.hostJid, remoting.hostPublicKey,
+ remoting.accessCode, getEmail(),
+ onClientStateChange_);
+ remoting.oauth2.callWithToken(function(token) {
+ remoting.session.createPluginAndConnect(
+ document.getElementById('session-mode'),
+ token);
+ });
}
function showConnectError_(responseCode, responseString) {
@@ -324,21 +442,16 @@ function normalizeAccessCode_(accessCode) {
}
function resolveSupportId(supportId) {
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) {
- return;
- }
- parseServerResponse_(xhr);
- };
+ var headers = {
+ 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
+ };
- xhr.open('GET',
- 'https://www.googleapis.com/chromoting/v1/support-hosts/' +
- encodeURIComponent(supportId),
- true);
- xhr.setRequestHeader('Authorization',
- 'OAuth ' + remoting.oauth2.getAccessToken());
- xhr.send(null);
+ remoting.xhr.get(
+ 'https://www.googleapis.com/chromoting/v1/support-hosts/' +
+ encodeURIComponent(supportId),
+ parseServerResponse_,
+ '',
+ headers);
}
function tryConnect() {
@@ -364,10 +477,44 @@ function tryConnect() {
resolveSupportId(supportId);
}
}
+remoting.tryConnect = tryConnect;
function cancelPendingOperation() {
document.getElementById('cancel-button').disabled = true;
- if (remoting.currentMode == remoting.HOST_MODE) {
+ if (remoting.currentMode == remoting.AppMode.HOST) {
cancelShare();
}
}
+
+/**
+ * Changes the major-mode of the application (Eg., client or host).
+ *
+ * @param {remoting.AppMode} mode The mode to shift the application into.
+ * @return {void}
+ */
+remoting.setAppMode = function(mode) {
+ setGlobalMode(mode);
+ window.localStorage.setItem(KEY_APP_MODE_, mode);
+}
+
+/**
+ * Gets the major-mode that this application should start up in.
+ *
+ * @return {remoting.AppMode}
+ */
+function getAppStartupMode() {
+ var mode = window.localStorage.getItem(KEY_APP_MODE_);
+ if (!mode) {
+ mode = remoting.AppMode.HOST;
+ }
+ return mode;
+}
+
+remoting.toggleScaleToFit = function() {
+ remoting.scaleToFit = !remoting.scaleToFit;
+ document.getElementById('scale-to-fit-toggle').value =
+ remoting.scaleToFit ? 'No scaling' : 'Scale to fit';
+ remoting.session.toggleScaleToFit(remoting.scaleToFit);
+}
+
+}());
diff --git a/remoting/webapp/me2mom/remoting_session.css b/remoting/webapp/me2mom/remoting_session.css
deleted file mode 100644
index cc33d240..0000000
--- a/remoting/webapp/me2mom/remoting_session.css
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Copyright (c) 2011 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/* Elements */
-body {
- margin: 0;
- overflow: auto;
- padding: 0;
- -webkit-user-select: none;
-}
-
-/* Ids */
-#plugin-scroll-panel {
- overflow: auto;
- width: 100%;
- -webkit-user-select: none;
-}
-
-#session-buttons {
- float: right;
-}
-
-#session-controls {
- background-color: white;
- border-bottom: solid 1px black;
- height: 1.5em;
- left: 0;
- opacity: 0.85;
- position: fixed;
- top: 0;
- width: 100%;
- z-index: 100;
-}
-
-#session-controls-padding {
- /*
- Maintains space for session-controls so that the display initially appears
- lower than the controls, since session-controls is position:fixed.
- */
- height: 1.5em;
-}
-
-#status-msg {
- /*
- clear: both; forces the session-controls div to size appropriately for
- session-buttons.
- */
- clear: both;
- font-family: monospace;
- font-size: small;
- margin: 5px;
-}
diff --git a/remoting/webapp/me2mom/remoting_session.html b/remoting/webapp/me2mom/remoting_session.html
deleted file mode 100644
index e1e3eed..0000000
--- a/remoting/webapp/me2mom/remoting_session.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!doctype html>
-<!--
-Copyright (c) 2011 The Chromium Authors. All rights reserved.
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<html>
- <head>
- <title>Chromoting Session</title>
- <link rel="shortcut icon" href="chromoting128.png" />
- <link rel="stylesheet" href="debug_log.css" />
- <link rel="stylesheet" href="main.css" />
- <link rel="stylesheet" href="remoting_session.css" />
- <script src="debug_log.js"></script>
- <script src="oauth2.js"></script>
- <script src="remoting_session.js"></script>
- </head>
- <body id="session-body">
- <div id="session-controls">
- <form id="session-buttons">
- <input id="scale-to-fit-toggle" type="button" value="Scale to fit"
- onclick="toggleScaleToFit();"/>
- <input id="debug-log-toggle" type="button" value="Show Debug Log"
- onclick="toggleDebugLog(); return false;"/>
- </form>
- <span id="status-msg">Initializing...</span>
- </div>
- <div id="session-controls-padding">
- </div>
- <div id="plugin-scroll-panel">
- <embed name="remoting" id="remoting"
- src="about://none" type="pepper-application/x-chromoting">
- </div>
- <div id="debug-log">
- </div>
- </body>
-</html>
diff --git a/remoting/webapp/me2mom/remoting_session.js b/remoting/webapp/me2mom/remoting_session.js
deleted file mode 100644
index 556d869..0000000
--- a/remoting/webapp/me2mom/remoting_session.js
+++ /dev/null
@@ -1,317 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var remoting = chrome.extension.getBackgroundPage().remoting;
-
-// Chromoting session API version (for this javascript).
-// This is compared with the plugin API version to verify that they are
-// compatible.
-remoting.apiVersion = 2;
-
-// The oldest API version that we support.
-// This will differ from the |apiVersion| if we maintain backward
-// compatibility with older API versions.
-remoting.apiMinVersion = 1;
-
-// Message id so that we can identify (and ignore) message fade operations for
-// old messages. This starts at 1 and is incremented for each new message.
-remoting.messageId = 1;
-
-remoting.scaleToFit = false;
-
-remoting.httpXmppProxy =
- 'https://chromoting-httpxmpp-oauth2-dev.corp.google.com';
-
-window.addEventListener("load", init_, false);
-
-// This executes a poll loop on the server for more Iq packets, and feeds them
-// to the plugin.
-function feedIq() {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', remoting.httpXmppProxy + '/readIq?host_jid=' +
- encodeURIComponent(remoting.hostJid), true);
- xhr.withCredentials = true;
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- if (xhr.status == 200) {
- addToDebugLog('Receiving Iq: --' + xhr.responseText + '--');
- remoting.plugin.onIq(xhr.responseText);
- }
- if (xhr.status == 200 || xhr.status == 204) {
- window.setTimeout(feedIq, 0);
- } else {
- addToDebugLog('HttpXmpp gateway returned code: ' + xhr.status);
- remoting.plugin.disconnect();
- setClientStateMessage('Failed');
- }
- }
- }
- xhr.send(null);
-}
-
-function registerConnection() {
- var xhr = new XMLHttpRequest();
- xhr.open('POST', remoting.httpXmppProxy + '/newConnection', true);
- xhr.withCredentials = true;
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- if (xhr.status == 200) {
- addToDebugLog('Receiving Iq: --' + xhr.responseText + '--');
- var clientjid = xhr.responseText;
-
- remoting.plugin.sendIq = sendIq;
- if (remoting.plugin.apiVersion >= 2) {
- remoting.plugin.connect(remoting.hostJid, remoting.hostPublicKey,
- clientjid, remoting.accessCode);
- } else {
- remoting.plugin.connect(remoting.hostJid, clientjid,
- remoting.accessCode);
- }
- // TODO(ajwong): This should just be feedIq();
- window.setTimeout(feedIq, 1000);
- } else {
- addToDebugLog('FailedToConnect: --' + xhr.responseText +
- '-- (status=' + xhr.status + ')');
- setClientStateMessage('Failed');
- }
- }
- }
- xhr.send('host_jid=' + encodeURIComponent(remoting.hostJid) +
- '&username=' + encodeURIComponent(remoting.username) +
- '&password=' + encodeURIComponent(remoting.oauth2.getAccessToken()));
- setClientStateMessage('Connecting');
-}
-
-function sendIq(msg) {
- addToDebugLog('Sending Iq: ' + msg);
-
- // Extract the top level fields of the Iq packet.
- // TODO(ajwong): Can the plugin just return these fields broken out.
- parser = new DOMParser();
- iqNode = parser.parseFromString(msg, 'text/xml').firstChild;
- id = iqNode.getAttribute('id');
- type = iqNode.getAttribute('type');
- to = iqNode.getAttribute('to');
- serializer = new XMLSerializer();
- payload_xml = serializer.serializeToString(iqNode.firstChild);
-
- var xhr = new XMLHttpRequest();
- xhr.open('POST', remoting.httpXmppProxy + '/sendIq', true);
- xhr.withCredentials = true;
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.send('to=' + encodeURIComponent(to) +
- '&payload_xml=' + encodeURIComponent(payload_xml) +
- '&id=' + id + '&type=' + type +
- '&host_jid=' + encodeURIComponent(remoting.hostJid));
-}
-
-function checkVersion(plugin) {
- return remoting.apiVersion >= plugin.apiMinVersion &&
- plugin.apiVersion >= remoting.apiMinVersion;
-}
-
-function init_() {
- // Kick off the connection.
- var plugin = document.getElementById('remoting');
-
- remoting.plugin = plugin;
-
- // Only allow https connections to the httpXmppProxy.
- var regExp = /^ *https:\/\//;
- if (remoting.httpXmppProxy.search(regExp) == -1) {
- addToDebugLog('Aborting. httpXmppProxy does not specify https protocol: ' +
- remoting.httpXmppProxy);
- return;
- }
-
- // Setup the callback that the plugin will call when the connection status
- // has changes and the UI needs to be updated. It needs to be an object with
- // a 'callback' property that contains the callback function.
- plugin.connectionInfoUpdate = connectionInfoUpdateCallback;
- plugin.debugInfo = debugInfoCallback;
- plugin.desktopSizeUpdate = desktopSizeChanged;
- plugin.loginChallenge = loginChallengeCallback;
-
- if (typeof plugin.connect !== 'function') {
- setClientStateMessage("Unable to load plugin. Please make sure that " +
- "'Remoting' and 'P2P API' are enabled in chrome://flags.");
- return;
- }
-
- if (!checkVersion(plugin)) {
- // TODO(garykac): We need better messaging here. Perhaps an install link.
- setClientStateMessage("Out of date. Please re-install.");
- return;
- }
-
- addToDebugLog('Connect as user ' + remoting.username);
- registerConnection();
-}
-
-function toggleScaleToFit() {
- remoting.scaleToFit = !remoting.scaleToFit;
- document.getElementById('scale-to-fit-toggle').value =
- remoting.scaleToFit ? 'No scaling' : 'Scale to fit';
- remoting.plugin.setScaleToFit(remoting.scaleToFit);
-}
-
-/**
- * This is the callback method that the plugin calls to request username and
- * password for logging into the remote host. For Me2Mom we are pre-authorized
- * so this is a no-op.
- */
-function loginChallengeCallback() {
-}
-
-/**
- * This is a callback that gets called when the desktop size contained in the
- * the plugin has changed.
- */
-function desktopSizeChanged() {
- var width = remoting.plugin.desktopWidth;
- var height = remoting.plugin.desktopHeight;
-
- addToDebugLog('desktop size changed: ' + width + 'x' + height);
- remoting.plugin.style.width = width + 'px';
- remoting.plugin.style.height = height + 'px';
-}
-
-/**
- * This is that callback that the plugin invokes to indicate that the
- * host/client connection status has changed.
- */
-function connectionInfoUpdateCallback() {
- var status = remoting.plugin.status;
- var quality = remoting.plugin.quality;
-
- if (status == remoting.plugin.STATUS_UNKNOWN) {
- setClientStateMessage('');
- } else if (status == remoting.plugin.STATUS_CONNECTING) {
- setClientStateMessage('Connecting as ' + remoting.username);
- } else if (status == remoting.plugin.STATUS_INITIALIZING) {
- setClientStateMessageFade('Initializing connection');
- } else if (status == remoting.plugin.STATUS_CONNECTED) {
- desktopSizeChanged();
- setClientStateMessageFade('Connected', 1000);
- window.setTimeout(updateStatusBarStats, 1000);
- } else if (status == remoting.plugin.STATUS_CLOSED) {
- setClientStateMessage('Closed');
- } else if (status == remoting.plugin.STATUS_FAILED) {
- setClientStateMessage('Failed');
- }
-}
-
-/**
- * Show a client message that stays on the screeen until the state changes.
- *
- * @param {string} message The message to display.
- */
-function setClientStateMessage(message) {
- // Increment message id to ignore any previous fadeout requests.
- remoting.messageId++;
-
- // Update the status message.
- var msg = document.getElementById('status-msg');
- msg.innerText = message;
- msg.style.opacity = 1;
- msg.style.display = '';
-}
-
-/**
- * Show a client message for the specified amount of time.
- *
- * @param {string} message The message to display.
- * @param {number} duration Milliseconds to show message before fading.
- */
-function setClientStateMessageFade(message, duration) {
- setClientStateMessage(message);
-
- // Set message duration.
- window.setTimeout("fade('status-msg', " + remoting.messageId + ', ' +
- '100, 10, 200)',
- duration);
-}
-
-/**
- * Fade the specified element.
- * For example, to have element 'foo' fade away over 2 seconds, you could use
- * either:
- * fade('foo', 100, 10, 200)
- * - Start at 100%, decrease by 10% each time, wait 200ms between updates.
- * fade('foo', 100, 5, 100)
- * - Start at 100%, decrease by 5% each time, wait 100ms between updates.
- *
- * @param {string} name Name of element to fade.
- * @param {number} id The id of the message associated with this fade request.
- * @param {number} val The new opacity value (0-100) for this element.
- * @param {number} delta Amount to adjust the opacity each iteration.
- * @param {number} delay Delay (in ms) to wait between each update.
- */
-function fade(name, id, val, delta, delay) {
- // Ignore the fade call if it does not apply to the current message.
- if (id != remoting.messageId) {
- return;
- }
-
- var e = document.getElementById(name);
- if (e) {
- var newVal = val - delta;
- if (newVal > 0) {
- // Decrease opacity and set timer for next fade event.
- e.style.opacity = newVal / 100;
- window.setTimeout("fade('status-msg', " + id + ', ' + newVal + ', ' +
- delta + ', ' + delay + ')',
- delay);
- } else {
- // Completely hide the text and stop fading.
- e.style.opacity = 0;
- e.style.display = 'none';
- }
- }
-}
-
-/**
- * This is that callback that the plugin invokes to indicate that there
- * is additional debug log info to display.
- */
-function debugInfoCallback(msg) {
- addToDebugLog('plugin: ' + msg);
-}
-
-function updateStatusBarStats() {
- if (remoting.plugin.status != remoting.plugin.STATUS_CONNECTED)
- return;
- var videoBandwidth = remoting.plugin.videoBandwidth;
- var videoCaptureLatency = remoting.plugin.videoCaptureLatency;
- var videoEncodeLatency = remoting.plugin.videoEncodeLatency;
- var videoDecodeLatency = remoting.plugin.videoDecodeLatency;
- var videoRenderLatency = remoting.plugin.videoRenderLatency;
- var roundTripLatency = remoting.plugin.roundTripLatency;
-
- var units = '';
- if (videoBandwidth < 1024) {
- units = 'Bps';
- } else if (videoBandwidth < 1048576) {
- units = 'KiBps';
- videoBandwidth = videoBandwidth / 1024;
- } else if (videoBandwidth < 1073741824) {
- units = 'MiBps';
- videoBandwidth = videoBandwidth / 1048576;
- } else {
- units = 'GiBps';
- videoBandwidth = videoBandwidth / 1073741824;
- }
-
- setClientStateMessage(
- 'Bandwidth: ' + videoBandwidth.toFixed(2) + units +
- ', Capture: ' + videoCaptureLatency.toFixed(2) + 'ms' +
- ', Encode: ' + videoEncodeLatency.toFixed(2) + 'ms' +
- ', Decode: ' + videoDecodeLatency.toFixed(2) + 'ms' +
- ', Render: ' + videoRenderLatency.toFixed(2) + 'ms' +
- ', Latency: ' + roundTripLatency.toFixed(2) + 'ms');
-
- // Update the stats once per second.
- window.setTimeout('updateStatusBarStats()', 1000);
-}
diff --git a/remoting/webapp/me2mom/storage.js b/remoting/webapp/me2mom/storage.js
deleted file mode 100644
index 3837d36..0000000
--- a/remoting/webapp/me2mom/storage.js
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var remoting = remoting || {};
-
-(function() {
-"use strict";
-
-/**
- * Creates a storage object abstracting our storage layer.
- *
- * This class provide accessors/mutators for all stateful items that are
- * persisted by the webapp. This ensures that there are no collisions in
- * the naming between different modules.
- *
- * Note that not all data items are stored with the same lifetime.
- *
- * @constructor
- */
-remoting.Storage = function() {
- /**
- * Key name for storing the OAuth2 refresh token.
- *
- * @const
- * @type {string}
- */
- this.KEY_OAUTH2_REFRESH_TOKEN_ = 'storage-oauth2-refresh-token';
-
- /**
- * Key name for storing the OAuth2 access token.
- *
- * @const
- * @type {string}
- */
- this.KEY_OAUTH2_ACCESS_TOKEN_ = 'storage-oauth2-access-token';
-
- /**
- * Key name for storing the user's e-mail address.
- *
- * @const
- * @type {string}
- */
- this.KEY_EMAIL_ = 'storage-email';
-}
-
-/**
- * @return {string} The refresh token.
- */
-remoting.Storage.prototype.getOAuth2RefreshToken = function() {
- return unescape(window.localStorage.getItem(this.KEY_OAUTH2_REFRESH_TOKEN_));
-}
-
-/**
- * @param token {string} The new refresh token.
- * @return {void}
- */
-remoting.Storage.prototype.setOAuth2RefreshToken = function(token) {
- window.localStorage.setItem(this.KEY_OAUTH2_REFRESH_TOKEN_, escape(token));
-}
-
-/**
- * @return {{access_token: string, expires_in: number}} The access token.
- */
-remoting.Storage.prototype.getOAuth2AccessToken = function() {
- return JSON.parse(
- window.localStorage.getItem(this.KEY_OAUTH2_ACCESS_TOKEN_));
-}
-
-/**
- * @param token {string} The new access token.
- * @param expires {number} When token expires in seconds since epoch.
- * @return {void}
- */
-remoting.Storage.prototype.setOAuth2AccessToken = function(token, expires) {
- var access_token = {'access_token': token, 'expires_in': expires}
- window.localStorage.setItem(this.KEY_OAUTH2_ACCESS_TOKEN_,
- JSON.stringify(access_token));
-}
-
-/**
- * @return {string} The user's e-mail.
- */
-remoting.Storage.prototype.getEmail = function() {
- return unescape(window.localStorage.getItem(this.KEY_EMAIL_));
-}
-
-/**
- * @param email {string} The user's email.
- * @return {void}
- */
-remoting.Storage.prototype.setEmail = function(email) {
- window.localStorage.setItem(this.KEY_EMAIL_, escape(email));
-}
-
-}());
diff --git a/remoting/webapp/me2mom/xhr.js b/remoting/webapp/me2mom/xhr.js
new file mode 100644
index 0000000..26eb01e
--- /dev/null
+++ b/remoting/webapp/me2mom/xhr.js
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Simple utilities for makeing XHRs more pleasant.
+ */
+
+"use strict";
+
+var remoting = remoting || {};
+remoting.xhr = remoting.xhr || {};
+
+(function() {
+
+/**
+ * Takes an associative array of parameters and urlencodes it.
+ *
+ * @param {Object.<string>} paramHash The parameter key/value pairs.
+ * @return {string} URLEncoded version of paramHash.
+ */
+remoting.xhr.urlencodeParamHash = function(paramHash) {
+ var paramArray = [];
+ for (var key in paramHash) {
+ paramArray.push(encodeURIComponent(key) +
+ '=' + encodeURIComponent(paramHash[key]));
+ }
+ if (paramArray.length > 0) {
+ return paramArray.join('&');
+ }
+ return '';
+}
+
+/**
+ * Execute an XHR GET asynchronously.
+ *
+ * @param {string} url The base URL to GET, excluding parameters.
+ * @param {function(XMLHttpRequest):void} onDone The function to call on
+ * completion.
+ * @param {(string|Object.<string>)} opt_parameters The request parameters,
+ * either as an associative array, or a string. If it is a string, do
+ * not include the ? and be sure it is correctly URLEncoded.
+ * @param {Object.<string>} opt_headers Additional headers to include on the
+ * request.
+ * @param {boolean} opt_withCredentials Set the withCredentials flags in the
+ * XHR.
+ * @return {void}
+ */
+remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers,
+ opt_withCredentials) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) {
+ return;
+ }
+ onDone(xhr);
+ };
+
+ // Add parameters into URL.
+ if (typeof(opt_parameters) === 'string') {
+ if (opt_parameters.length > 0) {
+ url = url + '?' + opt_parameters
+ }
+ } else if (typeof(opt_parameters) === 'object') {
+ var paramString = remoting.xhr.urlencodeParamHash(opt_parameters);
+ if (paramString.length > 0) {
+ url = url + '?' + paramString;
+ }
+ } else if (opt_parameters === undefined) {
+ // No problem here. Do nothing.
+ } else {
+ throw "opt_parameters must be string or associated array.";
+ }
+
+ xhr.open('GET', url, true);
+
+ // Add in request headers.
+ if (typeof(opt_headers) === 'object') {
+ for (var key in opt_headers) {
+ xhr.setRequestHeader(key, opt_headers[key]);
+ }
+ } else if (opt_headers === undefined) {
+ // No problem here. Do nothing.
+ } else {
+ throw "opt_headers must be associative array.";
+ }
+
+ if (opt_withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ xhr.send(null);
+}
+
+/**
+ * Execute an XHR POST asynchronously.
+ *
+ * @param {string} url The base URL to POST, excluding parameters.
+ * @param {function(XMLHttpRequest):void} onDone The function to call on
+ * completion.
+ * @param {(string|Object.<string>)} opt_parameters The request parameters,
+ * either as an associative array, or a string. If it is a string, be
+ * sure it is correctly URLEncoded.
+ * @param {Object.<string>} opt_headers Additional headers to include on the
+ * request.
+ * @param {boolean} opt_withCredentials Set the withCredentials flags in the
+ * XHR.
+ * @return {void}
+ */
+remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers,
+ opt_withCredentials) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) {
+ return;
+ }
+ onDone(xhr);
+ };
+
+ // Add parameters into URL.
+ var postData = '';
+ if (typeof(opt_parameters) === 'string') {
+ postData = opt_parameters;
+ } else if (typeof(opt_parameters) === 'object') {
+ postData = remoting.xhr.urlencodeParamHash(opt_parameters);
+ } else if (opt_parameters === undefined) {
+ // No problem here. Do nothing.
+ } else {
+ throw "opt_parameters must be string or associated array.";
+ }
+
+ xhr.open('POST', url, true);
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+ // Add in request headers.
+ if (typeof(opt_headers) === 'object') {
+ for (var key in opt_headers) {
+ xhr.setRequestHeader(key, opt_headers[key]);
+ }
+ } else if (opt_headers === undefined) {
+ // No problem here. Do nothing.
+ } else {
+ throw "opt_headers must be associative array.";
+ }
+
+ if (opt_withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ xhr.send(postData);
+}
+
+}());