|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "MediaDocument.h" |
|
7 #include "nsGkAtoms.h" |
|
8 #include "nsRect.h" |
|
9 #include "nsPresContext.h" |
|
10 #include "nsIPresShell.h" |
|
11 #include "nsIScrollable.h" |
|
12 #include "nsViewManager.h" |
|
13 #include "nsITextToSubURI.h" |
|
14 #include "nsIURL.h" |
|
15 #include "nsIContentViewer.h" |
|
16 #include "nsIMarkupDocumentViewer.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 |
|
26 namespace mozilla { |
|
27 namespace dom { |
|
28 |
|
29 MediaDocumentStreamListener::MediaDocumentStreamListener(MediaDocument *aDocument) |
|
30 { |
|
31 mDocument = aDocument; |
|
32 } |
|
33 |
|
34 MediaDocumentStreamListener::~MediaDocumentStreamListener() |
|
35 { |
|
36 } |
|
37 |
|
38 |
|
39 NS_IMPL_ISUPPORTS(MediaDocumentStreamListener, |
|
40 nsIRequestObserver, |
|
41 nsIStreamListener) |
|
42 |
|
43 |
|
44 void |
|
45 MediaDocumentStreamListener::SetStreamListener(nsIStreamListener *aListener) |
|
46 { |
|
47 mNextStream = aListener; |
|
48 } |
|
49 |
|
50 NS_IMETHODIMP |
|
51 MediaDocumentStreamListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt) |
|
52 { |
|
53 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); |
|
54 |
|
55 mDocument->StartLayout(); |
|
56 |
|
57 if (mNextStream) { |
|
58 return mNextStream->OnStartRequest(request, ctxt); |
|
59 } |
|
60 |
|
61 return NS_BINDING_ABORTED; |
|
62 } |
|
63 |
|
64 NS_IMETHODIMP |
|
65 MediaDocumentStreamListener::OnStopRequest(nsIRequest* request, |
|
66 nsISupports *ctxt, |
|
67 nsresult status) |
|
68 { |
|
69 nsresult rv = NS_OK; |
|
70 if (mNextStream) { |
|
71 rv = mNextStream->OnStopRequest(request, ctxt, status); |
|
72 } |
|
73 |
|
74 // No more need for our document so clear our reference and prevent leaks |
|
75 mDocument = nullptr; |
|
76 |
|
77 return rv; |
|
78 } |
|
79 |
|
80 NS_IMETHODIMP |
|
81 MediaDocumentStreamListener::OnDataAvailable(nsIRequest* request, |
|
82 nsISupports *ctxt, |
|
83 nsIInputStream *inStr, |
|
84 uint64_t sourceOffset, |
|
85 uint32_t count) |
|
86 { |
|
87 if (mNextStream) { |
|
88 return mNextStream->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); |
|
89 } |
|
90 |
|
91 return NS_OK; |
|
92 } |
|
93 |
|
94 // default format names for MediaDocument. |
|
95 const char* const MediaDocument::sFormatNames[4] = |
|
96 { |
|
97 "MediaTitleWithNoInfo", // eWithNoInfo |
|
98 "MediaTitleWithFile", // eWithFile |
|
99 "", // eWithDim |
|
100 "" // eWithDimAndFile |
|
101 }; |
|
102 |
|
103 MediaDocument::MediaDocument() |
|
104 : nsHTMLDocument(), |
|
105 mDocumentElementInserted(false) |
|
106 { |
|
107 } |
|
108 MediaDocument::~MediaDocument() |
|
109 { |
|
110 } |
|
111 |
|
112 nsresult |
|
113 MediaDocument::Init() |
|
114 { |
|
115 nsresult rv = nsHTMLDocument::Init(); |
|
116 NS_ENSURE_SUCCESS(rv, rv); |
|
117 |
|
118 // Create a bundle for the localization |
|
119 nsCOMPtr<nsIStringBundleService> stringService = |
|
120 mozilla::services::GetStringBundleService(); |
|
121 if (stringService) { |
|
122 stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI, |
|
123 getter_AddRefs(mStringBundle)); |
|
124 } |
|
125 |
|
126 mIsSyntheticDocument = true; |
|
127 |
|
128 return NS_OK; |
|
129 } |
|
130 |
|
131 nsresult |
|
132 MediaDocument::StartDocumentLoad(const char* aCommand, |
|
133 nsIChannel* aChannel, |
|
134 nsILoadGroup* aLoadGroup, |
|
135 nsISupports* aContainer, |
|
136 nsIStreamListener** aDocListener, |
|
137 bool aReset, |
|
138 nsIContentSink* aSink) |
|
139 { |
|
140 nsresult rv = nsDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, |
|
141 aContainer, aDocListener, aReset, |
|
142 aSink); |
|
143 if (NS_FAILED(rv)) { |
|
144 return rv; |
|
145 } |
|
146 |
|
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. |
|
153 |
|
154 // Note that we |
|
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. |
|
160 |
|
161 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer)); |
|
162 |
|
163 // not being able to set the charset is not critical. |
|
164 NS_ENSURE_TRUE(docShell, NS_OK); |
|
165 |
|
166 nsAutoCString charset; |
|
167 int32_t source; |
|
168 nsCOMPtr<nsIPrincipal> principal; |
|
169 // opening in a new tab |
|
170 docShell->GetParentCharset(charset, &source, getter_AddRefs(principal)); |
|
171 |
|
172 if (!charset.IsEmpty() && |
|
173 !charset.Equals("UTF-8") && |
|
174 NodePrincipal()->Equals(principal)) { |
|
175 SetDocumentCharacterSetSource(source); |
|
176 SetDocumentCharacterSet(charset); |
|
177 } |
|
178 |
|
179 return NS_OK; |
|
180 } |
|
181 |
|
182 void |
|
183 MediaDocument::BecomeInteractive() |
|
184 { |
|
185 // In principle, if we knew the readyState code to work, we could infer |
|
186 // restoration from GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE. |
|
187 bool restoring = false; |
|
188 nsPIDOMWindow* window = GetWindow(); |
|
189 if (window) { |
|
190 nsIDocShell* docShell = window->GetDocShell(); |
|
191 if (docShell) { |
|
192 docShell->GetRestoringDocument(&restoring); |
|
193 } |
|
194 } |
|
195 if (!restoring) { |
|
196 MOZ_ASSERT(GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING, |
|
197 "Bad readyState"); |
|
198 SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); |
|
199 } |
|
200 } |
|
201 |
|
202 nsresult |
|
203 MediaDocument::CreateSyntheticDocument() |
|
204 { |
|
205 // Synthesize an empty html document |
|
206 nsresult rv; |
|
207 |
|
208 nsCOMPtr<nsINodeInfo> nodeInfo; |
|
209 nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr, |
|
210 kNameSpaceID_XHTML, |
|
211 nsIDOMNode::ELEMENT_NODE); |
|
212 |
|
213 nsRefPtr<nsGenericHTMLElement> root = NS_NewHTMLHtmlElement(nodeInfo.forget()); |
|
214 NS_ENSURE_TRUE(root, NS_ERROR_OUT_OF_MEMORY); |
|
215 |
|
216 NS_ASSERTION(GetChildCount() == 0, "Shouldn't have any kids"); |
|
217 rv = AppendChildTo(root, false); |
|
218 NS_ENSURE_SUCCESS(rv, rv); |
|
219 |
|
220 nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::head, nullptr, |
|
221 kNameSpaceID_XHTML, |
|
222 nsIDOMNode::ELEMENT_NODE); |
|
223 |
|
224 // Create a <head> so our title has somewhere to live |
|
225 nsRefPtr<nsGenericHTMLElement> head = NS_NewHTMLHeadElement(nodeInfo.forget()); |
|
226 NS_ENSURE_TRUE(head, NS_ERROR_OUT_OF_MEMORY); |
|
227 |
|
228 nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::meta, nullptr, |
|
229 kNameSpaceID_XHTML, |
|
230 nsIDOMNode::ELEMENT_NODE); |
|
231 |
|
232 nsRefPtr<nsGenericHTMLElement> metaContent = NS_NewHTMLMetaElement(nodeInfo.forget()); |
|
233 NS_ENSURE_TRUE(metaContent, NS_ERROR_OUT_OF_MEMORY); |
|
234 metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::name, |
|
235 NS_LITERAL_STRING("viewport"), |
|
236 true); |
|
237 |
|
238 metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::content, |
|
239 NS_LITERAL_STRING("width=device-width; height=device-height;"), |
|
240 true); |
|
241 head->AppendChildTo(metaContent, false); |
|
242 |
|
243 root->AppendChildTo(head, false); |
|
244 |
|
245 nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr, |
|
246 kNameSpaceID_XHTML, |
|
247 nsIDOMNode::ELEMENT_NODE); |
|
248 |
|
249 nsRefPtr<nsGenericHTMLElement> body = NS_NewHTMLBodyElement(nodeInfo.forget()); |
|
250 NS_ENSURE_TRUE(body, NS_ERROR_OUT_OF_MEMORY); |
|
251 |
|
252 root->AppendChildTo(body, false); |
|
253 |
|
254 return NS_OK; |
|
255 } |
|
256 |
|
257 nsresult |
|
258 MediaDocument::StartLayout() |
|
259 { |
|
260 mMayStartLayout = true; |
|
261 nsCOMPtr<nsIPresShell> shell = GetShell(); |
|
262 // Don't mess with the presshell if someone has already handled |
|
263 // its initial reflow. |
|
264 if (shell && !shell->DidInitialize()) { |
|
265 nsRect visibleArea = shell->GetPresContext()->GetVisibleArea(); |
|
266 nsresult rv = shell->Initialize(visibleArea.width, visibleArea.height); |
|
267 NS_ENSURE_SUCCESS(rv, rv); |
|
268 } |
|
269 |
|
270 return NS_OK; |
|
271 } |
|
272 |
|
273 void |
|
274 MediaDocument::GetFileName(nsAString& aResult) |
|
275 { |
|
276 aResult.Truncate(); |
|
277 |
|
278 nsCOMPtr<nsIURL> url = do_QueryInterface(mDocumentURI); |
|
279 if (!url) |
|
280 return; |
|
281 |
|
282 nsAutoCString fileName; |
|
283 url->GetFileName(fileName); |
|
284 if (fileName.IsEmpty()) |
|
285 return; |
|
286 |
|
287 nsAutoCString docCharset; |
|
288 // Now that the charset is set in |StartDocumentLoad| to the charset of |
|
289 // the document viewer instead of a bogus value ("ISO-8859-1" set in |
|
290 // |nsDocument|'s ctor), the priority is given to the current charset. |
|
291 // This is necessary to deal with a media document being opened in a new |
|
292 // window or a new tab, in which case |originCharset| of |nsIURI| is not |
|
293 // reliable. |
|
294 if (mCharacterSetSource != kCharsetUninitialized) { |
|
295 docCharset = mCharacterSet; |
|
296 } else { |
|
297 // resort to |originCharset| |
|
298 url->GetOriginCharset(docCharset); |
|
299 SetDocumentCharacterSet(docCharset); |
|
300 } |
|
301 |
|
302 nsresult rv; |
|
303 nsCOMPtr<nsITextToSubURI> textToSubURI = |
|
304 do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); |
|
305 if (NS_SUCCEEDED(rv)) { |
|
306 // UnEscapeURIForUI always succeeds |
|
307 textToSubURI->UnEscapeURIForUI(docCharset, fileName, aResult); |
|
308 } else { |
|
309 CopyUTF8toUTF16(fileName, aResult); |
|
310 } |
|
311 } |
|
312 |
|
313 nsresult |
|
314 MediaDocument::LinkStylesheet(const nsAString& aStylesheet) |
|
315 { |
|
316 nsCOMPtr<nsINodeInfo> nodeInfo; |
|
317 nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::link, nullptr, |
|
318 kNameSpaceID_XHTML, |
|
319 nsIDOMNode::ELEMENT_NODE); |
|
320 |
|
321 nsRefPtr<nsGenericHTMLElement> link = NS_NewHTMLLinkElement(nodeInfo.forget()); |
|
322 NS_ENSURE_TRUE(link, NS_ERROR_OUT_OF_MEMORY); |
|
323 |
|
324 link->SetAttr(kNameSpaceID_None, nsGkAtoms::rel, |
|
325 NS_LITERAL_STRING("stylesheet"), true); |
|
326 |
|
327 link->SetAttr(kNameSpaceID_None, nsGkAtoms::href, aStylesheet, true); |
|
328 |
|
329 Element* head = GetHeadElement(); |
|
330 return head->AppendChildTo(link, false); |
|
331 } |
|
332 |
|
333 void |
|
334 MediaDocument::UpdateTitleAndCharset(const nsACString& aTypeStr, |
|
335 const char* const* aFormatNames, |
|
336 int32_t aWidth, int32_t aHeight, |
|
337 const nsAString& aStatus) |
|
338 { |
|
339 nsXPIDLString fileStr; |
|
340 GetFileName(fileStr); |
|
341 |
|
342 NS_ConvertASCIItoUTF16 typeStr(aTypeStr); |
|
343 nsXPIDLString title; |
|
344 |
|
345 if (mStringBundle) { |
|
346 // if we got a valid size (not all media have a size) |
|
347 if (aWidth != 0 && aHeight != 0) { |
|
348 nsAutoString widthStr; |
|
349 nsAutoString heightStr; |
|
350 widthStr.AppendInt(aWidth); |
|
351 heightStr.AppendInt(aHeight); |
|
352 // If we got a filename, display it |
|
353 if (!fileStr.IsEmpty()) { |
|
354 const char16_t *formatStrings[4] = {fileStr.get(), typeStr.get(), |
|
355 widthStr.get(), heightStr.get()}; |
|
356 NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDimAndFile]); |
|
357 mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 4, |
|
358 getter_Copies(title)); |
|
359 } |
|
360 else { |
|
361 const char16_t *formatStrings[3] = {typeStr.get(), widthStr.get(), |
|
362 heightStr.get()}; |
|
363 NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDim]); |
|
364 mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 3, |
|
365 getter_Copies(title)); |
|
366 } |
|
367 } |
|
368 else { |
|
369 // If we got a filename, display it |
|
370 if (!fileStr.IsEmpty()) { |
|
371 const char16_t *formatStrings[2] = {fileStr.get(), typeStr.get()}; |
|
372 NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithFile]); |
|
373 mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2, |
|
374 getter_Copies(title)); |
|
375 } |
|
376 else { |
|
377 const char16_t *formatStrings[1] = {typeStr.get()}; |
|
378 NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithNoInfo]); |
|
379 mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 1, |
|
380 getter_Copies(title)); |
|
381 } |
|
382 } |
|
383 } |
|
384 |
|
385 // set it on the document |
|
386 if (aStatus.IsEmpty()) { |
|
387 SetTitle(title); |
|
388 } |
|
389 else { |
|
390 nsXPIDLString titleWithStatus; |
|
391 const nsPromiseFlatString& status = PromiseFlatString(aStatus); |
|
392 const char16_t *formatStrings[2] = {title.get(), status.get()}; |
|
393 NS_NAMED_LITERAL_STRING(fmtName, "TitleWithStatus"); |
|
394 mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2, |
|
395 getter_Copies(titleWithStatus)); |
|
396 SetTitle(titleWithStatus); |
|
397 } |
|
398 } |
|
399 |
|
400 void |
|
401 MediaDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) |
|
402 { |
|
403 nsHTMLDocument::SetScriptGlobalObject(aGlobalObject); |
|
404 if (!mDocumentElementInserted && aGlobalObject) { |
|
405 mDocumentElementInserted = true; |
|
406 nsContentUtils::AddScriptRunner( |
|
407 new nsDocElementCreatedNotificationRunner(this)); |
|
408 } |
|
409 } |
|
410 |
|
411 } // namespace dom |
|
412 } // namespace mozilla |