summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-17 01:54:39 +0000
committerjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-17 01:54:39 +0000
commit7caa553c7857652e2fe13b9beeed38958275c83e (patch)
tree1febf0c4dcd10c9a5f2271afa1dd0a4fb782c5b8 /remoting
parentedd55cb2839886784859c5b3088b36eefb9f111e (diff)
downloadchromium_src-7caa553c7857652e2fe13b9beeed38958275c83e.zip
chromium_src-7caa553c7857652e2fe13b9beeed38958275c83e.tar.gz
chromium_src-7caa553c7857652e2fe13b9beeed38958275c83e.tar.bz2
Allow resize-to-client to be enabled by the user.
Off by default (on all platforms) but saved between connections. BUG=157254 Review URL: https://chromiumcodereview.appspot.com/11886007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@177306 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/host/remoting_me2me_host.cc5
-rw-r--r--remoting/remoting.gyp1
-rw-r--r--remoting/resources/remoting_strings.grd6
-rw-r--r--remoting/webapp/client_plugin.js1
-rw-r--r--remoting/webapp/client_screen.js9
-rw-r--r--remoting/webapp/client_session.js192
-rw-r--r--remoting/webapp/host_settings.js99
-rw-r--r--remoting/webapp/main.html8
8 files changed, 243 insertions, 78 deletions
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 741a1d2..67b1045 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -909,13 +909,8 @@ void HostProcess::StartHost() {
new LogToServer(host_, ServerLogEntry::ME2ME, signal_strategy_.get()));
host_event_logger_ = HostEventLogger::Create(host_, kApplicationName);
-#if defined(OS_LINUX)
- // Desktop resizing is implemented on all three platforms, but may not be
- // the right thing to do for non-virtual desktops. Disable it until we can
- // implement a configuration UI.
resizing_host_observer_.reset(
new ResizingHostObserver(desktop_resizer_.get(), host_));
-#endif
// Create a host observer to enable/disable curtain mode as clients connect
// and disconnect.
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 902fb72..86a68e5 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -179,6 +179,7 @@
'webapp/host_list.js',
'webapp/host_screen.js',
'webapp/host_session.js',
+ 'webapp/host_settings.js',
'webapp/host_setup_dialog.js',
'webapp/host_table_entry.js',
'webapp/l10n.js',
diff --git a/remoting/resources/remoting_strings.grd b/remoting/resources/remoting_strings.grd
index ce91dbc..d3efbba 100644
--- a/remoting/resources/remoting_strings.grd
+++ b/remoting/resources/remoting_strings.grd
@@ -447,9 +447,6 @@
<message desc="Modified version of the host name shown for hosts that are not currently accessible." name="IDR_OFFLINE">
<ph name="HOSTNAME">$1<ex>My Linux desktop</ex></ph> (offline)
</message>
- <message desc="Menu option for disabling shrink-to-fit. When clicked, the remote desktop will be displayed at 1:1 scale." name="IDR_ORIGINAL_SIZE">
- Original size
- </message>
<message desc="In the connection history dialog, clicking this button shows only recent connections from this computer." name="IDR_OUTGOING_CONNECTIONS">
From this computer
</message>
@@ -468,6 +465,9 @@
<message desc="Label for button to reconnect to the previous Me2Me host. This button appears on the 'session-finished' page." name="IDR_RECONNECT">
Reconnect
</message>
+ <message desc="Button for enabling or disabling the 'resize-to-client' functionality, whereby the host desktop is resized to match the client size as closely as possible." name="IDR_RESIZE_TO_CLIENT">
+ Resize desktop to fit
+ </message>
<message desc="Label for button to retry connecting to a Me2Me host, after failing to connect to that host. This button appears on the 'session-finished' page." name="IDR_RETRY">
Retry
</message>
diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js
index 0d91c07..97a10da 100644
--- a/remoting/webapp/client_plugin.js
+++ b/remoting/webapp/client_plugin.js
@@ -53,7 +53,6 @@ remoting.ClientPlugin.prototype.isSupportedVersion = function() {};
* @enum {string}
*/
remoting.ClientPlugin.Feature = {
- HIGH_QUALITY_SCALING: 'highQualityScaling',
INJECT_KEY_EVENT: 'injectKeyEvent',
NOTIFY_CLIENT_DIMENSIONS: 'notifyClientDimensions',
PAUSE_VIDEO: 'pauseVideo',
diff --git a/remoting/webapp/client_screen.js b/remoting/webapp/client_screen.js
index 0ee39dd..a1a01b9 100644
--- a/remoting/webapp/client_screen.js
+++ b/remoting/webapp/client_screen.js
@@ -94,15 +94,6 @@ remoting.cancelConnect = function() {
*/
/**
- * Toggle the scale-to-fit feature for the current client session.
- *
- * @return {void} Nothing.
- */
-remoting.toggleScaleToFit = function() {
- remoting.clientSession.setScaleToFit(!remoting.clientSession.getScaleToFit());
-};
-
-/**
* Update the remoting client layout in response to a resize event.
*
* @return {void} Nothing.
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index 5659a77..3dfaa43 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -30,8 +30,8 @@ var remoting = remoting || {};
* for Me2Me.
* @param {string} authenticationMethods Comma-separated list of
* authentication methods the client should attempt to use.
- * @param {string} authenticationTag A host-specific tag to mix into
- * authentication hashes.
+ * @param {string} hostId The host identifier for Me2Me, or empty for IT2Me.
+ * Mixed into authentication hashes for some authentication methods.
* @param {remoting.ClientSession.Mode} mode The mode of this connection.
* @param {function(remoting.ClientSession.State,
remoting.ClientSession.State):void} onStateChange
@@ -39,7 +39,7 @@ var remoting = remoting || {};
* @constructor
*/
remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret,
- authenticationMethods, authenticationTag,
+ authenticationMethods, hostId,
mode, onStateChange) {
this.state = remoting.ClientSession.State.CREATED;
@@ -47,30 +47,43 @@ remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret,
this.hostPublicKey = hostPublicKey;
this.sharedSecret = sharedSecret;
this.authenticationMethods = authenticationMethods;
- this.authenticationTag = authenticationTag;
+ this.hostId = hostId;
this.mode = mode;
this.clientJid = '';
this.sessionId = '';
/** @type {remoting.ClientPlugin} */
this.plugin = null;
- this.scaleToFit = false;
+ /** @private */
+ this.shrinkToFit_ = true;
+ /** @private */
+ this.resizeToClient_ = false;
+ /** @private */
this.hasReceivedFrame_ = false;
this.logToServer = new remoting.LogToServer();
this.onStateChange = onStateChange;
/** @type {number?} @private */
this.notifyClientDimensionsTimer_ = null;
+ /** @type {number?} @private */
+ this.bumpScrollTimer_ = null;
+
+ /**
+ * Allow error reporting to be suppressed in situations where it would not
+ * be useful, for example, when the device is offline.
+ *
+ * @type {boolean} @private
+ */
+ this.logErrors_ = true;
/** @private */
this.callPluginLostFocus_ = this.pluginLostFocus_.bind(this);
/** @private */
this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this);
/** @private */
- this.callEnableShrink_ = this.setScaleToFit.bind(this, true);
- /** @private */
- this.callDisableShrink_ = this.setScaleToFit.bind(this, false);
+ this.callSetScreenMode_ = this.onSetScreenMode_.bind(this);
/** @private */
this.callToggleFullScreen_ = this.toggleFullScreen_.bind(this);
+
/** @private */
this.screenOptionsMenu_ = new remoting.MenuButton(
document.getElementById('screen-options-menu'),
@@ -81,24 +94,26 @@ remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret,
);
/** @type {HTMLElement} @private */
- this.shrinkToFit_ = document.getElementById('enable-shrink-to-fit');
+ this.resizeToClientButton_ =
+ document.getElementById('screen-resize-to-client');
/** @type {HTMLElement} @private */
- this.originalSize_ = document.getElementById('disable-shrink-to-fit');
+ this.shrinkToFitButton_ = document.getElementById('screen-shrink-to-fit');
/** @type {HTMLElement} @private */
- this.fullScreen_ = document.getElementById('toggle-full-screen');
+ this.fullScreenButton_ = document.getElementById('toggle-full-screen');
- this.shrinkToFit_.addEventListener('click', this.callEnableShrink_, false);
- this.originalSize_.addEventListener('click', this.callDisableShrink_, false);
- this.fullScreen_.addEventListener('click', this.callToggleFullScreen_, false);
- /** @type {number?} @private */
- this.bumpScrollTimer_ = null;
- /**
- * Allow error reporting to be suppressed in situations where it would not
- * be useful, for example, when the device is offline.
- *
- * @type {boolean} @private
- */
- this.logErrors_ = true;
+ if (this.mode == remoting.ClientSession.Mode.IT2ME) {
+ // Resize-to-client is not supported for IT2Me hosts.
+ this.resizeToClientButton_.parentNode.removeChild(
+ this.resizeToClientButton_);
+ } else {
+ this.resizeToClientButton_.addEventListener(
+ 'click', this.callSetScreenMode_, false);
+ }
+
+ this.shrinkToFitButton_.addEventListener(
+ 'click', this.callSetScreenMode_, false);
+ this.fullScreenButton_.addEventListener(
+ 'click', this.callToggleFullScreen_, false);
};
// Note that the positive values in both of these enums are copied directly
@@ -166,6 +181,10 @@ remoting.ClientSession.STATS_KEY_DECODE_LATENCY = 'decodeLatency';
remoting.ClientSession.STATS_KEY_RENDER_LATENCY = 'renderLatency';
remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY = 'roundtripLatency';
+// Keys for per-host settings.
+remoting.ClientSession.KEY_RESIZE_TO_CLIENT = 'resizeToClient';
+remoting.ClientSession.KEY_SHRINK_TO_FIT = 'shrinkToFit';
+
/**
* The current state of the session.
* @type {remoting.ClientSession.State}
@@ -245,6 +264,28 @@ remoting.ClientSession.prototype.pluginLostFocus_ = function() {
remoting.ClientSession.prototype.createPluginAndConnect =
function(container) {
this.plugin = this.createClientPlugin_(container, this.PLUGIN_ID);
+ remoting.HostSettings.load(this.hostId,
+ this.onHostSettingsLoaded_.bind(this));
+};
+
+/**
+ * @param {Object.<string>} options The current options for the host, or {}
+ * if this client has no saved settings for the host.
+ * @private
+ */
+remoting.ClientSession.prototype.onHostSettingsLoaded_ = function(options) {
+ if (remoting.ClientSession.KEY_RESIZE_TO_CLIENT in options &&
+ typeof(options[remoting.ClientSession.KEY_RESIZE_TO_CLIENT]) ==
+ 'boolean') {
+ this.resizeToClient_ = /** @type {boolean} */
+ options[remoting.ClientSession.KEY_RESIZE_TO_CLIENT];
+ }
+ if (remoting.ClientSession.KEY_SHRINK_TO_FIT in options &&
+ typeof(options[remoting.ClientSession.KEY_SHRINK_TO_FIT]) ==
+ 'boolean') {
+ this.shrinkToFit_ = /** @type {boolean} */
+ options[remoting.ClientSession.KEY_SHRINK_TO_FIT];
+ }
this.plugin.element().focus();
@@ -292,11 +333,6 @@ remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) {
this.plugin.remapKey(0x0700e4, 0x0700e7);
}
- // Enable scale-to-fit if and only if the plugin is new enough for
- // high-quality scaling.
- this.setScaleToFit(this.plugin.hasFeature(
- remoting.ClientPlugin.Feature.HIGH_QUALITY_SCALING));
-
/** @param {string} msg The IQ stanza to send. */
this.plugin.onOutgoingIqHandler = this.sendIq_.bind(this);
/** @param {string} msg The message to log. */
@@ -330,11 +366,12 @@ remoting.ClientSession.prototype.removePlugin = function() {
this.plugin.cleanup();
this.plugin = null;
}
- this.shrinkToFit_.removeEventListener('click', this.callEnableShrink_, false);
- this.originalSize_.removeEventListener('click', this.callDisableShrink_,
- false);
- this.fullScreen_.removeEventListener('click', this.callToggleFullScreen_,
- false);
+ this.resizeToClientButton_.removeEventListener(
+ 'click', this.callSetScreenMode_, false);
+ this.shrinkToFitButton_.removeEventListener(
+ 'click', this.callSetScreenMode_, false);
+ this.fullScreenButton_.removeEventListener(
+ 'click', this.callToggleFullScreen_, false);
};
/**
@@ -404,27 +441,63 @@ remoting.ClientSession.prototype.sendPrintScreen = function() {
}
/**
- * Enables or disables the client's scale-to-fit feature.
+ * Callback for the two "screen mode" related menu items: Resize desktop to
+ * fit and Shrink to fit.
*
- * @param {boolean} scaleToFit True to enable scale-to-fit, false otherwise.
+ * @param {Event} event The click event indicating which mode was selected.
* @return {void} Nothing.
+ * @private
*/
-remoting.ClientSession.prototype.setScaleToFit = function(scaleToFit) {
- this.scaleToFit = scaleToFit;
- this.updateDimensions();
- // If enabling scaling, reset bump-scroll offsets.
- if (scaleToFit) {
- this.scroll_(0, 0);
+remoting.ClientSession.prototype.onSetScreenMode_ = function(event) {
+ var shrinkToFit = this.shrinkToFit_;
+ var resizeToClient = this.resizeToClient_;
+ if (event.target == this.shrinkToFitButton_) {
+ shrinkToFit = !shrinkToFit;
}
-}
+ if (event.target == this.resizeToClientButton_) {
+ resizeToClient = !resizeToClient;
+ }
+ this.setScreenMode_(shrinkToFit, resizeToClient);
+};
/**
- * Returns whether the client is currently scaling the host to fit the tab.
+ * Set the shrink-to-fit and resize-to-client flags and save them if this is
+ * a Me2Me connection.
*
- * @return {boolean} The current scale-to-fit setting.
+ * @param {boolean} shrinkToFit True if the remote desktop should be scaled
+ * down if it is larger than the client window; false if scroll-bars
+ * should be added in this case.
+ * @param {boolean} resizeToClient True if window resizes should cause the
+ * host to attempt to resize its desktop to match the client window size;
+ * false to disable this behaviour for subsequent window resizes--the
+ * current host desktop size is not restored in this case.
+ * @return {void} Nothing.
+ * @private
*/
-remoting.ClientSession.prototype.getScaleToFit = function() {
- return this.scaleToFit;
+remoting.ClientSession.prototype.setScreenMode_ =
+ function(shrinkToFit, resizeToClient) {
+
+ if (resizeToClient && !this.resizeToClient_) {
+ this.plugin.notifyClientDimensions(window.innerWidth, window.innerHeight);
+ }
+
+ // If enabling shrink, reset bump-scroll offsets.
+ var needsScrollReset = shrinkToFit && !this.shrinkToFit_;
+
+ this.shrinkToFit_ = shrinkToFit;
+ this.resizeToClient_ = resizeToClient;
+
+ if (this.hostId != '') {
+ var options = {};
+ options[remoting.ClientSession.KEY_SHRINK_TO_FIT] = this.shrinkToFit_;
+ options[remoting.ClientSession.KEY_RESIZE_TO_CLIENT] = this.resizeToClient_;
+ remoting.HostSettings.save(this.hostId, options);
+ }
+
+ this.updateDimensions();
+ if (needsScrollReset) {
+ this.scroll_(0, 0);
+ }
}
/**
@@ -495,7 +568,7 @@ remoting.ClientSession.prototype.connectPluginToWcs_ = function() {
remoting.wcs.setOnIq(onIncomingIq);
this.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid,
this.sharedSecret, this.authenticationMethods,
- this.authenticationTag);
+ this.hostId);
};
/**
@@ -510,7 +583,9 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ =
function(status, error) {
if (status == remoting.ClientSession.State.CONNECTED) {
this.onDesktopSizeChanged_();
- this.plugin.notifyClientDimensions(window.innerWidth, window.innerHeight)
+ if (this.resizeToClient_) {
+ this.plugin.notifyClientDimensions(window.innerWidth, window.innerHeight);
+ }
} else if (status == remoting.ClientSession.State.FAILED) {
this.error = /** @type {remoting.ClientSession.ConnectionError} */ (error);
}
@@ -573,11 +648,13 @@ remoting.ClientSession.prototype.onResize = function() {
// Defer notifying the host of the change until the window stops resizing, to
// avoid overloading the control channel with notifications.
- this.notifyClientDimensionsTimer_ = window.setTimeout(
- this.plugin.notifyClientDimensions.bind(this.plugin,
- window.innerWidth,
- window.innerHeight),
- 1000);
+ if (this.resizeToClient_) {
+ this.notifyClientDimensionsTimer_ = window.setTimeout(
+ this.plugin.notifyClientDimensions.bind(this.plugin,
+ window.innerWidth,
+ window.innerHeight),
+ 1000);
+ }
// If bump-scrolling is enabled, adjust the plugin margins to fully utilize
// the new window area.
@@ -642,7 +719,7 @@ remoting.ClientSession.prototype.updateDimensions = function() {
var desktopHeight = this.plugin.desktopHeight;
var scale = 1.0;
- if (this.getScaleToFit()) {
+ if (this.shrinkToFit_) {
// Scale to fit the entire desktop in the client window.
var scaleFitWidth = Math.min(1.0, 1.0 * windowWidth / desktopWidth);
var scaleFitHeight = Math.min(1.0, 1.0 * windowHeight / desktopHeight);
@@ -754,9 +831,10 @@ remoting.ClientSession.prototype.toggleFullScreen_ = function() {
* @private
*/
remoting.ClientSession.prototype.onShowOptionsMenu_ = function() {
- remoting.MenuButton.select(this.shrinkToFit_, this.scaleToFit);
- remoting.MenuButton.select(this.originalSize_, !this.scaleToFit);
- remoting.MenuButton.select(this.fullScreen_, document.webkitIsFullScreen);
+ remoting.MenuButton.select(this.resizeToClientButton_, this.resizeToClient_);
+ remoting.MenuButton.select(this.shrinkToFitButton_, this.shrinkToFit_);
+ remoting.MenuButton.select(this.fullScreenButton_,
+ document.webkitIsFullScreen);
};
/**
diff --git a/remoting/webapp/host_settings.js b/remoting/webapp/host_settings.js
new file mode 100644
index 0000000..426d8b2
--- /dev/null
+++ b/remoting/webapp/host_settings.js
@@ -0,0 +1,99 @@
+// 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.
+
+/**
+ * @fileoverview
+ * Class handling saving and restoring of per-host options.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/** @type {Object} */
+remoting.HostSettings = {};
+
+/**
+ * Load the settings for the specified host. Settings are returned as a
+ * dictionary of (name, value) pairs.
+ *
+ * @param {string} hostId The host identifer for which to load options.
+ * @param {function(Object):void} callback Callback to which the
+ * current settings for the host are passed. If there are no settings,
+ * then an empty dictionary is passed.
+ * @return {void} Nothing.
+ */
+remoting.HostSettings.load = function(hostId, callback) {
+ /**
+ * @param {Object} requestedHost
+ * @param {Object} allHosts
+ * @return {void} Nothing.
+ */
+ var onDone = function(requestedHost, allHosts) {
+ callback(requestedHost);
+ };
+ remoting.HostSettings.loadInternal_(hostId, onDone);
+};
+
+/**
+ * Save the settings for the specified hosts. Existing settings with the same
+ * names will be overwritten; settings not currently saved will be created.
+ *
+ * @param {string} hostId The host identifer for which to save options.
+ * @param {Object} options The options to save, expressed as a dictionary of
+ * (name, value) pairs.
+ * @param {function():void=} opt_callback Optional completion callback.
+ * @return {void} Nothing.
+ */
+remoting.HostSettings.save = function(hostId, options, opt_callback) {
+ /**
+ * @param {Object} requestedHost
+ * @param {Object} allHosts
+ * @return {void} Nothing.
+ */
+ var onDone = function(requestedHost, allHosts) {
+ for (var option in options) {
+ requestedHost[option] = options[option];
+ }
+ allHosts[hostId] = requestedHost;
+ var newSettings = {};
+ newSettings[remoting.HostSettings.KEY_] = JSON.stringify(allHosts);
+ chrome.storage.local.set(newSettings, opt_callback);
+ };
+ remoting.HostSettings.loadInternal_(hostId, onDone);
+};
+
+/**
+ * Helper function for both load and save.
+ *
+ * @param {string} hostId The host identifer for which to load options.
+ * @param {function(Object, Object):void} callback Callback to which the
+ * current settings for the specified host and for all hosts are passed.
+ * @return {void} Nothing.
+ */
+remoting.HostSettings.loadInternal_ = function(hostId, callback) {
+ /**
+ * @param {Object.<string>} allHosts The current options for all hosts.
+ * @return {void} Nothing.
+ */
+ var onDone = function(allHosts) {
+ var result = {};
+ try {
+ var result = jsonParseSafe(allHosts[remoting.HostSettings.KEY_]);
+ if (hostId in result && typeof(result[hostId] == 'Object')) {
+ callback(result[hostId], /** @type {Object} */(result));
+ return;
+ }
+ } catch (err) {
+ var typedErr = /** @type {*} */ (err);
+ console.error('Error loading host settings:', typedErr);
+ }
+ callback({}, {});
+ };
+ chrome.storage.local.get(remoting.HostSettings.KEY_, onDone);
+};
+
+/** @type {string} @private */
+remoting.HostSettings.KEY_ = 'remoting-host-options'; \ No newline at end of file
diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html
index 26fa487..a3cbb54 100644
--- a/remoting/webapp/main.html
+++ b/remoting/webapp/main.html
@@ -16,7 +16,6 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="menu_button.css">
<link rel="stylesheet" href="toolbar.css">
- <script src="host_setup_dialog.js"></script>
<script src="client_plugin.js"></script>
<script src="client_plugin_async.js"></script>
<script src="client_screen.js"></script>
@@ -30,6 +29,8 @@ found in the LICENSE file.
<script src="host_list.js"></script>
<script src="host_screen.js"></script>
<script src="host_session.js"></script>
+ <script src="host_settings.js"></script>
+ <script src="host_setup_dialog.js"></script>
<script src="host_table_entry.js"></script>
<script src="l10n.js"></script>
<script src="log_to_server.js"></script>
@@ -624,8 +625,9 @@ found in the LICENSE file.
class="kd-disclosureindicator">
</button>
<ul>
- <li id="enable-shrink-to-fit" i18n-content="SHRINK_TO_FIT"></li>
- <li id="disable-shrink-to-fit" i18n-content="ORIGINAL_SIZE"></li>
+ <li id="screen-resize-to-client"
+ i18n-content="RESIZE_TO_CLIENT"></li>
+ <li id="screen-shrink-to-fit" i18n-content="SHRINK_TO_FIT"></li>
<li class="menu-separator"></li>
<li id="toggle-full-screen" i18n-content="FULL_SCREEN"></li>
</ul>