1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MediaDocument.h"
10 #include "nsPresContext.h"
11 #include "nsViewManager.h"
12 #include "nsITextToSubURI.h"
14 #include "nsIDocShell.h"
15 #include "nsCharsetSource.h" // kCharsetFrom* macro definition
16 #include "nsNodeInfoManager.h"
17 #include "nsContentUtils.h"
18 #include "nsDocElementCreatedNotificationRunner.h"
19 #include "mozilla/Encoding.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/Components.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsIPrincipal.h"
24 #include "nsIMultiPartChannel.h"
25 #include "nsProxyRelease.h"
27 namespace mozilla::dom
{
29 MediaDocumentStreamListener::MediaDocumentStreamListener(
30 MediaDocument
* aDocument
)
31 : mDocument(aDocument
) {}
33 MediaDocumentStreamListener::~MediaDocumentStreamListener() {
34 if (mDocument
&& !NS_IsMainThread()) {
35 nsCOMPtr
<nsIEventTarget
> mainTarget(do_GetMainThread());
36 NS_ProxyRelease("MediaDocumentStreamListener::mDocument", mainTarget
,
41 NS_IMPL_ISUPPORTS(MediaDocumentStreamListener
, nsIRequestObserver
,
42 nsIStreamListener
, nsIThreadRetargetableStreamListener
)
45 MediaDocumentStreamListener::OnStartRequest(nsIRequest
* request
) {
46 NS_ENSURE_TRUE(mDocument
, NS_ERROR_FAILURE
);
48 mDocument
->StartLayout();
51 return mNextStream
->OnStartRequest(request
);
54 return NS_ERROR_PARSED_DATA_CACHED
;
58 MediaDocumentStreamListener::OnStopRequest(nsIRequest
* request
,
62 rv
= mNextStream
->OnStopRequest(request
, status
);
65 // Don't release mDocument here if we're in the middle of a multipart
68 nsCOMPtr
<nsIMultiPartChannel
> mpchan(do_QueryInterface(request
));
70 mpchan
->GetIsLastPart(&lastPart
);
80 MediaDocumentStreamListener::OnDataAvailable(nsIRequest
* request
,
81 nsIInputStream
* inStr
,
82 uint64_t sourceOffset
,
85 return mNextStream
->OnDataAvailable(request
, inStr
, sourceOffset
, count
);
92 MediaDocumentStreamListener::OnDataFinished(nsresult aStatus
) {
94 return NS_ERROR_FAILURE
;
96 nsCOMPtr
<nsIThreadRetargetableStreamListener
> retargetable
=
97 do_QueryInterface(mNextStream
);
99 return retargetable
->OnDataFinished(aStatus
);
106 MediaDocumentStreamListener::CheckListenerChain() {
107 nsCOMPtr
<nsIThreadRetargetableStreamListener
> retargetable
=
108 do_QueryInterface(mNextStream
);
110 return retargetable
->CheckListenerChain();
112 return NS_ERROR_NO_INTERFACE
;
115 // default format names for MediaDocument.
116 const char* const MediaDocument::sFormatNames
[4] = {
117 "MediaTitleWithNoInfo", // eWithNoInfo
118 "MediaTitleWithFile", // eWithFile
120 "" // eWithDimAndFile
123 MediaDocument::MediaDocument() : mDidInitialDocumentSetup(false) {
124 mCompatMode
= eCompatibility_FullStandards
;
126 MediaDocument::~MediaDocument() = default;
128 nsresult
MediaDocument::Init(nsIPrincipal
* aPrincipal
,
129 nsIPrincipal
* aPartitionedPrincipal
) {
130 nsresult rv
= nsHTMLDocument::Init(aPrincipal
, aPartitionedPrincipal
);
131 NS_ENSURE_SUCCESS(rv
, rv
);
133 mIsSyntheticDocument
= true;
138 nsresult
MediaDocument::StartDocumentLoad(
139 const char* aCommand
, nsIChannel
* aChannel
, nsILoadGroup
* aLoadGroup
,
140 nsISupports
* aContainer
, nsIStreamListener
** aDocListener
, bool aReset
) {
141 nsresult rv
= Document::StartDocumentLoad(aCommand
, aChannel
, aLoadGroup
,
142 aContainer
, aDocListener
, aReset
);
147 // We try to set the charset of the current document to that of the
148 // 'genuine' (as opposed to an intervening 'chrome') parent document
149 // that may be in a different window/tab. Even if we fail here,
150 // we just return NS_OK because another attempt is made in
151 // |UpdateTitleAndCharset| and the worst thing possible is a mangled
152 // filename in the titlebar and the file picker.
155 // exclude UTF-8 as 'invalid' because UTF-8 is likely to be the charset
156 // of a chrome document that has nothing to do with the actual content
157 // whose charset we want to know. Even if "the actual content" is indeed
158 // in UTF-8, we don't lose anything because the default empty value is
159 // considered synonymous with UTF-8.
161 nsCOMPtr
<nsIDocShell
> docShell(do_QueryInterface(aContainer
));
163 // not being able to set the charset is not critical.
164 NS_ENSURE_TRUE(docShell
, NS_OK
);
166 const Encoding
* encoding
;
168 nsCOMPtr
<nsIPrincipal
> principal
;
169 // opening in a new tab
170 docShell
->GetParentCharset(encoding
, &source
, getter_AddRefs(principal
));
172 if (encoding
&& encoding
!= UTF_8_ENCODING
&&
173 NodePrincipal()->Equals(principal
)) {
174 SetDocumentCharacterSetSource(source
);
175 SetDocumentCharacterSet(WrapNotNull(encoding
));
181 void MediaDocument::InitialSetupDone() {
182 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_LOADING
,
183 "Bad readyState: we should still be doing our initial load");
184 mDidInitialDocumentSetup
= true;
185 nsContentUtils::AddScriptRunner(
186 new nsDocElementCreatedNotificationRunner(this));
187 SetReadyStateInternal(Document::READYSTATE_INTERACTIVE
);
190 nsresult
MediaDocument::CreateSyntheticDocument() {
191 MOZ_ASSERT(!InitialSetupHasBeenDone());
193 // Synthesize an empty html document
195 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
;
196 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
197 nsGkAtoms::html
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
199 RefPtr
<nsGenericHTMLElement
> root
= NS_NewHTMLHtmlElement(nodeInfo
.forget());
200 NS_ENSURE_TRUE(root
, NS_ERROR_OUT_OF_MEMORY
);
202 NS_ASSERTION(GetChildCount() == 0, "Shouldn't have any kids");
204 AppendChildTo(root
, false, rv
);
206 return rv
.StealNSResult();
209 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
210 nsGkAtoms::head
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
212 // Create a <head> so our title has somewhere to live
213 RefPtr
<nsGenericHTMLElement
> head
= NS_NewHTMLHeadElement(nodeInfo
.forget());
214 NS_ENSURE_TRUE(head
, NS_ERROR_OUT_OF_MEMORY
);
216 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
217 nsGkAtoms::meta
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
219 RefPtr
<nsGenericHTMLElement
> metaContent
=
220 NS_NewHTMLMetaElement(nodeInfo
.forget());
221 NS_ENSURE_TRUE(metaContent
, NS_ERROR_OUT_OF_MEMORY
);
222 metaContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::name
, u
"viewport"_ns
,
225 metaContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::content
,
226 u
"width=device-width; height=device-height;"_ns
, true);
227 head
->AppendChildTo(metaContent
, false, IgnoreErrors());
229 root
->AppendChildTo(head
, false, IgnoreErrors());
231 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
232 nsGkAtoms::body
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
234 RefPtr
<nsGenericHTMLElement
> body
= NS_NewHTMLBodyElement(nodeInfo
.forget());
235 NS_ENSURE_TRUE(body
, NS_ERROR_OUT_OF_MEMORY
);
237 root
->AppendChildTo(body
, false, IgnoreErrors());
242 nsresult
MediaDocument::StartLayout() {
243 mMayStartLayout
= true;
244 RefPtr
<PresShell
> presShell
= GetPresShell();
245 // Don't mess with the presshell if someone has already handled
246 // its initial reflow.
247 if (presShell
&& !presShell
->DidInitialize()) {
248 nsresult rv
= presShell
->Initialize();
249 NS_ENSURE_SUCCESS(rv
, rv
);
255 void MediaDocument::GetFileName(nsAString
& aResult
, nsIChannel
* aChannel
) {
259 aChannel
->GetContentDispositionFilename(aResult
);
260 if (!aResult
.IsEmpty()) return;
263 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(mDocumentURI
);
266 nsAutoCString fileName
;
267 url
->GetFileName(fileName
);
268 if (fileName
.IsEmpty()) return;
270 // Now that the charset is set in |StartDocumentLoad| to the charset of
271 // the document viewer instead of a bogus value ("windows-1252" set in
272 // |Document|'s ctor), the priority is given to the current charset.
273 // This is necessary to deal with a media document being opened in a new
274 // window or a new tab.
275 if (mCharacterSetSource
== kCharsetUninitialized
) {
277 SetDocumentCharacterSet(UTF_8_ENCODING
);
281 nsCOMPtr
<nsITextToSubURI
> textToSubURI
=
282 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
283 if (NS_SUCCEEDED(rv
)) {
284 // UnEscapeURIForUI always succeeds
285 textToSubURI
->UnEscapeURIForUI(fileName
, aResult
);
287 CopyUTF8toUTF16(fileName
, aResult
);
291 nsresult
MediaDocument::LinkStylesheet(const nsAString
& aStylesheet
) {
292 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
;
293 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
294 nsGkAtoms::link
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
296 RefPtr
<nsGenericHTMLElement
> link
= NS_NewHTMLLinkElement(nodeInfo
.forget());
297 NS_ENSURE_TRUE(link
, NS_ERROR_OUT_OF_MEMORY
);
299 link
->SetAttr(kNameSpaceID_None
, nsGkAtoms::rel
, u
"stylesheet"_ns
, true);
301 link
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
, aStylesheet
, true);
304 Element
* head
= GetHeadElement();
305 head
->AppendChildTo(link
, false, rv
);
306 return rv
.StealNSResult();
309 nsresult
MediaDocument::LinkScript(const nsAString
& aScript
) {
310 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
;
311 nodeInfo
= mNodeInfoManager
->GetNodeInfo(
312 nsGkAtoms::script
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
314 RefPtr
<nsGenericHTMLElement
> script
=
315 NS_NewHTMLScriptElement(nodeInfo
.forget());
316 NS_ENSURE_TRUE(script
, NS_ERROR_OUT_OF_MEMORY
);
318 script
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
, u
"text/javascript"_ns
,
321 script
->SetAttr(kNameSpaceID_None
, nsGkAtoms::src
, aScript
, true);
324 Element
* head
= GetHeadElement();
325 head
->AppendChildTo(script
, false, rv
);
326 return rv
.StealNSResult();
329 void MediaDocument::FormatStringFromName(const char* aName
,
330 const nsTArray
<nsString
>& aParams
,
331 nsAString
& aResult
) {
332 bool spoofLocale
= nsContentUtils::SpoofLocaleEnglish() && !AllowsL10n();
334 if (!mStringBundle
) {
335 nsCOMPtr
<nsIStringBundleService
> stringService
=
336 mozilla::components::StringBundle::Service();
338 stringService
->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI
,
339 getter_AddRefs(mStringBundle
));
343 mStringBundle
->FormatStringFromName(aName
, aParams
, aResult
);
346 if (!mStringBundleEnglish
) {
347 nsCOMPtr
<nsIStringBundleService
> stringService
=
348 mozilla::components::StringBundle::Service();
350 stringService
->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI_en_US
,
351 getter_AddRefs(mStringBundleEnglish
));
354 if (mStringBundleEnglish
) {
355 mStringBundleEnglish
->FormatStringFromName(aName
, aParams
, aResult
);
360 void MediaDocument::UpdateTitleAndCharset(const nsACString
& aTypeStr
,
361 nsIChannel
* aChannel
,
362 const char* const* aFormatNames
,
363 int32_t aWidth
, int32_t aHeight
,
364 const nsAString
& aStatus
) {
365 nsAutoString fileStr
;
366 GetFileName(fileStr
, aChannel
);
368 NS_ConvertASCIItoUTF16
typeStr(aTypeStr
);
371 // if we got a valid size (not all media have a size)
372 if (aWidth
!= 0 && aHeight
!= 0) {
373 nsAutoString widthStr
;
374 nsAutoString heightStr
;
375 widthStr
.AppendInt(aWidth
);
376 heightStr
.AppendInt(aHeight
);
377 // If we got a filename, display it
378 if (!fileStr
.IsEmpty()) {
379 AutoTArray
<nsString
, 4> formatStrings
= {fileStr
, typeStr
, widthStr
,
381 FormatStringFromName(aFormatNames
[eWithDimAndFile
], formatStrings
, title
);
383 AutoTArray
<nsString
, 3> formatStrings
= {typeStr
, widthStr
, heightStr
};
384 FormatStringFromName(aFormatNames
[eWithDim
], formatStrings
, title
);
387 // If we got a filename, display it
388 if (!fileStr
.IsEmpty()) {
389 AutoTArray
<nsString
, 2> formatStrings
= {fileStr
, typeStr
};
390 FormatStringFromName(aFormatNames
[eWithFile
], formatStrings
, title
);
392 AutoTArray
<nsString
, 1> formatStrings
= {typeStr
};
393 FormatStringFromName(aFormatNames
[eWithNoInfo
], formatStrings
, title
);
397 // set it on the document
398 if (aStatus
.IsEmpty()) {
399 IgnoredErrorResult ignored
;
400 SetTitle(title
, ignored
);
402 nsAutoString titleWithStatus
;
403 AutoTArray
<nsString
, 2> formatStrings
;
404 formatStrings
.AppendElement(title
);
405 formatStrings
.AppendElement(aStatus
);
406 FormatStringFromName("TitleWithStatus", formatStrings
, titleWithStatus
);
407 SetTitle(titleWithStatus
, IgnoreErrors());
411 } // namespace mozilla::dom