michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MediaDocument.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsRect.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIScrollable.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsITextToSubURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIMarkupDocumentViewer.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsCharsetSource.h" // kCharsetFrom* macro definition michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDocElementCreatedNotificationRunner.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIPrincipal.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: MediaDocumentStreamListener::MediaDocumentStreamListener(MediaDocument *aDocument) michael@0: { michael@0: mDocument = aDocument; michael@0: } michael@0: michael@0: MediaDocumentStreamListener::~MediaDocumentStreamListener() michael@0: { michael@0: } michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(MediaDocumentStreamListener, michael@0: nsIRequestObserver, michael@0: nsIStreamListener) michael@0: michael@0: michael@0: void michael@0: MediaDocumentStreamListener::SetStreamListener(nsIStreamListener *aListener) michael@0: { michael@0: mNextStream = aListener; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: MediaDocumentStreamListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt) michael@0: { michael@0: NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); michael@0: michael@0: mDocument->StartLayout(); michael@0: michael@0: if (mNextStream) { michael@0: return mNextStream->OnStartRequest(request, ctxt); michael@0: } michael@0: michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: MediaDocumentStreamListener::OnStopRequest(nsIRequest* request, michael@0: nsISupports *ctxt, michael@0: nsresult status) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (mNextStream) { michael@0: rv = mNextStream->OnStopRequest(request, ctxt, status); michael@0: } michael@0: michael@0: // No more need for our document so clear our reference and prevent leaks michael@0: mDocument = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: MediaDocumentStreamListener::OnDataAvailable(nsIRequest* request, michael@0: nsISupports *ctxt, michael@0: nsIInputStream *inStr, michael@0: uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: if (mNextStream) { michael@0: return mNextStream->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // default format names for MediaDocument. michael@0: const char* const MediaDocument::sFormatNames[4] = michael@0: { michael@0: "MediaTitleWithNoInfo", // eWithNoInfo michael@0: "MediaTitleWithFile", // eWithFile michael@0: "", // eWithDim michael@0: "" // eWithDimAndFile michael@0: }; michael@0: michael@0: MediaDocument::MediaDocument() michael@0: : nsHTMLDocument(), michael@0: mDocumentElementInserted(false) michael@0: { michael@0: } michael@0: MediaDocument::~MediaDocument() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: MediaDocument::Init() michael@0: { michael@0: nsresult rv = nsHTMLDocument::Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create a bundle for the localization michael@0: nsCOMPtr stringService = michael@0: mozilla::services::GetStringBundleService(); michael@0: if (stringService) { michael@0: stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI, michael@0: getter_AddRefs(mStringBundle)); michael@0: } michael@0: michael@0: mIsSyntheticDocument = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MediaDocument::StartDocumentLoad(const char* aCommand, michael@0: nsIChannel* aChannel, michael@0: nsILoadGroup* aLoadGroup, michael@0: nsISupports* aContainer, michael@0: nsIStreamListener** aDocListener, michael@0: bool aReset, michael@0: nsIContentSink* aSink) michael@0: { michael@0: nsresult rv = nsDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, michael@0: aContainer, aDocListener, aReset, michael@0: aSink); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // We try to set the charset of the current document to that of the michael@0: // 'genuine' (as opposed to an intervening 'chrome') parent document michael@0: // that may be in a different window/tab. Even if we fail here, michael@0: // we just return NS_OK because another attempt is made in michael@0: // |UpdateTitleAndCharset| and the worst thing possible is a mangled michael@0: // filename in the titlebar and the file picker. michael@0: michael@0: // Note that we michael@0: // exclude UTF-8 as 'invalid' because UTF-8 is likely to be the charset michael@0: // of a chrome document that has nothing to do with the actual content michael@0: // whose charset we want to know. Even if "the actual content" is indeed michael@0: // in UTF-8, we don't lose anything because the default empty value is michael@0: // considered synonymous with UTF-8. michael@0: michael@0: nsCOMPtr docShell(do_QueryInterface(aContainer)); michael@0: michael@0: // not being able to set the charset is not critical. michael@0: NS_ENSURE_TRUE(docShell, NS_OK); michael@0: michael@0: nsAutoCString charset; michael@0: int32_t source; michael@0: nsCOMPtr principal; michael@0: // opening in a new tab michael@0: docShell->GetParentCharset(charset, &source, getter_AddRefs(principal)); michael@0: michael@0: if (!charset.IsEmpty() && michael@0: !charset.Equals("UTF-8") && michael@0: NodePrincipal()->Equals(principal)) { michael@0: SetDocumentCharacterSetSource(source); michael@0: SetDocumentCharacterSet(charset); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: MediaDocument::BecomeInteractive() michael@0: { michael@0: // In principle, if we knew the readyState code to work, we could infer michael@0: // restoration from GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE. michael@0: bool restoring = false; michael@0: nsPIDOMWindow* window = GetWindow(); michael@0: if (window) { michael@0: nsIDocShell* docShell = window->GetDocShell(); michael@0: if (docShell) { michael@0: docShell->GetRestoringDocument(&restoring); michael@0: } michael@0: } michael@0: if (!restoring) { michael@0: MOZ_ASSERT(GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING, michael@0: "Bad readyState"); michael@0: SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: MediaDocument::CreateSyntheticDocument() michael@0: { michael@0: // Synthesize an empty html document michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: nsRefPtr root = NS_NewHTMLHtmlElement(nodeInfo.forget()); michael@0: NS_ENSURE_TRUE(root, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: NS_ASSERTION(GetChildCount() == 0, "Shouldn't have any kids"); michael@0: rv = AppendChildTo(root, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::head, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: // Create a so our title has somewhere to live michael@0: nsRefPtr head = NS_NewHTMLHeadElement(nodeInfo.forget()); michael@0: NS_ENSURE_TRUE(head, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::meta, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: nsRefPtr metaContent = NS_NewHTMLMetaElement(nodeInfo.forget()); michael@0: NS_ENSURE_TRUE(metaContent, NS_ERROR_OUT_OF_MEMORY); michael@0: metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::name, michael@0: NS_LITERAL_STRING("viewport"), michael@0: true); michael@0: michael@0: metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::content, michael@0: NS_LITERAL_STRING("width=device-width; height=device-height;"), michael@0: true); michael@0: head->AppendChildTo(metaContent, false); michael@0: michael@0: root->AppendChildTo(head, false); michael@0: michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: nsRefPtr body = NS_NewHTMLBodyElement(nodeInfo.forget()); michael@0: NS_ENSURE_TRUE(body, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: root->AppendChildTo(body, false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MediaDocument::StartLayout() michael@0: { michael@0: mMayStartLayout = true; michael@0: nsCOMPtr shell = GetShell(); michael@0: // Don't mess with the presshell if someone has already handled michael@0: // its initial reflow. michael@0: if (shell && !shell->DidInitialize()) { michael@0: nsRect visibleArea = shell->GetPresContext()->GetVisibleArea(); michael@0: nsresult rv = shell->Initialize(visibleArea.width, visibleArea.height); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: MediaDocument::GetFileName(nsAString& aResult) michael@0: { michael@0: aResult.Truncate(); michael@0: michael@0: nsCOMPtr url = do_QueryInterface(mDocumentURI); michael@0: if (!url) michael@0: return; michael@0: michael@0: nsAutoCString fileName; michael@0: url->GetFileName(fileName); michael@0: if (fileName.IsEmpty()) michael@0: return; michael@0: michael@0: nsAutoCString docCharset; michael@0: // Now that the charset is set in |StartDocumentLoad| to the charset of michael@0: // the document viewer instead of a bogus value ("ISO-8859-1" set in michael@0: // |nsDocument|'s ctor), the priority is given to the current charset. michael@0: // This is necessary to deal with a media document being opened in a new michael@0: // window or a new tab, in which case |originCharset| of |nsIURI| is not michael@0: // reliable. michael@0: if (mCharacterSetSource != kCharsetUninitialized) { michael@0: docCharset = mCharacterSet; michael@0: } else { michael@0: // resort to |originCharset| michael@0: url->GetOriginCharset(docCharset); michael@0: SetDocumentCharacterSet(docCharset); michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr textToSubURI = michael@0: do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // UnEscapeURIForUI always succeeds michael@0: textToSubURI->UnEscapeURIForUI(docCharset, fileName, aResult); michael@0: } else { michael@0: CopyUTF8toUTF16(fileName, aResult); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: MediaDocument::LinkStylesheet(const nsAString& aStylesheet) michael@0: { michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::link, nullptr, michael@0: kNameSpaceID_XHTML, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: nsRefPtr link = NS_NewHTMLLinkElement(nodeInfo.forget()); michael@0: NS_ENSURE_TRUE(link, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: link->SetAttr(kNameSpaceID_None, nsGkAtoms::rel, michael@0: NS_LITERAL_STRING("stylesheet"), true); michael@0: michael@0: link->SetAttr(kNameSpaceID_None, nsGkAtoms::href, aStylesheet, true); michael@0: michael@0: Element* head = GetHeadElement(); michael@0: return head->AppendChildTo(link, false); michael@0: } michael@0: michael@0: void michael@0: MediaDocument::UpdateTitleAndCharset(const nsACString& aTypeStr, michael@0: const char* const* aFormatNames, michael@0: int32_t aWidth, int32_t aHeight, michael@0: const nsAString& aStatus) michael@0: { michael@0: nsXPIDLString fileStr; michael@0: GetFileName(fileStr); michael@0: michael@0: NS_ConvertASCIItoUTF16 typeStr(aTypeStr); michael@0: nsXPIDLString title; michael@0: michael@0: if (mStringBundle) { michael@0: // if we got a valid size (not all media have a size) michael@0: if (aWidth != 0 && aHeight != 0) { michael@0: nsAutoString widthStr; michael@0: nsAutoString heightStr; michael@0: widthStr.AppendInt(aWidth); michael@0: heightStr.AppendInt(aHeight); michael@0: // If we got a filename, display it michael@0: if (!fileStr.IsEmpty()) { michael@0: const char16_t *formatStrings[4] = {fileStr.get(), typeStr.get(), michael@0: widthStr.get(), heightStr.get()}; michael@0: NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDimAndFile]); michael@0: mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 4, michael@0: getter_Copies(title)); michael@0: } michael@0: else { michael@0: const char16_t *formatStrings[3] = {typeStr.get(), widthStr.get(), michael@0: heightStr.get()}; michael@0: NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDim]); michael@0: mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 3, michael@0: getter_Copies(title)); michael@0: } michael@0: } michael@0: else { michael@0: // If we got a filename, display it michael@0: if (!fileStr.IsEmpty()) { michael@0: const char16_t *formatStrings[2] = {fileStr.get(), typeStr.get()}; michael@0: NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithFile]); michael@0: mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2, michael@0: getter_Copies(title)); michael@0: } michael@0: else { michael@0: const char16_t *formatStrings[1] = {typeStr.get()}; michael@0: NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithNoInfo]); michael@0: mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 1, michael@0: getter_Copies(title)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // set it on the document michael@0: if (aStatus.IsEmpty()) { michael@0: SetTitle(title); michael@0: } michael@0: else { michael@0: nsXPIDLString titleWithStatus; michael@0: const nsPromiseFlatString& status = PromiseFlatString(aStatus); michael@0: const char16_t *formatStrings[2] = {title.get(), status.get()}; michael@0: NS_NAMED_LITERAL_STRING(fmtName, "TitleWithStatus"); michael@0: mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2, michael@0: getter_Copies(titleWithStatus)); michael@0: SetTitle(titleWithStatus); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) michael@0: { michael@0: nsHTMLDocument::SetScriptGlobalObject(aGlobalObject); michael@0: if (!mDocumentElementInserted && aGlobalObject) { michael@0: mDocumentElementInserted = true; michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsDocElementCreatedNotificationRunner(this)); michael@0: } michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla