Bug 18333: Make the XML content sink incremental. Patch by Henri Sivonen<hsivonen@iki.fi>. r=peterv sr=sicking

git-svn-id: svn://10.0.0.236/trunk@219201 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
jonas%sicking.cc 2007-01-30 21:21:06 +00:00
parent 1172c6e14c
commit dc89ea1f2d
29 changed files with 1262 additions and 864 deletions

View File

@ -75,6 +75,7 @@ REQUIRES = xpcom \
uriloader \
rdf \
xultmpl \
util \
$(NULL)
EXPORTS = \

View File

@ -100,6 +100,7 @@ public:
nsAString& aStr);
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void) { return NS_OK; }
NS_IMETHOD DidBuildModel(void) { return NS_OK; }
NS_IMETHOD WillInterrupt(void) { return NS_OK; }

View File

@ -20,6 +20,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -78,6 +79,107 @@
#include "nsWeakReference.h"
#include "nsUnicharUtils.h"
#include "nsNodeInfoManager.h"
#include "nsTimer.h"
#include "nsIAppShell.h"
#include "nsWidgetsCID.h"
#include "nsIDOMNSDocument.h"
#include "nsIRequest.h"
#include "nsNodeUtils.h"
#include "nsIDOMNode.h"
PRLogModuleInfo* gContentSinkLogModuleInfo;
//----------------------------------------------------------------------
//
// DummyParserRequest
//
// This is a dummy request implementation that we add to the document's load
// group. It ensures that EndDocumentLoad() in the docshell doesn't fire
// before we've finished all of parsing and tokenizing of the document.
//
class DummyParserRequest : public nsIRequest
{
protected:
nsContentSink* mSink; // Weak reference
public:
DummyParserRequest(nsContentSink* aSink);
NS_DECL_ISUPPORTS
// nsIRequest
NS_IMETHOD GetName(nsACString &result)
{
result.AssignLiteral("about:layout-dummy-request");
return NS_OK;
}
NS_IMETHOD IsPending(PRBool *_retval)
{
*_retval = PR_TRUE;
return NS_OK;
}
NS_IMETHOD GetStatus(nsresult *status)
{
*status = NS_OK;
return NS_OK;
}
NS_IMETHOD Cancel(nsresult status);
NS_IMETHOD Suspend(void)
{
return NS_OK;
}
NS_IMETHOD Resume(void)
{
return NS_OK;
}
NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup)
{
*aLoadGroup = nsnull;
return NS_OK;
}
NS_IMETHOD SetLoadGroup(nsILoadGroup * aLoadGroup)
{
return NS_OK;
}
NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags)
{
*aLoadFlags = nsIRequest::LOAD_NORMAL;
return NS_OK;
}
NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags)
{
return NS_OK;
}
};
NS_IMPL_ISUPPORTS1(DummyParserRequest, nsIRequest)
DummyParserRequest::DummyParserRequest(nsContentSink* aSink)
: mSink(aSink)
{
}
NS_IMETHODIMP
DummyParserRequest::Cancel(nsresult status)
{
// Cancel parser
if (mSink && mSink->mParser) {
mSink->mParser->CancelParsingEvents();
}
return NS_OK;
}
#ifdef ALLOW_ASYNCH_STYLE_SHEETS
@ -86,7 +188,6 @@ const PRBool kBlockByDefault = PR_FALSE;
const PRBool kBlockByDefault = PR_TRUE;
#endif
class nsScriptLoaderObserverProxy : public nsIScriptLoaderObserver
{
public:
@ -146,7 +247,14 @@ NS_IMPL_ISUPPORTS3(nsContentSink,
nsIScriptLoaderObserver)
nsContentSink::nsContentSink()
: mLayoutStarted(PR_FALSE),
mInMonolithicContainer(0)
{
#ifdef NS_DEBUG
if (!gContentSinkLogModuleInfo) {
gContentSinkLogModuleInfo = PR_NewLogModule("nscontentsink");
}
#endif
}
nsContentSink::~nsContentSink()
@ -186,6 +294,49 @@ nsContentSink::Init(nsIDocument* aDoc,
ProcessHTTPHeaders(aChannel);
mNodeInfoManager = aDoc->NodeInfoManager();
mNotifyOnTimer =
nsContentUtils::GetBoolPref("content.notify.ontimer", PR_TRUE);
// -1 means never
mBackoffCount =
nsContentUtils::GetIntPref("content.notify.backoffcount", -1);
// The mNotificationInterval has a dramatic effect on how long it
// takes to initially display content for slow connections.
// The current value provides good
// incremental display of content without causing an increase
// in page load time. If this value is set below 1/10 of second
// it starts to impact page load performance.
// see bugzilla bug 72138 for more info.
mNotificationInterval =
nsContentUtils::GetIntPref("content.notify.interval", 120000);
// The mMaxTokenProcessingTime controls how long we stay away from
// the event loop when processing token. A lower value makes the app
// more responsive, but may increase page load time. The content
// sink mNotificationInterval gates how frequently the content is
// processed so it will also affect how interactive the app is
// during page load also. The mNotification prevents contents
// flushes from happening too frequently. while
// mMaxTokenProcessingTime prevents flushes from happening too
// infrequently.
// The current ratio of 3 to 1 was determined to be the lowest
// mMaxTokenProcessingTime which does not impact page load
// performance. See bugzilla bug 76722 for details.
mMaxTokenProcessingTime =
nsContentUtils::GetIntPref("content.max.tokenizing.time",
mNotificationInterval * 3);
// 3/4 second (750000us) default for switching
mDynamicIntervalSwitchThreshold =
nsContentUtils::GetIntPref("content.switch.threshold", 750000);
mCanInterruptParser =
nsContentUtils::GetBoolPref("content.interrupt.parsing", PR_TRUE);
return NS_OK;
}
@ -808,6 +959,8 @@ nsContentSink::RefreshIfEnabled(nsIViewManager* vm)
void
nsContentSink::StartLayout(PRBool aIsFrameset)
{
mLayoutStarted = PR_TRUE;
mLastNotificationTime = PR_Now();
PRUint32 i, ns = mDocument->GetNumberOfShells();
for (i = 0; i < ns; i++) {
nsIPresShell *shell = mDocument->GetShellAt(i);
@ -870,3 +1023,472 @@ nsContentSink::StartLayout(PRBool aIsFrameset)
}
}
}
void
nsContentSink::NotifyAppend(nsIContent* aContainer, PRUint32 aStartIndex)
{
if (aContainer->GetCurrentDoc() != mDocument) {
// aContainer is not actually in our document anymore.... Just bail out of
// here; notifying on our document for this append would be wrong.
return;
}
mInNotification++;
MOZ_TIMER_DEBUGLOG(("Save and stop: nsHTMLContentSink::NotifyAppend()\n"));
MOZ_TIMER_SAVE(mWatch)
MOZ_TIMER_STOP(mWatch);
nsNodeUtils::ContentAppended(aContainer, aStartIndex);
mLastNotificationTime = PR_Now();
MOZ_TIMER_DEBUGLOG(("Restore: nsHTMLContentSink::NotifyAppend()\n"));
MOZ_TIMER_RESTORE(mWatch);
mInNotification--;
}
NS_IMETHODIMP
nsContentSink::Notify(nsITimer *timer)
{
MOZ_TIMER_DEBUGLOG(("Start: nsHTMLContentSink::Notify()\n"));
MOZ_TIMER_START(mWatch);
if (mParsing) {
// We shouldn't interfere with our normal DidProcessAToken logic
mDroppedTimer = PR_TRUE;
return NS_OK;
}
#ifdef MOZ_DEBUG
{
PRTime now = PR_Now();
PRInt64 diff, interval;
PRInt32 delay;
LL_I2L(interval, GetNotificationInterval());
LL_SUB(diff, now, mLastNotificationTime);
LL_SUB(diff, diff, interval);
LL_L2I(delay, diff);
delay /= PR_USEC_PER_MSEC;
mBackoffCount--;
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
("nsContentSink::Notify: reflow on a timer: %d milliseconds "
"late, backoff count: %d", delay, mBackoffCount));
}
#endif
FlushTags();
// Now try and scroll to the reference
// XXX Should we scroll unconditionally for history loads??
TryToScrollToRef();
mNotificationTimer = nsnull;
MOZ_TIMER_DEBUGLOG(("Stop: nsHTMLContentSink::Notify()\n"));
MOZ_TIMER_STOP(mWatch);
return NS_OK;
}
PRBool
nsContentSink::IsTimeToNotify()
{
if (!mNotifyOnTimer || !mLayoutStarted || !mBackoffCount ||
mInMonolithicContainer) {
return PR_FALSE;
}
PRTime now = PR_Now();
PRInt64 interval, diff;
LL_I2L(interval, GetNotificationInterval());
LL_SUB(diff, now, mLastNotificationTime);
if (LL_CMP(diff, >, interval)) {
mBackoffCount--;
return PR_TRUE;
}
return PR_FALSE;
}
NS_IMETHODIMP
nsContentSink::WillInterruptImpl()
{
nsresult result = NS_OK;
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS,
("nsContentSink::WillInterrupt: this=%p", this));
#ifndef SINK_NO_INCREMENTAL
if (mNotifyOnTimer && mLayoutStarted) {
if (mBackoffCount && !mInMonolithicContainer) {
PRInt64 now = PR_Now();
PRInt64 interval = GetNotificationInterval();
PRInt64 diff = now - mLastNotificationTime;
// If it's already time for us to have a notification
if (diff > interval || mDroppedTimer) {
mBackoffCount--;
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
("nsContentSink::WillInterrupt: flushing tags since we've "
"run out time; backoff count: %d", mBackoffCount));
result = FlushTags();
if (mDroppedTimer) {
TryToScrollToRef();
mDroppedTimer = PR_FALSE;
}
} else if (!mNotificationTimer) {
interval -= diff;
PRInt32 delay = interval;
// Convert to milliseconds
delay /= PR_USEC_PER_MSEC;
mNotificationTimer = do_CreateInstance("@mozilla.org/timer;1",
&result);
if (NS_SUCCEEDED(result)) {
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
("nsContentSink::WillInterrupt: setting up timer with "
"delay %d", delay));
result =
mNotificationTimer->InitWithCallback(this, delay,
nsITimer::TYPE_ONE_SHOT);
if (NS_FAILED(result)) {
mNotificationTimer = nsnull;
}
}
}
}
} else {
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
("nsContentSink::WillInterrupt: flushing tags "
"unconditionally"));
result = FlushTags();
}
#endif
mParsing = PR_FALSE;
return result;
}
NS_IMETHODIMP
nsContentSink::WillResumeImpl()
{
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS,
("nsContentSink::WillResume: this=%p", this));
mParsing = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsContentSink::DidProcessATokenImpl()
{
if (!mCanInterruptParser) {
return NS_OK;
}
// There is both a high frequency interrupt mode and a low
// frequency interupt mode controlled by the flag
// mDynamicLowerValue The high frequency mode
// interupts the parser frequently to provide UI responsiveness at
// the expense of page load time. The low frequency mode
// interrupts the parser and samples the system clock infrequently
// to provide fast page load time. When the user moves the mouse,
// clicks or types the mode switches to the high frequency
// interrupt mode. If the user stops moving the mouse or typing
// for a duration of time (mDynamicIntervalSwitchThreshold) it
// switches to low frequency interrupt mode.
// Get the current user event time
nsIPresShell *shell = mDocument->GetShellAt(0);
if (!shell) {
// If there's no pres shell in the document, return early since
// we're not laying anything out here.
return NS_OK;
}
nsIViewManager* vm = shell->GetViewManager();
NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
PRUint32 eventTime;
nsCOMPtr<nsIWidget> widget;
nsresult rv = vm->GetWidget(getter_AddRefs(widget));
if (!widget || NS_FAILED(widget->GetLastInputEventTime(eventTime))) {
// If we can't get the last input time from the widget
// then we will get it from the viewmanager.
rv = vm->GetLastUserEventTime(eventTime);
NS_ENSURE_SUCCESS(rv , NS_ERROR_FAILURE);
}
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
if (!mDynamicLowerValue && mLastSampledUserEventTime == eventTime) {
// The magic value of NS_MAX_TOKENS_DEFLECTED_IN_LOW_FREQ_MODE
// was selected by empirical testing. It provides reasonable
// user response and prevents us from sampling the clock too
// frequently.
if (mDeflectedCount < NS_MAX_TOKENS_DEFLECTED_IN_LOW_FREQ_MODE) {
mDeflectedCount++;
// return early to prevent sampling the clock. Note: This
// prevents us from switching to higher frequency (better UI
// responsive) mode, so limit ourselves to doing for no more
// than NS_MAX_TOKENS_DEFLECTED_IN_LOW_FREQ_MODE tokens.
return NS_OK;
}
// reset count and drop through to the code which samples the
// clock and does the dynamic switch between the high
// frequency and low frequency interruption of the parser.
mDeflectedCount = 0;
}
mLastSampledUserEventTime = eventTime;
PRUint32 currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
// Get the last user event time and compare it with the current
// time to determine if the lower value for content notification
// and max token processing should be used. But only consider
// using the lower value if the document has already been loading
// for 2 seconds. 2 seconds was chosen because it is greater than
// the default 3/4 of second that is used to determine when to
// switch between the modes and it gives the document a little
// time to create windows. This is important because on some
// systems (Windows, for example) when a window is created and the
// mouse is over it, a mouse move event is sent, which will kick
// us into interactive mode otherwise. It also suppresses reaction
// to pressing the ENTER key in the URL bar...
PRUint32 delayBeforeLoweringThreshold =
NS_STATIC_CAST(PRUint32, ((2 * mDynamicIntervalSwitchThreshold) +
NS_DELAY_FOR_WINDOW_CREATION));
if ((currentTime - mBeginLoadTime) > delayBeforeLoweringThreshold) {
if ((currentTime - eventTime) <
NS_STATIC_CAST(PRUint32, mDynamicIntervalSwitchThreshold)) {
if (!mDynamicLowerValue) {
// lower the dynamic values to favor application
// responsiveness over page load time.
mDynamicLowerValue = PR_TRUE;
// Set the performance hint to prevent event starvation when
// dispatching PLEvents. This improves application responsiveness
// during page loads.
FavorPerformanceHint(PR_FALSE, 0);
}
}
else if (mDynamicLowerValue) {
// raise the content notification and MaxTokenProcessing time
// to favor overall page load speed over responsiveness.
mDynamicLowerValue = PR_FALSE;
// Reset the hint that to favoring performance for PLEvent dispatch.
FavorPerformanceHint(PR_TRUE, 0);
}
}
if ((currentTime - mDelayTimerStart) >
NS_STATIC_CAST(PRUint32, GetMaxTokenProcessingTime())) {
return NS_ERROR_HTMLPARSER_INTERRUPTED;
}
return NS_OK;
}
//----------------------------------------------------------------------
void
nsContentSink::FavorPerformanceHint(PRBool perfOverStarvation, PRUint32 starvationDelay)
{
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
if (appShell)
appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay);
}
void
nsContentSink::BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType)
{
// If we're in a script and we didn't do the notification,
// something else in the script processing caused the
// notification to occur. Since this could result in frame
// creation, make sure we've flushed everything before we
// continue.
// Note that UPDATE_CONTENT_STATE notifications never cause
// synchronous frame construction, so we never have to worry about
// them here. The code that handles the async event these
// notifications post will flush us out if it needs to.
// Also, if this is not an UPDATE_CONTENT_STATE notification,
// increment mInNotification to make sure we don't flush again until
// the end of this update, even if nested updates or
// FlushPendingNotifications calls happen during it.
NS_ASSERTION(aUpdateType && (aUpdateType & UPDATE_ALL) == aUpdateType,
"Weird update type bitmask");
if (aUpdateType != UPDATE_CONTENT_STATE && !mInNotification++) {
FlushTags();
}
}
void
nsContentSink::EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType)
{
// If we're in a script and we didn't do the notification,
// something else in the script processing caused the
// notification to occur. Update our notion of how much
// has been flushed to include any new content if ending
// this update leaves us not inside a notification. Note that we
// exclude UPDATE_CONTENT_STATE notifications here, since those
// never affect the frame model directly while inside the
// notification.
NS_ASSERTION(aUpdateType && (aUpdateType & UPDATE_ALL) == aUpdateType,
"Weird update type bitmask");
if (aUpdateType != UPDATE_CONTENT_STATE && !--mInNotification) {
UpdateChildCounts();
}
}
NS_IMETHODIMP
nsContentSink::DidBuildModelImpl(void)
{
if (mDocument && mDocument->GetDocumentTitle().IsVoid()) {
nsCOMPtr<nsIDOMNSDocument> dom_doc(do_QueryInterface(mDocument));
dom_doc->SetTitle(EmptyString());
}
// Cancel a timer if we had one out there
if (mNotificationTimer) {
SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
("nsContentSink::DidBuildModel: canceling notification "
"timeout"));
mNotificationTimer->Cancel();
mNotificationTimer = 0;
}
return NS_OK;
}
NS_IMETHODIMP
nsContentSink::DropParserAndPerfHint(void)
{
// Ref. Bug 49115
// Do this hack to make sure that the parser
// doesn't get destroyed, accidently, before
// the circularity, between sink & parser, is
// actually borken.
nsCOMPtr<nsIParser> kungFuDeathGrip(mParser);
// Drop our reference to the parser to get rid of a circular
// reference.
mParser = nsnull;
if (mDynamicLowerValue) {
// Reset the performance hint which was set to FALSE
// when mDynamicLowerValue was set.
FavorPerformanceHint(PR_TRUE, 0);
}
if (mCanInterruptParser) {
// Note: Don't return value from RemoveDummyParserRequest,
// If RemoveDummyParserRequests fails it should not affect
// DidBuildModel. The remove can fail if the parser request
// was already removed by a DummyParserRequest::Cancel
RemoveDummyParserRequest();
}
return NS_OK;
}
NS_IMETHODIMP
nsContentSink::WillProcessTokensImpl(void)
{
if (mCanInterruptParser) {
mDelayTimerStart = PR_IntervalToMicroseconds(PR_IntervalNow());
}
return NS_OK;
}
NS_IMETHODIMP
nsContentSink::WillBuildModelImpl()
{
if (mCanInterruptParser) {
nsresult rv = AddDummyParserRequest();
if (NS_FAILED(rv)) {
NS_ERROR("Adding dummy parser request failed");
// Don't return the error result, just reset flag which
// indicates that it can interrupt parsing. If
// AddDummyParserRequests fails it should not affect
// WillBuildModel.
mCanInterruptParser = PR_FALSE;
}
mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow());
}
mScrolledToRefAlready = PR_FALSE;
return NS_OK;
}
// If the content sink can interrupt the parser (@see mCanInteruptParsing)
// then it needs to schedule a dummy parser request to delay the document
// from firing onload handlers and other document done actions until all of the
// parsing has completed.
nsresult
nsContentSink::AddDummyParserRequest(void)
{
nsresult rv = NS_OK;
NS_ASSERTION(!mDummyParserRequest, "Already have a dummy parser request");
mDummyParserRequest = new DummyParserRequest(this);
if (!mDummyParserRequest) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsCOMPtr<nsILoadGroup> loadGroup;
if (mDocument) {
loadGroup = mDocument->GetDocumentLoadGroup();
}
if (loadGroup) {
rv = mDummyParserRequest->SetLoadGroup(loadGroup);
if (NS_FAILED(rv)) {
return rv;
}
rv = loadGroup->AddRequest(mDummyParserRequest, nsnull);
}
return rv;
}
nsresult
nsContentSink::RemoveDummyParserRequest(void)
{
nsresult rv = NS_OK;
nsCOMPtr<nsILoadGroup> loadGroup;
if (mDocument) {
loadGroup = mDocument->GetDocumentLoadGroup();
}
if (loadGroup && mDummyParserRequest) {
rv = loadGroup->RemoveRequest(mDummyParserRequest, nsnull, NS_OK);
if (NS_FAILED(rv)) {
return rv;
}
mDummyParserRequest = nsnull;
}
return rv;
}

View File

@ -55,6 +55,13 @@
#include "nsGkAtoms.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsITimer.h"
#include "nsStubDocumentObserver.h"
#include "nsIParserService.h"
#include "nsIContentSink.h"
#include "prlog.h"
#include "nsIRequest.h"
class nsIDocument;
class nsIURI;
@ -68,19 +75,75 @@ class nsIContent;
class nsIViewManager;
class nsNodeInfoManager;
#ifdef NS_DEBUG
extern PRLogModuleInfo* gContentSinkLogModuleInfo;
#define SINK_TRACE_CALLS 0x1
#define SINK_TRACE_REFLOW 0x2
#define SINK_ALWAYS_REFLOW 0x4
#define SINK_LOG_TEST(_lm, _bit) (PRIntn((_lm)->level) & (_bit))
#define SINK_TRACE(_lm, _bit, _args) \
PR_BEGIN_MACRO \
if (SINK_LOG_TEST(_lm, _bit)) { \
PR_LogPrint _args; \
} \
PR_END_MACRO
#else
#define SINK_TRACE(_lm, _bit, _args)
#endif
#undef SINK_NO_INCREMENTAL
//----------------------------------------------------------------------
// 1/2 second fudge factor for window creation
#define NS_DELAY_FOR_WINDOW_CREATION 500000
// 200 determined empirically to provide good user response without
// sampling the clock too often.
#define NS_MAX_TOKENS_DEFLECTED_IN_LOW_FREQ_MODE 200
class nsContentSink : public nsICSSLoaderObserver,
public nsIScriptLoaderObserver,
public nsSupportsWeakReference
public nsSupportsWeakReference,
public nsStubDocumentObserver,
public nsITimerCallback
{
friend class DummyParserRequest;
NS_DECL_ISUPPORTS
NS_DECL_NSISCRIPTLOADEROBSERVER
// nsITimerCallback
NS_DECL_NSITIMERCALLBACK
// nsICSSLoaderObserver
NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aWasAlternate,
nsresult aStatus);
nsresult ProcessMETATag(nsIContent* aContent);
// nsIContentSink impl
NS_IMETHOD WillInterruptImpl(void);
NS_IMETHOD WillResumeImpl(void);
NS_IMETHOD DidProcessATokenImpl(void);
NS_IMETHOD WillBuildModelImpl(void);
NS_IMETHOD DidBuildModelImpl(void);
NS_IMETHOD DropParserAndPerfHint(void);
NS_IMETHOD WillProcessTokensImpl(void);
void NotifyAppend(nsIContent* aContent, PRUint32 aStartIndex);
// nsIDocumentObserver
virtual void BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType);
virtual void EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType);
virtual void UpdateChildCounts() = 0;
protected:
nsContentSink();
virtual ~nsContentSink();
@ -110,10 +173,43 @@ protected:
nsresult RefreshIfEnabled(nsIViewManager* vm);
void StartLayout(PRBool aIsFrameset);
PRBool IsTimeToNotify();
void
FavorPerformanceHint(PRBool perfOverStarvation, PRUint32 starvationDelay);
inline PRInt32 GetNotificationInterval()
{
if (mDynamicLowerValue) {
return 1000;
}
return mNotificationInterval;
}
inline PRInt32 GetMaxTokenProcessingTime()
{
if (mDynamicLowerValue) {
return 3000;
}
return mMaxTokenProcessingTime;
}
// Overridable hooks into script evaluation
virtual void PreEvaluateScript() {return;}
virtual void PostEvaluateScript(nsIScriptElement *aElement) {return;}
virtual nsresult FlushTags() = 0;
virtual void TryToScrollToRef()
{
}
// CanInterrupt parsing related routines
nsresult AddDummyParserRequest(void);
nsresult RemoveDummyParserRequest(void);
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsIParser> mParser;
nsCOMPtr<nsIURI> mDocumentURI;
@ -125,6 +221,63 @@ protected:
nsCOMArray<nsIScriptElement> mScriptElements;
nsCString mRef; // ScrollTo #ref
// back off timer notification after count
PRInt32 mBackoffCount;
// Notification interval in microseconds
PRInt32 mNotificationInterval;
// Time of last notification
PRTime mLastNotificationTime;
// Timer used for notification
nsCOMPtr<nsITimer> mNotificationTimer;
// Do we notify based on time?
PRPackedBool mNotifyOnTimer;
PRPackedBool mLayoutStarted;
PRPackedBool mScrolledToRefAlready;
PRUint8 mScriptEnabled : 1;
PRUint8 mFramesEnabled : 1;
PRUint8 mCanInterruptParser : 1;
PRUint8 mDynamicLowerValue : 1;
PRUint8 mFormOnStack : 1;
PRUint8 mParsing : 1;
PRUint8 mDroppedTimer : 1;
// -- Can interrupt parsing members --
PRUint32 mDelayTimerStart;
// Interrupt parsing during token procesing after # of microseconds
PRInt32 mMaxTokenProcessingTime;
// Switch between intervals when time is exceeded
PRInt32 mDynamicIntervalSwitchThreshold;
PRInt32 mBeginLoadTime;
// Last mouse event or keyboard event time sampled by the content
// sink
PRUint32 mLastSampledUserEventTime;
// The number of tokens that have been processed while in the low
// frequency parser interrupt mode without falling through to the
// logic which decides whether to switch to the high frequency
// parser interrupt mode.
PRUint8 mDeflectedCount;
// Boolean indicating whether we've notified insertion of our root content
// yet. We want to make sure to only do this once.
PRPackedBool mNotifiedRootInsertion;
PRInt32 mInMonolithicContainer;
PRInt32 mInNotification;
nsCOMPtr<nsIRequest> mDummyParserRequest;
};

View File

@ -141,6 +141,7 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID);
#include "nsICharsetAlias.h"
#include "nsIParser.h"
#include "nsIContentSink.h"
#include "nsDateTimeFormatCID.h"
#include "nsIDateTimeFormat.h"
@ -4586,6 +4587,16 @@ nsDocument::CreateEventGroup(nsIDOMEventGroup **aInstancePtrResult)
void
nsDocument::FlushPendingNotifications(mozFlushType aType)
{
// Determine if it is safe to flush the sink notifications
// by determining if it safe to flush all the presshells.
if ((aType & Flush_Content) && mParser &&
(!(aType & Flush_SinkNotifications) || IsSafeToFlush())) {
nsCOMPtr<nsIContentSink> sink = mParser->GetContentSink();
if (sink) {
sink->FlushPendingNotifications(aType);
}
}
nsPIDOMWindow *window = GetWindow();
if (aType == (aType & (Flush_Content | Flush_SinkNotifications)) ||

View File

@ -95,6 +95,7 @@ public:
nsAString& aStr);
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void) { return NS_OK; }
NS_IMETHOD DidBuildModel(void) { return NS_OK; }
NS_IMETHOD WillInterrupt(void) { return NS_OK; }

File diff suppressed because it is too large Load Diff

View File

@ -1319,22 +1319,6 @@ nsHTMLDocument::AttributeChanged(nsIDocument* aDocument,
}
}
void
nsHTMLDocument::FlushPendingNotifications(mozFlushType aType)
{
// Determine if it is safe to flush the sink notifications
// by determining if it safe to flush all the presshells.
if ((aType & Flush_Content) && mParser &&
(!(aType & Flush_SinkNotifications) || IsSafeToFlush())) {
nsCOMPtr<nsIContentSink> sink = mParser->GetContentSink();
if (sink) {
sink->FlushPendingNotifications(aType);
}
}
nsDocument::FlushPendingNotifications(aType);
}
PRBool
nsHTMLDocument::IsCaseSensitive()
{

View File

@ -125,8 +125,6 @@ public:
PRInt32 aNameSpaceID,
nsIAtom* aAttribute);
virtual void FlushPendingNotifications(mozFlushType aType);
virtual PRBool IsCaseSensitive();
// nsIMutationObserver

View File

@ -84,6 +84,7 @@ public:
NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel(void);
NS_IMETHOD WillInterrupt(void);

View File

@ -109,6 +109,12 @@ nsXBLContentSink::Init(nsIDocument* aDoc,
return rv;
}
void
nsXBLContentSink::MaybeStartLayout()
{
return;
}
nsresult
nsXBLContentSink::FlushText(PRBool aCreateTextNode,
PRBool* aDidFlush)

View File

@ -110,6 +110,8 @@ public:
protected:
// nsXMLContentSink overrides
void MaybeStartLayout();
PRBool OnOpenContainer(const PRUnichar **aAtts,
PRUint32 aAttsCount,
PRInt32 aNameSpaceID,

View File

@ -70,6 +70,7 @@ REQUIRES = xpcom \
unicharutil \
windowwatcher \
locale \
util \
$(NULL)
CPPSRCS = \

View File

@ -21,6 +21,7 @@
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -95,6 +96,8 @@
#include "nsContentErrors.h"
#include "nsIDOMProcessingInstruction.h"
#include "nsNodeUtils.h"
#include "nsTimer.h"
#include "nsIScriptGlobalObject.h"
#ifdef MOZ_SVG
#include "nsGUIEvent.h"
@ -140,6 +143,7 @@ nsXMLContentSink::nsXMLContentSink()
mText(nsnull),
mTextLength(0),
mTextSize(0),
mNotifyLevel(0),
mConstrainSize(PR_TRUE),
mInTitle(PR_FALSE),
mPrettyPrintXML(PR_TRUE),
@ -152,6 +156,12 @@ nsXMLContentSink::nsXMLContentSink()
nsXMLContentSink::~nsXMLContentSink()
{
if (mDocument) {
// Remove ourselves just to be safe, though we really should have
// been removed in DidBuildModel if everything worked right.
mDocument->RemoveObserver(this);
}
NS_IF_RELEASE(mDocElement);
if (mText) {
PR_Free(mText); // Doesn't null out, unlike PR_FREEIF
@ -164,29 +174,50 @@ nsXMLContentSink::Init(nsIDocument* aDoc,
nsISupports* aContainer,
nsIChannel* aChannel)
{
MOZ_TIMER_DEBUGLOG(("Reset and start: nsXMLContentSink::Init(), this=%p\n",
this));
MOZ_TIMER_RESET(mWatch);
MOZ_TIMER_START(mWatch);
nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
NS_ENSURE_SUCCESS(rv, rv);
aDoc->AddObserver(this);
if (!mDocShell) {
mPrettyPrintXML = PR_FALSE;
}
mState = eXMLContentSinkState_InProlog;
mDocElement = nsnull;
MOZ_TIMER_DEBUGLOG(("Stop: nsXMLContentSink::Init()\n"));
MOZ_TIMER_STOP(mWatch);
return NS_OK;
}
NS_IMPL_ISUPPORTS_INHERITED4(nsXMLContentSink,
NS_IMPL_ISUPPORTS_INHERITED7(nsXMLContentSink,
nsContentSink,
nsIContentSink,
nsIXMLContentSink,
nsIExpatSink,
nsITimerCallback,
nsIDocumentObserver,
nsIMutationObserver,
nsITransformObserver)
// nsIContentSink
NS_IMETHODIMP
nsXMLContentSink::WillTokenize(void)
{
return WillProcessTokensImpl();
}
NS_IMETHODIMP
nsXMLContentSink::WillBuildModel(void)
{
WillBuildModelImpl();
// Notify document that the load is beginning
mDocument->BeginLoad();
@ -202,16 +233,25 @@ nsXMLContentSink::WillBuildModel(void)
return NS_OK;
}
PRBool
nsXMLContentSink::CanStillPrettyPrint()
{
return mPrettyPrintXML &&
(!mPrettyPrintHasFactoredElements || mPrettyPrintHasSpecialRoot);
}
nsresult
nsXMLContentSink::MaybePrettyPrint()
{
if (!mPrettyPrintXML || (mPrettyPrintHasFactoredElements &&
!mPrettyPrintHasSpecialRoot)) {
if (!CanStillPrettyPrint()) {
mPrettyPrintXML = PR_FALSE;
return NS_OK;
}
// stop observing in order to avoid crashing when replacing content
mDocument->RemoveObserver(this);
// Reenable the CSSLoader so that the prettyprinting stylesheets can load
if (mCSSLoader) {
mCSSLoader->SetEnabled(PR_TRUE);
@ -269,12 +309,11 @@ CheckXSLTParamPI(nsIDOMProcessingInstruction* aPi,
NS_IMETHODIMP
nsXMLContentSink::DidBuildModel()
{
if (mDocument && mDocument->GetDocumentTitle().IsVoid()) {
nsCOMPtr<nsIDOMNSDocument> dom_doc(do_QueryInterface(mDocument));
dom_doc->SetTitle(EmptyString());
}
DidBuildModelImpl();
if (mXSLTProcessor) {
// stop observing in order to avoid crashing when replacing content
mDocument->RemoveObserver(this);
// Check for xslt-param and xslt-param-namespace PIs
PRUint32 i;
@ -310,10 +349,6 @@ nsXMLContentSink::DidBuildModel()
// documentElement?
NS_ASSERTION(mDocument->IndexOf(mDocElement) != -1,
"mDocElement not in doc?");
mozAutoDocUpdate docUpdate(mDocument, UPDATE_CONTENT_MODEL, PR_TRUE);
nsNodeUtils::ContentInserted(mDocument, mDocElement,
mDocument->IndexOf(mDocElement));
}
// Check if we want to prettyprint
@ -332,12 +367,12 @@ nsXMLContentSink::DidBuildModel()
ScrollToRef(PR_TRUE);
#endif
mDocument->RemoveObserver(this);
mDocument->EndLoad();
}
// Drop our reference to the parser to get rid of a circular
// reference.
mParser = nsnull;
DropParserAndPerfHint();
return NS_OK;
}
@ -429,13 +464,13 @@ nsXMLContentSink::OnTransformDone(nsresult aResult,
NS_IMETHODIMP
nsXMLContentSink::WillInterrupt(void)
{
return NS_OK;
return WillInterruptImpl();
}
NS_IMETHODIMP
nsXMLContentSink::WillResume(void)
{
return NS_OK;
return WillResumeImpl();
}
NS_IMETHODIMP
@ -547,6 +582,10 @@ nsXMLContentSink::CloseElement(nsIContent* aContent)
aContent->DoneAddingChildren(PR_FALSE);
}
if (IsMonolithicContainer(nodeInfo)) {
mInMonolithicContainer--;
}
if (!nodeInfo->NamespaceEquals(kNameSpaceID_XHTML) &&
!nodeInfo->NamespaceEquals(kNameSpaceID_SVG)) {
return NS_OK;
@ -641,7 +680,6 @@ nsXMLContentSink::AddContentAsLeaf(nsIContent *aContent)
result = parent->AppendChildTo(aContent, PR_FALSE);
}
}
return result;
}
@ -822,47 +860,61 @@ nsXMLContentSink::FlushText(PRBool aCreateTextNode, PRBool* aDidFlush)
nsIContent*
nsXMLContentSink::GetCurrentContent()
{
PRInt32 count = mContentStack.Count();
if (count == 0) {
if (mContentStack.Length() == 0) {
return nsnull;
}
return GetCurrentStackNode().mContent;
}
NS_ASSERTION(count > 0, "Bogus Count()");
StackNode &
nsXMLContentSink::GetCurrentStackNode()
{
PRInt32 count = mContentStack.Length();
NS_ASSERTION(count > 0, "Bogus Length()");
return mContentStack[count-1];
}
PRInt32
nsresult
nsXMLContentSink::PushContent(nsIContent *aContent)
{
NS_PRECONDITION(aContent, "Null content being pushed!");
mContentStack.AppendObject(aContent);
return mContentStack.Count();
StackNode *sn = mContentStack.AppendElement();
NS_ENSURE_TRUE(sn, NS_ERROR_OUT_OF_MEMORY);
sn->mContent = aContent;
sn->mNumFlushed = 0;
return NS_OK;
}
already_AddRefed<nsIContent>
void
nsXMLContentSink::PopContent()
{
PRInt32 count = mContentStack.Count();
{
PRInt32 count = mContentStack.Length();
if (count == 0) {
NS_WARNING("Popping empty stack");
return nsnull;
return;
}
NS_ASSERTION(count > 0, "Bogus Count()");
mContentStack.RemoveElementAt(count - 1);
}
nsIContent* content = mContentStack[count - 1];
NS_IF_ADDREF(content);
mContentStack.RemoveObjectAt(count - 1);
return content;
void
nsXMLContentSink::MaybeStartLayout()
{
if (mLayoutStarted || mXSLTProcessor || CanStillPrettyPrint()) {
return;
}
StartLayout();
}
void
nsXMLContentSink::StartLayout()
{
if (mLayoutStarted) {
return;
}
PRBool topLevelFrameset = PR_FALSE;
nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
if (docShellAsItem) {
@ -933,8 +985,7 @@ nsXMLContentSink::SetDocElement(PRInt32 aNameSpaceID,
mDocElement = aContent;
NS_ADDREF(mDocElement);
nsresult rv = mDocument->AppendChildTo(mDocElement, PR_FALSE);
nsresult rv = mDocument->AppendChildTo(mDocElement, PR_TRUE);
if (NS_FAILED(rv)) {
// If we return PR_FALSE here, the caller will bail out because it won't
// find a parent content node to append to, which is fine.
@ -965,6 +1016,7 @@ nsXMLContentSink::HandleStartElement(const PRUnichar *aName,
PR_ASSERT(eXMLContentSinkState_InEpilog != mState);
FlushText();
DidAddContent();
mState = eXMLContentSinkState_InDocumentElement;
@ -1014,7 +1066,8 @@ nsXMLContentSink::HandleStartElement(const PRUnichar *aName,
parent->AppendChildTo(content, PR_FALSE);
}
PushContent(content);
result = PushContent(content);
NS_ENSURE_SUCCESS(result, result);
}
// Some HTML nodes need DoneCreatingElement() called to initialize
@ -1025,7 +1078,13 @@ nsXMLContentSink::HandleStartElement(const PRUnichar *aName,
content->DoneCreatingElement();
}
return result;
if (IsMonolithicContainer(nodeInfo)) {
mInMonolithicContainer++;
}
MaybeStartLayout();
return NS_SUCCEEDED(result) ? DidProcessATokenImpl() : result;
}
NS_IMETHODIMP
@ -1040,7 +1099,13 @@ nsXMLContentSink::HandleEndElement(const PRUnichar *aName)
FlushText();
nsCOMPtr<nsIContent> content = PopContent();
StackNode & sn = GetCurrentStackNode();
nsCOMPtr<nsIContent> content;
sn.mContent.swap(content);
PRUint32 numFlushed = sn.mNumFlushed;
PopContent();
NS_ASSERTION(content, "failed to pop content");
#ifdef DEBUG
// Check that we're closing the right thing
@ -1073,7 +1138,16 @@ nsXMLContentSink::HandleEndElement(const PRUnichar *aName)
}
#endif
return result;
PRInt32 stackLen = mContentStack.Length();
if (mNotifyLevel >= stackLen) {
if (numFlushed < content->GetChildCount()) {
NotifyAppend(content, numFlushed);
}
mNotifyLevel = stackLen - 1;
}
DidAddContent();
return NS_SUCCEEDED(result) ? DidProcessATokenImpl() : result;
}
NS_IMETHODIMP
@ -1086,9 +1160,10 @@ nsXMLContentSink::HandleComment(const PRUnichar *aName)
if (comment) {
comment->SetText(nsDependentString(aName), PR_FALSE);
rv = AddContentAsLeaf(comment);
DidAddContent();
}
return rv;
return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
}
NS_IMETHODIMP
@ -1112,9 +1187,10 @@ nsXMLContentSink::HandleCDataSection(const PRUnichar *aData,
if (cdata) {
cdata->SetText(aData, aLength, PR_FALSE);
rv = AddContentAsLeaf(cdata);
DidAddContent();
}
return rv;
return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
}
NS_IMETHODIMP
@ -1166,18 +1242,21 @@ nsXMLContentSink::HandleDoctypeDecl(const nsAString & aSubset,
nsCOMPtr<nsIContent> content = do_QueryInterface(docType);
NS_ASSERTION(content, "doctype isn't content?");
return mDocument->AppendChildTo(content, PR_FALSE);
rv = mDocument->AppendChildTo(content, PR_FALSE);
DidAddContent();
return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
}
NS_IMETHODIMP
nsXMLContentSink::HandleCharacterData(const PRUnichar *aData,
PRUint32 aLength)
{
nsresult rv = NS_OK;
if (aData && mState != eXMLContentSinkState_InProlog &&
mState != eXMLContentSinkState_InEpilog) {
return AddText(aData, aLength);
rv = AddText(aData, aLength);
}
return NS_OK;
return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
}
NS_IMETHODIMP
@ -1204,6 +1283,7 @@ nsXMLContentSink::HandleProcessingInstruction(const PRUnichar *aTarget,
rv = AddContentAsLeaf(node);
NS_ENSURE_SUCCESS(rv, rv);
DidAddContent();
if (ssle) {
ssle->SetEnableUpdates(PR_TRUE);
@ -1224,7 +1304,7 @@ nsXMLContentSink::HandleProcessingInstruction(const PRUnichar *aTarget,
if (mState != eXMLContentSinkState_InProlog ||
!target.EqualsLiteral("xml-stylesheet") ||
type.LowerCaseEqualsLiteral("text/css")) {
return NS_OK;
return DidProcessATokenImpl();
}
nsAutoString href, title, media;
@ -1233,10 +1313,11 @@ nsXMLContentSink::HandleProcessingInstruction(const PRUnichar *aTarget,
// If there was no href, we can't do anything with this PI
if (href.IsEmpty()) {
return NS_OK;
return DidProcessATokenImpl();
}
return ProcessStyleLink(node, href, isAlternate, title, type, media);
rv = ProcessStyleLink(node, href, isAlternate, title, type, media);
return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
}
/* static */
@ -1269,7 +1350,7 @@ nsXMLContentSink::HandleXMLDeclaration(const PRUnichar *aVersion,
{
mDocument->SetXMLDeclaration(aVersion, aEncoding, aStandalone);
return NS_OK;
return DidProcessATokenImpl();
}
NS_IMETHODIMP
@ -1288,6 +1369,11 @@ nsXMLContentSink::ReportError(const PRUnichar* aErrorText,
mState = eXMLContentSinkState_InProlog;
// XXX need to stop scripts here -- hsivonen
// stop observing in order to avoid crashing when removing content
mDocument->RemoveObserver(this);
// Clear the current content and
// prepare to set <parsererror> as the document root
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mDocument));
@ -1313,6 +1399,10 @@ nsXMLContentSink::ReportError(const PRUnichar* aErrorText,
mXSLTProcessor = nsnull;
}
// release the nodes on stack
mContentStack.Clear();
mNotifyLevel = 0;
const PRUnichar* noAtts[] = { 0, 0 };
NS_NAMED_LITERAL_STRING(errorNs,
@ -1344,6 +1434,8 @@ nsXMLContentSink::ReportError(const PRUnichar* aErrorText,
rv = HandleEndElement(parsererror.get());
NS_ENSURE_SUCCESS(rv,rv);
FlushTags();
return NS_OK;
}
@ -1427,6 +1519,7 @@ nsXMLContentSink::AddText(const PRUnichar* aText,
}
}
}
// Line breaks should not be normalized twice. See bug 343870.
mTextLength +=
nsContentUtils::CopyNewlineNormalizedUnicodeTo(str,
offset,
@ -1439,3 +1532,106 @@ nsXMLContentSink::AddText(const PRUnichar* aText,
return NS_OK;
}
void
nsXMLContentSink::FlushPendingNotifications(mozFlushType aType)
{
// Only flush tags if we're not doing the notification ourselves
// (since we aren't reentrant)
if (!mInNotification) {
if (aType & Flush_SinkNotifications) {
FlushTags();
}
else {
FlushText();
}
if (aType & Flush_OnlyReflow) {
// Make sure that layout has started so that the reflow flush
// will actually happen.
MaybeStartLayout();
}
}
}
/**
* NOTE!! Forked from SinkContext. Please keep in sync.
*
* Flush all elements that have been seen so far such that
* they are visible in the tree. Specifically, make sure
* that they are all added to their respective parents.
* Also, do notification at the top for all content that
* has been newly added so that the frame tree is complete.
*/
nsresult
nsXMLContentSink::FlushTags()
{
// Don't release last text node in case we need to add to it again
FlushText();
++mInNotification;
mozAutoDocUpdate updateBatch(mDocument, UPDATE_CONTENT_MODEL, PR_TRUE);
// Start from the base of the stack (growing downward) and do
// a notification from the node that is closest to the root of
// tree for any content that has been added.
PRInt32 stackPos;
PRInt32 stackLen = mContentStack.Length();
PRBool flushed = PR_FALSE;
PRUint32 childCount;
nsIContent* content;
for (stackPos = 0; stackPos < stackLen; ++stackPos) {
content = mContentStack[stackPos].mContent;
childCount = content->GetChildCount();
if (!flushed && (mContentStack[stackPos].mNumFlushed < childCount)) {
NotifyAppend(content, mContentStack[stackPos].mNumFlushed);
flushed = PR_TRUE;
}
mContentStack[stackPos].mNumFlushed = childCount;
}
mNotifyLevel = stackLen - 1;
--mInNotification;
return NS_OK;
}
/**
* NOTE!! Forked from SinkContext. Please keep in sync.
*/
void
nsXMLContentSink::UpdateChildCounts()
{
// Start from the top of the stack (growing upwards) and see if any
// new content has been appended. If so, we recognize that reflows
// have been generated for it and we should make sure that no
// further reflows occur. Note that we have to include stackPos == 0
// to properly notify on kids of <html>.
PRInt32 stackLen = mContentStack.Length();
PRInt32 stackPos = stackLen - 1;
while (stackPos >= 0) {
StackNode & node = mContentStack[stackPos];
node.mNumFlushed = node.mContent->GetChildCount();
stackPos--;
}
mNotifyLevel = stackLen - 1;
}
PRBool
nsXMLContentSink::IsMonolithicContainer(nsINodeInfo* aNodeInfo)
{
return ((aNodeInfo->NamespaceID() == kNameSpaceID_XHTML &&
(aNodeInfo->NameAtom() == nsGkAtoms::tr ||
aNodeInfo->NameAtom() == nsGkAtoms::select ||
aNodeInfo->NameAtom() == nsGkAtoms::object ||
aNodeInfo->NameAtom() == nsGkAtoms::applet))
#ifdef MOZ_MATHML
|| (aNodeInfo->NamespaceID() == kNameSpaceID_MathML &&
(aNodeInfo->NameAtom() == nsGkAtoms::math))
#endif
);
}

View File

@ -42,7 +42,7 @@
#include "nsIXMLContentSink.h"
#include "nsIExpatSink.h"
#include "nsIDocumentTransformer.h"
#include "nsCOMArray.h"
#include "nsTArray.h"
#include "nsCOMPtr.h"
@ -59,6 +59,11 @@ typedef enum {
eXMLContentSinkState_InEpilog
} XMLContentSinkState;
struct StackNode {
nsCOMPtr<nsIContent> mContent;
PRUint32 mNumFlushed;
};
class nsXMLContentSink : public nsContentSink,
public nsIXMLContentSink,
public nsITransformObserver,
@ -79,12 +84,13 @@ public:
NS_DECL_NSIEXPATSINK
// nsIContentSink
NS_IMETHOD WillTokenize(void);
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel(void);
NS_IMETHOD WillInterrupt(void);
NS_IMETHOD WillResume(void);
NS_IMETHOD SetParser(nsIParser* aParser);
virtual void FlushPendingNotifications(mozFlushType aType) { }
virtual void FlushPendingNotifications(mozFlushType aType);
NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
virtual nsISupports *GetTarget();
@ -97,6 +103,7 @@ public:
PRBool &aIsAlternate);
protected:
virtual void MaybeStartLayout();
void StartLayout();
virtual nsresult AddAttributes(const PRUnichar** aNode, nsIContent* aContent);
@ -127,11 +134,22 @@ protected:
nsresult AddContentAsLeaf(nsIContent *aContent);
nsIContent* GetCurrentContent();
PRInt32 PushContent(nsIContent *aContent);
already_AddRefed<nsIContent> PopContent();
StackNode & GetCurrentStackNode();
nsresult PushContent(nsIContent *aContent);
void PopContent();
nsresult ProcessBASETag(nsIContent* aContent);
nsresult FlushTags();
void UpdateChildCounts();
void DidAddContent()
{
if (IsTimeToNotify()) {
FlushTags();
}
}
// nsContentSink override
virtual nsresult ProcessStyleLink(nsIContent* aElement,
@ -143,8 +161,12 @@ protected:
nsresult LoadXSLStyleSheet(nsIURI* aUrl);
PRBool CanStillPrettyPrint();
nsresult MaybePrettyPrint();
PRBool IsMonolithicContainer(nsINodeInfo* aNodeInfo);
nsIContent* mDocElement;
PRUnichar* mText;
@ -155,6 +177,8 @@ protected:
PRInt32 mTextLength;
PRInt32 mTextSize;
PRInt32 mNotifyLevel;
PRUint8 mConstrainSize : 1;
PRUint8 mInTitle : 1;
PRUint8 mPrettyPrintXML : 1;
@ -164,7 +188,8 @@ protected:
PRUint8 mAllowAutoXLinks : 1;
PRUint8 unused : 1; // bit available if someone needs one
nsCOMArray<nsIContent> mContentStack;
nsTArray<StackNode> mContentStack;
nsCOMPtr<nsIDocumentTransformer> mXSLTProcessor;
};

View File

@ -92,6 +92,7 @@ public:
NS_IMETHOD DidBuildModel();
NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
virtual nsISupports *GetTarget();
NS_IMETHOD DidProcessATokenImpl();
// nsIXMLContentSink
@ -111,6 +112,8 @@ protected:
nsIContent** aResult, PRBool* aAppendContent);
virtual nsresult CloseElement(nsIContent* aContent);
void MaybeStartLayout();
// nsContentSink overrides
virtual nsresult ProcessStyleLink(nsIContent* aElement,
const nsSubstring& aHref,
@ -197,8 +200,7 @@ NS_IMETHODIMP
nsXMLFragmentContentSink::DidBuildModel()
{
if (mAllContent) {
// Need the nsCOMPtr to properly release
nsCOMPtr<nsIContent> root = PopContent(); // remove mRoot pushed above
PopContent(); // remove mRoot pushed above
}
nsCOMPtr<nsIParser> kungFuDeathGrip(mParser);
@ -246,7 +248,7 @@ nsXMLFragmentContentSink::CreateElement(const PRUnichar** aAtts, PRUint32 aAttsC
// When we aren't grabbing all of the content we, never open a doc
// element, we run into trouble on the first element, so we don't append,
// and simply push this onto the content stack.
if (!mAllContent && mContentStack.Count() == 0) {
if (!mAllContent && mContentStack.Length() == 0) {
*aAppendContent = PR_FALSE;
}
@ -260,6 +262,12 @@ nsXMLFragmentContentSink::CloseElement(nsIContent* aContent)
return NS_OK;
}
void
nsXMLFragmentContentSink::MaybeStartLayout()
{
return;
}
////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
@ -422,13 +430,18 @@ nsXMLFragmentContentSink::DidBuildContent()
if (!mParseError) {
FlushText();
}
// Need the nsCOMPtr to properly release
nsCOMPtr<nsIContent> root = PopContent();
PopContent();
}
return NS_OK;
}
NS_IMETHODIMP
nsXMLFragmentContentSink::DidProcessATokenImpl()
{
return NS_OK;
}
NS_IMETHODIMP
nsXMLFragmentContentSink::IgnoreFirstContainer()
{

View File

@ -107,6 +107,7 @@ public:
NS_DECL_NSIINTERFACEREQUESTOR
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void) { return NS_OK; }
NS_IMETHOD DidBuildModel();
NS_IMETHOD WillInterrupt(void) { return NS_OK; }

View File

@ -123,6 +123,7 @@ public:
NS_DECL_NSIEXPATSINK
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel(void);
NS_IMETHOD WillInterrupt(void);

View File

@ -20,6 +20,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -63,6 +64,17 @@ public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_SINK_IID)
/**
* This method gets called before the nsParser calls tokenize.
* This is needed because the XML side actually builds
* the content model as part of the tokenization and
* not on BuildModel(). The XML side can use this call
* to do stuff that the HTML side does in WillProcessTokens().
*
* @update 2006-10-17 hsivonen
*/
NS_IMETHOD WillTokenize(void)=0;
/**
* This method gets called when the parser begins the process
* of building the content model via the content sink.

View File

@ -85,6 +85,7 @@ public:
NS_IMETHOD AddComment(const nsIParserNode& aNode);
NS_IMETHOD AddProcessingInstruction(const nsIParserNode& aNode);
NS_IMETHOD AddDocTypeDecl(const nsIParserNode& aNode);
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void) { return NS_OK; }
NS_IMETHOD DidBuildModel(void) { return NS_OK; }
NS_IMETHOD WillInterrupt(void) { return NS_OK; }

View File

@ -20,6 +20,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -418,10 +419,11 @@ nsExpatDriver::HandleStartElement(const PRUnichar *aValue,
}
if (mSink) {
mSink->HandleStartElement(aValue, aAtts,
attrArrayLength,
XML_GetIdAttributeIndex(mExpatParser),
XML_GetCurrentLineNumber(mExpatParser));
mInternalState = mSink->
HandleStartElement(aValue, aAtts, attrArrayLength,
XML_GetIdAttributeIndex(mExpatParser),
XML_GetCurrentLineNumber(mExpatParser));
MaybeStopParser();
}
return NS_OK;
@ -432,9 +434,9 @@ nsExpatDriver::HandleEndElement(const PRUnichar *aValue)
{
NS_ASSERTION(mSink, "content sink not found!");
if (mSink &&
mSink->HandleEndElement(aValue) == NS_ERROR_HTMLPARSER_BLOCK) {
XML_StopParser(mExpatParser, XML_TRUE);
if (mSink) {
mInternalState = mSink->HandleEndElement(aValue);
MaybeStopParser();
}
return NS_OK;
@ -451,6 +453,7 @@ nsExpatDriver::HandleCharacterData(const PRUnichar *aValue,
}
else if (mSink) {
mInternalState = mSink->HandleCharacterData(aValue, aLength);
MaybeStopParser();
}
return NS_OK;
@ -473,6 +476,7 @@ nsExpatDriver::HandleComment(const PRUnichar *aValue)
}
else if (mSink) {
mInternalState = mSink->HandleComment(aValue);
MaybeStopParser();
}
return NS_OK;
@ -497,10 +501,9 @@ nsExpatDriver::HandleProcessingInstruction(const PRUnichar *aTarget,
mInternalSubset.Append(aData);
mInternalSubset.AppendLiteral("?>");
}
else if (mSink &&
mSink->HandleProcessingInstruction(aTarget, aData) ==
NS_ERROR_HTMLPARSER_BLOCK) {
XML_StopParser(mExpatParser, XML_TRUE);
else if (mSink) {
mInternalState = mSink->HandleProcessingInstruction(aTarget, aData);
MaybeStopParser();
}
return NS_OK;
@ -511,7 +514,12 @@ nsExpatDriver::HandleXMLDeclaration(const PRUnichar *aVersion,
const PRUnichar *aEncoding,
PRInt32 aStandalone)
{
return mSink->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
if (mSink) {
mInternalState = mSink->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
MaybeStopParser();
}
return NS_OK;
}
nsresult
@ -536,6 +544,8 @@ nsExpatDriver::HandleDefault(const PRUnichar *aValue,
mInternalState = mSink->HandleCharacterData(newline, 1);
}
}
// The line breaks should not be normalized twice. See bug 343870.
MaybeStopParser();
}
return NS_OK;
@ -558,6 +568,7 @@ nsExpatDriver::HandleEndCdataSection()
if (mSink) {
mInternalState = mSink->HandleCDataSection(mCDataText.get(),
mCDataText.Length());
MaybeStopParser();
}
mCDataText.Truncate();
@ -659,7 +670,7 @@ nsExpatDriver::HandleEndDoctypeDecl()
// Note: mInternalSubset already doesn't include the [] around it.
mInternalState = mSink->HandleDoctypeDecl(mInternalSubset, mDoctypeName,
mSystemID, mPublicID, data);
MaybeStopParser();
}
mInternalSubset.SetCapacity(0);
@ -960,18 +971,17 @@ nsExpatDriver::ParseBuffer(const PRUnichar *aBuffer,
NS_ASSERTION((aBuffer && aLength != 0) || (!aBuffer && aLength == 0), "?");
NS_ASSERTION(mInternalState != NS_OK || aIsFinal || aBuffer,
"Useless call, we won't call Expat");
NS_PRECONDITION(mInternalState != NS_ERROR_HTMLPARSER_BLOCK || !aBuffer,
NS_PRECONDITION(!BlockedOrInterrupted() || !aBuffer,
"Non-null buffer when resuming");
NS_PRECONDITION(XML_GetCurrentByteIndex(mExpatParser) % sizeof(PRUnichar) == 0,
"Consumed part of a PRUnichar?");
if (mExpatParser && (mInternalState == NS_OK ||
mInternalState == NS_ERROR_HTMLPARSER_BLOCK)) {
if (mExpatParser && (mInternalState == NS_OK || BlockedOrInterrupted())) {
PRInt32 parserBytesBefore = XML_GetCurrentByteIndex(mExpatParser);
NS_ASSERTION(parserBytesBefore >= 0, "Unexpected value");
XML_Status status;
if (mInternalState == NS_ERROR_HTMLPARSER_BLOCK) {
if (BlockedOrInterrupted()) {
mInternalState = NS_OK; // Resume in case we're blocked.
status = XML_ResumeParser(mExpatParser);
}
@ -994,10 +1004,12 @@ nsExpatDriver::ParseBuffer(const PRUnichar *aBuffer,
NS_ASSERTION(*aConsumed <= aLength + mExpatBuffered,
"Too many bytes consumed?");
if (status == XML_STATUS_SUSPENDED) {
mInternalState = NS_ERROR_HTMLPARSER_BLOCK;
}
else if (status == XML_STATUS_ERROR) {
NS_ASSERTION(status != XML_STATUS_SUSPENDED ||
(mInternalState == NS_ERROR_HTMLPARSER_BLOCK ||
mInternalState == NS_ERROR_HTMLPARSER_INTERRUPTED),
"Inconsistent expat suspension state.");
if (status == XML_STATUS_ERROR) {
mInternalState = NS_ERROR_HTMLPARSER_STOPPARSING;
}
}
@ -1033,9 +1045,9 @@ nsExpatDriver::ConsumeToken(nsScanner& aScanner, PRBool& aFlushTokens)
// be more buffers (and so we want to flush the remaining data), or if we're
// currently blocked and there's data in Expat's buffer.
while (start != end || flush ||
(mInternalState == NS_ERROR_HTMLPARSER_BLOCK && mExpatBuffered > 0)) {
(BlockedOrInterrupted() && mExpatBuffered > 0)) {
PRBool noMoreBuffers = start == end && mIsFinalChunk;
PRBool blocked = mInternalState == NS_ERROR_HTMLPARSER_BLOCK;
PRBool blocked = BlockedOrInterrupted();
// If we're resuming and we know there won't be more data we want to
// flush the remaining data after we resumed the parser (so loop once
@ -1110,10 +1122,10 @@ nsExpatDriver::ConsumeToken(nsScanner& aScanner, PRBool& aFlushTokens)
mExpatBuffered += length - consumed;
if (mInternalState == NS_ERROR_HTMLPARSER_BLOCK) {
if (BlockedOrInterrupted()) {
PR_LOG(gExpatDriverLog, PR_LOG_DEBUG,
("Blocked parser (probably for loading linked stylesheets or "
"scripts)."));
("Blocked or interrupted parser (probably for loading linked "
"stylesheets or scripts)."));
aScanner.SetPosition(currentExpatPosition, PR_TRUE);
aScanner.Mark();
@ -1387,3 +1399,11 @@ nsExpatDriver::CanContain(PRInt32 aParent,PRInt32 aChild) const
{
return PR_TRUE;
}
void
nsExpatDriver::MaybeStopParser()
{
if (BlockedOrInterrupted()) {
XML_StopParser(mExpatParser, XML_TRUE);
}
}

View File

@ -45,6 +45,7 @@
#include "nsIDTD.h"
#include "nsITokenizer.h"
#include "nsIInputStream.h"
#include "nsIParser.h"
class nsIExpatSink;
class nsIExtendedExpatSink;
@ -123,6 +124,14 @@ private:
PRUint32 *aConsumed);
nsresult HandleError();
void MaybeStopParser();
PRBool BlockedOrInterrupted()
{
return mInternalState == NS_ERROR_HTMLPARSER_BLOCK ||
mInternalState == NS_ERROR_HTMLPARSER_INTERRUPTED;
}
XML_Parser mExpatParser;
nsString mLastLine;
nsString mCDataText;

View File

@ -93,6 +93,10 @@ void WriteTabs(PRFileDesc * out,int aTabCount) {
PR_fprintf(out, " ");
}
NS_IMETHODIMP
nsLoggingSink::WillTokenize() {
return NS_OK;
}
NS_IMETHODIMP
nsLoggingSink::WillBuildModel() {

View File

@ -60,6 +60,7 @@ public:
NS_DECL_ISUPPORTS
// nsIContentSink
NS_IMETHOD WillTokenize();
NS_IMETHOD WillBuildModel();
NS_IMETHOD DidBuildModel();
NS_IMETHOD WillInterrupt();

View File

@ -2352,7 +2352,7 @@ nsParser::WillTokenize(PRBool aIsFinalChunk)
NS_IPARSER_FLAG_HTML;
nsresult result = mParserContext->GetTokenizer(type, mSink, theTokenizer);
NS_ENSURE_SUCCESS(result, PR_FALSE);
mSink->WillTokenize();
return NS_SUCCEEDED(theTokenizer->WillTokenize(aIsFinalChunk,
&mTokenAllocator));
}

View File

@ -70,6 +70,11 @@ public:
nsSAXXMLReader();
//nsIContentSink
NS_IMETHOD WillTokenize()
{
return NS_OK;
}
NS_IMETHOD WillBuildModel();
NS_IMETHOD DidBuildModel();
NS_IMETHOD SetParser(nsIParser* aParser);

View File

@ -159,6 +159,7 @@ public:
NS_DECL_NSIEXPATSINK
// nsIContentSink
NS_IMETHOD WillTokenize(void);
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel(void);
NS_IMETHOD WillInterrupt(void);
@ -606,6 +607,13 @@ RDFContentSinkImpl::ReportError(const PRUnichar* aErrorText,
////////////////////////////////////////////////////////////////////////
// nsIContentSink interface
NS_IMETHODIMP
RDFContentSinkImpl::WillTokenize(void)
{
return NS_OK;
}
NS_IMETHODIMP
RDFContentSinkImpl::WillBuildModel(void)
{

View File

@ -219,6 +219,7 @@ public:
NS_DECL_ISUPPORTS
// nsIContentSink (superclass of nsIHTMLContentSink)
NS_IMETHOD WillTokenize() { return NS_OK; }
NS_IMETHOD WillBuildModel() { return NS_OK; }
NS_IMETHOD DidBuildModel() { return NS_OK; }
NS_IMETHOD WillInterrupt() { return NS_OK; }

View File

@ -735,6 +735,17 @@ NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \
#define NS_IMPL_THREADSAFE_QUERY_INTERFACE10 NS_IMPL_QUERY_INTERFACE10
#define NS_IMPL_THREADSAFE_QUERY_INTERFACE11 NS_IMPL_QUERY_INTERFACE11
#define NS_IMPL_QUERY_INTERFACE_INHERITED7(Class,Super,i1,i2,i3,i4,i5,i6,i7) \
NS_IMPL_QUERY_HEAD(Class) \
NS_IMPL_QUERY_BODY(i1) \
NS_IMPL_QUERY_BODY(i2) \
NS_IMPL_QUERY_BODY(i3) \
NS_IMPL_QUERY_BODY(i4) \
NS_IMPL_QUERY_BODY(i5) \
NS_IMPL_QUERY_BODY(i6) \
NS_IMPL_QUERY_BODY(i7) \
NS_IMPL_QUERY_TAIL_INHERITING(Super) \
/**
* Declare that you're going to inherit from something that already
* implements nsISupports, but also implements an additional interface, thus
@ -943,6 +954,11 @@ NS_IMETHODIMP_(nsrefcnt) Class::Release(void) \
NS_IMPL_ADDREF_INHERITED(Class, Super) \
NS_IMPL_RELEASE_INHERITED(Class, Super) \
#define NS_IMPL_ISUPPORTS_INHERITED7(Class, Super, i1, i2, i3, i4, i5, i6, i7) \
NS_IMPL_QUERY_INTERFACE_INHERITED7(Class, Super, i1, i2, i3, i4, i5, i6, i7) \
NS_IMPL_ADDREF_INHERITED(Class, Super) \
NS_IMPL_RELEASE_INHERITED(Class, Super) \
///////////////////////////////////////////////////////////////////////////////
/**
*