#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
-#include "base/command_line.h"
#include "base/debug/crash_logging.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
#import "content/public/browser/render_widget_host_view_mac_delegate.h"
#include "content/public/browser/user_metrics.h"
-#include "content/public/common/content_switches.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/public/platform/WebScreenInfo.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
-enum CoreAnimationStatus {
- CORE_ANIMATION_DISABLED,
- CORE_ANIMATION_ENABLED,
-};
-
-static CoreAnimationStatus GetCoreAnimationStatus() {
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kUseCoreAnimation)) {
- return CORE_ANIMATION_ENABLED;
- }
- return CORE_ANIMATION_DISABLED;
-}
-
// These are not documented, so use only after checking -respondsToSelector:.
@interface NSApplication (UndocumentedSpeechMethods)
- (void)speakString:(NSString*)string;
styleMask:windowStyle
backing:bufferingType
defer:deferCreation]) {
- CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
+ DCHECK_EQ(content::CORE_ANIMATION_DISABLED,
+ content::GetCoreAnimationStatus());
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self startObservingClicks];
TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
}
+// Calls to [NSScreen screens], required by FlipYFromRectToScreen and
+// FlipNSRectToRectScreen, can take several milliseconds. Only re-compute this
+// value when screen info changes.
+// TODO(ccameron): An observer on every RWHVCocoa will set this to false
+// on NSApplicationDidChangeScreenParametersNotification. Only one observer
+// is necessary.
+bool g_screen_info_up_to_date = false;
+
+float FlipYFromRectToScreen(float y, float rect_height) {
+ TRACE_EVENT0("browser", "FlipYFromRectToScreen");
+ static CGFloat screen_zero_height = 0;
+ if (!g_screen_info_up_to_date) {
+ if ([[NSScreen screens] count] > 0) {
+ screen_zero_height =
+ [[[NSScreen screens] objectAtIndex:0] frame].size.height;
+ g_screen_info_up_to_date = true;
+ } else {
+ return y;
+ }
+ }
+ return screen_zero_height - y - rect_height;
+}
+
// Adjusts an NSRect in Cocoa screen coordinates to have an origin in the upper
// left of the primary screen (Carbon coordinates), and stuffs it into a
// gfx::Rect.
gfx::Rect FlipNSRectToRectScreen(const NSRect& rect) {
gfx::Rect new_rect(NSRectToCGRect(rect));
- if ([[NSScreen screens] count] > 0) {
- new_rect.set_y([[[NSScreen screens] objectAtIndex:0] frame].size.height -
- new_rect.y() - new_rect.height());
- }
+ new_rect.set_y(FlipYFromRectToScreen(new_rect.y(), new_rect.height()));
return new_rect;
}
last_frame_was_accelerated_(false),
text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
can_compose_inline_(true),
+ compositing_iosurface_layer_async_timer_(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(250),
+ this, &RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired),
allow_overlapping_views_(false),
use_core_animation_(false),
+ pending_latency_info_delay_(0),
+ pending_latency_info_delay_weak_ptr_factory_(this),
is_loading_(false),
weak_factory_(this),
fullscreen_parent_host_view_(NULL),
}
bool RenderWidgetHostViewMac::CreateCompositedIOSurface() {
- int current_window_number = window_number();
+ int current_window_number = use_core_animation_ ?
+ CompositingIOSurfaceContext::kOffscreenContextWindowNumber :
+ window_number();
bool new_surface_needed = !compositing_iosurface_;
bool new_context_needed =
!compositing_iosurface_context_ ||
}
void RenderWidgetHostViewMac::ClearBoundContextDrawable() {
+ if (use_core_animation_)
+ return;
+
if (compositing_iosurface_context_ &&
cocoa_view_ &&
[[compositing_iosurface_context_->nsgl_context() view]
[cocoa_view_ setCanBeKeyView:activatable ? YES : NO];
NSPoint origin_global = NSPointFromCGPoint(pos.origin().ToCGPoint());
- if ([[NSScreen screens] count] > 0) {
- origin_global.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height -
- pos.height() - origin_global.y;
- }
+ origin_global.y = FlipYFromRectToScreen(origin_global.y, pos.height());
popup_window_.reset([[RenderWidgetPopupWindow alloc]
initWithContentRect:NSMakeRect(origin_global.x, origin_global.y,
NSPoint origin_global = NSPointFromCGPoint(rect.origin().ToCGPoint());
NSSize size = NSMakeSize(rect.width(), rect.height());
size = [cocoa_view_ convertSize:size toView:nil];
- if ([[NSScreen screens] count] > 0) {
- NSScreen* screen =
- static_cast<NSScreen*>([[NSScreen screens] objectAtIndex:0]);
- origin_global.y =
- NSHeight([screen frame]) - size.height - origin_global.y;
- }
+ origin_global.y = FlipYFromRectToScreen(origin_global.y, size.height);
[popup_window_ setFrame:NSMakeRect(origin_global.x, origin_global.y,
size.width, size.height)
display:YES];
const std::vector<ui::LatencyInfo>& latency_info) {
GotSoftwareFrame();
- for (size_t i = 0; i < latency_info.size(); i++)
- software_latency_info_.push_back(latency_info[i]);
+ AddPendingLatencyInfo(latency_info);
if (render_widget_host_->is_hidden())
return;
if (render_widget_host_->is_hidden())
return;
+ AddPendingLatencyInfo(latency_info);
+
NSWindow* window = [cocoa_view_ window];
if (window_number() <= 0) {
// There is no window to present so capturing during present won't work.
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
if (frame_subscriber_->ShouldCaptureFrame(present_time,
&frame, &callback)) {
- CGLSetCurrentContext(compositing_iosurface_context_->cgl_context());
+ gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
+ compositing_iosurface_context_->cgl_context());
compositing_iosurface_->SetIOSurfaceWithContextCurrent(
compositing_iosurface_context_, surface_handle, size,
- surface_scale_factor, latency_info);
+ surface_scale_factor);
compositing_iosurface_->CopyToVideoFrame(
gfx::Rect(size), frame,
base::Bind(callback, present_time));
// Make the context current and update the IOSurface with the handle
// passed in by the swap command.
- CGLSetCurrentContext(compositing_iosurface_context_->cgl_context());
+ gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
+ compositing_iosurface_context_->cgl_context());
if (!compositing_iosurface_->SetIOSurfaceWithContextCurrent(
compositing_iosurface_context_, surface_handle, size,
- surface_scale_factor, latency_info)) {
+ surface_scale_factor)) {
LOG(ERROR) << "Failed SetIOSurface on CompositingIOSurfaceMac";
GotAcceleratedCompositingError();
return;
compositing_iosurface_->CopyToVideoFrame(
gfx::Rect(size), frame,
base::Bind(callback, present_time));
- CGLSetCurrentContext(compositing_iosurface_context_->cgl_context());
+ DCHECK_EQ(CGLGetCurrentContext(),
+ compositing_iosurface_context_->cgl_context());
}
}
if (!about_to_validate_and_paint_) {
if (use_core_animation_) {
DCHECK(compositing_iosurface_layer_);
- [compositing_iosurface_layer_ setNeedsDisplay];
+ compositing_iosurface_layer_async_timer_.Reset();
+ [compositing_iosurface_layer_ gotNewFrame];
} else {
if (!DrawIOSurfaceWithoutCoreAnimation()) {
[cocoa_view_ setNeedsDisplay:YES];
if (underlay_view_ &&
underlay_view_->compositing_iosurface_ &&
underlay_view_has_drawn_) {
- [underlay_view_->cocoa_view() setNeedsDisplay:YES];
+ [underlay_view_->cocoa_view() setNeedsDisplayInRect:NSMakeRect(0, 0, 1, 1)];
return true;
}
}
}
+ SendPendingLatencyInfoToHost();
return true;
}
void RenderWidgetHostViewMac::SetOverlayView(
RenderWidgetHostViewMac* overlay, const gfx::Point& offset) {
+ if (use_core_animation_)
+ return;
+
if (overlay_view_)
overlay_view_->underlay_view_.reset();
}
void RenderWidgetHostViewMac::RemoveOverlayView() {
+ if (use_core_animation_)
+ return;
+
if (overlay_view_) {
overlay_view_->underlay_view_.reset();
overlay_view_.reset();
return;
}
+ // Add latency info to report when the frame finishes drawing.
+ AddPendingLatencyInfo(frame->metadata.latency_info);
+ GotSoftwareFrame();
+
+ // Draw the contents of the frame immediately. It is critical that this
+ // happen before the frame be acked, otherwise the new frame will likely be
+ // ready before the drawing is complete, thrashing the browser main thread.
+ if (use_core_animation_) {
+ [software_layer_ setNeedsDisplay];
+ if (!about_to_validate_and_paint_)
+ [software_layer_ displayIfNeeded];
+ } else {
+ [cocoa_view_ setNeedsDisplay:YES];
+ if (!about_to_validate_and_paint_)
+ [cocoa_view_ displayIfNeeded];
+ }
+
cc::CompositorFrameAck ack;
RenderWidgetHostImpl::SendSwapCompositorFrameAck(
render_widget_host_->GetRoutingID(),
software_frame_manager_->GetCurrentFrameOutputSurfaceId(),
render_widget_host_->GetProcess()->GetID(),
ack);
- for (size_t i = 0; i < frame->metadata.latency_info.size(); i++) {
- software_latency_info_.push_back(frame->metadata.latency_info[i]);
- }
software_frame_manager_->SwapToNewFrameComplete(
!render_widget_host_->is_hidden());
- GotSoftwareFrame();
- [cocoa_view_ setNeedsDisplay:YES];
+ // Notify observers, tab capture observers in particular, that a new software
+ // frame has come in.
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
+ Source<RenderWidgetHost>(render_widget_host_),
+ NotificationService::NoDetails());
}
void RenderWidgetHostViewMac::OnAcceleratedCompositingStateChange() {
}
void RenderWidgetHostViewMac::GotAcceleratedFrame() {
+ // Update the host with VSync parametrs.
+ base::TimeTicks timebase;
+ base::TimeDelta interval;
+ if (compositing_iosurface_context_ &&
+ compositing_iosurface_context_->display_link() &&
+ compositing_iosurface_context_->display_link()->GetVSyncParameters(
+ &timebase, &interval)) {
+ render_widget_host_->UpdateVSyncParameters(timebase, interval);
+ }
+
// Update the scale factor of the layer to match the scale factor of the
// IOSurface.
[compositing_iosurface_layer_ updateScaleFactor];
}
}
+void RenderWidgetHostViewMac::TimerSinceGotAcceleratedFrameFired() {
+ [compositing_iosurface_layer_ timerSinceGotNewFrameFired];
+}
+
void RenderWidgetHostViewMac::SetActive(bool active) {
if (render_widget_host_) {
render_widget_host_->SetActive(active);
scale_factor()));
}
-void RenderWidgetHostViewMac::SendSoftwareLatencyInfoToHost() {
- for (size_t i = 0; i < software_latency_info_.size(); i++) {
- software_latency_info_[i].AddLatencyNumber(
+void RenderWidgetHostViewMac::AddPendingLatencyInfo(
+ const std::vector<ui::LatencyInfo>& latency_info) {
+ // If a screenshot is being taken when using CoreAnimation, send a few extra
+ // calls to setNeedsDisplay and wait for their resulting display calls,
+ // before reporting that the frame has reached the screen.
+ if (use_core_animation_) {
+ bool should_defer = false;
+ for (size_t i = 0; i < latency_info.size(); i++) {
+ if (latency_info[i].FindLatency(
+ ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+ render_widget_host_->GetLatencyComponentId(),
+ NULL)) {
+ should_defer = true;
+ }
+ }
+ if (should_defer) {
+ // Multiple pending screenshot requests will work, but if every frame
+ // requests a screenshot, then the delay will never expire. Assert this
+ // here to avoid this.
+ CHECK_EQ(pending_latency_info_delay_, 0u);
+ // Wait a fixed number of frames (calls to CALayer::display) before
+ // claiming that the screenshot has reached the screen. This number
+ // comes from taking the first number where tests didn't fail (six),
+ // and doubling it.
+ const uint32 kScreenshotLatencyDelayInFrames = 12;
+ pending_latency_info_delay_ = kScreenshotLatencyDelayInFrames;
+ TickPendingLatencyInfoDelay();
+ }
+ }
+
+ for (size_t i = 0; i < latency_info.size(); i++) {
+ pending_latency_info_.push_back(latency_info[i]);
+ }
+}
+
+void RenderWidgetHostViewMac::SendPendingLatencyInfoToHost() {
+ if (pending_latency_info_delay_) {
+ pending_latency_info_delay_ -= 1;
+ return;
+ }
+ pending_latency_info_delay_weak_ptr_factory_.InvalidateWeakPtrs();
+
+ for (size_t i = 0; i < pending_latency_info_.size(); i++) {
+ pending_latency_info_[i].AddLatencyNumber(
ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
- render_widget_host_->FrameSwapped(software_latency_info_[i]);
+ render_widget_host_->FrameSwapped(pending_latency_info_[i]);
}
- software_latency_info_.clear();
+ pending_latency_info_.clear();
+}
+
+void RenderWidgetHostViewMac::TickPendingLatencyInfoDelay() {
+ // Keep calling setNeedsDisplay in a loop until enough display calls come in.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&RenderWidgetHostViewMac::TickPendingLatencyInfoDelay,
+ pending_latency_info_delay_weak_ptr_factory_.GetWeakPtr()));
+ [software_layer_ setNeedsDisplay];
+ [compositing_iosurface_layer_ setNeedsDisplay];
}
} // namespace content
selector:@selector(globalFrameDidChange:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(didChangeScreenParameters:)
+ name:NSApplicationDidChangeScreenParametersNotification
+ object:nil];
}
return self;
}
[super dealloc];
}
+- (void)didChangeScreenParameters:(NSNotification*)notify {
+ g_screen_info_up_to_date = false;
+}
+
- (void)setResponderDelegate:
(NSObject<RenderWidgetHostViewMacDelegate>*)delegate {
DCHECK(!responderDelegate_);
return;
handlingGlobalFrameDidChange_ = YES;
- if (renderWidgetHostView_->compositing_iosurface_context_) {
+ if (!renderWidgetHostView_->use_core_animation_ &&
+ renderWidgetHostView_->compositing_iosurface_context_) {
[renderWidgetHostView_->compositing_iosurface_context_->nsgl_context()
update];
}
if (renderWidgetHostView_->last_frame_was_accelerated_ &&
renderWidgetHostView_->compositing_iosurface_) {
if (renderWidgetHostView_->allow_overlapping_views_) {
- CHECK_EQ(CORE_ANIMATION_DISABLED, GetCoreAnimationStatus());
-
// If overlapping views need to be allowed, punch a hole in the window
// to expose the GL underlay.
TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
NSRectFill(dirtyRect);
}
- CGLSetCurrentContext(
+ gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
renderWidgetHostView_->compositing_iosurface_context_->cgl_context());
if (renderWidgetHostView_->DrawIOSurfaceWithoutCoreAnimation())
return;
}
}
- renderWidgetHostView_->SendSoftwareLatencyInfoToHost();
+ renderWidgetHostView_->SendPendingLatencyInfoToHost();
// Fill the remaining portion of the damagedRect with white
[self fillBottomRightRemainderOfRect:bitmapRect