Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/documentpip/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 14 kB image not shown  

Quelle  DocumentPictureInPicture.cpp

  Sprache: C
 

/* 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 https://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/DocumentPictureInPicture.h"

#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/WidgetUtils.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentPictureInPictureEvent.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/widget/Screen.h"
#include "nsDocShell.h"
#include "nsDocShellLoadState.h"
#include "nsGlobalWindowOuter.h"
#include "nsIWindowWatcher.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindowInlines.h"
#include "nsPIWindowWatcher.h"
#include "nsServiceManagerUtils.h"
#include "nsWindowWatcher.h"

namespace mozilla::dom {

static mozilla::LazyLogModule gDPIPLog("DocumentPIP");

NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentPictureInPicture)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentPictureInPicture,
                                                  DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastOpenedWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentPictureInPicture,
                                                DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastOpenedWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentPictureInPicture)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_ADDREF_INHERITED(DocumentPictureInPicture, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(DocumentPictureInPicture, DOMEventTargetHelper)

JSObject* DocumentPictureInPicture::WrapObject(
    JSContext* cx, JS::Handle<JSObject*> aGivenProto) {
  return DocumentPictureInPicture_Binding::Wrap(cx, this, aGivenProto);
}

DocumentPictureInPicture::DocumentPictureInPicture(nsPIDOMWindowInner* aWindow)
    : DOMEventTargetHelper(aWindow) {
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  NS_ENSURE_TRUE_VOID(os);
  MOZ_ALWAYS_SUCCEEDS(os->AddObserver(this, "domwindowclosed"false));
  MOZ_ALWAYS_SUCCEEDS(
      os->AddObserver(this, "docshell-position-size-changed"false));
}

DocumentPictureInPicture::~DocumentPictureInPicture() {
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  NS_ENSURE_TRUE_VOID(os);
  MOZ_ALWAYS_SUCCEEDS(os->RemoveObserver(this, "domwindowclosed"));
  MOZ_ALWAYS_SUCCEEDS(
      os->RemoveObserver(this, "docshell-position-size-changed"));
}

void DocumentPictureInPicture::OnPiPResized() {
  if (!mLastOpenedWindow) {
    return;
  }

  RefPtr<nsGlobalWindowInner> innerWindow =
      nsGlobalWindowInner::Cast(mLastOpenedWindow);

  int x = innerWindow->GetScreenLeft(CallerType::System, IgnoreErrors());
  int y = innerWindow->GetScreenTop(CallerType::System, IgnoreErrors());
  int width = static_cast<int>(innerWindow->GetInnerWidth(IgnoreErrors()));
  int height = static_cast<int>(innerWindow->GetInnerHeight(IgnoreErrors()));

  mPreviousExtent = Some(CSSIntRect(x, y, width, height));

  MOZ_LOG(gDPIPLog, LogLevel::Debug,
          ("PiP was resized, remembering position %s",
           ToString(mPreviousExtent).c_str()));
}

void DocumentPictureInPicture::OnPiPClosed() {
  if (!mLastOpenedWindow) {
    return;
  }

  MOZ_LOG(gDPIPLog, LogLevel::Debug, ("PiP was closed"));

  mLastOpenedWindow = nullptr;

  if (RefPtr<nsPIDOMWindowInner> ownerWin = GetOwnerWindow()) {
    if (BrowsingContext* bc = ownerWin->GetBrowsingContext()) {
      MOZ_ASSERT(bc->GetControlsDocumentPiP());
      DebugOnly<nsresult> rv = bc->SetControlsDocumentPiP(false);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }
  }
}

nsGlobalWindowInner* DocumentPictureInPicture::GetWindow() const {
  if (mLastOpenedWindow && mLastOpenedWindow->GetOuterWindow() &&
      !mLastOpenedWindow->GetOuterWindow()->Closed()) {
    return nsGlobalWindowInner::Cast(mLastOpenedWindow);
  }
  return nullptr;
}

// Some sane default.
const CSSIntSize DocumentPictureInPicture::sDefaultSize = {400300};
const CSSIntSize DocumentPictureInPicture::sMinSize = {24050};

static nsresult OpenPiPWindowUtility(nsPIDOMWindowOuter* aParent,
                                     const CSSIntRect& aExtent, bool aPrivate,
                                     bool aDisallowReturnToOpener,
                                     mozilla::dom::BrowsingContext** aRet) {
  MOZ_DIAGNOSTIC_ASSERT(aParent);

  nsresult rv = NS_OK;
  nsCOMPtr<nsIWindowWatcher> ww =
      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsPIWindowWatcher> pww(do_QueryInterface(ww));
  NS_ENSURE_TRUE(pww, NS_ERROR_FAILURE);

  nsCOMPtr<nsIURI> uri;
  rv = NS_NewURI(getter_AddRefs(uri), "about:blank"_ns, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsDocShellLoadState> loadState =
      nsWindowWatcher::CreateLoadState(uri, aParent);

  // pictureinpicture, disallow_return_to_oopener are non-standard window
  // features not available from JS
  nsPrintfCString features("pictureinpicture,top=%d,left=%d,width=%d,height=%d",
                           aExtent.y, aExtent.x, aExtent.width, aExtent.height);
  if (aDisallowReturnToOpener) {
    features += ",disallow_return_to_opener";
  }

  rv = pww->OpenWindow2(aParent, uri, "_blank"_ns, features,
                        mozilla::dom::UserActivation::Modifiers::None(), false,
                        false, true, nullptr, falsefalsefalse,
                        nsPIWindowWatcher::PrintKind::PRINT_NONE, loadState,
                        aRet);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(aRet, NS_ERROR_FAILURE);
  return NS_OK;
}

/* static */
Maybe<CSSIntRect> DocumentPictureInPicture::GetScreenRect(
    nsPIDOMWindowOuter* aWindow) {
  nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(aWindow);
  NS_ENSURE_TRUE(widget, Nothing());
  RefPtr<widget::Screen> screen = widget->GetWidgetScreen();
  NS_ENSURE_TRUE(screen, Nothing());
  LayoutDeviceIntRect rect = screen->GetRect();

  nsGlobalWindowOuter* outerWindow = nsGlobalWindowOuter::Cast(aWindow);
  NS_ENSURE_TRUE(outerWindow, Nothing());
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = outerWindow->GetTreeOwnerWindow();
  NS_ENSURE_TRUE(treeOwnerAsWin, Nothing());
  auto scale = outerWindow->CSSToDevScaleForBaseWindow(treeOwnerAsWin);

  return Some(RoundedToInt(rect / scale));
}

// Place window in the bottom right of the opener window's screen
static CSSIntRect CalcInitialExtent(const CSSIntRect& aScreen,
                                    const CSSIntSize& aSize) {
  // aSize is the inner size not including browser UI. But we need the outer
  // size for calculating where the top left corner of the PiP should be
  // initially. For now use a guess of ~80px for the browser UI?
  const CSSIntPoint pos = {
      std::max(aScreen.X(), aScreen.XMost() - aSize.width - 100),
      std::max(aScreen.Y(), aScreen.YMost() - aSize.height - 100 - 80)};
  return CSSIntRect(pos, aSize);
}

/* static */
CSSIntSize DocumentPictureInPicture::CalcMaxDimensions(
    const CSSIntRect& aScreen) {
  // Limit PIP size to 80% (arbitrary number) of screen size
  // https://wicg.github.io/document-picture-in-picture/#maximum-size
  CSSIntSize size =
      RoundedToInt(aScreen.Size() * gfx::ScaleFactor<CSSPixel, CSSPixel>(0.8));
  size.width = std::max(size.width, sMinSize.width);
  size.height = std::max(size.height, sMinSize.height);
  return size;
}

CSSIntRect DocumentPictureInPicture::DetermineExtent(
    bool aPreferInitialWindowPlacement, const CSSIntSize& aRequestedSize,
    const CSSIntRect& aScreen) {
  // The user agent may use the previous position and size.
  // https://wicg.github.io/document-picture-in-picture/#example-prefer-initial-window-placement

  // If no width/height was specified (it's 0), consider it unchanged too.
  const bool emptyRequest = aRequestedSize == CSSIntSize(00);
  const bool requestChanged =
      !emptyRequest &&
      (mLastRequestedSize.isNothing() || *mLastRequestedSize != aRequestedSize);

  if (!emptyRequest) {
    mLastRequestedSize = Some(aRequestedSize);
  }

  // If we remembered an extent, don't preferInitialWindowPlacement, and the
  // requested size didn't change, then restore the remembered extent.
  const bool reusePreviousExtent = mPreviousExtent.isSome() &&
                                   !aPreferInitialWindowPlacement &&
                                   !requestChanged;

  MOZ_LOG_FMT(gDPIPLog, LogLevel::Debug,
              "{} reuse previous extent (hasPrevious={}, preferInitial={}, "
              "requestChanged={})",
              reusePreviousExtent ? "Will" : "Won't", mPreviousExtent.isSome(),
              aPreferInitialWindowPlacement, requestChanged);

  CSSIntRect extent;
  if (reusePreviousExtent) {
    extent = mPreviousExtent.value();
  } else {
    extent = CalcInitialExtent(aScreen,
                               emptyRequest ? sDefaultSize : aRequestedSize);

    MOZ_LOG(gDPIPLog, LogLevel::Debug,
            ("Calculated initial PiP rect %s", ToString(extent).c_str()));
  }

  // https://wicg.github.io/document-picture-in-picture/#maximum-size
  CSSIntSize maxSize = CalcMaxDimensions(aScreen);
  extent.width = std::clamp(extent.width, sMinSize.width, maxSize.width);
  extent.height = std::clamp(extent.height, sMinSize.height, maxSize.height);

  return extent;
}

already_AddRefed<Promise> DocumentPictureInPicture::RequestWindow(
    const DocumentPictureInPictureOptions& aOptions, ErrorResult& aRv) {
  // Not part of the spec, but check the document is active
  RefPtr<nsPIDOMWindowInner> ownerWin = GetOwnerWindow();
  if (!ownerWin || !ownerWin->IsFullyActive()) {
    aRv.ThrowNotAllowedError("Document is not fully active");
    return nullptr;
  }

  // 2. Throw if not top-level
  BrowsingContext* bc = ownerWin->GetBrowsingContext();
  if (!bc || !bc->IsTop()) {
    aRv.ThrowNotAllowedError(
        "Document Picture-in-Picture is only available in top-level contexts");
    return nullptr;
  }

  // 3. Throw if already in a Document PIP window
  if (bc->GetIsDocumentPiP()) {
    aRv.ThrowNotAllowedError(
        "Cannot open a Picture-in-Picture window from inside one");
    return nullptr;
  }

  // 4, 7. Require transient activation
  WindowContext* wc = ownerWin->GetWindowContext();
  if (!wc || !wc->ConsumeTransientUserGestureActivation()) {
    aRv.ThrowNotAllowedError(
        "Document Picture-in-Picture requires user activation");
    return nullptr;
  }

  // 5-6. If width or height is given, both must be specified
  if ((aOptions.mWidth > 0) != (aOptions.mHeight > 0)) {
    aRv.ThrowRangeError(
        "requestWindow: width and height must be specified together");
    return nullptr;
  }

  // 8. Possibly close last opened window
  if (RefPtr<nsPIDOMWindowInner> lastOpenedWindow = mLastOpenedWindow) {
    lastOpenedWindow->Close();
  }

  CSSIntRect screen;
  if (Maybe<CSSIntRect> maybeScreen =
          GetScreenRect(ownerWin->GetOuterWindow())) {
    screen = maybeScreen.value();
  } else {
    aRv.ThrowRangeError("Could not determine screen for window");
    return nullptr;
  }

  // 13-15. Determine PiP extent
  const CSSIntSize requestedSize = {SaturatingCast<int>(aOptions.mWidth),
                                    SaturatingCast<int>(aOptions.mHeight)};
  CSSIntRect extent = DetermineExtent(aOptions.mPreferInitialWindowPlacement,
                                      requestedSize, screen);

  MOZ_LOG(gDPIPLog, LogLevel::Debug,
          ("Will place PiP at rect %s", ToString(extent).c_str()));

  // 9. Optionally, close any existing PIP windows
  // I think it's useful to have multiple PiP windows from different top pages.

  // 10. Create a new top-level traversable for target _blank
  // 15. aOptions.mDisallowReturnToOpener
  // 16. Configure PIP to float on top via window features
  RefPtr<BrowsingContext> pipTraversable;
  nsresult rv = OpenPiPWindowUtility(
      ownerWin->GetOuterWindow(), extent, bc->UsePrivateBrowsing(),
      aOptions.mDisallowReturnToOpener, getter_AddRefs(pipTraversable));
  if (NS_FAILED(rv)) {
    aRv.ThrowUnknownError("Failed to create PIP window");
    return nullptr;
  }

  // 11. Set PIP's active document's mode to this's document's mode
  pipTraversable->GetDocument()->SetCompatibilityMode(
      ownerWin->GetDoc()->GetCompatibilityMode());

  // 12. Set PIP's IsDocumentPIP flag
  rv = pipTraversable->SetIsDocumentPiP(true);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  MOZ_ASSERT(!bc->GetControlsDocumentPiP());
  rv = bc->SetControlsDocumentPiP(true);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  // 16. Set mLastOpenedWindow
  mLastOpenedWindow = pipTraversable->GetDOMWindow()->GetCurrentInnerWindow();
  MOZ_ASSERT(mLastOpenedWindow);

  // 17. Queue a task to fire a DocumentPictureInPictureEvent named "enter" on
  // this with pipTraversable as it's window attribute
  DocumentPictureInPictureEventInit eventInit;
  eventInit.mWindow = nsGlobalWindowInner::Cast(mLastOpenedWindow);
  RefPtr<Event> event =
      DocumentPictureInPictureEvent::Constructor(this, u"enter"_ns, eventInit);
  RefPtr<AsyncEventDispatcher> asyncDispatcher =
      new AsyncEventDispatcher(this, event.forget());
  asyncDispatcher->PostDOMEvent();

  // 18. Return pipTraversable
  RefPtr<Promise> promise = Promise::CreateInfallible(GetRelevantGlobal());
  promise->MaybeResolve(nsGlobalWindowInner::Cast(mLastOpenedWindow));
  return promise.forget();
}

NS_IMETHODIMP DocumentPictureInPicture::Observe(nsISupports* aSubject,
                                                const char* aTopic,
                                                const char16_t* aData) {
  if (!mLastOpenedWindow) {
    return NS_OK;
  }

  if (nsCRT::strcmp(aTopic, "domwindowclosed") == 0) {
    nsCOMPtr<nsPIDOMWindowOuter> subjectWin = do_QueryInterface(aSubject);
    NS_ENSURE_TRUE(!!subjectWin, NS_OK);

    if (subjectWin->GetCurrentInnerWindow() == mLastOpenedWindow) {
      OnPiPClosed();
    }
  } else if (nsCRT::strcmp(aTopic, "docshell-position-size-changed") == 0) {
    nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(aSubject);
    NS_ENSURE_TRUE(!!docshell, NS_OK);
    BrowsingContext* bc = docshell->GetBrowsingContext();

    if (!bc || !bc->GetIsDocumentPiP()) {
      return NS_OK;
    }

    if (bc == mLastOpenedWindow->GetBrowsingContext()) {
      // Async invocation due to MOZ_CAN_RUN_SCRIPT
      NS_DispatchToMainThread(NS_NewRunnableFunction(
          "DocumentPictureInPicture::OnPiPResized",
          [_self = RefPtr(this)]()
              MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { _self->OnPiPResized(); }));
    }
  }
  return NS_OK;
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=88 H=94 G=90

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.