michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Base class for the XML and HTML content sinks, which construct a michael@0: * DOM based on information from the parser. michael@0: */ michael@0: michael@0: #include "nsContentSink.h" michael@0: #include "nsScriptLoader.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "nsStyleLinkElement.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsCPrefetchService.h" michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsIOfflineCacheUpdate.h" michael@0: #include "nsIApplicationCache.h" michael@0: #include "nsIApplicationCacheContainer.h" michael@0: #include "nsIApplicationCacheChannel.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsICookieService.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsIAppShell.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsGenericHTMLElement.h" michael@0: #include "nsHTMLDNSPrefetch.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsParserConstants.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: PRLogModuleInfo* gContentSinkLogModuleInfo; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentObserver) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSink) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsContentSink) michael@0: if (tmp->mDocument) { michael@0: tmp->mDocument->RemoveObserver(tmp); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: michael@0: nsContentSink::nsContentSink() michael@0: { michael@0: // We have a zeroing operator new michael@0: NS_ASSERTION(!mLayoutStarted, "What?"); michael@0: NS_ASSERTION(!mDynamicLowerValue, "What?"); michael@0: NS_ASSERTION(!mParsing, "What?"); michael@0: NS_ASSERTION(mLastSampledUserEventTime == 0, "What?"); michael@0: NS_ASSERTION(mDeflectedCount == 0, "What?"); michael@0: NS_ASSERTION(!mDroppedTimer, "What?"); michael@0: NS_ASSERTION(mInMonolithicContainer == 0, "What?"); michael@0: NS_ASSERTION(mInNotification == 0, "What?"); michael@0: NS_ASSERTION(!mDeferredLayoutStart, "What?"); michael@0: michael@0: #ifdef DEBUG michael@0: if (!gContentSinkLogModuleInfo) { michael@0: gContentSinkLogModuleInfo = PR_NewLogModule("nscontentsink"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsContentSink::~nsContentSink() michael@0: { michael@0: if (mDocument) { michael@0: // Remove ourselves just to be safe, though we really should have michael@0: // been removed in DidBuildModel if everything worked right. michael@0: mDocument->RemoveObserver(this); michael@0: } michael@0: } michael@0: michael@0: bool nsContentSink::sNotifyOnTimer; michael@0: int32_t nsContentSink::sBackoffCount; michael@0: int32_t nsContentSink::sNotificationInterval; michael@0: int32_t nsContentSink::sInteractiveDeflectCount; michael@0: int32_t nsContentSink::sPerfDeflectCount; michael@0: int32_t nsContentSink::sPendingEventMode; michael@0: int32_t nsContentSink::sEventProbeRate; michael@0: int32_t nsContentSink::sInteractiveParseTime; michael@0: int32_t nsContentSink::sPerfParseTime; michael@0: int32_t nsContentSink::sInteractiveTime; michael@0: int32_t nsContentSink::sInitialPerfTime; michael@0: int32_t nsContentSink::sEnablePerfMode; michael@0: michael@0: void michael@0: nsContentSink::InitializeStatics() michael@0: { michael@0: Preferences::AddBoolVarCache(&sNotifyOnTimer, michael@0: "content.notify.ontimer", true); michael@0: // -1 means never. michael@0: Preferences::AddIntVarCache(&sBackoffCount, michael@0: "content.notify.backoffcount", -1); michael@0: // The gNotificationInterval has a dramatic effect on how long it michael@0: // takes to initially display content for slow connections. michael@0: // The current value provides good michael@0: // incremental display of content without causing an increase michael@0: // in page load time. If this value is set below 1/10 of second michael@0: // it starts to impact page load performance. michael@0: // see bugzilla bug 72138 for more info. michael@0: Preferences::AddIntVarCache(&sNotificationInterval, michael@0: "content.notify.interval", 120000); michael@0: Preferences::AddIntVarCache(&sInteractiveDeflectCount, michael@0: "content.sink.interactive_deflect_count", 0); michael@0: Preferences::AddIntVarCache(&sPerfDeflectCount, michael@0: "content.sink.perf_deflect_count", 200); michael@0: Preferences::AddIntVarCache(&sPendingEventMode, michael@0: "content.sink.pending_event_mode", 1); michael@0: Preferences::AddIntVarCache(&sEventProbeRate, michael@0: "content.sink.event_probe_rate", 1); michael@0: Preferences::AddIntVarCache(&sInteractiveParseTime, michael@0: "content.sink.interactive_parse_time", 3000); michael@0: Preferences::AddIntVarCache(&sPerfParseTime, michael@0: "content.sink.perf_parse_time", 360000); michael@0: Preferences::AddIntVarCache(&sInteractiveTime, michael@0: "content.sink.interactive_time", 750000); michael@0: Preferences::AddIntVarCache(&sInitialPerfTime, michael@0: "content.sink.initial_perf_time", 2000000); michael@0: Preferences::AddIntVarCache(&sEnablePerfMode, michael@0: "content.sink.enable_perf_mode", 0); michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::Init(nsIDocument* aDoc, michael@0: nsIURI* aURI, michael@0: nsISupports* aContainer, michael@0: nsIChannel* aChannel) michael@0: { michael@0: NS_PRECONDITION(aDoc, "null ptr"); michael@0: NS_PRECONDITION(aURI, "null ptr"); michael@0: michael@0: if (!aDoc || !aURI) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: mDocument = aDoc; michael@0: michael@0: mDocumentURI = aURI; michael@0: mDocShell = do_QueryInterface(aContainer); michael@0: mScriptLoader = mDocument->ScriptLoader(); michael@0: michael@0: if (!mRunsToCompletion) { michael@0: if (mDocShell) { michael@0: uint32_t loadType = 0; michael@0: mDocShell->GetLoadType(&loadType); michael@0: mDocument->SetChangeScrollPosWhenScrollingToRef( michael@0: (loadType & nsIDocShell::LOAD_CMD_HISTORY) == 0); michael@0: } michael@0: michael@0: ProcessHTTPHeaders(aChannel); michael@0: } michael@0: michael@0: mCSSLoader = aDoc->CSSLoader(); michael@0: michael@0: mNodeInfoManager = aDoc->NodeInfoManager(); michael@0: michael@0: mBackoffCount = sBackoffCount; michael@0: michael@0: if (sEnablePerfMode != 0) { michael@0: mDynamicLowerValue = sEnablePerfMode == 1; michael@0: FavorPerformanceHint(!mDynamicLowerValue, 0); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentSink::StyleSheetLoaded(nsCSSStyleSheet* aSheet, michael@0: bool aWasAlternate, michael@0: nsresult aStatus) michael@0: { michael@0: NS_ASSERTION(!mRunsToCompletion, "How come a fragment parser observed sheets?"); michael@0: if (!aWasAlternate) { michael@0: NS_ASSERTION(mPendingSheetCount > 0, "How'd that happen?"); michael@0: --mPendingSheetCount; michael@0: michael@0: if (mPendingSheetCount == 0 && michael@0: (mDeferredLayoutStart || mDeferredFlushTags)) { michael@0: if (mDeferredFlushTags) { michael@0: FlushTags(); michael@0: } michael@0: if (mDeferredLayoutStart) { michael@0: // We might not have really started layout, since this sheet was still michael@0: // loading. Do it now. Probably doesn't matter whether we do this michael@0: // before or after we unblock scripts, but before feels saner. Note michael@0: // that if mDeferredLayoutStart is true, that means any subclass michael@0: // StartLayout() stuff that needs to happen has already happened, so we michael@0: // don't need to worry about it. michael@0: StartLayout(false); michael@0: } michael@0: michael@0: // Go ahead and try to scroll to our ref if we have one michael@0: ScrollToRef(); michael@0: } michael@0: michael@0: mScriptLoader->RemoveExecuteBlocker(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) michael@0: { michael@0: nsCOMPtr httpchannel(do_QueryInterface(aChannel)); michael@0: michael@0: if (!httpchannel) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Note that the only header we care about is the "link" header, since we michael@0: // have all the infrastructure for kicking off stylesheet loads. michael@0: michael@0: nsAutoCString linkHeader; michael@0: michael@0: nsresult rv = httpchannel->GetResponseHeader(NS_LITERAL_CSTRING("link"), michael@0: linkHeader); michael@0: if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) { michael@0: mDocument->SetHeaderData(nsGkAtoms::link, michael@0: NS_ConvertASCIItoUTF16(linkHeader)); michael@0: michael@0: NS_ASSERTION(!mProcessLinkHeaderEvent.get(), michael@0: "Already dispatched an event?"); michael@0: michael@0: mProcessLinkHeaderEvent = michael@0: NS_NewNonOwningRunnableMethod(this, michael@0: &nsContentSink::DoProcessLinkHeader); michael@0: rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get()); michael@0: if (NS_FAILED(rv)) { michael@0: mProcessLinkHeaderEvent.Forget(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue, michael@0: nsIContent* aContent) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: // necko doesn't process headers coming in from the parser michael@0: michael@0: mDocument->SetHeaderData(aHeader, aValue); michael@0: michael@0: if (aHeader == nsGkAtoms::setcookie) { michael@0: // Note: Necko already handles cookies set via the channel. We can't just michael@0: // call SetCookie on the channel because we want to do some security checks michael@0: // here. michael@0: nsCOMPtr cookieServ = michael@0: do_GetService(NS_COOKIESERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Get a URI from the document principal michael@0: michael@0: // We use the original codebase in case the codebase was changed michael@0: // by SetDomain michael@0: michael@0: // Note that a non-codebase principal (eg the system principal) will return michael@0: // a null URI. michael@0: nsCOMPtr codebaseURI; michael@0: rv = mDocument->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI)); michael@0: NS_ENSURE_TRUE(codebaseURI, rv); michael@0: michael@0: nsCOMPtr channel; michael@0: if (mParser) { michael@0: mParser->GetChannel(getter_AddRefs(channel)); michael@0: } michael@0: michael@0: rv = cookieServ->SetCookieString(codebaseURI, michael@0: nullptr, michael@0: NS_ConvertUTF16toUTF8(aValue).get(), michael@0: channel); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: else if (aHeader == nsGkAtoms::msthemecompatible) { michael@0: // Disable theming for the presshell if the value is no. michael@0: // XXXbz don't we want to support this as an HTTP header too? michael@0: nsAutoString value(aValue); michael@0: if (value.LowerCaseEqualsLiteral("no")) { michael@0: nsIPresShell* shell = mDocument->GetShell(); michael@0: if (shell) { michael@0: shell->DisableThemeSupport(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsContentSink::DoProcessLinkHeader() michael@0: { michael@0: nsAutoString value; michael@0: mDocument->GetHeaderData(nsGkAtoms::link, value); michael@0: ProcessLinkHeader(value); michael@0: } michael@0: michael@0: // check whether the Link header field applies to the context resource michael@0: // see michael@0: michael@0: bool michael@0: nsContentSink::LinkContextIsOurDocument(const nsSubstring& aAnchor) michael@0: { michael@0: if (aAnchor.IsEmpty()) { michael@0: // anchor parameter not present or empty -> same document reference michael@0: return true; michael@0: } michael@0: michael@0: nsIURI* docUri = mDocument->GetDocumentURI(); michael@0: michael@0: // the document URI might contain a fragment identifier ("#...') michael@0: // we want to ignore that because it's invisible to the server michael@0: // and just affects the local interpretation in the recipient michael@0: nsCOMPtr contextUri; michael@0: nsresult rv = docUri->CloneIgnoringRef(getter_AddRefs(contextUri)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // copying failed michael@0: return false; michael@0: } michael@0: michael@0: // resolve anchor against context michael@0: nsCOMPtr resolvedUri; michael@0: rv = NS_NewURI(getter_AddRefs(resolvedUri), aAnchor, michael@0: nullptr, contextUri); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // resolving failed michael@0: return false; michael@0: } michael@0: michael@0: bool same; michael@0: rv = contextUri->Equals(resolvedUri, &same); michael@0: if (NS_FAILED(rv)) { michael@0: // comparison failed michael@0: return false; michael@0: } michael@0: michael@0: return same; michael@0: } michael@0: michael@0: // Decode a parameter value using the encoding defined in RFC 5987 (in place) michael@0: // michael@0: // charset "'" [ language ] "'" value-chars michael@0: // michael@0: // returns true when decoding happened successfully (otherwise leaves michael@0: // passed value alone) michael@0: bool michael@0: nsContentSink::Decode5987Format(nsAString& aEncoded) { michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr mimehdrpar = michael@0: do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: nsAutoCString asciiValue; michael@0: michael@0: const char16_t* encstart = aEncoded.BeginReading(); michael@0: const char16_t* encend = aEncoded.EndReading(); michael@0: michael@0: // create a plain ASCII string, aborting if we can't do that michael@0: // converted form is always shorter than input michael@0: while (encstart != encend) { michael@0: if (*encstart > 0 && *encstart < 128) { michael@0: asciiValue.Append((char)*encstart); michael@0: } else { michael@0: return false; michael@0: } michael@0: encstart++; michael@0: } michael@0: michael@0: nsAutoString decoded; michael@0: nsAutoCString language; michael@0: michael@0: rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: aEncoded = decoded; michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessLinkHeader(const nsAString& aLinkData) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // keep track where we are within the header field michael@0: bool seenParameters = false; michael@0: michael@0: // parse link content and call process style link michael@0: nsAutoString href; michael@0: nsAutoString rel; michael@0: nsAutoString title; michael@0: nsAutoString titleStar; michael@0: nsAutoString type; michael@0: nsAutoString media; michael@0: nsAutoString anchor; michael@0: michael@0: // copy to work buffer michael@0: nsAutoString stringList(aLinkData); michael@0: michael@0: // put an extra null at the end michael@0: stringList.Append(kNullCh); michael@0: michael@0: char16_t* start = stringList.BeginWriting(); michael@0: char16_t* end = start; michael@0: char16_t* last = start; michael@0: char16_t endCh; michael@0: michael@0: while (*start != kNullCh) { michael@0: // skip leading space michael@0: while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) { michael@0: ++start; michael@0: } michael@0: michael@0: end = start; michael@0: last = end - 1; michael@0: michael@0: bool wasQuotedString = false; michael@0: michael@0: // look for semicolon or comma michael@0: while (*end != kNullCh && *end != kSemicolon && *end != kComma) { michael@0: char16_t ch = *end; michael@0: michael@0: if (ch == kQuote || ch == kLessThan) { michael@0: // quoted string michael@0: michael@0: char16_t quote = ch; michael@0: if (quote == kLessThan) { michael@0: quote = kGreaterThan; michael@0: } michael@0: michael@0: wasQuotedString = (ch == kQuote); michael@0: michael@0: char16_t* closeQuote = (end + 1); michael@0: michael@0: // seek closing quote michael@0: while (*closeQuote != kNullCh && quote != *closeQuote) { michael@0: // in quoted-string, "\" is an escape character michael@0: if (wasQuotedString && *closeQuote == kBackSlash && *(closeQuote + 1) != kNullCh) { michael@0: ++closeQuote; michael@0: } michael@0: michael@0: ++closeQuote; michael@0: } michael@0: michael@0: if (quote == *closeQuote) { michael@0: // found closer michael@0: michael@0: // skip to close quote michael@0: end = closeQuote; michael@0: michael@0: last = end - 1; michael@0: michael@0: ch = *(end + 1); michael@0: michael@0: if (ch != kNullCh && ch != kSemicolon && ch != kComma) { michael@0: // end string here michael@0: *(++end) = kNullCh; michael@0: michael@0: ch = *(end + 1); michael@0: michael@0: // keep going until semi or comma michael@0: while (ch != kNullCh && ch != kSemicolon && ch != kComma) { michael@0: ++end; michael@0: michael@0: ch = *end; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: ++end; michael@0: ++last; michael@0: } michael@0: michael@0: endCh = *end; michael@0: michael@0: // end string here michael@0: *end = kNullCh; michael@0: michael@0: if (start < end) { michael@0: if ((*start == kLessThan) && (*last == kGreaterThan)) { michael@0: *last = kNullCh; michael@0: michael@0: // first instance of <...> wins michael@0: // also, do not allow hrefs after the first param was seen michael@0: if (href.IsEmpty() && !seenParameters) { michael@0: href = (start + 1); michael@0: href.StripWhitespace(); michael@0: } michael@0: } else { michael@0: char16_t* equals = start; michael@0: seenParameters = true; michael@0: michael@0: while ((*equals != kNullCh) && (*equals != kEqual)) { michael@0: equals++; michael@0: } michael@0: michael@0: if (*equals != kNullCh) { michael@0: *equals = kNullCh; michael@0: nsAutoString attr(start); michael@0: attr.StripWhitespace(); michael@0: michael@0: char16_t* value = ++equals; michael@0: while (nsCRT::IsAsciiSpace(*value)) { michael@0: value++; michael@0: } michael@0: michael@0: if ((*value == kQuote) && (*value == *last)) { michael@0: *last = kNullCh; michael@0: value++; michael@0: } michael@0: michael@0: if (wasQuotedString) { michael@0: // unescape in-place michael@0: char16_t* unescaped = value; michael@0: char16_t *src = value; michael@0: michael@0: while (*src != kNullCh) { michael@0: if (*src == kBackSlash && *(src + 1) != kNullCh) { michael@0: src++; michael@0: } michael@0: *unescaped++ = *src++; michael@0: } michael@0: michael@0: *unescaped = kNullCh; michael@0: } michael@0: michael@0: if (attr.LowerCaseEqualsLiteral("rel")) { michael@0: if (rel.IsEmpty()) { michael@0: rel = value; michael@0: rel.CompressWhitespace(); michael@0: } michael@0: } else if (attr.LowerCaseEqualsLiteral("title")) { michael@0: if (title.IsEmpty()) { michael@0: title = value; michael@0: title.CompressWhitespace(); michael@0: } michael@0: } else if (attr.LowerCaseEqualsLiteral("title*")) { michael@0: if (titleStar.IsEmpty() && !wasQuotedString) { michael@0: // RFC 5987 encoding; uses token format only, so skip if we get michael@0: // here with a quoted-string michael@0: nsAutoString tmp; michael@0: tmp = value; michael@0: if (Decode5987Format(tmp)) { michael@0: titleStar = tmp; michael@0: titleStar.CompressWhitespace(); michael@0: } else { michael@0: // header value did not parse, throw it away michael@0: titleStar.Truncate(); michael@0: } michael@0: } michael@0: } else if (attr.LowerCaseEqualsLiteral("type")) { michael@0: if (type.IsEmpty()) { michael@0: type = value; michael@0: type.StripWhitespace(); michael@0: } michael@0: } else if (attr.LowerCaseEqualsLiteral("media")) { michael@0: if (media.IsEmpty()) { michael@0: media = value; michael@0: michael@0: // The HTML5 spec is formulated in terms of the CSS3 spec, michael@0: // which specifies that media queries are case insensitive. michael@0: nsContentUtils::ASCIIToLower(media); michael@0: } michael@0: } else if (attr.LowerCaseEqualsLiteral("anchor")) { michael@0: if (anchor.IsEmpty()) { michael@0: anchor = value; michael@0: anchor.StripWhitespace(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (endCh == kComma) { michael@0: // hit a comma, process what we've got so far michael@0: michael@0: href.Trim(" \t\n\r\f"); // trim HTML5 whitespace michael@0: if (!href.IsEmpty() && !rel.IsEmpty()) { michael@0: rv = ProcessLink(anchor, href, rel, michael@0: // prefer RFC 5987 variant over non-I18zed version michael@0: titleStar.IsEmpty() ? title : titleStar, michael@0: type, media); michael@0: } michael@0: michael@0: href.Truncate(); michael@0: rel.Truncate(); michael@0: title.Truncate(); michael@0: type.Truncate(); michael@0: media.Truncate(); michael@0: anchor.Truncate(); michael@0: michael@0: seenParameters = false; michael@0: } michael@0: michael@0: start = ++end; michael@0: } michael@0: michael@0: href.Trim(" \t\n\r\f"); // trim HTML5 whitespace michael@0: if (!href.IsEmpty() && !rel.IsEmpty()) { michael@0: rv = ProcessLink(anchor, href, rel, michael@0: // prefer RFC 5987 variant over non-I18zed version michael@0: titleStar.IsEmpty() ? title : titleStar, michael@0: type, media); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessLink(const nsSubstring& aAnchor, const nsSubstring& aHref, michael@0: const nsSubstring& aRel, const nsSubstring& aTitle, michael@0: const nsSubstring& aType, const nsSubstring& aMedia) michael@0: { michael@0: uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(aRel); michael@0: michael@0: // The link relation may apply to a different resource, specified michael@0: // in the anchor parameter. For the link relations supported so far, michael@0: // we simply abort if the link applies to a resource different to the michael@0: // one we've loaded michael@0: if (!LinkContextIsOurDocument(aAnchor)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH; michael@0: // prefetch href if relation is "next" or "prefetch" michael@0: if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) { michael@0: PrefetchHref(aHref, mDocument, hasPrefetch); michael@0: } michael@0: michael@0: if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) { michael@0: PrefetchDNS(aHref); michael@0: } michael@0: michael@0: // is it a stylesheet link? michael@0: if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isAlternate = linkTypes & nsStyleLinkElement::eALTERNATE; michael@0: return ProcessStyleLink(nullptr, aHref, isAlternate, aTitle, aType, michael@0: aMedia); michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessStyleLink(nsIContent* aElement, michael@0: const nsSubstring& aHref, michael@0: bool aAlternate, michael@0: const nsSubstring& aTitle, michael@0: const nsSubstring& aType, michael@0: const nsSubstring& aMedia) michael@0: { michael@0: if (aAlternate && aTitle.IsEmpty()) { michael@0: // alternates must have title return without error, for now michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString mimeType; michael@0: nsAutoString params; michael@0: nsContentUtils::SplitMimeType(aType, mimeType, params); michael@0: michael@0: // see bug 18817 michael@0: if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) { michael@0: // Unknown stylesheet language michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr url; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(url), aHref, nullptr, michael@0: mDocument->GetDocBaseURI()); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // The URI is bad, move along, don't propagate the error (for now) michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(!aElement || michael@0: aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE, michael@0: "We only expect processing instructions here"); michael@0: michael@0: // If this is a fragment parser, we don't want to observe. michael@0: // We don't support CORS for processing instructions michael@0: bool isAlternate; michael@0: rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate, michael@0: CORS_NONE, michael@0: mRunsToCompletion ? nullptr : this, &isAlternate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!isAlternate && !mRunsToCompletion) { michael@0: ++mPendingSheetCount; michael@0: mScriptLoader->AddExecuteBlocker(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsContentSink::ProcessMETATag(nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent, "missing meta-element"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // set any HTTP-EQUIV data into document's header data as well as url michael@0: nsAutoString header; michael@0: aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); michael@0: if (!header.IsEmpty()) { michael@0: nsAutoString result; michael@0: aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result); michael@0: if (!result.IsEmpty()) { michael@0: nsContentUtils::ASCIIToLower(header); michael@0: nsCOMPtr fieldAtom(do_GetAtom(header)); michael@0: rv = ProcessHeaderData(fieldAtom, result, aContent); michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, michael@0: nsGkAtoms::handheldFriendly, eIgnoreCase)) { michael@0: nsAutoString result; michael@0: aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result); michael@0: if (!result.IsEmpty()) { michael@0: nsContentUtils::ASCIIToLower(result); michael@0: mDocument->SetHeaderData(nsGkAtoms::handheldFriendly, result); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsContentSink::PrefetchHref(const nsAString &aHref, michael@0: nsINode *aSource, michael@0: bool aExplicit) michael@0: { michael@0: // michael@0: // SECURITY CHECK: disable prefetching from mailnews! michael@0: // michael@0: // walk up the docshell tree to see if any containing michael@0: // docshell are of type MAIL. michael@0: // michael@0: if (!mDocShell) michael@0: return; michael@0: michael@0: nsCOMPtr docshell = mDocShell; michael@0: michael@0: nsCOMPtr parentItem; michael@0: do { michael@0: uint32_t appType = 0; michael@0: nsresult rv = docshell->GetAppType(&appType); michael@0: if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL) michael@0: return; // do not prefetch from mailnews michael@0: docshell->GetParent(getter_AddRefs(parentItem)); michael@0: if (parentItem) { michael@0: docshell = do_QueryInterface(parentItem); michael@0: if (!docshell) { michael@0: NS_ERROR("cannot get a docshell from a treeItem!"); michael@0: return; michael@0: } michael@0: } michael@0: } while (parentItem); michael@0: michael@0: // OK, we passed the security check... michael@0: michael@0: nsCOMPtr prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID)); michael@0: if (prefetchService) { michael@0: // construct URI using document charset michael@0: const nsACString &charset = mDocument->GetDocumentCharacterSet(); michael@0: nsCOMPtr uri; michael@0: NS_NewURI(getter_AddRefs(uri), aHref, michael@0: charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(), michael@0: mDocument->GetDocBaseURI()); michael@0: if (uri) { michael@0: nsCOMPtr domNode = do_QueryInterface(aSource); michael@0: prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentSink::PrefetchDNS(const nsAString &aHref) michael@0: { michael@0: nsAutoString hostname; michael@0: michael@0: if (StringBeginsWith(aHref, NS_LITERAL_STRING("//"))) { michael@0: hostname = Substring(aHref, 2); michael@0: } michael@0: else { michael@0: nsCOMPtr uri; michael@0: NS_NewURI(getter_AddRefs(uri), aHref); michael@0: if (!uri) { michael@0: return; michael@0: } michael@0: nsAutoCString host; michael@0: uri->GetHost(host); michael@0: CopyUTF8toUTF16(host, hostname); michael@0: } michael@0: michael@0: if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) { michael@0: nsHTMLDNSPrefetch::PrefetchLow(hostname); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache, michael@0: nsIURI *aManifestURI, michael@0: bool aFetchedWithHTTPGetOrEquiv, michael@0: CacheSelectionAction *aAction) michael@0: { michael@0: nsresult rv; michael@0: michael@0: *aAction = CACHE_SELECTION_NONE; michael@0: michael@0: nsCOMPtr applicationCacheDocument = michael@0: do_QueryInterface(mDocument); michael@0: NS_ASSERTION(applicationCacheDocument, michael@0: "mDocument must implement nsIApplicationCacheContainer."); michael@0: michael@0: if (aLoadApplicationCache) { michael@0: nsCOMPtr groupURI; michael@0: rv = aLoadApplicationCache->GetManifestURI(getter_AddRefs(groupURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool equal = false; michael@0: rv = groupURI->Equals(aManifestURI, &equal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!equal) { michael@0: // This is a foreign entry, force a reload to avoid loading the foreign michael@0: // entry. The entry will be marked as foreign to avoid loading it again. michael@0: michael@0: *aAction = CACHE_SELECTION_RELOAD; michael@0: } michael@0: else { michael@0: // The http manifest attribute URI is equal to the manifest URI of michael@0: // the cache the document was loaded from - associate the document with michael@0: // that cache and invoke the cache update process. michael@0: #ifdef DEBUG michael@0: nsAutoCString docURISpec, clientID; michael@0: mDocumentURI->GetAsciiSpec(docURISpec); michael@0: aLoadApplicationCache->GetClientID(clientID); michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS, michael@0: ("Selection: assigning app cache %s to document %s", clientID.get(), docURISpec.get())); michael@0: #endif michael@0: michael@0: rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Document will be added as implicit entry to the cache as part of michael@0: // the update process. michael@0: *aAction = CACHE_SELECTION_UPDATE; michael@0: } michael@0: } michael@0: else { michael@0: // The document was not loaded from an application cache michael@0: // Here we know the manifest has the same origin as the michael@0: // document. There is call to CheckMayLoad() on it above. michael@0: michael@0: if (!aFetchedWithHTTPGetOrEquiv) { michael@0: // The document was not loaded using HTTP GET or equivalent michael@0: // method. The spec says to run the cache selection algorithm w/o michael@0: // the manifest specified. michael@0: *aAction = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; michael@0: } michael@0: else { michael@0: // Always do an update in this case michael@0: *aAction = CACHE_SELECTION_UPDATE; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache, michael@0: nsIURI **aManifestURI, michael@0: CacheSelectionAction *aAction) michael@0: { michael@0: *aManifestURI = nullptr; michael@0: *aAction = CACHE_SELECTION_NONE; michael@0: michael@0: nsresult rv; michael@0: michael@0: if (aLoadApplicationCache) { michael@0: // The document was loaded from an application cache, use that michael@0: // application cache as the document's application cache. michael@0: nsCOMPtr applicationCacheDocument = michael@0: do_QueryInterface(mDocument); michael@0: NS_ASSERTION(applicationCacheDocument, michael@0: "mDocument must implement nsIApplicationCacheContainer."); michael@0: michael@0: #ifdef DEBUG michael@0: nsAutoCString docURISpec, clientID; michael@0: mDocumentURI->GetAsciiSpec(docURISpec); michael@0: aLoadApplicationCache->GetClientID(clientID); michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS, michael@0: ("Selection, no manifest: assigning app cache %s to document %s", clientID.get(), docURISpec.get())); michael@0: #endif michael@0: michael@0: rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Return the uri and invoke the update process for the selected michael@0: // application cache. michael@0: rv = aLoadApplicationCache->GetManifestURI(aManifestURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aAction = CACHE_SELECTION_UPDATE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsContentSink::ProcessOfflineManifest(nsIContent *aElement) michael@0: { michael@0: // Only check the manifest for root document nodes. michael@0: if (aElement != mDocument->GetRootElement()) { michael@0: return; michael@0: } michael@0: michael@0: // Don't bother processing offline manifest for documents michael@0: // without a docshell michael@0: if (!mDocShell) { michael@0: return; michael@0: } michael@0: michael@0: // Check for a manifest= attribute. michael@0: nsAutoString manifestSpec; michael@0: aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec); michael@0: ProcessOfflineManifest(manifestSpec); michael@0: } michael@0: michael@0: void michael@0: nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec) michael@0: { michael@0: // Don't bother processing offline manifest for documents michael@0: // without a docshell michael@0: if (!mDocShell) { michael@0: return; michael@0: } michael@0: michael@0: // If the docshell's in private browsing mode, we don't want to do any michael@0: // manifest processing. michael@0: nsCOMPtr loadContext = do_QueryInterface(mDocShell); michael@0: if (loadContext->UsePrivateBrowsing()) { michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // Grab the application cache the document was loaded from, if any. michael@0: nsCOMPtr applicationCache; michael@0: michael@0: nsCOMPtr applicationCacheChannel = michael@0: do_QueryInterface(mDocument->GetChannel()); michael@0: if (applicationCacheChannel) { michael@0: bool loadedFromApplicationCache; michael@0: rv = applicationCacheChannel->GetLoadedFromApplicationCache( michael@0: &loadedFromApplicationCache); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: if (loadedFromApplicationCache) { michael@0: rv = applicationCacheChannel->GetApplicationCache( michael@0: getter_AddRefs(applicationCache)); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aManifestSpec.IsEmpty() && !applicationCache) { michael@0: // Not loaded from an application cache, and no manifest michael@0: // attribute. Nothing to do here. michael@0: return; michael@0: } michael@0: michael@0: CacheSelectionAction action = CACHE_SELECTION_NONE; michael@0: nsCOMPtr manifestURI; michael@0: michael@0: if (aManifestSpec.IsEmpty()) { michael@0: action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; michael@0: } michael@0: else { michael@0: nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI), michael@0: aManifestSpec, mDocument, michael@0: mDocumentURI); michael@0: if (!manifestURI) { michael@0: return; michael@0: } michael@0: michael@0: // Documents must list a manifest from the same origin michael@0: rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, true, false); michael@0: if (NS_FAILED(rv)) { michael@0: action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; michael@0: } michael@0: else { michael@0: // Only continue if the document has permission to use offline APIs or michael@0: // when preferences indicate to permit it automatically. michael@0: if (!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal()) && michael@0: !nsContentUtils::MaybeAllowOfflineAppByDefault(mDocument->NodePrincipal(), mDocument->GetWindow()) && michael@0: !nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())) { michael@0: return; michael@0: } michael@0: michael@0: bool fetchedWithHTTPGetOrEquiv = false; michael@0: nsCOMPtr httpChannel(do_QueryInterface(mDocument->GetChannel())); michael@0: if (httpChannel) { michael@0: nsAutoCString method; michael@0: rv = httpChannel->GetRequestMethod(method); michael@0: if (NS_SUCCEEDED(rv)) michael@0: fetchedWithHTTPGetOrEquiv = method.Equals("GET"); michael@0: } michael@0: michael@0: rv = SelectDocAppCache(applicationCache, manifestURI, michael@0: fetchedWithHTTPGetOrEquiv, &action); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (action == CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST) { michael@0: rv = SelectDocAppCacheNoManifest(applicationCache, michael@0: getter_AddRefs(manifestURI), michael@0: &action); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: switch (action) michael@0: { michael@0: case CACHE_SELECTION_NONE: michael@0: break; michael@0: case CACHE_SELECTION_UPDATE: { michael@0: nsCOMPtr updateService = michael@0: do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); michael@0: michael@0: if (updateService) { michael@0: nsCOMPtr domdoc = do_QueryInterface(mDocument); michael@0: updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc); michael@0: } michael@0: break; michael@0: } michael@0: case CACHE_SELECTION_RELOAD: { michael@0: // This situation occurs only for toplevel documents, see bottom michael@0: // of SelectDocAppCache method. michael@0: // The document has been loaded from a different offline cache group than michael@0: // the manifest it refers to, i.e. this is a foreign entry, mark it as such michael@0: // and force a reload to avoid loading it. The next attempt will not michael@0: // choose it. michael@0: michael@0: applicationCacheChannel->MarkOfflineCacheEntryAsForeign(); michael@0: michael@0: nsCOMPtr webNav = do_QueryInterface(mDocShell); michael@0: michael@0: webNav->Stop(nsIWebNavigation::STOP_ALL); michael@0: webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE); michael@0: break; michael@0: } michael@0: default: michael@0: NS_ASSERTION(false, michael@0: "Cache selection algorithm didn't decide on proper action"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentSink::ScrollToRef() michael@0: { michael@0: mDocument->ScrollToRef(); michael@0: } michael@0: michael@0: void michael@0: nsContentSink::StartLayout(bool aIgnorePendingSheets) michael@0: { michael@0: if (mLayoutStarted) { michael@0: // Nothing to do here michael@0: return; michael@0: } michael@0: michael@0: mDeferredLayoutStart = true; michael@0: michael@0: if (!aIgnorePendingSheets && WaitForPendingSheets()) { michael@0: // Bail out; we'll start layout when the sheets load michael@0: return; michael@0: } michael@0: michael@0: mDeferredLayoutStart = false; michael@0: michael@0: // Notify on all our content. If none of our presshells have started layout michael@0: // yet it'll be a no-op except for updating our data structures, a la michael@0: // UpdateChildCounts() (because we don't want to double-notify on whatever we michael@0: // have right now). If some of them _have_ started layout, we want to make michael@0: // sure to flush tags instead of just calling UpdateChildCounts() after we michael@0: // loop over the shells. michael@0: FlushTags(); michael@0: michael@0: mLayoutStarted = true; michael@0: mLastNotificationTime = PR_Now(); michael@0: michael@0: mDocument->SetMayStartLayout(true); michael@0: nsCOMPtr shell = mDocument->GetShell(); michael@0: // Make sure we don't call Initialize() for a shell that has michael@0: // already called it. This can happen when the layout frame for michael@0: // an iframe is constructed *between* the Embed() call for the michael@0: // docshell in the iframe, and the content sink's call to OpenBody(). michael@0: // (Bug 153815) michael@0: if (shell && !shell->DidInitialize()) { michael@0: nsRect r = shell->GetPresContext()->GetVisibleArea(); michael@0: nsCOMPtr shellGrip = shell; michael@0: nsresult rv = shell->Initialize(r.width, r.height); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // If the document we are loading has a reference or it is a michael@0: // frameset document, disable the scroll bars on the views. michael@0: michael@0: mDocument->SetScrollToRef(mDocumentURI); michael@0: } michael@0: michael@0: void michael@0: nsContentSink::NotifyAppend(nsIContent* aContainer, uint32_t aStartIndex) michael@0: { michael@0: if (aContainer->GetCurrentDoc() != mDocument) { michael@0: // aContainer is not actually in our document anymore.... Just bail out of michael@0: // here; notifying on our document for this append would be wrong. michael@0: return; michael@0: } michael@0: michael@0: mInNotification++; michael@0: michael@0: { michael@0: // Scope so we call EndUpdate before we decrease mInNotification michael@0: MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate); michael@0: nsNodeUtils::ContentAppended(aContainer, michael@0: aContainer->GetChildAt(aStartIndex), michael@0: aStartIndex); michael@0: mLastNotificationTime = PR_Now(); michael@0: } michael@0: michael@0: mInNotification--; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentSink::Notify(nsITimer *timer) michael@0: { michael@0: if (mParsing) { michael@0: // We shouldn't interfere with our normal DidProcessAToken logic michael@0: mDroppedTimer = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (WaitForPendingSheets()) { michael@0: mDeferredFlushTags = true; michael@0: } else { michael@0: FlushTags(); michael@0: michael@0: // Now try and scroll to the reference michael@0: // XXX Should we scroll unconditionally for history loads?? michael@0: ScrollToRef(); michael@0: } michael@0: michael@0: mNotificationTimer = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsContentSink::IsTimeToNotify() michael@0: { michael@0: if (!sNotifyOnTimer || !mLayoutStarted || !mBackoffCount || michael@0: mInMonolithicContainer) { michael@0: return false; michael@0: } michael@0: michael@0: if (WaitForPendingSheets()) { michael@0: mDeferredFlushTags = true; michael@0: return false; michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: michael@0: int64_t interval = GetNotificationInterval(); michael@0: int64_t diff = now - mLastNotificationTime; michael@0: michael@0: if (diff > interval) { michael@0: mBackoffCount--; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::WillInterruptImpl() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS, michael@0: ("nsContentSink::WillInterrupt: this=%p", this)); michael@0: #ifndef SINK_NO_INCREMENTAL michael@0: if (WaitForPendingSheets()) { michael@0: mDeferredFlushTags = true; michael@0: } else if (sNotifyOnTimer && mLayoutStarted) { michael@0: if (mBackoffCount && !mInMonolithicContainer) { michael@0: int64_t now = PR_Now(); michael@0: int64_t interval = GetNotificationInterval(); michael@0: int64_t diff = now - mLastNotificationTime; michael@0: michael@0: // If it's already time for us to have a notification michael@0: if (diff > interval || mDroppedTimer) { michael@0: mBackoffCount--; michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW, michael@0: ("nsContentSink::WillInterrupt: flushing tags since we've " michael@0: "run out time; backoff count: %d", mBackoffCount)); michael@0: result = FlushTags(); michael@0: if (mDroppedTimer) { michael@0: ScrollToRef(); michael@0: mDroppedTimer = false; michael@0: } michael@0: } else if (!mNotificationTimer) { michael@0: interval -= diff; michael@0: int32_t delay = interval; michael@0: michael@0: // Convert to milliseconds michael@0: delay /= PR_USEC_PER_MSEC; michael@0: michael@0: mNotificationTimer = do_CreateInstance("@mozilla.org/timer;1", michael@0: &result); michael@0: if (NS_SUCCEEDED(result)) { michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW, michael@0: ("nsContentSink::WillInterrupt: setting up timer with " michael@0: "delay %d", delay)); michael@0: michael@0: result = michael@0: mNotificationTimer->InitWithCallback(this, delay, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: if (NS_FAILED(result)) { michael@0: mNotificationTimer = nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW, michael@0: ("nsContentSink::WillInterrupt: flushing tags " michael@0: "unconditionally")); michael@0: result = FlushTags(); michael@0: } michael@0: #endif michael@0: michael@0: mParsing = false; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::WillResumeImpl() michael@0: { michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS, michael@0: ("nsContentSink::WillResume: this=%p", this)); michael@0: michael@0: mParsing = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::DidProcessATokenImpl() michael@0: { michael@0: if (mRunsToCompletion || !mParser) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the current user event time michael@0: nsIPresShell *shell = mDocument->GetShell(); michael@0: if (!shell) { michael@0: // If there's no pres shell in the document, return early since michael@0: // we're not laying anything out here. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Increase before comparing to gEventProbeRate michael@0: ++mDeflectedCount; michael@0: michael@0: // Check if there's a pending event michael@0: if (sPendingEventMode != 0 && !mHasPendingEvent && michael@0: (mDeflectedCount % sEventProbeRate) == 0) { michael@0: nsViewManager* vm = shell->GetViewManager(); michael@0: NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); michael@0: nsCOMPtr widget; michael@0: vm->GetRootWidget(getter_AddRefs(widget)); michael@0: mHasPendingEvent = widget && widget->HasPendingInputEvent(); michael@0: } michael@0: michael@0: if (mHasPendingEvent && sPendingEventMode == 2) { michael@0: return NS_ERROR_HTMLPARSER_INTERRUPTED; michael@0: } michael@0: michael@0: // Have we processed enough tokens to check time? michael@0: if (!mHasPendingEvent && michael@0: mDeflectedCount < uint32_t(mDynamicLowerValue ? sInteractiveDeflectCount : michael@0: sPerfDeflectCount)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mDeflectedCount = 0; michael@0: michael@0: // Check if it's time to return to the main event loop michael@0: if (PR_IntervalToMicroseconds(PR_IntervalNow()) > mCurrentParseEndTime) { michael@0: return NS_ERROR_HTMLPARSER_INTERRUPTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsContentSink::FavorPerformanceHint(bool perfOverStarvation, uint32_t starvationDelay) michael@0: { michael@0: static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: if (appShell) michael@0: appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay); michael@0: } michael@0: michael@0: void michael@0: nsContentSink::BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType) michael@0: { michael@0: // Remember nested updates from updates that we started. michael@0: if (mInNotification > 0 && mUpdatesInNotification < 2) { michael@0: ++mUpdatesInNotification; michael@0: } michael@0: michael@0: // If we're in a script and we didn't do the notification, michael@0: // something else in the script processing caused the michael@0: // notification to occur. Since this could result in frame michael@0: // creation, make sure we've flushed everything before we michael@0: // continue. michael@0: michael@0: if (!mInNotification++) { michael@0: FlushTags(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentSink::EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType) michael@0: { michael@0: // If we're in a script and we didn't do the notification, michael@0: // something else in the script processing caused the michael@0: // notification to occur. Update our notion of how much michael@0: // has been flushed to include any new content if ending michael@0: // this update leaves us not inside a notification. michael@0: if (!--mInNotification) { michael@0: UpdateChildCounts(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentSink::DidBuildModelImpl(bool aTerminated) michael@0: { michael@0: if (mDocument) { michael@0: MOZ_ASSERT(aTerminated || michael@0: mDocument->GetReadyStateEnum() == michael@0: nsIDocument::READYSTATE_LOADING, "Bad readyState"); michael@0: mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); michael@0: } michael@0: michael@0: if (mScriptLoader) { michael@0: mScriptLoader->ParsingComplete(aTerminated); michael@0: } michael@0: michael@0: if (!mDocument->HaveFiredDOMTitleChange()) { michael@0: mDocument->NotifyPossibleTitleChange(false); michael@0: } michael@0: michael@0: // Cancel a timer if we had one out there michael@0: if (mNotificationTimer) { michael@0: SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW, michael@0: ("nsContentSink::DidBuildModel: canceling notification " michael@0: "timeout")); michael@0: mNotificationTimer->Cancel(); michael@0: mNotificationTimer = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentSink::DropParserAndPerfHint(void) michael@0: { michael@0: if (!mParser) { michael@0: // Make sure we don't unblock unload too many times michael@0: return; michael@0: } michael@0: michael@0: // Ref. Bug 49115 michael@0: // Do this hack to make sure that the parser michael@0: // doesn't get destroyed, accidently, before michael@0: // the circularity, between sink & parser, is michael@0: // actually broken. michael@0: // Drop our reference to the parser to get rid of a circular michael@0: // reference. michael@0: nsRefPtr kungFuDeathGrip(mParser.forget()); michael@0: michael@0: if (mDynamicLowerValue) { michael@0: // Reset the performance hint which was set to FALSE michael@0: // when mDynamicLowerValue was set. michael@0: FavorPerformanceHint(true, 0); michael@0: } michael@0: michael@0: if (!mRunsToCompletion) { michael@0: mDocument->UnblockOnload(true); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsContentSink::IsScriptExecutingImpl() michael@0: { michael@0: return !!mScriptLoader->GetCurrentScript(); michael@0: } michael@0: michael@0: nsresult michael@0: nsContentSink::WillParseImpl(void) michael@0: { michael@0: if (mRunsToCompletion || !mDocument) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIPresShell *shell = mDocument->GetShell(); michael@0: if (!shell) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow()); michael@0: michael@0: if (sEnablePerfMode == 0) { michael@0: nsViewManager* vm = shell->GetViewManager(); michael@0: NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); michael@0: uint32_t lastEventTime; michael@0: vm->GetLastUserEventTime(lastEventTime); michael@0: michael@0: bool newDynLower = michael@0: mDocument->IsInBackgroundWindow() || michael@0: ((currentTime - mBeginLoadTime) > uint32_t(sInitialPerfTime) && michael@0: (currentTime - lastEventTime) < uint32_t(sInteractiveTime)); michael@0: michael@0: if (mDynamicLowerValue != newDynLower) { michael@0: FavorPerformanceHint(!newDynLower, 0); michael@0: mDynamicLowerValue = newDynLower; michael@0: } michael@0: } michael@0: michael@0: mDeflectedCount = 0; michael@0: mHasPendingEvent = false; michael@0: michael@0: mCurrentParseEndTime = currentTime + michael@0: (mDynamicLowerValue ? sInteractiveParseTime : sPerfParseTime); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsContentSink::WillBuildModelImpl() michael@0: { michael@0: if (!mRunsToCompletion) { michael@0: mDocument->BlockOnload(); michael@0: michael@0: mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow()); michael@0: } michael@0: michael@0: mDocument->ResetScrolledToRefAlready(); michael@0: michael@0: if (mProcessLinkHeaderEvent.get()) { michael@0: mProcessLinkHeaderEvent.Revoke(); michael@0: michael@0: DoProcessLinkHeader(); michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsContentSink::NotifyDocElementCreated(nsIDocument* aDoc) michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: nsCOMPtr domDoc = do_QueryInterface(aDoc); michael@0: observerService-> michael@0: NotifyObservers(domDoc, "document-element-inserted", michael@0: EmptyString().get()); michael@0: } michael@0: }