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 @@
'host/win/host_messages.mc.jinja2',
'host/win/version.rc.jinja2',
'resources/play_store_resources.cc',
'webapp/background.js',
'webapp/background/background.js',
'webapp/butter_bar.js',
'webapp/client_screen.js',
'webapp/error.js',
......
......@@ -85,6 +85,22 @@
'--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'
......@@ -114,7 +130,6 @@
'variables': {
'output_dir': '<(PRODUCT_DIR)/remoting/remoting.webapp.v2',
'zip_path': '<(PRODUCT_DIR)/remoting-webapp.v2.zip',
'extra_files': [ 'webapp/background.js' ],
},
'conditions': [
['disable_nacl==0 and disable_nacl_untrusted==0', {
......
......@@ -297,6 +297,7 @@
'webapp_js_files': [
'<@(remoting_webapp_main_html_js_files)',
'<@(remoting_webapp_js_wcs_sandbox_files)',
'<@(remoting_webapp_background_js_files)',
]
},
'copies': [
......
......@@ -11,6 +11,7 @@
'generated_html_files': [
'<(SHARED_INTERMEDIATE_DIR)/main.html',
'<(SHARED_INTERMEDIATE_DIR)/wcs_sandbox.html',
'<(SHARED_INTERMEDIATE_DIR)/background.html',
],
},
'dependencies': [
......
......@@ -131,6 +131,9 @@
],
# These product files are excluded from our JavaScript unittest
'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
# makes it the entry point of the webapp.
'webapp/event_handlers.js',
......@@ -165,7 +168,16 @@
'<@(remoting_webapp_js_wcs_container_files)',
# Uncomment this line to include browser test files in the web app
# 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.
......@@ -179,6 +191,7 @@
'remoting_webapp_all_js_files': [
# JS files for main.html.
'<@(remoting_webapp_main_html_js_files)',
'<@(remoting_webapp_background_js_files)',
# JS files for wcs_sandbox.html.
# 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.
......@@ -231,6 +244,9 @@
'remoting_webapp_template_wcs_sandbox':
'webapp/html/template_wcs_sandbox.html',
'remoting_webapp_template_background':
'webapp/html/template_background.html',
'remoting_webapp_template_files': [
'webapp/html/butterbar.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) {
});
};
/**
* 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() {};
/**
......
......@@ -21,7 +21,7 @@ var remoting = remoting || {};
remoting.HangoutSession = function() {
/**
* @private
* @type {chrome.extension.Port}
* @type {chrome.runtime.Port}
*/
this.port_ = null;
};
......
......@@ -28,7 +28,7 @@ remoting.HostDaemonFacade = function() {
*/
this.pendingReplies_ = {};
/** @type {?chrome.extension.Port} @private */
/** @type {?chrome.runtime.Port} @private */
this.port_ = null;
/** @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() {
this.nextId_ = 0;
/**
* @type {?chrome.extension.Port}
* @type {?chrome.runtime.Port}
* @private
*/
this.port_ = null;
......
......@@ -9,6 +9,14 @@
/** @type {Object} */
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} */
chrome.app = {};
......@@ -31,7 +39,12 @@ chrome.app.window = {
/**
* @return {AppWindow}
*/
current: function() {}
current: function() {},
/**
* @param {string} id
* @param {function()=} opt_callback
*/
get: function(id, opt_callback) {}
};
......@@ -43,19 +56,29 @@ chrome.runtime = {
message: ''
},
/** @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) {};
/**
* @param {{name:string}} connectInfo
* @return {chrome.extension.Port}
* @param {{ name: string}} config
* @return {chrome.runtime.Port}
*/
chrome.runtime.connect = function(connectInfo) {};
chrome.runtime.connect = function(config) {};
/**
* @param {string} extensionId
......@@ -66,22 +89,40 @@ chrome.runtime.connect = function(connectInfo) {};
chrome.runtime.sendMessage = function(
extensionId, message, opt_options, opt_callback) {};
/** @type {Object} */
chrome.extension = {};
/** @constructor */
chrome.runtime.MessageSender = function(){
/** @type {chrome.Tab} */
this.tab = null;
};
/** @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} */
chrome.extension.Port.prototype.onMessage;
chrome.runtime.Port.prototype.onMessage = null;
/** @type {chrome.Event} */
chrome.extension.Port.prototype.onDisconnect;
chrome.runtime.Port.prototype.onDisconnect = null;
chrome.runtime.Port.prototype.disconnect = function() {};
/**
* @param {Object} message
*/
chrome.extension.Port.prototype.postMessage = function(message) {};
chrome.runtime.Port.prototype.postMessage = function(message) {};
/** @type {Object} */
chrome.extension = {};
/**
* @param {*} message
......@@ -148,7 +189,7 @@ chrome.Storage.prototype.clear = function(opt_callback) {};
* src/chrome/common/extensions/api/context_menus.json
*/
chrome.contextMenus = {};
/** @type {ChromeEvent} */
/** @type {chrome.Event} */
chrome.contextMenus.onClicked;
/**
* @param {!Object} createProperties
......@@ -216,27 +257,6 @@ chrome.identity = {
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} */
chrome.permissions = {
......@@ -259,12 +279,33 @@ chrome.tabs = {};
/** @param {function(chrome.Tab):void} 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 */
chrome.Tab = function() {
/** @type {boolean} */
this.pinned = false;
/** @type {number} */
this.windowId = 0;
/** @type {string} */
this.id = '';
};
......@@ -294,6 +335,8 @@ var AppWindow = function() {
this.onMaximized = null;
/** @type {chrome.Event} */
this.onFullscreened = null;
/** @type {string} */
this.id = '';
};
AppWindow.prototype.close = function() {};
......
......@@ -13,8 +13,8 @@
}
{% else %}
"background": {
"scripts": ["background.js"]
}
"page": "background.html"
}
{% endif %}
},
"icons": {
......
......@@ -56,6 +56,25 @@ test('dispose(obj) should not crash if |obj| is 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|',
function() {
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