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