Commit a9438c47 authored by ericzeng's avatar ericzeng Committed by Commit bot

Implement smoother autosizing of the extension options overlay

 - Use element.animate to resize the overlay for embedded extension
   options. When an <extensionoptions> element is embedded in a WebUI
   overlay, if its size changes the overlay will smoothly expand to
   surround the element.
 - Add an API to <extensionoptions> to defer autosizing until its
   embedder is ready. This is used to 'pin' the element's size until
   the overlay is done expanding.
 - Move the <extensionoptions> off screen until the overlay is done
   expanding.
 - Prevent <extensionoptions> from shrinking after the initial sizing.
   This makes the look of the embedded options page more consistent.

BUG=386842

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=291286

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

Cr-Commit-Position: refs/heads/master@{#291583}
parent 9a3ddb33
......@@ -17,7 +17,11 @@ const char kAttributeMinHeight[] = "minheight";
const char kAttributeMinWidth[] = "minwidth";
const char kExtensionId[] = "extensionId";
const char kHeight[] = "height";
const char kWidth[] = "width";
const char kNewHeight[] = "newHeight";
const char kNewWidth[] = "newWidth";
const char kOldHeight[] = "oldHeight";
const char kOldWidth[] = "oldWidth";
} // namespace extensionoptions
......@@ -18,8 +18,12 @@ extern const char kAttributeMinHeight[];
extern const char kAttributeMinWidth[];
extern const char kExtensionId[];
extern const char kHeight[];
extern const char kWidth[];
// SizeChanged event properties.
extern const char kNewHeight[];
extern const char kNewWidth[];
extern const char kOldHeight[];
extern const char kOldWidth[];
} // namespace extensionoptions
......
......@@ -151,8 +151,10 @@ void ExtensionOptionsGuest::GuestSizeChangedDueToAutoSize(
const gfx::Size& old_size,
const gfx::Size& new_size) {
scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
args->SetInteger(extensionoptions::kWidth, new_size.width());
args->SetInteger(extensionoptions::kHeight, new_size.height());
args->SetInteger(extensionoptions::kNewWidth, new_size.width());
args->SetInteger(extensionoptions::kNewHeight, new_size.height());
args->SetInteger(extensionoptions::kOldWidth, old_size.width());
args->SetInteger(extensionoptions::kOldHeight, old_size.height());
DispatchEventToEmbedder(new extensions::GuestViewBase::Event(
extension_options_internal::OnSizeChanged::kEventName, args.Pass()));
}
......
/* 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. */
#extension-options-overlay {
overflow: hidden;
}
......@@ -63,24 +63,74 @@ cr.define('extensions', function() {
* as the header of the overlay.
*/
setExtensionAndShowOverlay: function(extensionId, extensionName) {
$('extension-options-overlay-title').textContent = extensionName;
var extensionoptions = new ExtensionOptions();
extensionoptions.extension = extensionId;
extensionoptions.autosize = 'on';
// The <extensionoptions> content's size needs to be restricted to the
// bounds of the overlay window. The overlay gives a min width and
// max height, but the maxheight does not include our header height
// (title and close button), so we need to subtract that to get the
// max height for the extension options.
var headerHeight = $('extension-options-overlay-title').offsetHeight;
var overlayMaxHeight =
parseInt($('extension-options-overlay').style.maxHeight);
extensionoptions.maxheight = overlayMaxHeight - headerHeight;
extensionoptions.minwidth =
parseInt(window.getComputedStyle($('extension-options-overlay'))
.minWidth);
extensionoptions.setDeferAutoSize(true);
extensionoptions.onclose = function() {
this.handleDismiss_();
}.bind(this);
// TODO(ericzeng): Resize in a non-jarring way.
// Resize the overlay if the <extensionoptions> changes size.
extensionoptions.onsizechanged = function(evt) {
$('extension-options-overlay').style.width = evt.width;
$('extension-options-overlay').style.height = evt.height;
var overlayStyle =
window.getComputedStyle($('extension-options-overlay'));
var oldWidth = parseInt(overlayStyle.width);
var oldHeight = parseInt(overlayStyle.height);
// animationTime is the amount of time in ms that will be used to resize
// the overlay. It is calculated by multiplying the pythagorean distance
// between old and the new size (in px) with a constant speed of
// 0.25 ms/px.
var animationTime = 0.25 * Math.sqrt(
Math.pow(evt.newWidth - oldWidth, 2) +
Math.pow(evt.newHeight - oldHeight, 2));
var player = $('extension-options-overlay').animate([
{width: oldWidth + 'px', height: oldHeight + 'px'},
{width: evt.newWidth + 'px', height: evt.newHeight + 'px'}
], {
duration: animationTime,
delay: 0
});
player.onfinish = function(e) {
// Allow the <extensionoptions> to autosize now that the overlay
// has resized, and move it back on-screen.
extensionoptions.resumeDeferredAutoSize();
$('extension-options-overlay-guest').style.position = 'static';
$('extension-options-overlay-guest').style.left = 'auto';
};
}.bind(this);
$('extension-options-overlay-guest').appendChild(extensionoptions);
// Don't allow the <extensionoptions> to autosize until the overlay
// animation is complete.
extensionoptions.setDeferAutoSize(true);
$('extension-options-overlay-title').textContent = extensionName;
// Move the <extensionoptions> off screen until the overlay is ready
$('extension-options-overlay-guest').style.position = 'fixed';
$('extension-options-overlay-guest').style.left =
window.outerWidth + 'px';
$('extension-options-overlay-guest').appendChild(extensionoptions);
this.setVisible_(true);
},
......
......@@ -8,6 +8,7 @@
<link rel="stylesheet" href="extension_error.css">
<link rel="stylesheet" href="extension_error_overlay.css">
<link rel="stylesheet" href="extension_load_error.css">
<link rel="stylesheet" href="extension_options_overlay.css">
<link rel="stylesheet" href="pack_extension_overlay.css">
<link rel="stylesheet" href="chrome://resources/css/alert_overlay.css">
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
......
......@@ -25,6 +25,8 @@ function ExtensionOptionsInternal(extensionoptionsNode) {
this.extensionoptionsNode = extensionoptionsNode;
this.viewInstanceId = IdGenerator.GetNextId();
this.autosizeDeferred = false;
// on* Event handlers.
this.eventHandlers = {};
......@@ -138,9 +140,18 @@ ExtensionOptionsInternal.prototype.init = function() {
this.createGuest();
};
ExtensionOptionsInternal.prototype.onSizeChanged = function(width, height) {
this.browserPluginNode.style.width = width + 'px';
this.browserPluginNode.style.height = height + 'px';
ExtensionOptionsInternal.prototype.onSizeChanged =
function(newWidth, newHeight, oldWidth, oldHeight) {
if (this.autosizeDeferred) {
this.deferredAutoSizeState = {
newWidth: newWidth,
newHeight: newHeight,
oldWidth: oldWidth,
oldHeight: oldHeight
};
} else {
this.resize(newWidth, newHeight, oldWidth, oldHeight);
}
};
ExtensionOptionsInternal.prototype.parseExtensionAttribute = function() {
......@@ -151,6 +162,32 @@ ExtensionOptionsInternal.prototype.parseExtensionAttribute = function() {
return false;
};
ExtensionOptionsInternal.prototype.resize =
function(newWidth, newHeight, oldWidth, oldHeight) {
this.browserPluginNode.style.width = newWidth + 'px';
this.browserPluginNode.style.height = newHeight + 'px';
// Do not allow the options page's dimensions to shrink so that the options
// page has a consistent UI. If the new size is larger than the minimum,
// make that the new minimum size.
if (newWidth > this.minwidth)
this.minwidth = newWidth;
if (newHeight > this.minheight)
this.minheight = newHeight;
GuestViewInternal.setAutoSize(this.guestInstanceId, {
'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
'min': {
'width': parseInt(this.minwidth || 0),
'height': parseInt(this.minheight || 0)
},
'max': {
'width': parseInt(this.maxwidth || 0),
'height': parseInt(this.maxheight || 0)
}
});
};
// Adds an 'on<event>' property on the view, which can be used to set/unset
// an event handler.
ExtensionOptionsInternal.prototype.setupEventProperty = function(eventName) {
......@@ -216,7 +253,35 @@ ExtensionOptionsInternal.prototype.resetSizeConstraintsIfInvalid = function () {
this.minwidth = AUTO_SIZE_ATTRIBUTES.minwidth;
this.maxwidth = AUTO_SIZE_ATTRIBUTES.maxwidth;
}
}
};
/**
* Toggles whether the element should automatically resize to its preferred
* size. If set to true, when the element receives new autosize dimensions,
* it passes them to the embedder in a sizechanged event, but does not resize
* itself to those dimensions until the embedder calls resumeDeferredAutoSize.
* This allows the embedder to defer the resizing until it is ready.
* When set to false, the element resizes whenever it receives new autosize
* dimensions.
*/
ExtensionOptionsInternal.prototype.setDeferAutoSize = function(value) {
if (!value)
resumeDeferredAutoSize();
this.autosizeDeferred = value;
};
/**
* Allows the element to resize to most recent set of autosize dimensions if
* autosizing is being deferred.
*/
ExtensionOptionsInternal.prototype.resumeDeferredAutoSize = function() {
if (this.autosizeDeferred) {
this.resize(this.deferredAutoSizeState.newWidth,
this.deferredAutoSizeState.newHeight,
this.deferredAutoSizeState.oldWidth,
this.deferredAutoSizeState.oldHeight);
}
};
function registerBrowserPluginElement() {
var proto = Object.create(HTMLObjectElement.prototype);
......@@ -255,6 +320,22 @@ function registerExtensionOptionsElement() {
internal.handleExtensionOptionsAttributeMutation(name, oldValue, newValue);
};
var methods = [
'setDeferAutoSize',
'resumeDeferredAutoSize'
];
// Forward proto.foo* method calls to ExtensionOptionsInternal.foo*.
for (var i = 0; methods[i]; ++i) {
var createHandler = function(m) {
return function(var_args) {
var internal = privates(this).internal;
return $Function.apply(internal[m], internal, arguments);
};
};
proto[methods[i]] = createHandler(methods[i]);
}
window.ExtensionOptions =
DocumentNatives.RegisterElement('extensionoptions', {prototype: proto});
......
......@@ -20,10 +20,10 @@ var EXTENSION_OPTIONS_EVENTS = {
},
'sizechanged': {
evt: CreateEvent('extensionOptionsInternal.onSizeChanged'),
customHandler: function(handler, event, webViewEvent) {
handler.handleSizeChangedEvent(event, webViewEvent);
customHandler: function(handler, event, extensionOptionsEvent) {
handler.handleSizeChangedEvent(event, extensionOptionsEvent);
},
fields:['width', 'height']
fields:['newWidth', 'newHeight', 'oldWidth', 'oldHeight']
}
}
......@@ -67,8 +67,10 @@ ExtensionOptionsEvents.prototype.setupEvent = function(name, info) {
ExtensionOptionsEvents.prototype.handleSizeChangedEvent = function(
event, extensionOptionsEvent) {
this.extensionOptionsInternal.onSizeChanged(extensionOptionsEvent.width,
extensionOptionsEvent.height);
this.extensionOptionsInternal.onSizeChanged(extensionOptionsEvent.newWidth,
extensionOptionsEvent.newHeight,
extensionOptionsEvent.oldWidth,
extensionOptionsEvent.oldHeight);
this.extensionOptionsInternal.dispatchEvent(extensionOptionsEvent);
}
......
......@@ -125,10 +125,10 @@ chrome.test.runTests([
extensionoptions.onsizechanged = function(evt) {
try {
chrome.test.assertTrue(evt.width >= 499);
chrome.test.assertTrue(evt.height >= 499);
chrome.test.assertTrue(evt.width <= 501);
chrome.test.assertTrue(evt.height <= 501);
chrome.test.assertTrue(evt.newWidth >= 499);
chrome.test.assertTrue(evt.newHeight >= 499);
chrome.test.assertTrue(evt.newWidth <= 501);
chrome.test.assertTrue(evt.newHeight <= 501);
done();
} finally {
document.body.removeChild(extensionoptions);
......
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