Commit f34acc69 authored by kelvinp@chromium.org's avatar kelvinp@chromium.org

Hangout remote desktop part II - background.html and AppLauncher

This CL:
- Moves background.js to background.html
- Introduces remoting.appLauncher that allows the caller to launch and close the webapp without knowing the implementation difference between a v1 app and a v2 app.

NOTRY=true
R=jamiewalch@chromium.org

Review URL: https://codereview.chromium.org/450383003

Cr-Commit-Position: refs/heads/master@{#289096}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289096 0039d316-1c4b-4281-b951-d872f2087c98
parent 2e842237
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
'host/win/host_messages.mc.jinja2', 'host/win/host_messages.mc.jinja2',
'host/win/version.rc.jinja2', 'host/win/version.rc.jinja2',
'resources/play_store_resources.cc', 'resources/play_store_resources.cc',
'webapp/background.js', 'webapp/background/background.js',
'webapp/butter_bar.js', 'webapp/butter_bar.js',
'webapp/client_screen.js', 'webapp/client_screen.js',
'webapp/error.js', 'webapp/error.js',
......
...@@ -85,6 +85,22 @@ ...@@ -85,6 +85,22 @@
'--js', '<@(remoting_webapp_wcs_sandbox_html_js_files)', '--js', '<@(remoting_webapp_wcs_sandbox_html_js_files)',
], ],
}, },
{
'action_name': 'Build Remoting Webapp background.html',
'inputs': [
'webapp/build-html.py',
'<(remoting_webapp_template_background)',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/background.html',
],
'action': [
'python', 'webapp/build-html.py',
'<(SHARED_INTERMEDIATE_DIR)/background.html',
'<(remoting_webapp_template_background)',
'--js', '<@(remoting_webapp_background_js_files)',
],
},
], ],
}, # end of target 'remoting_webapp_html' }, # end of target 'remoting_webapp_html'
...@@ -114,7 +130,6 @@ ...@@ -114,7 +130,6 @@
'variables': { 'variables': {
'output_dir': '<(PRODUCT_DIR)/remoting/remoting.webapp.v2', 'output_dir': '<(PRODUCT_DIR)/remoting/remoting.webapp.v2',
'zip_path': '<(PRODUCT_DIR)/remoting-webapp.v2.zip', 'zip_path': '<(PRODUCT_DIR)/remoting-webapp.v2.zip',
'extra_files': [ 'webapp/background.js' ],
}, },
'conditions': [ 'conditions': [
['disable_nacl==0 and disable_nacl_untrusted==0', { ['disable_nacl==0 and disable_nacl_untrusted==0', {
......
...@@ -297,6 +297,7 @@ ...@@ -297,6 +297,7 @@
'webapp_js_files': [ 'webapp_js_files': [
'<@(remoting_webapp_main_html_js_files)', '<@(remoting_webapp_main_html_js_files)',
'<@(remoting_webapp_js_wcs_sandbox_files)', '<@(remoting_webapp_js_wcs_sandbox_files)',
'<@(remoting_webapp_background_js_files)',
] ]
}, },
'copies': [ 'copies': [
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
'generated_html_files': [ 'generated_html_files': [
'<(SHARED_INTERMEDIATE_DIR)/main.html', '<(SHARED_INTERMEDIATE_DIR)/main.html',
'<(SHARED_INTERMEDIATE_DIR)/wcs_sandbox.html', '<(SHARED_INTERMEDIATE_DIR)/wcs_sandbox.html',
'<(SHARED_INTERMEDIATE_DIR)/background.html',
], ],
}, },
'dependencies': [ 'dependencies': [
......
...@@ -131,6 +131,9 @@ ...@@ -131,6 +131,9 @@
], ],
# These product files are excluded from our JavaScript unittest # These product files are excluded from our JavaScript unittest
'remoting_webapp_unittest_exclude_files': [ 'remoting_webapp_unittest_exclude_files': [
# background.js is where the onLoad handler is defined, which
# makes it the entry point of the background page.
'webapp/background/background.js',
# event_handlers.js is where the onLoad handler is defined, which # event_handlers.js is where the onLoad handler is defined, which
# makes it the entry point of the webapp. # makes it the entry point of the webapp.
'webapp/event_handlers.js', 'webapp/event_handlers.js',
...@@ -165,7 +168,16 @@ ...@@ -165,7 +168,16 @@
'<@(remoting_webapp_js_wcs_container_files)', '<@(remoting_webapp_js_wcs_container_files)',
# Uncomment this line to include browser test files in the web app # Uncomment this line to include browser test files in the web app
# to expedite debugging or local development. # to expedite debugging or local development.
'<@(remoting_webapp_js_browser_test_files)' # '<@(remoting_webapp_js_browser_test_files)'
],
# The JavaScript files that are used as background pages.
'remoting_webapp_background_js_files': [
'webapp/base.js',
'webapp/client_session.js',
'webapp/typecheck.js',
'webapp/background/app_launcher.js',
'webapp/background/background.js'
], ],
# The JavaScript files required by wcs_sandbox.html. # The JavaScript files required by wcs_sandbox.html.
...@@ -179,6 +191,7 @@ ...@@ -179,6 +191,7 @@
'remoting_webapp_all_js_files': [ 'remoting_webapp_all_js_files': [
# JS files for main.html. # JS files for main.html.
'<@(remoting_webapp_main_html_js_files)', '<@(remoting_webapp_main_html_js_files)',
'<@(remoting_webapp_background_js_files)',
# JS files for wcs_sandbox.html. # JS files for wcs_sandbox.html.
# Use r_w_js_wcs_sandbox_files instead of r_w_wcs_sandbox_html_js_files # Use r_w_js_wcs_sandbox_files instead of r_w_wcs_sandbox_html_js_files
# so that we don't double include error.js and plugin_settings.js. # so that we don't double include error.js and plugin_settings.js.
...@@ -231,6 +244,9 @@ ...@@ -231,6 +244,9 @@
'remoting_webapp_template_wcs_sandbox': 'remoting_webapp_template_wcs_sandbox':
'webapp/html/template_wcs_sandbox.html', 'webapp/html/template_wcs_sandbox.html',
'remoting_webapp_template_background':
'webapp/html/template_background.html',
'remoting_webapp_template_files': [ 'remoting_webapp_template_files': [
'webapp/html/butterbar.html', 'webapp/html/butterbar.html',
'webapp/html/client_plugin.html', 'webapp/html/client_plugin.html',
......
// Copyright (c) 2012 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.
/** @type {string} */
var kNewWindowId = 'new-window';
function createWindow() {
chrome.app.window.create('main.html', {
'width': 800,
'height': 600,
'frame': 'none'
});
};
/** @param {OnClickData} info */
function onContextMenu(info) {
if (info.menuItemId == kNewWindowId) {
createWindow();
}
};
function initializeContextMenu() {
chrome.contextMenus.create({
id: kNewWindowId,
contexts: ['launcher'],
title: chrome.i18n.getMessage(/*i18n-content*/'NEW_WINDOW')
});
}
chrome.app.runtime.onLaunched.addListener(createWindow);
chrome.contextMenus.onClicked.addListener(onContextMenu);
initializeContextMenu();
// Copyright 2014 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
* AppLauncher is an interface that allows the client code to launch and close
* the app without knowing the implementation difference between a v1 app and
* a v2 app.
*
* To launch an app:
* var appLauncher = new remoting.V1AppLauncher();
* var appId = "";
* appLauncher.launch({arg1:'someValue'}).then(function(id){
* appId = id;
* });
*
* To close an app:
* appLauncher.close(appId);
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/** @interface */
remoting.AppLauncher = function() {};
/**
* @param {Object=} opt_launchArgs
* @return {Promise} The promise will resolve when the app is launched. It will
* provide the caller with the appId (which is either the id of the hosting tab
* or window). The caller can use the appId to close the app.
*/
remoting.AppLauncher.prototype.launch = function(opt_launchArgs) { };
/**
* @param {string} id The id of the app to close.
* @return {Promise} The promise will resolve when the app is closed.
*/
remoting.AppLauncher.prototype.close = function(id) {};
/**
* @constructor
* @implements {remoting.AppLauncher}
*/
remoting.V1AppLauncher = function() {};
remoting.V1AppLauncher.prototype.launch = function(opt_launchArgs) {
var url = base.urlJoin('main.html', opt_launchArgs);
/**
* @param {function(*=):void} resolve
* @param {function(*=):void} reject
*/
return new Promise(function(resolve, reject) {
chrome.tabs.create({ url: url, selected: true },
/** @param {chrome.Tab} tab The created tab. */
function(tab) {
if (!tab) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(tab.id);
}
});
});
};
remoting.V1AppLauncher.prototype.close = function(id) {
/**
* @param {function(*=):void} resolve
* @param {function(*=):void} reject
*/
return new Promise(function(resolve, reject) {
/** @param {chrome.Tab} tab The retrieved tab. */
chrome.tabs.get(id, function(tab) {
if (!tab) {
reject(new Error(chrome.runtime.lastError.message));
} else {
chrome.tabs.remove(tab.id, resolve);
}
});
});
};
/**
* @constructor
* @implements {remoting.AppLauncher}
*/
remoting.V2AppLauncher = function() {};
/**
* @type {number}
* @private
*/
remoting.V2AppLauncher.nextWindowId_ = 0;
remoting.V2AppLauncher.prototype.launch = function(opt_launchArgs) {
var url = base.urlJoin('main.html', opt_launchArgs);
/**
* @param {function(*=):void} resolve
* @param {function(*=):void} reject
*/
return new Promise(function(resolve, reject) {
chrome.app.window.create(url, {
'width': 800,
'height': 600,
'frame': 'none',
'id': String(remoting.V2AppLauncher.nextWindowId_++)
},
/** @param {AppWindow} appWindow */
function(appWindow) {
if (!appWindow) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(appWindow.id);
}
});
});
};
remoting.V2AppLauncher.prototype.close = function(id) {
var appWindow = chrome.app.window.get(id);
if (!appWindow) {
return Promise.reject(new Error(chrome.runtime.lastError.message));
}
appWindow.close();
return Promise.resolve();
};
// Copyright 2014 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.
/** @suppress {duplicate} */
var remoting = remoting || {};
(function(){
/** @return {boolean} */
function isAppsV2() {
var manifest = chrome.runtime.getManifest();
if (manifest && manifest.app && manifest.app.background) {
return true;
}
return false;
}
/** @param {remoting.AppLauncher} appLauncher */
function initializeAppV2(appLauncher) {
/** @type {string} */
var kNewWindowId = 'new-window';
/** @param {OnClickData} info */
function onContextMenu(info) {
if (info.menuItemId == kNewWindowId) {
appLauncher.launch();
}
}
function initializeContextMenu() {
chrome.contextMenus.create({
id: kNewWindowId,
contexts: ['launcher'],
title: chrome.i18n.getMessage(/*i18n-content*/'NEW_WINDOW')
});
chrome.contextMenus.onClicked.addListener(onContextMenu);
}
initializeContextMenu();
chrome.app.runtime.onLaunched.addListener(
appLauncher.launch.bind(appLauncher)
);
}
function main() {
/** @type {remoting.AppLauncher} */
var appLauncher = new remoting.V1AppLauncher();
if (isAppsV2()) {
appLauncher = new remoting.V2AppLauncher();
initializeAppV2(appLauncher);
}
}
window.addEventListener('load', main, false);
}());
...@@ -112,6 +112,26 @@ base.values = function(dict) { ...@@ -112,6 +112,26 @@ base.values = function(dict) {
}); });
}; };
/**
* Joins the |url| with optional query parameters defined in |opt_params|
* See unit test for usage.
* @param {string} url
* @param {Object.<string>=} opt_params
* @return {string}
*/
base.urlJoin = function(url, opt_params) {
if (!opt_params) {
return url;
}
var queryParameters = [];
for (var key in opt_params) {
queryParameters.push(encodeURIComponent(key) + "=" +
encodeURIComponent(opt_params[key]));
}
return url + '?' + queryParameters.join('&');
};
base.Promise = function() {}; base.Promise = function() {};
/** /**
......
...@@ -21,7 +21,7 @@ var remoting = remoting || {}; ...@@ -21,7 +21,7 @@ var remoting = remoting || {};
remoting.HangoutSession = function() { remoting.HangoutSession = function() {
/** /**
* @private * @private
* @type {chrome.extension.Port} * @type {chrome.runtime.Port}
*/ */
this.port_ = null; this.port_ = null;
}; };
......
...@@ -28,7 +28,7 @@ remoting.HostDaemonFacade = function() { ...@@ -28,7 +28,7 @@ remoting.HostDaemonFacade = function() {
*/ */
this.pendingReplies_ = {}; this.pendingReplies_ = {};
/** @type {?chrome.extension.Port} @private */ /** @type {?chrome.runtime.Port} @private */
this.port_ = null; this.port_ = null;
/** @type {string} @private */ /** @type {string} @private */
......
<!doctype html>
<!--
Copyright (c) 2013 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>
<meta charset="utf-8">
<meta-include type="javascript"/>
</head>
<body>
</body>
</html>
...@@ -23,7 +23,7 @@ remoting.It2MeHostFacade = function() { ...@@ -23,7 +23,7 @@ remoting.It2MeHostFacade = function() {
this.nextId_ = 0; this.nextId_ = 0;
/** /**
* @type {?chrome.extension.Port} * @type {?chrome.runtime.Port}
* @private * @private
*/ */
this.port_ = null; this.port_ = null;
......
...@@ -9,6 +9,14 @@ ...@@ -9,6 +9,14 @@
/** @type {Object} */ /** @type {Object} */
var chrome = {}; var chrome = {};
/** @constructor */
chrome.Event = function() {};
/** @param {Function} callback */
chrome.Event.prototype.addListener = function(callback) {};
/** @param {Function} callback */
chrome.Event.prototype.removeListener = function(callback) {};
/** @type {Object} */ /** @type {Object} */
chrome.app = {}; chrome.app = {};
...@@ -31,7 +39,12 @@ chrome.app.window = { ...@@ -31,7 +39,12 @@ chrome.app.window = {
/** /**
* @return {AppWindow} * @return {AppWindow}
*/ */
current: function() {} current: function() {},
/**
* @param {string} id
* @param {function()=} opt_callback
*/
get: function(id, opt_callback) {}
}; };
...@@ -43,19 +56,29 @@ chrome.runtime = { ...@@ -43,19 +56,29 @@ chrome.runtime = {
message: '' message: ''
}, },
/** @return {{version: string, app: {background: Object}}} */ /** @return {{version: string, app: {background: Object}}} */
getManifest: function() {} getManifest: function() {},
/** @type {chrome.Event} */
onSuspend: null,
/** @type {chrome.Event} */
onConnect: null,
/** @type {chrome.Event} */
onConnectExternal: null,
/** @type {chrome.Event} */
onMessage: null,
/** @type {chrome.Event} */
onMessageExternal: null
}; };
/** /**
* @type {?function(string):chrome.extension.Port} * @type {?function(string):chrome.runtime.Port}
*/ */
chrome.runtime.connectNative = function(name) {}; chrome.runtime.connectNative = function(name) {};
/** /**
* @param {{name:string}} connectInfo * @param {{ name: string}} config
* @return {chrome.extension.Port} * @return {chrome.runtime.Port}
*/ */
chrome.runtime.connect = function(connectInfo) {}; chrome.runtime.connect = function(config) {};
/** /**
* @param {string} extensionId * @param {string} extensionId
...@@ -66,22 +89,40 @@ chrome.runtime.connect = function(connectInfo) {}; ...@@ -66,22 +89,40 @@ chrome.runtime.connect = function(connectInfo) {};
chrome.runtime.sendMessage = function( chrome.runtime.sendMessage = function(
extensionId, message, opt_options, opt_callback) {}; extensionId, message, opt_options, opt_callback) {};
/** @type {Object} */ /** @constructor */
chrome.extension = {}; chrome.runtime.MessageSender = function(){
/** @type {chrome.Tab} */
this.tab = null;
};
/** @constructor */ /** @constructor */
chrome.extension.Port = function() {}; chrome.runtime.Port = function() {
this.onMessage = new chrome.Event();
this.onDisconnect = new chrome.Event();
/** @type {string} */
this.name = '';
/** @type {chrome.runtime.MessageSender} */
this.sender = null;
};
/** @type {chrome.Event} */ /** @type {chrome.Event} */
chrome.extension.Port.prototype.onMessage; chrome.runtime.Port.prototype.onMessage = null;
/** @type {chrome.Event} */ /** @type {chrome.Event} */
chrome.extension.Port.prototype.onDisconnect; chrome.runtime.Port.prototype.onDisconnect = null;
chrome.runtime.Port.prototype.disconnect = function() {};
/** /**
* @param {Object} message * @param {Object} message
*/ */
chrome.extension.Port.prototype.postMessage = function(message) {}; chrome.runtime.Port.prototype.postMessage = function(message) {};
/** @type {Object} */
chrome.extension = {};
/** /**
* @param {*} message * @param {*} message
...@@ -148,7 +189,7 @@ chrome.Storage.prototype.clear = function(opt_callback) {}; ...@@ -148,7 +189,7 @@ chrome.Storage.prototype.clear = function(opt_callback) {};
* src/chrome/common/extensions/api/context_menus.json * src/chrome/common/extensions/api/context_menus.json
*/ */
chrome.contextMenus = {}; chrome.contextMenus = {};
/** @type {ChromeEvent} */ /** @type {chrome.Event} */
chrome.contextMenus.onClicked; chrome.contextMenus.onClicked;
/** /**
* @param {!Object} createProperties * @param {!Object} createProperties
...@@ -216,27 +257,6 @@ chrome.identity = { ...@@ -216,27 +257,6 @@ chrome.identity = {
launchWebAuthFlow: function(parameters, callback) {} launchWebAuthFlow: function(parameters, callback) {}
}; };
// TODO(garykac): Combine chrome.Event and ChromeEvent
/** @constructor */
function ChromeEvent() {}
/** @param {Function} callback */
ChromeEvent.prototype.addListener = function(callback) {};
/** @param {Function} callback */
ChromeEvent.prototype.removeListener = function(callback) {};
/** @param {Function} callback */
ChromeEvent.prototype.hasListener = function(callback) {};
/** @param {Function} callback */
ChromeEvent.prototype.hasListeners = function(callback) {};
/** @constructor */
chrome.Event = function() {};
/** @param {function():void} callback */
chrome.Event.prototype.addListener = function(callback) {};
/** @param {function():void} callback */
chrome.Event.prototype.removeListener = function(callback) {};
/** @type {Object} */ /** @type {Object} */
chrome.permissions = { chrome.permissions = {
...@@ -259,12 +279,33 @@ chrome.tabs = {}; ...@@ -259,12 +279,33 @@ chrome.tabs = {};
/** @param {function(chrome.Tab):void} callback */ /** @param {function(chrome.Tab):void} callback */
chrome.tabs.getCurrent = function(callback) {}; chrome.tabs.getCurrent = function(callback) {};
/**
* @param {Object?} options
* @param {function(chrome.Tab)=} opt_callback
*/
chrome.tabs.create = function(options, opt_callback) {};
/**
* @param {string} id
* @param {function(chrome.Tab)} callback
*/
chrome.tabs.get = function(id, callback) {};
/**
* @param {string} id
* @param {function()=} opt_callback
*/
chrome.tabs.remove = function(id, opt_callback) {};
/** @constructor */ /** @constructor */
chrome.Tab = function() { chrome.Tab = function() {
/** @type {boolean} */ /** @type {boolean} */
this.pinned = false; this.pinned = false;
/** @type {number} */ /** @type {number} */
this.windowId = 0; this.windowId = 0;
/** @type {string} */
this.id = '';
}; };
...@@ -294,6 +335,8 @@ var AppWindow = function() { ...@@ -294,6 +335,8 @@ var AppWindow = function() {
this.onMaximized = null; this.onMaximized = null;
/** @type {chrome.Event} */ /** @type {chrome.Event} */
this.onFullscreened = null; this.onFullscreened = null;
/** @type {string} */
this.id = '';
}; };
AppWindow.prototype.close = function() {}; AppWindow.prototype.close = function() {};
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
} }
{% else %} {% else %}
"background": { "background": {
"scripts": ["background.js"] "page": "background.html"
} }
{% endif %} {% endif %}
}, },
"icons": { "icons": {
......
...@@ -56,6 +56,25 @@ test('dispose(obj) should not crash if |obj| is null', ...@@ -56,6 +56,25 @@ test('dispose(obj) should not crash if |obj| is null',
base.dispose(null); base.dispose(null);
}); });
test('urljoin(url, opt_param) should return url if |opt_param| is missing',
function() {
QUnit.equal(
base.urlJoin('http://www.chromium.org'), 'http://www.chromium.org');
});
test('urljoin(url, opt_param) should urlencode |opt_param|',
function() {
var result = base.urlJoin('http://www.chromium.org', {
a: 'a',
foo: 'foo',
escapist: ':/?#[]@$&+,;='
});
QUnit.equal(
result,
'http://www.chromium.org?a=a&foo=foo' +
'&escapist=%3A%2F%3F%23%5B%5D%40%24%26%2B%2C%3B%3D');
});
QUnit.asyncTest('Promise.sleep(delay) should fulfill the promise after |delay|', QUnit.asyncTest('Promise.sleep(delay) should fulfill the promise after |delay|',
function() { function() {
var isCalled = false; var isCalled = false;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment