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/. */
6 
7 #include "MediaDocument.h"
8 #include "nsGkAtoms.h"
9 #include "nsRect.h"
10 #include "nsPresContext.h"
11 #include "nsViewManager.h"
12 #include "nsITextToSubURI.h"
13 #include "nsIURL.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"
26 
27 namespace mozilla::dom {
28 
MediaDocumentStreamListener(MediaDocument * aDocument)29 MediaDocumentStreamListener::MediaDocumentStreamListener(
30     MediaDocument* aDocument)
31     : mDocument(aDocument) {}
32 
~MediaDocumentStreamListener()33 MediaDocumentStreamListener::~MediaDocumentStreamListener() {
34   if (mDocument && !NS_IsMainThread()) {
35     nsCOMPtr<nsIEventTarget> mainTarget(do_GetMainThread());
36     NS_ProxyRelease("MediaDocumentStreamListener::mDocument", mainTarget,
37                     mDocument.forget());
38   }
39 }
40 
NS_IMPL_ISUPPORTS(MediaDocumentStreamListener,nsIRequestObserver,nsIStreamListener,nsIThreadRetargetableStreamListener)41 NS_IMPL_ISUPPORTS(MediaDocumentStreamListener, nsIRequestObserver,
42                   nsIStreamListener, nsIThreadRetargetableStreamListener)
43 
44 void MediaDocumentStreamListener::SetStreamListener(
45     nsIStreamListener* aListener) {
46   mNextStream = aListener;
47 }
48 
49 NS_IMETHODIMP
OnStartRequest(nsIRequest * request)50 MediaDocumentStreamListener::OnStartRequest(nsIRequest* request) {
51   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
52 
53   mDocument->StartLayout();
54 
55   if (mNextStream) {
56     return mNextStream->OnStartRequest(request);
57   }
58 
59   return NS_ERROR_PARSED_DATA_CACHED;
60 }
61 
62 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsresult status)63 MediaDocumentStreamListener::OnStopRequest(nsIRequest* request,
64                                            nsresult status) {
65   nsresult rv = NS_OK;
66   if (mNextStream) {
67     rv = mNextStream->OnStopRequest(request, status);
68   }
69 
70   // Don't release mDocument here if we're in the middle of a multipart
71   // response.
72   bool lastPart = true;
73   nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(request));
74   if (mpchan) {
75     mpchan->GetIsLastPart(&lastPart);
76   }
77 
78   if (lastPart) {
79     mDocument = nullptr;
80   }
81   return rv;
82 }
83 
84 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)85 MediaDocumentStreamListener::OnDataAvailable(nsIRequest* request,
86                                              nsIInputStream* inStr,
87                                              uint64_t sourceOffset,
88                                              uint32_t count) {
89   if (mNextStream) {
90     return mNextStream->OnDataAvailable(request, inStr, sourceOffset, count);
91   }
92 
93   return NS_OK;
94 }
95 
96 NS_IMETHODIMP
CheckListenerChain()97 MediaDocumentStreamListener::CheckListenerChain() {
98   nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
99       do_QueryInterface(mNextStream);
100   if (retargetable) {
101     return retargetable->CheckListenerChain();
102   }
103   return NS_ERROR_NO_INTERFACE;
104 }
105 
106 // default format names for MediaDocument.
107 const char* const MediaDocument::sFormatNames[4] = {
108     "MediaTitleWithNoInfo",  // eWithNoInfo
109     "MediaTitleWithFile",    // eWithFile
110     "",                      // eWithDim
111     ""                       // eWithDimAndFile
112 };
113 
MediaDocument()114 MediaDocument::MediaDocument()
115     : nsHTMLDocument(), mDidInitialDocumentSetup(false) {
116   mCompatMode = eCompatibility_FullStandards;
117 }
118 MediaDocument::~MediaDocument() = default;
119 
Init()120 nsresult MediaDocument::Init() {
121   nsresult rv = nsHTMLDocument::Init();
122   NS_ENSURE_SUCCESS(rv, rv);
123 
124   mIsSyntheticDocument = true;
125 
126   return NS_OK;
127 }
128 
StartDocumentLoad(const char * aCommand,nsIChannel * aChannel,nsILoadGroup * aLoadGroup,nsISupports * aContainer,nsIStreamListener ** aDocListener,bool aReset)129 nsresult MediaDocument::StartDocumentLoad(
130     const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup,
131     nsISupports* aContainer, nsIStreamListener** aDocListener, bool aReset) {
132   nsresult rv = Document::StartDocumentLoad(aCommand, aChannel, aLoadGroup,
133                                             aContainer, aDocListener, aReset);
134   if (NS_FAILED(rv)) {
135     return rv;
136   }
137 
138   // We try to set the charset of the current document to that of the
139   // 'genuine' (as opposed to an intervening 'chrome') parent document
140   // that may be in a different window/tab. Even if we fail here,
141   // we just return NS_OK because another attempt is made in
142   // |UpdateTitleAndCharset| and the worst thing possible is a mangled
143   // filename in the titlebar and the file picker.
144 
145   // Note that we
146   // exclude UTF-8 as 'invalid' because UTF-8 is likely to be the charset
147   // of a chrome document that has nothing to do with the actual content
148   // whose charset we want to know. Even if "the actual content" is indeed
149   // in UTF-8, we don't lose anything because the default empty value is
150   // considered synonymous with UTF-8.
151 
152   nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
153 
154   // not being able to set the charset is not critical.
155   NS_ENSURE_TRUE(docShell, NS_OK);
156 
157   const Encoding* encoding;
158   int32_t source;
159   nsCOMPtr<nsIPrincipal> principal;
160   // opening in a new tab
161   docShell->GetParentCharset(encoding, &source, getter_AddRefs(principal));
162 
163   if (encoding && encoding != UTF_8_ENCODING &&
164       NodePrincipal()->Equals(principal)) {
165     SetDocumentCharacterSetSource(source);
166     SetDocumentCharacterSet(WrapNotNull(encoding));
167   }
168 
169   return NS_OK;
170 }
171 
InitialSetupDone()172 void MediaDocument::InitialSetupDone() {
173   MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_LOADING,
174              "Bad readyState: we should still be doing our initial load");
175   mDidInitialDocumentSetup = true;
176   nsContentUtils::AddScriptRunner(
177       new nsDocElementCreatedNotificationRunner(this));
178   SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
179 }
180 
CreateSyntheticDocument()181 nsresult MediaDocument::CreateSyntheticDocument() {
182   MOZ_ASSERT(!InitialSetupHasBeenDone());
183 
184   // Synthesize an empty html document
185 
186   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
187   nodeInfo = mNodeInfoManager->GetNodeInfo(
188       nsGkAtoms::html, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
189 
190   RefPtr<nsGenericHTMLElement> root = NS_NewHTMLHtmlElement(nodeInfo.forget());
191   NS_ENSURE_TRUE(root, NS_ERROR_OUT_OF_MEMORY);
192 
193   NS_ASSERTION(GetChildCount() == 0, "Shouldn't have any kids");
194   ErrorResult rv;
195   AppendChildTo(root, false, rv);
196   if (rv.Failed()) {
197     return rv.StealNSResult();
198   }
199 
200   nodeInfo = mNodeInfoManager->GetNodeInfo(
201       nsGkAtoms::head, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
202 
203   // Create a <head> so our title has somewhere to live
204   RefPtr<nsGenericHTMLElement> head = NS_NewHTMLHeadElement(nodeInfo.forget());
205   NS_ENSURE_TRUE(head, NS_ERROR_OUT_OF_MEMORY);
206 
207   nodeInfo = mNodeInfoManager->GetNodeInfo(
208       nsGkAtoms::meta, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
209 
210   RefPtr<nsGenericHTMLElement> metaContent =
211       NS_NewHTMLMetaElement(nodeInfo.forget());
212   NS_ENSURE_TRUE(metaContent, NS_ERROR_OUT_OF_MEMORY);
213   metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::name, u"viewport"_ns,
214                        true);
215 
216   metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
217                        u"width=device-width; height=device-height;"_ns, true);
218   head->AppendChildTo(metaContent, false, IgnoreErrors());
219 
220   root->AppendChildTo(head, false, IgnoreErrors());
221 
222   nodeInfo = mNodeInfoManager->GetNodeInfo(
223       nsGkAtoms::body, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
224 
225   RefPtr<nsGenericHTMLElement> body = NS_NewHTMLBodyElement(nodeInfo.forget());
226   NS_ENSURE_TRUE(body, NS_ERROR_OUT_OF_MEMORY);
227 
228   root->AppendChildTo(body, false, IgnoreErrors());
229 
230   return NS_OK;
231 }
232 
StartLayout()233 nsresult MediaDocument::StartLayout() {
234   mMayStartLayout = true;
235   RefPtr<PresShell> presShell = GetPresShell();
236   // Don't mess with the presshell if someone has already handled
237   // its initial reflow.
238   if (presShell && !presShell->DidInitialize()) {
239     nsresult rv = presShell->Initialize();
240     NS_ENSURE_SUCCESS(rv, rv);
241   }
242 
243   return NS_OK;
244 }
245 
GetFileName(nsAString & aResult,nsIChannel * aChannel)246 void MediaDocument::GetFileName(nsAString& aResult, nsIChannel* aChannel) {
247   aResult.Truncate();
248 
249   if (aChannel) {
250     aChannel->GetContentDispositionFilename(aResult);
251     if (!aResult.IsEmpty()) return;
252   }
253 
254   nsCOMPtr<nsIURL> url = do_QueryInterface(mDocumentURI);
255   if (!url) return;
256 
257   nsAutoCString fileName;
258   url->GetFileName(fileName);
259   if (fileName.IsEmpty()) return;
260 
261   // Now that the charset is set in |StartDocumentLoad| to the charset of
262   // the document viewer instead of a bogus value ("windows-1252" set in
263   // |Document|'s ctor), the priority is given to the current charset.
264   // This is necessary to deal with a media document being opened in a new
265   // window or a new tab.
266   if (mCharacterSetSource == kCharsetUninitialized) {
267     // resort to UTF-8
268     SetDocumentCharacterSet(UTF_8_ENCODING);
269   }
270 
271   nsresult rv;
272   nsCOMPtr<nsITextToSubURI> textToSubURI =
273       do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
274   if (NS_SUCCEEDED(rv)) {
275     // UnEscapeURIForUI always succeeds
276     textToSubURI->UnEscapeURIForUI(fileName, aResult);
277   } else {
278     CopyUTF8toUTF16(fileName, aResult);
279   }
280 }
281 
LinkStylesheet(const nsAString & aStylesheet)282 nsresult MediaDocument::LinkStylesheet(const nsAString& aStylesheet) {
283   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
284   nodeInfo = mNodeInfoManager->GetNodeInfo(
285       nsGkAtoms::link, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
286 
287   RefPtr<nsGenericHTMLElement> link = NS_NewHTMLLinkElement(nodeInfo.forget());
288   NS_ENSURE_TRUE(link, NS_ERROR_OUT_OF_MEMORY);
289 
290   link->SetAttr(kNameSpaceID_None, nsGkAtoms::rel, u"stylesheet"_ns, true);
291 
292   link->SetAttr(kNameSpaceID_None, nsGkAtoms::href, aStylesheet, true);
293 
294   ErrorResult rv;
295   Element* head = GetHeadElement();
296   head->AppendChildTo(link, false, rv);
297   return rv.StealNSResult();
298 }
299 
LinkScript(const nsAString & aScript)300 nsresult MediaDocument::LinkScript(const nsAString& aScript) {
301   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
302   nodeInfo = mNodeInfoManager->GetNodeInfo(
303       nsGkAtoms::script, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
304 
305   RefPtr<nsGenericHTMLElement> script =
306       NS_NewHTMLScriptElement(nodeInfo.forget());
307   NS_ENSURE_TRUE(script, NS_ERROR_OUT_OF_MEMORY);
308 
309   script->SetAttr(kNameSpaceID_None, nsGkAtoms::type, u"text/javascript"_ns,
310                   true);
311 
312   script->SetAttr(kNameSpaceID_None, nsGkAtoms::src, aScript, true);
313 
314   ErrorResult rv;
315   Element* head = GetHeadElement();
316   head->AppendChildTo(script, false, rv);
317   return rv.StealNSResult();
318 }
319 
FormatStringFromName(const char * aName,const nsTArray<nsString> & aParams,nsAString & aResult)320 void MediaDocument::FormatStringFromName(const char* aName,
321                                          const nsTArray<nsString>& aParams,
322                                          nsAString& aResult) {
323   bool spoofLocale = nsContentUtils::SpoofLocaleEnglish() && !AllowsL10n();
324   if (!spoofLocale) {
325     if (!mStringBundle) {
326       nsCOMPtr<nsIStringBundleService> stringService =
327           mozilla::components::StringBundle::Service();
328       if (stringService) {
329         stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI,
330                                     getter_AddRefs(mStringBundle));
331       }
332     }
333     if (mStringBundle) {
334       mStringBundle->FormatStringFromName(aName, aParams, aResult);
335     }
336   } else {
337     if (!mStringBundleEnglish) {
338       nsCOMPtr<nsIStringBundleService> stringService =
339           mozilla::components::StringBundle::Service();
340       if (stringService) {
341         stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI_en_US,
342                                     getter_AddRefs(mStringBundleEnglish));
343       }
344     }
345     if (mStringBundleEnglish) {
346       mStringBundleEnglish->FormatStringFromName(aName, aParams, aResult);
347     }
348   }
349 }
350 
UpdateTitleAndCharset(const nsACString & aTypeStr,nsIChannel * aChannel,const char * const * aFormatNames,int32_t aWidth,int32_t aHeight,const nsAString & aStatus)351 void MediaDocument::UpdateTitleAndCharset(const nsACString& aTypeStr,
352                                           nsIChannel* aChannel,
353                                           const char* const* aFormatNames,
354                                           int32_t aWidth, int32_t aHeight,
355                                           const nsAString& aStatus) {
356   nsAutoString fileStr;
357   GetFileName(fileStr, aChannel);
358 
359   NS_ConvertASCIItoUTF16 typeStr(aTypeStr);
360   nsAutoString title;
361 
362   // if we got a valid size (not all media have a size)
363   if (aWidth != 0 && aHeight != 0) {
364     nsAutoString widthStr;
365     nsAutoString heightStr;
366     widthStr.AppendInt(aWidth);
367     heightStr.AppendInt(aHeight);
368     // If we got a filename, display it
369     if (!fileStr.IsEmpty()) {
370       AutoTArray<nsString, 4> formatStrings = {fileStr, typeStr, widthStr,
371                                                heightStr};
372       FormatStringFromName(aFormatNames[eWithDimAndFile], formatStrings, title);
373     } else {
374       AutoTArray<nsString, 3> formatStrings = {typeStr, widthStr, heightStr};
375       FormatStringFromName(aFormatNames[eWithDim], formatStrings, title);
376     }
377   } else {
378     // If we got a filename, display it
379     if (!fileStr.IsEmpty()) {
380       AutoTArray<nsString, 2> formatStrings = {fileStr, typeStr};
381       FormatStringFromName(aFormatNames[eWithFile], formatStrings, title);
382     } else {
383       AutoTArray<nsString, 1> formatStrings = {typeStr};
384       FormatStringFromName(aFormatNames[eWithNoInfo], formatStrings, title);
385     }
386   }
387 
388   // set it on the document
389   if (aStatus.IsEmpty()) {
390     IgnoredErrorResult ignored;
391     SetTitle(title, ignored);
392   } else {
393     nsAutoString titleWithStatus;
394     AutoTArray<nsString, 2> formatStrings;
395     formatStrings.AppendElement(title);
396     formatStrings.AppendElement(aStatus);
397     FormatStringFromName("TitleWithStatus", formatStrings, titleWithStatus);
398     SetTitle(titleWithStatus, IgnoreErrors());
399   }
400 }
401 
402 }  // namespace mozilla::dom
403