Bug 944443 - Uplift rotation feature from simulator to b2g desktop. r=vingtetun

This commit is contained in:
Alexandre Poirot 2014-03-19 08:38:59 -04:00
parent 774578ba82
commit 68ca432d8e
10 changed files with 339 additions and 36 deletions

View File

@ -34,3 +34,26 @@
background-color: #888;
border-color: black;
}
#rotate-button {
position: absolute;
top: 3px;
bottom: 3px;
right: 3px;
width: 24px;
height: 24px;
background: #eee url("images/desktop/rotate.png") center no-repeat;
border: 1px solid #888;
border-radius: 12px;
display: block;
}
#rotate-button:hover {
background-color: #ccc;
border-color: #555;
}
#rotate-button.active {
background-color: #888;
border-color: black;
}

View File

@ -16,7 +16,7 @@ function enableTouch() {
touchEventHandler.start();
}
function setupHomeButton() {
function setupButtons() {
let homeButton = document.getElementById('home-button');
if (!homeButton) {
// The toolbar only exists in b2g desktop build with
@ -34,9 +34,19 @@ function setupHomeButton() {
shell.sendChromeEvent({type: 'home-button-release'});
homeButton.classList.remove('active');
});
Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
let rotateButton = document.getElementById('rotate-button');
rotateButton.addEventListener('touchstart', function () {
rotateButton.classList.add('active');
});
rotateButton.addEventListener('touchend', function() {
GlobalSimulatorScreen.flipScreen();
rotateButton.classList.remove('active');
});
}
window.addEventListener('ContentStart', function() {
enableTouch();
setupHomeButton();
setupButtons();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

View File

@ -18,7 +18,7 @@ window.addEventListener('ContentStart', function() {
.getInterface(Components.interfaces.nsIDOMWindowUtils);
let hostDPI = windowUtils.displayDPI;
let DEFAULT_SCREEN = "320x480";
let DEFAULT_SCREEN = '320x480';
// This is a somewhat random selection of named screens.
// Add more to this list when we support more hardware.
@ -120,43 +120,93 @@ window.addEventListener('ContentStart', function() {
if (!width || !height || !dpi)
usage();
}
// In order to do rescaling, we set the <browser> tag to the specified
// width and height, and then use a CSS transform to scale it so that
// it appears at the correct size on the host display. We also set
// the size of the <window> element to that scaled target size.
let scale = rescale ? hostDPI / dpi : 1;
// Set the window width and height to desired size plus chrome
// Set the window width and height to desired size plus chrome
// Include the size of the toolbox displayed under the system app
let controls = document.getElementById('controls');
let controlsHeight = 0;
if (controls) {
controlsHeight = controls.getBoundingClientRect().height;
}
let chromewidth = window.outerWidth - window.innerWidth;
let chromeheight = window.outerHeight - window.innerHeight + controlsHeight;
window.resizeTo(Math.round(width * scale) + chromewidth,
Math.round(height * scale) + chromeheight);
Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
function resize(width, height, dpi, shouldFlip) {
GlobalSimulatorScreen.width = width;
GlobalSimulatorScreen.height = height;
// Set the browser element to the full unscaled size of the screen
browser.style.width = browser.style.minWidth = browser.style.maxWidth =
width + 'px';
browser.style.height = browser.style.minHeight = browser.style.maxHeight =
height + 'px';
browser.setAttribute('flex', '0'); // Don't let it stretch
// In order to do rescaling, we set the <browser> tag to the specified
// width and height, and then use a CSS transform to scale it so that
// it appears at the correct size on the host display. We also set
// the size of the <window> element to that scaled target size.
let scale = rescale ? hostDPI / dpi : 1;
// Now scale the browser element as needed
if (scale !== 1) {
browser.style.transformOrigin = 'top left';
browser.style.transform = 'scale(' + scale + ',' + scale + ')';
// Set the window width and height to desired size plus chrome
// Include the size of the toolbox displayed under the system app
let controls = document.getElementById('controls');
let controlsHeight = 0;
if (controls) {
controlsHeight = controls.getBoundingClientRect().height;
}
let chromewidth = window.outerWidth - window.innerWidth;
let chromeheight = window.outerHeight - window.innerHeight + controlsHeight;
window.resizeTo(Math.round(width * scale) + chromewidth,
Math.round(height * scale) + chromeheight);
let frameWidth = width, frameHeight = height;
if (shouldFlip) {
frameWidth = height;
frameHeight = width;
}
// Set the browser element to the full unscaled size of the screen
let style = browser.style;
style.width = style.minWidth = style.maxWidth =
frameWidth + 'px';
style.height = style.minHeight = style.maxHeight =
frameHeight + 'px';
browser.setAttribute('flex', '0'); // Don't let it stretch
style.transformOrigin = '';
style.transform = '';
// Now scale the browser element as needed
if (scale !== 1) {
style.transformOrigin = 'top left';
style.transform += ' scale(' + scale + ',' + scale + ')';
}
if (shouldFlip) {
// Display the system app with a 90° clockwise rotation
let shift = Math.floor(Math.abs(frameWidth-frameHeight) / 2);
style.transform +=
' rotate(0.25turn) translate(-' + shift + 'px, -' + shift + 'px)';
}
// Set the pixel density that we want to simulate.
// This doesn't change the on-screen size, but makes
// CSS media queries and mozmm units work right.
Services.prefs.setIntPref('layout.css.dpi', dpi);
}
// Set the pixel density that we want to simulate.
// This doesn't change the on-screen size, but makes
// CSS media queries and mozmm units work right.
Services.prefs.setIntPref('layout.css.dpi', dpi);
// Resize on startup
resize(width, height, dpi, false);
let defaultOrientation = width < height ? 'portrait' : 'landscape';
// Then resize on each rotation button click,
// or when the system app lock/unlock the orientation
Services.obs.addObserver(function orientationChangeListener(subject) {
let screen = subject.wrappedJSObject;
let { mozOrientation, screenOrientation } = screen;
let newWidth = width;
let newHeight = height;
// If we have an orientation different than the startup one,
// we switch the sizes
if (screenOrientation != defaultOrientation) {
newWidth = height;
newHeight = width;
}
// If the current app doesn't supports the current screen orientation
// still resize the window, but rotate its frame so that
// it is displayed rotated on the side
let shouldFlip = mozOrientation != screenOrientation;
resize(newWidth, newHeight, dpi, shouldFlip);
}, 'simulator-adjust-window-size', false);
// A utility function like console.log() for printing to the terminal window
// Uses dump(), but enables it first, if necessary
@ -164,7 +214,7 @@ window.addEventListener('ContentStart', function() {
let dump_enabled =
Services.prefs.getBoolPref('browser.dom.window.dump.enabled');
if (!dump_enabled)
if (!dump_enabled)
Services.prefs.setBoolPref('browser.dom.window.dump.enabled', true);
dump(Array.prototype.join.call(arguments, ' ') + '\n');

View File

@ -41,6 +41,7 @@
-->
<footer id="controls">
<button id="home-button"></button>
<button id="rotate-button"></button>
</footer>
#elifdef MOZ_WIDGET_COCOA
<!--

View File

@ -18,6 +18,7 @@ chrome.jar:
content/desktop.css (content/desktop.css)
content/images/desktop/home-black.png (content/images/desktop/home-black.png)
content/images/desktop/home-white.png (content/images/desktop/home-white.png)
content/images/desktop/rotate.png (content/images/desktop/rotate.png)
#endif
#ifndef MOZ_WIDGET_GONK
content/desktop.js (content/desktop.js)

View File

@ -81,3 +81,9 @@ contract @mozilla.org/fxaccounts/fxaccounts-ui-glue;1 {51875c14-91d7-4b8c-b65d-3
# HelperAppDialog.js
component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
#ifndef MOZ_WIDGET_GONK
component {c83c02c0-5d43-4e3e-987f-9173b313e880} SimulatorScreen.js
contract @mozilla.org/simulator-screen;1 {c83c02c0-5d43-4e3e-987f-9173b313e880}
category profile-after-change SimulatorScreen @mozilla.org/simulator-screen;1
#endif

View File

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = [ 'GlobalSimulatorScreen' ];
const Cu = Components.utils;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
this.GlobalSimulatorScreen = {
mozOrientationLocked: false,
// Actual orientation of apps
mozOrientation: 'portrait',
// The restricted list of actual orientation that can be used
// if mozOrientationLocked is true
lockedOrientation: [],
// The faked screen orientation
// if screenOrientation doesn't match mozOrientation due
// to lockedOrientation restriction, the app will be displayed
// on the side on desktop
screenOrientation: 'portrait',
// Updated by screen.js
width: 0, height: 0,
lock: function(orientation) {
this.mozOrientationLocked = true;
// Normalize to portrait or landscape,
// i.e. the possible values of screenOrientation
function normalize(str) {
if (str.match(/^portrait/)) {
return 'portrait';
} else if (str.match(/^landscape/)) {
return 'landscape';
} else {
return 'portrait';
}
}
this.lockedOrientation = orientation.map(normalize);
this.updateOrientation();
},
unlock: function() {
this.mozOrientationLocked = false;
this.updateOrientation();
},
updateOrientation: function () {
let orientation = this.screenOrientation;
// If the orientation is locked, we have to ensure ending up with a value
// of lockedOrientation. If none of lockedOrientation values matches
// the screen orientation we just choose the first locked orientation.
// This will be the precise scenario where the app is displayed on the
// side on desktop!
if (this.mozOrientationLocked &&
this.lockedOrientation.indexOf(this.screenOrientation) == -1) {
orientation = this.lockedOrientation[0];
}
// If the actual orientation changed,
// we have to fire mozorientation DOM events
if (this.mozOrientation != orientation) {
this.mozOrientation = orientation;
// Notify each app screen object to fire the event
Services.obs.notifyObservers(null, 'simulator-orientation-change', null);
}
// Finally, in any case, we update the window size and orientation
// (Use wrappedJSObject trick to be able to pass a raw JS object)
Services.obs.notifyObservers({wrappedJSObject:this}, 'simulator-adjust-window-size', null);
},
flipScreen: function() {
if (this.screenOrientation == 'portrait') {
this.screenOrientation = 'landscape';
} else if (this.screenOrientation == 'landscape') {
this.screenOrientation = 'portrait';
}
this.updateOrientation();
}
}

View File

@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DOMRequestHelper.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'GlobalSimulatorScreen',
'resource://gre/modules/GlobalSimulatorScreen.jsm');
let DEBUG_PREFIX = 'SimulatorScreen.js - ';
function debug() {
//dump(DEBUG_PREFIX + Array.slice(arguments) + '\n');
}
function fireOrientationEvent(window) {
let e = new window.Event('mozorientationchange');
window.screen.dispatchEvent(e);
}
function hookScreen(window) {
let nodePrincipal = window.document.nodePrincipal;
let origin = nodePrincipal.origin;
if (nodePrincipal.appStatus == nodePrincipal.APP_STATUS_NOT_INSTALLED) {
Cu.reportError('deny mozLockOrientation:' + origin + 'is not installed');
return;
}
let screen = window.wrappedJSObject.screen;
screen.mozLockOrientation = function (orientation) {
debug('mozLockOrientation:', orientation, 'from', origin);
// Normalize and do some checks against orientation input
if (typeof(orientation) == 'string') {
orientation = [orientation];
}
function isInvalidOrientationString(str) {
return typeof(str) != 'string' ||
!str.match(/^default$|^(portrait|landscape)(-(primary|secondary))?$/);
}
if (!Array.isArray(orientation) ||
orientation.some(isInvalidOrientationString)) {
Cu.reportError('Invalid orientation "' + orientation + '"');
return false;
}
GlobalSimulatorScreen.lock(orientation);
return true;
};
screen.mozUnlockOrientation = function() {
debug('mozOrientationUnlock from', origin);
GlobalSimulatorScreen.unlock();
return true;
};
Object.defineProperty(screen, 'width', {
get: function () GlobalSimulatorScreen.width
});
Object.defineProperty(screen, 'height', {
get: function () GlobalSimulatorScreen.height
});
Object.defineProperty(screen, 'mozOrientation', {
get: function () GlobalSimulatorScreen.mozOrientation
});
}
function SimulatorScreen() {}
SimulatorScreen.prototype = {
classID: Components.ID('{c83c02c0-5d43-4e3e-987f-9173b313e880}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
_windows: new Set(),
observe: function (subject, topic, data) {
switch (topic) {
case 'profile-after-change':
Services.obs.addObserver(this, 'document-element-inserted', false);
Services.obs.addObserver(this, 'simulator-orientation-change', false);
break;
case 'document-element-inserted':
let window = subject.defaultView;
if (!window) {
return;
}
hookScreen(window);
let windows = this._windows;
window.addEventListener('unload', function unload() {
window.removeEventListener('unload', unload);
windows.delete(window);
});
windows.add(window);
break;
case 'simulator-orientation-change':
this._windows.forEach(fireOrientationEvent);
break;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SimulatorScreen]);

View File

@ -23,6 +23,11 @@ EXTRA_COMPONENTS += [
'YoutubeProtocolHandler.js',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
EXTRA_COMPONENTS += [
'SimulatorScreen.js'
]
EXTRA_PP_COMPONENTS += [
'B2GComponents.manifest',
'DirectoryProvider.js',
@ -41,6 +46,11 @@ EXTRA_JS_MODULES += [
'WebappsUpdater.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
EXTRA_JS_MODULES += [
'GlobalSimulatorScreen.jsm'
]
if CONFIG['MOZ_SERVICES_FXACCOUNTS']:
EXTRA_COMPONENTS += [
'FxAccountsUIGlue.js'