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