1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 22:39:36 +00:00
Files
UXP/dom/html/nsHTMLDocument.cpp
T
wolfbeast fc5d61608c Issue #1118 - Part 5: Change the way document.open() works
This changes the work we do for document.open() in the following ways:
- We no longer create a new Window when doing document.open().
  We use the same Window but remove all the event listeners on the
  existing DOM tree and Window before removing the document's existing
  children to provide a clean slate document to use for .write().
- We no longer create a session history entry (previously would be a
  wyciwyg URI). We now replace the current one, effectively losing the
  entry for the original document.
- We now support document.open() on windowless documents.
2019-12-28 21:20:03 +08:00

3551 lines
104 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsHTMLDocument.h"
#include "nsIContentPolicy.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/HTMLAllCollection.h"
#include "nsCOMPtr.h"
#include "nsGlobalWindow.h"
#include "nsXPIDLString.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsIDocumentLoader.h"
#include "nsIHTMLContentSink.h"
#include "nsIXMLContentSink.h"
#include "nsHTMLParts.h"
#include "nsHTMLStyleSheet.h"
#include "nsGkAtoms.h"
#include "nsIPresShell.h"
#include "nsPresContext.h"
#include "nsIDOMNode.h" // for Find
#include "nsIDOMNodeList.h"
#include "nsIDOMElement.h"
#include "nsPIDOMWindow.h"
#include "nsDOMString.h"
#include "nsIStreamListener.h"
#include "nsIURI.h"
#include "nsIIOService.h"
#include "nsNetUtil.h"
#include "nsIPrivateBrowsingChannel.h"
#include "nsIContentViewerContainer.h"
#include "nsIContentViewer.h"
#include "nsDocShell.h"
#include "nsDocShellLoadTypes.h"
#include "nsIWebNavigation.h"
#include "nsIBaseWindow.h"
#include "nsIWebShellServices.h"
#include "nsIScriptContext.h"
#include "nsIXPConnect.h"
#include "nsContentList.h"
#include "nsError.h"
#include "nsIPrincipal.h"
#include "nsJSPrincipals.h"
#include "nsIScriptSecurityManager.h"
#include "nsAttrName.h"
#include "nsNodeUtils.h"
#include "nsNetCID.h"
#include "nsICookieService.h"
#include "nsIServiceManager.h"
#include "nsIConsoleService.h"
#include "nsIComponentManager.h"
#include "nsParserCIID.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMHTMLHeadElement.h"
#include "nsNameSpaceManager.h"
#include "nsGenericHTMLElement.h"
#include "mozilla/css/Loader.h"
#include "nsIHttpChannel.h"
#include "nsIFile.h"
#include "nsFrameSelection.h"
#include "nsISelectionPrivate.h"//for toStringwithformat code
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsIDocumentInlines.h"
#include "nsIDocumentEncoder.h" //for outputting selection
#include "nsICachingChannel.h"
#include "nsIContentViewer.h"
#include "nsIWyciwygChannel.h"
#include "nsIScriptElement.h"
#include "nsIScriptError.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"
#include "nsIEffectiveTLDService.h"
//AHMED 12-2
#include "nsBidiUtils.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FallbackEncoding.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/LoadInfo.h"
#include "nsIEditingSession.h"
#include "nsIEditor.h"
#include "nsNodeInfoManager.h"
#include "nsIPlaintextEditor.h"
#include "nsIHTMLEditor.h"
#include "nsIEditorStyleSheets.h"
#include "nsIInlineSpellChecker.h"
#include "nsRange.h"
#include "mozAutoDocUpdate.h"
#include "nsCCUncollectableMarker.h"
#include "nsHtml5Module.h"
#include "prprf.h"
#include "mozilla/dom/Element.h"
#include "mozilla/Preferences.h"
#include "nsMimeTypes.h"
#include "nsIRequest.h"
#include "nsHtml5TreeOpExecutor.h"
#include "nsHtml5Parser.h"
#include "nsSandboxFlags.h"
#include "nsIImageDocument.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLDocumentBinding.h"
#include "mozilla/dom/SimpleTreeIterator.h"
#include "nsCharsetSource.h"
#include "nsIStringBundle.h"
#include "nsDOMClassInfo.h"
#include "nsFocusManager.h"
#include "nsIFrame.h"
#include "nsIContent.h"
#include "nsIStructuredCloneContainer.h"
#include "nsLayoutStylesheetCache.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
using namespace mozilla;
using namespace mozilla::dom;
#define NS_MAX_DOCUMENT_WRITE_DEPTH 20
#include "prtime.h"
//#define DEBUG_charset
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
uint32_t nsHTMLDocument::gWyciwygSessionCnt = 0;
// this function will return false if the command is not recognized
// inCommandID will be converted as necessary for internal operations
// inParam will be converted as necessary for internal operations
// outParam will be Empty if no parameter is needed or if returning a boolean
// outIsBoolean will determine whether to send param as a boolean or string
// outBooleanParam will not be set unless outIsBoolean
static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
const nsAString & inParam,
nsACString& outCommandID,
nsACString& outParam,
bool& isBoolean,
bool& boolValue);
static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
nsACString& outCommandID);
// ==================================================================
// =
// ==================================================================
nsresult
NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData)
{
RefPtr<nsHTMLDocument> doc = new nsHTMLDocument();
nsresult rv = doc->Init();
if (NS_FAILED(rv)) {
*aInstancePtrResult = nullptr;
return rv;
}
doc->SetLoadedAsData(aLoadedAsData);
doc.forget(aInstancePtrResult);
return NS_OK;
}
// NOTE! nsDocument::operator new() zeroes out all members, so don't
// bother initializing members to 0.
nsHTMLDocument::nsHTMLDocument()
: nsDocument("text/html")
{
// NOTE! nsDocument::operator new() zeroes out all members, so don't
// bother initializing members to 0.
mType = eHTML;
mDefaultElementType = kNameSpaceID_XHTML;
mCompatMode = eCompatibility_NavQuirks;
}
nsHTMLDocument::~nsHTMLDocument()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTMLDocument, nsDocument,
mAll,
mImages,
mApplets,
mEmbeds,
mLinks,
mAnchors,
mScripts,
mForms,
mFormControls,
mWyciwygChannel,
mMidasCommandManager)
NS_IMPL_ADDREF_INHERITED(nsHTMLDocument, nsDocument)
NS_IMPL_RELEASE_INHERITED(nsHTMLDocument, nsDocument)
// QueryInterface implementation for nsHTMLDocument
NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLDocument)
NS_INTERFACE_TABLE_INHERITED(nsHTMLDocument, nsIHTMLDocument,
nsIDOMHTMLDocument)
NS_INTERFACE_TABLE_TAIL_INHERITING(nsDocument)
JSObject*
nsHTMLDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return HTMLDocumentBinding::Wrap(aCx, this, aGivenProto);
}
nsresult
nsHTMLDocument::Init()
{
nsresult rv = nsDocument::Init();
NS_ENSURE_SUCCESS(rv, rv);
// Now reset the compatibility mode of the CSSLoader
// to match our compat mode.
CSSLoader()->SetCompatibilityMode(mCompatMode);
return NS_OK;
}
void
nsHTMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
{
nsDocument::Reset(aChannel, aLoadGroup);
if (aChannel) {
aChannel->GetLoadFlags(&mLoadFlags);
}
}
void
nsHTMLDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
nsIPrincipal* aPrincipal)
{
mLoadFlags = nsIRequest::LOAD_NORMAL;
nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
mImages = nullptr;
mApplets = nullptr;
mEmbeds = nullptr;
mLinks = nullptr;
mAnchors = nullptr;
mScripts = nullptr;
mForms = nullptr;
NS_ASSERTION(!mWyciwygChannel,
"nsHTMLDocument::Reset() - Wyciwyg Channel still exists!");
mWyciwygChannel = nullptr;
// Make the content type default to "text/html", we are a HTML
// document, after all. Once we start getting data, this may be
// changed.
SetContentTypeInternal(nsDependentCString("text/html"));
}
already_AddRefed<nsIPresShell>
nsHTMLDocument::CreateShell(nsPresContext* aContext,
nsViewManager* aViewManager,
StyleSetHandle aStyleSet)
{
return doCreateShell(aContext, aViewManager, aStyleSet);
}
void
nsHTMLDocument::TryHintCharset(nsIContentViewer* aCv,
int32_t& aCharsetSource, nsACString& aCharset)
{
if (aCv) {
int32_t requestCharsetSource;
nsresult rv = aCv->GetHintCharacterSetSource(&requestCharsetSource);
if(NS_SUCCEEDED(rv) && kCharsetUninitialized != requestCharsetSource) {
nsAutoCString requestCharset;
rv = aCv->GetHintCharacterSet(requestCharset);
aCv->SetHintCharacterSetSource((int32_t)(kCharsetUninitialized));
if(requestCharsetSource <= aCharsetSource)
return;
if(NS_SUCCEEDED(rv) && EncodingUtils::IsAsciiCompatible(requestCharset)) {
aCharsetSource = requestCharsetSource;
aCharset = requestCharset;
return;
}
}
}
return;
}
void
nsHTMLDocument::TryUserForcedCharset(nsIContentViewer* aCv,
nsIDocShell* aDocShell,
int32_t& aCharsetSource,
nsACString& aCharset)
{
nsresult rv = NS_OK;
if(kCharsetFromUserForced <= aCharsetSource)
return;
// mCharacterSet not updated yet for channel, so check aCharset, too.
if (WillIgnoreCharsetOverride() || !EncodingUtils::IsAsciiCompatible(aCharset)) {
return;
}
nsAutoCString forceCharsetFromDocShell;
if (aCv) {
// XXX mailnews-only
rv = aCv->GetForceCharacterSet(forceCharsetFromDocShell);
}
if(NS_SUCCEEDED(rv) &&
!forceCharsetFromDocShell.IsEmpty() &&
EncodingUtils::IsAsciiCompatible(forceCharsetFromDocShell)) {
aCharset = forceCharsetFromDocShell;
aCharsetSource = kCharsetFromUserForced;
return;
}
if (aDocShell) {
// This is the Character Encoding menu code path in Firefox
nsAutoCString charset;
rv = aDocShell->GetForcedCharset(charset);
if (NS_SUCCEEDED(rv) && !charset.IsEmpty()) {
if (!EncodingUtils::IsAsciiCompatible(charset)) {
return;
}
aCharset = charset;
aCharsetSource = kCharsetFromUserForced;
aDocShell->SetForcedCharset(NS_LITERAL_CSTRING(""));
}
}
}
void
nsHTMLDocument::TryCacheCharset(nsICachingChannel* aCachingChannel,
int32_t& aCharsetSource,
nsACString& aCharset)
{
nsresult rv;
if (kCharsetFromCache <= aCharsetSource) {
return;
}
nsCString cachedCharset;
rv = aCachingChannel->GetCacheTokenCachedCharset(cachedCharset);
// Check EncodingUtils::IsAsciiCompatible() even in the cache case, because the value
// might be stale and in the case of a stale charset that is not a rough
// ASCII superset, the parser has no way to recover.
if (NS_SUCCEEDED(rv) &&
!cachedCharset.IsEmpty() &&
EncodingUtils::IsAsciiCompatible(cachedCharset))
{
aCharset = cachedCharset;
aCharsetSource = kCharsetFromCache;
}
}
void
nsHTMLDocument::TryParentCharset(nsIDocShell* aDocShell,
int32_t& aCharsetSource,
nsACString& aCharset)
{
if (!aDocShell) {
return;
}
if (aCharsetSource >= kCharsetFromParentForced) {
return;
}
int32_t parentSource;
nsAutoCString parentCharset;
nsCOMPtr<nsIPrincipal> parentPrincipal;
aDocShell->GetParentCharset(parentCharset,
&parentSource,
getter_AddRefs(parentPrincipal));
if (parentCharset.IsEmpty()) {
return;
}
if (kCharsetFromParentForced == parentSource ||
kCharsetFromUserForced == parentSource) {
if (WillIgnoreCharsetOverride() ||
!EncodingUtils::IsAsciiCompatible(aCharset) || // if channel said UTF-16
!EncodingUtils::IsAsciiCompatible(parentCharset)) {
return;
}
aCharset.Assign(parentCharset);
aCharsetSource = kCharsetFromParentForced;
return;
}
if (aCharsetSource >= kCharsetFromParentFrame) {
return;
}
if (kCharsetFromCache <= parentSource) {
// Make sure that's OK
if (!NodePrincipal()->Equals(parentPrincipal) ||
!EncodingUtils::IsAsciiCompatible(parentCharset)) {
return;
}
aCharset.Assign(parentCharset);
aCharsetSource = kCharsetFromParentFrame;
}
}
void
nsHTMLDocument::TryTLD(int32_t& aCharsetSource, nsACString& aCharset)
{
if (aCharsetSource >= kCharsetFromTopLevelDomain) {
return;
}
if (!FallbackEncoding::sGuessFallbackFromTopLevelDomain) {
return;
}
if (!mDocumentURI) {
return;
}
nsAutoCString host;
mDocumentURI->GetAsciiHost(host);
if (host.IsEmpty()) {
return;
}
// First let's see if the host is DNS-absolute and ends with a dot and
// get rid of that one.
if (host.Last() == '.') {
host.SetLength(host.Length() - 1);
if (host.IsEmpty()) {
return;
}
}
// If we still have a dot, the host is weird, so let's continue only
// if we have something other than a dot now.
if (host.Last() == '.') {
return;
}
int32_t index = host.RFindChar('.');
if (index == kNotFound) {
// We have an intranet host, Gecko-internal URL or an IPv6 address.
return;
}
// Since the string didn't end with a dot and we found a dot,
// there is at least one character between the dot and the end of
// the string, so taking the substring below is safe.
nsAutoCString tld;
ToLowerCase(Substring(host, index + 1, host.Length() - (index + 1)), tld);
// Reject generic TLDs and country TLDs that need more research
if (!FallbackEncoding::IsParticipatingTopLevelDomain(tld)) {
return;
}
// Check if we have an IPv4 address
bool seenNonDigit = false;
for (size_t i = 0; i < tld.Length(); ++i) {
char c = tld.CharAt(i);
if (c < '0' || c > '9') {
seenNonDigit = true;
break;
}
}
if (!seenNonDigit) {
return;
}
aCharsetSource = kCharsetFromTopLevelDomain;
FallbackEncoding::FromTopLevelDomain(tld, aCharset);
}
void
nsHTMLDocument::TryFallback(int32_t& aCharsetSource, nsACString& aCharset)
{
if (kCharsetFromFallback <= aCharsetSource)
return;
aCharsetSource = kCharsetFromFallback;
FallbackEncoding::FromLocale(aCharset);
}
void
nsHTMLDocument::SetDocumentCharacterSet(const nsACString& aCharSetID)
{
nsDocument::SetDocumentCharacterSet(aCharSetID);
// Make sure to stash this charset on our channel as needed if it's a wyciwyg
// channel.
nsCOMPtr<nsIWyciwygChannel> wyciwygChannel = do_QueryInterface(mChannel);
if (wyciwygChannel) {
wyciwygChannel->SetCharsetAndSource(GetDocumentCharacterSetSource(),
aCharSetID);
}
}
nsresult
nsHTMLDocument::StartDocumentLoad(const char* aCommand,
nsIChannel* aChannel,
nsILoadGroup* aLoadGroup,
nsISupports* aContainer,
nsIStreamListener **aDocListener,
bool aReset,
nsIContentSink* aSink)
{
if (!aCommand) {
MOZ_ASSERT(false, "Command is mandatory");
return NS_ERROR_INVALID_POINTER;
}
if (aSink) {
MOZ_ASSERT(false, "Got a sink override. Should not happen for HTML doc.");
return NS_ERROR_INVALID_ARG;
}
if (mType != eHTML) {
MOZ_ASSERT(mType == eXHTML);
MOZ_ASSERT(false, "Must not set HTML doc to XHTML mode before load start.");
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsAutoCString contentType;
aChannel->GetContentType(contentType);
bool view = !strcmp(aCommand, "view") ||
!strcmp(aCommand, "external-resource");
bool viewSource = !strcmp(aCommand, "view-source");
bool asData = !strcmp(aCommand, kLoadAsData);
bool import = !strcmp(aCommand, "import");
if (!(view || viewSource || asData || import)) {
MOZ_ASSERT(false, "Bad parser command");
return NS_ERROR_INVALID_ARG;
}
bool html = contentType.EqualsLiteral(TEXT_HTML);
bool xhtml = !html && (contentType.EqualsLiteral(APPLICATION_XHTML_XML) || contentType.EqualsLiteral(APPLICATION_WAPXHTML_XML));
bool plainText = !html && !xhtml && nsContentUtils::IsPlainTextType(contentType);
if (!(html || xhtml || plainText || viewSource)) {
MOZ_ASSERT(false, "Channel with bad content type.");
return NS_ERROR_INVALID_ARG;
}
bool loadAsHtml5 = true;
if (!viewSource && xhtml) {
// We're parsing XHTML as XML, remember that.
mType = eXHTML;
mCompatMode = eCompatibility_FullStandards;
loadAsHtml5 = false;
}
// TODO: Proper about:blank treatment is bug 543435
if (loadAsHtml5 && view) {
// mDocumentURI hasn't been set, yet, so get the URI from the channel
nsCOMPtr<nsIURI> uri;
aChannel->GetOriginalURI(getter_AddRefs(uri));
// Adapted from nsDocShell:
// GetSpec can be expensive for some URIs, so check the scheme first.
bool isAbout = false;
if (uri && NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)) && isAbout) {
if (uri->GetSpecOrDefault().EqualsLiteral("about:blank")) {
loadAsHtml5 = false;
}
}
}
CSSLoader()->SetCompatibilityMode(mCompatMode);
nsresult rv = nsDocument::StartDocumentLoad(aCommand,
aChannel, aLoadGroup,
aContainer,
aDocListener, aReset);
if (NS_FAILED(rv)) {
return rv;
}
// Store the security info for future use with wyciwyg channels.
aChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
nsCOMPtr<nsIURI> uri;
rv = aChannel->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsICachingChannel> cachingChan = do_QueryInterface(aChannel);
if (loadAsHtml5) {
mParser = nsHtml5Module::NewHtml5Parser();
if (plainText) {
if (viewSource) {
mParser->MarkAsNotScriptCreated("view-source-plain");
} else {
mParser->MarkAsNotScriptCreated("plain-text");
}
} else if (viewSource && !html) {
mParser->MarkAsNotScriptCreated("view-source-xml");
} else {
mParser->MarkAsNotScriptCreated(aCommand);
}
} else {
mParser = do_CreateInstance(kCParserCID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// Look for the parent document. Note that at this point we don't have our
// content viewer set up yet, and therefore do not have a useful
// mParentDocument.
// in this block of code, if we get an error result, we return it
// but if we get a null pointer, that's perfectly legal for parent
// and parentContentViewer
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
if (docShell) {
docShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
}
nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
nsCOMPtr<nsIContentViewer> parentContentViewer;
if (parent) {
rv = parent->GetContentViewer(getter_AddRefs(parentContentViewer));
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIContentViewer> cv;
if (docShell) {
docShell->GetContentViewer(getter_AddRefs(cv));
}
if (!cv) {
cv = parentContentViewer.forget();
}
nsAutoCString urlSpec;
uri->GetSpec(urlSpec);
#ifdef DEBUG_charset
printf("Determining charset for %s\n", urlSpec.get());
#endif
// These are the charset source and charset for our document
int32_t charsetSource;
nsAutoCString charset;
// These are the charset source and charset for the parser. This can differ
// from that for the document if the channel is a wyciwyg channel.
int32_t parserCharsetSource;
nsAutoCString parserCharset;
nsCOMPtr<nsIWyciwygChannel> wyciwygChannel;
// For error reporting and referrer policy setting
nsHtml5TreeOpExecutor* executor = nullptr;
if (loadAsHtml5) {
executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
if (mReferrerPolicySet) {
// CSP may have set the referrer policy, so a speculative parser should
// start with the new referrer policy.
executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
}
}
if (!IsHTMLDocument() || !docShell) { // no docshell for text/html XHR
charsetSource = IsHTMLDocument() ? kCharsetFromFallback
: kCharsetFromDocTypeDefault;
charset.AssignLiteral("UTF-8");
TryChannelCharset(aChannel, charsetSource, charset, executor);
parserCharsetSource = charsetSource;
parserCharset = charset;
} else {
NS_ASSERTION(docShell, "Unexpected null value");
charsetSource = kCharsetUninitialized;
wyciwygChannel = do_QueryInterface(aChannel);
// The following will try to get the character encoding from various
// sources. Each Try* function will return early if the source is already
// at least as large as any of the sources it might look at. Some of
// these functions (like TryHintCharset and TryParentCharset) can set
// charsetSource to various values depending on where the charset they
// end up finding originally comes from.
// Don't actually get the charset from the channel if this is a
// wyciwyg channel; it'll always be UTF-16
if (!wyciwygChannel) {
// Otherwise, try the channel's charset (e.g., charset from HTTP
// "Content-Type" header) first. This way, we get to reject overrides in
// TryParentCharset and TryUserForcedCharset if the channel said UTF-16.
// This is to avoid socially engineered XSS by adding user-supplied
// content to a UTF-16 site such that the byte have a dangerous
// interpretation as ASCII and the user can be lured to using the
// charset menu.
TryChannelCharset(aChannel, charsetSource, charset, executor);
}
TryUserForcedCharset(cv, docShell, charsetSource, charset);
TryHintCharset(cv, charsetSource, charset); // XXX mailnews-only
TryParentCharset(docShell, charsetSource, charset);
if (cachingChan && !urlSpec.IsEmpty()) {
TryCacheCharset(cachingChan, charsetSource, charset);
}
TryTLD(charsetSource, charset);
TryFallback(charsetSource, charset);
if (wyciwygChannel) {
// We know for sure that the parser needs to be using UTF16.
parserCharset = "UTF-16";
parserCharsetSource = charsetSource < kCharsetFromChannel ?
kCharsetFromChannel : charsetSource;
nsAutoCString cachedCharset;
int32_t cachedSource;
rv = wyciwygChannel->GetCharsetAndSource(&cachedSource, cachedCharset);
if (NS_SUCCEEDED(rv)) {
if (cachedSource > charsetSource) {
charsetSource = cachedSource;
charset = cachedCharset;
}
} else {
// Don't propagate this error.
rv = NS_OK;
}
} else {
parserCharset = charset;
parserCharsetSource = charsetSource;
}
}
SetDocumentCharacterSetSource(charsetSource);
SetDocumentCharacterSet(charset);
if (cachingChan) {
NS_ASSERTION(charset == parserCharset,
"How did those end up different here? wyciwyg channels are "
"not nsICachingChannel");
rv = cachingChan->SetCacheTokenCachedCharset(charset);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "cannot SetMetaDataElement");
rv = NS_OK; // don't propagate error
}
// Set the parser as the stream listener for the document loader...
rv = NS_OK;
nsCOMPtr<nsIStreamListener> listener = mParser->GetStreamListener();
listener.forget(aDocListener);
#ifdef DEBUG_charset
printf(" charset = %s source %d\n",
charset.get(), charsetSource);
#endif
mParser->SetDocumentCharset(parserCharset, parserCharsetSource);
mParser->SetCommand(aCommand);
if (!IsHTMLDocument()) {
MOZ_ASSERT(!loadAsHtml5);
nsCOMPtr<nsIXMLContentSink> xmlsink;
NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri,
docShell, aChannel);
mParser->SetContentSink(xmlsink);
} else {
if (loadAsHtml5) {
nsHtml5Module::Initialize(mParser, this, uri, docShell, aChannel);
} else {
// about:blank *only*
nsCOMPtr<nsIHTMLContentSink> htmlsink;
NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri,
docShell, aChannel);
mParser->SetContentSink(htmlsink);
}
}
if (plainText && !nsContentUtils::IsChildOfSameType(this) &&
Preferences::GetBool("plain_text.wrap_long_lines")) {
nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
NS_ASSERTION(NS_SUCCEEDED(rv) && bundleService, "The bundle service could not be loaded");
nsCOMPtr<nsIStringBundle> bundle;
rv = bundleService->CreateBundle("chrome://global/locale/browser.properties",
getter_AddRefs(bundle));
NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/browser.properties could not be loaded");
nsXPIDLString title;
if (bundle) {
bundle->GetStringFromName(u"plainText.wordWrap", getter_Copies(title));
}
SetSelectedStyleSheetSet(title);
}
// parser the content of the URI
mParser->Parse(uri, nullptr, (void *)this);
return rv;
}
void
nsHTMLDocument::StopDocumentLoad()
{
BlockOnload();
// Remove the wyciwyg channel request from the document load group
// that we added in Open() if Open() was called on this doc.
RemoveWyciwygChannel();
NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::StopDocumentLoad(): "
"nsIWyciwygChannel could not be removed!");
nsDocument::StopDocumentLoad();
UnblockOnload(false);
return;
}
void
nsHTMLDocument::BeginLoad()
{
if (IsEditingOn()) {
// Reset() blows away all event listeners in the document, and our
// editor relies heavily on those. Midas is turned on, to make it
// work, re-initialize it to give it a chance to add its event
// listeners again.
TurnEditingOff();
EditingStateChanged();
}
nsDocument::BeginLoad();
}
void
nsHTMLDocument::EndLoad()
{
bool turnOnEditing =
mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
// Note: nsDocument::EndLoad nulls out mParser.
nsDocument::EndLoad();
if (turnOnEditing) {
EditingStateChanged();
}
if (!GetWindow()) {
// This is a document that's not in a window. For example, this could be an
// XMLHttpRequest responseXML document, or a document created via DOMParser
// or DOMImplementation. We don't reach this code normally for such
// documents (which is not obviously correct), but can reach it via
// document.open()/document.close().
//
// Such documents don't fire load events, but per spec should set their
// readyState to "complete" when parsing and all loading of subresources is
// done. Parsing is done now, and documents not in a window don't load
// subresources, so just go ahead and mark ourselves as complete.
SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE,
/* updateTimingInformation = */ false);
// Reset mSkipLoadEventAfterClose just in case.
mSkipLoadEventAfterClose = false;
}
}
void
nsHTMLDocument::SetCompatibilityMode(nsCompatibility aMode)
{
NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
"Bad compat mode for XHTML document!");
mCompatMode = aMode;
CSSLoader()->SetCompatibilityMode(mCompatMode);
nsCOMPtr<nsIPresShell> shell = GetShell();
if (shell) {
nsPresContext *pc = shell->GetPresContext();
if (pc) {
pc->CompatibilityModeChanged();
}
}
}
//
// nsIDOMHTMLDocument interface implementation
//
already_AddRefed<nsIURI>
nsHTMLDocument::GetDomainURI()
{
nsIPrincipal* principal = NodePrincipal();
nsCOMPtr<nsIURI> uri;
principal->GetDomain(getter_AddRefs(uri));
if (uri) {
return uri.forget();
}
principal->GetURI(getter_AddRefs(uri));
return uri.forget();
}
NS_IMETHODIMP
nsHTMLDocument::GetDomain(nsAString& aDomain)
{
nsCOMPtr<nsIURI> uri = GetDomainURI();
if (!uri) {
aDomain.Truncate();
return NS_OK;
}
nsAutoCString hostName;
nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
if (NS_SUCCEEDED(rv)) {
CopyUTF8toUTF16(hostName, aDomain);
} else {
// If we can't get the host from the URI (e.g. about:, javascript:,
// etc), just return an empty string.
aDomain.Truncate();
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetDomain(const nsAString& aDomain)
{
ErrorResult rv;
SetDomain(aDomain, rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv)
{
if (mSandboxFlags & SANDBOXED_DOMAIN) {
// We're sandboxed; disallow setting domain
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
if (aDomain.IsEmpty()) {
rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
return;
}
// Create new URI
nsCOMPtr<nsIURI> uri = GetDomainURI();
if (!uri) {
rv.Throw(NS_ERROR_FAILURE);
return;
}
nsCOMPtr<nsIURI> newURI;
nsresult rv2 = uri->Clone(getter_AddRefs(newURI));
if (NS_FAILED(rv2)) {
rv.Throw(rv2);
return;
}
rv2 = newURI->SetUserPass(EmptyCString());
if (NS_FAILED(rv2)) {
rv.Throw(rv2);
return;
}
// We use SetHostAndPort because we want to reset the port number if needed.
rv2 = newURI->SetHostAndPort(NS_ConvertUTF16toUTF8(aDomain));
if (NS_FAILED(rv2)) {
rv.Throw(rv2);
return;
}
// Check new domain - must be a superdomain of the current host
// For example, a page from foo.bar.com may set domain to bar.com,
// but not to ar.com, baz.com, or fi.foo.bar.com.
nsAutoCString current, domain;
if (NS_FAILED(uri->GetAsciiHost(current)))
current.Truncate();
if (NS_FAILED(newURI->GetAsciiHost(domain)))
domain.Truncate();
bool ok = current.Equals(domain);
if (current.Length() > domain.Length() &&
StringEndsWith(current, domain) &&
current.CharAt(current.Length() - domain.Length() - 1) == '.') {
// We're golden if the new domain is the current page's base domain or a
// subdomain of it.
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (!tldService) {
rv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
nsAutoCString currentBaseDomain;
ok = NS_SUCCEEDED(tldService->GetBaseDomain(uri, 0, currentBaseDomain));
NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
(domain.Length() >= currentBaseDomain.Length()),
"uh-oh! slight optimization wasn't valid somehow!");
ok = ok && domain.Length() >= currentBaseDomain.Length();
}
if (!ok) {
// Error: illegal domain
rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
return;
}
NS_TryToSetImmutable(newURI);
rv = NodePrincipal()->SetDomain(newURI);
}
nsGenericHTMLElement*
nsHTMLDocument::GetBody()
{
Element* html = GetHtmlElement();
if (!html) {
return nullptr;
}
for (nsIContent* child = html->GetFirstChild();
child;
child = child->GetNextSibling()) {
if (child->IsHTMLElement(nsGkAtoms::body) ||
child->IsHTMLElement(nsGkAtoms::frameset)) {
return static_cast<nsGenericHTMLElement*>(child);
}
}
return nullptr;
}
NS_IMETHODIMP
nsHTMLDocument::GetBody(nsIDOMHTMLElement** aBody)
{
*aBody = nullptr;
nsIContent *body = GetBody();
return body ? CallQueryInterface(body, aBody) : NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetBody(nsIDOMHTMLElement* aBody)
{
nsCOMPtr<nsIContent> newBody = do_QueryInterface(aBody);
MOZ_ASSERT(!newBody || newBody->IsHTMLElement(),
"How could we be an nsIContent but not actually HTML here?");
ErrorResult rv;
SetBody(static_cast<nsGenericHTMLElement*>(newBody.get()), rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv)
{
nsCOMPtr<Element> root = GetRootElement();
// The body element must be either a body tag or a frameset tag. And we must
// have a html root tag, otherwise GetBody will not return the newly set
// body.
if (!newBody ||
!newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset) ||
!root || !root->IsHTMLElement() ||
!root->IsHTMLElement(nsGkAtoms::html)) {
rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
return;
}
// Use DOM methods so that we pass through the appropriate security checks.
nsCOMPtr<Element> currentBody = GetBodyElement();
if (currentBody) {
root->ReplaceChild(*newBody, *currentBody, rv);
} else {
root->AppendChild(*newBody, rv);
}
}
NS_IMETHODIMP
nsHTMLDocument::GetHead(nsIDOMHTMLHeadElement** aHead)
{
*aHead = nullptr;
Element* head = GetHeadElement();
return head ? CallQueryInterface(head, aHead) : NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetImages(nsIDOMHTMLCollection** aImages)
{
NS_ADDREF(*aImages = Images());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Images()
{
if (!mImages) {
mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img, nsGkAtoms::img);
}
return mImages;
}
NS_IMETHODIMP
nsHTMLDocument::GetApplets(nsIDOMHTMLCollection** aApplets)
{
NS_ADDREF(*aApplets = Applets());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Applets()
{
if (!mApplets) {
mApplets = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::applet, nsGkAtoms::applet);
}
return mApplets;
}
bool
nsHTMLDocument::MatchLinks(Element* aElement, int32_t aNamespaceID,
nsIAtom* aAtom, void* aData)
{
nsIDocument* doc = aElement->GetUncomposedDoc();
if (doc) {
NS_ASSERTION(aElement->IsInUncomposedDoc(),
"This method should never be called on content nodes that "
"are not in a document!");
#ifdef DEBUG
{
nsCOMPtr<nsIHTMLDocument> htmldoc =
do_QueryInterface(aElement->GetUncomposedDoc());
NS_ASSERTION(htmldoc,
"Huh, how did this happen? This should only be used with "
"HTML documents!");
}
#endif
mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
nsIAtom *localName = ni->NameAtom();
if (ni->NamespaceID() == kNameSpaceID_XHTML &&
(localName == nsGkAtoms::a || localName == nsGkAtoms::area)) {
return aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
}
}
return false;
}
NS_IMETHODIMP
nsHTMLDocument::GetLinks(nsIDOMHTMLCollection** aLinks)
{
NS_ADDREF(*aLinks = Links());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Links()
{
if (!mLinks) {
mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
}
return mLinks;
}
bool
nsHTMLDocument::MatchAnchors(Element* aElement, int32_t aNamespaceID,
nsIAtom* aAtom, void* aData)
{
NS_ASSERTION(aElement->IsInUncomposedDoc(),
"This method should never be called on content nodes that "
"are not in a document!");
#ifdef DEBUG
{
nsCOMPtr<nsIHTMLDocument> htmldoc =
do_QueryInterface(aElement->GetUncomposedDoc());
NS_ASSERTION(htmldoc,
"Huh, how did this happen? This should only be used with "
"HTML documents!");
}
#endif
if (aElement->IsHTMLElement(nsGkAtoms::a)) {
return aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
}
return false;
}
NS_IMETHODIMP
nsHTMLDocument::GetAnchors(nsIDOMHTMLCollection** aAnchors)
{
NS_ADDREF(*aAnchors = Anchors());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Anchors()
{
if (!mAnchors) {
mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
}
return mAnchors;
}
NS_IMETHODIMP
nsHTMLDocument::GetScripts(nsIDOMHTMLCollection** aScripts)
{
NS_ADDREF(*aScripts = Scripts());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Scripts()
{
if (!mScripts) {
mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script, nsGkAtoms::script);
}
return mScripts;
}
NS_IMETHODIMP
nsHTMLDocument::GetCookie(nsAString& aCookie)
{
ErrorResult rv;
GetCookie(aCookie, rv);
return rv.StealNSResult();
}
already_AddRefed<nsIChannel>
nsHTMLDocument::CreateDummyChannelForCookies(nsIURI* aCodebaseURI)
{
// The cookie service reads the privacy status of the channel we pass to it in
// order to determine which cookie database to query. In some cases we don't
// have a proper channel to hand it to the cookie service though. This
// function creates a dummy channel that is not used to load anything, for the
// sole purpose of handing it to the cookie service. DO NOT USE THIS CHANNEL
// FOR ANY OTHER PURPOSE.
MOZ_ASSERT(!mChannel);
// The following channel is never openend, so it does not matter what
// securityFlags we pass; let's follow the principle of least privilege.
nsCOMPtr<nsIChannel> channel;
NS_NewChannel(getter_AddRefs(channel), aCodebaseURI, this,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INVALID);
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
do_QueryInterface(channel);
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
if (!pbChannel || !loadContext) {
return nullptr;
}
pbChannel->SetPrivate(loadContext->UsePrivateBrowsing());
return channel.forget();
}
void
nsHTMLDocument::GetCookie(nsAString& aCookie, ErrorResult& rv)
{
aCookie.Truncate(); // clear current cookie in case service fails;
// no cookie isn't an error condition.
if (mDisableCookieAccess) {
return;
}
// If the document's sandboxed origin flag is set, access to read cookies
// is prohibited.
if (mSandboxFlags & SANDBOXED_ORIGIN) {
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
// If the document is a cookie-averse document, return an empty string.
if (IsCookieAverse()) {
return;
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (service) {
// Get a URI from the document principal. We use the original
// codebase in case the codebase was changed by SetDomain
nsCOMPtr<nsIURI> codebaseURI;
NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
if (!codebaseURI) {
// Document's principal is not a codebase (may be system), so
// can't set cookies
return;
}
nsCOMPtr<nsIChannel> channel(mChannel);
if (!channel) {
channel = CreateDummyChannelForCookies(codebaseURI);
if (!channel) {
return;
}
}
nsXPIDLCString cookie;
service->GetCookieString(codebaseURI, channel, getter_Copies(cookie));
// CopyUTF8toUTF16 doesn't handle error
// because it assumes that the input is valid.
nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"),
cookie, aCookie);
}
}
NS_IMETHODIMP
nsHTMLDocument::SetCookie(const nsAString& aCookie)
{
ErrorResult rv;
SetCookie(aCookie, rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::SetCookie(const nsAString& aCookie, ErrorResult& rv)
{
if (mDisableCookieAccess) {
return;
}
// If the document's sandboxed origin flag is set, access to write cookies
// is prohibited.
if (mSandboxFlags & SANDBOXED_ORIGIN) {
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
// If the document is a cookie-averse document, do nothing.
if (IsCookieAverse()) {
return;
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (service && mDocumentURI) {
// The for getting the URI matches nsNavigator::GetCookieEnabled
nsCOMPtr<nsIURI> codebaseURI;
NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
if (!codebaseURI) {
// Document's principal is not a codebase (may be system), so
// can't set cookies
return;
}
nsCOMPtr<nsIChannel> channel(mChannel);
if (!channel) {
channel = CreateDummyChannelForCookies(codebaseURI);
if (!channel) {
return;
}
}
NS_ConvertUTF16toUTF8 cookie(aCookie);
service->SetCookieString(codebaseURI, nullptr, cookie.get(), channel);
}
}
NS_IMETHODIMP
nsHTMLDocument::Open(const nsAString& aContentTypeOrUrl,
const nsAString& aReplaceOrName,
const nsAString& aFeatures,
JSContext* cx, uint8_t aOptionalArgCount,
nsISupports** aReturn)
{
// When called with 3 or more arguments, document.open() calls window.open().
if (aOptionalArgCount > 2) {
ErrorResult rv;
*aReturn = Open(cx, aContentTypeOrUrl, aReplaceOrName, aFeatures,
false, rv).take();
return rv.StealNSResult();
}
nsString type;
if (aOptionalArgCount > 0) {
type = aContentTypeOrUrl;
} else {
type.AssignLiteral("text/html");
}
nsString replace;
if (aOptionalArgCount > 1) {
replace = aReplaceOrName;
}
ErrorResult rv;
*aReturn = Open(cx, type, replace, rv).take();
return rv.StealNSResult();
}
already_AddRefed<nsPIDOMWindowOuter>
nsHTMLDocument::Open(JSContext* /* unused */,
const nsAString& aURL,
const nsAString& aName,
const nsAString& aFeatures,
bool aReplace,
ErrorResult& rv)
{
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!");
nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
if (!window) {
rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
nsCOMPtr<nsPIDOMWindowOuter> outer =
nsPIDOMWindowOuter::GetFromCurrentInner(window);
if (!outer) {
rv.Throw(NS_ERROR_NOT_INITIALIZED);
return nullptr;
}
RefPtr<nsGlobalWindow> win = nsGlobalWindow::Cast(outer);
nsCOMPtr<nsPIDOMWindowOuter> newWindow;
// XXXbz We ignore aReplace for now.
rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newWindow));
return newWindow.forget();
}
already_AddRefed<nsIDocument>
nsHTMLDocument::Open(JSContext* cx,
const nsAString& aType,
const nsAString& aReplace,
ErrorResult& aError)
{
// Implements
// <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!");
// Step 1 - Throw if we're the wrong type of document.
if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
// Set up the content type for insertion
nsAutoCString contentType;
contentType.AssignLiteral("text/html");
nsAutoString type;
nsContentUtils::ASCIIToLower(aType, type);
nsAutoCString actualType, dummy;
NS_ParseRequestContentType(NS_ConvertUTF16toUTF8(type), actualType, dummy);
if (!actualType.EqualsLiteral("text/html") &&
!type.EqualsLiteral("replace")) {
contentType.AssignLiteral("text/plain");
}
// Step 3 - Get the entryDocument for security checks
nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument();
if (!callerDoc) {
// If we're called from C++ or in some other way without an originating
// document we can't do a document.open w/o changing the principal of the
// document to something like about:blank (as that's the only sane thing to
// do when we don't know the origin of this call), and since we can't
// change the principals of a document for security reasons we'll have to
// refuse to go ahead with this call.
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// Step 4 - Throw if we're not same-origin
if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// Step 5 - If we have an active parser, abort with no-op
if (mParser || mParserAborted) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// Step 6 - Check if document.open() is called during unload
nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
if (shell) {
bool inUnload;
shell->GetIsInUnload(&inUnload);
if (inUnload) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
}
// Step 7 - Stop existing navigation of our browsing context (and all
// other loads it's doing) if we're the active document of our browsing
// context. If there's no existing navigation, we don't want to stop
// anything.
if (shell && IsCurrentActiveDocument() &&
mScriptGlobalObject) {
nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
webnav->Stop(nsIWebNavigation::STOP_NETWORK);
// The Stop call may have cancelled the onload blocker request or prevented
// it from getting added, so we need to make sure it gets added to the
// document again otherwise the document could have a non-zero onload block
// count without the onload blocker request being in the loadgroup.
EnsureOnloadBlocker();
}
// Step 8 - Clear all event listeners out of our DOM tree
for (nsINode* node : SimpleTreeIterator(*this)) {
if (EventListenerManager* elm = node->GetExistingListenerManager()) {
elm->RemoveAllListeners();
}
}
// Step 9 - Clear event listeners from our window, if we have one.
//
// Note that we explicitly want the inner window, and only if we're its
// document. We want to do this (per spec) even when we're not the "active
// document", so we can't go through GetWindow(), because it might forward to
// the wrong inner.
if (nsPIDOMWindowInner* win = GetInnerWindow()) {
if (win->GetExtantDoc() == this) {
if (EventListenerManager* elm =
nsGlobalWindow::Cast(win)->GetExistingListenerManager()) {
elm->RemoveAllListeners();
}
}
}
// Step 10 - Remove all of our DOM children without firing any mutation events.
DisconnectNodeTree();
// --- At this point our tree is clean and we can switch to the new URI ---
// Step 11 - If we're the current document in our docshell, do the
// equivalent of pushState() with the new URL we should have.
if (shell && IsCurrentActiveDocument()) {
nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
// UpdateURLAndHistory might do various member-setting, so make sure we're
// holding strong refs to all the refcounted args on the stack. We can
// assume that our caller is holding on to "this" already.
nsCOMPtr<nsIURI> currentURI = nsIDocument::GetDocumentURI();
bool equalURIs;
nsresult rv = currentURI->Equals(newURI, &equalURIs);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return nullptr;
}
nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, EmptyString(),
/* aReplace = */ true, currentURI,
equalURIs);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return nullptr;
}
// And use the security info of the caller document as well, since
// it's the thing providing our data.
mSecurityInfo = callerDoc->GetSecurityInfo();
// This is not mentioned in the spec, but that's probably a spec bug.
// See <https://github.com/whatwg/html/issues/4299>.
// Since our URL may be changing away from about:blank here, we really want
// to unset this flag on any document.open(), since only about:blank can be
// an initial document.
SetIsInitialDocument(false);
// And let our docloader know that it will need to track our load event.
nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
}
// Step 12
mSkipLoadEventAfterClose = mLoadEventFiring;
// Preliminary to steps 13-16. Set our ready state to uninitialized before
// we do anything else, so we can then proceed to later ready state levels.
SetReadyStateInternal(READYSTATE_UNINITIALIZED,
/* updateTimingInformation = */ false);
mDidDocumentOpen = true;
// Step 13 - Set our compatibility mode to standards.
SetCompatibilityMode(eCompatibility_FullStandards);
// Step 14 - Create a new parser associated with document.
// This also does step 16 implicitly.
mParserAborted = false;
mParser = nsHtml5Module::NewHtml5Parser();
nsHtml5Module::Initialize(mParser, this, nsIDocument::GetDocumentURI(), shell, nullptr);
if (mReferrerPolicySet) {
// CSP may have set the referrer policy, so a speculative parser should
// start with the new referrer policy.
nsHtml5TreeOpExecutor* executor = nullptr;
executor = static_cast<nsHtml5TreeOpExecutor*>(mParser->GetContentSink());
if (executor && mReferrerPolicySet) {
executor->SetSpeculationReferrerPolicy(
static_cast<ReferrerPolicy>(mReferrerPolicy));
}
}
if (shell) {
// Prepare the docshell and the document viewer for the impending
// out-of-band document.write()
shell->PrepareForNewContentModel();
nsCOMPtr<nsIContentViewer> cv;
shell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
cv->LoadStart(this);
}
}
// Step 15.
SetReadyStateInternal(nsIDocument::READYSTATE_LOADING,
/* updateTimingInformation = */ false);
// Step 16 happened with step 14 above.
// Step 17.
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
NS_IMETHODIMP
nsHTMLDocument::Clear()
{
// This method has been deprecated
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::Close()
{
ErrorResult rv;
Close(rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::Close(ErrorResult& rv)
{
if (!IsHTMLDocument()) {
// No calling document.close() on XHTML!
rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (!mParser || !mParser->IsScriptCreated()) {
return;
}
++mWriteLevel;
rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
EmptyString(), nullptr, GetContentTypeInternal(), true);
--mWriteLevel;
// Even if that Parse() call failed, do the rest of this method
// XXX Make sure that all the document.written content is
// reflowed. We should remove this call once we change
// nsHTMLDocument::OpenCommon() so that it completely destroys the
// earlier document's content and frame hierarchy. Right now, it
// re-uses the earlier document's root content object and
// corresponding frame objects. These re-used frame objects think
// that they have already been reflowed, so they drop initial
// reflows. For certain cases of document.written content, like a
// frameset document, the dropping of the initial reflow means
// that we end up in document.close() without appended any reflow
// commands to the reflow queue and, consequently, without adding
// the dummy layout request to the load group. Since the dummy
// layout request is not added to the load group, the onload
// handler of the frameset fires before the frames get reflowed
// and loaded. That is the long explanation for why we need this
// one line of code here!
// XXXbz as far as I can tell this may not be needed anymore; all
// the testcases in bug 57636 pass without this line... Leaving
// it be for now, though. In any case, there's no reason to do
// this if we have no presshell, since in that case none of the
// above about reusing frames applies.
//
// XXXhsivonen keeping this around for bug 577508 / 253951 still :-(
if (GetShell()) {
FlushPendingNotifications(Flush_Layout);
}
}
void
nsHTMLDocument::WriteCommon(JSContext *cx,
const Sequence<nsString>& aText,
bool aNewlineTerminate,
mozilla::ErrorResult& rv)
{
// Fast path the common case
if (aText.Length() == 1) {
rv = WriteCommon(cx, aText[0], aNewlineTerminate);
} else {
// XXXbz it would be nice if we could pass all the strings to the parser
// without having to do all this copying and then ask it to start
// parsing....
nsString text;
for (uint32_t i = 0; i < aText.Length(); ++i) {
text.Append(aText[i]);
}
rv = WriteCommon(cx, text, aNewlineTerminate);
}
}
nsresult
nsHTMLDocument::WriteCommon(JSContext *cx,
const nsAString& aText,
bool aNewlineTerminate)
{
mTooDeepWriteRecursion =
(mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
NS_ENSURE_STATE(!mTooDeepWriteRecursion);
if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
// No calling document.write*() on XHTML!
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
if (mParserAborted) {
// Hixie says aborting the parser doesn't undefine the insertion point.
// However, since we null out mParser in that case, we track the
// theoretically defined insertion point using mParserAborted.
return NS_OK;
}
nsresult rv = NS_OK;
void *key = GenerateParserKey();
if (mParser && !mParser->IsInsertionPointDefined()) {
if (mExternalScriptsBeingEvaluated) {
// Instead of implying a call to document.open(), ignore the call.
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("DOM Events"), this,
nsContentUtils::eDOM_PROPERTIES,
"DocumentWriteIgnored",
nullptr, 0,
mDocumentURI);
return NS_OK;
}
mParser->Terminate();
NS_ASSERTION(!mParser, "mParser should have been null'd out");
}
if (!mParser) {
if (mExternalScriptsBeingEvaluated) {
// Instead of implying a call to document.open(), ignore the call.
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("DOM Events"), this,
nsContentUtils::eDOM_PROPERTIES,
"DocumentWriteIgnored",
nullptr, 0,
mDocumentURI);
return NS_OK;
}
nsCOMPtr<nsISupports> ignored;
rv = Open(NS_LITERAL_STRING("text/html"), EmptyString(), EmptyString(), cx,
1, getter_AddRefs(ignored));
// If Open() fails, or if it didn't create a parser (as it won't
// if the user chose to not discard the current document through
// onbeforeunload), don't write anything.
if (NS_FAILED(rv) || !mParser) {
return rv;
}
MOZ_ASSERT(!JS_IsExceptionPending(cx),
"Open() succeeded but JS exception is pending");
}
static NS_NAMED_LITERAL_STRING(new_line, "\n");
// Save the data in cache if the write isn't from within the doc
if (mWyciwygChannel && !key) {
if (!aText.IsEmpty()) {
mWyciwygChannel->WriteToCacheEntry(aText);
}
if (aNewlineTerminate) {
mWyciwygChannel->WriteToCacheEntry(new_line);
}
}
++mWriteLevel;
// This could be done with less code, but for performance reasons it
// makes sense to have the code for two separate Parse() calls here
// since the concatenation of strings costs more than we like. And
// why pay that price when we don't need to?
if (aNewlineTerminate) {
rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
aText + new_line, key, GetContentTypeInternal(), false);
} else {
rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
aText, key, GetContentTypeInternal(), false);
}
--mWriteLevel;
mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
return rv;
}
NS_IMETHODIMP
nsHTMLDocument::Write(const nsAString& aText, JSContext *cx)
{
return WriteCommon(cx, aText, false);
}
void
nsHTMLDocument::Write(JSContext* cx, const Sequence<nsString>& aText,
ErrorResult& rv)
{
WriteCommon(cx, aText, false, rv);
}
NS_IMETHODIMP
nsHTMLDocument::Writeln(const nsAString& aText, JSContext *cx)
{
return WriteCommon(cx, aText, true);
}
void
nsHTMLDocument::Writeln(JSContext* cx, const Sequence<nsString>& aText,
ErrorResult& rv)
{
WriteCommon(cx, aText, true, rv);
}
bool
nsHTMLDocument::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
nsIAtom* aAtom, void* aData)
{
NS_PRECONDITION(aElement, "Must have element to work with!");
nsString* elementName = static_cast<nsString*>(aData);
return
aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
*elementName, eCaseMatters);
}
/* static */
void*
nsHTMLDocument::UseExistingNameString(nsINode* aRootNode, const nsString* aName)
{
return const_cast<nsString*>(aName);
}
NS_IMETHODIMP
nsHTMLDocument::GetElementsByName(const nsAString& aElementName,
nsIDOMNodeList** aReturn)
{
*aReturn = GetElementsByName(aElementName).take();
return NS_OK;
}
void
nsHTMLDocument::AddedForm()
{
++mNumForms;
}
void
nsHTMLDocument::RemovedForm()
{
--mNumForms;
}
int32_t
nsHTMLDocument::GetNumFormsSynchronous()
{
return mNumForms;
}
NS_IMETHODIMP
nsHTMLDocument::GetAlinkColor(nsAString& aAlinkColor)
{
aAlinkColor.Truncate();
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->GetALink(aAlinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetAlinkColor(const nsAString& aAlinkColor)
{
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->SetALink(aAlinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetLinkColor(nsAString& aLinkColor)
{
aLinkColor.Truncate();
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->GetLink(aLinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetLinkColor(const nsAString& aLinkColor)
{
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->SetLink(aLinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetVlinkColor(nsAString& aVlinkColor)
{
aVlinkColor.Truncate();
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->GetVLink(aVlinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetVlinkColor(const nsAString& aVlinkColor)
{
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->SetVLink(aVlinkColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetBgColor(nsAString& aBgColor)
{
aBgColor.Truncate();
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->GetBgColor(aBgColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetBgColor(const nsAString& aBgColor)
{
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->SetBgColor(aBgColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetFgColor(nsAString& aFgColor)
{
aFgColor.Truncate();
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->GetText(aFgColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetFgColor(const nsAString& aFgColor)
{
HTMLBodyElement* body = GetBodyElement();
if (body) {
body->SetText(aFgColor);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::GetEmbeds(nsIDOMHTMLCollection** aEmbeds)
{
NS_ADDREF(*aEmbeds = Embeds());
return NS_OK;
}
nsIHTMLCollection*
nsHTMLDocument::Embeds()
{
if (!mEmbeds) {
mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed, nsGkAtoms::embed);
}
return mEmbeds;
}
NS_IMETHODIMP
nsHTMLDocument::GetSelection(nsISelection** aReturn)
{
ErrorResult rv;
NS_IF_ADDREF(*aReturn = nsDocument::GetSelection(rv));
return rv.StealNSResult();
}
NS_IMETHODIMP
nsHTMLDocument::CaptureEvents()
{
WarnOnceAbout(nsIDocument::eUseOfCaptureEvents);
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::ReleaseEvents()
{
WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
return NS_OK;
}
// Mapped to document.embeds for NS4 compatibility
NS_IMETHODIMP
nsHTMLDocument::GetPlugins(nsIDOMHTMLCollection** aPlugins)
{
*aPlugins = nullptr;
return GetEmbeds(aPlugins);
}
nsIHTMLCollection*
nsHTMLDocument::Plugins()
{
return Embeds();
}
nsISupports*
nsHTMLDocument::ResolveName(const nsAString& aName, nsWrapperCache **aCache)
{
nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aName);
if (!entry) {
*aCache = nullptr;
return nullptr;
}
nsBaseContentList *list = entry->GetNameContentList();
uint32_t length = list ? list->Length() : 0;
if (length > 0) {
if (length == 1) {
// Only one element in the list, return the element instead of returning
// the list.
nsIContent *node = list->Item(0);
*aCache = node;
return node;
}
// The list contains more than one element, return the whole list.
*aCache = list;
return list;
}
// No named items were found, see if there's one registerd by id for aName.
Element *e = entry->GetIdElement();
if (e && nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(e)) {
*aCache = e;
return e;
}
*aCache = nullptr;
return nullptr;
}
void
nsHTMLDocument::NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
JS::MutableHandle<JSObject*> aRetval,
ErrorResult& rv)
{
nsWrapperCache* cache;
nsISupports* supp = ResolveName(aName, &cache);
if (!supp) {
aFound = false;
aRetval.set(nullptr);
return;
}
JS::Rooted<JS::Value> val(cx);
if (!dom::WrapObject(cx, supp, cache, nullptr, &val)) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aFound = true;
aRetval.set(&val.toObject());
}
void
nsHTMLDocument::GetSupportedNames(nsTArray<nsString>& aNames)
{
for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
nsIdentifierMapEntry* entry = iter.Get();
if (entry->HasNameElement() ||
entry->HasIdElementExposedAsHTMLDocumentProperty()) {
aNames.AppendElement(entry->GetKey());
}
}
}
//----------------------------
// forms related stuff
NS_IMETHODIMP
nsHTMLDocument::GetForms(nsIDOMHTMLCollection** aForms)
{
NS_ADDREF(*aForms = nsHTMLDocument::GetForms());
return NS_OK;
}
nsContentList*
nsHTMLDocument::GetForms()
{
if (!mForms) {
mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form, nsGkAtoms::form);
}
return mForms;
}
static bool MatchFormControls(Element* aElement, int32_t aNamespaceID,
nsIAtom* aAtom, void* aData)
{
return aElement->IsNodeOfType(nsIContent::eHTML_FORM_CONTROL);
}
nsContentList*
nsHTMLDocument::GetFormControls()
{
if (!mFormControls) {
mFormControls = new nsContentList(this, MatchFormControls, nullptr, nullptr);
}
return mFormControls;
}
nsresult
nsHTMLDocument::CreateAndAddWyciwygChannel(void)
{
nsresult rv = NS_OK;
nsAutoCString url, originalSpec;
mDocumentURI->GetSpec(originalSpec);
// Generate the wyciwyg url
url = NS_LITERAL_CSTRING("wyciwyg://")
+ nsPrintfCString("%d", gWyciwygSessionCnt++)
+ NS_LITERAL_CSTRING("/")
+ originalSpec;
nsCOMPtr<nsIURI> wcwgURI;
NS_NewURI(getter_AddRefs(wcwgURI), url);
// Create the nsIWyciwygChannel to store out-of-band
// document.write() script to cache
nsCOMPtr<nsIChannel> channel;
// Create a wyciwyg Channel
rv = NS_NewChannel(getter_AddRefs(channel),
wcwgURI,
NodePrincipal(),
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
nsIContentPolicy::TYPE_OTHER);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
loadInfo->SetPrincipalToInherit(NodePrincipal());
mWyciwygChannel = do_QueryInterface(channel);
mWyciwygChannel->SetSecurityInfo(mSecurityInfo);
// Note: we want to treat this like a "previous document" hint so that,
// e.g. a <meta> tag in the document.write content can override it.
SetDocumentCharacterSetSource(kCharsetFromHintPrevDoc);
mWyciwygChannel->SetCharsetAndSource(kCharsetFromHintPrevDoc,
GetDocumentCharacterSet());
// Inherit load flags from the original document's channel
channel->SetLoadFlags(mLoadFlags);
nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
// Use the Parent document's loadgroup to trigger load notifications
if (loadGroup && channel) {
rv = channel->SetLoadGroup(loadGroup);
NS_ENSURE_SUCCESS(rv, rv);
nsLoadFlags loadFlags = 0;
channel->GetLoadFlags(&loadFlags);
loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
channel->SetLoadFlags(loadFlags);
channel->SetOriginalURI(wcwgURI);
rv = loadGroup->AddRequest(mWyciwygChannel, nullptr);
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add request to load group.");
}
return rv;
}
nsresult
nsHTMLDocument::RemoveWyciwygChannel(void)
{
nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
// note there can be a write request without a load group if
// this is a synchronously constructed about:blank document
if (loadGroup && mWyciwygChannel) {
mWyciwygChannel->CloseCacheEntry(NS_OK);
loadGroup->RemoveRequest(mWyciwygChannel, nullptr, NS_OK);
}
mWyciwygChannel = nullptr;
return NS_OK;
}
void *
nsHTMLDocument::GenerateParserKey(void)
{
if (!mScriptLoader) {
// If we don't have a script loader, then the parser probably isn't parsing
// anything anyway, so just return null.
return nullptr;
}
// The script loader provides us with the currently executing script element,
// which is guaranteed to be unique per script.
nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
if (script && mParser && mParser->IsScriptCreated()) {
nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
if (creatorParser != mParser) {
// Make scripts that aren't inserted by the active parser of this document
// participate in the context of the script that document.open()ed
// this document.
return nullptr;
}
}
return script;
}
NS_IMETHODIMP
nsHTMLDocument::GetDesignMode(nsAString& aDesignMode)
{
if (HasFlag(NODE_IS_EDITABLE)) {
aDesignMode.AssignLiteral("on");
}
else {
aDesignMode.AssignLiteral("off");
}
return NS_OK;
}
void
nsHTMLDocument::MaybeEditingStateChanged()
{
if (!mPendingMaybeEditingStateChanged &&
mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
if (nsContentUtils::IsSafeToRunScript()) {
EditingStateChanged();
} else if (!mInDestructor) {
nsContentUtils::AddScriptRunner(
NewRunnableMethod(this, &nsHTMLDocument::MaybeEditingStateChanged));
}
}
}
void
nsHTMLDocument::EndUpdate(nsUpdateType aUpdateType)
{
const bool reset = !mPendingMaybeEditingStateChanged;
mPendingMaybeEditingStateChanged = true;
nsDocument::EndUpdate(aUpdateType);
if (reset) {
mPendingMaybeEditingStateChanged = false;
}
MaybeEditingStateChanged();
}
// Helper class, used below in ChangeContentEditableCount().
class DeferredContentEditableCountChangeEvent : public Runnable
{
public:
DeferredContentEditableCountChangeEvent(nsHTMLDocument *aDoc,
nsIContent *aElement)
: mDoc(aDoc)
, mElement(aElement)
{
}
NS_IMETHOD Run() override {
if (mElement && mElement->OwnerDoc() == mDoc) {
mDoc->DeferredContentEditableCountChange(mElement);
}
return NS_OK;
}
private:
RefPtr<nsHTMLDocument> mDoc;
nsCOMPtr<nsIContent> mElement;
};
nsresult
nsHTMLDocument::ChangeContentEditableCount(nsIContent *aElement,
int32_t aChange)
{
NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
"Trying to decrement too much.");
mContentEditableCount += aChange;
nsContentUtils::AddScriptRunner(
new DeferredContentEditableCountChangeEvent(this, aElement));
return NS_OK;
}
void
nsHTMLDocument::DeferredContentEditableCountChange(nsIContent *aElement)
{
if (mParser ||
(mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
return;
}
EditingState oldState = mEditingState;
nsresult rv = EditingStateChanged();
NS_ENSURE_SUCCESS_VOID(rv);
if (oldState == mEditingState && mEditingState == eContentEditable) {
// We just changed the contentEditable state of a node, we need to reset
// the spellchecking state of that node.
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
if (node) {
nsPIDOMWindowOuter *window = GetWindow();
if (!window)
return;
nsIDocShell *docshell = window->GetDocShell();
if (!docshell)
return;
nsCOMPtr<nsIEditor> editor;
docshell->GetEditor(getter_AddRefs(editor));
if (editor) {
RefPtr<nsRange> range = new nsRange(aElement);
rv = range->SelectNode(node);
if (NS_FAILED(rv)) {
// The node might be detached from the document at this point,
// which would cause this call to fail. In this case, we can
// safely ignore the contenteditable count change.
return;
}
nsCOMPtr<nsIInlineSpellChecker> spellChecker;
rv = editor->GetInlineSpellChecker(false,
getter_AddRefs(spellChecker));
NS_ENSURE_SUCCESS_VOID(rv);
if (spellChecker) {
rv = spellChecker->SpellCheckRange(range);
}
}
}
}
}
HTMLAllCollection*
nsHTMLDocument::All()
{
if (!mAll) {
mAll = new HTMLAllCollection(this);
}
return mAll;
}
static void
NotifyEditableStateChange(nsINode *aNode, nsIDocument *aDocument)
{
for (nsIContent* child = aNode->GetFirstChild();
child;
child = child->GetNextSibling()) {
if (child->IsElement()) {
child->AsElement()->UpdateState(true);
}
NotifyEditableStateChange(child, aDocument);
}
}
void
nsHTMLDocument::TearingDownEditor(nsIEditor *aEditor)
{
if (IsEditingOn()) {
EditingState oldState = mEditingState;
mEditingState = eTearingDown;
nsCOMPtr<nsIPresShell> presShell = GetShell();
if (!presShell)
return;
nsTArray<RefPtr<StyleSheet>> agentSheets;
presShell->GetAgentStyleSheets(agentSheets);
auto cache = nsLayoutStylesheetCache::For(GetStyleBackendType());
agentSheets.RemoveElement(cache->ContentEditableSheet());
if (oldState == eDesignMode)
agentSheets.RemoveElement(cache->DesignModeSheet());
presShell->SetAgentStyleSheets(agentSheets);
presShell->RestyleForCSSRuleChanges();
}
}
nsresult
nsHTMLDocument::TurnEditingOff()
{
NS_ASSERTION(mEditingState != eOff, "Editing is already off.");
nsPIDOMWindowOuter *window = GetWindow();
if (!window)
return NS_ERROR_FAILURE;
nsIDocShell *docshell = window->GetDocShell();
if (!docshell)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIEditingSession> editSession;
nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
NS_ENSURE_SUCCESS(rv, rv);
// turn editing off
rv = editSession->TearDownEditorOnWindow(window);
NS_ENSURE_SUCCESS(rv, rv);
mEditingState = eOff;
return NS_OK;
}
static bool HasPresShell(nsPIDOMWindowOuter *aWindow)
{
nsIDocShell *docShell = aWindow->GetDocShell();
if (!docShell)
return false;
return docShell->GetPresShell() != nullptr;
}
nsresult
nsHTMLDocument::SetEditingState(EditingState aState)
{
mEditingState = aState;
return NS_OK;
}
nsresult
nsHTMLDocument::EditingStateChanged()
{
if (mRemovedFromDocShell) {
return NS_OK;
}
if (mEditingState == eSettingUp || mEditingState == eTearingDown) {
// XXX We shouldn't recurse
return NS_OK;
}
bool designMode = HasFlag(NODE_IS_EDITABLE);
EditingState newState = designMode ? eDesignMode :
(mContentEditableCount > 0 ? eContentEditable : eOff);
if (mEditingState == newState) {
// No changes in editing mode.
return NS_OK;
}
if (newState == eOff) {
// Editing is being turned off.
nsAutoScriptBlocker scriptBlocker;
NotifyEditableStateChange(this, this);
return TurnEditingOff();
}
// Flush out style changes on our _parent_ document, if any, so that
// our check for a presshell won't get stale information.
if (mParentDocument) {
mParentDocument->FlushPendingNotifications(Flush_Style);
}
// get editing session, make sure this is a strong reference so the
// window can't get deleted during the rest of this call.
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
if (!window)
return NS_ERROR_FAILURE;
nsIDocShell *docshell = window->GetDocShell();
if (!docshell)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIEditingSession> editSession;
nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIEditor> existingEditor;
editSession->GetEditorForWindow(window, getter_AddRefs(existingEditor));
if (existingEditor) {
// We might already have an editor if it was set up for mail, let's see
// if this is actually the case.
#ifdef DEBUG
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(existingEditor);
MOZ_ASSERT(htmlEditor, "If we have an editor, it must be an HTML editor");
#endif
uint32_t flags = 0;
existingEditor->GetFlags(&flags);
if (flags & nsIPlaintextEditor::eEditorMailMask) {
// We already have a mail editor, then we should not attempt to create
// another one.
return NS_OK;
}
}
if (!HasPresShell(window)) {
// We should not make the window editable or setup its editor.
// It's probably style=display:none.
return NS_OK;
}
bool makeWindowEditable = mEditingState == eOff;
bool updateState = false;
bool spellRecheckAll = false;
bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
nsCOMPtr<nsIEditor> editor;
{
EditingState oldState = mEditingState;
nsAutoEditingState push(this, eSettingUp);
nsCOMPtr<nsIPresShell> presShell = GetShell();
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
// Before making this window editable, we need to modify UA style sheet
// because new style may change whether focused element will be focusable
// or not.
nsTArray<RefPtr<StyleSheet>> agentSheets;
rv = presShell->GetAgentStyleSheets(agentSheets);
NS_ENSURE_SUCCESS(rv, rv);
auto cache = nsLayoutStylesheetCache::For(GetStyleBackendType());
StyleSheet* contentEditableSheet = cache->ContentEditableSheet();
if (!agentSheets.Contains(contentEditableSheet)) {
agentSheets.AppendElement(contentEditableSheet);
}
// Should we update the editable state of all the nodes in the document? We
// need to do this when the designMode value changes, as that overrides
// specific states on the elements.
if (designMode) {
// designMode is being turned on (overrides contentEditable).
StyleSheet* designModeSheet = cache->DesignModeSheet();
if (!agentSheets.Contains(designModeSheet)) {
agentSheets.AppendElement(designModeSheet);
}
updateState = true;
spellRecheckAll = oldState == eContentEditable;
}
else if (oldState == eDesignMode) {
// designMode is being turned off (contentEditable is still on).
agentSheets.RemoveElement(cache->DesignModeSheet());
updateState = true;
}
rv = presShell->SetAgentStyleSheets(agentSheets);
NS_ENSURE_SUCCESS(rv, rv);
presShell->RestyleForCSSRuleChanges();
// Adjust focused element with new style but blur event shouldn't be fired
// until mEditingState is modified with newState.
nsAutoScriptBlocker scriptBlocker;
if (designMode) {
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
nsIContent* focusedContent =
nsFocusManager::GetFocusedDescendant(window, false,
getter_AddRefs(focusedWindow));
if (focusedContent) {
nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable() :
!focusedContent->IsFocusable();
if (clearFocus) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->ClearFocus(window);
// If we need to dispatch blur event, we should put off after
// modifying mEditingState since blur event handler may change
// designMode state again.
putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
}
}
}
}
if (makeWindowEditable) {
// Editing is being turned on (through designMode or contentEditable)
// Turn on editor.
// XXX This can cause flushing which can change the editing state, so make
// sure to avoid recursing.
rv = editSession->MakeWindowEditable(window, "html", false, false,
true);
NS_ENSURE_SUCCESS(rv, rv);
}
// XXX Need to call TearDownEditorOnWindow for all failures.
docshell->GetEditor(getter_AddRefs(editor));
if (!editor)
return NS_ERROR_FAILURE;
// If we're entering the design mode, put the selection at the beginning of
// the document for compatibility reasons.
if (designMode && oldState == eOff) {
editor->BeginningOfDocument();
}
if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
nsContentUtils::AddScriptBlocker();
}
}
mEditingState = newState;
if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
nsContentUtils::RemoveScriptBlocker();
// If mEditingState is overwritten by another call and already disabled
// the editing, we shouldn't keep making window editable.
if (mEditingState == eOff) {
return NS_OK;
}
}
if (makeWindowEditable) {
// Set the editor to not insert br's on return when in p
// elements by default.
// XXX Do we only want to do this for designMode?
bool unused;
rv = ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), false,
NS_LITERAL_STRING("false"), &unused);
if (NS_FAILED(rv)) {
// Editor setup failed. Editing is not on after all.
// XXX Should we reset the editable flag on nodes?
editSession->TearDownEditorOnWindow(window);
mEditingState = eOff;
return rv;
}
}
if (updateState) {
nsAutoScriptBlocker scriptBlocker;
NotifyEditableStateChange(this, this);
}
// Resync the editor's spellcheck state.
if (spellRecheckAll) {
nsCOMPtr<nsISelectionController> selcon;
nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISelection> spellCheckSelection;
rv = selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
getter_AddRefs(spellCheckSelection));
if (NS_SUCCEEDED(rv)) {
spellCheckSelection->RemoveAllRanges();
}
}
editor->SyncRealTimeSpell();
return NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode)
{
ErrorResult rv;
SetDesignMode(aDesignMode, nsContentUtils::GetCurrentJSContext()
? Some(nsContentUtils::SubjectPrincipal())
: Nothing(), rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& rv)
{
SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
}
void
nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode,
const Maybe<nsIPrincipal*>& aSubjectPrincipal,
ErrorResult& rv)
{
if (aSubjectPrincipal.isSome() &&
!aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
return;
}
bool editableMode = HasFlag(NODE_IS_EDITABLE);
if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
SetEditableFlag(!editableMode);
rv = EditingStateChanged();
}
}
nsresult
nsHTMLDocument::GetMidasCommandManager(nsICommandManager** aCmdMgr)
{
// initialize return value
NS_ENSURE_ARG_POINTER(aCmdMgr);
// check if we have it cached
if (mMidasCommandManager) {
NS_ADDREF(*aCmdMgr = mMidasCommandManager);
return NS_OK;
}
*aCmdMgr = nullptr;
nsPIDOMWindowOuter *window = GetWindow();
if (!window)
return NS_ERROR_FAILURE;
nsIDocShell *docshell = window->GetDocShell();
if (!docshell)
return NS_ERROR_FAILURE;
mMidasCommandManager = docshell->GetCommandManager();
if (!mMidasCommandManager)
return NS_ERROR_FAILURE;
NS_ADDREF(*aCmdMgr = mMidasCommandManager);
return NS_OK;
}
struct MidasCommand {
const char* incomingCommandString;
const char* internalCommandString;
const char* internalParamString;
bool useNewParam;
bool convertToBoolean;
};
static const struct MidasCommand gMidasCommandTable[] = {
{ "bold", "cmd_bold", "", true, false },
{ "italic", "cmd_italic", "", true, false },
{ "underline", "cmd_underline", "", true, false },
{ "strikethrough", "cmd_strikethrough", "", true, false },
{ "subscript", "cmd_subscript", "", true, false },
{ "superscript", "cmd_superscript", "", true, false },
{ "cut", "cmd_cut", "", true, false },
{ "copy", "cmd_copy", "", true, false },
{ "paste", "cmd_paste", "", true, false },
{ "delete", "cmd_deleteCharBackward", "", true, false },
{ "forwarddelete", "cmd_deleteCharForward", "", true, false },
{ "selectall", "cmd_selectAll", "", true, false },
{ "undo", "cmd_undo", "", true, false },
{ "redo", "cmd_redo", "", true, false },
{ "indent", "cmd_indent", "", true, false },
{ "outdent", "cmd_outdent", "", true, false },
{ "backcolor", "cmd_highlight", "", false, false },
{ "forecolor", "cmd_fontColor", "", false, false },
{ "hilitecolor", "cmd_highlight", "", false, false },
{ "fontname", "cmd_fontFace", "", false, false },
{ "fontsize", "cmd_fontSize", "", false, false },
{ "increasefontsize", "cmd_increaseFont", "", false, false },
{ "decreasefontsize", "cmd_decreaseFont", "", false, false },
{ "inserthorizontalrule", "cmd_insertHR", "", true, false },
{ "createlink", "cmd_insertLinkNoUI", "", false, false },
{ "insertimage", "cmd_insertImageNoUI", "", false, false },
{ "inserthtml", "cmd_insertHTML", "", false, false },
{ "inserttext", "cmd_insertText", "", false, false },
{ "gethtml", "cmd_getContents", "", false, false },
{ "justifyleft", "cmd_align", "left", true, false },
{ "justifyright", "cmd_align", "right", true, false },
{ "justifycenter", "cmd_align", "center", true, false },
{ "justifyfull", "cmd_align", "justify", true, false },
{ "removeformat", "cmd_removeStyles", "", true, false },
{ "unlink", "cmd_removeLinks", "", true, false },
{ "insertorderedlist", "cmd_ol", "", true, false },
{ "insertunorderedlist", "cmd_ul", "", true, false },
{ "insertparagraph", "cmd_insertParagraph", "", true, false },
{ "insertlinebreak", "cmd_insertLineBreak", "", true, false },
{ "formatblock", "cmd_paragraphState", "", false, false },
{ "heading", "cmd_paragraphState", "", false, false },
{ "styleWithCSS", "cmd_setDocumentUseCSS", "", false, true },
{ "contentReadOnly", "cmd_setDocumentReadOnly", "", false, true },
{ "insertBrOnReturn", "cmd_insertBrOnReturn", "", false, true },
{ "enableObjectResizing", "cmd_enableObjectResizing", "", false, true },
{ "enableInlineTableEditing", "cmd_enableInlineTableEditing", "", false, true },
#if 0
// no editor support to remove alignments right now
{ "justifynone", "cmd_align", "", true, false },
// the following will need special review before being turned on
{ "saveas", "cmd_saveAs", "", true, false },
{ "print", "cmd_print", "", true, false },
#endif
{ nullptr, nullptr, nullptr, false, false }
};
#define MidasCommandCount ((sizeof(gMidasCommandTable) / sizeof(struct MidasCommand)) - 1)
static const char* const gBlocks[] = {
"ADDRESS",
"BLOCKQUOTE",
"DD",
"DIV",
"DL",
"DT",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"P",
"PRE"
};
static bool
ConvertToMidasInternalCommandInner(const nsAString& inCommandID,
const nsAString& inParam,
nsACString& outCommandID,
nsACString& outParam,
bool& outIsBoolean,
bool& outBooleanValue,
bool aIgnoreParams)
{
NS_ConvertUTF16toUTF8 convertedCommandID(inCommandID);
// Hack to support old boolean commands that were backwards (see bug 301490).
bool invertBool = false;
if (convertedCommandID.LowerCaseEqualsLiteral("usecss")) {
convertedCommandID.AssignLiteral("styleWithCSS");
invertBool = true;
} else if (convertedCommandID.LowerCaseEqualsLiteral("readonly")) {
convertedCommandID.AssignLiteral("contentReadOnly");
invertBool = true;
}
uint32_t i;
bool found = false;
for (i = 0; i < MidasCommandCount; ++i) {
if (convertedCommandID.Equals(gMidasCommandTable[i].incomingCommandString,
nsCaseInsensitiveCStringComparator())) {
found = true;
break;
}
}
if (!found) {
// reset results if the command is not found in our table
outCommandID.SetLength(0);
outParam.SetLength(0);
outIsBoolean = false;
return false;
}
// set outCommandID (what we use internally)
outCommandID.Assign(gMidasCommandTable[i].internalCommandString);
// set outParam & outIsBoolean based on flags from the table
outIsBoolean = gMidasCommandTable[i].convertToBoolean;
if (aIgnoreParams) {
// No further work to do
return true;
}
if (gMidasCommandTable[i].useNewParam) {
// Just have to copy it, no checking
outParam.Assign(gMidasCommandTable[i].internalParamString);
return true;
}
// handle checking of param passed in
if (outIsBoolean) {
// If this is a boolean value and it's not explicitly false (e.g. no value)
// we default to "true". For old backwards commands we invert the check (see
// bug 301490).
if (invertBool) {
outBooleanValue = inParam.LowerCaseEqualsLiteral("false");
} else {
outBooleanValue = !inParam.LowerCaseEqualsLiteral("false");
}
outParam.Truncate();
return true;
}
// String parameter -- see if we need to convert it (necessary for
// cmd_paragraphState and cmd_fontSize)
if (outCommandID.EqualsLiteral("cmd_paragraphState")) {
const char16_t* start = inParam.BeginReading();
const char16_t* end = inParam.EndReading();
if (start != end && *start == '<' && *(end - 1) == '>') {
++start;
--end;
}
NS_ConvertUTF16toUTF8 convertedParam(Substring(start, end));
uint32_t j;
for (j = 0; j < ArrayLength(gBlocks); ++j) {
if (convertedParam.Equals(gBlocks[j],
nsCaseInsensitiveCStringComparator())) {
outParam.Assign(gBlocks[j]);
break;
}
}
if (j == ArrayLength(gBlocks)) {
outParam.Truncate();
}
} else if (outCommandID.EqualsLiteral("cmd_fontSize")) {
// Per editing spec as of April 23, 2012, we need to reject the value if
// it's not a valid floating-point number surrounded by optional whitespace.
// Otherwise, we parse it as a legacy font size. For now, we just parse as
// a legacy font size regardless (matching WebKit) -- bug 747879.
outParam.Truncate();
int32_t size = nsContentUtils::ParseLegacyFontSize(inParam);
if (size) {
outParam.AppendInt(size);
}
} else {
CopyUTF16toUTF8(inParam, outParam);
}
return true;
}
static bool
ConvertToMidasInternalCommand(const nsAString & inCommandID,
const nsAString & inParam,
nsACString& outCommandID,
nsACString& outParam,
bool& outIsBoolean,
bool& outBooleanValue)
{
return ConvertToMidasInternalCommandInner(inCommandID, inParam, outCommandID,
outParam, outIsBoolean,
outBooleanValue, false);
}
static bool
ConvertToMidasInternalCommand(const nsAString & inCommandID,
nsACString& outCommandID)
{
nsAutoCString dummyCString;
nsAutoString dummyString;
bool dummyBool;
return ConvertToMidasInternalCommandInner(inCommandID, dummyString,
outCommandID, dummyCString,
dummyBool, dummyBool, true);
}
/* TODO: don't let this call do anything if the page is not done loading */
NS_IMETHODIMP
nsHTMLDocument::ExecCommand(const nsAString& commandID,
bool doShowUI,
const nsAString& value,
bool* _retval)
{
ErrorResult rv;
*_retval = ExecCommand(commandID, doShowUI, value, rv);
return rv.StealNSResult();
}
bool
nsHTMLDocument::ExecCommand(const nsAString& commandID,
bool doShowUI,
const nsAString& value,
ErrorResult& rv)
{
// for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
// this might add some ugly JS dependencies?
nsAutoCString cmdToDispatch, paramStr;
bool isBool, boolVal;
if (!ConvertToMidasInternalCommand(commandID, value,
cmdToDispatch, paramStr,
isBool, boolVal)) {
return false;
}
bool isCutCopy = (commandID.LowerCaseEqualsLiteral("cut") ||
commandID.LowerCaseEqualsLiteral("copy"));
// if editing is not on, bail
if (!isCutCopy && !IsEditingOnAfterFlush()) {
return false;
}
// if they are requesting UI from us, let's fail since we have no UI
if (doShowUI) {
return false;
}
// special case for cut & copy
// cut & copy are allowed in non editable documents
if (isCutCopy) {
if (!nsContentUtils::IsCutCopyAllowed()) {
// We have rejected the event due to it not being performed in an
// input-driven context therefore, we report the error to the console.
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("DOM"), this,
nsContentUtils::eDOM_PROPERTIES,
"ExecCommandCutCopyDeniedNotInputDriven");
return false;
}
// For cut & copy commands, we need the behaviour from nsWindowRoot::GetControllers
// which is to look at the focused element, and defer to a focused textbox's controller
// The code past taken by other commands in ExecCommand always uses the window directly,
// rather than deferring to the textbox, which is desireable for most editor commands,
// but not 'cut' and 'copy' (as those should allow copying out of embedded editors).
// This behaviour is invoked if we call DoCommand directly on the docShell.
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
if (docShell) {
nsresult res = docShell->DoCommand(cmdToDispatch.get());
return NS_SUCCEEDED(res);
}
return false;
}
if (commandID.LowerCaseEqualsLiteral("gethtml")) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
bool restricted = commandID.LowerCaseEqualsLiteral("paste");
if (restricted && !nsContentUtils::IsCallerChrome()) {
return false;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
if ((cmdToDispatch.EqualsLiteral("cmd_fontSize") ||
cmdToDispatch.EqualsLiteral("cmd_insertImageNoUI") ||
cmdToDispatch.EqualsLiteral("cmd_insertLinkNoUI") ||
cmdToDispatch.EqualsLiteral("cmd_paragraphState")) &&
paramStr.IsEmpty()) {
// Invalid value, return false
return false;
}
// Return false for disabled commands (bug 760052)
bool enabled = false;
cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &enabled);
if (!enabled) {
return false;
}
if (!isBool && paramStr.IsEmpty()) {
rv = cmdMgr->DoCommand(cmdToDispatch.get(), nullptr, window);
} else {
// we have a command that requires a parameter, create params
nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
NS_COMMAND_PARAMS_CONTRACTID);
if (!cmdParams) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
if (isBool) {
rv = cmdParams->SetBooleanValue("state_attribute", boolVal);
} else if (cmdToDispatch.EqualsLiteral("cmd_fontFace")) {
rv = cmdParams->SetStringValue("state_attribute", value);
} else if (cmdToDispatch.EqualsLiteral("cmd_insertHTML") ||
cmdToDispatch.EqualsLiteral("cmd_insertText")) {
rv = cmdParams->SetStringValue("state_data", value);
} else {
rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
}
if (rv.Failed()) {
return false;
}
rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
}
return !rv.Failed();
}
NS_IMETHODIMP
nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID,
bool* _retval)
{
ErrorResult rv;
*_retval = QueryCommandEnabled(commandID, rv);
return rv.StealNSResult();
}
bool
nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID, ErrorResult& rv)
{
nsAutoCString cmdToDispatch;
if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
return false;
}
// cut & copy are always allowed
bool isCutCopy = commandID.LowerCaseEqualsLiteral("cut") ||
commandID.LowerCaseEqualsLiteral("copy");
if (isCutCopy) {
return nsContentUtils::IsCutCopyAllowed();
}
// Report false for restricted commands
bool restricted = commandID.LowerCaseEqualsLiteral("paste");
if (restricted && !nsContentUtils::IsCallerChrome()) {
return false;
}
// if editing is not on, bail
if (!IsEditingOnAfterFlush()) {
return false;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
bool retval;
rv = cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &retval);
return retval;
}
NS_IMETHODIMP
nsHTMLDocument::QueryCommandIndeterm(const nsAString & commandID,
bool *_retval)
{
ErrorResult rv;
*_retval = QueryCommandIndeterm(commandID, rv);
return rv.StealNSResult();
}
bool
nsHTMLDocument::QueryCommandIndeterm(const nsAString& commandID, ErrorResult& rv)
{
nsAutoCString cmdToDispatch;
if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
return false;
}
// if editing is not on, bail
if (!IsEditingOnAfterFlush()) {
return false;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
nsresult res;
nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
NS_COMMAND_PARAMS_CONTRACTID, &res);
if (NS_FAILED(res)) {
rv.Throw(res);
return false;
}
rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
if (rv.Failed()) {
return false;
}
// If command does not have a state_mixed value, this call fails and sets
// retval to false. This is fine -- we want to return false in that case
// anyway (bug 738385), so we just don't throw regardless.
bool retval = false;
cmdParams->GetBooleanValue("state_mixed", &retval);
return retval;
}
NS_IMETHODIMP
nsHTMLDocument::QueryCommandState(const nsAString & commandID, bool *_retval)
{
ErrorResult rv;
*_retval = QueryCommandState(commandID, rv);
return rv.StealNSResult();
}
bool
nsHTMLDocument::QueryCommandState(const nsAString& commandID, ErrorResult& rv)
{
nsAutoCString cmdToDispatch, paramToCheck;
bool dummy, dummy2;
if (!ConvertToMidasInternalCommand(commandID, commandID,
cmdToDispatch, paramToCheck,
dummy, dummy2)) {
return false;
}
// if editing is not on, bail
if (!IsEditingOnAfterFlush()) {
return false;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
if (commandID.LowerCaseEqualsLiteral("usecss")) {
// Per spec, state is supported for styleWithCSS but not useCSS, so we just
// return false always.
return false;
}
nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
NS_COMMAND_PARAMS_CONTRACTID);
if (!cmdParams) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
if (rv.Failed()) {
return false;
}
// handle alignment as a special case (possibly other commands too?)
// Alignment is special because the external api is individual
// commands but internally we use cmd_align with different
// parameters. When getting the state of this command, we need to
// return the boolean for this particular alignment rather than the
// string of 'which alignment is this?'
if (cmdToDispatch.EqualsLiteral("cmd_align")) {
char * actualAlignmentType = nullptr;
rv = cmdParams->GetCStringValue("state_attribute", &actualAlignmentType);
bool retval = false;
if (!rv.Failed() && actualAlignmentType && actualAlignmentType[0]) {
retval = paramToCheck.Equals(actualAlignmentType);
}
if (actualAlignmentType) {
free(actualAlignmentType);
}
return retval;
}
// If command does not have a state_all value, this call fails and sets
// retval to false. This is fine -- we want to return false in that case
// anyway (bug 738385), so we just succeed and return false regardless.
bool retval = false;
cmdParams->GetBooleanValue("state_all", &retval);
return retval;
}
NS_IMETHODIMP
nsHTMLDocument::QueryCommandSupported(const nsAString & commandID,
bool *_retval)
{
*_retval = QueryCommandSupported(commandID);
return NS_OK;
}
bool
nsHTMLDocument::QueryCommandSupported(const nsAString& commandID)
{
// Gecko technically supports all the clipboard commands including
// cut/copy/paste, but non-privileged content will be unable to call
// paste, and depending on the pref "dom.allow_cut_copy", cut and copy
// may also be disallowed to be called from non-privileged content.
// For that reason, we report the support status of corresponding
// command accordingly.
if (!nsContentUtils::IsCallerChrome()) {
if (commandID.LowerCaseEqualsLiteral("paste")) {
return false;
}
if (nsContentUtils::IsCutCopyRestricted()) {
if (commandID.LowerCaseEqualsLiteral("cut") ||
commandID.LowerCaseEqualsLiteral("copy")) {
return false;
}
}
}
// commandID is supported if it can be converted to a Midas command
nsAutoCString cmdToDispatch;
return ConvertToMidasInternalCommand(commandID, cmdToDispatch);
}
NS_IMETHODIMP
nsHTMLDocument::QueryCommandValue(const nsAString & commandID,
nsAString &_retval)
{
ErrorResult rv;
QueryCommandValue(commandID, _retval, rv);
return rv.StealNSResult();
}
void
nsHTMLDocument::QueryCommandValue(const nsAString& commandID,
nsAString& aValue,
ErrorResult& rv)
{
aValue.Truncate();
nsAutoCString cmdToDispatch, paramStr;
if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
// Return empty string
return;
}
// if editing is not on, bail
if (!IsEditingOnAfterFlush()) {
return;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
return;
}
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
rv.Throw(NS_ERROR_FAILURE);
return;
}
// create params
nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
NS_COMMAND_PARAMS_CONTRACTID);
if (!cmdParams) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
// this is a special command since we are calling DoCommand rather than
// GetCommandState like the other commands
if (cmdToDispatch.EqualsLiteral("cmd_getContents")) {
rv = cmdParams->SetBooleanValue("selection_only", true);
if (rv.Failed()) {
return;
}
rv = cmdParams->SetCStringValue("format", "text/html");
if (rv.Failed()) {
return;
}
rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
if (rv.Failed()) {
return;
}
rv = cmdParams->GetStringValue("result", aValue);
return;
}
rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
if (rv.Failed()) {
return;
}
rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
if (rv.Failed()) {
return;
}
// If command does not have a state_attribute value, this call fails, and
// aValue will wind up being the empty string. This is fine -- we want to
// return "" in that case anyway (bug 738385), so we just return NS_OK
// regardless.
nsXPIDLCString cStringResult;
cmdParams->GetCStringValue("state_attribute",
getter_Copies(cStringResult));
CopyUTF8toUTF16(cStringResult, aValue);
}
nsresult
nsHTMLDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
{
NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager,
"Can't import this document into another document!");
RefPtr<nsHTMLDocument> clone = new nsHTMLDocument();
nsresult rv = CloneDocHelper(clone.get());
NS_ENSURE_SUCCESS(rv, rv);
// State from nsHTMLDocument
clone->mLoadFlags = mLoadFlags;
return CallQueryInterface(clone.get(), aResult);
}
bool
nsHTMLDocument::IsEditingOnAfterFlush()
{
nsIDocument* doc = GetParentDocument();
if (doc) {
// Make sure frames are up to date, since that can affect whether
// we're editable.
doc->FlushPendingNotifications(Flush_Frames);
}
return IsEditingOn();
}
void
nsHTMLDocument::RemovedFromDocShell()
{
mEditingState = eOff;
nsDocument::RemovedFromDocShell();
}
/* virtual */ void
nsHTMLDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
{
nsDocument::DocAddSizeOfExcludingThis(aWindowSizes);
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mImages
// - mApplets
// - mEmbeds
// - mLinks
// - mAnchors
// - mScripts
// - mForms
// - mFormControls
// - mWyciwygChannel
// - mMidasCommandManager
}
bool
nsHTMLDocument::WillIgnoreCharsetOverride()
{
if (mType != eHTML) {
MOZ_ASSERT(mType == eXHTML);
return true;
}
if (mCharacterSetSource == kCharsetFromByteOrderMark) {
return true;
}
if (!EncodingUtils::IsAsciiCompatible(mCharacterSet)) {
return true;
}
nsCOMPtr<nsIWyciwygChannel> wyciwyg = do_QueryInterface(mChannel);
if (wyciwyg) {
return true;
}
nsIURI* uri = GetOriginalURI();
if (uri) {
bool schemeIs = false;
uri->SchemeIs("about", &schemeIs);
if (schemeIs) {
return true;
}
bool isResource;
nsresult rv = NS_URIChainHasFlags(uri,
nsIProtocolHandler::URI_IS_UI_RESOURCE,
&isResource);
if (NS_FAILED(rv) || isResource) {
return true;
}
}
return false;
}