| Index: third_party/WebKit/Source/modules/vr/VRDisplay.cpp
 | 
| diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
 | 
| index 3da99b51be19bc344dec04cf86d0b1fa6097a2ba..cc8f1038aae5d967f3865cc8b9a1881194dcb3d9 100644
 | 
| --- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
 | 
| +++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
 | 
| @@ -8,17 +8,13 @@
 | 
|  #include "core/dom/DOMException.h"
 | 
|  #include "core/dom/DocumentUserGestureToken.h"
 | 
|  #include "core/dom/FrameRequestCallback.h"
 | 
| +#include "core/dom/Fullscreen.h"
 | 
|  #include "core/dom/ScriptedAnimationController.h"
 | 
|  #include "core/dom/TaskRunnerHelper.h"
 | 
| -#include "core/frame/FrameView.h"
 | 
| -#include "core/frame/ImageBitmap.h"
 | 
|  #include "core/frame/UseCounter.h"
 | 
|  #include "core/inspector/ConsoleMessage.h"
 | 
| -#include "core/layout/LayoutView.h"
 | 
| -#include "core/layout/compositing/PaintLayerCompositor.h"
 | 
|  #include "core/loader/DocumentLoader.h"
 | 
|  #include "gpu/command_buffer/client/gles2_interface.h"
 | 
| -#include "gpu/command_buffer/common/mailbox_holder.h"
 | 
|  #include "modules/EventTargetModules.h"
 | 
|  #include "modules/vr/NavigatorVR.h"
 | 
|  #include "modules/vr/VRController.h"
 | 
| @@ -31,7 +27,6 @@
 | 
|  #include "modules/webgl/WebGLRenderingContextBase.h"
 | 
|  #include "platform/Histogram.h"
 | 
|  #include "platform/UserGestureIndicator.h"
 | 
| -#include "platform/instrumentation/tracing/TraceEvent.h"
 | 
|  #include "public/platform/Platform.h"
 | 
|  #include "wtf/AutoReset.h"
 | 
|  #include "wtf/Time.h"
 | 
| @@ -41,6 +36,10 @@
 | 
|  namespace blink {
 | 
|  
 | 
|  namespace {
 | 
| +
 | 
| +// Magic numbers used to mark valid pose index values encoded in frame
 | 
| +// data. Must match the magic numbers used in vr_shell.cc.
 | 
| +static constexpr std::array<uint8_t, 2> kWebVrPosePixelMagicNumbers{{42, 142}};
 | 
|  
 | 
|  VREye stringToVREye(const String& whichEye) {
 | 
|    if (whichEye == "left")
 | 
| @@ -60,8 +59,12 @@
 | 
|        m_capabilities(new VRDisplayCapabilities()),
 | 
|        m_eyeParametersLeft(new VREyeParameters()),
 | 
|        m_eyeParametersRight(new VREyeParameters()),
 | 
| +      m_fullscreenCheckTimer(
 | 
| +          TaskRunnerHelper::get(TaskType::UnspecedTimer,
 | 
| +                                navigatorVR->document()->frame()),
 | 
| +          this,
 | 
| +          &VRDisplay::onFullscreenCheck),
 | 
|        m_display(std::move(display)),
 | 
| -      m_submit_frame_client_binding(this),
 | 
|        m_displayClientBinding(this, std::move(request)) {}
 | 
|  
 | 
|  VRDisplay::~VRDisplay() {}
 | 
| @@ -233,6 +236,17 @@
 | 
|          InvalidStateError, "API can only be initiated by a user gesture.");
 | 
|      resolver->reject(exception);
 | 
|      ReportPresentationResult(PresentationResult::NotInitiatedByUserGesture);
 | 
| +    return promise;
 | 
| +  }
 | 
| +
 | 
| +  // TODO(mthiesse): Remove fullscreen requirement for presentation. See
 | 
| +  // crbug.com/687369
 | 
| +  Document* doc = this->document();
 | 
| +  if (!doc || !Fullscreen::fullscreenEnabled(*doc)) {
 | 
| +    DOMException* exception =
 | 
| +        DOMException::create(InvalidStateError, "Fullscreen is not enabled.");
 | 
| +    resolver->reject(exception);
 | 
| +    ReportPresentationResult(PresentationResult::FullscreenNotEnabled);
 | 
|      return promise;
 | 
|    }
 | 
|  
 | 
| @@ -310,12 +324,9 @@
 | 
|      }
 | 
|  
 | 
|      m_pendingPresentResolvers.append(resolver);
 | 
| -    m_submit_frame_client_binding.Close();
 | 
| -    m_display->RequestPresent(
 | 
| -        secureContext,
 | 
| -        m_submit_frame_client_binding.CreateInterfacePtrAndBind(),
 | 
| -        convertToBaseCallback(
 | 
| -            WTF::bind(&VRDisplay::onPresentComplete, wrapPersistent(this))));
 | 
| +    m_display->RequestPresent(secureContext, convertToBaseCallback(WTF::bind(
 | 
| +                                                 &VRDisplay::onPresentComplete,
 | 
| +                                                 wrapPersistent(this))));
 | 
|    } else {
 | 
|      updateLayerBounds();
 | 
|      resolver->resolve();
 | 
| @@ -384,9 +395,50 @@
 | 
|      return;
 | 
|    } else {
 | 
|      if (m_layer.source().isHTMLCanvasElement()) {
 | 
| -      // TODO(klausw,crbug.com/698923): suppress compositor updates
 | 
| -      // since they aren't needed, they do a fair amount of extra
 | 
| -      // work.
 | 
| +      HTMLCanvasElement* canvas = m_layer.source().getAsHTMLCanvasElement();
 | 
| +      // TODO(klausw,crbug.com/655722): Need a proper VR compositor, but
 | 
| +      // for the moment on mobile we'll just make the canvas fullscreen
 | 
| +      // so that VrShell can pick it up through the standard (high
 | 
| +      // latency) compositing path.      auto canvas =
 | 
| +      // m_layer.source().getAsHTMLCanvasElement();
 | 
| +      auto inlineStyle = canvas->inlineStyle();
 | 
| +      if (inlineStyle) {
 | 
| +        // THREE.js's VREffect sets explicit style.width/height on its rendering
 | 
| +        // canvas based on the non-fullscreen window dimensions, and it keeps
 | 
| +        // those unchanged when presenting. Unfortunately it appears that a
 | 
| +        // fullscreened canvas just gets centered if it has explicitly set a
 | 
| +        // size smaller than the fullscreen dimensions. Manually set size to
 | 
| +        // 100% in this case and restore it when exiting fullscreen. This is a
 | 
| +        // stopgap measure since THREE.js's usage appears legal according to the
 | 
| +        // WebVR API spec. This will no longer be necessary once we can get rid
 | 
| +        // of this fullscreen hack.
 | 
| +        m_fullscreenOrigWidth = inlineStyle->getPropertyValue(CSSPropertyWidth);
 | 
| +        if (!m_fullscreenOrigWidth.isNull()) {
 | 
| +          canvas->setInlineStyleProperty(CSSPropertyWidth, "100%");
 | 
| +        }
 | 
| +        m_fullscreenOrigHeight =
 | 
| +            inlineStyle->getPropertyValue(CSSPropertyHeight);
 | 
| +        if (!m_fullscreenOrigHeight.isNull()) {
 | 
| +          canvas->setInlineStyleProperty(CSSPropertyHeight, "100%");
 | 
| +        }
 | 
| +      } else {
 | 
| +        m_fullscreenOrigWidth = String();
 | 
| +        m_fullscreenOrigHeight = String();
 | 
| +      }
 | 
| +
 | 
| +      if (doc) {
 | 
| +        // Since the callback for requestPresent is asynchronous, we've lost our
 | 
| +        // UserGestureToken, and need to create a new one to enter fullscreen.
 | 
| +        gestureIndicator = WTF::wrapUnique(
 | 
| +            new UserGestureIndicator(DocumentUserGestureToken::create(
 | 
| +                doc, UserGestureToken::Status::PossiblyExistingGesture)));
 | 
| +      }
 | 
| +      Fullscreen::requestFullscreen(*canvas);
 | 
| +
 | 
| +      // Check to see if the canvas is still the current fullscreen
 | 
| +      // element once every 2 seconds.
 | 
| +      m_fullscreenCheckTimer.startRepeating(2.0, BLINK_FROM_HERE);
 | 
| +      m_reenteredFullscreen = false;
 | 
|      } else {
 | 
|        DCHECK(m_layer.source().isOffscreenCanvas());
 | 
|        // TODO(junov, crbug.com/695497): Implement OffscreenCanvas presentation
 | 
| @@ -467,8 +519,7 @@
 | 
|    }
 | 
|  
 | 
|    m_display->UpdateLayerBounds(m_vrFrameId, std::move(leftBounds),
 | 
| -                               std::move(rightBounds), m_sourceWidth,
 | 
| -                               m_sourceHeight);
 | 
| +                               std::move(rightBounds));
 | 
|  }
 | 
|  
 | 
|  HeapVector<VRLayer> VRDisplay::getLayers() {
 | 
| @@ -484,7 +535,6 @@
 | 
|  void VRDisplay::submitFrame() {
 | 
|    if (!m_display)
 | 
|      return;
 | 
| -  TRACE_EVENT1("gpu", "submitFrame", "frame", m_vrFrameId);
 | 
|  
 | 
|    Document* doc = this->document();
 | 
|    if (!m_isPresenting) {
 | 
| @@ -513,110 +563,42 @@
 | 
|  
 | 
|    // No frame Id to write before submitting the frame.
 | 
|    if (m_vrFrameId < 0) {
 | 
| -    // TODO(klausw): There used to be a submitFrame here, but we can't
 | 
| -    // submit without a frameId and associated pose data. Just drop it.
 | 
| -    return;
 | 
| -  }
 | 
| -
 | 
| -  m_contextGL->Flush();
 | 
| -  auto elem = m_layer.source();
 | 
| -
 | 
| -  // Check if the canvas got resized, if yes send a bounds update.
 | 
| -  int currentWidth = m_renderingContext->drawingBufferWidth();
 | 
| -  int currentHeight = m_renderingContext->drawingBufferHeight();
 | 
| -  if ((currentWidth != m_sourceWidth || currentHeight != m_sourceHeight) &&
 | 
| -      currentWidth != 0 && currentHeight != 0) {
 | 
| -    m_sourceWidth = currentWidth;
 | 
| -    m_sourceHeight = currentHeight;
 | 
| -    updateLayerBounds();
 | 
| -  }
 | 
| -
 | 
| -  // There's two types of synchronization needed for submitting frames:
 | 
| -  //
 | 
| -  // - Before submitting, need to wait for the previous frame to be
 | 
| -  //   pulled off the transfer surface to avoid lost frames. This
 | 
| -  //   is currently a compile-time option, normally we always want
 | 
| -  //   to defer this wait to increase parallelism.
 | 
| -  //
 | 
| -  // - After submitting, need to wait for the mailbox to be consumed,
 | 
| -  //   and must remain inside the execution context while waiting.
 | 
| -  //   The waitForPreviousTransferToFinish option defers this wait
 | 
| -  //   until the next frame. That's more efficient, but seems to
 | 
| -  //   cause wobbly framerates.
 | 
| -  bool waitForPreviousTransferToFinish =
 | 
| -      RuntimeEnabledFeatures::webVRExperimentalRenderingEnabled();
 | 
| -
 | 
| -  if (waitForPreviousTransferToFinish) {
 | 
| -    TRACE_EVENT0("gpu", "VRDisplay::waitForPreviousTransferToFinish");
 | 
| -    while (m_pendingSubmitFrame) {
 | 
| -      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
 | 
| -        DLOG(ERROR) << "Failed to receive SubmitFrame response";
 | 
| -        break;
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  RefPtr<Image> imageRef = m_renderingContext->getImage(
 | 
| -      PreferAcceleration, SnapshotReasonCreateImageBitmap);
 | 
| -
 | 
| -  // Hardware-accelerated rendering should always be texture backed.
 | 
| -  // I hope nobody is trying to do WebVR with software rendering.
 | 
| -  DCHECK(imageRef->isTextureBacked());
 | 
| -
 | 
| -  // The AcceleratedStaticBitmapImage must be kept alive until the
 | 
| -  // mailbox is used via createAndConsumeTextureCHROMIUM, the mailbox
 | 
| -  // itself does not keep it alive. We must keep a reference to the
 | 
| -  // image until the mailbox was consumed.
 | 
| -  StaticBitmapImage* staticImage =
 | 
| -      static_cast<StaticBitmapImage*>(imageRef.get());
 | 
| -  staticImage->ensureMailbox();
 | 
| -
 | 
| -  if (waitForPreviousTransferToFinish) {
 | 
| -    // Save a reference to the image to keep it alive until next frame,
 | 
| -    // where we'll wait for the transfer to finish before overwriting
 | 
| -    // it.
 | 
| -    m_previousImage = std::move(imageRef);
 | 
| -  }
 | 
| -
 | 
| -  // Wait for the previous render to finish, to avoid losing frames in the
 | 
| -  // Android Surface / GLConsumer pair. TODO(klausw): make this tunable?
 | 
| -  // Other devices may have different preferences.
 | 
| -  {
 | 
| -    TRACE_EVENT0("gpu", "waitForPreviousRenderToFinish");
 | 
| -    while (m_pendingPreviousFrameRender) {
 | 
| -      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
 | 
| -        DLOG(ERROR) << "Failed to receive SubmitFrame response";
 | 
| -        break;
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  m_pendingPreviousFrameRender = true;
 | 
| -  m_pendingSubmitFrame = true;
 | 
| -  m_display->SubmitFrame(
 | 
| -      m_vrFrameId, gpu::MailboxHolder(staticImage->mailbox(),
 | 
| -                                      staticImage->syncToken(), GL_TEXTURE_2D));
 | 
| -
 | 
| -  // If we're not deferring the wait for transferring the mailbox,
 | 
| -  // we need to wait for it now to prevent the image going out of
 | 
| -  // scope before its mailbox is retrieved.
 | 
| -  if (!waitForPreviousTransferToFinish) {
 | 
| -    TRACE_EVENT0("gpu", "waitForCurrentTransferToFinish");
 | 
| -    while (m_pendingSubmitFrame) {
 | 
| -      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
 | 
| -        DLOG(ERROR) << "Failed to receive SubmitFrame response";
 | 
| -        break;
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -void VRDisplay::OnSubmitFrameTransferred() {
 | 
| -  m_pendingSubmitFrame = false;
 | 
| -}
 | 
| -
 | 
| -void VRDisplay::OnSubmitFrameRendered() {
 | 
| -  m_pendingPreviousFrameRender = false;
 | 
| +    m_display->SubmitFrame(m_framePose.Clone());
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  // Write the frame number for the pose used into a bottom left pixel block.
 | 
| +  // It is read by chrome/browser/android/vr_shell/vr_shell.cc to associate
 | 
| +  // the correct corresponding pose for submission.
 | 
| +  auto gl = m_contextGL;
 | 
| +
 | 
| +  // We must ensure that the WebGL app's GL state is preserved. We do this by
 | 
| +  // calling low-level GL commands directly so that the rendering context's
 | 
| +  // saved parameters don't get overwritten.
 | 
| +
 | 
| +  gl->Enable(GL_SCISSOR_TEST);
 | 
| +  // Use a few pixels to ensure we get a clean color. The resolution for the
 | 
| +  // WebGL buffer may not match the final rendered destination size, and
 | 
| +  // texture filtering could interfere for single pixels. This isn't visible
 | 
| +  // since the final rendering hides the edges via a vignette effect.
 | 
| +  gl->Scissor(0, 0, 4, 4);
 | 
| +  gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 | 
| +  // Careful with the arithmetic here. Float color 1.f is equivalent to int 255.
 | 
| +  // Use the low byte of the index as the red component, and store an arbitrary
 | 
| +  // magic number in green/blue. This number must match the reading code in
 | 
| +  // vr_shell.cc. Avoid all-black/all-white.
 | 
| +  gl->ClearColor((m_vrFrameId & 255) / 255.0f,
 | 
| +                 kWebVrPosePixelMagicNumbers[0] / 255.0f,
 | 
| +                 kWebVrPosePixelMagicNumbers[1] / 255.0f, 1.0f);
 | 
| +  gl->Clear(GL_COLOR_BUFFER_BIT);
 | 
| +
 | 
| +  // Set the GL state back to what was set by the WebVR application.
 | 
| +  m_renderingContext->restoreScissorEnabled();
 | 
| +  m_renderingContext->restoreScissorBox();
 | 
| +  m_renderingContext->restoreColorMask();
 | 
| +  m_renderingContext->restoreClearColor();
 | 
| +
 | 
| +  m_display->SubmitFrame(m_framePose.Clone());
 | 
|  }
 | 
|  
 | 
|  Document* VRDisplay::document() {
 | 
| @@ -625,7 +607,7 @@
 | 
|  
 | 
|  void VRDisplay::OnPresentChange() {
 | 
|    if (m_isPresenting && !m_isValidDeviceForPresenting) {
 | 
| -    DVLOG(1) << __FUNCTION__ << ": device not valid, not sending event";
 | 
| +    VLOG(1) << __FUNCTION__ << ": device not valid, not sending event";
 | 
|      return;
 | 
|    }
 | 
|    m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
 | 
| @@ -654,8 +636,19 @@
 | 
|    if (m_isPresenting) {
 | 
|      if (!m_capabilities->hasExternalDisplay()) {
 | 
|        if (m_layer.source().isHTMLCanvasElement()) {
 | 
| -        // TODO(klausw,crbug.com/698923): If compositor updates are
 | 
| -        // suppressed, restore them here.
 | 
| +        auto canvas = m_layer.source().getAsHTMLCanvasElement();
 | 
| +        Fullscreen::fullyExitFullscreen(canvas->document());
 | 
| +        m_fullscreenCheckTimer.stop();
 | 
| +        if (!m_fullscreenOrigWidth.isNull()) {
 | 
| +          canvas->setInlineStyleProperty(CSSPropertyWidth,
 | 
| +                                         m_fullscreenOrigWidth);
 | 
| +          m_fullscreenOrigWidth = String();
 | 
| +        }
 | 
| +        if (!m_fullscreenOrigHeight.isNull()) {
 | 
| +          canvas->setInlineStyleProperty(CSSPropertyWidth,
 | 
| +                                         m_fullscreenOrigHeight);
 | 
| +          m_fullscreenOrigHeight = String();
 | 
| +        }
 | 
|        } else {
 | 
|          // TODO(junov, crbug.com/695497): Implement for OffscreenCanvas
 | 
|        }
 | 
| @@ -668,8 +661,6 @@
 | 
|  
 | 
|    m_renderingContext = nullptr;
 | 
|    m_contextGL = nullptr;
 | 
| -  m_pendingSubmitFrame = false;
 | 
| -  m_pendingPreviousFrameRender = false;
 | 
|  }
 | 
|  
 | 
|  void VRDisplay::OnActivate(device::mojom::blink::VRDisplayEventReason reason) {
 | 
| @@ -692,12 +683,18 @@
 | 
|    switch (error) {
 | 
|      case device::mojom::blink::VRVSyncProvider::Status::SUCCESS:
 | 
|        break;
 | 
| -    case device::mojom::blink::VRVSyncProvider::Status::CLOSING:
 | 
| +    case device::mojom::blink::VRVSyncProvider::Status::RETRY:
 | 
| +      m_vrVSyncProvider->GetVSync(convertToBaseCallback(
 | 
| +          WTF::bind(&VRDisplay::OnVSync, wrapWeakPersistent(this))));
 | 
|        return;
 | 
|    }
 | 
|    m_pendingVsync = false;
 | 
| +  if (m_displayBlurred)
 | 
| +    return;
 | 
| +  if (!m_scriptedAnimationController)
 | 
| +    return;
 | 
|    Document* doc = this->document();
 | 
| -  if (!doc || m_displayBlurred || !m_scriptedAnimationController)
 | 
| +  if (!doc)
 | 
|      return;
 | 
|  
 | 
|    WTF::TimeDelta timeDelta =
 | 
| @@ -719,8 +716,6 @@
 | 
|    if (!m_navigatorVR->isFocused() || m_vrVSyncProvider.is_bound())
 | 
|      return;
 | 
|    m_display->GetVRVSyncProvider(mojo::MakeRequest(&m_vrVSyncProvider));
 | 
| -  m_vrVSyncProvider.set_connection_error_handler(convertToBaseCallback(
 | 
| -      WTF::bind(&VRDisplay::OnVSyncConnectionError, wrapWeakPersistent(this))));
 | 
|    if (m_pendingRaf && !m_displayBlurred) {
 | 
|      m_pendingVsync = true;
 | 
|      m_vrVSyncProvider->GetVSync(convertToBaseCallback(
 | 
| @@ -728,9 +723,42 @@
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -void VRDisplay::OnVSyncConnectionError() {
 | 
| -  m_vrVSyncProvider.reset();
 | 
| -  ConnectVSyncProvider();
 | 
| +void VRDisplay::onFullscreenCheck(TimerBase*) {
 | 
| +  DCHECK(m_layer.source().isHTMLCanvasElement());
 | 
| +  if (!m_isPresenting) {
 | 
| +    m_fullscreenCheckTimer.stop();
 | 
| +    return;
 | 
| +  }
 | 
| +  // TODO: This is a temporary measure to track if fullscreen mode has been
 | 
| +  // exited by the UA. If so we need to end VR presentation. Soon we won't
 | 
| +  // depend on the Fullscreen API to fake VR presentation, so this will
 | 
| +  // become unnessecary. Until that point, though, this seems preferable to
 | 
| +  // adding a bunch of notification plumbing to Fullscreen.
 | 
| +  if (!Fullscreen::isCurrentFullScreenElement(
 | 
| +          *m_layer.source().getAsHTMLCanvasElement())) {
 | 
| +    // TODO(mthiesse): Due to asynchronous resizing, we might get kicked out of
 | 
| +    // fullscreen when changing display parameters upon entering WebVR. So one
 | 
| +    // time only, we reenter fullscreen after having left it; otherwise we exit
 | 
| +    // presentation.
 | 
| +    if (m_reenteredFullscreen) {
 | 
| +      m_isPresenting = false;
 | 
| +      OnPresentChange();
 | 
| +      m_fullscreenCheckTimer.stop();
 | 
| +      if (m_display)
 | 
| +        m_display->ExitPresent();
 | 
| +      return;
 | 
| +    }
 | 
| +    m_reenteredFullscreen = true;
 | 
| +    auto canvas = m_layer.source().getAsHTMLCanvasElement();
 | 
| +    Document* doc = this->document();
 | 
| +    std::unique_ptr<UserGestureIndicator> gestureIndicator;
 | 
| +    if (doc) {
 | 
| +      gestureIndicator = WTF::wrapUnique(
 | 
| +          new UserGestureIndicator(DocumentUserGestureToken::create(
 | 
| +              doc, UserGestureToken::Status::PossiblyExistingGesture)));
 | 
| +    }
 | 
| +    Fullscreen::requestFullscreen(*canvas);
 | 
| +  }
 | 
|  }
 | 
|  
 | 
|  ScriptedAnimationController& VRDisplay::ensureScriptedAnimationController(
 | 
| 
 |