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