dom/power/WakeLock.cpp
author Timothy Nikkel <tnikkel@gmail.com>
Thu, 16 Mar 2017 03:06:06 -0500
changeset 348004 d4cafcc44babd82e009801f6d453b6002fb280d7
parent 282426 e22b3043887ed36bf2c634c2924a7c8d39d226b1
child 393136 7b021ff1814fdba7aef2bf4e169535ea99e9396b
permissions -rw-r--r--
Bug 1343341. Only set the animation state as discarded if we discarded the animated frames (and not a static frame from a first-frame-only decode). r=aosmond The SurfaceCache can hold the first frame of a "static" decode as well as the animated frames in two seperate entries. We only care about what happens to the animated frames, so ignore OnSurfaceDiscarded for anything else. To accomplish this we must pass the SurfaceKey to OnSurfaceDiscarded.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#include "WakeLock.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
#include "mozilla/dom/MozWakeLockBinding.h"
#include "mozilla/Hal.h"
#include "mozilla/HalWakeLock.h"
#include "nsError.h"
#include "nsIDocument.h"
#include "nsIDOMWindow.h"
#include "nsIDOMEvent.h"
#include "nsPIDOMWindow.h"
#include "nsIPropertyBag2.h"

using namespace mozilla::hal;

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WakeLock)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WakeLock)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(WakeLock)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WakeLock)

WakeLock::WakeLock()
  : mLocked(false)
  , mHidden(true)
  , mContentParentID(CONTENT_PROCESS_ID_UNKNOWN)
{
}

WakeLock::~WakeLock()
{
  DoUnlock();
  DetachEventListener();
}

JSObject*
WakeLock::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return MozWakeLockBinding::Wrap(aCx, this, aGivenProto);
}

nsresult
WakeLock::Init(const nsAString &aTopic, nsPIDOMWindowInner* aWindow)
{
  // Don't Init() a WakeLock twice.
  MOZ_ASSERT(mTopic.IsEmpty());

  if (aTopic.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  mTopic.Assign(aTopic);

  mWindow = do_GetWeakReference(aWindow);

  /**
   * Null windows are allowed. A wake lock without associated window
   * is always considered invisible.
   */
  if (aWindow) {
    nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
    NS_ENSURE_STATE(doc);
    mHidden = doc->Hidden();
  }

  AttachEventListener();
  DoLock();

  return NS_OK;
}

nsresult
WakeLock::Init(const nsAString& aTopic, ContentParent* aContentParent)
{
  // Don't Init() a WakeLock twice.
  MOZ_ASSERT(mTopic.IsEmpty());
  MOZ_ASSERT(aContentParent);

  if (aTopic.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }

  mTopic.Assign(aTopic);
  mContentParentID = aContentParent->ChildID();
  mHidden = false;

  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ true);
  }

  DoLock();
  return NS_OK;
}

NS_IMETHODIMP
WakeLock::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* data)
{
  // If this wake lock was acquired on behalf of another process, unlock it
  // when that process dies.
  //
  // Note that we do /not/ call DoUnlock() here!  The wake lock back-end is
  // already listening for ipc:content-shutdown messages and will clear out its
  // tally for the process when it dies.  All we need to do here is ensure that
  // unlock() becomes a nop.

  MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));

  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
  if (!props) {
    NS_WARNING("ipc:content-shutdown message without property bag as subject");
    return NS_OK;
  }

  uint64_t childID = 0;
  nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
                                           &childID);
  if (NS_SUCCEEDED(rv)) {
    if (childID == mContentParentID) {
      mLocked = false;
    }
  } else {
    NS_WARNING("ipc:content-shutdown message without childID property");
  }
  return NS_OK;
}

void
WakeLock::DoLock()
{
  if (!mLocked) {
    // Change the flag immediately to prevent recursive reentering
    mLocked = true;

    hal::ModifyWakeLock(mTopic,
                        hal::WAKE_LOCK_ADD_ONE,
                        mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE,
                        mContentParentID);
  }
}

void
WakeLock::DoUnlock()
{
  if (mLocked) {
    // Change the flag immediately to prevent recursive reentering
    mLocked = false;

    hal::ModifyWakeLock(mTopic,
                        hal::WAKE_LOCK_REMOVE_ONE,
                        mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE,
                        mContentParentID);
  }
}

void
WakeLock::AttachEventListener()
{
  if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
    if (doc) {
      doc->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
                                  this,
                                  /* useCapture = */ true,
                                  /* wantsUntrusted = */ false);

      nsCOMPtr<EventTarget> target = do_QueryInterface(window);
      target->AddSystemEventListener(NS_LITERAL_STRING("pagehide"),
                                     this,
                                     /* useCapture = */ true,
                                     /* wantsUntrusted = */ false);
      target->AddSystemEventListener(NS_LITERAL_STRING("pageshow"),
                                     this,
                                     /* useCapture = */ true,
                                     /* wantsUntrusted = */ false);
    }
  }
}

void
WakeLock::DetachEventListener()
{
  if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
    if (doc) {
      doc->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
                                     this,
                                     /* useCapture = */ true);
      nsCOMPtr<EventTarget> target = do_QueryInterface(window);
      target->RemoveSystemEventListener(NS_LITERAL_STRING("pagehide"),
                                        this,
                                        /* useCapture = */ true);
      target->RemoveSystemEventListener(NS_LITERAL_STRING("pageshow"),
                                        this,
                                        /* useCapture = */ true);
    }
  }
}

void
WakeLock::Unlock(ErrorResult& aRv)
{
  /*
   * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock.
   */
  if (!mLocked) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  DoUnlock();
  DetachEventListener();
}

void
WakeLock::GetTopic(nsAString &aTopic)
{
  aTopic.Assign(mTopic);
}

NS_IMETHODIMP
WakeLock::HandleEvent(nsIDOMEvent *aEvent)
{
  nsAutoString type;
  aEvent->GetType(type);

  if (type.EqualsLiteral("visibilitychange")) {
    nsCOMPtr<nsIDocument> doc =
      do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
    NS_ENSURE_STATE(doc);

    bool oldHidden = mHidden;
    mHidden = doc->Hidden();

    if (mLocked && oldHidden != mHidden) {
      hal::ModifyWakeLock(mTopic,
                          hal::WAKE_LOCK_NO_CHANGE,
                          mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE,
                          mContentParentID);
    }

    return NS_OK;
  }

  if (type.EqualsLiteral("pagehide")) {
    DoUnlock();
    return NS_OK;
  }

  if (type.EqualsLiteral("pageshow")) {
    DoLock();
    return NS_OK;
  }

  return NS_OK;
}

nsPIDOMWindowInner*
WakeLock::GetParentObject() const
{
  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mWindow);
  return window;
}

} // namespace dom
} // namespace mozilla