diff options
-rw-r--r-- | remoting/remoting.gyp | 2 | ||||
-rw-r--r-- | remoting/remoting_client.gypi | 17 | ||||
-rw-r--r-- | remoting/remoting_test.gypi | 1 | ||||
-rw-r--r-- | remoting/remoting_webapp.gypi | 1 | ||||
-rw-r--r-- | remoting/remoting_webapp_files.gypi | 18 | ||||
-rw-r--r-- | remoting/webapp/background.js | 33 | ||||
-rw-r--r-- | remoting/webapp/background/app_launcher.js | 132 | ||||
-rw-r--r-- | remoting/webapp/background/background.js | 57 | ||||
-rw-r--r-- | remoting/webapp/base.js | 20 | ||||
-rw-r--r-- | remoting/webapp/hangout_session.js | 2 | ||||
-rw-r--r-- | remoting/webapp/host_daemon_facade.js | 2 | ||||
-rw-r--r-- | remoting/webapp/html/template_background.html | 15 | ||||
-rw-r--r-- | remoting/webapp/it2me_host_facade.js | 2 | ||||
-rw-r--r-- | remoting/webapp/js_proto/chrome_proto.js | 111 | ||||
-rw-r--r-- | remoting/webapp/manifest.json.jinja2 | 4 | ||||
-rw-r--r-- | remoting/webapp/unittests/base_unittest.js | 19 |
16 files changed, 361 insertions, 75 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index f197916..2a902d6 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -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', diff --git a/remoting/remoting_client.gypi b/remoting/remoting_client.gypi index d7d66f1..3da83ae 100644 --- a/remoting/remoting_client.gypi +++ b/remoting/remoting_client.gypi @@ -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', { diff --git a/remoting/remoting_test.gypi b/remoting/remoting_test.gypi index 70d5f9f..03292a4 100644 --- a/remoting/remoting_test.gypi +++ b/remoting/remoting_test.gypi @@ -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': [ diff --git a/remoting/remoting_webapp.gypi b/remoting/remoting_webapp.gypi index ea498b6..19692d1 100644 --- a/remoting/remoting_webapp.gypi +++ b/remoting/remoting_webapp.gypi @@ -11,6 +11,7 @@ 'generated_html_files': [ '<(SHARED_INTERMEDIATE_DIR)/main.html', '<(SHARED_INTERMEDIATE_DIR)/wcs_sandbox.html', + '<(SHARED_INTERMEDIATE_DIR)/background.html', ], }, 'dependencies': [ diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi index 93b74bb..7bfa958 100644 --- a/remoting/remoting_webapp_files.gypi +++ b/remoting/remoting_webapp_files.gypi @@ -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', diff --git a/remoting/webapp/background.js b/remoting/webapp/background.js deleted file mode 100644 index 1976b91..0000000 --- a/remoting/webapp/background.js +++ /dev/null @@ -1,33 +0,0 @@ -// 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(); diff --git a/remoting/webapp/background/app_launcher.js b/remoting/webapp/background/app_launcher.js new file mode 100644 index 0000000..d1fc57c --- /dev/null +++ b/remoting/webapp/background/app_launcher.js @@ -0,0 +1,132 @@ +// 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(); +}; diff --git a/remoting/webapp/background/background.js b/remoting/webapp/background/background.js new file mode 100644 index 0000000..5f26788 --- /dev/null +++ b/remoting/webapp/background/background.js @@ -0,0 +1,57 @@ +// 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); + +}()); diff --git a/remoting/webapp/base.js b/remoting/webapp/base.js index 4771e53..d12db235 100644 --- a/remoting/webapp/base.js +++ b/remoting/webapp/base.js @@ -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() {}; /** diff --git a/remoting/webapp/hangout_session.js b/remoting/webapp/hangout_session.js index 7f11ee3..58c1804 100644 --- a/remoting/webapp/hangout_session.js +++ b/remoting/webapp/hangout_session.js @@ -21,7 +21,7 @@ var remoting = remoting || {}; remoting.HangoutSession = function() { /** * @private - * @type {chrome.extension.Port} + * @type {chrome.runtime.Port} */ this.port_ = null; }; diff --git a/remoting/webapp/host_daemon_facade.js b/remoting/webapp/host_daemon_facade.js index 5b1567a..abd9719 100644 --- a/remoting/webapp/host_daemon_facade.js +++ b/remoting/webapp/host_daemon_facade.js @@ -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 */ diff --git a/remoting/webapp/html/template_background.html b/remoting/webapp/html/template_background.html new file mode 100644 index 0000000..c9d416b --- /dev/null +++ b/remoting/webapp/html/template_background.html @@ -0,0 +1,15 @@ +<!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> diff --git a/remoting/webapp/it2me_host_facade.js b/remoting/webapp/it2me_host_facade.js index e83e225..b8ce5d6 100644 --- a/remoting/webapp/it2me_host_facade.js +++ b/remoting/webapp/it2me_host_facade.js @@ -23,7 +23,7 @@ remoting.It2MeHostFacade = function() { this.nextId_ = 0; /** - * @type {?chrome.extension.Port} + * @type {?chrome.runtime.Port} * @private */ this.port_ = null; diff --git a/remoting/webapp/js_proto/chrome_proto.js b/remoting/webapp/js_proto/chrome_proto.js index f7b9a6f..455af5e 100644 --- a/remoting/webapp/js_proto/chrome_proto.js +++ b/remoting/webapp/js_proto/chrome_proto.js @@ -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() {}; diff --git a/remoting/webapp/manifest.json.jinja2 b/remoting/webapp/manifest.json.jinja2 index 1ce73d2..a5efc1f 100644 --- a/remoting/webapp/manifest.json.jinja2 +++ b/remoting/webapp/manifest.json.jinja2 @@ -13,8 +13,8 @@ } {% else %} "background": { - "scripts": ["background.js"] - } + "page": "background.html" + } {% endif %} }, "icons": { diff --git a/remoting/webapp/unittests/base_unittest.js b/remoting/webapp/unittests/base_unittest.js index d021a80..17a1903 100644 --- a/remoting/webapp/unittests/base_unittest.js +++ b/remoting/webapp/unittests/base_unittest.js @@ -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; |