content/html/document/src/nsHTMLDocument.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:743a6c6637d4
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=2 et 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 "nsHTMLDocument.h"
8
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/dom/HTMLAllCollection.h"
11 #include "nsCOMPtr.h"
12 #include "nsGlobalWindow.h"
13 #include "nsXPIDLString.h"
14 #include "nsPrintfCString.h"
15 #include "nsReadableUtils.h"
16 #include "nsUnicharUtils.h"
17 #include "nsIHTMLContentSink.h"
18 #include "nsIXMLContentSink.h"
19 #include "nsHTMLParts.h"
20 #include "nsHTMLStyleSheet.h"
21 #include "nsGkAtoms.h"
22 #include "nsIPresShell.h"
23 #include "nsPresContext.h"
24 #include "nsIDOMNode.h" // for Find
25 #include "nsIDOMNodeList.h"
26 #include "nsIDOMElement.h"
27 #include "nsPIDOMWindow.h"
28 #include "nsDOMString.h"
29 #include "nsIStreamListener.h"
30 #include "nsIURI.h"
31 #include "nsIIOService.h"
32 #include "nsNetUtil.h"
33 #include "nsIContentViewerContainer.h"
34 #include "nsIContentViewer.h"
35 #include "nsIMarkupDocumentViewer.h"
36 #include "nsDocShell.h"
37 #include "nsDocShellLoadTypes.h"
38 #include "nsIWebNavigation.h"
39 #include "nsIBaseWindow.h"
40 #include "nsIWebShellServices.h"
41 #include "nsIScriptContext.h"
42 #include "nsIXPConnect.h"
43 #include "nsContentList.h"
44 #include "nsError.h"
45 #include "nsIPrincipal.h"
46 #include "nsJSPrincipals.h"
47 #include "nsIScriptSecurityManager.h"
48 #include "nsAttrName.h"
49 #include "nsNodeUtils.h"
50
51 #include "nsNetCID.h"
52 #include "nsICookieService.h"
53
54 #include "nsIServiceManager.h"
55 #include "nsIConsoleService.h"
56 #include "nsIComponentManager.h"
57 #include "nsParserCIID.h"
58 #include "nsIDOMHTMLElement.h"
59 #include "nsIDOMHTMLHeadElement.h"
60 #include "nsNameSpaceManager.h"
61 #include "nsGenericHTMLElement.h"
62 #include "mozilla/css/Loader.h"
63 #include "nsIHttpChannel.h"
64 #include "nsIFile.h"
65 #include "nsFrameSelection.h"
66 #include "nsISelectionPrivate.h"//for toStringwithformat code
67
68 #include "nsContentUtils.h"
69 #include "nsJSUtils.h"
70 #include "nsIDocumentInlines.h"
71 #include "nsIDocumentEncoder.h" //for outputting selection
72 #include "nsICachingChannel.h"
73 #include "nsIContentViewer.h"
74 #include "nsIWyciwygChannel.h"
75 #include "nsIScriptElement.h"
76 #include "nsIScriptError.h"
77 #include "nsIMutableArray.h"
78 #include "nsArrayUtils.h"
79 #include "nsIEffectiveTLDService.h"
80
81 //AHMED 12-2
82 #include "nsBidiUtils.h"
83
84 #include "mozilla/dom/EncodingUtils.h"
85 #include "mozilla/dom/FallbackEncoding.h"
86 #include "nsIEditingSession.h"
87 #include "nsIEditor.h"
88 #include "nsNodeInfoManager.h"
89 #include "nsIPlaintextEditor.h"
90 #include "nsIHTMLEditor.h"
91 #include "nsIEditorStyleSheets.h"
92 #include "nsIInlineSpellChecker.h"
93 #include "nsRange.h"
94 #include "mozAutoDocUpdate.h"
95 #include "nsCCUncollectableMarker.h"
96 #include "nsHtml5Module.h"
97 #include "prprf.h"
98 #include "mozilla/dom/Element.h"
99 #include "mozilla/Preferences.h"
100 #include "nsMimeTypes.h"
101 #include "nsIRequest.h"
102 #include "nsHtml5TreeOpExecutor.h"
103 #include "nsHtml5Parser.h"
104 #include "nsIDOMJSWindow.h"
105 #include "nsSandboxFlags.h"
106 #include "nsIImageDocument.h"
107 #include "mozilla/dom/HTMLBodyElement.h"
108 #include "mozilla/dom/HTMLDocumentBinding.h"
109 #include "nsCharsetSource.h"
110 #include "nsIStringBundle.h"
111 #include "nsDOMClassInfo.h"
112
113 using namespace mozilla;
114 using namespace mozilla::dom;
115
116 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
117
118 #include "prtime.h"
119
120 //#define DEBUG_charset
121
122 static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
123
124 uint32_t nsHTMLDocument::gWyciwygSessionCnt = 0;
125
126 // this function will return false if the command is not recognized
127 // inCommandID will be converted as necessary for internal operations
128 // inParam will be converted as necessary for internal operations
129 // outParam will be Empty if no parameter is needed or if returning a boolean
130 // outIsBoolean will determine whether to send param as a boolean or string
131 // outBooleanParam will not be set unless outIsBoolean
132 static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
133 const nsAString & inParam,
134 nsACString& outCommandID,
135 nsACString& outParam,
136 bool& isBoolean,
137 bool& boolValue);
138
139 static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
140 nsACString& outCommandID);
141
142 // ==================================================================
143 // =
144 // ==================================================================
145 static nsresult
146 RemoveFromAgentSheets(nsCOMArray<nsIStyleSheet> &aAgentSheets, const nsAString& url)
147 {
148 nsCOMPtr<nsIURI> uri;
149 nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
150 NS_ENSURE_SUCCESS(rv, rv);
151
152 for (int32_t i = aAgentSheets.Count() - 1; i >= 0; --i) {
153 nsIStyleSheet* sheet = aAgentSheets[i];
154 nsIURI* sheetURI = sheet->GetSheetURI();
155
156 bool equals = false;
157 uri->Equals(sheetURI, &equals);
158 if (equals) {
159 aAgentSheets.RemoveObjectAt(i);
160 }
161 }
162
163 return NS_OK;
164 }
165
166 nsresult
167 NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData)
168 {
169 nsRefPtr<nsHTMLDocument> doc = new nsHTMLDocument();
170
171 nsresult rv = doc->Init();
172
173 if (NS_FAILED(rv)) {
174 *aInstancePtrResult = nullptr;
175 return rv;
176 }
177
178 doc->SetLoadedAsData(aLoadedAsData);
179 doc.forget(aInstancePtrResult);
180
181 return NS_OK;
182 }
183
184 // NOTE! nsDocument::operator new() zeroes out all members, so don't
185 // bother initializing members to 0.
186
187 nsHTMLDocument::nsHTMLDocument()
188 : nsDocument("text/html")
189 {
190 // NOTE! nsDocument::operator new() zeroes out all members, so don't
191 // bother initializing members to 0.
192
193 mIsRegularHTML = true;
194 mDefaultElementType = kNameSpaceID_XHTML;
195 mCompatMode = eCompatibility_NavQuirks;
196 }
197
198 nsHTMLDocument::~nsHTMLDocument()
199 {
200 }
201
202 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTMLDocument, nsDocument,
203 mAll,
204 mImages,
205 mApplets,
206 mEmbeds,
207 mLinks,
208 mAnchors,
209 mScripts,
210 mForms,
211 mFormControls,
212 mWyciwygChannel,
213 mMidasCommandManager)
214
215 NS_IMPL_ADDREF_INHERITED(nsHTMLDocument, nsDocument)
216 NS_IMPL_RELEASE_INHERITED(nsHTMLDocument, nsDocument)
217
218 // QueryInterface implementation for nsHTMLDocument
219 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLDocument)
220 NS_INTERFACE_TABLE_INHERITED(nsHTMLDocument, nsIHTMLDocument,
221 nsIDOMHTMLDocument)
222 NS_INTERFACE_TABLE_TAIL_INHERITING(nsDocument)
223
224 JSObject*
225 nsHTMLDocument::WrapNode(JSContext* aCx)
226 {
227 return HTMLDocumentBinding::Wrap(aCx, this);
228 }
229
230 nsresult
231 nsHTMLDocument::Init()
232 {
233 nsresult rv = nsDocument::Init();
234 NS_ENSURE_SUCCESS(rv, rv);
235
236 // Now reset the compatibility mode of the CSSLoader
237 // to match our compat mode.
238 CSSLoader()->SetCompatibilityMode(mCompatMode);
239
240 return NS_OK;
241 }
242
243
244 void
245 nsHTMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
246 {
247 nsDocument::Reset(aChannel, aLoadGroup);
248
249 if (aChannel) {
250 aChannel->GetLoadFlags(&mLoadFlags);
251 }
252 }
253
254 void
255 nsHTMLDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
256 nsIPrincipal* aPrincipal)
257 {
258 mLoadFlags = nsIRequest::LOAD_NORMAL;
259
260 nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
261
262 mImages = nullptr;
263 mApplets = nullptr;
264 mEmbeds = nullptr;
265 mLinks = nullptr;
266 mAnchors = nullptr;
267 mScripts = nullptr;
268
269 mForms = nullptr;
270
271 NS_ASSERTION(!mWyciwygChannel,
272 "nsHTMLDocument::Reset() - Wyciwyg Channel still exists!");
273
274 mWyciwygChannel = nullptr;
275
276 // Make the content type default to "text/html", we are a HTML
277 // document, after all. Once we start getting data, this may be
278 // changed.
279 SetContentTypeInternal(nsDependentCString("text/html"));
280 }
281
282 already_AddRefed<nsIPresShell>
283 nsHTMLDocument::CreateShell(nsPresContext* aContext,
284 nsViewManager* aViewManager,
285 nsStyleSet* aStyleSet)
286 {
287 return doCreateShell(aContext, aViewManager, aStyleSet, mCompatMode);
288 }
289
290 void
291 nsHTMLDocument::TryHintCharset(nsIMarkupDocumentViewer* aMarkupDV,
292 int32_t& aCharsetSource, nsACString& aCharset)
293 {
294 if (aMarkupDV) {
295 int32_t requestCharsetSource;
296 nsresult rv = aMarkupDV->GetHintCharacterSetSource(&requestCharsetSource);
297
298 if(NS_SUCCEEDED(rv) && kCharsetUninitialized != requestCharsetSource) {
299 nsAutoCString requestCharset;
300 rv = aMarkupDV->GetHintCharacterSet(requestCharset);
301 aMarkupDV->SetHintCharacterSetSource((int32_t)(kCharsetUninitialized));
302
303 if(requestCharsetSource <= aCharsetSource)
304 return;
305
306 if(NS_SUCCEEDED(rv) && EncodingUtils::IsAsciiCompatible(requestCharset)) {
307 aCharsetSource = requestCharsetSource;
308 aCharset = requestCharset;
309
310 return;
311 }
312 }
313 }
314 return;
315 }
316
317
318 void
319 nsHTMLDocument::TryUserForcedCharset(nsIMarkupDocumentViewer* aMarkupDV,
320 nsIDocShell* aDocShell,
321 int32_t& aCharsetSource,
322 nsACString& aCharset)
323 {
324 nsresult rv = NS_OK;
325
326 if(kCharsetFromUserForced <= aCharsetSource)
327 return;
328
329 // mCharacterSet not updated yet for channel, so check aCharset, too.
330 if (WillIgnoreCharsetOverride() || !EncodingUtils::IsAsciiCompatible(aCharset)) {
331 return;
332 }
333
334 nsAutoCString forceCharsetFromDocShell;
335 if (aMarkupDV) {
336 // XXX mailnews-only
337 rv = aMarkupDV->GetForceCharacterSet(forceCharsetFromDocShell);
338 }
339
340 if(NS_SUCCEEDED(rv) &&
341 !forceCharsetFromDocShell.IsEmpty() &&
342 EncodingUtils::IsAsciiCompatible(forceCharsetFromDocShell)) {
343 aCharset = forceCharsetFromDocShell;
344 aCharsetSource = kCharsetFromUserForced;
345 return;
346 }
347
348 if (aDocShell) {
349 // This is the Character Encoding menu code path in Firefox
350 nsAutoCString charset;
351 rv = aDocShell->GetForcedCharset(charset);
352
353 if (NS_SUCCEEDED(rv) && !charset.IsEmpty()) {
354 if (!EncodingUtils::IsAsciiCompatible(charset)) {
355 return;
356 }
357 aCharset = charset;
358 aCharsetSource = kCharsetFromUserForced;
359 aDocShell->SetForcedCharset(NS_LITERAL_CSTRING(""));
360 }
361 }
362 }
363
364 void
365 nsHTMLDocument::TryCacheCharset(nsICachingChannel* aCachingChannel,
366 int32_t& aCharsetSource,
367 nsACString& aCharset)
368 {
369 nsresult rv;
370
371 if (kCharsetFromCache <= aCharsetSource) {
372 return;
373 }
374
375 nsCString cachedCharset;
376 rv = aCachingChannel->GetCacheTokenCachedCharset(cachedCharset);
377 // Check EncodingUtils::IsAsciiCompatible() even in the cache case, because the value
378 // might be stale and in the case of a stale charset that is not a rough
379 // ASCII superset, the parser has no way to recover.
380 if (NS_SUCCEEDED(rv) &&
381 !cachedCharset.IsEmpty() &&
382 EncodingUtils::IsAsciiCompatible(cachedCharset))
383 {
384 aCharset = cachedCharset;
385 aCharsetSource = kCharsetFromCache;
386 }
387 }
388
389 void
390 nsHTMLDocument::TryParentCharset(nsIDocShell* aDocShell,
391 int32_t& aCharsetSource,
392 nsACString& aCharset)
393 {
394 if (!aDocShell) {
395 return;
396 }
397 if (aCharsetSource >= kCharsetFromParentForced) {
398 return;
399 }
400
401 int32_t parentSource;
402 nsAutoCString parentCharset;
403 nsCOMPtr<nsIPrincipal> parentPrincipal;
404 aDocShell->GetParentCharset(parentCharset,
405 &parentSource,
406 getter_AddRefs(parentPrincipal));
407 if (parentCharset.IsEmpty()) {
408 return;
409 }
410 if (kCharsetFromParentForced == parentSource ||
411 kCharsetFromUserForced == parentSource) {
412 if (WillIgnoreCharsetOverride() ||
413 !EncodingUtils::IsAsciiCompatible(aCharset) || // if channel said UTF-16
414 !EncodingUtils::IsAsciiCompatible(parentCharset)) {
415 return;
416 }
417 aCharset.Assign(parentCharset);
418 aCharsetSource = kCharsetFromParentForced;
419 return;
420 }
421
422 if (aCharsetSource >= kCharsetFromParentFrame) {
423 return;
424 }
425
426 if (kCharsetFromCache <= parentSource) {
427 // Make sure that's OK
428 if (!NodePrincipal()->Equals(parentPrincipal) ||
429 !EncodingUtils::IsAsciiCompatible(parentCharset)) {
430 return;
431 }
432
433 aCharset.Assign(parentCharset);
434 aCharsetSource = kCharsetFromParentFrame;
435 }
436 }
437
438 void
439 nsHTMLDocument::TryTLD(int32_t& aCharsetSource, nsACString& aCharset)
440 {
441 if (aCharsetSource >= kCharsetFromTopLevelDomain) {
442 return;
443 }
444 if (!FallbackEncoding::sGuessFallbackFromTopLevelDomain) {
445 return;
446 }
447 if (!mDocumentURI) {
448 return;
449 }
450 nsAutoCString host;
451 mDocumentURI->GetAsciiHost(host);
452 if (host.IsEmpty()) {
453 return;
454 }
455 // First let's see if the host is DNS-absolute and ends with a dot and
456 // get rid of that one.
457 if (host.Last() == '.') {
458 host.SetLength(host.Length() - 1);
459 if (host.IsEmpty()) {
460 return;
461 }
462 }
463 // If we still have a dot, the host is weird, so let's continue only
464 // if we have something other than a dot now.
465 if (host.Last() == '.') {
466 return;
467 }
468 int32_t index = host.RFindChar('.');
469 if (index == kNotFound) {
470 // We have an intranet host, Gecko-internal URL or an IPv6 address.
471 return;
472 }
473 // Since the string didn't end with a dot and we found a dot,
474 // there is at least one character between the dot and the end of
475 // the string, so taking the substring below is safe.
476 nsAutoCString tld;
477 ToLowerCase(Substring(host, index + 1, host.Length() - (index + 1)), tld);
478 // Reject generic TLDs and country TLDs that need more research
479 if (!FallbackEncoding::IsParticipatingTopLevelDomain(tld)) {
480 return;
481 }
482 // Check if we have an IPv4 address
483 bool seenNonDigit = false;
484 for (size_t i = 0; i < tld.Length(); ++i) {
485 char c = tld.CharAt(i);
486 if (c < '0' || c > '9') {
487 seenNonDigit = true;
488 break;
489 }
490 }
491 if (!seenNonDigit) {
492 return;
493 }
494 aCharsetSource = kCharsetFromTopLevelDomain;
495 FallbackEncoding::FromTopLevelDomain(tld, aCharset);
496 }
497
498 void
499 nsHTMLDocument::TryFallback(int32_t& aCharsetSource, nsACString& aCharset)
500 {
501 if (kCharsetFromFallback <= aCharsetSource)
502 return;
503
504 aCharsetSource = kCharsetFromFallback;
505 FallbackEncoding::FromLocale(aCharset);
506 }
507
508 void
509 nsHTMLDocument::SetDocumentCharacterSet(const nsACString& aCharSetID)
510 {
511 nsDocument::SetDocumentCharacterSet(aCharSetID);
512 // Make sure to stash this charset on our channel as needed if it's a wyciwyg
513 // channel.
514 nsCOMPtr<nsIWyciwygChannel> wyciwygChannel = do_QueryInterface(mChannel);
515 if (wyciwygChannel) {
516 wyciwygChannel->SetCharsetAndSource(GetDocumentCharacterSetSource(),
517 aCharSetID);
518 }
519 }
520
521 nsresult
522 nsHTMLDocument::StartDocumentLoad(const char* aCommand,
523 nsIChannel* aChannel,
524 nsILoadGroup* aLoadGroup,
525 nsISupports* aContainer,
526 nsIStreamListener **aDocListener,
527 bool aReset,
528 nsIContentSink* aSink)
529 {
530 if (!aCommand) {
531 MOZ_ASSERT(false, "Command is mandatory");
532 return NS_ERROR_INVALID_POINTER;
533 }
534 if (aSink) {
535 MOZ_ASSERT(false, "Got a sink override. Should not happen for HTML doc.");
536 return NS_ERROR_INVALID_ARG;
537 }
538 if (!mIsRegularHTML) {
539 MOZ_ASSERT(false, "Must not set HTML doc to XHTML mode before load start.");
540 return NS_ERROR_DOM_INVALID_STATE_ERR;
541 }
542
543 nsAutoCString contentType;
544 aChannel->GetContentType(contentType);
545
546 bool view = !strcmp(aCommand, "view") ||
547 !strcmp(aCommand, "external-resource");
548 bool viewSource = !strcmp(aCommand, "view-source");
549 bool asData = !strcmp(aCommand, kLoadAsData);
550 if(!(view || viewSource || asData)) {
551 MOZ_ASSERT(false, "Bad parser command");
552 return NS_ERROR_INVALID_ARG;
553 }
554
555 bool html = contentType.EqualsLiteral(TEXT_HTML);
556 bool xhtml = !html && contentType.EqualsLiteral(APPLICATION_XHTML_XML);
557 bool plainText = !html && !xhtml && nsContentUtils::IsPlainTextType(contentType);
558 if (!(html || xhtml || plainText || viewSource)) {
559 MOZ_ASSERT(false, "Channel with bad content type.");
560 return NS_ERROR_INVALID_ARG;
561 }
562
563 bool loadAsHtml5 = true;
564
565 if (!viewSource && xhtml) {
566 // We're parsing XHTML as XML, remember that.
567 mIsRegularHTML = false;
568 mCompatMode = eCompatibility_FullStandards;
569 loadAsHtml5 = false;
570 }
571
572 // TODO: Proper about:blank treatment is bug 543435
573 if (loadAsHtml5 && view) {
574 // mDocumentURI hasn't been set, yet, so get the URI from the channel
575 nsCOMPtr<nsIURI> uri;
576 aChannel->GetOriginalURI(getter_AddRefs(uri));
577 // Adapted from nsDocShell:
578 // GetSpec can be expensive for some URIs, so check the scheme first.
579 bool isAbout = false;
580 if (uri && NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)) && isAbout) {
581 nsAutoCString str;
582 uri->GetSpec(str);
583 if (str.EqualsLiteral("about:blank")) {
584 loadAsHtml5 = false;
585 }
586 }
587 }
588
589 CSSLoader()->SetCompatibilityMode(mCompatMode);
590
591 nsresult rv = nsDocument::StartDocumentLoad(aCommand,
592 aChannel, aLoadGroup,
593 aContainer,
594 aDocListener, aReset);
595 if (NS_FAILED(rv)) {
596 return rv;
597 }
598
599 // Store the security info for future use with wyciwyg channels.
600 aChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
601
602 nsCOMPtr<nsIURI> uri;
603 rv = aChannel->GetURI(getter_AddRefs(uri));
604 if (NS_FAILED(rv)) {
605 return rv;
606 }
607
608 nsCOMPtr<nsICachingChannel> cachingChan = do_QueryInterface(aChannel);
609
610 if (loadAsHtml5) {
611 mParser = nsHtml5Module::NewHtml5Parser();
612 if (plainText) {
613 if (viewSource) {
614 mParser->MarkAsNotScriptCreated("view-source-plain");
615 } else {
616 mParser->MarkAsNotScriptCreated("plain-text");
617 }
618 } else if (viewSource && !html) {
619 mParser->MarkAsNotScriptCreated("view-source-xml");
620 } else {
621 mParser->MarkAsNotScriptCreated(aCommand);
622 }
623 } else {
624 mParser = do_CreateInstance(kCParserCID, &rv);
625 NS_ENSURE_SUCCESS(rv, rv);
626 }
627
628 // Look for the parent document. Note that at this point we don't have our
629 // content viewer set up yet, and therefore do not have a useful
630 // mParentDocument.
631
632 // in this block of code, if we get an error result, we return it
633 // but if we get a null pointer, that's perfectly legal for parent
634 // and parentContentViewer
635 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
636 nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
637 if (docShell) {
638 docShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
639 }
640
641 nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
642 nsCOMPtr<nsIContentViewer> parentContentViewer;
643 if (parent) {
644 rv = parent->GetContentViewer(getter_AddRefs(parentContentViewer));
645 NS_ENSURE_SUCCESS(rv, rv);
646 }
647
648 nsCOMPtr<nsIMarkupDocumentViewer> muCV;
649 nsCOMPtr<nsIContentViewer> cv;
650 if (docShell) {
651 docShell->GetContentViewer(getter_AddRefs(cv));
652 }
653 if (cv) {
654 muCV = do_QueryInterface(cv);
655 } else {
656 muCV = do_QueryInterface(parentContentViewer);
657 }
658
659 nsAutoCString urlSpec;
660 uri->GetSpec(urlSpec);
661 #ifdef DEBUG_charset
662 printf("Determining charset for %s\n", urlSpec.get());
663 #endif
664
665 // These are the charset source and charset for our document
666 int32_t charsetSource;
667 nsAutoCString charset;
668
669 // These are the charset source and charset for the parser. This can differ
670 // from that for the document if the channel is a wyciwyg channel.
671 int32_t parserCharsetSource;
672 nsAutoCString parserCharset;
673
674 nsCOMPtr<nsIWyciwygChannel> wyciwygChannel;
675
676 // For error reporting
677 nsHtml5TreeOpExecutor* executor = nullptr;
678 if (loadAsHtml5) {
679 executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
680 }
681
682 if (!IsHTML() || !docShell) { // no docshell for text/html XHR
683 charsetSource = IsHTML() ? kCharsetFromFallback
684 : kCharsetFromDocTypeDefault;
685 charset.AssignLiteral("UTF-8");
686 TryChannelCharset(aChannel, charsetSource, charset, executor);
687 parserCharsetSource = charsetSource;
688 parserCharset = charset;
689 } else {
690 NS_ASSERTION(docShell, "Unexpected null value");
691
692 charsetSource = kCharsetUninitialized;
693 wyciwygChannel = do_QueryInterface(aChannel);
694
695 // The following will try to get the character encoding from various
696 // sources. Each Try* function will return early if the source is already
697 // at least as large as any of the sources it might look at. Some of
698 // these functions (like TryHintCharset and TryParentCharset) can set
699 // charsetSource to various values depending on where the charset they
700 // end up finding originally comes from.
701
702 // Don't actually get the charset from the channel if this is a
703 // wyciwyg channel; it'll always be UTF-16
704 if (!wyciwygChannel) {
705 // Otherwise, try the channel's charset (e.g., charset from HTTP
706 // "Content-Type" header) first. This way, we get to reject overrides in
707 // TryParentCharset and TryUserForcedCharset if the channel said UTF-16.
708 // This is to avoid socially engineered XSS by adding user-supplied
709 // content to a UTF-16 site such that the byte have a dangerous
710 // interpretation as ASCII and the user can be lured to using the
711 // charset menu.
712 TryChannelCharset(aChannel, charsetSource, charset, executor);
713 }
714
715 TryUserForcedCharset(muCV, docShell, charsetSource, charset);
716
717 TryHintCharset(muCV, charsetSource, charset); // XXX mailnews-only
718 TryParentCharset(docShell, charsetSource, charset);
719
720 if (cachingChan && !urlSpec.IsEmpty()) {
721 TryCacheCharset(cachingChan, charsetSource, charset);
722 }
723
724 TryTLD(charsetSource, charset);
725 TryFallback(charsetSource, charset);
726
727 if (wyciwygChannel) {
728 // We know for sure that the parser needs to be using UTF16.
729 parserCharset = "UTF-16";
730 parserCharsetSource = charsetSource < kCharsetFromChannel ?
731 kCharsetFromChannel : charsetSource;
732
733 nsAutoCString cachedCharset;
734 int32_t cachedSource;
735 rv = wyciwygChannel->GetCharsetAndSource(&cachedSource, cachedCharset);
736 if (NS_SUCCEEDED(rv)) {
737 if (cachedSource > charsetSource) {
738 charsetSource = cachedSource;
739 charset = cachedCharset;
740 }
741 } else {
742 // Don't propagate this error.
743 rv = NS_OK;
744 }
745
746 } else {
747 parserCharset = charset;
748 parserCharsetSource = charsetSource;
749 }
750 }
751
752 SetDocumentCharacterSetSource(charsetSource);
753 SetDocumentCharacterSet(charset);
754
755 if (cachingChan) {
756 NS_ASSERTION(charset == parserCharset,
757 "How did those end up different here? wyciwyg channels are "
758 "not nsICachingChannel");
759 rv = cachingChan->SetCacheTokenCachedCharset(charset);
760 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "cannot SetMetaDataElement");
761 rv = NS_OK; // don't propagate error
762 }
763
764 // Set the parser as the stream listener for the document loader...
765 rv = NS_OK;
766 nsCOMPtr<nsIStreamListener> listener = mParser->GetStreamListener();
767 listener.forget(aDocListener);
768
769 #ifdef DEBUG_charset
770 printf(" charset = %s source %d\n",
771 charset.get(), charsetSource);
772 #endif
773 mParser->SetDocumentCharset(parserCharset, parserCharsetSource);
774 mParser->SetCommand(aCommand);
775
776 if (!IsHTML()) {
777 MOZ_ASSERT(!loadAsHtml5);
778 nsCOMPtr<nsIXMLContentSink> xmlsink;
779 NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri,
780 docShell, aChannel);
781 mParser->SetContentSink(xmlsink);
782 } else {
783 if (loadAsHtml5) {
784 nsHtml5Module::Initialize(mParser, this, uri, docShell, aChannel);
785 } else {
786 // about:blank *only*
787 nsCOMPtr<nsIHTMLContentSink> htmlsink;
788 NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri,
789 docShell, aChannel);
790 mParser->SetContentSink(htmlsink);
791 }
792 }
793
794 if (plainText && !nsContentUtils::IsChildOfSameType(this) &&
795 Preferences::GetBool("plain_text.wrap_long_lines")) {
796 nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
797 NS_ASSERTION(NS_SUCCEEDED(rv) && bundleService, "The bundle service could not be loaded");
798 nsCOMPtr<nsIStringBundle> bundle;
799 rv = bundleService->CreateBundle("chrome://global/locale/browser.properties",
800 getter_AddRefs(bundle));
801 NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/browser.properties could not be loaded");
802 nsXPIDLString title;
803 if (bundle) {
804 bundle->GetStringFromName(MOZ_UTF16("plainText.wordWrap"), getter_Copies(title));
805 }
806 SetSelectedStyleSheetSet(title);
807 }
808
809 // parser the content of the URI
810 mParser->Parse(uri, nullptr, (void *)this);
811
812 return rv;
813 }
814
815 void
816 nsHTMLDocument::StopDocumentLoad()
817 {
818 BlockOnload();
819
820 // Remove the wyciwyg channel request from the document load group
821 // that we added in Open() if Open() was called on this doc.
822 RemoveWyciwygChannel();
823 NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::StopDocumentLoad(): "
824 "nsIWyciwygChannel could not be removed!");
825
826 nsDocument::StopDocumentLoad();
827 UnblockOnload(false);
828 return;
829 }
830
831 void
832 nsHTMLDocument::BeginLoad()
833 {
834 if (IsEditingOn()) {
835 // Reset() blows away all event listeners in the document, and our
836 // editor relies heavily on those. Midas is turned on, to make it
837 // work, re-initialize it to give it a chance to add its event
838 // listeners again.
839
840 TurnEditingOff();
841 EditingStateChanged();
842 }
843 nsDocument::BeginLoad();
844 }
845
846 void
847 nsHTMLDocument::EndLoad()
848 {
849 bool turnOnEditing =
850 mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
851 // Note: nsDocument::EndLoad nulls out mParser.
852 nsDocument::EndLoad();
853 if (turnOnEditing) {
854 EditingStateChanged();
855 }
856 }
857
858 void
859 nsHTMLDocument::SetCompatibilityMode(nsCompatibility aMode)
860 {
861 NS_ASSERTION(IsHTML() || aMode == eCompatibility_FullStandards,
862 "Bad compat mode for XHTML document!");
863
864 mCompatMode = aMode;
865 CSSLoader()->SetCompatibilityMode(mCompatMode);
866 nsCOMPtr<nsIPresShell> shell = GetShell();
867 if (shell) {
868 nsPresContext *pc = shell->GetPresContext();
869 if (pc) {
870 pc->CompatibilityModeChanged();
871 }
872 }
873 }
874
875 //
876 // nsIDOMHTMLDocument interface implementation
877 //
878 already_AddRefed<nsIURI>
879 nsHTMLDocument::GetDomainURI()
880 {
881 nsIPrincipal* principal = NodePrincipal();
882
883 nsCOMPtr<nsIURI> uri;
884 principal->GetDomain(getter_AddRefs(uri));
885 if (uri) {
886 return uri.forget();
887 }
888
889 principal->GetURI(getter_AddRefs(uri));
890 return uri.forget();
891 }
892
893
894 NS_IMETHODIMP
895 nsHTMLDocument::GetDomain(nsAString& aDomain)
896 {
897 ErrorResult rv;
898 GetDomain(aDomain, rv);
899 return rv.ErrorCode();
900 }
901
902 void
903 nsHTMLDocument::GetDomain(nsAString& aDomain, ErrorResult& rv)
904 {
905 nsCOMPtr<nsIURI> uri = GetDomainURI();
906
907 if (!uri) {
908 rv.Throw(NS_ERROR_FAILURE);
909 return;
910 }
911
912 nsAutoCString hostName;
913
914 if (NS_SUCCEEDED(uri->GetHost(hostName))) {
915 CopyUTF8toUTF16(hostName, aDomain);
916 } else {
917 // If we can't get the host from the URI (e.g. about:, javascript:,
918 // etc), just return an null string.
919 SetDOMStringToNull(aDomain);
920 }
921 }
922
923 NS_IMETHODIMP
924 nsHTMLDocument::SetDomain(const nsAString& aDomain)
925 {
926 ErrorResult rv;
927 SetDomain(aDomain, rv);
928 return rv.ErrorCode();
929 }
930
931 void
932 nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv)
933 {
934 if (mSandboxFlags & SANDBOXED_DOMAIN) {
935 // We're sandboxed; disallow setting domain
936 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
937 return;
938 }
939
940 if (aDomain.IsEmpty()) {
941 rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
942 return;
943 }
944
945 // Create new URI
946 nsCOMPtr<nsIURI> uri = GetDomainURI();
947
948 if (!uri) {
949 rv.Throw(NS_ERROR_FAILURE);
950 return;
951 }
952
953 nsAutoCString newURIString;
954 if (NS_FAILED(uri->GetScheme(newURIString))) {
955 rv.Throw(NS_ERROR_FAILURE);
956 return;
957 }
958 nsAutoCString path;
959 if (NS_FAILED(uri->GetPath(path))) {
960 rv.Throw(NS_ERROR_FAILURE);
961 return;
962 }
963 newURIString.AppendLiteral("://");
964 AppendUTF16toUTF8(aDomain, newURIString);
965 newURIString.Append(path);
966
967 nsCOMPtr<nsIURI> newURI;
968 if (NS_FAILED(NS_NewURI(getter_AddRefs(newURI), newURIString))) {
969 rv.Throw(NS_ERROR_FAILURE);
970 return;
971 }
972
973 // Check new domain - must be a superdomain of the current host
974 // For example, a page from foo.bar.com may set domain to bar.com,
975 // but not to ar.com, baz.com, or fi.foo.bar.com.
976 nsAutoCString current, domain;
977 if (NS_FAILED(uri->GetAsciiHost(current)))
978 current.Truncate();
979 if (NS_FAILED(newURI->GetAsciiHost(domain)))
980 domain.Truncate();
981
982 bool ok = current.Equals(domain);
983 if (current.Length() > domain.Length() &&
984 StringEndsWith(current, domain) &&
985 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
986 // We're golden if the new domain is the current page's base domain or a
987 // subdomain of it.
988 nsCOMPtr<nsIEffectiveTLDService> tldService =
989 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
990 if (!tldService) {
991 rv.Throw(NS_ERROR_NOT_AVAILABLE);
992 return;
993 }
994
995 nsAutoCString currentBaseDomain;
996 ok = NS_SUCCEEDED(tldService->GetBaseDomain(uri, 0, currentBaseDomain));
997 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
998 (domain.Length() >= currentBaseDomain.Length()),
999 "uh-oh! slight optimization wasn't valid somehow!");
1000 ok = ok && domain.Length() >= currentBaseDomain.Length();
1001 }
1002 if (!ok) {
1003 // Error: illegal domain
1004 rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
1005 return;
1006 }
1007
1008 rv = NodePrincipal()->SetDomain(newURI);
1009 }
1010
1011 nsGenericHTMLElement*
1012 nsHTMLDocument::GetBody()
1013 {
1014 Element* html = GetHtmlElement();
1015 if (!html) {
1016 return nullptr;
1017 }
1018
1019 for (nsIContent* child = html->GetFirstChild();
1020 child;
1021 child = child->GetNextSibling()) {
1022 if (child->IsHTML(nsGkAtoms::body) || child->IsHTML(nsGkAtoms::frameset)) {
1023 return static_cast<nsGenericHTMLElement*>(child);
1024 }
1025 }
1026
1027 return nullptr;
1028 }
1029
1030 NS_IMETHODIMP
1031 nsHTMLDocument::GetBody(nsIDOMHTMLElement** aBody)
1032 {
1033 *aBody = nullptr;
1034
1035 nsIContent *body = GetBody();
1036
1037 return body ? CallQueryInterface(body, aBody) : NS_OK;
1038 }
1039
1040 NS_IMETHODIMP
1041 nsHTMLDocument::SetBody(nsIDOMHTMLElement* aBody)
1042 {
1043 nsCOMPtr<nsIContent> newBody = do_QueryInterface(aBody);
1044 MOZ_ASSERT(!newBody || newBody->IsHTML(),
1045 "How could we be an nsIContent but not actually HTML here?");
1046 ErrorResult rv;
1047 SetBody(static_cast<nsGenericHTMLElement*>(newBody.get()), rv);
1048 return rv.ErrorCode();
1049 }
1050
1051 void
1052 nsHTMLDocument::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv)
1053 {
1054 Element* root = GetRootElement();
1055
1056 // The body element must be either a body tag or a frameset tag. And we must
1057 // have a html root tag, otherwise GetBody will not return the newly set
1058 // body.
1059 if (!newBody || !(newBody->Tag() == nsGkAtoms::body ||
1060 newBody->Tag() == nsGkAtoms::frameset) ||
1061 !root || !root->IsHTML() ||
1062 root->Tag() != nsGkAtoms::html) {
1063 rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
1064 return;
1065 }
1066
1067 // Use DOM methods so that we pass through the appropriate security checks.
1068 nsCOMPtr<Element> currentBody = GetBodyElement();
1069 if (currentBody) {
1070 root->ReplaceChild(*newBody, *currentBody, rv);
1071 } else {
1072 root->AppendChild(*newBody, rv);
1073 }
1074 }
1075
1076 NS_IMETHODIMP
1077 nsHTMLDocument::GetHead(nsIDOMHTMLHeadElement** aHead)
1078 {
1079 *aHead = nullptr;
1080
1081 Element* head = GetHeadElement();
1082
1083 return head ? CallQueryInterface(head, aHead) : NS_OK;
1084 }
1085
1086 NS_IMETHODIMP
1087 nsHTMLDocument::GetImages(nsIDOMHTMLCollection** aImages)
1088 {
1089 NS_ADDREF(*aImages = Images());
1090 return NS_OK;
1091 }
1092
1093 nsIHTMLCollection*
1094 nsHTMLDocument::Images()
1095 {
1096 if (!mImages) {
1097 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img, nsGkAtoms::img);
1098 }
1099 return mImages;
1100 }
1101
1102 NS_IMETHODIMP
1103 nsHTMLDocument::GetApplets(nsIDOMHTMLCollection** aApplets)
1104 {
1105 NS_ADDREF(*aApplets = Applets());
1106 return NS_OK;
1107 }
1108
1109 nsIHTMLCollection*
1110 nsHTMLDocument::Applets()
1111 {
1112 if (!mApplets) {
1113 mApplets = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::applet, nsGkAtoms::applet);
1114 }
1115 return mApplets;
1116 }
1117
1118 bool
1119 nsHTMLDocument::MatchLinks(nsIContent *aContent, int32_t aNamespaceID,
1120 nsIAtom* aAtom, void* aData)
1121 {
1122 nsIDocument* doc = aContent->GetCurrentDoc();
1123
1124 if (doc) {
1125 NS_ASSERTION(aContent->IsInDoc(),
1126 "This method should never be called on content nodes that "
1127 "are not in a document!");
1128 #ifdef DEBUG
1129 {
1130 nsCOMPtr<nsIHTMLDocument> htmldoc =
1131 do_QueryInterface(aContent->GetCurrentDoc());
1132 NS_ASSERTION(htmldoc,
1133 "Huh, how did this happen? This should only be used with "
1134 "HTML documents!");
1135 }
1136 #endif
1137
1138 nsINodeInfo *ni = aContent->NodeInfo();
1139
1140 nsIAtom *localName = ni->NameAtom();
1141 if (ni->NamespaceID() == kNameSpaceID_XHTML &&
1142 (localName == nsGkAtoms::a || localName == nsGkAtoms::area)) {
1143 return aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
1144 }
1145 }
1146
1147 return false;
1148 }
1149
1150 NS_IMETHODIMP
1151 nsHTMLDocument::GetLinks(nsIDOMHTMLCollection** aLinks)
1152 {
1153 NS_ADDREF(*aLinks = Links());
1154 return NS_OK;
1155 }
1156
1157 nsIHTMLCollection*
1158 nsHTMLDocument::Links()
1159 {
1160 if (!mLinks) {
1161 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
1162 }
1163 return mLinks;
1164 }
1165
1166 bool
1167 nsHTMLDocument::MatchAnchors(nsIContent *aContent, int32_t aNamespaceID,
1168 nsIAtom* aAtom, void* aData)
1169 {
1170 NS_ASSERTION(aContent->IsInDoc(),
1171 "This method should never be called on content nodes that "
1172 "are not in a document!");
1173 #ifdef DEBUG
1174 {
1175 nsCOMPtr<nsIHTMLDocument> htmldoc =
1176 do_QueryInterface(aContent->GetCurrentDoc());
1177 NS_ASSERTION(htmldoc,
1178 "Huh, how did this happen? This should only be used with "
1179 "HTML documents!");
1180 }
1181 #endif
1182
1183 if (aContent->NodeInfo()->Equals(nsGkAtoms::a, kNameSpaceID_XHTML)) {
1184 return aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
1185 }
1186
1187 return false;
1188 }
1189
1190 NS_IMETHODIMP
1191 nsHTMLDocument::GetAnchors(nsIDOMHTMLCollection** aAnchors)
1192 {
1193 NS_ADDREF(*aAnchors = Anchors());
1194 return NS_OK;
1195 }
1196
1197 nsIHTMLCollection*
1198 nsHTMLDocument::Anchors()
1199 {
1200 if (!mAnchors) {
1201 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
1202 }
1203 return mAnchors;
1204 }
1205
1206 NS_IMETHODIMP
1207 nsHTMLDocument::GetScripts(nsIDOMHTMLCollection** aScripts)
1208 {
1209 NS_ADDREF(*aScripts = Scripts());
1210 return NS_OK;
1211 }
1212
1213 nsIHTMLCollection*
1214 nsHTMLDocument::Scripts()
1215 {
1216 if (!mScripts) {
1217 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script, nsGkAtoms::script);
1218 }
1219 return mScripts;
1220 }
1221
1222 NS_IMETHODIMP
1223 nsHTMLDocument::GetCookie(nsAString& aCookie)
1224 {
1225 ErrorResult rv;
1226 GetCookie(aCookie, rv);
1227 return rv.ErrorCode();
1228 }
1229
1230 void
1231 nsHTMLDocument::GetCookie(nsAString& aCookie, ErrorResult& rv)
1232 {
1233 aCookie.Truncate(); // clear current cookie in case service fails;
1234 // no cookie isn't an error condition.
1235
1236 if (mDisableCookieAccess) {
1237 return;
1238 }
1239
1240 // If the document's sandboxed origin flag is set, access to read cookies
1241 // is prohibited.
1242 if (mSandboxFlags & SANDBOXED_ORIGIN) {
1243 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1244 return;
1245 }
1246
1247 // not having a cookie service isn't an error
1248 nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
1249 if (service) {
1250 // Get a URI from the document principal. We use the original
1251 // codebase in case the codebase was changed by SetDomain
1252 nsCOMPtr<nsIURI> codebaseURI;
1253 NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
1254
1255 if (!codebaseURI) {
1256 // Document's principal is not a codebase (may be system), so
1257 // can't set cookies
1258
1259 return;
1260 }
1261
1262 nsXPIDLCString cookie;
1263 service->GetCookieString(codebaseURI, mChannel, getter_Copies(cookie));
1264 // CopyUTF8toUTF16 doesn't handle error
1265 // because it assumes that the input is valid.
1266 nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"),
1267 cookie, aCookie);
1268 }
1269 }
1270
1271 NS_IMETHODIMP
1272 nsHTMLDocument::SetCookie(const nsAString& aCookie)
1273 {
1274 ErrorResult rv;
1275 SetCookie(aCookie, rv);
1276 return rv.ErrorCode();
1277 }
1278
1279 void
1280 nsHTMLDocument::SetCookie(const nsAString& aCookie, ErrorResult& rv)
1281 {
1282 if (mDisableCookieAccess) {
1283 return;
1284 }
1285
1286 // If the document's sandboxed origin flag is set, access to write cookies
1287 // is prohibited.
1288 if (mSandboxFlags & SANDBOXED_ORIGIN) {
1289 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1290 return;
1291 }
1292
1293 // not having a cookie service isn't an error
1294 nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
1295 if (service && mDocumentURI) {
1296 // The for getting the URI matches nsNavigator::GetCookieEnabled
1297 nsCOMPtr<nsIURI> codebaseURI;
1298 NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
1299
1300 if (!codebaseURI) {
1301 // Document's principal is not a codebase (may be system), so
1302 // can't set cookies
1303
1304 return;
1305 }
1306
1307 NS_ConvertUTF16toUTF8 cookie(aCookie);
1308 service->SetCookieString(codebaseURI, nullptr, cookie.get(), mChannel);
1309 }
1310 }
1311
1312 NS_IMETHODIMP
1313 nsHTMLDocument::Open(const nsAString& aContentTypeOrUrl,
1314 const nsAString& aReplaceOrName,
1315 const nsAString& aFeatures,
1316 JSContext* cx, uint8_t aOptionalArgCount,
1317 nsISupports** aReturn)
1318 {
1319 // When called with 3 or more arguments, document.open() calls window.open().
1320 if (aOptionalArgCount > 2) {
1321 ErrorResult rv;
1322 *aReturn = Open(cx, aContentTypeOrUrl, aReplaceOrName, aFeatures,
1323 false, rv).take();
1324 return rv.ErrorCode();
1325 }
1326
1327 nsString type;
1328 if (aOptionalArgCount > 0) {
1329 type = aContentTypeOrUrl;
1330 } else {
1331 type.AssignLiteral("text/html");
1332 }
1333 nsString replace;
1334 if (aOptionalArgCount > 1) {
1335 replace = aReplaceOrName;
1336 }
1337 ErrorResult rv;
1338 *aReturn = Open(cx, type, replace, rv).take();
1339 return rv.ErrorCode();
1340 }
1341
1342 already_AddRefed<nsIDOMWindow>
1343 nsHTMLDocument::Open(JSContext* /* unused */,
1344 const nsAString& aURL,
1345 const nsAString& aName,
1346 const nsAString& aFeatures,
1347 bool aReplace,
1348 ErrorResult& rv)
1349 {
1350 NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
1351 "XOW should have caught this!");
1352
1353 nsCOMPtr<nsIDOMWindow> window = GetInnerWindow();
1354 if (!window) {
1355 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
1356 return nullptr;
1357 }
1358 nsCOMPtr<nsIDOMJSWindow> win = do_QueryInterface(window);
1359 nsCOMPtr<nsIDOMWindow> newWindow;
1360 // XXXbz We ignore aReplace for now.
1361 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newWindow));
1362 return newWindow.forget();
1363 }
1364
1365 already_AddRefed<nsIDocument>
1366 nsHTMLDocument::Open(JSContext* cx,
1367 const nsAString& aType,
1368 const nsAString& aReplace,
1369 ErrorResult& rv)
1370 {
1371 NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
1372 "XOW should have caught this!");
1373 if (!IsHTML() || mDisableDocWrite) {
1374 // No calling document.open() on XHTML
1375 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1376 return nullptr;
1377 }
1378
1379 nsAutoCString contentType;
1380 contentType.AssignLiteral("text/html");
1381
1382 nsAutoString type;
1383 nsContentUtils::ASCIIToLower(aType, type);
1384 nsAutoCString actualType, dummy;
1385 NS_ParseContentType(NS_ConvertUTF16toUTF8(type), actualType, dummy);
1386 if (!actualType.EqualsLiteral("text/html") &&
1387 !type.EqualsLiteral("replace")) {
1388 contentType.AssignLiteral("text/plain");
1389 }
1390
1391 // If we already have a parser we ignore the document.open call.
1392 if (mParser || mParserAborted) {
1393 // The WHATWG spec says: "If the document has an active parser that isn't
1394 // a script-created parser, and the insertion point associated with that
1395 // parser's input stream is not undefined (that is, it does point to
1396 // somewhere in the input stream), then the method does nothing. Abort
1397 // these steps and return the Document object on which the method was
1398 // invoked."
1399 // Note that aborting a parser leaves the parser "active" with its
1400 // insertion point "not undefined". We track this using mParserAborted,
1401 // because aborting a parser nulls out mParser.
1402 nsCOMPtr<nsIDocument> ret = this;
1403 return ret.forget();
1404 }
1405
1406 // No calling document.open() without a script global object
1407 if (!mScriptGlobalObject) {
1408 nsCOMPtr<nsIDocument> ret = this;
1409 return ret.forget();
1410 }
1411
1412 nsPIDOMWindow* outer = GetWindow();
1413 if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
1414 nsCOMPtr<nsIDocument> ret = this;
1415 return ret.forget();
1416 }
1417
1418 // check whether we're in the middle of unload. If so, ignore this call.
1419 nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
1420 if (!shell) {
1421 // We won't be able to create a parser anyway.
1422 nsCOMPtr<nsIDocument> ret = this;
1423 return ret.forget();
1424 }
1425
1426 bool inUnload;
1427 shell->GetIsInUnload(&inUnload);
1428 if (inUnload) {
1429 nsCOMPtr<nsIDocument> ret = this;
1430 return ret.forget();
1431 }
1432
1433 // Note: We want to use GetDocumentFromContext here because this document
1434 // should inherit the security information of the document that's opening us,
1435 // (since if it's secure, then it's presumably trusted).
1436 nsCOMPtr<nsIDocument> callerDoc = nsContentUtils::GetDocumentFromContext();
1437 if (!callerDoc) {
1438 // If we're called from C++ or in some other way without an originating
1439 // document we can't do a document.open w/o changing the principal of the
1440 // document to something like about:blank (as that's the only sane thing to
1441 // do when we don't know the origin of this call), and since we can't
1442 // change the principals of a document for security reasons we'll have to
1443 // refuse to go ahead with this call.
1444
1445 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1446 return nullptr;
1447 }
1448
1449 // Grab a reference to the calling documents security info (if any)
1450 // and URIs as they may be lost in the call to Reset().
1451 nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo();
1452 nsCOMPtr<nsIURI> uri = callerDoc->GetDocumentURI();
1453 nsCOMPtr<nsIURI> baseURI = callerDoc->GetBaseURI();
1454 nsCOMPtr<nsIPrincipal> callerPrincipal = callerDoc->NodePrincipal();
1455 nsCOMPtr<nsIChannel> callerChannel = callerDoc->GetChannel();
1456
1457 // We're called from script. Make sure the script is from the same
1458 // origin, not just that the caller can access the document. This is
1459 // needed to keep document principals from ever changing, which is
1460 // needed because of the way we use our XOW code, and is a sane
1461 // thing to do anyways.
1462
1463 bool equals = false;
1464 if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) ||
1465 !equals) {
1466
1467 #ifdef DEBUG
1468 nsCOMPtr<nsIURI> callerDocURI = callerDoc->GetDocumentURI();
1469 nsCOMPtr<nsIURI> thisURI = nsIDocument::GetDocumentURI();
1470 nsAutoCString callerSpec;
1471 nsAutoCString thisSpec;
1472 if (callerDocURI) {
1473 callerDocURI->GetSpec(callerSpec);
1474 }
1475 if (thisURI) {
1476 thisURI->GetSpec(thisSpec);
1477 }
1478 printf("nsHTMLDocument::Open callerDoc %s this %s\n", callerSpec.get(), thisSpec.get());
1479 #endif
1480
1481 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1482 return nullptr;
1483 }
1484
1485 // Stop current loads targeted at the window this document is in.
1486 if (mScriptGlobalObject) {
1487 nsCOMPtr<nsIContentViewer> cv;
1488 shell->GetContentViewer(getter_AddRefs(cv));
1489
1490 if (cv) {
1491 bool okToUnload;
1492 if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) && !okToUnload) {
1493 // We don't want to unload, so stop here, but don't throw an
1494 // exception.
1495 nsCOMPtr<nsIDocument> ret = this;
1496 return ret.forget();
1497 }
1498 }
1499
1500 nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
1501 webnav->Stop(nsIWebNavigation::STOP_NETWORK);
1502
1503 // The Stop call may have cancelled the onload blocker request or prevented
1504 // it from getting added, so we need to make sure it gets added to the
1505 // document again otherwise the document could have a non-zero onload block
1506 // count without the onload blocker request being in the loadgroup.
1507 EnsureOnloadBlocker();
1508 }
1509
1510 // The open occurred after the document finished loading.
1511 // So we reset the document and create a new one.
1512 nsCOMPtr<nsIChannel> channel;
1513 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
1514
1515 rv = NS_NewChannel(getter_AddRefs(channel), uri, nullptr, group);
1516
1517 if (rv.Failed()) {
1518 return nullptr;
1519 }
1520
1521 // We can't depend on channels implementing property bags, so do our
1522 // base URI manually after reset.
1523
1524 // Set the caller principal, if any, on the channel so that we'll
1525 // make sure to use it when we reset.
1526 rv = channel->SetOwner(callerPrincipal);
1527 if (rv.Failed()) {
1528 return nullptr;
1529 }
1530
1531 if (callerChannel) {
1532 nsLoadFlags callerLoadFlags;
1533 rv = callerChannel->GetLoadFlags(&callerLoadFlags);
1534 if (rv.Failed()) {
1535 return nullptr;
1536 }
1537
1538 nsLoadFlags loadFlags;
1539 rv = channel->GetLoadFlags(&loadFlags);
1540 if (rv.Failed()) {
1541 return nullptr;
1542 }
1543
1544 loadFlags |= callerLoadFlags & nsIRequest::INHIBIT_PERSISTENT_CACHING;
1545
1546 rv = channel->SetLoadFlags(loadFlags);
1547 if (rv.Failed()) {
1548 return nullptr;
1549 }
1550
1551 // If the user has allowed mixed content on the rootDoc, then we should propogate it
1552 // down to the new document channel.
1553 bool rootHasSecureConnection = false;
1554 bool allowMixedContent = false;
1555 bool isDocShellRoot = false;
1556 nsresult rvalue = shell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isDocShellRoot);
1557 if (NS_SUCCEEDED(rvalue) && allowMixedContent && isDocShellRoot) {
1558 shell->SetMixedContentChannel(channel);
1559 }
1560 }
1561
1562 // Before we reset the doc notify the globalwindow of the change,
1563 // but only if we still have a window (i.e. our window object the
1564 // current inner window in our outer window).
1565
1566 // Hold onto ourselves on the offchance that we're down to one ref
1567 nsCOMPtr<nsIDocument> kungFuDeathGrip = this;
1568
1569 nsPIDOMWindow *window = GetInnerWindow();
1570 if (window) {
1571 // Remember the old scope in case the call to SetNewDocument changes it.
1572 nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject));
1573
1574 #ifdef DEBUG
1575 bool willReparent = mWillReparent;
1576 mWillReparent = true;
1577 #endif
1578
1579 // Should this pass true for aForceReuseInnerWindow?
1580 rv = window->SetNewDocument(this, nullptr, false);
1581 if (rv.Failed()) {
1582 return nullptr;
1583 }
1584
1585 #ifdef DEBUG
1586 mWillReparent = willReparent;
1587 #endif
1588
1589 // Now make sure we're not flagged as the initial document anymore, now
1590 // that we've had stuff done to us. From now on, if anyone tries to
1591 // document.open() us, they get a new inner window.
1592 SetIsInitialDocument(false);
1593
1594 nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject));
1595 JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
1596 if (oldScope && newScope != oldScope && wrapper) {
1597 JSAutoCompartment ac(cx, wrapper);
1598 rv = mozilla::dom::ReparentWrapper(cx, wrapper);
1599 if (rv.Failed()) {
1600 return nullptr;
1601 }
1602 nsIXPConnect *xpc = nsContentUtils::XPConnect();
1603 rv = xpc->RescueOrphansInScope(cx, oldScope->GetGlobalJSObject());
1604 if (rv.Failed()) {
1605 return nullptr;
1606 }
1607 }
1608 }
1609
1610 mDidDocumentOpen = true;
1611
1612 // Call Reset(), this will now do the full reset
1613 Reset(channel, group);
1614 if (baseURI) {
1615 mDocumentBaseURI = baseURI;
1616 }
1617
1618 // Store the security info of the caller now that we're done
1619 // resetting the document.
1620 mSecurityInfo = securityInfo;
1621
1622 mParserAborted = false;
1623 mParser = nsHtml5Module::NewHtml5Parser();
1624 nsHtml5Module::Initialize(mParser, this, uri, shell, channel);
1625
1626 // This will be propagated to the parser when someone actually calls write()
1627 SetContentTypeInternal(contentType);
1628
1629 // Prepare the docshell and the document viewer for the impending
1630 // out of band document.write()
1631 shell->PrepareForNewContentModel();
1632
1633 // Now check whether we were opened with a "replace" argument. If
1634 // so, we need to tell the docshell to not create a new history
1635 // entry for this load. Otherwise, make sure that we're doing a normal load,
1636 // not whatever type of load was previously done on this docshell.
1637 shell->SetLoadType(aReplace.LowerCaseEqualsLiteral("replace") ?
1638 LOAD_NORMAL_REPLACE : LOAD_NORMAL);
1639
1640 nsCOMPtr<nsIContentViewer> cv;
1641 shell->GetContentViewer(getter_AddRefs(cv));
1642 if (cv) {
1643 cv->LoadStart(this);
1644 }
1645
1646 // Add a wyciwyg channel request into the document load group
1647 NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Open(): wyciwyg "
1648 "channel already exists!");
1649
1650 // In case the editor is listening and will see the new channel
1651 // being added, make sure mWriteLevel is non-zero so that the editor
1652 // knows that document.open/write/close() is being called on this
1653 // document.
1654 ++mWriteLevel;
1655
1656 CreateAndAddWyciwygChannel();
1657
1658 --mWriteLevel;
1659
1660 SetReadyStateInternal(nsIDocument::READYSTATE_LOADING);
1661
1662 // After changing everything around, make sure that the principal on the
1663 // document's compartment exactly matches NodePrincipal().
1664 DebugOnly<JSObject*> wrapper = GetWrapperPreserveColor();
1665 MOZ_ASSERT_IF(wrapper,
1666 JS_GetCompartmentPrincipals(js::GetObjectCompartment(wrapper)) ==
1667 nsJSPrincipals::get(NodePrincipal()));
1668
1669 return kungFuDeathGrip.forget();
1670 }
1671
1672 NS_IMETHODIMP
1673 nsHTMLDocument::Clear()
1674 {
1675 // This method has been deprecated
1676 return NS_OK;
1677 }
1678
1679 NS_IMETHODIMP
1680 nsHTMLDocument::Close()
1681 {
1682 ErrorResult rv;
1683 Close(rv);
1684 return rv.ErrorCode();
1685 }
1686
1687 void
1688 nsHTMLDocument::Close(ErrorResult& rv)
1689 {
1690 if (!IsHTML()) {
1691 // No calling document.close() on XHTML!
1692
1693 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1694 return;
1695 }
1696
1697 if (!mParser || !mParser->IsScriptCreated()) {
1698 return;
1699 }
1700
1701 ++mWriteLevel;
1702 rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
1703 EmptyString(), nullptr, GetContentTypeInternal(), true);
1704 --mWriteLevel;
1705
1706 // Even if that Parse() call failed, do the rest of this method
1707
1708 // XXX Make sure that all the document.written content is
1709 // reflowed. We should remove this call once we change
1710 // nsHTMLDocument::OpenCommon() so that it completely destroys the
1711 // earlier document's content and frame hierarchy. Right now, it
1712 // re-uses the earlier document's root content object and
1713 // corresponding frame objects. These re-used frame objects think
1714 // that they have already been reflowed, so they drop initial
1715 // reflows. For certain cases of document.written content, like a
1716 // frameset document, the dropping of the initial reflow means
1717 // that we end up in document.close() without appended any reflow
1718 // commands to the reflow queue and, consequently, without adding
1719 // the dummy layout request to the load group. Since the dummy
1720 // layout request is not added to the load group, the onload
1721 // handler of the frameset fires before the frames get reflowed
1722 // and loaded. That is the long explanation for why we need this
1723 // one line of code here!
1724 // XXXbz as far as I can tell this may not be needed anymore; all
1725 // the testcases in bug 57636 pass without this line... Leaving
1726 // it be for now, though. In any case, there's no reason to do
1727 // this if we have no presshell, since in that case none of the
1728 // above about reusing frames applies.
1729 //
1730 // XXXhsivonen keeping this around for bug 577508 / 253951 still :-(
1731 if (GetShell()) {
1732 FlushPendingNotifications(Flush_Layout);
1733 }
1734
1735 // Removing the wyciwygChannel here is wrong when document.close() is
1736 // called from within the document itself. However, legacy requires the
1737 // channel to be removed here. Otherwise, the load event never fires.
1738 NS_ASSERTION(mWyciwygChannel, "nsHTMLDocument::Close(): Trying to remove "
1739 "nonexistent wyciwyg channel!");
1740 RemoveWyciwygChannel();
1741 NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Close(): "
1742 "nsIWyciwygChannel could not be removed!");
1743 }
1744
1745 void
1746 nsHTMLDocument::WriteCommon(JSContext *cx,
1747 const Sequence<nsString>& aText,
1748 bool aNewlineTerminate,
1749 mozilla::ErrorResult& rv)
1750 {
1751 // Fast path the common case
1752 if (aText.Length() == 1) {
1753 rv = WriteCommon(cx, aText[0], aNewlineTerminate);
1754 } else {
1755 // XXXbz it would be nice if we could pass all the strings to the parser
1756 // without having to do all this copying and then ask it to start
1757 // parsing....
1758 nsString text;
1759 for (uint32_t i = 0; i < aText.Length(); ++i) {
1760 text.Append(aText[i]);
1761 }
1762 rv = WriteCommon(cx, text, aNewlineTerminate);
1763 }
1764 }
1765
1766 nsresult
1767 nsHTMLDocument::WriteCommon(JSContext *cx,
1768 const nsAString& aText,
1769 bool aNewlineTerminate)
1770 {
1771 mTooDeepWriteRecursion =
1772 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
1773 NS_ENSURE_STATE(!mTooDeepWriteRecursion);
1774
1775 if (!IsHTML() || mDisableDocWrite) {
1776 // No calling document.write*() on XHTML!
1777
1778 return NS_ERROR_DOM_INVALID_STATE_ERR;
1779 }
1780
1781 if (mParserAborted) {
1782 // Hixie says aborting the parser doesn't undefine the insertion point.
1783 // However, since we null out mParser in that case, we track the
1784 // theoretically defined insertion point using mParserAborted.
1785 return NS_OK;
1786 }
1787
1788 nsresult rv = NS_OK;
1789
1790 void *key = GenerateParserKey();
1791 if (mParser && !mParser->IsInsertionPointDefined()) {
1792 if (mExternalScriptsBeingEvaluated) {
1793 // Instead of implying a call to document.open(), ignore the call.
1794 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1795 NS_LITERAL_CSTRING("DOM Events"), this,
1796 nsContentUtils::eDOM_PROPERTIES,
1797 "DocumentWriteIgnored",
1798 nullptr, 0,
1799 mDocumentURI);
1800 return NS_OK;
1801 }
1802 mParser->Terminate();
1803 NS_ASSERTION(!mParser, "mParser should have been null'd out");
1804 }
1805
1806 if (!mParser) {
1807 if (mExternalScriptsBeingEvaluated) {
1808 // Instead of implying a call to document.open(), ignore the call.
1809 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1810 NS_LITERAL_CSTRING("DOM Events"), this,
1811 nsContentUtils::eDOM_PROPERTIES,
1812 "DocumentWriteIgnored",
1813 nullptr, 0,
1814 mDocumentURI);
1815 return NS_OK;
1816 }
1817 nsCOMPtr<nsISupports> ignored;
1818 rv = Open(NS_LITERAL_STRING("text/html"), EmptyString(), EmptyString(), cx,
1819 1, getter_AddRefs(ignored));
1820
1821 // If Open() fails, or if it didn't create a parser (as it won't
1822 // if the user chose to not discard the current document through
1823 // onbeforeunload), don't write anything.
1824 if (NS_FAILED(rv) || !mParser) {
1825 return rv;
1826 }
1827 NS_ABORT_IF_FALSE(!JS_IsExceptionPending(cx),
1828 "Open() succeeded but JS exception is pending");
1829 }
1830
1831 static NS_NAMED_LITERAL_STRING(new_line, "\n");
1832
1833 // Save the data in cache if the write isn't from within the doc
1834 if (mWyciwygChannel && !key) {
1835 if (!aText.IsEmpty()) {
1836 mWyciwygChannel->WriteToCacheEntry(aText);
1837 }
1838
1839 if (aNewlineTerminate) {
1840 mWyciwygChannel->WriteToCacheEntry(new_line);
1841 }
1842 }
1843
1844 ++mWriteLevel;
1845
1846 // This could be done with less code, but for performance reasons it
1847 // makes sense to have the code for two separate Parse() calls here
1848 // since the concatenation of strings costs more than we like. And
1849 // why pay that price when we don't need to?
1850 if (aNewlineTerminate) {
1851 rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
1852 aText + new_line, key, GetContentTypeInternal(), false);
1853 } else {
1854 rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
1855 aText, key, GetContentTypeInternal(), false);
1856 }
1857
1858 --mWriteLevel;
1859
1860 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
1861
1862 return rv;
1863 }
1864
1865 NS_IMETHODIMP
1866 nsHTMLDocument::Write(const nsAString& aText, JSContext *cx)
1867 {
1868 return WriteCommon(cx, aText, false);
1869 }
1870
1871 void
1872 nsHTMLDocument::Write(JSContext* cx, const Sequence<nsString>& aText,
1873 ErrorResult& rv)
1874 {
1875 WriteCommon(cx, aText, false, rv);
1876 }
1877
1878 NS_IMETHODIMP
1879 nsHTMLDocument::Writeln(const nsAString& aText, JSContext *cx)
1880 {
1881 return WriteCommon(cx, aText, true);
1882 }
1883
1884 void
1885 nsHTMLDocument::Writeln(JSContext* cx, const Sequence<nsString>& aText,
1886 ErrorResult& rv)
1887 {
1888 WriteCommon(cx, aText, true, rv);
1889 }
1890
1891 bool
1892 nsHTMLDocument::MatchNameAttribute(nsIContent* aContent, int32_t aNamespaceID,
1893 nsIAtom* aAtom, void* aData)
1894 {
1895 NS_PRECONDITION(aContent, "Must have content node to work with!");
1896 nsString* elementName = static_cast<nsString*>(aData);
1897 return
1898 aContent->GetNameSpaceID() == kNameSpaceID_XHTML &&
1899 aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
1900 *elementName, eCaseMatters);
1901 }
1902
1903 /* static */
1904 void*
1905 nsHTMLDocument::UseExistingNameString(nsINode* aRootNode, const nsString* aName)
1906 {
1907 return const_cast<nsString*>(aName);
1908 }
1909
1910 NS_IMETHODIMP
1911 nsHTMLDocument::GetElementsByName(const nsAString& aElementName,
1912 nsIDOMNodeList** aReturn)
1913 {
1914 *aReturn = GetElementsByName(aElementName).take();
1915 return NS_OK;
1916 }
1917
1918 static bool MatchItems(nsIContent* aContent, int32_t aNameSpaceID,
1919 nsIAtom* aAtom, void* aData)
1920 {
1921 if (!(aContent->IsElement() && aContent->AsElement()->IsHTML())) {
1922 return false;
1923 }
1924
1925 nsGenericHTMLElement* elem = static_cast<nsGenericHTMLElement*>(aContent);
1926 if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::itemscope) ||
1927 elem->HasAttr(kNameSpaceID_None, nsGkAtoms::itemprop)) {
1928 return false;
1929 }
1930
1931 nsTArray<nsCOMPtr<nsIAtom> >* tokens = static_cast<nsTArray<nsCOMPtr<nsIAtom> >*>(aData);
1932 if (tokens->IsEmpty()) {
1933 return true;
1934 }
1935
1936 const nsAttrValue* attr = elem->GetParsedAttr(nsGkAtoms::itemtype);
1937 if (!attr)
1938 return false;
1939
1940 for (uint32_t i = 0; i < tokens->Length(); i++) {
1941 if (!attr->Contains(tokens->ElementAt(i), eCaseMatters)) {
1942 return false;
1943 }
1944 }
1945 return true;
1946 }
1947
1948 static void DestroyTokens(void* aData)
1949 {
1950 nsTArray<nsCOMPtr<nsIAtom> >* tokens = static_cast<nsTArray<nsCOMPtr<nsIAtom> >*>(aData);
1951 delete tokens;
1952 }
1953
1954 static void* CreateTokens(nsINode* aRootNode, const nsString* types)
1955 {
1956 nsTArray<nsCOMPtr<nsIAtom> >* tokens = new nsTArray<nsCOMPtr<nsIAtom> >();
1957 nsAString::const_iterator iter, end;
1958 types->BeginReading(iter);
1959 types->EndReading(end);
1960
1961 // skip initial whitespace
1962 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1963 ++iter;
1964 }
1965
1966 // parse the tokens
1967 while (iter != end) {
1968 nsAString::const_iterator start(iter);
1969
1970 do {
1971 ++iter;
1972 } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
1973
1974 nsCOMPtr<nsIAtom> token = do_GetAtom(Substring(start, iter));
1975 tokens->AppendElement(token);
1976
1977 // skip whitespace
1978 while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
1979 ++iter;
1980 }
1981 }
1982 return tokens;
1983 }
1984
1985 NS_IMETHODIMP
1986 nsHTMLDocument::GetItems(const nsAString& types, nsIDOMNodeList** aReturn)
1987 {
1988 *aReturn = GetItems(types).take();
1989 return NS_OK;
1990 }
1991
1992 already_AddRefed<nsINodeList>
1993 nsHTMLDocument::GetItems(const nsAString& aTypeNames)
1994 {
1995 return NS_GetFuncStringNodeList(this, MatchItems, DestroyTokens, CreateTokens,
1996 aTypeNames);
1997 }
1998
1999 void
2000 nsHTMLDocument::AddedForm()
2001 {
2002 ++mNumForms;
2003 }
2004
2005 void
2006 nsHTMLDocument::RemovedForm()
2007 {
2008 --mNumForms;
2009 }
2010
2011 int32_t
2012 nsHTMLDocument::GetNumFormsSynchronous()
2013 {
2014 return mNumForms;
2015 }
2016
2017 NS_IMETHODIMP
2018 nsHTMLDocument::GetAlinkColor(nsAString& aAlinkColor)
2019 {
2020 aAlinkColor.Truncate();
2021
2022 HTMLBodyElement* body = GetBodyElement();
2023 if (body) {
2024 body->GetALink(aAlinkColor);
2025 }
2026
2027 return NS_OK;
2028 }
2029
2030 NS_IMETHODIMP
2031 nsHTMLDocument::SetAlinkColor(const nsAString& aAlinkColor)
2032 {
2033 HTMLBodyElement* body = GetBodyElement();
2034 if (body) {
2035 body->SetALink(aAlinkColor);
2036 }
2037
2038 return NS_OK;
2039 }
2040
2041 NS_IMETHODIMP
2042 nsHTMLDocument::GetLinkColor(nsAString& aLinkColor)
2043 {
2044 aLinkColor.Truncate();
2045
2046 HTMLBodyElement* body = GetBodyElement();
2047 if (body) {
2048 body->GetLink(aLinkColor);
2049 }
2050
2051 return NS_OK;
2052 }
2053
2054 NS_IMETHODIMP
2055 nsHTMLDocument::SetLinkColor(const nsAString& aLinkColor)
2056 {
2057 HTMLBodyElement* body = GetBodyElement();
2058 if (body) {
2059 body->SetLink(aLinkColor);
2060 }
2061
2062 return NS_OK;
2063 }
2064
2065 NS_IMETHODIMP
2066 nsHTMLDocument::GetVlinkColor(nsAString& aVlinkColor)
2067 {
2068 aVlinkColor.Truncate();
2069
2070 HTMLBodyElement* body = GetBodyElement();
2071 if (body) {
2072 body->GetVLink(aVlinkColor);
2073 }
2074
2075 return NS_OK;
2076 }
2077
2078 NS_IMETHODIMP
2079 nsHTMLDocument::SetVlinkColor(const nsAString& aVlinkColor)
2080 {
2081 HTMLBodyElement* body = GetBodyElement();
2082 if (body) {
2083 body->SetVLink(aVlinkColor);
2084 }
2085
2086 return NS_OK;
2087 }
2088
2089 NS_IMETHODIMP
2090 nsHTMLDocument::GetBgColor(nsAString& aBgColor)
2091 {
2092 aBgColor.Truncate();
2093
2094 HTMLBodyElement* body = GetBodyElement();
2095 if (body) {
2096 body->GetBgColor(aBgColor);
2097 }
2098
2099 return NS_OK;
2100 }
2101
2102 NS_IMETHODIMP
2103 nsHTMLDocument::SetBgColor(const nsAString& aBgColor)
2104 {
2105 HTMLBodyElement* body = GetBodyElement();
2106 if (body) {
2107 body->SetBgColor(aBgColor);
2108 }
2109
2110 return NS_OK;
2111 }
2112
2113 NS_IMETHODIMP
2114 nsHTMLDocument::GetFgColor(nsAString& aFgColor)
2115 {
2116 aFgColor.Truncate();
2117
2118 HTMLBodyElement* body = GetBodyElement();
2119 if (body) {
2120 body->GetText(aFgColor);
2121 }
2122
2123 return NS_OK;
2124 }
2125
2126 NS_IMETHODIMP
2127 nsHTMLDocument::SetFgColor(const nsAString& aFgColor)
2128 {
2129 HTMLBodyElement* body = GetBodyElement();
2130 if (body) {
2131 body->SetText(aFgColor);
2132 }
2133
2134 return NS_OK;
2135 }
2136
2137
2138 NS_IMETHODIMP
2139 nsHTMLDocument::GetEmbeds(nsIDOMHTMLCollection** aEmbeds)
2140 {
2141 NS_ADDREF(*aEmbeds = Embeds());
2142 return NS_OK;
2143 }
2144
2145 nsIHTMLCollection*
2146 nsHTMLDocument::Embeds()
2147 {
2148 if (!mEmbeds) {
2149 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed, nsGkAtoms::embed);
2150 }
2151 return mEmbeds;
2152 }
2153
2154 NS_IMETHODIMP
2155 nsHTMLDocument::GetSelection(nsISelection** aReturn)
2156 {
2157 ErrorResult rv;
2158 NS_IF_ADDREF(*aReturn = GetSelection(rv));
2159 return rv.ErrorCode();
2160 }
2161
2162 Selection*
2163 nsHTMLDocument::GetSelection(ErrorResult& aRv)
2164 {
2165 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetScopeObject());
2166 if (!window) {
2167 return nullptr;
2168 }
2169
2170 NS_ASSERTION(window->IsInnerWindow(), "Should have inner window here!");
2171 if (!window->IsCurrentInnerWindow()) {
2172 return nullptr;
2173 }
2174
2175 return static_cast<nsGlobalWindow*>(window.get())->GetSelection(aRv);
2176 }
2177
2178 NS_IMETHODIMP
2179 nsHTMLDocument::CaptureEvents()
2180 {
2181 WarnOnceAbout(nsIDocument::eUseOfCaptureEvents);
2182 return NS_OK;
2183 }
2184
2185 NS_IMETHODIMP
2186 nsHTMLDocument::ReleaseEvents()
2187 {
2188 WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
2189 return NS_OK;
2190 }
2191
2192 // Mapped to document.embeds for NS4 compatibility
2193 NS_IMETHODIMP
2194 nsHTMLDocument::GetPlugins(nsIDOMHTMLCollection** aPlugins)
2195 {
2196 *aPlugins = nullptr;
2197
2198 return GetEmbeds(aPlugins);
2199 }
2200
2201 nsIHTMLCollection*
2202 nsHTMLDocument::Plugins()
2203 {
2204 return Embeds();
2205 }
2206
2207 nsISupports*
2208 nsHTMLDocument::ResolveName(const nsAString& aName, nsWrapperCache **aCache)
2209 {
2210 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aName);
2211 if (!entry) {
2212 *aCache = nullptr;
2213 return nullptr;
2214 }
2215
2216 nsBaseContentList *list = entry->GetNameContentList();
2217 uint32_t length = list ? list->Length() : 0;
2218
2219 if (length > 0) {
2220 if (length == 1) {
2221 // Only one element in the list, return the element instead of returning
2222 // the list.
2223 nsIContent *node = list->Item(0);
2224 *aCache = node;
2225 return node;
2226 }
2227
2228 // The list contains more than one element, return the whole list.
2229 *aCache = list;
2230 return list;
2231 }
2232
2233 // No named items were found, see if there's one registerd by id for aName.
2234 Element *e = entry->GetIdElement();
2235
2236 if (e && nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(e)) {
2237 *aCache = e;
2238 return e;
2239 }
2240
2241 *aCache = nullptr;
2242 return nullptr;
2243 }
2244
2245 void
2246 nsHTMLDocument::NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
2247 JS::MutableHandle<JSObject*> aRetval,
2248 ErrorResult& rv)
2249 {
2250 nsWrapperCache* cache;
2251 nsISupports* supp = ResolveName(aName, &cache);
2252 if (!supp) {
2253 aFound = false;
2254 aRetval.set(nullptr);
2255 return;
2256 }
2257
2258 JS::Rooted<JS::Value> val(cx);
2259 // XXXbz Should we call the (slightly misnamed, really) WrapNativeParent
2260 // here?
2261 if (!dom::WrapObject(cx, supp, cache, nullptr, &val)) {
2262 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
2263 return;
2264 }
2265 aFound = true;
2266 aRetval.set(&val.toObject());
2267 }
2268
2269 bool
2270 nsHTMLDocument::NameIsEnumerable(const nsAString& aName)
2271 {
2272 return true;
2273 }
2274
2275 static PLDHashOperator
2276 IdentifierMapEntryAddNames(nsIdentifierMapEntry* aEntry, void* aArg)
2277 {
2278 nsTArray<nsString>* aNames = static_cast<nsTArray<nsString>*>(aArg);
2279 if (aEntry->HasNameElement() ||
2280 aEntry->HasIdElementExposedAsHTMLDocumentProperty()) {
2281 aNames->AppendElement(aEntry->GetKey());
2282 }
2283 return PL_DHASH_NEXT;
2284 }
2285
2286 void
2287 nsHTMLDocument::GetSupportedNames(unsigned, nsTArray<nsString>& aNames)
2288 {
2289 mIdentifierMap.EnumerateEntries(IdentifierMapEntryAddNames, &aNames);
2290 }
2291
2292 //----------------------------
2293
2294 // forms related stuff
2295
2296 NS_IMETHODIMP
2297 nsHTMLDocument::GetForms(nsIDOMHTMLCollection** aForms)
2298 {
2299 NS_ADDREF(*aForms = nsHTMLDocument::GetForms());
2300 return NS_OK;
2301 }
2302
2303 nsContentList*
2304 nsHTMLDocument::GetForms()
2305 {
2306 if (!mForms) {
2307 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form, nsGkAtoms::form);
2308 }
2309
2310 return mForms;
2311 }
2312
2313 static bool MatchFormControls(nsIContent* aContent, int32_t aNamespaceID,
2314 nsIAtom* aAtom, void* aData)
2315 {
2316 return aContent->IsNodeOfType(nsIContent::eHTML_FORM_CONTROL);
2317 }
2318
2319 nsContentList*
2320 nsHTMLDocument::GetFormControls()
2321 {
2322 if (!mFormControls) {
2323 mFormControls = new nsContentList(this, MatchFormControls, nullptr, nullptr);
2324 }
2325
2326 return mFormControls;
2327 }
2328
2329 nsresult
2330 nsHTMLDocument::CreateAndAddWyciwygChannel(void)
2331 {
2332 nsresult rv = NS_OK;
2333 nsAutoCString url, originalSpec;
2334
2335 mDocumentURI->GetSpec(originalSpec);
2336
2337 // Generate the wyciwyg url
2338 url = NS_LITERAL_CSTRING("wyciwyg://")
2339 + nsPrintfCString("%d", gWyciwygSessionCnt++)
2340 + NS_LITERAL_CSTRING("/")
2341 + originalSpec;
2342
2343 nsCOMPtr<nsIURI> wcwgURI;
2344 NS_NewURI(getter_AddRefs(wcwgURI), url);
2345
2346 // Create the nsIWyciwygChannel to store out-of-band
2347 // document.write() script to cache
2348 nsCOMPtr<nsIChannel> channel;
2349 // Create a wyciwyg Channel
2350 rv = NS_NewChannel(getter_AddRefs(channel), wcwgURI);
2351 NS_ENSURE_SUCCESS(rv, rv);
2352
2353 mWyciwygChannel = do_QueryInterface(channel);
2354
2355 mWyciwygChannel->SetSecurityInfo(mSecurityInfo);
2356
2357 // Note: we want to treat this like a "previous document" hint so that,
2358 // e.g. a <meta> tag in the document.write content can override it.
2359 SetDocumentCharacterSetSource(kCharsetFromHintPrevDoc);
2360 mWyciwygChannel->SetCharsetAndSource(kCharsetFromHintPrevDoc,
2361 GetDocumentCharacterSet());
2362
2363 // Use our new principal
2364 channel->SetOwner(NodePrincipal());
2365
2366 // Inherit load flags from the original document's channel
2367 channel->SetLoadFlags(mLoadFlags);
2368
2369 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
2370
2371 // Use the Parent document's loadgroup to trigger load notifications
2372 if (loadGroup && channel) {
2373 rv = channel->SetLoadGroup(loadGroup);
2374 NS_ENSURE_SUCCESS(rv, rv);
2375
2376 nsLoadFlags loadFlags = 0;
2377 channel->GetLoadFlags(&loadFlags);
2378 loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
2379 channel->SetLoadFlags(loadFlags);
2380
2381 channel->SetOriginalURI(wcwgURI);
2382
2383 rv = loadGroup->AddRequest(mWyciwygChannel, nullptr);
2384 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add request to load group.");
2385 }
2386
2387 return rv;
2388 }
2389
2390 nsresult
2391 nsHTMLDocument::RemoveWyciwygChannel(void)
2392 {
2393 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
2394
2395 // note there can be a write request without a load group if
2396 // this is a synchronously constructed about:blank document
2397 if (loadGroup && mWyciwygChannel) {
2398 mWyciwygChannel->CloseCacheEntry(NS_OK);
2399 loadGroup->RemoveRequest(mWyciwygChannel, nullptr, NS_OK);
2400 }
2401
2402 mWyciwygChannel = nullptr;
2403
2404 return NS_OK;
2405 }
2406
2407 void *
2408 nsHTMLDocument::GenerateParserKey(void)
2409 {
2410 if (!mScriptLoader) {
2411 // If we don't have a script loader, then the parser probably isn't parsing
2412 // anything anyway, so just return null.
2413 return nullptr;
2414 }
2415
2416 // The script loader provides us with the currently executing script element,
2417 // which is guaranteed to be unique per script.
2418 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
2419 if (script && mParser && mParser->IsScriptCreated()) {
2420 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
2421 if (creatorParser != mParser) {
2422 // Make scripts that aren't inserted by the active parser of this document
2423 // participate in the context of the script that document.open()ed
2424 // this document.
2425 return nullptr;
2426 }
2427 }
2428 return script;
2429 }
2430
2431 /* attribute DOMString designMode; */
2432 NS_IMETHODIMP
2433 nsHTMLDocument::GetDesignMode(nsAString & aDesignMode)
2434 {
2435 if (HasFlag(NODE_IS_EDITABLE)) {
2436 aDesignMode.AssignLiteral("on");
2437 }
2438 else {
2439 aDesignMode.AssignLiteral("off");
2440 }
2441 return NS_OK;
2442 }
2443
2444 void
2445 nsHTMLDocument::MaybeEditingStateChanged()
2446 {
2447 if (!mPendingMaybeEditingStateChanged &&
2448 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
2449 if (nsContentUtils::IsSafeToRunScript()) {
2450 EditingStateChanged();
2451 } else if (!mInDestructor) {
2452 nsContentUtils::AddScriptRunner(
2453 NS_NewRunnableMethod(this, &nsHTMLDocument::MaybeEditingStateChanged));
2454 }
2455 }
2456 }
2457
2458 void
2459 nsHTMLDocument::EndUpdate(nsUpdateType aUpdateType)
2460 {
2461 const bool reset = !mPendingMaybeEditingStateChanged;
2462 mPendingMaybeEditingStateChanged = true;
2463 nsDocument::EndUpdate(aUpdateType);
2464 if (reset) {
2465 mPendingMaybeEditingStateChanged = false;
2466 }
2467 MaybeEditingStateChanged();
2468 }
2469
2470
2471 // Helper class, used below in ChangeContentEditableCount().
2472 class DeferredContentEditableCountChangeEvent : public nsRunnable
2473 {
2474 public:
2475 DeferredContentEditableCountChangeEvent(nsHTMLDocument *aDoc,
2476 nsIContent *aElement)
2477 : mDoc(aDoc)
2478 , mElement(aElement)
2479 {
2480 }
2481
2482 NS_IMETHOD Run() {
2483 if (mElement && mElement->OwnerDoc() == mDoc) {
2484 mDoc->DeferredContentEditableCountChange(mElement);
2485 }
2486 return NS_OK;
2487 }
2488
2489 private:
2490 nsRefPtr<nsHTMLDocument> mDoc;
2491 nsCOMPtr<nsIContent> mElement;
2492 };
2493
2494 nsresult
2495 nsHTMLDocument::ChangeContentEditableCount(nsIContent *aElement,
2496 int32_t aChange)
2497 {
2498 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
2499 "Trying to decrement too much.");
2500
2501 mContentEditableCount += aChange;
2502
2503 nsContentUtils::AddScriptRunner(
2504 new DeferredContentEditableCountChangeEvent(this, aElement));
2505
2506 return NS_OK;
2507 }
2508
2509 void
2510 nsHTMLDocument::DeferredContentEditableCountChange(nsIContent *aElement)
2511 {
2512 if (mParser ||
2513 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
2514 return;
2515 }
2516
2517 EditingState oldState = mEditingState;
2518
2519 nsresult rv = EditingStateChanged();
2520 NS_ENSURE_SUCCESS_VOID(rv);
2521
2522 if (oldState == mEditingState && mEditingState == eContentEditable) {
2523 // We just changed the contentEditable state of a node, we need to reset
2524 // the spellchecking state of that node.
2525 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
2526 if (node) {
2527 nsPIDOMWindow *window = GetWindow();
2528 if (!window)
2529 return;
2530
2531 nsIDocShell *docshell = window->GetDocShell();
2532 if (!docshell)
2533 return;
2534
2535 nsCOMPtr<nsIEditor> editor;
2536 docshell->GetEditor(getter_AddRefs(editor));
2537 if (editor) {
2538 nsRefPtr<nsRange> range = new nsRange(aElement);
2539 rv = range->SelectNode(node);
2540 if (NS_FAILED(rv)) {
2541 // The node might be detached from the document at this point,
2542 // which would cause this call to fail. In this case, we can
2543 // safely ignore the contenteditable count change.
2544 return;
2545 }
2546
2547 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
2548 rv = editor->GetInlineSpellChecker(false,
2549 getter_AddRefs(spellChecker));
2550 NS_ENSURE_SUCCESS_VOID(rv);
2551
2552 if (spellChecker) {
2553 rv = spellChecker->SpellCheckRange(range);
2554 }
2555 }
2556 }
2557 }
2558 }
2559
2560 HTMLAllCollection*
2561 nsHTMLDocument::All()
2562 {
2563 if (!mAll) {
2564 mAll = new HTMLAllCollection(this);
2565 }
2566 return mAll;
2567 }
2568
2569 void
2570 nsHTMLDocument::GetAll(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
2571 ErrorResult& aRv)
2572 {
2573 aRetval.set(All()->GetObject(aCx, aRv));
2574 }
2575
2576 static void
2577 NotifyEditableStateChange(nsINode *aNode, nsIDocument *aDocument)
2578 {
2579 for (nsIContent* child = aNode->GetFirstChild();
2580 child;
2581 child = child->GetNextSibling()) {
2582 if (child->IsElement()) {
2583 child->AsElement()->UpdateState(true);
2584 }
2585 NotifyEditableStateChange(child, aDocument);
2586 }
2587 }
2588
2589 void
2590 nsHTMLDocument::TearingDownEditor(nsIEditor *aEditor)
2591 {
2592 if (IsEditingOn()) {
2593 EditingState oldState = mEditingState;
2594 mEditingState = eTearingDown;
2595
2596 nsCOMPtr<nsIPresShell> presShell = GetShell();
2597 if (!presShell)
2598 return;
2599
2600 nsCOMArray<nsIStyleSheet> agentSheets;
2601 presShell->GetAgentStyleSheets(agentSheets);
2602
2603 RemoveFromAgentSheets(agentSheets, NS_LITERAL_STRING("resource://gre/res/contenteditable.css"));
2604 if (oldState == eDesignMode)
2605 RemoveFromAgentSheets(agentSheets, NS_LITERAL_STRING("resource://gre/res/designmode.css"));
2606
2607 presShell->SetAgentStyleSheets(agentSheets);
2608
2609 presShell->ReconstructStyleData();
2610 }
2611 }
2612
2613 nsresult
2614 nsHTMLDocument::TurnEditingOff()
2615 {
2616 NS_ASSERTION(mEditingState != eOff, "Editing is already off.");
2617
2618 nsPIDOMWindow *window = GetWindow();
2619 if (!window)
2620 return NS_ERROR_FAILURE;
2621
2622 nsIDocShell *docshell = window->GetDocShell();
2623 if (!docshell)
2624 return NS_ERROR_FAILURE;
2625
2626 nsresult rv;
2627 nsCOMPtr<nsIEditingSession> editSession = do_GetInterface(docshell, &rv);
2628 NS_ENSURE_SUCCESS(rv, rv);
2629
2630 // turn editing off
2631 rv = editSession->TearDownEditorOnWindow(window);
2632 NS_ENSURE_SUCCESS(rv, rv);
2633
2634 mEditingState = eOff;
2635
2636 return NS_OK;
2637 }
2638
2639 static bool HasPresShell(nsPIDOMWindow *aWindow)
2640 {
2641 nsIDocShell *docShell = aWindow->GetDocShell();
2642 if (!docShell)
2643 return false;
2644 return docShell->GetPresShell() != nullptr;
2645 }
2646
2647 nsresult
2648 nsHTMLDocument::SetEditingState(EditingState aState)
2649 {
2650 mEditingState = aState;
2651 return NS_OK;
2652 }
2653
2654 nsresult
2655 nsHTMLDocument::EditingStateChanged()
2656 {
2657 if (mRemovedFromDocShell) {
2658 return NS_OK;
2659 }
2660
2661 if (mEditingState == eSettingUp || mEditingState == eTearingDown) {
2662 // XXX We shouldn't recurse.
2663 return NS_OK;
2664 }
2665
2666 bool designMode = HasFlag(NODE_IS_EDITABLE);
2667 EditingState newState = designMode ? eDesignMode :
2668 (mContentEditableCount > 0 ? eContentEditable : eOff);
2669 if (mEditingState == newState) {
2670 // No changes in editing mode.
2671 return NS_OK;
2672 }
2673
2674 if (newState == eOff) {
2675 // Editing is being turned off.
2676 nsAutoScriptBlocker scriptBlocker;
2677 NotifyEditableStateChange(this, this);
2678 return TurnEditingOff();
2679 }
2680
2681 // Flush out style changes on our _parent_ document, if any, so that
2682 // our check for a presshell won't get stale information.
2683 if (mParentDocument) {
2684 mParentDocument->FlushPendingNotifications(Flush_Style);
2685 }
2686
2687 // get editing session, make sure this is a strong reference so the
2688 // window can't get deleted during the rest of this call.
2689 nsCOMPtr<nsPIDOMWindow> window = GetWindow();
2690 if (!window)
2691 return NS_ERROR_FAILURE;
2692
2693 nsIDocShell *docshell = window->GetDocShell();
2694 if (!docshell)
2695 return NS_ERROR_FAILURE;
2696
2697 nsresult rv;
2698 nsCOMPtr<nsIEditingSession> editSession = do_GetInterface(docshell, &rv);
2699 NS_ENSURE_SUCCESS(rv, rv);
2700
2701 nsCOMPtr<nsIEditor> existingEditor;
2702 editSession->GetEditorForWindow(window, getter_AddRefs(existingEditor));
2703 if (existingEditor) {
2704 // We might already have an editor if it was set up for mail, let's see
2705 // if this is actually the case.
2706 nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(existingEditor);
2707 NS_ABORT_IF_FALSE(htmlEditor, "If we have an editor, it must be an HTML editor");
2708 uint32_t flags = 0;
2709 existingEditor->GetFlags(&flags);
2710 if (flags & nsIPlaintextEditor::eEditorMailMask) {
2711 // We already have a mail editor, then we should not attempt to create
2712 // another one.
2713 return NS_OK;
2714 }
2715 }
2716
2717 if (!HasPresShell(window)) {
2718 // We should not make the window editable or setup its editor.
2719 // It's probably style=display:none.
2720 return NS_OK;
2721 }
2722
2723 bool makeWindowEditable = mEditingState == eOff;
2724 bool updateState = false;
2725 bool spellRecheckAll = false;
2726 nsCOMPtr<nsIEditor> editor;
2727
2728 {
2729 EditingState oldState = mEditingState;
2730 nsAutoEditingState push(this, eSettingUp);
2731
2732 if (makeWindowEditable) {
2733 // Editing is being turned on (through designMode or contentEditable)
2734 // Turn on editor.
2735 // XXX This can cause flushing which can change the editing state, so make
2736 // sure to avoid recursing.
2737 rv = editSession->MakeWindowEditable(window, "html", false, false,
2738 true);
2739 NS_ENSURE_SUCCESS(rv, rv);
2740 }
2741
2742 // XXX Need to call TearDownEditorOnWindow for all failures.
2743 docshell->GetEditor(getter_AddRefs(editor));
2744 if (!editor)
2745 return NS_ERROR_FAILURE;
2746
2747 nsCOMPtr<nsIPresShell> presShell = GetShell();
2748 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
2749
2750 // If we're entering the design mode, put the selection at the beginning of
2751 // the document for compatibility reasons.
2752 if (designMode && oldState == eOff) {
2753 editor->BeginningOfDocument();
2754 }
2755
2756 nsCOMArray<nsIStyleSheet> agentSheets;
2757 rv = presShell->GetAgentStyleSheets(agentSheets);
2758 NS_ENSURE_SUCCESS(rv, rv);
2759
2760 nsCOMPtr<nsIURI> uri;
2761 rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("resource://gre/res/contenteditable.css"));
2762 NS_ENSURE_SUCCESS(rv, rv);
2763
2764 nsRefPtr<nsCSSStyleSheet> sheet;
2765 rv = LoadChromeSheetSync(uri, true, getter_AddRefs(sheet));
2766 NS_ENSURE_TRUE(sheet, rv);
2767
2768 bool result = agentSheets.AppendObject(sheet);
2769 NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
2770
2771 // Should we update the editable state of all the nodes in the document? We
2772 // need to do this when the designMode value changes, as that overrides
2773 // specific states on the elements.
2774 if (designMode) {
2775 // designMode is being turned on (overrides contentEditable).
2776 rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("resource://gre/res/designmode.css"));
2777 NS_ENSURE_SUCCESS(rv, rv);
2778
2779 rv = LoadChromeSheetSync(uri, true, getter_AddRefs(sheet));
2780 NS_ENSURE_TRUE(sheet, rv);
2781
2782 result = agentSheets.AppendObject(sheet);
2783 NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
2784
2785 updateState = true;
2786 spellRecheckAll = oldState == eContentEditable;
2787 }
2788 else if (oldState == eDesignMode) {
2789 // designMode is being turned off (contentEditable is still on).
2790 RemoveFromAgentSheets(agentSheets, NS_LITERAL_STRING("resource://gre/res/designmode.css"));
2791
2792 updateState = true;
2793 }
2794
2795 rv = presShell->SetAgentStyleSheets(agentSheets);
2796 NS_ENSURE_SUCCESS(rv, rv);
2797
2798 presShell->ReconstructStyleData();
2799 }
2800
2801 mEditingState = newState;
2802
2803 if (makeWindowEditable) {
2804 // Set the editor to not insert br's on return when in p
2805 // elements by default.
2806 // XXX Do we only want to do this for designMode?
2807 bool unused;
2808 rv = ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), false,
2809 NS_LITERAL_STRING("false"), &unused);
2810
2811 if (NS_FAILED(rv)) {
2812 // Editor setup failed. Editing is not on after all.
2813 // XXX Should we reset the editable flag on nodes?
2814 editSession->TearDownEditorOnWindow(window);
2815 mEditingState = eOff;
2816
2817 return rv;
2818 }
2819 }
2820
2821 if (updateState) {
2822 nsAutoScriptBlocker scriptBlocker;
2823 NotifyEditableStateChange(this, this);
2824 }
2825
2826 // Resync the editor's spellcheck state.
2827 if (spellRecheckAll) {
2828 nsCOMPtr<nsISelectionController> selcon;
2829 nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
2830 NS_ENSURE_SUCCESS(rv, rv);
2831
2832 nsCOMPtr<nsISelection> spellCheckSelection;
2833 rv = selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
2834 getter_AddRefs(spellCheckSelection));
2835 if (NS_SUCCEEDED(rv)) {
2836 spellCheckSelection->RemoveAllRanges();
2837 }
2838 }
2839 editor->SyncRealTimeSpell();
2840
2841 return NS_OK;
2842 }
2843
2844 NS_IMETHODIMP
2845 nsHTMLDocument::SetDesignMode(const nsAString & aDesignMode)
2846 {
2847 ErrorResult rv;
2848 SetDesignMode(aDesignMode, rv);
2849 return rv.ErrorCode();
2850 }
2851
2852 void
2853 nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode, ErrorResult& rv)
2854 {
2855 if (!nsContentUtils::IsCallerChrome()) {
2856 nsCOMPtr<nsIPrincipal> subject;
2857 nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
2858 rv = secMan->GetSubjectPrincipal(getter_AddRefs(subject));
2859 if (rv.Failed()) {
2860 return;
2861 }
2862 if (subject) {
2863 bool subsumes;
2864 rv = subject->Subsumes(NodePrincipal(), &subsumes);
2865 if (rv.Failed()) {
2866 return;
2867 }
2868
2869 if (!subsumes) {
2870 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
2871 return;
2872 }
2873 }
2874 }
2875
2876 bool editableMode = HasFlag(NODE_IS_EDITABLE);
2877 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
2878 SetEditableFlag(!editableMode);
2879
2880 rv = EditingStateChanged();
2881 }
2882 }
2883
2884 nsresult
2885 nsHTMLDocument::GetMidasCommandManager(nsICommandManager** aCmdMgr)
2886 {
2887 // initialize return value
2888 NS_ENSURE_ARG_POINTER(aCmdMgr);
2889
2890 // check if we have it cached
2891 if (mMidasCommandManager) {
2892 NS_ADDREF(*aCmdMgr = mMidasCommandManager);
2893 return NS_OK;
2894 }
2895
2896 *aCmdMgr = nullptr;
2897
2898 nsPIDOMWindow *window = GetWindow();
2899 if (!window)
2900 return NS_ERROR_FAILURE;
2901
2902 nsIDocShell *docshell = window->GetDocShell();
2903 if (!docshell)
2904 return NS_ERROR_FAILURE;
2905
2906 mMidasCommandManager = do_GetInterface(docshell);
2907 if (!mMidasCommandManager)
2908 return NS_ERROR_FAILURE;
2909
2910 NS_ADDREF(*aCmdMgr = mMidasCommandManager);
2911
2912 return NS_OK;
2913 }
2914
2915
2916 struct MidasCommand {
2917 const char* incomingCommandString;
2918 const char* internalCommandString;
2919 const char* internalParamString;
2920 bool useNewParam;
2921 bool convertToBoolean;
2922 };
2923
2924 static const struct MidasCommand gMidasCommandTable[] = {
2925 { "bold", "cmd_bold", "", true, false },
2926 { "italic", "cmd_italic", "", true, false },
2927 { "underline", "cmd_underline", "", true, false },
2928 { "strikethrough", "cmd_strikethrough", "", true, false },
2929 { "subscript", "cmd_subscript", "", true, false },
2930 { "superscript", "cmd_superscript", "", true, false },
2931 { "cut", "cmd_cut", "", true, false },
2932 { "copy", "cmd_copy", "", true, false },
2933 { "paste", "cmd_paste", "", true, false },
2934 { "delete", "cmd_deleteCharBackward", "", true, false },
2935 { "forwarddelete", "cmd_deleteCharForward", "", true, false },
2936 { "selectall", "cmd_selectAll", "", true, false },
2937 { "undo", "cmd_undo", "", true, false },
2938 { "redo", "cmd_redo", "", true, false },
2939 { "indent", "cmd_indent", "", true, false },
2940 { "outdent", "cmd_outdent", "", true, false },
2941 { "backcolor", "cmd_highlight", "", false, false },
2942 { "forecolor", "cmd_fontColor", "", false, false },
2943 { "hilitecolor", "cmd_highlight", "", false, false },
2944 { "fontname", "cmd_fontFace", "", false, false },
2945 { "fontsize", "cmd_fontSize", "", false, false },
2946 { "increasefontsize", "cmd_increaseFont", "", false, false },
2947 { "decreasefontsize", "cmd_decreaseFont", "", false, false },
2948 { "inserthorizontalrule", "cmd_insertHR", "", true, false },
2949 { "createlink", "cmd_insertLinkNoUI", "", false, false },
2950 { "insertimage", "cmd_insertImageNoUI", "", false, false },
2951 { "inserthtml", "cmd_insertHTML", "", false, false },
2952 { "inserttext", "cmd_insertText", "", false, false },
2953 { "gethtml", "cmd_getContents", "", false, false },
2954 { "justifyleft", "cmd_align", "left", true, false },
2955 { "justifyright", "cmd_align", "right", true, false },
2956 { "justifycenter", "cmd_align", "center", true, false },
2957 { "justifyfull", "cmd_align", "justify", true, false },
2958 { "removeformat", "cmd_removeStyles", "", true, false },
2959 { "unlink", "cmd_removeLinks", "", true, false },
2960 { "insertorderedlist", "cmd_ol", "", true, false },
2961 { "insertunorderedlist", "cmd_ul", "", true, false },
2962 { "insertparagraph", "cmd_paragraphState", "p", true, false },
2963 { "formatblock", "cmd_paragraphState", "", false, false },
2964 { "heading", "cmd_paragraphState", "", false, false },
2965 { "styleWithCSS", "cmd_setDocumentUseCSS", "", false, true },
2966 { "contentReadOnly", "cmd_setDocumentReadOnly", "", false, true },
2967 { "insertBrOnReturn", "cmd_insertBrOnReturn", "", false, true },
2968 { "enableObjectResizing", "cmd_enableObjectResizing", "", false, true },
2969 { "enableInlineTableEditing", "cmd_enableInlineTableEditing", "", false, true },
2970 #if 0
2971 // no editor support to remove alignments right now
2972 { "justifynone", "cmd_align", "", true, false },
2973
2974 // the following will need special review before being turned on
2975 { "saveas", "cmd_saveAs", "", true, false },
2976 { "print", "cmd_print", "", true, false },
2977 #endif
2978 { nullptr, nullptr, nullptr, false, false }
2979 };
2980
2981 #define MidasCommandCount ((sizeof(gMidasCommandTable) / sizeof(struct MidasCommand)) - 1)
2982
2983 static const char* const gBlocks[] = {
2984 "ADDRESS",
2985 "BLOCKQUOTE",
2986 "DD",
2987 "DIV",
2988 "DL",
2989 "DT",
2990 "H1",
2991 "H2",
2992 "H3",
2993 "H4",
2994 "H5",
2995 "H6",
2996 "P",
2997 "PRE"
2998 };
2999
3000 static bool
3001 ConvertToMidasInternalCommandInner(const nsAString& inCommandID,
3002 const nsAString& inParam,
3003 nsACString& outCommandID,
3004 nsACString& outParam,
3005 bool& outIsBoolean,
3006 bool& outBooleanValue,
3007 bool aIgnoreParams)
3008 {
3009 NS_ConvertUTF16toUTF8 convertedCommandID(inCommandID);
3010
3011 // Hack to support old boolean commands that were backwards (see bug 301490).
3012 bool invertBool = false;
3013 if (convertedCommandID.LowerCaseEqualsLiteral("usecss")) {
3014 convertedCommandID.Assign("styleWithCSS");
3015 invertBool = true;
3016 } else if (convertedCommandID.LowerCaseEqualsLiteral("readonly")) {
3017 convertedCommandID.Assign("contentReadOnly");
3018 invertBool = true;
3019 }
3020
3021 uint32_t i;
3022 bool found = false;
3023 for (i = 0; i < MidasCommandCount; ++i) {
3024 if (convertedCommandID.Equals(gMidasCommandTable[i].incomingCommandString,
3025 nsCaseInsensitiveCStringComparator())) {
3026 found = true;
3027 break;
3028 }
3029 }
3030
3031 if (!found) {
3032 // reset results if the command is not found in our table
3033 outCommandID.SetLength(0);
3034 outParam.SetLength(0);
3035 outIsBoolean = false;
3036 return false;
3037 }
3038
3039 // set outCommandID (what we use internally)
3040 outCommandID.Assign(gMidasCommandTable[i].internalCommandString);
3041
3042 // set outParam & outIsBoolean based on flags from the table
3043 outIsBoolean = gMidasCommandTable[i].convertToBoolean;
3044
3045 if (aIgnoreParams) {
3046 // No further work to do
3047 return true;
3048 }
3049
3050 if (gMidasCommandTable[i].useNewParam) {
3051 // Just have to copy it, no checking
3052 outParam.Assign(gMidasCommandTable[i].internalParamString);
3053 return true;
3054 }
3055
3056 // handle checking of param passed in
3057 if (outIsBoolean) {
3058 // If this is a boolean value and it's not explicitly false (e.g. no value)
3059 // we default to "true". For old backwards commands we invert the check (see
3060 // bug 301490).
3061 if (invertBool) {
3062 outBooleanValue = inParam.LowerCaseEqualsLiteral("false");
3063 } else {
3064 outBooleanValue = !inParam.LowerCaseEqualsLiteral("false");
3065 }
3066 outParam.Truncate();
3067
3068 return true;
3069 }
3070
3071 // String parameter -- see if we need to convert it (necessary for
3072 // cmd_paragraphState and cmd_fontSize)
3073 if (outCommandID.EqualsLiteral("cmd_paragraphState")) {
3074 const char16_t* start = inParam.BeginReading();
3075 const char16_t* end = inParam.EndReading();
3076 if (start != end && *start == '<' && *(end - 1) == '>') {
3077 ++start;
3078 --end;
3079 }
3080
3081 NS_ConvertUTF16toUTF8 convertedParam(Substring(start, end));
3082 uint32_t j;
3083 for (j = 0; j < ArrayLength(gBlocks); ++j) {
3084 if (convertedParam.Equals(gBlocks[j],
3085 nsCaseInsensitiveCStringComparator())) {
3086 outParam.Assign(gBlocks[j]);
3087 break;
3088 }
3089 }
3090
3091 if (j == ArrayLength(gBlocks)) {
3092 outParam.Truncate();
3093 }
3094 } else if (outCommandID.EqualsLiteral("cmd_fontSize")) {
3095 // Per editing spec as of April 23, 2012, we need to reject the value if
3096 // it's not a valid floating-point number surrounded by optional whitespace.
3097 // Otherwise, we parse it as a legacy font size. For now, we just parse as
3098 // a legacy font size regardless (matching WebKit) -- bug 747879.
3099 outParam.Truncate();
3100 int32_t size = nsContentUtils::ParseLegacyFontSize(inParam);
3101 if (size) {
3102 outParam.AppendInt(size);
3103 }
3104 } else {
3105 CopyUTF16toUTF8(inParam, outParam);
3106 }
3107
3108 return true;
3109 }
3110
3111 static bool
3112 ConvertToMidasInternalCommand(const nsAString & inCommandID,
3113 const nsAString & inParam,
3114 nsACString& outCommandID,
3115 nsACString& outParam,
3116 bool& outIsBoolean,
3117 bool& outBooleanValue)
3118 {
3119 return ConvertToMidasInternalCommandInner(inCommandID, inParam, outCommandID,
3120 outParam, outIsBoolean,
3121 outBooleanValue, false);
3122 }
3123
3124 static bool
3125 ConvertToMidasInternalCommand(const nsAString & inCommandID,
3126 nsACString& outCommandID)
3127 {
3128 nsAutoCString dummyCString;
3129 nsAutoString dummyString;
3130 bool dummyBool;
3131 return ConvertToMidasInternalCommandInner(inCommandID, dummyString,
3132 outCommandID, dummyCString,
3133 dummyBool, dummyBool, true);
3134 }
3135
3136 /* TODO: don't let this call do anything if the page is not done loading */
3137 /* boolean execCommand(in DOMString commandID, in boolean doShowUI,
3138 in DOMString value); */
3139 NS_IMETHODIMP
3140 nsHTMLDocument::ExecCommand(const nsAString& commandID,
3141 bool doShowUI,
3142 const nsAString& value,
3143 bool* _retval)
3144 {
3145 ErrorResult rv;
3146 *_retval = ExecCommand(commandID, doShowUI, value, rv);
3147 return rv.ErrorCode();
3148 }
3149
3150 bool
3151 nsHTMLDocument::ExecCommand(const nsAString& commandID,
3152 bool doShowUI,
3153 const nsAString& value,
3154 ErrorResult& rv)
3155 {
3156 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
3157 // this might add some ugly JS dependencies?
3158
3159 nsAutoCString cmdToDispatch, paramStr;
3160 bool isBool, boolVal;
3161 if (!ConvertToMidasInternalCommand(commandID, value,
3162 cmdToDispatch, paramStr,
3163 isBool, boolVal)) {
3164 return false;
3165 }
3166
3167 // if editing is not on, bail
3168 if (!IsEditingOnAfterFlush()) {
3169 rv.Throw(NS_ERROR_FAILURE);
3170 return false;
3171 }
3172
3173 // if they are requesting UI from us, let's fail since we have no UI
3174 if (doShowUI) {
3175 return false;
3176 }
3177
3178 if (commandID.LowerCaseEqualsLiteral("gethtml")) {
3179 rv.Throw(NS_ERROR_FAILURE);
3180 return false;
3181 }
3182
3183 bool restricted = commandID.LowerCaseEqualsLiteral("cut") ||
3184 commandID.LowerCaseEqualsLiteral("copy")||
3185 commandID.LowerCaseEqualsLiteral("paste");
3186 if (restricted && !nsContentUtils::IsCallerChrome()) {
3187 rv = NS_ERROR_DOM_SECURITY_ERR;
3188 return false;
3189 }
3190
3191 // get command manager and dispatch command to our window if it's acceptable
3192 nsCOMPtr<nsICommandManager> cmdMgr;
3193 GetMidasCommandManager(getter_AddRefs(cmdMgr));
3194 if (!cmdMgr) {
3195 rv.Throw(NS_ERROR_FAILURE);
3196 return false;
3197 }
3198
3199 nsIDOMWindow* window = GetWindow();
3200 if (!window) {
3201 rv.Throw(NS_ERROR_FAILURE);
3202 return false;
3203 }
3204
3205 if ((cmdToDispatch.EqualsLiteral("cmd_fontSize") ||
3206 cmdToDispatch.EqualsLiteral("cmd_insertImageNoUI") ||
3207 cmdToDispatch.EqualsLiteral("cmd_insertLinkNoUI") ||
3208 cmdToDispatch.EqualsLiteral("cmd_paragraphState")) &&
3209 paramStr.IsEmpty()) {
3210 // Invalid value, return false
3211 return false;
3212 }
3213
3214 // Return false for disabled commands (bug 760052)
3215 bool enabled = false;
3216 cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &enabled);
3217 if (!enabled) {
3218 return false;
3219 }
3220
3221 if (!isBool && paramStr.IsEmpty()) {
3222 rv = cmdMgr->DoCommand(cmdToDispatch.get(), nullptr, window);
3223 } else {
3224 // we have a command that requires a parameter, create params
3225 nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
3226 NS_COMMAND_PARAMS_CONTRACTID);
3227 if (!cmdParams) {
3228 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
3229 return false;
3230 }
3231
3232 if (isBool) {
3233 rv = cmdParams->SetBooleanValue("state_attribute", boolVal);
3234 } else if (cmdToDispatch.EqualsLiteral("cmd_fontFace")) {
3235 rv = cmdParams->SetStringValue("state_attribute", value);
3236 } else if (cmdToDispatch.EqualsLiteral("cmd_insertHTML") ||
3237 cmdToDispatch.EqualsLiteral("cmd_insertText")) {
3238 rv = cmdParams->SetStringValue("state_data", value);
3239 } else {
3240 rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
3241 }
3242 if (rv.Failed()) {
3243 return false;
3244 }
3245 rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
3246 }
3247
3248 return !rv.Failed();
3249 }
3250
3251 /* boolean queryCommandEnabled(in DOMString commandID); */
3252 NS_IMETHODIMP
3253 nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID,
3254 bool* _retval)
3255 {
3256 ErrorResult rv;
3257 *_retval = QueryCommandEnabled(commandID, rv);
3258 return rv.ErrorCode();
3259 }
3260
3261 bool
3262 nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID, ErrorResult& rv)
3263 {
3264 nsAutoCString cmdToDispatch;
3265 if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
3266 return false;
3267 }
3268
3269 // if editing is not on, bail
3270 if (!IsEditingOnAfterFlush()) {
3271 rv.Throw(NS_ERROR_FAILURE);
3272 return false;
3273 }
3274
3275 // get command manager and dispatch command to our window if it's acceptable
3276 nsCOMPtr<nsICommandManager> cmdMgr;
3277 GetMidasCommandManager(getter_AddRefs(cmdMgr));
3278 if (!cmdMgr) {
3279 rv.Throw(NS_ERROR_FAILURE);
3280 return false;
3281 }
3282
3283 nsIDOMWindow* window = GetWindow();
3284 if (!window) {
3285 rv.Throw(NS_ERROR_FAILURE);
3286 return false;
3287 }
3288
3289 bool retval;
3290 rv = cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &retval);
3291 return retval;
3292 }
3293
3294 /* boolean queryCommandIndeterm (in DOMString commandID); */
3295 NS_IMETHODIMP
3296 nsHTMLDocument::QueryCommandIndeterm(const nsAString & commandID,
3297 bool *_retval)
3298 {
3299 ErrorResult rv;
3300 *_retval = QueryCommandIndeterm(commandID, rv);
3301 return rv.ErrorCode();
3302 }
3303
3304 bool
3305 nsHTMLDocument::QueryCommandIndeterm(const nsAString& commandID, ErrorResult& rv)
3306 {
3307 nsAutoCString cmdToDispatch;
3308 if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
3309 return false;
3310 }
3311
3312 // if editing is not on, bail
3313 if (!IsEditingOnAfterFlush()) {
3314 rv.Throw(NS_ERROR_FAILURE);
3315 return false;
3316 }
3317
3318 // get command manager and dispatch command to our window if it's acceptable
3319 nsCOMPtr<nsICommandManager> cmdMgr;
3320 GetMidasCommandManager(getter_AddRefs(cmdMgr));
3321 if (!cmdMgr) {
3322 rv.Throw(NS_ERROR_FAILURE);
3323 return false;
3324 }
3325
3326 nsIDOMWindow* window = GetWindow();
3327 if (!window) {
3328 rv.Throw(NS_ERROR_FAILURE);
3329 return false;
3330 }
3331
3332 nsresult res;
3333 nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
3334 NS_COMMAND_PARAMS_CONTRACTID, &res);
3335 if (NS_FAILED(res)) {
3336 rv.Throw(res);
3337 return false;
3338 }
3339
3340 rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
3341 if (rv.Failed()) {
3342 return false;
3343 }
3344
3345 // If command does not have a state_mixed value, this call fails and sets
3346 // retval to false. This is fine -- we want to return false in that case
3347 // anyway (bug 738385), so we just don't throw regardless.
3348 bool retval = false;
3349 cmdParams->GetBooleanValue("state_mixed", &retval);
3350 return retval;
3351 }
3352
3353 /* boolean queryCommandState(in DOMString commandID); */
3354 NS_IMETHODIMP
3355 nsHTMLDocument::QueryCommandState(const nsAString & commandID, bool *_retval)
3356 {
3357 ErrorResult rv;
3358 *_retval = QueryCommandState(commandID, rv);
3359 return rv.ErrorCode();
3360 }
3361
3362 bool
3363 nsHTMLDocument::QueryCommandState(const nsAString& commandID, ErrorResult& rv)
3364 {
3365 nsAutoCString cmdToDispatch, paramToCheck;
3366 bool dummy, dummy2;
3367 if (!ConvertToMidasInternalCommand(commandID, commandID,
3368 cmdToDispatch, paramToCheck,
3369 dummy, dummy2)) {
3370 return false;
3371 }
3372
3373 // if editing is not on, bail
3374 if (!IsEditingOnAfterFlush()) {
3375 rv.Throw(NS_ERROR_FAILURE);
3376 return false;
3377 }
3378
3379 // get command manager and dispatch command to our window if it's acceptable
3380 nsCOMPtr<nsICommandManager> cmdMgr;
3381 GetMidasCommandManager(getter_AddRefs(cmdMgr));
3382 if (!cmdMgr) {
3383 rv.Throw(NS_ERROR_FAILURE);
3384 return false;
3385 }
3386
3387 nsIDOMWindow* window = GetWindow();
3388 if (!window) {
3389 rv.Throw(NS_ERROR_FAILURE);
3390 return false;
3391 }
3392
3393 if (commandID.LowerCaseEqualsLiteral("usecss")) {
3394 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
3395 // return false always.
3396 return false;
3397 }
3398
3399 nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
3400 NS_COMMAND_PARAMS_CONTRACTID);
3401 if (!cmdParams) {
3402 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
3403 return false;
3404 }
3405
3406 rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
3407 if (rv.Failed()) {
3408 return false;
3409 }
3410
3411 // handle alignment as a special case (possibly other commands too?)
3412 // Alignment is special because the external api is individual
3413 // commands but internally we use cmd_align with different
3414 // parameters. When getting the state of this command, we need to
3415 // return the boolean for this particular alignment rather than the
3416 // string of 'which alignment is this?'
3417 if (cmdToDispatch.EqualsLiteral("cmd_align")) {
3418 char * actualAlignmentType = nullptr;
3419 rv = cmdParams->GetCStringValue("state_attribute", &actualAlignmentType);
3420 bool retval = false;
3421 if (!rv.Failed() && actualAlignmentType && actualAlignmentType[0]) {
3422 retval = paramToCheck.Equals(actualAlignmentType);
3423 }
3424 if (actualAlignmentType) {
3425 nsMemory::Free(actualAlignmentType);
3426 }
3427 return retval;
3428 }
3429
3430 // If command does not have a state_all value, this call fails and sets
3431 // retval to false. This is fine -- we want to return false in that case
3432 // anyway (bug 738385), so we just succeed and return false regardless.
3433 bool retval = false;
3434 cmdParams->GetBooleanValue("state_all", &retval);
3435 return retval;
3436 }
3437
3438 /* boolean queryCommandSupported(in DOMString commandID); */
3439 NS_IMETHODIMP
3440 nsHTMLDocument::QueryCommandSupported(const nsAString & commandID,
3441 bool *_retval)
3442 {
3443 *_retval = QueryCommandSupported(commandID);
3444 return NS_OK;
3445 }
3446
3447 bool
3448 nsHTMLDocument::QueryCommandSupported(const nsAString& commandID)
3449 {
3450 // commandID is supported if it can be converted to a Midas command
3451 nsAutoCString cmdToDispatch;
3452 return ConvertToMidasInternalCommand(commandID, cmdToDispatch);
3453 }
3454
3455 /* DOMString queryCommandValue(in DOMString commandID); */
3456 NS_IMETHODIMP
3457 nsHTMLDocument::QueryCommandValue(const nsAString & commandID,
3458 nsAString &_retval)
3459 {
3460 ErrorResult rv;
3461 QueryCommandValue(commandID, _retval, rv);
3462 return rv.ErrorCode();
3463 }
3464
3465 void
3466 nsHTMLDocument::QueryCommandValue(const nsAString& commandID,
3467 nsAString& aValue,
3468 ErrorResult& rv)
3469 {
3470 aValue.Truncate();
3471
3472 nsAutoCString cmdToDispatch, paramStr;
3473 if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
3474 // Return empty string
3475 return;
3476 }
3477
3478 // if editing is not on, bail
3479 if (!IsEditingOnAfterFlush()) {
3480 rv.Throw(NS_ERROR_FAILURE);
3481 return;
3482 }
3483
3484 // get command manager and dispatch command to our window if it's acceptable
3485 nsCOMPtr<nsICommandManager> cmdMgr;
3486 GetMidasCommandManager(getter_AddRefs(cmdMgr));
3487 if (!cmdMgr) {
3488 rv.Throw(NS_ERROR_FAILURE);
3489 return;
3490 }
3491
3492 nsIDOMWindow* window = GetWindow();
3493 if (!window) {
3494 rv.Throw(NS_ERROR_FAILURE);
3495 return;
3496 }
3497
3498 // create params
3499 nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
3500 NS_COMMAND_PARAMS_CONTRACTID);
3501 if (!cmdParams) {
3502 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
3503 return;
3504 }
3505
3506 // this is a special command since we are calling DoCommand rather than
3507 // GetCommandState like the other commands
3508 if (cmdToDispatch.EqualsLiteral("cmd_getContents")) {
3509 rv = cmdParams->SetBooleanValue("selection_only", true);
3510 if (rv.Failed()) {
3511 return;
3512 }
3513 rv = cmdParams->SetCStringValue("format", "text/html");
3514 if (rv.Failed()) {
3515 return;
3516 }
3517 rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
3518 if (rv.Failed()) {
3519 return;
3520 }
3521 rv = cmdParams->GetStringValue("result", aValue);
3522 return;
3523 }
3524
3525 rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
3526 if (rv.Failed()) {
3527 return;
3528 }
3529
3530 rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
3531 if (rv.Failed()) {
3532 return;
3533 }
3534
3535 // If command does not have a state_attribute value, this call fails, and
3536 // aValue will wind up being the empty string. This is fine -- we want to
3537 // return "" in that case anyway (bug 738385), so we just return NS_OK
3538 // regardless.
3539 nsXPIDLCString cStringResult;
3540 cmdParams->GetCStringValue("state_attribute",
3541 getter_Copies(cStringResult));
3542 CopyUTF8toUTF16(cStringResult, aValue);
3543 }
3544
3545 nsresult
3546 nsHTMLDocument::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
3547 {
3548 NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager,
3549 "Can't import this document into another document!");
3550
3551 nsRefPtr<nsHTMLDocument> clone = new nsHTMLDocument();
3552 nsresult rv = CloneDocHelper(clone.get());
3553 NS_ENSURE_SUCCESS(rv, rv);
3554
3555 // State from nsHTMLDocument
3556 clone->mLoadFlags = mLoadFlags;
3557
3558 return CallQueryInterface(clone.get(), aResult);
3559 }
3560
3561 bool
3562 nsHTMLDocument::IsEditingOnAfterFlush()
3563 {
3564 nsIDocument* doc = GetParentDocument();
3565 if (doc) {
3566 // Make sure frames are up to date, since that can affect whether
3567 // we're editable.
3568 doc->FlushPendingNotifications(Flush_Frames);
3569 }
3570
3571 return IsEditingOn();
3572 }
3573
3574 void
3575 nsHTMLDocument::RemovedFromDocShell()
3576 {
3577 mEditingState = eOff;
3578 nsDocument::RemovedFromDocShell();
3579 }
3580
3581 /* virtual */ void
3582 nsHTMLDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
3583 {
3584 nsDocument::DocAddSizeOfExcludingThis(aWindowSizes);
3585
3586 // Measurement of the following members may be added later if DMD finds it is
3587 // worthwhile:
3588 // - mImages
3589 // - mApplets
3590 // - mEmbeds
3591 // - mLinks
3592 // - mAnchors
3593 // - mScripts
3594 // - mForms
3595 // - mFormControls
3596 // - mWyciwygChannel
3597 // - mMidasCommandManager
3598 }
3599
3600 bool
3601 nsHTMLDocument::WillIgnoreCharsetOverride()
3602 {
3603 if (!mIsRegularHTML) {
3604 return true;
3605 }
3606 if (mCharacterSetSource == kCharsetFromByteOrderMark) {
3607 return true;
3608 }
3609 if (!EncodingUtils::IsAsciiCompatible(mCharacterSet)) {
3610 return true;
3611 }
3612 nsCOMPtr<nsIWyciwygChannel> wyciwyg = do_QueryInterface(mChannel);
3613 if (wyciwyg) {
3614 return true;
3615 }
3616 nsIURI* uri = GetOriginalURI();
3617 if (uri) {
3618 bool schemeIs = false;
3619 uri->SchemeIs("about", &schemeIs);
3620 if (schemeIs) {
3621 return true;
3622 }
3623 bool isResource;
3624 nsresult rv = NS_URIChainHasFlags(uri,
3625 nsIProtocolHandler::URI_IS_UI_RESOURCE,
3626 &isResource);
3627 if (NS_FAILED(rv) || isResource) {
3628 return true;
3629 }
3630 }
3631 return false;
3632 }

mercurial