/* -*- 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/. */

/*
 * A base class implementing nsIObjectLoadingContent for use by
 * various content nodes that want to provide plugin/document/image
 * loading functionality (eg <embed>, <object>, etc).
 */

#ifndef NSOBJECTLOADINGCONTENT_H_
#define NSOBJECTLOADINGCONTENT_H_

#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsIFrame.h"  // for WeakFrame only
#include "nsImageLoadingContent.h"
#include "nsIStreamListener.h"
#include "nsIChannelEventSink.h"
#include "nsIObjectLoadingContent.h"
#include "nsIRunnable.h"
#include "nsFrameLoaderOwner.h"

class nsStopPluginRunnable;
class nsIPrincipal;
class nsFrameLoader;

namespace mozilla::dom {
struct BindContext;
template <typename T>
class Sequence;
struct MozPluginParameter;
class HTMLIFrameElement;
template <typename T>
struct Nullable;
class WindowProxyHolder;
class XULFrameElement;
}  // namespace mozilla::dom

class nsObjectLoadingContent : public nsImageLoadingContent,
                               public nsIStreamListener,
                               public nsFrameLoaderOwner,
                               public nsIObjectLoadingContent,
                               public nsIChannelEventSink {
  friend class AutoSetLoadingToFalse;

 public:
  // This enum's values must be the same as the constants on
  // nsIObjectLoadingContent
  enum ObjectType {
    // Loading, type not yet known. We may be waiting for a channel to open.
    eType_Loading = TYPE_LOADING,
    // Content is a *non-svg* image
    eType_Image = TYPE_IMAGE,
    // Content is a "special" plugin.  Plugins are removed but these MIME
    // types display an transparent region in their place.
    // (Special plugins that have an HTML fallback are eType_Null)
    eType_Fallback = TYPE_FALLBACK,
    // Content is a fake plugin, which loads as a document but behaves as a
    // plugin (see nsPluginHost::CreateFakePlugin).  Currently only used for
    // pdf.js.
    eType_FakePlugin = TYPE_FAKE_PLUGIN,
    // Content is a subdocument, possibly SVG
    eType_Document = TYPE_DOCUMENT,
    // Content is unknown and should be represented by an empty element,
    // unless an HTML fallback is available.
    eType_Null = TYPE_NULL
  };

  nsObjectLoadingContent();
  virtual ~nsObjectLoadingContent();

  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSIOBJECTLOADINGCONTENT
  NS_DECL_NSICHANNELEVENTSINK

  /**
   * Object state. This is a bitmask of NS_EVENT_STATEs epresenting the
   * current state of the object.
   */
  mozilla::dom::ElementState ObjectState() const;

  ObjectType Type() const { return mType; }

  void SetIsNetworkCreated(bool aNetworkCreated) {
    mNetworkCreated = aNetworkCreated;
  }

  /**
   * When the object is loaded, the attributes and all nested <param>
   * elements are cached as name:value string pairs to be passed as
   * parameters when instantiating the plugin.
   *
   * Note: these cached values can be overriden for different quirk cases.
   */
  // Returns the cached attributes array.
  void GetPluginAttributes(
      nsTArray<mozilla::dom::MozPluginParameter>& aAttributes);

  // Returns the cached <param> array.
  void GetPluginParameters(
      nsTArray<mozilla::dom::MozPluginParameter>& aParameters);

  /**
   * Notify this class the document state has changed
   * Called by Document so we may suspend plugins in inactive documents)
   */
  void NotifyOwnerDocumentActivityChanged();

  // Helper for WebIDL NeedResolve
  bool DoResolve(
      JSContext* aCx, JS::Handle<JSObject*> aObject, JS::Handle<jsid> aId,
      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc);
  // The return value is whether DoResolve might end up resolving the given
  // id.  If in doubt, return true.
  static bool MayResolve(jsid aId);

  static bool IsSuccessfulRequest(nsIRequest*, nsresult* aStatus);

  // Helper for WebIDL enumeration
  void GetOwnPropertyNames(JSContext* aCx,
                           JS::MutableHandleVector<jsid> /* unused */,
                           bool /* unused */, mozilla::ErrorResult& aRv);

  // WebIDL API
  mozilla::dom::Document* GetContentDocument(nsIPrincipal& aSubjectPrincipal);
  void GetActualType(nsAString& aType) const {
    CopyUTF8toUTF16(mContentType, aType);
  }
  uint32_t DisplayedType() const { return mType; }
  uint32_t GetContentTypeForMIMEType(const nsAString& aMIMEType) {
    return GetTypeOfContent(NS_ConvertUTF16toUTF8(aMIMEType), false);
  }
  void Reload(bool aClearActivation, mozilla::ErrorResult& aRv) {
    aRv = Reload(aClearActivation);
  }
  nsIURI* GetSrcURI() const { return mURI; }

  // FIXME rename this
  void SkipFakePlugins(mozilla::ErrorResult& aRv) { aRv = SkipFakePlugins(); }
  void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
                        mozilla::ErrorResult& aRv) {
    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
  }
  void SwapFrameLoaders(mozilla::dom::XULFrameElement& aOtherLoaderOwner,
                        mozilla::ErrorResult& aRv) {
    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
  }

  uint32_t GetRunID(mozilla::dom::SystemCallerGuarantee,
                    mozilla::ErrorResult& aRv);

  bool IsRewrittenYoutubeEmbed() const { return mRewrittenYoutubeEmbed; }

  void PresetOpenerWindow(const mozilla::dom::Nullable<
                              mozilla::dom::WindowProxyHolder>& aOpenerWindow,
                          mozilla::ErrorResult& aRv);

  const mozilla::Maybe<mozilla::IntrinsicSize>& GetSubdocumentIntrinsicSize()
      const {
    return mSubdocumentIntrinsicSize;
  }

  const mozilla::Maybe<mozilla::AspectRatio>& GetSubdocumentIntrinsicRatio()
      const {
    return mSubdocumentIntrinsicRatio;
  }

  void SubdocumentIntrinsicSizeOrRatioChanged(
      const mozilla::Maybe<mozilla::IntrinsicSize>& aIntrinsicSize,
      const mozilla::Maybe<mozilla::AspectRatio>& aIntrinsicRatio);

  void SubdocumentImageLoadComplete(nsresult aResult);

 protected:
  /**
   * Begins loading the object when called
   *
   * Attributes of |this| QI'd to nsIContent will be inspected, depending on
   * the node type. This function currently assumes it is a <object> or
   * <embed> tag.
   *
   * The instantiated plugin depends on:
   * - The URI (<embed src>, <object data>)
   * - The type 'hint' (type attribute)
   * - The mime type returned by opening the URI
   * - Enabled plugins claiming the ultimate mime type
   * - The capabilities returned by GetCapabilities
   * - The classid attribute, if eFallbackIfClassIDPresent is among the
   * capabilities
   *
   * If eAllowPluginSkipChannel is true, we may skip opening the URI if our
   * type hint points to a valid plugin, deferring that responsibility to the
   * plugin.
   * Similarly, if no URI is provided, but a type hint for a valid plugin is
   * present, that plugin will be instantiated
   *
   * Otherwise a request to that URI is made and the type sent by the server
   * is used to find a suitable handler, EXCEPT when:
   *  - The type hint refers to a *supported* plugin, in which case that
   *    plugin will be instantiated regardless of the server provided type
   *  - The server returns a binary-stream type, and our type hint refers to
   *    a valid non-document type, we will use the type hint
   *
   * @param aNotify    If we should send notifications. If false, content
   *                   loading may be deferred while appropriate frames are
   *                   created
   * @param aForceLoad If we should reload this content (and re-attempt the
   *                   channel open) even if our parameters did not change
   */
  nsresult LoadObject(bool aNotify, bool aForceLoad = false);

  enum Capabilities {
    eSupportImages = 1u << 0,     // Images are supported (imgILoader)
    eSupportPlugins = 1u << 1,    // Plugins are supported (nsIPluginHost)
    eSupportDocuments = 1u << 2,  // Documents are supported
                                  // (DocumentLoaderFactory)
                                  // This flag always includes SVG

    // Node supports class ID as an attribute, and should fallback if it is
    // present, as class IDs are not supported.
    eFallbackIfClassIDPresent = 1u << 3,

    // If possible to get a *plugin* type from the type attribute *or* file
    // extension, we can use that type and begin loading the plugin before
    // opening a channel.
    // A side effect of this is if the channel fails, the plugin is still
    // running.
    eAllowPluginSkipChannel = 1u << 4
  };

  /**
   * Returns the list of capabilities this content node supports. This is a
   * bitmask consisting of flags from the Capabilities enum.
   *
   * The default implementation supports all types but not
   * eSupportClassID or eAllowPluginSkipChannel
   */
  virtual uint32_t GetCapabilities() const;

  /**
   * Destroys all loaded documents/plugins and releases references
   */
  void Destroy();

  // Subclasses should call cycle collection methods from the respective
  // traverse / unlink.
  static void Traverse(nsObjectLoadingContent* tmp,
                       nsCycleCollectionTraversalCallback& cb);
  static void Unlink(nsObjectLoadingContent* tmp);

  void CreateStaticClone(nsObjectLoadingContent* aDest) const;

  nsresult BindToTree(mozilla::dom::BindContext& aCxt, nsINode& aParent) {
    nsImageLoadingContent::BindToTree(aCxt, aParent);
    return NS_OK;
  }
  void UnbindFromTree(bool aNullParent = true);

  /**
   * Return the content policy type used for loading the element.
   */
  virtual nsContentPolicyType GetContentPolicyType() const = 0;

  /**
   * Decides whether we should load <embed>/<object> node content.
   *
   * If this is an <embed> or <object> node there are cases in which we should
   * not try to load the content:
   *
   * - If the node is the child of a media element
   * - If the node is the child of an <object> node that already has
   *   content being loaded.
   *
   * In these cases, this function will return false, which will cause
   * us to skip calling LoadObject.
   */
  bool BlockEmbedOrObjectContentLoading();

 private:
  // Object parameter changes returned by UpdateObjectParameters
  enum ParameterUpdateFlags {
    eParamNoChange = 0,
    // Parameters that potentially affect the channel changed
    // - mOriginalURI, mOriginalContentType
    eParamChannelChanged = 1u << 0,
    // Parameters that affect displayed content changed
    // - mURI, mContentType, mType, mBaseURI
    eParamStateChanged = 1u << 1,
    // The effective content type changed, independant of object type. This
    // can happen when changing from Loading -> Final type, but doesn't
    // necessarily happen when changing between object types. E.g., if a PDF
    // handler was installed between the last load of this object and now, we
    // might change from eType_Document -> eType_Plugin without changing
    // ContentType
    eParamContentTypeChanged = 1u << 2
  };

  /**
   * Getter for child <param> elements that are not nested in another plugin
   * dom element.
   * This is an internal helper function and should not be used directly for
   * passing parameters to the plugin instance.
   *
   * See GetPluginParameters and GetPluginAttributes, which also handle
   * quirk-overrides.
   *
   * @param aParameters     The array containing pairs of name/value strings
   *                        from nested <param> objects.
   */
  void GetNestedParams(nsTArray<mozilla::dom::MozPluginParameter>& aParameters);

  [[nodiscard]] nsresult BuildParametersArray();

  /**
   * Configure fallback for deprecated plugin and broken elements.
   */
  void ConfigureFallback();

  /**
   * Internal version of LoadObject that should only be used by this class
   * aLoadingChannel is passed by the LoadObject call from OnStartRequest,
   * primarily for sanity-preservation
   */
  nsresult LoadObject(bool aNotify, bool aForceLoad,
                      nsIRequest* aLoadingChannel);

  /**
   * Inspects the object and sets the following member variables:
   * - mOriginalContentType : This is the type attribute on the element
   * - mOriginalURI         : The src or data attribute on the element
   * - mURI                 : The final URI, considering mChannel if
   *                          mChannelLoaded is set
   * - mContentType         : The final content type, considering mChannel if
   *                          mChannelLoaded is set
   * - mBaseURI             : The object's base URI, which may be set by the
   *                          object
   * - mType                : The type the object is determined to be based
   *                          on the above
   *
   * NOTE The class assumes that mType is the currently loaded type at various
   *      points, so the caller of this function must take the appropriate
   *      actions to ensure this
   *
   * NOTE This function does not perform security checks, only determining the
   *      requested type and parameters of the object.
   *
   * @return Returns a bitmask of ParameterUpdateFlags values
   */
  ParameterUpdateFlags UpdateObjectParameters();

 public:
  bool IsAboutBlankLoadOntoInitialAboutBlank(nsIURI* aURI,
                                             bool aInheritPrincipal,
                                             nsIPrincipal* aPrincipalToInherit);

 private:
  /**
   * Opens the channel pointed to by mURI into mChannel.
   */
  nsresult OpenChannel();

  /**
   * Closes and releases references to mChannel and, if opened, mFinalListener
   */
  nsresult CloseChannel();

  /**
   * If this object should be tested against blocking list.
   */
  bool ShouldBlockContent();

  /**
   * This method tells the final answer on whether this object's fallback
   * content should be used instead of the original plugin content.
   *
   * @param aIsPluginClickToPlay Whether this object instance is CTP.
   */
  bool PreferFallback(bool aIsPluginClickToPlay);

  /**
   * Helper to check if our current URI passes policy
   *
   * @param aContentPolicy [out] The result of the content policy decision
   *
   * @return true if call succeeded and NS_CP_ACCEPTED(*aContentPolicy)
   */
  bool CheckLoadPolicy(int16_t* aContentPolicy);

  /**
   * Helper to check if the object passes process policy. Assumes we have a
   * final determined type.
   *
   * @param aContentPolicy [out] The result of the content policy decision
   *
   * @return true if call succeeded and NS_CP_ACCEPTED(*aContentPolicy)
   */
  bool CheckProcessPolicy(int16_t* aContentPolicy);

  void SetupFrameLoader(int32_t aJSPluginId);

  /**
   * Helper to spawn mFrameLoader and return a pointer to its docshell
   *
   * @param aURI URI we intend to load for the recursive load check (does not
   *             actually load anything)
   */
  already_AddRefed<nsIDocShell> SetupDocShell(nsIURI* aRecursionCheckURI);

  /**
   * Unloads all content and resets the object to a completely unloaded state
   *
   * NOTE Calls StopPluginInstance() and may spin the event loop
   *
   * @param aResetState Reset the object type to 'loading' and destroy channel
   *                    as well
   */
  void UnloadObject(bool aResetState = true);

  /**
   * Notifies document observes about a new type/state of this object.
   * Triggers frame construction as needed. mType must be set correctly when
   * this method is called. This method is cheap if the type and state didn't
   * actually change.
   *
   * @param aNotify if false, only need to update the state of our element.
   */
  void NotifyStateChanged(ObjectType aOldType,
                          mozilla::dom::ElementState aOldState, bool aNotify,
                          bool aForceRestyle);

  /**
   * Returns a ObjectType value corresponding to the type of content we would
   * support the given MIME type as, taking capabilities and plugin state
   * into account
   *
   * @param aNoFakePlugin Don't select a fake plugin handler as a valid type,
   *                      as when SkipFakePlugins() is called.
   * @return The ObjectType enum value that we would attempt to load
   *
   * NOTE this does not consider whether the content would be suppressed by
   *      click-to-play or other content policy checks
   */
  ObjectType GetTypeOfContent(const nsCString& aMIMEType, bool aNoFakePlugin);

  /**
   * Used for identifying whether we can rewrite a youtube flash embed to
   * possibly use HTML5 instead.
   *
   * Returns true if plugin.rewrite_youtube_embeds pref is true and the
   * element this nsObjectLoadingContent instance represents:
   *
   * - is an embed or object node
   * - has a URL pointing at the youtube.com domain, using "/v/" style video
   *   path reference.
   *
   * Having the enablejsapi flag means the document that contains the element
   * could possibly be manipulating the youtube video elsewhere on the page
   * via javascript. In the context of embed elements, this usage has been
   * deprecated by youtube, so we can just rewrite as normal.
   *
   * If we can rewrite the URL, we change the "/v/" to "/embed/", and change
   * our type to eType_Document so that we render similarly to an iframe
   * embed.
   */
  void MaybeRewriteYoutubeEmbed(nsIURI* aURI, nsIURI* aBaseURI,
                                nsIURI** aRewrittenURI);

  // Utility for firing an error event, if we're an <object>.
  void MaybeFireErrorEvent();

  /**
   * Store feature policy in container browsing context so that it can be
   * accessed cross process.
   */
  void MaybeStoreCrossOriginFeaturePolicy();

  // The final listener for mChannel (uriloader, pluginstreamlistener, etc.)
  nsCOMPtr<nsIStreamListener> mFinalListener;

  // The content type of our current load target, updated by
  // UpdateObjectParameters(). Takes the channel's type into account once
  // opened.
  //
  // May change if a channel is opened, does not imply a loaded state
  nsCString mContentType;

  // The content type 'hint' provided by the element's type attribute. May
  // or may not be used as a final type
  nsCString mOriginalContentType;

  // The channel that's currently being loaded. If set, but mChannelLoaded is
  // false, has not yet reached OnStartRequest
  nsCOMPtr<nsIChannel> mChannel;

  // The URI of the current content.
  // May change as we open channels and encounter redirects - does not imply
  // a loaded type
  nsCOMPtr<nsIURI> mURI;

  // The original URI obtained from inspecting the element. May differ from
  // mURI due to redirects
  nsCOMPtr<nsIURI> mOriginalURI;

  // The baseURI used for constructing mURI.
  nsCOMPtr<nsIURI> mBaseURI;

  // Type of the currently-loaded content.
  ObjectType mType : 8;

  uint32_t mRunID;
  bool mHasRunID : 1;

  // If true, we have opened a channel as the listener and it has reached
  // OnStartRequest. Does not get set for channels that are passed directly to
  // the plugin listener.
  bool mChannelLoaded : 1;

  // True when the object is created for an element which the parser has
  // created using NS_FROM_PARSER_NETWORK flag. If the element is modified,
  // it may lose the flag.
  bool mNetworkCreated : 1;

  // Whether content blocking is enabled or not for this object.
  bool mContentBlockingEnabled : 1;

  // If we should not use fake plugins until the next type change
  bool mSkipFakePlugins : 1;

  // Protects DoStopPlugin from reentry (bug 724781).
  bool mIsStopping : 1;

  // Protects LoadObject from re-entry
  bool mIsLoading : 1;

  // For plugin stand-in types (click-to-play) tracks whether content js has
  // tried to access the plugin script object.
  bool mScriptRequested : 1;

  // True if object represents an object/embed tag pointing to a flash embed
  // for a youtube video. When possible (see IsRewritableYoutubeEmbed function
  // comments for details), we change these to try to load HTML5 versions of
  // videos.
  bool mRewrittenYoutubeEmbed : 1;

  bool mLoadingSyntheticDocument : 1;

  nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
  nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;

  // The intrinsic size and aspect ratio from a child SVG document that
  // we should use.  These are only set when we are an <object> or <embed>
  // and the inner document is SVG.
  //
  // We store these here rather than on nsSubDocumentFrame since we are
  // sometimes notified of our child's intrinsics before we've constructed
  // our own frame.
  mozilla::Maybe<mozilla::IntrinsicSize> mSubdocumentIntrinsicSize;
  mozilla::Maybe<mozilla::AspectRatio> mSubdocumentIntrinsicRatio;
};

#endif
