diff --git a/accessible/public/nsIAccessibilityService.h b/accessible/public/nsIAccessibilityService.h index 009f73b0ea09..a46b91dc6343 100644 --- a/accessible/public/nsIAccessibilityService.h +++ b/accessible/public/nsIAccessibilityService.h @@ -162,12 +162,6 @@ public: */ virtual void PresShellDestroyed(nsIPresShell *aPresShell) = 0; - /** - * Recreate an accessible for the given content node in the presshell. - */ - virtual void RecreateAccessible(nsIPresShell* aPresShell, - nsIContent* aContent) = 0; - /** * Fire accessible event of the given type for the given target. * diff --git a/accessible/src/base/AccIterator.cpp b/accessible/src/base/AccIterator.cpp index 58c4de6af7b1..e8b305a4e51b 100644 --- a/accessible/src/base/AccIterator.cpp +++ b/accessible/src/base/AccIterator.cpp @@ -277,8 +277,10 @@ XULDescriptionIterator::Next() // IDRefsIterator //////////////////////////////////////////////////////////////////////////////// -IDRefsIterator::IDRefsIterator(nsIContent* aContent, nsIAtom* aIDRefsAttr) : - mCurrIdx(0), mContent(aContent) +IDRefsIterator:: + IDRefsIterator(nsDocAccessible* aDoc, nsIContent* aContent, + nsIAtom* aIDRefsAttr) : + mCurrIdx(0), mContent(aContent), mDoc(aDoc) { if (mContent->IsInDoc()) mContent->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs); @@ -368,7 +370,7 @@ nsAccessible* IDRefsIterator::Next() { nsIContent* nextElm = NextElem(); - return nextElm ? GetAccService()->GetAccessible(nextElm, nsnull) : nsnull; + return nextElm ? mDoc->GetAccessible(nextElm) : nsnull; } nsAccessible* diff --git a/accessible/src/base/AccIterator.h b/accessible/src/base/AccIterator.h index 0be6880a4dd2..60acf78cafdd 100644 --- a/accessible/src/base/AccIterator.h +++ b/accessible/src/base/AccIterator.h @@ -264,7 +264,8 @@ private: class IDRefsIterator : public AccIterable { public: - IDRefsIterator(nsIContent* aContent, nsIAtom* aIDRefsAttr); + IDRefsIterator(nsDocAccessible* aDoc, nsIContent* aContent, + nsIAtom* aIDRefsAttr); virtual ~IDRefsIterator() { } /** @@ -292,6 +293,7 @@ private: nsString mIDs; nsIContent* mContent; + nsDocAccessible* mDoc; nsAString::index_type mCurrIdx; }; diff --git a/accessible/src/base/nsARIAMap.cpp b/accessible/src/base/nsARIAMap.cpp index 6a00ce24d231..69f1314d8a44 100644 --- a/accessible/src/base/nsARIAMap.cpp +++ b/accessible/src/base/nsARIAMap.cpp @@ -335,6 +335,15 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] = kNoReqStates, eARIACheckableBool }, + { + "note", + roles::NOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kNoReqStates + }, { "option", roles::OPTION, diff --git a/accessible/src/base/nsAccessibilityService.cpp b/accessible/src/base/nsAccessibilityService.cpp index ce736ac30094..df7f820a90b4 100644 --- a/accessible/src/base/nsAccessibilityService.cpp +++ b/accessible/src/base/nsAccessibilityService.cpp @@ -676,10 +676,8 @@ nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent) { nsDocAccessible* document = GetDocAccessible(aPresShell->GetDocument()); - if (document) { - document->HandleNotification - (document, &nsDocAccessible::RecreateAccessible, aContent); - } + if (document) + document->RecreateAccessible(aContent); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/base/nsAccessibilityService.h b/accessible/src/base/nsAccessibilityService.h index 8d47b0b0d4c6..a3ab0ddf69f6 100644 --- a/accessible/src/base/nsAccessibilityService.h +++ b/accessible/src/base/nsAccessibilityService.h @@ -173,8 +173,10 @@ public: */ virtual void PresShellActivated(nsIPresShell* aPresShell); - virtual void RecreateAccessible(nsIPresShell* aPresShell, - nsIContent* aContent); + /** + * Recreate an accessible for the given content node in the presshell. + */ + void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent); virtual void FireAccessibleEvent(PRUint32 aEvent, nsAccessible* aTarget); diff --git a/accessible/src/base/nsAccessible.cpp b/accessible/src/base/nsAccessible.cpp index d1f1a57dec8b..df02dd788023 100644 --- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -2015,14 +2015,14 @@ nsAccessible::RelationByType(PRUint32 aType) Relation rel(new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_labelledby)); if (mContent->Tag() == nsGkAtoms::label) - rel.AppendIter(new IDRefsIterator(mContent, mContent->IsHTML() ? + rel.AppendIter(new IDRefsIterator(mDoc, mContent, mContent->IsHTML() ? nsGkAtoms::_for : nsGkAtoms::control)); return rel; } case nsIAccessibleRelation::RELATION_LABELLED_BY: { - Relation rel(new IDRefsIterator(mContent, + Relation rel(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_labelledby)); if (mContent->IsHTML()) { rel.AppendIter(new HTMLLabelIterator(Document(), this)); @@ -2033,8 +2033,8 @@ nsAccessible::RelationByType(PRUint32 aType) return rel; } case nsIAccessibleRelation::RELATION_DESCRIBED_BY: { - Relation rel(new IDRefsIterator(mContent, - nsGkAtoms::aria_describedby)); + Relation rel(new IDRefsIterator(mDoc, mContent, + nsGkAtoms::aria_describedby)); if (mContent->IsXUL()) rel.AppendIter(new XULDescriptionIterator(Document(), mContent)); @@ -2049,7 +2049,7 @@ nsAccessible::RelationByType(PRUint32 aType) // tied to a control. if (mContent->Tag() == nsGkAtoms::description && mContent->IsXUL()) - rel.AppendIter(new IDRefsIterator(mContent, + rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control)); return rel; @@ -2091,13 +2091,13 @@ nsAccessible::RelationByType(PRUint32 aType) return Relation(new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_controls)); case nsIAccessibleRelation::RELATION_CONTROLLER_FOR: { - Relation rel(new IDRefsIterator(mContent, + Relation rel(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_controls)); rel.AppendIter(new HTMLOutputIterator(Document(), mContent)); return rel; } case nsIAccessibleRelation::RELATION_FLOWS_TO: - return Relation(new IDRefsIterator(mContent, + return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_flowto)); case nsIAccessibleRelation::RELATION_FLOWS_FROM: return Relation(new RelatedAccIterator(Document(), mContent, diff --git a/accessible/src/base/nsDocAccessible.cpp b/accessible/src/base/nsDocAccessible.cpp index a958cd290bd5..9e3e5e81252b 100644 --- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -1467,14 +1467,8 @@ nsDocAccessible::RecreateAccessible(nsIContent* aContent) // coalescence with normal hide and show events. Note, in this case they // should be coalesced with normal show/hide events. - // Check if the node is in accessible document. - nsAccessible* container = GetContainerAccessible(aContent); - if (container) { - // Remove and reinsert. - UpdateTree(container, aContent, false); - container->UpdateChildren(); - UpdateTree(container, aContent, true); - } + ContentRemoved(aContent->GetParent(), aContent); + ContentInserted(aContent->GetParent(), aContent, aContent->GetNextSibling()); } void @@ -1623,7 +1617,7 @@ nsDocAccessible::AddDependentIDsFor(nsAccessible* aRelProvider, continue; } - IDRefsIterator iter(aRelProvider->GetContent(), relAttr); + IDRefsIterator iter(this, aRelProvider->GetContent(), relAttr); while (true) { const nsDependentSubstring id = iter.NextID(); if (id.IsEmpty()) @@ -1674,7 +1668,7 @@ nsDocAccessible::RemoveDependentIDsFor(nsAccessible* aRelProvider, if (aRelAttr && aRelAttr != *kRelationAttrs[idx]) continue; - IDRefsIterator iter(aRelProvider->GetContent(), relAttr); + IDRefsIterator iter(this, aRelProvider->GetContent(), relAttr); while (true) { const nsDependentSubstring id = iter.NextID(); if (id.IsEmpty()) @@ -1717,8 +1711,7 @@ nsDocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, // Recreate the accessible when role is changed because we might require a // different accessible class for the new role or the accessible may expose // a different sets of interfaces (COM restriction). - HandleNotification - (this, &nsDocAccessible::RecreateAccessible, aElement); + RecreateAccessible(aElement); return true; } @@ -1729,11 +1722,9 @@ nsDocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, // kill use to recreate the accessible even if the attribute was used in // the wrong namespace or an element that doesn't support it. - // Recreate accessible asynchronously to allow the content to handle - // the attribute change. - mNotificationController->ScheduleNotification - (this, &nsDocAccessible::RecreateAccessible, aElement); - + // Make sure the accessible is recreated asynchronously to allow the content + // to handle the attribute change. + RecreateAccessible(aElement); return true; } @@ -1742,8 +1733,7 @@ nsDocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, // This affects whether the accessible supports SelectAccessible. // COM says we cannot change what interfaces are supported on-the-fly, // so invalidate this object. A new one will be created on demand. - HandleNotification - (this, &nsDocAccessible::RecreateAccessible, aElement); + RecreateAccessible(aElement); return true; } diff --git a/accessible/src/base/nsTextEquivUtils.cpp b/accessible/src/base/nsTextEquivUtils.cpp index 881e51bb170b..44ed8e41d1aa 100644 --- a/accessible/src/base/nsTextEquivUtils.cpp +++ b/accessible/src/base/nsTextEquivUtils.cpp @@ -94,7 +94,7 @@ nsTextEquivUtils::GetTextEquivFromIDRefs(nsAccessible *aAccessible, return NS_OK; nsIContent* refContent = nsnull; - IDRefsIterator iter(content, aIDRefsAttr); + IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr); while ((refContent = iter.NextElem())) { if (!aTextEquiv.IsEmpty()) aTextEquiv += ' '; diff --git a/accessible/src/html/nsHTMLTableAccessible.cpp b/accessible/src/html/nsHTMLTableAccessible.cpp index e487a54f3761..a5547bb3d1cc 100644 --- a/accessible/src/html/nsHTMLTableAccessible.cpp +++ b/accessible/src/html/nsHTMLTableAccessible.cpp @@ -334,7 +334,7 @@ nsHTMLTableCellAccessible::GetHeaderCells(PRInt32 aRowOrColumnHeaderCell, nsIArray **aHeaderCells) { // Get header cells from @header attribute. - IDRefsIterator iter(mContent, nsGkAtoms::headers); + IDRefsIterator iter(mDoc, mContent, nsGkAtoms::headers); nsIContent* headerCellElm = iter.NextElem(); if (headerCellElm) { nsresult rv = NS_OK; diff --git a/accessible/src/html/nsHTMLTextAccessible.cpp b/accessible/src/html/nsHTMLTextAccessible.cpp index 766462f3dda9..d997253e0fcb 100644 --- a/accessible/src/html/nsHTMLTextAccessible.cpp +++ b/accessible/src/html/nsHTMLTextAccessible.cpp @@ -204,7 +204,7 @@ nsHTMLOutputAccessible::RelationByType(PRUint32 aType) { Relation rel = nsAccessibleWrap::RelationByType(aType); if (aType == nsIAccessibleRelation::RELATION_CONTROLLED_BY) - rel.AppendIter(new IDRefsIterator(mContent, nsGkAtoms::_for)); + rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for)); return rel; } diff --git a/accessible/tests/mochitest/test_aria_roles.html b/accessible/tests/mochitest/test_aria_roles.html index dbeacbe86731..2bd59a11decb 100644 --- a/accessible/tests/mochitest/test_aria_roles.html +++ b/accessible/tests/mochitest/test_aria_roles.html @@ -68,6 +68,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=529289 ////////////////////////////////////////////////////////////////////////// // misc roles + testRole("note", ROLE_NOTE); testRole("scrollbar", ROLE_SCROLLBAR); testRole("dir", ROLE_LIST); @@ -154,6 +155,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=529289
sectionhead
+
note
scrollbar
diff --git a/accessible/tests/mochitest/treeupdate/test_imagemap.html b/accessible/tests/mochitest/treeupdate/test_imagemap.html index 88a625681658..1a448d1693a3 100644 --- a/accessible/tests/mochitest/treeupdate/test_imagemap.html +++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html @@ -345,6 +345,41 @@ } } + function hideImageMap(aContainerID, aImageID) + { + this.container = getAccessible(aContainerID); + this.imageMap = null; + this.imageMapNode = getNode(aImageID); + + function getImageMap(aThisObj) + { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_REORDER, aContainerID) + ]; + + this.invoke = function hideImageMap_invoke() + { + this.imageMap = getAccessible(this.imageMapNode); + this.imageMapNode.style.display = "none"; + } + + this.finalCheck = function hideImageMap_finalCheck() + { + var accTree = + { SECTION: [ ] }; + testAccessibleTree(this.container, accTree); + } + + this.getID = function hideImageMap_getID() + { + return "display:none image"; + } + } + //gA11yEventDumpToConsole = true; var gQueue = null; @@ -359,6 +394,7 @@ gQueue.push(new restoreNameOnMap("container", "imgmap", "map")); gQueue.push(new removeMap("container", "imgmap", "map")); gQueue.push(new insertMap("container", "imgmap")); + gQueue.push(new hideImageMap("container", "imgmap")); gQueue.invoke(); // Will call SimpleTest.finish(); } diff --git a/configure.in b/configure.in index 21d5e494ed04..677a665ca437 100644 --- a/configure.in +++ b/configure.in @@ -2450,7 +2450,6 @@ ia64*-hpux*) ZIP=zip UNZIP=unzip DOXYGEN=: - GARBAGE='$(OBJDIR)/vc20.pdb $(OBJDIR)/vc40.pdb' ASM_SUFFIX=asm OBJ_SUFFIX=obj LIB_SUFFIX=lib diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index d818dc949965..17f9cff61331 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -111,6 +111,7 @@ class imgIRequest; class nsISHEntry; class nsDOMNavigationTiming; class nsWindowSizes; +class nsIObjectLoadingContent; namespace mozilla { namespace css { @@ -1566,6 +1567,10 @@ public: // state is unlocked/false. virtual nsresult SetImageLockingState(bool aLocked) = 0; + virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) = 0; + virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0; + virtual void GetPlugins(nsTArray& aPlugins) = 0; + virtual nsresult GetStateObject(nsIVariant** aResult) = 0; virtual nsDOMNavigationTiming* GetNavigationTiming() const = 0; diff --git a/content/base/public/nsIObjectLoadingContent.idl b/content/base/public/nsIObjectLoadingContent.idl index bf688caa76f9..1b0a1a7f470e 100644 --- a/content/base/public/nsIObjectLoadingContent.idl +++ b/content/base/public/nsIObjectLoadingContent.idl @@ -52,7 +52,7 @@ interface nsIURI; /** * This interface represents a content node that loads objects. */ -[scriptable, uuid(3FF07AB3-5BAC-4D98-9549-5BD15CCEBCD3)] +[scriptable, uuid(fd56fda8-d3c3-4368-8cf3-67dbc992aec9)] interface nsIObjectLoadingContent : nsISupports { const unsigned long TYPE_LOADING = 0; @@ -125,6 +125,12 @@ interface nsIObjectLoadingContent : nsISupports */ void playPlugin(); + /** + * This attribute will return true if the plugin has been activated + * and false if the plugin is still in the click-to-play state. + */ + readonly attribute boolean activated; + [noscript] void stopPluginInstance(); [noscript] void syncStartPluginInstance(); diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 9e8e28cc9429..c530fd7ea978 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1673,6 +1673,8 @@ nsDocument::~nsDocument() // unlocked state, and then clear the table. SetImageLockingState(false); mImageTracker.Clear(); + + mPlugins.Clear(); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument) @@ -2023,7 +2025,8 @@ nsDocument::Init() mScriptLoader = new nsScriptLoader(this); NS_ENSURE_TRUE(mScriptLoader, NS_ERROR_OUT_OF_MEMORY); - if (!mImageTracker.Init()) { + if (!mImageTracker.Init() || + !mPlugins.Init()) { return NS_ERROR_OUT_OF_MEMORY; } @@ -8354,6 +8357,51 @@ nsDocument::RemoveImage(imgIRequest* aImage) return rv; } +nsresult +nsDocument::AddPlugin(nsIObjectLoadingContent* aPlugin) +{ + MOZ_ASSERT(aPlugin); + if (!mPlugins.PutEntry(aPlugin)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void +nsDocument::RemovePlugin(nsIObjectLoadingContent* aPlugin) +{ + MOZ_ASSERT(aPlugin); + mPlugins.RemoveEntry(aPlugin); +} + +static bool +AllSubDocumentPluginEnum(nsIDocument* aDocument, void* userArg) +{ + nsTArray* plugins = + reinterpret_cast< nsTArray* >(userArg); + MOZ_ASSERT(plugins); + aDocument->GetPlugins(*plugins); + return true; +} + +static PLDHashOperator +AllPluginEnum(nsPtrHashKey* aPlugin, void* userArg) +{ + nsTArray* allPlugins = + reinterpret_cast< nsTArray* >(userArg); + MOZ_ASSERT(allPlugins); + allPlugins->AppendElement(aPlugin->GetKey()); + return PL_DHASH_NEXT; +} + +void +nsDocument::GetPlugins(nsTArray& aPlugins) +{ + aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count()); + mPlugins.EnumerateEntries(AllPluginEnum, &aPlugins); + EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins); +} + PLDHashOperator LockEnumerator(imgIRequest* aKey, PRUint32 aData, void* userArg) diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index 8655935d1193..e133084baddd 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -934,6 +934,16 @@ public: virtual NS_HIDDEN_(nsresult) RemoveImage(imgIRequest* aImage); virtual NS_HIDDEN_(nsresult) SetImageLockingState(bool aLocked); + // AddPlugin adds a plugin-related element to mPlugins when the element is + // added to the tree. + virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin); + // RemovePlugin removes a plugin-related element to mPlugins when the + // element is removed from the tree. + virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin); + // GetPlugins returns the plugin-related elements from + // the frame and any subframes. + virtual void GetPlugins(nsTArray& aPlugins); + virtual nsresult GetStateObject(nsIVariant** aResult); virtual nsDOMNavigationTiming* GetNavigationTiming() const; @@ -1298,6 +1308,9 @@ private: // Tracking for images in the document. nsDataHashtable< nsPtrHashKey, PRUint32> mImageTracker; + // Tracking for plugins in the document. + nsTHashtable< nsPtrHashKey > mPlugins; + VisibilityState mVisibilityState; #ifdef DEBUG diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index b8b54bcccd5f..56c1079d1695 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -115,6 +115,18 @@ static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc"); #include "mozilla/Preferences.h" +static bool gClickToPlayPlugins = false; + +static void +InitPrefCache() +{ + static bool initializedPrefCache = false; + if (!initializedPrefCache) { + mozilla::Preferences::AddBoolVarCache(&gClickToPlayPlugins, "plugins.click_to_play"); + } + initializedPrefCache = true; +} + class nsAsyncInstantiateEvent : public nsRunnable { public: nsObjectLoadingContent *mContent; @@ -546,6 +558,26 @@ bool nsObjectLoadingContent::IsPluginEnabledByExtension(nsIURI* uri, nsCString& return false; } +nsresult +nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* /*aParent*/, + nsIContent* /*aBindingParent*/, + bool /*aCompileEventHandlers*/) +{ + if (aDocument) { + return aDocument->AddPlugin(this); + } + return NS_OK; +} + +void +nsObjectLoadingContent::UnbindFromTree(bool /*aDeep*/, bool /*aNullParent*/) +{ + nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); + MOZ_ASSERT(thisContent); + nsIDocument* ownerDoc = thisContent->OwnerDoc(); + ownerDoc->RemovePlugin(this); +} + nsObjectLoadingContent::nsObjectLoadingContent() : mPendingInstantiateEvent(nsnull) , mChannel(nsnull) @@ -554,11 +586,14 @@ nsObjectLoadingContent::nsObjectLoadingContent() , mUserDisabled(false) , mSuppressed(false) , mNetworkCreated(true) - // If plugins.click_to_play is false, plugins should always play - , mShouldPlay(!mozilla::Preferences::GetBool("plugins.click_to_play", false)) , mSrcStreamLoading(false) , mFallbackReason(ePluginOtherState) { + InitPrefCache(); + // If plugins.click_to_play is false, plugins should always play + mShouldPlay = !gClickToPlayPlugins; + // If plugins.click_to_play is true, track the activated state of plugins. + mActivated = !gClickToPlayPlugins; } nsObjectLoadingContent::~nsObjectLoadingContent() @@ -2205,5 +2240,13 @@ nsObjectLoadingContent::PlayPlugin() return NS_OK; mShouldPlay = true; + mActivated = true; return LoadObject(mURI, true, mContentType, true); } + +NS_IMETHODIMP +nsObjectLoadingContent::GetActivated(bool* aActivated) +{ + *aActivated = mActivated; + return NS_OK; +} diff --git a/content/base/src/nsObjectLoadingContent.h b/content/base/src/nsObjectLoadingContent.h index 720e9ca8bb95..8e24376f386d 100644 --- a/content/base/src/nsObjectLoadingContent.h +++ b/content/base/src/nsObjectLoadingContent.h @@ -244,6 +244,12 @@ class nsObjectLoadingContent : public nsImageLoadingContent static void DoStopPlugin(nsPluginInstanceOwner *aInstanceOwner, bool aDelayedStop); + nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandler); + void UnbindFromTree(bool aDeep = true, + bool aNullParent = true); + private: void NotifyContentObjectWrapper(); @@ -399,6 +405,10 @@ class nsObjectLoadingContent : public nsImageLoadingContent // This is used for click-to-play plugins. bool mShouldPlay : 1; + // Used to keep track of whether or not a plugin has been played. + // This is used for click-to-play plugins. + bool mActivated : 1; + // Used to track when we might try to instantiate a plugin instance based on // a src data stream being delivered to this object. When this is true we don't // want plugin instance instantiation code to attempt to load src data again or diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index fc1d0cf876f0..39eec8236bfc 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -2779,6 +2779,10 @@ nsXMLHttpRequest::Send(nsIVariant *aBody) } } + // Blocking gets are common enough out of XHR that we should mark + // the channel slow by default for pipeline purposes + AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE); + if (!IsSystemXHR()) { // Always create a nsCORSListenerProxy here even if it's // a same-origin request right now, since it could be redirected. diff --git a/content/html/content/src/nsHTMLMediaElement.cpp b/content/html/content/src/nsHTMLMediaElement.cpp index a3d92bca8744..ca91cddcdda2 100644 --- a/content/html/content/src/nsHTMLMediaElement.cpp +++ b/content/html/content/src/nsHTMLMediaElement.cpp @@ -57,6 +57,8 @@ #include "nsThreadUtils.h" #include "nsIThreadInternal.h" #include "nsContentUtils.h" +#include "nsIRequest.h" + #include "nsFrameManager.h" #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" @@ -2850,6 +2852,13 @@ void nsHTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel) // Send Accept header for video and audio types only (Bug 489071) SetAcceptHeader(aChannel); + // Media elements are likely candidates for HTTP Pipeline head of line + // blocking problems, so disable pipelines. + nsLoadFlags loadflags; + aChannel->GetLoadFlags(&loadflags); + loadflags |= nsIRequest::INHIBIT_PIPELINE; + aChannel->SetLoadFlags(loadflags); + // Apache doesn't send Content-Length when gzip transfer encoding is used, // which prevents us from estimating the video length (if explicit Content-Duration // and a length spec in the container are not present either) and from seeking. diff --git a/content/html/content/src/nsHTMLObjectElement.cpp b/content/html/content/src/nsHTMLObjectElement.cpp index a45e4de30355..b9d8a79bfbad 100644 --- a/content/html/content/src/nsHTMLObjectElement.cpp +++ b/content/html/content/src/nsHTMLObjectElement.cpp @@ -265,6 +265,11 @@ nsHTMLObjectElement::BindToTree(nsIDocument *aDocument, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); + rv = nsObjectLoadingContent::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + // If we already have all the children, start the load. if (mIsDoneAddingChildren) { void (nsHTMLObjectElement::*start)() = &nsHTMLObjectElement::StartObjectLoad; @@ -279,6 +284,7 @@ nsHTMLObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { RemovedFromDocument(); + nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent); } diff --git a/content/html/content/src/nsHTMLSharedObjectElement.cpp b/content/html/content/src/nsHTMLSharedObjectElement.cpp index 177518aef2bf..975f78211aab 100644 --- a/content/html/content/src/nsHTMLSharedObjectElement.cpp +++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp @@ -283,6 +283,11 @@ nsHTMLSharedObjectElement::BindToTree(nsIDocument *aDocument, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); + rv = nsObjectLoadingContent::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + // If we already have all the children, start the load. if (mIsDoneAddingChildren) { void (nsHTMLSharedObjectElement::*start)() = @@ -298,6 +303,7 @@ nsHTMLSharedObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { RemovedFromDocument(); + nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); } diff --git a/content/xbl/src/nsXBLContentSink.cpp b/content/xbl/src/nsXBLContentSink.cpp index 282b44ef732a..892aa13feb9b 100644 --- a/content/xbl/src/nsXBLContentSink.cpp +++ b/content/xbl/src/nsXBLContentSink.cpp @@ -423,8 +423,13 @@ nsXBLContentSink::OnOpenContainer(const PRUnichar **aAtts, bool ret = true; if (aTagName == nsGkAtoms::bindings) { ENSURE_XBL_STATE(mState == eXBL_InDocument); - - mDocInfo = NS_NewXBLDocumentInfo(mDocument); + + NS_ASSERTION(mDocument, "Must have a document!"); + nsRefPtr info = new nsXBLDocumentInfo(mDocument); + + // We keep a weak ref. We're creating a cycle between doc/binding manager/doc info. + mDocInfo = info; + if (!mDocInfo) { mState = eXBL_Error; return true; @@ -433,16 +438,14 @@ nsXBLContentSink::OnOpenContainer(const PRUnichar **aAtts, mDocument->BindingManager()->PutXBLDocumentInfo(mDocInfo); nsIURI *uri = mDocument->GetDocumentURI(); - + bool isChrome = false; bool isRes = false; uri->SchemeIs("chrome", &isChrome); uri->SchemeIs("resource", &isRes); mIsChromeOrResource = isChrome || isRes; - - nsXBLDocumentInfo* info = mDocInfo; - NS_RELEASE(info); // We keep a weak ref. We've created a cycle between doc/binding manager/doc info. + mState = eXBL_InBindings; } else if (aTagName == nsGkAtoms::binding) { diff --git a/content/xbl/src/nsXBLDocumentInfo.cpp b/content/xbl/src/nsXBLDocumentInfo.cpp index 406dc911ea0a..848b26931953 100644 --- a/content/xbl/src/nsXBLDocumentInfo.cpp +++ b/content/xbl/src/nsXBLDocumentInfo.cpp @@ -682,7 +682,8 @@ nsXBLDocumentInfo::ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocI NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr doc = do_QueryInterface(domdoc); - nsRefPtr docInfo = NS_NewXBLDocumentInfo(doc); + NS_ASSERTION(doc, "Must have a document!"); + nsRefPtr docInfo = new nsXBLDocumentInfo(doc); while (1) { PRUint8 flags; @@ -783,14 +784,3 @@ nsXBLDocumentInfo::GetScriptGlobalObject() return mGlobalObject; } - -nsXBLDocumentInfo* NS_NewXBLDocumentInfo(nsIDocument* aDocument) -{ - NS_PRECONDITION(aDocument, "Must have a document!"); - - nsXBLDocumentInfo* result; - - result = new nsXBLDocumentInfo(aDocument); - NS_ADDREF(result); - return result; -} diff --git a/content/xbl/src/nsXBLDocumentInfo.h b/content/xbl/src/nsXBLDocumentInfo.h index b94b968e5213..93ab6d09c024 100644 --- a/content/xbl/src/nsXBLDocumentInfo.h +++ b/content/xbl/src/nsXBLDocumentInfo.h @@ -101,6 +101,4 @@ private: nsRefPtr mGlobalObject; }; -nsXBLDocumentInfo* NS_NewXBLDocumentInfo(nsIDocument* aDocument); - #endif diff --git a/content/xbl/src/nsXBLService.cpp b/content/xbl/src/nsXBLService.cpp index ef614bf64568..bf71a64e91ca 100644 --- a/content/xbl/src/nsXBLService.cpp +++ b/content/xbl/src/nsXBLService.cpp @@ -1106,11 +1106,7 @@ nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement, } } - if (!info) - return NS_OK; - - *aResult = info; - NS_IF_ADDREF(*aResult); + info.forget(aResult); return NS_OK; } diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 6d81c6513541..93519679afd3 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -51,6 +51,7 @@ #include "nsRefreshDriver.h" #include "nsDOMTouchEvent.h" #include "nsIDOMTouchEvent.h" +#include "nsObjectLoadingContent.h" #include "nsIScrollableFrame.h" @@ -76,6 +77,7 @@ #include "nsCSSProps.h" #include "nsDOMFile.h" #include "BasicLayers.h" +#include "nsTArrayHelpers.h" #if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK2) #include @@ -2230,3 +2232,26 @@ nsDOMWindowUtils::GetPaintingSuppressed(bool *aPaintingSuppressed) return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::GetPlugins(JSContext* cx, jsval* aPlugins) +{ + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsIDOMDocument* ddoc = mWindow->GetExtantDocument(); + + nsresult rv; + nsCOMPtr doc = do_QueryInterface(ddoc, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray plugins; + doc->GetPlugins(plugins); + + JSObject* jsPlugins = nsnull; + rv = nsTArrayToJSArray(cx, plugins, &jsPlugins); + NS_ENSURE_SUCCESS(rv, rv); + + *aPlugins = OBJECT_TO_JSVAL(jsPlugins); + return NS_OK; +} diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index b90c2b57e8b9..a86f14081ae0 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -70,7 +70,7 @@ interface nsIDOMFile; interface nsIFile; interface nsIDOMTouch; -[scriptable, uuid(43feb172-30e1-4ff1-b021-004f973da516)] +[scriptable, uuid(c7f303a1-4f7b-4d38-a192-c3f0e25dadb1)] interface nsIDOMWindowUtils : nsISupports { /** @@ -1099,4 +1099,15 @@ interface nsIDOMWindowUtils : nsISupports { * otherwise. */ readonly attribute boolean paintingSuppressed; + + /** + * Returns an array of plugins on the page for opt-in activation. + * + * Cannot be accessed from unprivileged context (not content-accessible). + * Will throw a DOM security error if called without UniversalXPConnect + * privileges. + * + */ + [implicit_jscontext] + readonly attribute jsval plugins; }; diff --git a/dom/system/unix/QTMLocationProvider.cpp b/dom/system/unix/QTMLocationProvider.cpp index 74e2f473bcc9..0cd855cb576e 100644 --- a/dom/system/unix/QTMLocationProvider.cpp +++ b/dom/system/unix/QTMLocationProvider.cpp @@ -116,3 +116,8 @@ QTMLocationProvider::Shutdown() return NS_OK; } +NS_IMETHODIMP +QTMLocationProvider::SetHighAccuracy(bool) +{ + return NS_OK; +} diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp index e5cf0c7aacbb..533b199d71c9 100644 --- a/dom/telephony/Telephony.cpp +++ b/dom/telephony/Telephony.cpp @@ -53,6 +53,7 @@ #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "SystemWorkerManager.h" +#include "nsTArrayHelpers.h" #include "CallEvent.h" #include "TelephonyCall.h" @@ -69,53 +70,6 @@ typedef nsAutoTArray TelephonyList; TelephonyList* gTelephonyList; -template -inline nsresult -nsTArrayToJSArray(JSContext* aCx, JSObject* aGlobal, - const nsTArray >& aSourceArray, - JSObject** aResultArray) -{ - NS_ASSERTION(aCx, "Null context!"); - NS_ASSERTION(aGlobal, "Null global!"); - - JSAutoRequest ar(aCx); - JSAutoEnterCompartment ac; - if (!ac.enter(aCx, aGlobal)) { - NS_WARNING("Failed to enter compartment!"); - return NS_ERROR_FAILURE; - } - - JSObject* arrayObj; - - if (aSourceArray.IsEmpty()) { - arrayObj = JS_NewArrayObject(aCx, 0, nsnull); - } else { - nsTArray valArray; - valArray.SetLength(aSourceArray.Length()); - - for (PRUint32 index = 0; index < valArray.Length(); index++) { - nsISupports* obj = aSourceArray[index]->ToISupports(); - nsresult rv = - nsContentUtils::WrapNative(aCx, aGlobal, obj, &valArray[index]); - NS_ENSURE_SUCCESS(rv, rv); - } - - arrayObj = JS_NewArrayObject(aCx, valArray.Length(), valArray.Elements()); - } - - if (!arrayObj) { - return NS_ERROR_OUT_OF_MEMORY; - } - - // XXX This is not what Jonas wants. He wants it to be live. - if (!JS_FreezeObject(aCx, arrayObj)) { - return NS_ERROR_FAILURE; - } - - *aResultArray = arrayObj; - return NS_OK; -} - } // anonymous namespace Telephony::Telephony() @@ -352,8 +306,7 @@ Telephony::GetCalls(jsval* aCalls) NS_ENSURE_SUCCESS(rv, rv); if (sc) { rv = - nsTArrayToJSArray(sc->GetNativeContext(), - sc->GetNativeGlobal(), mCalls, &calls); + nsTArrayToJSArray(mScriptContext->GetNativeContext(), mCalls, &calls); NS_ENSURE_SUCCESS(rv, rv); if (!mRooted) { diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 14d715cdea3a..978644b8cad7 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -1910,7 +1910,7 @@ GLContextProviderEGL::CreateOffscreen(const gfxIntSize& aSize, return nsnull; } -#if defined(ANDROID) || defined(XP_WIN) +#if !defined(MOZ_X11) bool usePBuffers = false; // Generally, prefer FBOs to PBuffers if (sEGLLibrary.IsANGLE()) diff --git a/gfx/layers/opengl/ImageLayerOGL.cpp b/gfx/layers/opengl/ImageLayerOGL.cpp index c885a380b615..588e2401bc0a 100644 --- a/gfx/layers/opengl/ImageLayerOGL.cpp +++ b/gfx/layers/opengl/ImageLayerOGL.cpp @@ -458,23 +458,11 @@ ImageLayerOGL::RenderLayer(int, } static void -InitTexture(GLContext* aGL, GLuint aTexture, GLenum aFormat, const gfxIntSize& aSize) +SetClamping(GLContext* aGL, GLuint aTexture) { aGL->fBindTexture(LOCAL_GL_TEXTURE_2D, aTexture); - aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR); - aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR); aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); - - aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, - 0, - aFormat, - aSize.width, - aSize.height, - 0, - aFormat, - LOCAL_GL_UNSIGNED_BYTE, - NULL); } static void @@ -529,13 +517,13 @@ ImageLayerOGL::AllocateTexturesYCbCr(PlanarYCbCrImage *aImage) gl()->MakeCurrent(); mTextureRecycleBin->GetTexture(TextureRecycleBin::TEXTURE_Y, data.mYSize, gl(), &backendData->mTextures[0]); - InitTexture(gl(), backendData->mTextures[0].GetTextureID(), LOCAL_GL_LUMINANCE, data.mYSize); + SetClamping(gl(), backendData->mTextures[0].GetTextureID()); mTextureRecycleBin->GetTexture(TextureRecycleBin::TEXTURE_C, data.mCbCrSize, gl(), &backendData->mTextures[1]); - InitTexture(gl(), backendData->mTextures[1].GetTextureID(), LOCAL_GL_LUMINANCE, data.mCbCrSize); + SetClamping(gl(), backendData->mTextures[1].GetTextureID()); mTextureRecycleBin->GetTexture(TextureRecycleBin::TEXTURE_C, data.mCbCrSize, gl(), &backendData->mTextures[2]); - InitTexture(gl(), backendData->mTextures[2].GetTextureID(), LOCAL_GL_LUMINANCE, data.mCbCrSize); + SetClamping(gl(), backendData->mTextures[2].GetTextureID()); UploadYUVToTexture(gl(), aImage->mData, &backendData->mTextures[0], @@ -569,6 +557,8 @@ ImageLayerOGL::AllocateTexturesCairo(CairoImage *aImage) GLuint tex = texture.GetTextureID(); gl->fActiveTexture(LOCAL_GL_TEXTURE0); + SetClamping(gl, tex); + #if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO) if (sGLXLibrary.SupportsTextureFromPixmap(aImage->mSurface)) { if (aImage->mSurface->GetContentType() == gfxASurface::CONTENT_COLOR_ALPHA) { @@ -636,9 +626,9 @@ ShadowImageLayerOGL::Init(const SharedImage& aFront) "Texture allocation failed!"); gl()->MakeCurrent(); - InitTexture(gl(), mYUVTexture[0].GetTextureID(), LOCAL_GL_LUMINANCE, mSize); - InitTexture(gl(), mYUVTexture[1].GetTextureID(), LOCAL_GL_LUMINANCE, mCbCrSize); - InitTexture(gl(), mYUVTexture[2].GetTextureID(), LOCAL_GL_LUMINANCE, mCbCrSize); + SetClamping(gl(), mYUVTexture[0].GetTextureID()); + SetClamping(gl(), mYUVTexture[1].GetTextureID()); + SetClamping(gl(), mYUVTexture[2].GetTextureID()); return true; } return false; diff --git a/gfx/skia/Makefile.in b/gfx/skia/Makefile.in index e09ae4bdcdb5..1272bdc602f8 100644 --- a/gfx/skia/Makefile.in +++ b/gfx/skia/Makefile.in @@ -333,6 +333,22 @@ CPPSRCS += \ OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS) endif +ifeq (qt,$(MOZ_WIDGET_TOOLKIT)) +CPPSRCS += \ + SkFontHost_FreeType.cpp \ + SkFontHost_gamma_none.cpp \ + SkMMapStream.cpp \ + SkOSFile.cpp \ + $(NULL) +ifeq (Linux,$(OS_TARGET)) +CPPSRCS += \ + SkFontHost_linux.cpp \ + SkTime_Unix.cpp \ + $(NULL) +endif +OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS) +endif + ifeq (windows,$(MOZ_WIDGET_TOOLKIT)) EXPORTS_skia += \ include/config/sk_stdint.h \ diff --git a/gfx/thebes/gfxQtPlatform.cpp b/gfx/thebes/gfxQtPlatform.cpp index a3a11418930b..86997a4377dc 100644 --- a/gfx/thebes/gfxQtPlatform.cpp +++ b/gfx/thebes/gfxQtPlatform.cpp @@ -46,6 +46,8 @@ #include "gfxFontconfigUtils.h" +#include "mozilla/gfx/2D.h" + #include "cairo.h" #include "gfxImageSurface.h" @@ -81,6 +83,7 @@ using namespace mozilla; using namespace mozilla::unicode; +using namespace mozilla::gfx; #define DEFAULT_RENDER_MODE RENDER_DIRECT @@ -596,3 +599,11 @@ gfxQtPlatform::GetOffscreenFormat() { return sOffscreenFormat; } + +bool +gfxQtPlatform::SupportsAzure(BackendType& aBackend) +{ + aBackend = BACKEND_SKIA; + return true; +} + diff --git a/gfx/thebes/gfxQtPlatform.h b/gfx/thebes/gfxQtPlatform.h index d6b0451b82f9..6c98bf0c4345 100644 --- a/gfx/thebes/gfxQtPlatform.h +++ b/gfx/thebes/gfxQtPlatform.h @@ -76,6 +76,8 @@ public: already_AddRefed CreateOffscreenSurface(const gfxIntSize& size, gfxASurface::gfxContentType contentType); + virtual bool SupportsAzure(mozilla::gfx::BackendType& aBackend); + nsresult GetFontList(nsIAtom *aLangGroup, const nsACString& aGenericFamily, nsTArray& aListOfFonts); diff --git a/image/src/imgRequest.cpp b/image/src/imgRequest.cpp index fa2cded54a8c..1f6b7d1972fb 100644 --- a/image/src/imgRequest.cpp +++ b/image/src/imgRequest.cpp @@ -685,6 +685,15 @@ NS_IMETHODIMP imgRequest::OnStopDecode(imgIRequest *aRequest, aStatusArg); } + if (NS_FAILED(aStatus)) { + // Some kind of problem has happened with image decoding. + // Report the URI to net:failed-to-decode-uri observers. + + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) + os->NotifyObservers(mURI, "net:failed-to-process-uri", nsnull); + } + // RasterImage and everything below it is completely correct and // bulletproof about its handling of decoder notifications. // Unfortunately, here and above we have to make some gross and diff --git a/image/test/mochitest/Makefile.in b/image/test/mochitest/Makefile.in index 39548c1d5a6f..8c3cb873f645 100644 --- a/image/test/mochitest/Makefile.in +++ b/image/test/mochitest/Makefile.in @@ -118,6 +118,9 @@ _CHROME_FILES = imgutils.js \ test_undisplayed_iframe.html \ iframe.html \ ref-iframe.html \ + test_net_failedtoprocess.html \ + invalid.jpg \ + damon.jpg \ $(NULL) libs:: $(_TEST_FILES) diff --git a/image/test/mochitest/invalid.jpg b/image/test/mochitest/invalid.jpg new file mode 100644 index 000000000000..c677a81e29f8 --- /dev/null +++ b/image/test/mochitest/invalid.jpg @@ -0,0 +1 @@ +notajpg diff --git a/image/test/mochitest/test_net_failedtoprocess.html b/image/test/mochitest/test_net_failedtoprocess.html new file mode 100644 index 000000000000..207a061ba5f8 --- /dev/null +++ b/image/test/mochitest/test_net_failedtoprocess.html @@ -0,0 +1,48 @@ + + + + + Test for image net:failed-to-process-uri + + + + + +

+
+
+
+ + + + diff --git a/js/src/configure.in b/js/src/configure.in index f72e69815371..8072423e7431 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -2225,7 +2225,6 @@ ia64*-hpux*) PKG_SKIP_STRIP=1 XARGS=xargs DOXYGEN=: - GARBAGE='$(OBJDIR)/vc20.pdb $(OBJDIR)/vc40.pdb' ASM_SUFFIX=asm OBJ_SUFFIX=obj LIB_SUFFIX=lib diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 788f4261f452..93d99cddedc1 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4104,7 +4104,7 @@ JS_SetElement(JSContext *cx, JSObject *obj, uint32_t index, jsval *vp) { AssertNoGC(cx); CHECK_REQUEST(cx); - assertSameCompartment(cx, obj); + assertSameCompartment(cx, obj, *vp); JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING); return obj->setElement(cx, index, vp, false); } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 8db5282356d5..4f42e46d8160 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1001,29 +1001,14 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t size += length * sizeof(jsbytecode) + nsrcnotes * sizeof(jssrcnote); - uint8_t *data = NULL; -#if JS_SCRIPT_INLINE_DATA_LIMIT - if (size <= JS_SCRIPT_INLINE_DATA_LIMIT) { - /* - * Check that if inlineData is big enough to store const values, we - * can do that without any special alignment requirements given that - * the script as a GC thing is always aligned on Cell::CellSize. - */ - JS_STATIC_ASSERT(Cell::CellSize % sizeof(Value) == 0); - JS_STATIC_ASSERT(JS_SCRIPT_INLINE_DATA_LIMIT < sizeof(Value) || - offsetof(JSScript, inlineData) % sizeof(Value) == 0); - } else -#endif - { - /* - * We assume that calloc aligns on sizeof(Value) if the size we ask to - * allocate divides sizeof(Value). - */ - JS_STATIC_ASSERT(sizeof(Value) == sizeof(double)); - data = static_cast(cx->calloc_(JS_ROUNDUP(size, sizeof(Value)))); - if (!data) - return NULL; - } + /* + * We assume that calloc aligns on sizeof(Value) if the size we ask to + * allocate divides sizeof(Value). + */ + JS_STATIC_ASSERT(sizeof(Value) == sizeof(double)); + uint8_t *data = static_cast(cx->calloc_(JS_ROUNDUP(size, sizeof(Value)))); + if (!data) + return NULL; JSScript *script = js_NewGCScript(cx); if (!script) { @@ -1034,10 +1019,6 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t PodZero(script); #ifdef JS_CRASH_DIAGNOSTICS script->cookie1[0] = script->cookie2[0] = JS_SCRIPT_COOKIE; -#endif -#if JS_SCRIPT_INLINE_DATA_LIMIT - if (!data) - data = script->inlineData; #endif script->data = data; script->length = length; @@ -1325,11 +1306,6 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce) size_t JSScript::computedSizeOfData() { -#if JS_SCRIPT_INLINE_DATA_LIMIT - if (data == inlineData) - return 0; -#endif - uint8_t *dataEnd = code + length * sizeof(jsbytecode) + numNotes() * sizeof(jssrcnote); JS_ASSERT(dataEnd >= data); return dataEnd - data; @@ -1338,11 +1314,6 @@ JSScript::computedSizeOfData() size_t JSScript::sizeOfData(JSMallocSizeOfFun mallocSizeOf) { -#if JS_SCRIPT_INLINE_DATA_LIMIT - if (data == inlineData) - return 0; -#endif - return mallocSizeOf(data); } @@ -1436,13 +1407,8 @@ JSScript::finalize(JSContext *cx, bool background) cx->free_(debug); } -#if JS_SCRIPT_INLINE_DATA_LIMIT - if (data != inlineData) -#endif - { - JS_POISON(data, 0xdb, computedSizeOfData()); - cx->free_(data); - } + JS_POISON(data, 0xdb, computedSizeOfData()); + cx->free_(data); } namespace js { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index a1db414c403e..deccce65b90a 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -452,16 +452,6 @@ struct JSScript : public js::gc::Cell uint16_t nClosedArgs; /* number of args which are closed over. */ uint16_t nClosedVars; /* number of vars which are closed over. */ - /* - * To ensure sizeof(JSScript) % gc::Cell::CellSize == 0 on we must pad - * the script with 4 bytes. We use them to store tiny scripts like empty - * scripts. - */ -#if JS_BITS_PER_WORD == 64 -#define JS_SCRIPT_INLINE_DATA_LIMIT 4 - uint8_t inlineData[JS_SCRIPT_INLINE_DATA_LIMIT]; -#endif - const char *filename; /* source filename or null */ JSAtom **atoms; /* maps immediate index to literal struct */ private: diff --git a/js/xpconnect/public/Makefile.in b/js/xpconnect/public/Makefile.in index ab1afea4b7b9..ded8d99b62cd 100644 --- a/js/xpconnect/public/Makefile.in +++ b/js/xpconnect/public/Makefile.in @@ -49,6 +49,7 @@ EXPORTS = \ nsAXPCNativeCallContext.h \ xpc_map_end.h \ nsAutoJSValHolder.h \ + nsTArrayHelpers.h \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/js/xpconnect/public/nsTArrayHelpers.h b/js/xpconnect/public/nsTArrayHelpers.h new file mode 100644 index 000000000000..1a5be9ece7ee --- /dev/null +++ b/js/xpconnect/public/nsTArrayHelpers.h @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __NSTARRAYHELPERS_H__ +#define __NSTARRAYHELPERS_H__ + +template +inline nsresult +nsTArrayToJSArray(JSContext* aCx, const nsTArray& aSourceArray, + JSObject** aResultArray) +{ + MOZ_ASSERT(aCx); + JSAutoRequest ar(aCx); + + JSObject* arrayObj = JS_NewArrayObject(aCx, aSourceArray.Length(), nsnull); + if (!arrayObj) { + NS_WARNING("JS_NewArrayObject failed!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + JSObject* global = JS_GetGlobalForScopeChain(aCx); + MOZ_ASSERT(global); + + for (PRUint32 index = 0; index < aSourceArray.Length(); index++) { + nsCOMPtr obj; + nsresult rv = CallQueryInterface(aSourceArray[index], getter_AddRefs(obj)); + NS_ENSURE_SUCCESS(rv, rv); + + jsval wrappedVal; + rv = nsContentUtils::WrapNative(aCx, global, obj, &wrappedVal, nsnull, true); + NS_ENSURE_SUCCESS(rv, rv); + + if (!JS_SetElement(aCx, arrayObj, index, &wrappedVal)) { + NS_WARNING("JS_SetElement failed!"); + return NS_ERROR_FAILURE; + } + } + + if (!JS_FreezeObject(aCx, arrayObj)) { + NS_WARNING("JS_FreezeObject failed!"); + return NS_ERROR_FAILURE; + } + + *aResultArray = arrayObj; + return NS_OK; +} + +#endif /* __NSTARRAYHELPERS_H__ */ diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index d7ab8aecfc1b..028e4a6a69a6 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -145,7 +145,7 @@ pref("browser.sessionhistory.max_entries", 50); /* session store */ pref("browser.sessionstore.resume_session_once", false); -pref("browser.sessionstore.resume_from_crash", true); +pref("browser.sessionstore.resume_from_crash", false); pref("browser.sessionstore.resume_from_crash_timeout", 60); // minutes pref("browser.sessionstore.interval", 10000); // milliseconds pref("browser.sessionstore.max_tabs_undo", 1); diff --git a/mobile/android/base/Favicons.java b/mobile/android/base/Favicons.java index 5dbc0338b63d..91bd2f8813d7 100644 --- a/mobile/android/base/Favicons.java +++ b/mobile/android/base/Favicons.java @@ -295,17 +295,7 @@ public class Favicons { image = (BitmapDrawable) Drawable.createFromStream(byteStream, "src"); } } catch (Exception e) { - // Trying to read icons from nested jar files will fail - if (mFaviconUrl.startsWith("jar:jar:")) { - InputStream stream = GeckoJarReader.getStream(mFaviconUrl); - if (stream != null) { - image = new BitmapDrawable(stream); - } else { - Log.d(LOGTAG, "Error getting favicon from jar: " + e); - } - } else { - Log.d(LOGTAG, "Error downloading favicon: " + e); - } + Log.e(LOGTAG, "Error downloading favicon", e); } finally { if (urlConnection != null && urlConnection instanceof HttpURLConnection) { HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index e578b5fcd5f2..5368066c7178 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -453,10 +453,10 @@ abstract public class GeckoApp forward.setEnabled(tab.canDoForward()); - // Disable share menuitem for about:, chrome: and file: URIs + // Disable share menuitem for about:, chrome:, file:, and resource: URIs String scheme = Uri.parse(tab.getURL()).getScheme(); share.setEnabled(!(scheme.equals("about") || scheme.equals("chrome") || - scheme.equals("file"))); + scheme.equals("file") || scheme.equals("resource"))); // Disable save as PDF for about:home and xul pages saveAsPDF.setEnabled(!(tab.getURL().equals("about:home") || @@ -1677,9 +1677,10 @@ abstract public class GeckoApp if (uri != null && uri.length() > 0) passedUri = mLastTitle = uri; + mRestoreSession |= getProfile().shouldRestoreSession(); if (passedUri == null || passedUri.equals("about:home")) { // show about:home if we aren't restoring previous session - if (! getProfile().hasSession()) { + if (!mRestoreSession) { mBrowserToolbar.updateTabCount(1); showAboutHome(); } @@ -1742,9 +1743,15 @@ abstract public class GeckoApp * run experience, perhaps? */ mLayerController = new LayerController(this); - mPlaceholderLayerClient = new PlaceholderLayerClient(mLayerController, mLastViewport); + View v = mLayerController.getView(); - mGeckoLayout.addView(mLayerController.getView(), 0); + mPlaceholderLayerClient = new PlaceholderLayerClient(mLayerController, mLastViewport); + if (!mPlaceholderLayerClient.loadScreenshot()) { + // Instead of flickering the checkerboard, show a white screen until Gecko paints + v.setBackgroundColor(Color.WHITE); + } + + mGeckoLayout.addView(v, 0); } mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container); diff --git a/mobile/android/base/GeckoJarReader.java b/mobile/android/base/GeckoJarReader.java index 15f764dac984..8c09835b9821 100644 --- a/mobile/android/base/GeckoJarReader.java +++ b/mobile/android/base/GeckoJarReader.java @@ -26,11 +26,12 @@ public class GeckoJarReader { Stack jarUrls = parseUrl(url); ZipInputStream inputStream = null; + ZipFile zip = null; try { // Load the initial jar file as a zip URL fileUrl = new URL(jarUrls.pop()); File file = new File(fileUrl.getPath()); - ZipFile zip = new ZipFile(file); + zip = new ZipFile(file); ZipEntry entry = null; // loop through children jar files until we reach the innermost one @@ -63,6 +64,14 @@ public class GeckoJarReader { Log.e(LOGTAG, "Exception ", ex); } catch (Exception ex) { Log.e(LOGTAG, "Exception ", ex); + } finally { + if (zip != null) { + try { + zip.close(); + } catch(IOException ex) { + Log.e(LOGTAG, "Error closing zip", ex); + } + } } return inputStream; diff --git a/mobile/android/base/GeckoProfile.java b/mobile/android/base/GeckoProfile.java index 5e141d0d6888..f58a0de24d66 100644 --- a/mobile/android/base/GeckoProfile.java +++ b/mobile/android/base/GeckoProfile.java @@ -33,6 +33,9 @@ public final class GeckoProfile { private File mMozDir; private File mDir; + // this short timeout is a temporary fix until bug 735399 is implemented + private static final long SESSION_TIMEOUT = 30 * 1000; // 30 seconds + public static GeckoProfile get(Context context) { return get(context, null); } @@ -80,12 +83,19 @@ public final class GeckoProfile { return mDir; } - public boolean hasSession() { + public boolean shouldRestoreSession() { Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - start check sessionstore.js exists"); File dir = getDir(); - boolean hasSession = (dir != null && new File(dir, "sessionstore.js").exists()); + if (dir == null) + return false; + + File sessionFile = new File(dir, "sessionstore.js"); + if (!sessionFile.exists()) + return false; + + boolean shouldRestore = (System.currentTimeMillis() - sessionFile.lastModified() < SESSION_TIMEOUT); Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - finish check sessionstore.js exists"); - return hasSession; + return shouldRestore; } public String readSessionFile(boolean geckoReady) { diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 6d44e12a0a6b..d7eaa392ea43 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -97,6 +97,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { Bookmarks.TITLE, Bookmarks.TYPE, Bookmarks.PARENT, + Bookmarks.KEYWORD, Bookmarks.FAVICON }; public LocalBrowserDB(String profile) { diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index eed593ebf961..4bfdc636bf90 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -367,6 +367,7 @@ public class GeckoLayerClient implements GeckoEventResponder, // a full viewport update, which is fine because if browser.js has somehow moved to // be out of sync with this first-paint viewport, then we force them back in sync. mLayerController.abortPanZoomAnimation(); + mLayerController.getView().setPaintState(LayerView.PAINT_BEFORE_FIRST); } } diff --git a/mobile/android/base/gfx/LayerRenderer.java b/mobile/android/base/gfx/LayerRenderer.java index 3d6249d74e24..76718b1bb399 100644 --- a/mobile/android/base/gfx/LayerRenderer.java +++ b/mobile/android/base/gfx/LayerRenderer.java @@ -680,6 +680,16 @@ public class LayerRenderer implements GLSurfaceView.Renderer { pixelBuffer.notify(); } } + + // Remove white screen once we've painted + if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) { + GeckoAppShell.getMainHandler().postAtFrontOfQueue(new Runnable() { + public void run() { + mView.setBackgroundColor(android.graphics.Color.TRANSPARENT); + } + }); + mView.setPaintState(LayerView.PAINT_AFTER_FIRST); + } } } } diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index 17367598a096..c11f48d3ca70 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -38,6 +38,7 @@ package org.mozilla.gecko.gfx; +import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoInputConnection; import org.mozilla.gecko.gfx.FloatSize; import org.mozilla.gecko.gfx.InputConnectionHandler; @@ -77,6 +78,14 @@ public class LayerView extends FlexibleGLSurfaceView { private static String LOGTAG = "GeckoLayerView"; /* List of events to be processed if the page does not prevent them. Should only be touched on the main thread */ private LinkedList mEventQueue = new LinkedList(); + /* Must be a PAINT_xxx constant */ + private int mPaintState = PAINT_NONE; + + /* Flags used to determine when to show the painted surface. The integer + * order must correspond to the order in which these states occur. */ + public static final int PAINT_NONE = 0; + public static final int PAINT_BEFORE_FIRST = 1; + public static final int PAINT_AFTER_FIRST = 2; public LayerView(Context context, LayerController controller) { @@ -237,5 +246,18 @@ public class LayerView extends FlexibleGLSurfaceView { public LayerRenderer getLayerRenderer() { return mRenderer; } + + /* paintState must be a PAINT_xxx constant. The state will only be changed + * if paintState represents a state that occurs after the current state. */ + public void setPaintState(int paintState) { + if (paintState > mPaintState) { + Log.d(LOGTAG, "LayerView paint state set to " + paintState); + mPaintState = paintState; + } + } + + public int getPaintState() { + return mPaintState; + } } diff --git a/mobile/android/base/gfx/PlaceholderLayerClient.java b/mobile/android/base/gfx/PlaceholderLayerClient.java index 17290eb25d2d..286549de2b41 100644 --- a/mobile/android/base/gfx/PlaceholderLayerClient.java +++ b/mobile/android/base/gfx/PlaceholderLayerClient.java @@ -79,25 +79,6 @@ public class PlaceholderLayerClient { } else { mViewport = new ViewportMetrics(); } - loadScreenshot(); - - - if (mViewportUnknown) - mViewport.setViewport(mLayerController.getViewport()); - mLayerController.setViewportMetrics(mViewport); - - BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat); - SingleTileLayer tileLayer = new SingleTileLayer(image); - - tileLayer.beginTransaction(); // calling thread irrelevant; nobody else has a ref to tileLayer yet - try { - Point origin = PointUtils.round(mViewport.getOrigin()); - tileLayer.setPosition(new Rect(origin.x, origin.y, origin.x + mWidth, origin.y + mHeight)); - } finally { - tileLayer.endTransaction(); - } - - mLayerController.setRoot(tileLayer); } public void destroy() { @@ -107,7 +88,7 @@ public class PlaceholderLayerClient { } } - boolean loadScreenshot() { + public boolean loadScreenshot() { if (GeckoApp.mAppContext.mLastScreen == null) return false; @@ -131,6 +112,18 @@ public class PlaceholderLayerClient { mLayerController.setPageSize(mViewport.getPageSize()); } + BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat); + SingleTileLayer tileLayer = new SingleTileLayer(image); + + tileLayer.beginTransaction(); // calling thread irrelevant; nobody else has a ref to tileLayer yet + try { + Point origin = PointUtils.round(mViewport.getOrigin()); + tileLayer.setPosition(new Rect(origin.x, origin.y, origin.x + mWidth, origin.y + mHeight)); + } finally { + tileLayer.endTransaction(); + } + + mLayerController.setRoot(tileLayer); return true; } } diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 463d6f35b51d..766e1239b57d 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -327,7 +327,7 @@ var BrowserApp = { }); if (this.isAppUpdated()) - this.onUpdate(); + this.onAppUpdated(); }, isAppUpdated: function() { @@ -1455,9 +1455,10 @@ function Tab(aURL, aParams) { this.create(aURL, aParams); this._zoom = 1.0; this.userScrollPos = { x: 0, y: 0 }; - this._pluginCount = 0; - this._pluginOverlayShowing = false; this.contentDocumentIsDisplayed = true; + this.clickToPlayPluginDoorhangerShown = false; + this.clickToPlayPluginsActivated = false; + this.loadEventProcessed = false; } Tab.prototype = { @@ -1511,6 +1512,7 @@ Tab.prototype = { this.browser.sessionHistory.addSHistoryListener(this); this.browser.addEventListener("DOMContentLoaded", this, true); + this.browser.addEventListener("load", this, true); this.browser.addEventListener("DOMLinkAdded", this, true); this.browser.addEventListener("DOMTitleChanged", this, true); this.browser.addEventListener("DOMWindowClose", this, true); @@ -1518,8 +1520,6 @@ Tab.prototype = { this.browser.addEventListener("scroll", this, true); this.browser.addEventListener("MozScrolledAreaChanged", this, true); this.browser.addEventListener("PluginClickToPlay", this, true); - this.browser.addEventListener("pagehide", this, true); - this.browser.addEventListener("pageshow", this, true); Services.obs.addObserver(this, "before-first-paint", false); @@ -1558,6 +1558,7 @@ Tab.prototype = { this.browser.removeProgressListener(this); this.browser.removeEventListener("DOMContentLoaded", this, true); + this.browser.removeEventListener("load", this, true); this.browser.removeEventListener("DOMLinkAdded", this, true); this.browser.removeEventListener("DOMTitleChanged", this, true); this.browser.removeEventListener("DOMWindowClose", this, true); @@ -1565,8 +1566,6 @@ Tab.prototype = { this.browser.removeEventListener("scroll", this, true); this.browser.removeEventListener("PluginClickToPlay", this, true); this.browser.removeEventListener("MozScrolledAreaChanged", this, true); - this.browser.removeEventListener("pagehide", this, true); - this.browser.removeEventListener("pageshow", this, true); Services.obs.removeObserver(this, "before-first-paint"); @@ -1763,13 +1762,25 @@ Tab.prototype = { this.browser.removeEventListener("pagehide", listener, true); }.bind(this), true); } + break; + } - // Show a plugin doorhanger if there are plugins on the page but no - // clickable overlays showing (this doesn't work on pages loaded after - // back/forward navigation - see bug 719875) - if (this._pluginCount && !this._pluginOverlayShowing) + case "load": { + this.loadEventProcessed = true; + // Show a plugin doorhanger if there are no clickable overlays showing + let contentWindow = this.browser.contentWindow; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // XXX not sure if we should enable plugins for the parent documents... + let plugins = cwu.plugins; + let isAnyPluginVisible = false; + for (let plugin of plugins) { + let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); + if (overlay && !PluginHelper.isTooSmall(plugin, overlay)) + isAnyPluginVisible = true; + } + if (plugins && plugins.length && !isAnyPluginVisible) PluginHelper.showDoorHanger(this); - break; } @@ -1877,39 +1888,36 @@ Tab.prototype = { } case "PluginClickToPlay": { - // Keep track of the number of plugins to know whether or not to show - // the hidden plugins doorhanger - this._pluginCount++; - let plugin = aEvent.target; - let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); - if (!overlay) + + if (this.clickToPlayPluginsActivated) { + PluginHelper.playPlugin(plugin); return; + } // If the overlay is too small, hide the overlay and act like this // is a hidden plugin object - if (PluginHelper.isTooSmall(plugin, overlay)) { - overlay.style.visibility = "hidden"; + let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); + if (!overlay || PluginHelper.isTooSmall(plugin, overlay)) { + if (overlay) + overlay.style.visibility = "hidden"; + if (this.loadEventProcessed && !this.clickToPlayPluginDoorhangerShown) + PluginHelper.showDoorHanger(this); return; } // Add click to play listener to the overlay - overlay.addEventListener("click", (function(event) { - // Play all the plugin objects when the user clicks on one - PluginHelper.playAllPlugins(this, event); - }).bind(this), true); - - this._pluginOverlayShowing = true; - break; - } - - case "pagehide": { - // Check to make sure it's top-level pagehide - if (aEvent.target.defaultView == this.browser.contentWindow) { - // Reset plugin state when we leave the page - this._pluginCount = 0; - this._pluginOverlayShowing = false; - } + overlay.addEventListener("click", function(e) { + if (e) { + if (!e.isTrusted) + return; + e.preventDefault(); + } + let win = e.target.ownerDocument.defaultView.top; + let tab = BrowserApp.getTabForWindow(win); + tab.clickToPlayPluginsActivated = true; + PluginHelper.playAllPlugins(win); + }, true); break; } } @@ -1969,6 +1977,11 @@ Tab.prototype = { contentType = browser.contentDocument.contentType; } + // Reset state of click-to-play plugin notifications. + this.clickToPlayPluginDoorhangerShown = false; + this.clickToPlayPluginsActivated = false; + this.loadEventProcessed = false; + let message = { gecko: { type: "Content:LocationChange", @@ -3854,12 +3867,13 @@ var ClipboardHelper = { var PluginHelper = { showDoorHanger: function(aTab) { + aTab.clickToPlayPluginDoorhangerShown = true; let message = Strings.browser.GetStringFromName("clickToPlayPlugins.message"); let buttons = [ { label: Strings.browser.GetStringFromName("clickToPlayPlugins.yes"), callback: function() { - PluginHelper.playAllPlugins(aTab); + PluginHelper.playAllPlugins(aTab.browser.contentWindow); } }, { @@ -3872,41 +3886,21 @@ var PluginHelper = { NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id); }, - playAllPlugins: function(aTab, aEvent) { - if (aEvent) { - if (!aEvent.isTrusted) - return; - aEvent.preventDefault(); - } + playAllPlugins: function(aContentWindow) { + let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // XXX not sure if we should enable plugins for the parent documents... + let plugins = cwu.plugins; + if (!plugins || !plugins.length) + return; - this._findAndPlayAllPlugins(aTab.browser.contentWindow); + plugins.forEach(this.playPlugin); }, - // Helper function that recurses through sub-frames to find all plugin objects - _findAndPlayAllPlugins: function _findAndPlayAllPlugins(aWindow) { - let embeds = aWindow.document.getElementsByTagName("embed"); - for (let i = 0; i < embeds.length; i++) { - if (!embeds[i].hasAttribute("played")) - this._playPlugin(embeds[i]); - } - - let objects = aWindow.document.getElementsByTagName("object"); - for (let i = 0; i < objects.length; i++) { - if (!objects[i].hasAttribute("played")) - this._playPlugin(objects[i]); - } - - for (let i = 0; i < aWindow.frames.length; i++) { - this._findAndPlayAllPlugins(aWindow.frames[i]); - } - }, - - _playPlugin: function _playPlugin(aPlugin) { - let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); - objLoadingContent.playPlugin(); - - // Set an attribute on the plugin object to avoid re-loading it - aPlugin.setAttribute("played", true); + playPlugin: function(plugin) { + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (!objLoadingContent.activated) + objLoadingContent.playPlugin(); }, getPluginPreference: function getPluginPreference() { diff --git a/mobile/android/themes/core/aboutAddons.css b/mobile/android/themes/core/aboutAddons.css index 3eb844cdf04d..2c596a0a6f74 100644 --- a/mobile/android/themes/core/aboutAddons.css +++ b/mobile/android/themes/core/aboutAddons.css @@ -205,6 +205,10 @@ setting > vbox { font-weight: bold; } +.preferences-description { + margin-top: 4px; +} + .preferences-description:empty { display: none; } @@ -217,7 +221,7 @@ setting[type="string"] { } .preferences-alignment > textbox { - margin-top: 12px; + margin: 12px 0 0 0; font-size: 22px !important; } @@ -225,16 +229,16 @@ checkbox { -moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox-with-spacing") !important; } +checkbox[label=""] > .checkbox-label-box, +checkbox:not([label]) > .checkbox-label-box { + display: none; +} + .checkbox-check { - border: 2px transparent; - -moz-border-top-colors: -moz-initial; - -moz-border-right-colors: -moz-initial; - -moz-border-bottom-colors: -moz-initial; - -moz-border-left-colors: -moz-initial; - -moz-box-sizing: border-box; + background: url("chrome://browser/skin/images/checkbox_unchecked.png") no-repeat 50% 50%; + border: 0 transparent; width: 48px; height: 48px; - background: url("chrome://browser/skin/images/checkbox_unchecked.png") no-repeat 50% 50%; } setting:active checkbox > .checkbox-spacer-box > .checkbox-check { @@ -257,6 +261,62 @@ checkbox[checked="true"][disabled="true"] > .checkbox-spacer-box > .checkbox-che background-image: url("chrome://browser/skin/images/checkbox_checked_disabled.png"); } +/* Textbox */ + +textbox[type="number"] > spinbuttons { + visibility: collapse; +} + +textbox { + background: white -moz-linear-gradient(top, rgba(27,113,177,0.5) 0, rgba(198,225,246,0.2) 3px, rgba(255,255,255,0.2) 16px); + border-radius: 3px; + border-color: rgb(94,128,153); + padding: 0 !important; +} + +.textbox-input-box { + padding: 8px 12px; +} + +/* Menulist */ + +menulist { + -moz-appearance: none !important; + -moz-user-focus: ignore; + /* min-width: 200px !important; */ + color: #000 !important; + border-radius: 5px; + border-color: rgb(94,128,153); + border-style: solid; + padding: 8px 12px; + background: white; + border: 1px solid #cacdd5; + border-style: solid; + border-color: rgb(94,128,153); + min-width: 200px; +} + +.menulist-label { + background-color: transparent !important; +} + +menulist > menupopup > menuitem > label{ +-moz-padding-start:3px !important; +-moz-padding-end:7px !important; +} + +menulist > dropmarker { + height: 32px; + width: 32px; + margin-left: @margin_snormal@; + background-color: transparent; /* for windows */ + border: none; /* for windows */ + -moz-box-align: center; + -moz-box-pack: center; + list-style-image: url("chrome://browser/skin/images/dropmarker.svg") !important; + -moz-image-region: auto; + display: block; +} /* XBL bindings */ diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index e73b5ac4af51..7d4478ca4a55 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -809,11 +809,23 @@ pref("network.http.pipelining.ssl" , false); // disable pipelining over SSL pref("network.http.proxy.pipelining", false); // Max number of requests in the pipeline -pref("network.http.pipelining.maxrequests" , 4); +pref("network.http.pipelining.maxrequests" , 32); + +// An optimistic request is one pipelined when policy might allow a new +// connection instead +pref("network.http.pipelining.max-optimistic-requests" , 4); + +pref("network.http.pipelining.aggressive", false); +pref("network.http.pipelining.maxsize" , 300000); +pref("network.http.pipelining.read-timeout", 10000); // Prompt for 307 redirects pref("network.http.prompt-temp-redirect", true); +// If true generate CORRUPTED_CONTENT errors for entities that +// contain an invalid Assoc-Req response header +pref("network.http.assoc-req.enforce", false); + // On networks deploying QoS, it is recommended that these be lockpref()'d, // since inappropriate marking can easily overwhelm bandwidth reservations // for certain services (i.e. EF for VoIP, AF4x for interactive video, diff --git a/netwerk/base/public/nsIRequest.idl b/netwerk/base/public/nsIRequest.idl index f04d9d2b3297..455d16b4fda4 100644 --- a/netwerk/base/public/nsIRequest.idl +++ b/netwerk/base/public/nsIRequest.idl @@ -159,6 +159,13 @@ interface nsIRequest : nsISupports * The following flags control the flow of data into the cache. */ + /** + * This flag prevents loading of the request with an HTTP pipeline. + * Generally this is because the resource is expected to take a + * while to load and may cause head of line blocking problems. + */ + const unsigned long INHIBIT_PIPELINE = 1 << 6; + /** * This flag prevents caching of any kind. It does not, however, prevent * cached content from being used to satisfy this request. diff --git a/netwerk/cache/nsCacheService.cpp b/netwerk/cache/nsCacheService.cpp index 8c85b6801d73..cf2509a2ca27 100644 --- a/netwerk/cache/nsCacheService.cpp +++ b/netwerk/cache/nsCacheService.cpp @@ -1020,7 +1020,8 @@ public: nsnull); // Don't delete the request if it was queued - if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) + if (!(mRequest->IsBlocking() && + rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)) delete mRequest; return NS_OK; @@ -1033,6 +1034,91 @@ private: nsCacheRequest *mRequest; }; +/****************************************************************************** + * nsNotifyDoomListener + *****************************************************************************/ + +class nsNotifyDoomListener : public nsRunnable { +public: + nsNotifyDoomListener(nsICacheListener *listener, + nsresult status) + : mListener(listener) // transfers reference + , mStatus(status) + {} + + NS_IMETHOD Run() + { + mListener->OnCacheEntryDoomed(mStatus); + NS_RELEASE(mListener); + return NS_OK; + } + +private: + nsICacheListener *mListener; + nsresult mStatus; +}; + +/****************************************************************************** + * nsDoomEvent + *****************************************************************************/ + +class nsDoomEvent : public nsRunnable { +public: + nsDoomEvent(nsCacheSession *session, + const nsACString &key, + nsICacheListener *listener) + { + mKey = *session->ClientID(); + mKey.Append(':'); + mKey.Append(key); + mStoragePolicy = session->StoragePolicy(); + mListener = listener; + mThread = do_GetCurrentThread(); + // We addref the listener here and release it in nsNotifyDoomListener + // on the callers thread. If posting of nsNotifyDoomListener event fails + // we leak the listener which is better than releasing it on a wrong + // thread. + NS_IF_ADDREF(mListener); + } + + NS_IMETHOD Run() + { + nsCacheServiceAutoLock lock; + + bool foundActive = true; + nsresult status = NS_ERROR_NOT_AVAILABLE; + nsCacheEntry *entry; + entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey); + if (!entry) { + bool collision = false; + foundActive = false; + entry = nsCacheService::gService->SearchCacheDevices(&mKey, + mStoragePolicy, + &collision); + } + + if (entry) { + status = NS_OK; + nsCacheService::gService->DoomEntry_Internal(entry, foundActive); + } + + if (mListener) { + mThread->Dispatch(new nsNotifyDoomListener(mListener, status), + NS_DISPATCH_NORMAL); + // posted event will release the reference on the correct thread + mListener = nsnull; + } + + return NS_OK; + } + +private: + nsCString mKey; + nsCacheStoragePolicy mStoragePolicy; + nsICacheListener *mListener; + nsCOMPtr mThread; +}; + /****************************************************************************** * nsCacheService *****************************************************************************/ @@ -1349,6 +1435,22 @@ nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, } +nsresult +nsCacheService::DoomEntry(nsCacheSession *session, + const nsACString &key, + nsICacheListener *listener) +{ + CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n", + session, PromiseFlatCString(key).get())); + NS_ASSERTION(gService, "nsCacheService::gService is null."); + + if (!gService->mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener)); +} + + bool nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy) { @@ -1662,11 +1764,13 @@ nsCacheService::ProcessRequest(nsCacheRequest * request, // entry->RequestAccess queues request on entry rv = entry->RequestAccess(request, &accessGranted); if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break; - - if (request->mListener) // async exits - validate, doom, or close will resume - return rv; - + if (request->IsBlocking()) { + if (request->mListener) { + // async exits - validate, doom, or close will resume + return rv; + } + // XXX this is probably wrong... Unlock(); rv = request->WaitForValidation(); @@ -1773,7 +1877,8 @@ nsCacheService::OpenCacheEntry(nsCacheSession * session, rv = gService->ProcessRequest(request, true, result); // delete requests that have completed - if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))) + if (!(listener && blockingMode && + (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))) delete request; } diff --git a/netwerk/cache/nsCacheService.h b/netwerk/cache/nsCacheService.h index 77102eca8449..3d141fbad8b5 100644 --- a/netwerk/cache/nsCacheService.h +++ b/netwerk/cache/nsCacheService.h @@ -99,6 +99,10 @@ public: static nsresult IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy, bool * result); + static nsresult DoomEntry(nsCacheSession *session, + const nsACString &key, + nsICacheListener *listener); + /** * Methods called by nsCacheEntryDescriptor */ @@ -198,6 +202,7 @@ private: friend class nsSetSmartSizeEvent; friend class nsBlockOnCacheThreadEvent; friend class nsSetDiskSmartSizeCallback; + friend class nsDoomEvent; /** * Internal Methods diff --git a/netwerk/cache/nsCacheSession.cpp b/netwerk/cache/nsCacheSession.cpp index 744e8de5221e..fb4e3b0936b3 100644 --- a/netwerk/cache/nsCacheSession.cpp +++ b/netwerk/cache/nsCacheSession.cpp @@ -102,13 +102,14 @@ nsCacheSession::OpenCacheEntry(const nsACString & key, NS_IMETHODIMP nsCacheSession::AsyncOpenCacheEntry(const nsACString & key, nsCacheAccessMode accessRequested, - nsICacheListener *listener) + nsICacheListener *listener, + bool noWait) { nsresult rv; rv = nsCacheService::OpenCacheEntry(this, key, accessRequested, - nsICache::BLOCKING, + !noWait, listener, nsnull); // no result @@ -127,3 +128,9 @@ NS_IMETHODIMP nsCacheSession::IsStorageEnabled(bool *result) return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result); } + +NS_IMETHODIMP nsCacheSession::DoomEntry(const nsACString &key, + nsICacheListener *listener) +{ + return nsCacheService::DoomEntry(this, key, listener); +} diff --git a/netwerk/cache/nsICacheListener.idl b/netwerk/cache/nsICacheListener.idl index 7d8c60398f24..d22469e25e73 100644 --- a/netwerk/cache/nsICacheListener.idl +++ b/netwerk/cache/nsICacheListener.idl @@ -47,7 +47,7 @@ interface nsICacheEntryDescriptor; -[scriptable, uuid(638c3848-778b-4851-8ff3-9400f65b8773)] +[scriptable, uuid(8eadf2ed-8cac-4961-8025-6da6d5827e74)] interface nsICacheListener : nsISupports { /** @@ -58,4 +58,11 @@ interface nsICacheListener : nsISupports void onCacheEntryAvailable(in nsICacheEntryDescriptor descriptor, in nsCacheAccessMode accessGranted, in nsresult status); + + /** + * Called when nsCacheSession::DoomEntry() is completed. The status + * parameter is NS_OK when the entry was doomed, or NS_ERROR_NOT_AVAILABLE + * when there is no such entry. + */ + void onCacheEntryDoomed(in nsresult status); }; diff --git a/netwerk/cache/nsICacheSession.idl b/netwerk/cache/nsICacheSession.idl index 0242c79b960b..d49f23c9ddde 100644 --- a/netwerk/cache/nsICacheSession.idl +++ b/netwerk/cache/nsICacheSession.idl @@ -46,7 +46,7 @@ interface nsICacheEntryDescriptor; interface nsICacheListener; -[scriptable, uuid(ae9e84b5-3e2d-457e-8fcd-5bbd2a8b832e)] +[scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)] interface nsICacheSession : nsISupports { /** @@ -76,13 +76,17 @@ interface nsICacheSession : nsISupports in boolean blockingMode); /** - * Asynchronous cache access. Does not block the calling thread. - * Instead, the listener will be notified when the descriptor is - * available. + * Asynchronous cache access. Does not block the calling thread. Instead, + * the listener will be notified when the descriptor is available. If + * 'noWait' is set to true, the listener will be notified immediately with + * status NS_ERROR_CACHE_WAIT_FOR_VALIDATION rather than queuing the request + * when another descriptor has been given WRITE access but hasn't validated + * the entry yet. */ - void asyncOpenCacheEntry(in ACString key, - in nsCacheAccessMode accessRequested, - in nsICacheListener listener); + void asyncOpenCacheEntry(in ACString key, + in nsCacheAccessMode accessRequested, + in nsICacheListener listener, + [optional] in boolean noWait); /** * Evict all entries for this session's clientID according to its storagePolicy. @@ -94,4 +98,11 @@ interface nsICacheSession : nsISupports * are currently enabled for instantiation if they don't already exist. */ boolean isStorageEnabled(); + + /** + * Asynchronously doom an entry specified by the key. Listener will be + * notified about the status of the operation. Null may be passed if caller + * doesn't care about the result. + */ + void doomEntry(in ACString key, in nsICacheListener listener); }; diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp index 78d34a7150ad..32dde15ef03f 100644 --- a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -2095,6 +2095,14 @@ nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, //----------------------------------------------------------------------------- +NS_IMETHODIMP +nsFtpState::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------------------------- + NS_IMETHODIMP nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context) { @@ -2288,7 +2296,7 @@ nsFtpState::CheckCache() } if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { - rv = session->AsyncOpenCacheEntry(key, accessReq, this); + rv = session->AsyncOpenCacheEntry(key, accessReq, this, false); return NS_SUCCEEDED(rv); } diff --git a/netwerk/protocol/http/SpdySession.cpp b/netwerk/protocol/http/SpdySession.cpp index 9a816dfaea71..74d184628f7d 100644 --- a/netwerk/protocol/http/SpdySession.cpp +++ b/netwerk/protocol/http/SpdySession.cpp @@ -2034,6 +2034,30 @@ SpdySession::Transport() return mConnection->Transport(); } +PRUint32 +SpdySession::CancelPipeline(nsresult reason) +{ + // we don't pipeline inside spdy, so this isn't an issue + return 0; +} + +nsAHttpTransaction::Classifier +SpdySession::Classification() +{ + if (!mConnection) + return nsAHttpTransaction::CLASS_GENERAL; + return mConnection->Classification(); +} + +void +SpdySession::Classify(nsAHttpTransaction::Classifier newclass) +{ + if (!mConnection) + return; + + mConnection->Classify(newclass); +} + //----------------------------------------------------------------------------- // unused methods of nsAHttpTransaction // We can be sure of this because SpdySession is only constructed in @@ -2064,8 +2088,7 @@ SpdySession::SetSSLConnectFailed() bool SpdySession::IsDone() { - NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()"); - return false; + return !mStreamTransactionHash.Count(); } nsresult @@ -2075,6 +2098,13 @@ SpdySession::Status() return NS_ERROR_UNEXPECTED; } +PRUint8 +SpdySession::Caps() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Caps()"); + return 0; +} + PRUint32 SpdySession::Available() { @@ -2132,6 +2162,42 @@ SpdySession::TakeSubTransactions( return NS_OK; } +nsresult +SpdySession::AddTransaction(nsAHttpTransaction *) +{ + // This API is meant for pipelining, SpdySession's should be + // extended with AddStream() + + NS_ABORT_IF_FALSE(false, + "SpdySession::AddTransaction() should not be called"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +PRUint32 +SpdySession::PipelineDepth() +{ + return IsDone() ? 0 : 1; +} + +nsresult +SpdySession::SetPipelinePosition(PRInt32 position) +{ + // This API is meant for pipelining, SpdySession's should be + // extended with AddStream() + + NS_ABORT_IF_FALSE(false, + "SpdySession::SetPipelinePosition() should not be called"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +PRInt32 +SpdySession::PipelinePosition() +{ + return 0; +} + //----------------------------------------------------------------------------- // Pass through methods of nsAHttpConnection //----------------------------------------------------------------------------- @@ -2179,6 +2245,13 @@ SpdySession::PushBack(const char *buf, PRUint32 len) return mConnection->PushBack(buf, len); } +bool +SpdySession::IsProxyConnectInProgress() +{ + NS_ABORT_IF_FALSE(mConnection, "no connection"); + return mConnection->IsProxyConnectInProgress(); +} + bool SpdySession::LastTransactionExpectedNoContent() { diff --git a/netwerk/protocol/http/SpdySession.h b/netwerk/protocol/http/SpdySession.h index d1e9b9e729b8..5fb947bd4d8b 100644 --- a/netwerk/protocol/http/SpdySession.h +++ b/netwerk/protocol/http/SpdySession.h @@ -75,10 +75,9 @@ public: bool AddStream(nsAHttpTransaction *, PRInt32); bool CanReuse() { return !mShouldGoAway && !mClosed; } - void DontReuse(); bool RoomForMoreStreams(); - // When the connection is active this is called every 15 seconds + // When the connection is active this is called every 1 second void ReadTimeoutTick(PRIntervalTime now); // Idle time represents time since "goodput".. e.g. a data or header frame diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h index 51d39ad27866..2bbb39aeb58e 100644 --- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -39,8 +39,8 @@ #define nsAHttpConnection_h__ #include "nsISupports.h" +#include "nsAHttpTransaction.h" -class nsAHttpTransaction; class nsHttpRequestHead; class nsHttpResponseHead; class nsHttpConnectionInfo; @@ -120,13 +120,19 @@ public: // persistent... important in determining the end of a response. virtual bool IsPersistent() = 0; - // called to determine if a connection has been reused. + // called to determine or set if a connection has been reused. virtual bool IsReused() = 0; - + virtual void DontReuse() = 0; + // called by a transaction when the transaction reads more from the socket // than it should have (eg. containing part of the next pipelined response). virtual nsresult PushBack(const char *data, PRUint32 length) = 0; + // Used to determine if the connection wants read events even though + // it has not written out a transaction. Used when a connection has issued + // a preamble such as a proxy ssl CONNECT sequence. + virtual bool IsProxyConnectInProgress() = 0; + // Used by a transaction to manage the state of previous response bodies on // the same connection and work around buggy servers. virtual bool LastTransactionExpectedNoContent() = 0; @@ -139,6 +145,14 @@ public: // Get the nsISocketTransport used by the connection without changing // references or ownership. virtual nsISocketTransport *Transport() = 0; + + // Cancel and reschedule transactions deeper than the current response. + // Returns the number of canceled transactions. + virtual PRUint32 CancelPipeline(nsresult originalReason) = 0; + + // Read and write class of transaction that is carried on this connection + virtual nsAHttpTransaction::Classifier Classification() = 0; + virtual void Classify(nsAHttpTransaction::Classifier newclass) = 0; }; #define NS_DECL_NSAHTTPCONNECTION \ @@ -153,10 +167,15 @@ public: void GetSecurityInfo(nsISupports **); \ bool IsPersistent(); \ bool IsReused(); \ + void DontReuse(); \ nsresult PushBack(const char *, PRUint32); \ + bool IsProxyConnectInProgress(); \ bool LastTransactionExpectedNoContent(); \ - void SetLastTransactionExpectedNoContent(bool); \ + void SetLastTransactionExpectedNoContent(bool); \ nsHttpConnection *TakeHttpConnection(); \ - nsISocketTransport *Transport(); + nsISocketTransport *Transport(); \ + PRUint32 CancelPipeline(nsresult originalReason); \ + nsAHttpTransaction::Classifier Classification(); \ + void Classify(nsAHttpTransaction::Classifier); #endif // nsAHttpConnection_h__ diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index 4a7980d2ad48..19d5ddd6b5bd 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -48,6 +48,7 @@ class nsIInterfaceRequestor; class nsIEventTarget; class nsITransport; class nsHttpRequestHead; +class nsHttpPipeline; //---------------------------------------------------------------------------- // Abstract base class for a HTTP transaction: @@ -63,6 +64,8 @@ class nsAHttpTransaction : public nsISupports public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; + + // used to obtain the connection associated with this transaction virtual nsAHttpConnection *Connection() = 0; // called by the connection to get security callbacks to set on the @@ -77,6 +80,7 @@ public: // called to check the transaction status. virtual bool IsDone() = 0; virtual nsresult Status() = 0; + virtual PRUint8 Caps() = 0; // called to find out how much request data is available for writing. virtual PRUint32 Available() = 0; @@ -114,6 +118,49 @@ public: // virtual nsresult TakeSubTransactions( nsTArray > &outTransactions) = 0; + + // called to add a sub-transaction in the case of pipelined transactions + // classes that do not implement sub transactions + // return NS_ERROR_NOT_IMPLEMENTED + virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0; + + // The total length of the outstanding pipeline comprised of transacations + // and sub-transactions. + virtual PRUint32 PipelineDepth() = 0; + + // Used to inform the connection that it is being used in a pipelined + // context. That may influence the handling of some errors. + // The value is the pipeline position (> 1). + virtual nsresult SetPipelinePosition(PRInt32) = 0; + virtual PRInt32 PipelinePosition() = 0; + + // If we used rtti this would be the result of doing + // dynamic_cast(this).. i.e. it can be nsnull for + // non pipeline implementations of nsAHttpTransaction + virtual nsHttpPipeline *QueryPipeline() { return nsnull; } + + // Every transaction is classified into one of the types below. When using + // HTTP pipelines, only transactions with the same type appear on the same + // pipeline. + enum Classifier { + // Transactions that expect a short 304 (no-content) response + CLASS_REVALIDATION, + + // Transactions for content expected to be CSS or JS + CLASS_SCRIPT, + + // Transactions for content expected to be an image + CLASS_IMAGE, + + // Transactions that cannot involve a pipeline + CLASS_SOLO, + + // Transactions that do not fit any of the other categories. HTML + // is normally GENERAL. + CLASS_GENERAL, + + CLASS_MAX + }; }; #define NS_DECL_NSAHTTPTRANSACTION \ @@ -125,6 +172,7 @@ public: nsresult status, PRUint64 progress); \ bool IsDone(); \ nsresult Status(); \ + PRUint8 Caps(); \ PRUint32 Available(); \ nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \ nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); \ @@ -132,7 +180,11 @@ public: void SetSSLConnectFailed(); \ nsHttpRequestHead *RequestHead(); \ PRUint32 Http1xTransactionCount(); \ - nsresult TakeSubTransactions(nsTArray > &outTransactions); + nsresult TakeSubTransactions(nsTArray > &outTransactions); \ + nsresult AddTransaction(nsAHttpTransaction *); \ + PRUint32 PipelineDepth(); \ + nsresult SetPipelinePosition(PRInt32); \ + PRInt32 PipelinePosition(); //----------------------------------------------------------------------------- // nsAHttpSegmentReader diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp index d7312a2f9e18..3fabffe97849 100644 --- a/netwerk/protocol/http/nsHttp.cpp +++ b/netwerk/protocol/http/nsHttp.cpp @@ -187,6 +187,12 @@ nsHttp::DestroyAtomTable() } } +Mutex * +nsHttp::GetLock() +{ + return sLock; +} + // this function may be called from multiple threads nsHttpAtom nsHttp::ResolveAtom(const char *str) diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index 551f6e9ba9ad..197edd384506 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -140,9 +140,6 @@ typedef PRUint8 nsHttpVersion; // some default values //----------------------------------------------------------------------------- -// hard upper limit on the number of requests that can be pipelined -#define NS_HTTP_MAX_PIPELINED_REQUESTS 8 - #define NS_HTTP_DEFAULT_PORT 80 #define NS_HTTPS_DEFAULT_PORT 443 @@ -169,6 +166,11 @@ struct nsHttp static nsresult CreateAtomTable(); static void DestroyAtomTable(); + // The mutex is valid any time the Atom Table is valid + // This mutex is used in the unusual case that the network thread and + // main thread might access the same data + static mozilla::Mutex *GetLock(); + // will dynamically add atoms to the table if they don't already exist static nsHttpAtom ResolveAtom(const char *); static nsHttpAtom ResolveAtom(const nsACString &s) diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h index 58fcbb0dce80..f634479a37d5 100644 --- a/netwerk/protocol/http/nsHttpAtomList.h +++ b/netwerk/protocol/http/nsHttpAtomList.h @@ -57,6 +57,7 @@ HTTP_ATOM(Accept_Ranges, "Accept-Ranges") HTTP_ATOM(Age, "Age") HTTP_ATOM(Allow, "Allow") HTTP_ATOM(Alternate_Protocol, "Alternate-Protocol") +HTTP_ATOM(Assoc_Req, "Assoc-Req") HTTP_ATOM(Authentication, "Authentication") HTTP_ATOM(Authorization, "Authorization") HTTP_ATOM(Cache_Control, "Cache-Control") diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index cf41ddc7382e..a85885c7fd7b 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -47,6 +47,7 @@ #include "nsHttpChannel.h" #include "nsHttpHandler.h" +#include "nsStandardURL.h" #include "nsIApplicationCacheService.h" #include "nsIApplicationCacheContainer.h" #include "nsIAuthInformation.h" @@ -70,6 +71,7 @@ #include "nsDOMError.h" #include "nsAlgorithm.h" #include "sampler.h" +#include "nsIConsoleService.h" using namespace mozilla; @@ -125,7 +127,6 @@ nsHttpChannel::nsHttpChannel() , mPostID(0) , mRequestTime(0) , mOnCacheEntryAvailableCallback(nsnull) - , mAsyncCacheOpen(false) , mCachedContentIsValid(false) , mCachedContentIsPartial(false) , mTransactionReplaced(false) @@ -246,6 +247,12 @@ nsHttpChannel::Connect(bool firstTime) // open a cache entry for this channel... rv = OpenCacheEntry(); + // do not continue if asyncOpenCacheEntry is in progress + if (mOnCacheEntryAvailableCallback) { + NS_ASSERTION(NS_SUCCEEDED(rv), "Unexpected state"); + return NS_OK; + } + if (NS_FAILED(rv)) { LOG(("OpenCacheEntry failed [rv=%x]\n", rv)); // if this channel is only allowed to pull from the cache, then @@ -266,10 +273,10 @@ nsHttpChannel::Connect(bool firstTime) if (mCacheForOfflineUse) { rv = OpenOfflineCacheEntryForWriting(); if (NS_FAILED(rv)) return rv; - } - if (NS_SUCCEEDED(rv) && mAsyncCacheOpen) - return NS_OK; + if (mOnCacheEntryAvailableCallback) + return NS_OK; + } } // we may or may not have a cache entry at this point @@ -500,16 +507,17 @@ nsHttpChannel::SetupTransaction() if (mCaps & NS_HTTP_ALLOW_PIPELINING) { // // disable pipelining if: - // (1) pipelining has been explicitly disabled - // (2) request corresponds to a top-level document load (link click) - // (3) request method is non-idempotent + // (1) pipelining has been disabled by config + // (2) pipelining has been disabled by connection mgr info + // (3) request corresponds to a top-level document load (link click) + // (4) request method is non-idempotent + // (5) request is marked slow (e.g XHR) // - // XXX does the toplevel document check really belong here? or, should - // we push it out entirely to necko consumers? - // - if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) || + if (!mAllowPipelining || + (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) || !(mRequestHead.Method() == nsHttp::Get || mRequestHead.Method() == nsHttp::Head || + mRequestHead.Method() == nsHttp::Options || mRequestHead.Method() == nsHttp::Propfind || mRequestHead.Method() == nsHttp::Proppatch)) { LOG((" pipelining disallowed\n")); @@ -768,6 +776,10 @@ nsHttpChannel::CallOnStartRequest() rv = ApplyContentConversions(); if (NS_FAILED(rv)) return rv; + rv = EnsureAssocReq(); + if (NS_FAILED(rv)) + return rv; + // if this channel is for a download, close off access to the cache. if (mCacheEntry && mChannelIsForDownload) { mCacheEntry->Doom(); @@ -1713,6 +1725,117 @@ nsHttpChannel::Hash(const char *buf, nsACString &hash) return NS_OK; } +nsresult +nsHttpChannel::EnsureAssocReq() +{ + // Confirm Assoc-Req response header on pipelined transactions + // per draft-nottingham-http-pipeline-01.txt + // of the form: GET http://blah.com/foo/bar?qv + // return NS_OK as long as we don't find a violation + // (i.e. no header is ok, as are malformed headers, as are + // transactions that have not been pipelined (unless those have been + // opted in via pragma)) + + if (!mResponseHead) + return NS_OK; + + const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req); + if (!assoc_val) + return NS_OK; + + if (!mTransaction || !mURI) + return NS_OK; + + if (!mTransaction->PipelinePosition()) { + // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined + // transactions. It is used by test harness. + + const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma); + if (!pragma_val || + !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req", + HTTP_HEADER_VALUE_SEPS)) + return NS_OK; + } + + char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS); + if (!method) + return NS_OK; + + bool equals; + char *endofmethod; + + assoc_val = nsnull; + endofmethod = net_FindCharInSet(method, HTTP_LWS); + if (endofmethod) + assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS); + if (!assoc_val) + return NS_OK; + + // check the method + PRInt32 methodlen = PL_strlen(mRequestHead.Method().get()); + if ((methodlen != (endofmethod - method)) || + PL_strncmp(method, + mRequestHead.Method().get(), + endofmethod - method)) { + LOG((" Assoc-Req failure Method %s", method)); + if (mConnectionInfo) + gHttpHandler->ConnMgr()-> + PipelineFeedbackInfo(mConnectionInfo, + nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + nsAutoString message + (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); + AppendASCIItoUTF16( + mResponseHead->PeekHeader(nsHttp::Assoc_Req), + message); + message += NS_LITERAL_STRING(" expected method "); + AppendASCIItoUTF16(mRequestHead.Method().get(), message); + consoleService->LogStringMessage(message.get()); + } + + if (gHttpHandler->EnforceAssocReq()) + return NS_ERROR_CORRUPTED_CONTENT; + return NS_OK; + } + + // check the URL + nsCOMPtr assoc_url; + if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) || + !assoc_url) + return NS_OK; + + mURI->Equals(assoc_url, &equals); + if (!equals) { + LOG((" Assoc-Req failure URL %s", assoc_val)); + if (mConnectionInfo) + gHttpHandler->ConnMgr()-> + PipelineFeedbackInfo(mConnectionInfo, + nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + nsAutoString message + (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); + AppendASCIItoUTF16( + mResponseHead->PeekHeader(nsHttp::Assoc_Req), + message); + message += NS_LITERAL_STRING(" expected URL "); + AppendASCIItoUTF16(mSpec.get(), message); + consoleService->LogStringMessage(message.get()); + } + + if (gHttpHandler->EnforceAssocReq()) + return NS_ERROR_CORRUPTED_CONTENT; + } + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel //----------------------------------------------------------------------------- @@ -1856,6 +1979,33 @@ nsHttpChannel::ProcessNotModified() NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED); + // If the 304 response contains a Last-Modified different than the + // one in our cache that is pretty suspicious and is, in at least the + // case of bug 716840, a sign of the server having previously corrupted + // our cache with a bad response. Take the minor step here of just dooming + // that cache entry so there is a fighting chance of getting things on the + // right track as well as disabling pipelining for that host. + + nsCAutoString lastModified; + nsCAutoString lastModified304; + + rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified, + lastModified); + if (NS_SUCCEEDED(rv)) + rv = mResponseHead->GetHeader(nsHttp::Last_Modified, + lastModified304); + if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModified)) { + LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match " + "%s and %s\n", lastModified.get(), lastModified304.get())); + + mCacheEntry->Doom(); + if (mConnectionInfo) + gHttpHandler->ConnMgr()-> + PipelineFeedbackInfo(mConnectionInfo, + nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); + } + // merge any new headers with the cached response headers rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers()); if (NS_FAILED(rv)) return rv; @@ -2034,7 +2184,7 @@ nsHttpChannel::OpenCacheEntry() { nsresult rv; - mAsyncCacheOpen = false; + NS_ASSERTION(!mOnCacheEntryAvailableCallback, "Unexpected state"); mLoadedFromApplicationCache = false; LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this)); @@ -2117,52 +2267,33 @@ nsHttpChannel::OpenCacheEntry() getter_AddRefs(session)); NS_ENSURE_SUCCESS(rv, rv); - if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) { - // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY - rv = session->OpenCacheEntry(cacheKey, - nsICache::ACCESS_READ, false, - getter_AddRefs(mCacheEntry)); - if (NS_SUCCEEDED(rv)) { - mCacheEntry->GetAccessGranted(&mCacheAccess); - LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]", - this, mCacheAccess)); - mLoadedFromApplicationCache = true; - return NS_OK; - } else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { - LOG(("bypassing local cache since it is busy\n")); - // Don't try to load normal cache entry - return NS_ERROR_NOT_AVAILABLE; - } - } else { - mOnCacheEntryAvailableCallback = - &nsHttpChannel::OnOfflineCacheEntryAvailable; - // We open with ACCESS_READ only, because we don't want to - // overwrite the offline cache entry non-atomically. - // ACCESS_READ will prevent us from writing to the offline - // cache as a normal cache entry. - rv = session->AsyncOpenCacheEntry(cacheKey, - nsICache::ACCESS_READ, - this); + mOnCacheEntryAvailableCallback = + &nsHttpChannel::OnOfflineCacheEntryAvailable; + // We open with ACCESS_READ only, because we don't want to overwrite + // the offline cache entry non-atomically. ACCESS_READ will prevent us + // from writing to the offline cache as a normal cache entry. + rv = session->AsyncOpenCacheEntry( + cacheKey, + nsICache::ACCESS_READ, + this, + mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY); - if (NS_SUCCEEDED(rv)) { - mAsyncCacheOpen = true; - return NS_OK; - } - } + if (NS_SUCCEEDED(rv)) + return NS_OK; - // sync or async opening failed - return OnOfflineCacheEntryAvailable(nsnull, nsICache::ACCESS_NONE, - rv, true); + mOnCacheEntryAvailableCallback = nsnull; + + // opening cache entry failed + return OnOfflineCacheEntryAvailable(nsnull, nsICache::ACCESS_NONE, rv); } - return OpenNormalCacheEntry(true); + return OpenNormalCacheEntry(); } nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, nsCacheAccessMode aAccess, - nsresult aEntryStatus, - bool aIsSync) + nsresult aEntryStatus) { nsresult rv; @@ -2174,14 +2305,19 @@ nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, mCacheAccess = aAccess; } + if (aEntryStatus == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { + LOG(("bypassing local cache since it is busy\n")); + // Don't try to load normal cache entry + return NS_ERROR_NOT_AVAILABLE; + } + if (mCanceled && NS_FAILED(mStatus)) { LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus)); return mStatus; } if (NS_SUCCEEDED(aEntryStatus)) - // Called from OnCacheEntryAvailable, advance to the next state - return Connect(false); + return NS_OK; if (!mCacheForOfflineUse && !mFallbackChannel) { nsCAutoString cacheKey; @@ -2191,8 +2327,6 @@ nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, nsCOMPtr namespaceEntry; rv = mApplicationCache->GetMatchingNamespace (cacheKey, getter_AddRefs(namespaceEntry)); - if (NS_FAILED(rv) && !aIsSync) - return Connect(false); NS_ENSURE_SUCCESS(rv, rv); PRUint32 namespaceType = 0; @@ -2210,14 +2344,12 @@ nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, // ... and if there were an application cache entry, // we would have found it earlier. - return aIsSync ? NS_ERROR_CACHE_KEY_NOT_FOUND : Connect(false); + return NS_ERROR_CACHE_KEY_NOT_FOUND; } if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) { rv = namespaceEntry->GetData(mFallbackKey); - if (NS_FAILED(rv) && !aIsSync) - return Connect(false); NS_ENSURE_SUCCESS(rv, rv); } @@ -2235,12 +2367,12 @@ nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, } } - return OpenNormalCacheEntry(aIsSync); + return OpenNormalCacheEntry(); } nsresult -nsHttpChannel::OpenNormalCacheEntry(bool aIsSync) +nsHttpChannel::OpenNormalCacheEntry() { NS_ASSERTION(!mCacheEntry, "We have already mCacheEntry"); @@ -2260,43 +2392,18 @@ nsHttpChannel::OpenNormalCacheEntry(bool aIsSync) rv = DetermineCacheAccess(&accessRequested); if (NS_FAILED(rv)) return rv; - if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) { - if (!aIsSync) { - // Unexpected state: we were called from OnCacheEntryAvailable(), - // so LOAD_BYPASS_LOCAL_CACHE_IF_BUSY shouldn't be set. Unless - // somebody altered mLoadFlags between OpenCacheEntry() and - // OnCacheEntryAvailable()... - NS_WARNING( - "OpenNormalCacheEntry() called from OnCacheEntryAvailable() " - "when LOAD_BYPASS_LOCAL_CACHE_IF_BUSY was specified"); - } + mOnCacheEntryAvailableCallback = + &nsHttpChannel::OnNormalCacheEntryAvailable; + rv = session->AsyncOpenCacheEntry( + cacheKey, + accessRequested, + this, + mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY); - // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY - rv = session->OpenCacheEntry(cacheKey, accessRequested, false, - getter_AddRefs(mCacheEntry)); - if (NS_SUCCEEDED(rv)) { - mCacheEntry->GetAccessGranted(&mCacheAccess); - LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]", - this, mCacheAccess)); - } - else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { - LOG(("bypassing local cache since it is busy\n")); - rv = NS_ERROR_NOT_AVAILABLE; - } - } - else { - mOnCacheEntryAvailableCallback = - &nsHttpChannel::OnNormalCacheEntryAvailable; - rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this); - if (NS_SUCCEEDED(rv)) { - mAsyncCacheOpen = true; - return NS_OK; - } - } + if (NS_SUCCEEDED(rv)) + return NS_OK; - if (!aIsSync) - // Called from OnCacheEntryAvailable, advance to the next state - rv = Connect(false); + mOnCacheEntryAvailableCallback = nsnull; return rv; } @@ -2304,16 +2411,17 @@ nsHttpChannel::OpenNormalCacheEntry(bool aIsSync) nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, nsCacheAccessMode aAccess, - nsresult aEntryStatus, - bool aIsSync) + nsresult aEntryStatus) { - NS_ASSERTION(!aIsSync, "aIsSync should be false"); - if (NS_SUCCEEDED(aEntryStatus)) { mCacheEntry = aEntry; mCacheAccess = aAccess; } + if (aEntryStatus == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { + LOG(("bypassing local cache since it is busy\n")); + } + if (mCanceled && NS_FAILED(mStatus)) { LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus)); return mStatus; @@ -2325,7 +2433,7 @@ nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, return NS_ERROR_DOCUMENT_NOT_CACHED; // advance to the next state... - return Connect(false); + return NS_OK; } @@ -2372,22 +2480,43 @@ nsHttpChannel::OpenOfflineCacheEntryForWriting() getter_AddRefs(session)); if (NS_FAILED(rv)) return rv; - rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE, - false, getter_AddRefs(mOfflineCacheEntry)); + mOnCacheEntryAvailableCallback = + &nsHttpChannel::OnOfflineCacheEntryForWritingAvailable; + rv = session->AsyncOpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE, + this, true); + if (NS_SUCCEEDED(rv)) + return NS_OK; - if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { + mOnCacheEntryAvailableCallback = nsnull; + + return rv; +} + +nsresult +nsHttpChannel::OnOfflineCacheEntryForWritingAvailable( + nsICacheEntryDescriptor *aEntry, + nsCacheAccessMode aAccess, + nsresult aEntryStatus) +{ + if (NS_SUCCEEDED(aEntryStatus)) { + mOfflineCacheEntry = aEntry; + mOfflineCacheAccess = aAccess; + } + + if (aEntryStatus == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { // access to the cache entry has been denied (because the cache entry // is probably in use by another channel). Either the cache is being // read from (we're offline) or it's being updated elsewhere. - return NS_OK; + aEntryStatus = NS_OK; } - if (NS_SUCCEEDED(rv)) { - mOfflineCacheEntry->GetAccessGranted(&mOfflineCacheAccess); - LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess)); + if (mCanceled && NS_FAILED(mStatus)) { + LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus)); + return mStatus; } - return rv; + // advance to the next state... + return aEntryStatus; } // Generates the proper cache-key for this instance of nsHttpChannel @@ -4843,24 +4972,8 @@ nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, if (!mIsPending) return NS_OK; - nsOnCacheEntryAvailableCallback callback = mOnCacheEntryAvailableCallback; - mOnCacheEntryAvailableCallback = nsnull; - - NS_ASSERTION(callback, - "nsHttpChannel::OnCacheEntryAvailable called without callback"); - rv = ((*this).*callback)(entry, access, status, false); - + rv = OnCacheEntryAvailableInternal(entry, access, status); if (NS_FAILED(rv)) { - LOG(("AsyncOpenCacheEntry failed [rv=%x]\n", rv)); - if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { - // If we have a fallback URI (and we're not already - // falling back), process the fallback asynchronously. - if (!mFallbackChannel && !mFallbackKey.IsEmpty()) { - rv = AsyncCall(&nsHttpChannel::HandleAsyncFallback); - if (NS_SUCCEEDED(rv)) - return rv; - } - } CloseCacheEntry(true); AsyncAbort(rv); } @@ -4868,6 +4981,67 @@ nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, return NS_OK; } +nsresult +nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntryDescriptor *entry, + nsCacheAccessMode access, + nsresult status) +{ + nsresult rv; + + nsOnCacheEntryAvailableCallback callback = mOnCacheEntryAvailableCallback; + mOnCacheEntryAvailableCallback = nsnull; + + NS_ASSERTION(callback, + "nsHttpChannel::OnCacheEntryAvailable called without callback"); + rv = ((*this).*callback)(entry, access, status); + + if (mOnCacheEntryAvailableCallback) { + // callback fired another async open + NS_ASSERTION(NS_SUCCEEDED(rv), "Unexpected state"); + return NS_OK; + } + + if (callback != &nsHttpChannel::OnOfflineCacheEntryForWritingAvailable) { + if (NS_FAILED(rv)) { + LOG(("AsyncOpenCacheEntry failed [rv=%x]\n", rv)); + if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { + // If we have a fallback URI (and we're not already + // falling back), process the fallback asynchronously. + if (!mFallbackChannel && !mFallbackKey.IsEmpty()) { + return AsyncCall(&nsHttpChannel::HandleAsyncFallback); + } + return NS_ERROR_DOCUMENT_NOT_CACHED; + } + // proceed without using the cache + } + + // if cacheForOfflineUse has been set, open up an offline cache entry + // to update + if (mCacheForOfflineUse) { + rv = OpenOfflineCacheEntryForWriting(); + if (mOnCacheEntryAvailableCallback) { + NS_ASSERTION(NS_SUCCEEDED(rv), "Unexpected state"); + return NS_OK; + } + + if (NS_FAILED(rv)) + return rv; + } + } else { + // check result of OnOfflineCacheEntryForWritingAvailable() + if (NS_FAILED(rv)) + return rv; + } + + return Connect(false); +} + +NS_IMETHODIMP +nsHttpChannel::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + nsresult nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn) { @@ -5186,19 +5360,7 @@ nsHttpChannel::DoInvalidateCacheEntry(nsACString &key) if (NS_FAILED(rv)) return; - // Now, find the actual cache-entry - nsCOMPtr tmpCacheEntry; - rv = session->OpenCacheEntry(key, nsICache::ACCESS_READ, - false, - getter_AddRefs(tmpCacheEntry)); - - // If entry was found, set its expiration-time = 0 - if(NS_SUCCEEDED(rv)) { - tmpCacheEntry->SetExpirationTime(0); - LOG((" cache-entry invalidated [key=%s]\n", key.Data())); - } else { - LOG((" cache-entry not found [key=%s]\n", key.Data())); - } + session->DoomEntry(key, nsnull); } nsCacheStoragePolicy diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index f8e59fe1b7da..e3f10002964b 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -182,6 +182,7 @@ private: nsresult ContinueProcessFallback(nsresult); bool ResponseWouldVary(); void HandleAsyncAbort(); + nsresult EnsureAssocReq(); nsresult ContinueOnStartRequest1(nsresult); nsresult ContinueOnStartRequest2(nsresult); @@ -210,14 +211,19 @@ private: nsresult OpenCacheEntry(); nsresult OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, nsCacheAccessMode aAccess, - nsresult aResult, - bool aSync); - nsresult OpenNormalCacheEntry(bool aSync); + nsresult aResult); + nsresult OpenNormalCacheEntry(); nsresult OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry, nsCacheAccessMode aAccess, - nsresult aResult, - bool aSync); + nsresult aResult); nsresult OpenOfflineCacheEntryForWriting(); + nsresult OnOfflineCacheEntryForWritingAvailable( + nsICacheEntryDescriptor *aEntry, + nsCacheAccessMode aAccess, + nsresult aResult); + nsresult OnCacheEntryAvailableInternal(nsICacheEntryDescriptor *entry, + nsCacheAccessMode access, + nsresult status); nsresult GenerateCacheKey(PRUint32 postID, nsACString &key); nsresult UpdateExpirationTime(); nsresult CheckCache(); @@ -299,9 +305,8 @@ private: PRUint32 mRequestTime; typedef nsresult (nsHttpChannel:: *nsOnCacheEntryAvailableCallback)( - nsICacheEntryDescriptor *, nsCacheAccessMode, nsresult, bool); + nsICacheEntryDescriptor *, nsCacheAccessMode, nsresult); nsOnCacheEntryAvailableCallback mOnCacheEntryAvailableCallback; - bool mAsyncCacheOpen; nsCOMPtr mOfflineCacheEntry; nsCacheAccessMode mOfflineCacheAccess; diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 6d6e6f29056d..bbe7dd99b37a 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -72,8 +72,6 @@ using namespace mozilla::net; nsHttpConnection::nsHttpConnection() : mTransaction(nsnull) - , mLastReadTime(0) - , mIdleTimeout(0) , mConsiderReusedAfterInterval(0) , mConsiderReusedAfterEpoch(0) , mCurrentBytesRead(0) @@ -86,7 +84,10 @@ nsHttpConnection::nsHttpConnection() , mCompletedProxyConnect(false) , mLastTransactionExpectedNoContent(false) , mIdleMonitoring(false) + , mProxyConnectInProgress(false) , mHttp1xTransactionCount(0) + , mRemainingConnectionUses(0xffffffff) + , mClassification(nsAHttpTransaction::CLASS_GENERAL) , mNPNComplete(false) , mSetupNPNCalled(false) , mUsingSpdy(false) @@ -141,20 +142,25 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, nsIAsyncInputStream *instream, nsIAsyncOutputStream *outstream, nsIInterfaceRequestor *callbacks, - nsIEventTarget *callbackTarget) + nsIEventTarget *callbackTarget, + PRIntervalTime rtt) { NS_ABORT_IF_FALSE(transport && instream && outstream, "invalid socket information"); LOG(("nsHttpConnection::Init [this=%p " - "transport=%p instream=%p outstream=%p]\n", - this, transport, instream, outstream)); + "transport=%p instream=%p outstream=%p rtt=%d]\n", + this, transport, instream, outstream, + PR_IntervalToMilliseconds(rtt))); NS_ENSURE_ARG_POINTER(info); NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED); mConnInfo = info; - mMaxHangTime = PR_SecondsToInterval(maxHangTime); mLastReadTime = PR_IntervalNow(); + mSupportsPipelining = + gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo); + mRtt = rtt; + mMaxHangTime = PR_SecondsToInterval(maxHangTime); mSocketTransport = transport; mSocketIn = instream; @@ -227,13 +233,16 @@ nsHttpConnection::StartSpdy() "into SpdySession %p\n", mTransaction.get(), mSpdySession.get())); } else { - NS_ABORT_IF_FALSE(!list.IsEmpty(), "sub transaction list empty"); - PRInt32 count = list.Length(); LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d " "into SpdySession %p\n", count, mSpdySession.get())); + if (!count) { + mTransaction->Close(NS_ERROR_ABORT); + return; + } + for (PRInt32 index = 0; index < count; ++index) { if (!mSpdySession) { mSpdySession = new SpdySession(list[index], @@ -338,6 +347,9 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); + // reset the read timers to wash away any idle time + mLastReadTime = PR_IntervalNow(); + // Update security callbacks nsCOMPtr callbacks; nsCOMPtr callbackTarget; @@ -369,6 +381,7 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) rv = SetupProxyConnect(); if (NS_FAILED(rv)) goto failed_activation; + mProxyConnectInProgress = true; } // Clear the per activation counter @@ -525,16 +538,35 @@ nsHttpConnection::DontReuse() mSpdySession->DontReuse(); } +// Checked by the Connection Manager before scheduling a pipelined transaction +bool +nsHttpConnection::SupportsPipelining() +{ + if (mTransaction && + mTransaction->PipelineDepth() >= mRemainingConnectionUses) { + LOG(("nsHttpConnection::SupportsPipelining this=%p deny pipeline " + "because current depth %d exceeds max remaining uses %d\n", + this, mTransaction->PipelineDepth(), mRemainingConnectionUses)); + return false; + } + return mSupportsPipelining && IsKeepAlive(); +} + bool nsHttpConnection::CanReuse() { + if ((mTransaction ? mTransaction->PipelineDepth() : 0) >= + mRemainingConnectionUses) { + return false; + } + bool canReuse; - if (mUsingSpdy) + if (mSpdySession) canReuse = mSpdySession->CanReuse(); else canReuse = IsKeepAlive(); - + canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive(); // An idle persistent connection should not have data waiting to be read @@ -560,7 +592,8 @@ nsHttpConnection::CanDirectlyActivate() // time through Activate(). In practice this means this is a healthy SPDY // connection with room for more concurrent streams. - return UsingSpdy() && CanReuse() && mSpdySession->RoomForMoreStreams(); + return UsingSpdy() && CanReuse() && + mSpdySession && mSpdySession->RoomForMoreStreams(); } PRIntervalTime @@ -619,21 +652,19 @@ nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) if (mUsingSpdy) return false; - // XXX there should be a strict mode available that disables this - // blacklisting. - // assuming connection is HTTP/1.1 with keep-alive enabled if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) { // XXX check for bad proxy servers... return true; } - // XXX what about checking for a Via header? (transparent proxies) - // check for bad origin servers const char *val = responseHead->PeekHeader(nsHttp::Server); + + // If there is no server header we will assume it should not be banned + // as facebook and some other prominent sites do this if (!val) - return false; // no header, no love + return true; // The blacklist is indexed by the first character. All of these servers are // known to return their identifier as the first thing in the server string, @@ -660,6 +691,8 @@ nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) for (int i = 0; bad_servers[index][i] != nsnull; i++) { if (!PL_strncmp (val, bad_servers[index][i], strlen (bad_servers[index][i]))) { LOG(("looks like this server does not support pipelining")); + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0); return false; } } @@ -715,11 +748,23 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, mKeepAlive = true; else mKeepAlive = false; + + // We need at least version 1.1 to use pipelines + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0); } else { // HTTP/1.1 connections are by default persistent - if (val && !PL_strcasecmp(val, "close")) + if (val && !PL_strcasecmp(val, "close")) { mKeepAlive = false; + + // persistent connections are required for pipelining to work - if + // this close was not pre-announced then generate the negative + // BadExplicitClose feedback + if (mRemainingConnectionUses > 1) + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0); + } else { mKeepAlive = true; @@ -734,6 +779,31 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, } mKeepAliveMask = mKeepAlive; + // Update the pipelining status in the connection info object + // and also read it back. It is possible the ci status is + // locked to false if pipelining has been banned on this ci due to + // some kind of observed flaky behavior + if (mSupportsPipelining) { + // report the pipelining-compatible header to the connection manager + // as positive feedback. This will undo 1 penalty point the host + // may have accumulated in the past. + + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0); + + mSupportsPipelining = + gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo); + } + + // If this connection is reserved for revalidations and we are + // receiving a document that failed revalidation then switch the + // classification to general to avoid pipelining more revalidations behind + // it. + if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION && + responseHead->Status() != 304) { + mClassification = nsAHttpTransaction::CLASS_GENERAL; + } + // if this connection is persistent, then the server may send a "Keep-Alive" // header specifying the maximum number of times the connection can be // reused as well as the maximum amount of time the connection can be idle @@ -741,6 +811,7 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, // a "keep-alive" connection is by definition capable of being reused, and // we only care about being able to reuse it once. if a timeout is not // specified then we use our advertized timeout value. + bool foundKeepAliveMax = false; if (mKeepAlive) { val = responseHead->PeekHeader(nsHttp::Keep_Alive); @@ -749,16 +820,28 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, if (cp) mIdleTimeout = PR_SecondsToInterval((PRUint32) atoi(cp + 8)); else - mIdleTimeout = gHttpHandler->IdleTimeout(); + mIdleTimeout = gHttpHandler->SpdyTimeout(); + + cp = PL_strcasestr(val, "max="); + if (cp) { + int val = atoi(cp + 4); + if (val > 0) { + foundKeepAliveMax = true; + mRemainingConnectionUses = static_cast(val); + } + } } else { mIdleTimeout = gHttpHandler->SpdyTimeout(); } - LOG(("Connection can be reused [this=%x idle-timeout=%usec]\n", + LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n", this, PR_IntervalToSeconds(mIdleTimeout))); } + if (!foundKeepAliveMax && mRemainingConnectionUses && !mUsingSpdy) + --mRemainingConnectionUses; + if (!mProxyConnectStream) HandleAlternateProtocol(responseHead); @@ -880,8 +963,56 @@ nsHttpConnection::ReadTimeoutTick(PRIntervalTime now) return; } - // Pending patches places pipeline rescheduling code will go here + PRIntervalTime delta = PR_IntervalNow() - mLastReadTime; + // we replicate some of the checks both here and in OnSocketReadable() as + // they will be discovered under different conditions. The ones here + // will generally be discovered if we are totally hung and OSR does + // not get called at all, however OSR discovers them with lower latency + // if the issue is just very slow (but not stalled) reading. + // + // Right now we only take action if pipelining is involved, but this would + // be the place to add general read timeout handling if it is desired. + + const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000); + + if (delta < k1000ms) + return; + + PRUint32 pipelineDepth = mTransaction->PipelineDepth(); + + // this just reschedules blocked transactions. no transaction + // is aborted completely. + LOG(("cancelling pipeline due to a %ums stall - depth %d\n", + PR_IntervalToMilliseconds(delta), pipelineDepth)); + + if (pipelineDepth > 1) { + nsHttpPipeline *pipeline = mTransaction->QueryPipeline(); + NS_ABORT_IF_FALSE(pipeline, "pipelinedepth > 1 without pipeline"); + // code this defensively for the moment and check for null in opt build + if (pipeline) + pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT); + } + + if (delta < gHttpHandler->GetPipelineTimeout()) + return; + + if (pipelineDepth <= 1 && !mTransaction->PipelinePosition()) + return; + + // nothing has transpired on this pipelined socket for many + // seconds. Call that a total stall and close the transaction. + // There is a chance the transaction will be restarted again + // depending on its state.. that will come back araound + // without pipelining on, so this won't loop. + + LOG(("canceling transaction stalled for %ums on a pipeline" + "of depth %d and scheduled originally at pos %d\n", + PR_IntervalToMilliseconds(delta), + pipelineDepth, mTransaction->PipelinePosition())); + + // This will also close the connection + CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT); } void @@ -930,6 +1061,14 @@ nsHttpConnection::ResumeRecv() NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + // the mLastReadTime timestamp is used for finding slowish readers + // and can be pretty sensitive. For that reason we actually reset it + // when we ask to read (resume recv()) so that when we get called back + // with actual read data in OnSocketReadable() we are only measuring + // the latency between those two acts and not all the processing that + // may get done before the ResumeRecv() call + mLastReadTime = PR_IntervalNow(); + if (mSocketIn) return mSocketIn->AsyncWait(this, 0, 0, nsnull); @@ -1052,7 +1191,8 @@ nsHttpConnection::OnReadSegment(const char *buf, nsresult nsHttpConnection::OnSocketWritable() { - LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this)); + LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n", + this, mConnInfo->Host())); nsresult rv; PRUint32 n; @@ -1094,6 +1234,7 @@ nsHttpConnection::OnSocketWritable() } LOG((" writing transaction request stream\n")); + mProxyConnectInProgress = false; rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n); } @@ -1131,7 +1272,7 @@ nsHttpConnection::OnSocketWritable() nsISocketTransport::STATUS_WAITING_FOR, LL_ZERO); - rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading + rv = ResumeRecv(); // start reading again = false; } // write more to the socket until error or end-of-request... @@ -1170,14 +1311,53 @@ nsHttpConnection::OnSocketReadable() LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this)); PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime delta = now - mLastReadTime; - if (mKeepAliveMask && ((now - mLastReadTime) >= mMaxHangTime)) { + if (mKeepAliveMask && (delta >= mMaxHangTime)) { LOG(("max hang time exceeded!\n")); // give the handler a chance to create a new persistent connection to // this host if we've been busy for too long. mKeepAliveMask = false; gHttpHandler->ProcessPendingQ(mConnInfo); } + + // Look for data being sent in bursts with large pauses. If the pauses + // are caused by server bottlenecks such as think-time, disk i/o, or + // cpu exhaustion (as opposed to network latency) then we generate negative + // pipelining feedback to prevent head of line problems + + // Reduce the estimate of the time since last read by up to 1 RTT to + // accommodate exhausted sender TCP congestion windows or minor I/O delays. + + if (delta > mRtt) + delta -= mRtt; + else + delta = 0; + + const PRIntervalTime k400ms = PR_MillisecondsToInterval(400); + const PRIntervalTime k1200ms = PR_MillisecondsToInterval(1200); + + if (delta > k1200ms) { + LOG(("Read delta ms of %u causing slow read major " + "event and pipeline cancellation", + PR_IntervalToMilliseconds(delta))); + + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0); + + if (mTransaction->PipelineDepth() > 1) { + nsHttpPipeline *pipeline = mTransaction->QueryPipeline(); + NS_ABORT_IF_FALSE(pipeline, "pipelinedepth > 1 without pipeline"); + // code this defensively for the moment and check for null + if (pipeline) + pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT); + } + } + else if (delta > k400ms) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0); + } + mLastReadTime = now; nsresult rv; @@ -1199,7 +1379,7 @@ nsHttpConnection::OnSocketReadable() if (NS_FAILED(mSocketInCondition)) { // continue waiting for the socket if necessary... if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) - rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); + rv = ResumeRecv(); else rv = mSocketInCondition; again = false; diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index d02f2e516319..959691323bdb 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -41,13 +41,14 @@ #include "nsHttp.h" #include "nsHttpConnectionInfo.h" -#include "nsAHttpConnection.h" #include "nsAHttpTransaction.h" +#include "nsHttpPipeline.h" #include "nsXPIDLString.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "prinrval.h" #include "SpdySession.h" +#include "mozilla/TimeStamp.h" #include "nsIStreamListener.h" #include "nsISocketTransport.h" @@ -56,6 +57,9 @@ #include "nsIInterfaceRequestor.h" #include "nsIEventTarget.h" +class nsHttpRequestHead; +class nsHttpResponseHead; + //----------------------------------------------------------------------------- // nsHttpConnection - represents a connection to a HTTP server (or proxy) // @@ -90,7 +94,7 @@ public: nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime, nsISocketTransport *, nsIAsyncInputStream *, nsIAsyncOutputStream *, nsIInterfaceRequestor *, - nsIEventTarget *); + nsIEventTarget *, PRIntervalTime); // Activate causes the given transaction to be processed on this // connection. It fails if there is already an existing transaction unless @@ -103,7 +107,7 @@ public: //------------------------------------------------------------------------- // XXX document when these are ok to call - bool SupportsPipelining() { return mSupportsPipelining; } + bool SupportsPipelining(); bool IsKeepAlive() { return mUsingSpdy || (mKeepAliveMask && mKeepAlive); } bool CanReuse(); // can this connection be reused? @@ -115,6 +119,11 @@ public: void DontReuse(); void DropTransport() { DontReuse(); mSocketTransport = 0; } + bool IsProxyConnectInProgress() + { + return mProxyConnectInProgress; + } + bool LastTransactionExpectedNoContent() { return mLastTransactionExpectedNoContent; @@ -158,9 +167,18 @@ public: bool UsingSpdy() { return mUsingSpdy; } - // When the connection is active this is called every 15 seconds + // When the connection is active this is called every 1 second void ReadTimeoutTick(PRIntervalTime now); + nsAHttpTransaction::Classifier Classification() { return mClassification; } + void Classify(nsAHttpTransaction::Classifier newclass) + { + mClassification = newclass; + } + + // When the connection is active this is called every second + void ReadTimeoutTick(); + private: // called to cause the underlying socket to start speaking SSL nsresult ProxyStartSSL(); @@ -210,7 +228,7 @@ private: nsRefPtr mConnInfo; - PRUint32 mLastReadTime; + PRIntervalTime mLastReadTime; PRIntervalTime mMaxHangTime; // max download time before dropping keep-alive status PRIntervalTime mIdleTimeout; // value of keep-alive: timeout= PRIntervalTime mConsiderReusedAfterInterval; @@ -221,6 +239,8 @@ private: nsRefPtr mInputOverflow; + PRIntervalTime mRtt; + bool mKeepAlive; bool mKeepAliveMask; bool mSupportsPipelining; @@ -228,11 +248,19 @@ private: bool mCompletedProxyConnect; bool mLastTransactionExpectedNoContent; bool mIdleMonitoring; + bool mProxyConnectInProgress; // The number of <= HTTP/1.1 transactions performed on this connection. This // excludes spdy transactions. PRUint32 mHttp1xTransactionCount; + // Keep-Alive: max="mRemainingConnectionUses" provides the number of future + // transactions (including the current one) that the server expects to allow + // on this persistent connection. + PRUint32 mRemainingConnectionUses; + + nsAHttpTransaction::Classifier mClassification; + // SPDY related bool mNPNComplete; bool mSetupNPNCalled; diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index fa0e43620dfe..ce469f8a38e5 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -125,6 +125,7 @@ public: void SetAnonymous(bool anon) { mHashKey.SetCharAt(anon ? 'A' : '.', 2); } bool GetAnonymous() { return mHashKey.CharAt(2) == 'A'; } + bool ShouldForceConnectMethod(); const nsCString &GetHost() { return mHost; } diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 63c9176a11a4..5293dd6f59f3 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -143,7 +143,8 @@ nsHttpConnectionMgr::Init(PRUint16 maxConns, PRUint16 maxPersistConnsPerHost, PRUint16 maxPersistConnsPerProxy, PRUint16 maxRequestDelay, - PRUint16 maxPipelinedRequests) + PRUint16 maxPipelinedRequests, + PRUint16 maxOptimisticPipelinedRequests) { LOG(("nsHttpConnectionMgr::Init\n")); @@ -157,6 +158,7 @@ nsHttpConnectionMgr::Init(PRUint16 maxConns, mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; mMaxRequestDelay = maxRequestDelay; mMaxPipelinedRequests = maxPipelinedRequests; + mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests; mIsShuttingDown = false; } @@ -211,10 +213,7 @@ nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, PRInt32 iparam, void } else { nsRefPtr event = new nsConnEvent(this, handler, iparam, vparam); - if (!event) - rv = NS_ERROR_OUT_OF_MEMORY; - else - rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); + rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); } return rv; } @@ -349,35 +348,6 @@ nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) return NS_OK; } -void -nsHttpConnectionMgr::AddTransactionToPipeline(nsHttpPipeline *pipeline) -{ - LOG(("nsHttpConnectionMgr::AddTransactionToPipeline [pipeline=%x]\n", pipeline)); - - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - - nsRefPtr ci; - pipeline->GetConnectionInfo(getter_AddRefs(ci)); - if (ci) { - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - if (ent) { - // search for another request to pipeline... - PRInt32 i, count = ent->mPendingQ.Length(); - for (i=0; imPendingQ[i]; - if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) { - pipeline->AddTransaction(trans); - - // remove transaction from pending queue - ent->mPendingQ.RemoveElementAt(i); - NS_RELEASE(trans); - break; - } - } - } - } -} - nsresult nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn) { @@ -810,18 +780,14 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, } else { self->ConditionallyStopPruneDeadConnectionsTimer(); } -#ifdef DEBUG - count = ent->mActiveConns.Length(); - if (count > 0) { - for (PRInt32 i=count-1; i>=0; --i) { - nsHttpConnection *conn = ent->mActiveConns[i]; - LOG((" active conn [%x] with trans [%x]\n", conn, conn->Transaction())); - } - } -#endif - // if this entry is empty, then we can remove it. - if (ent->mIdleConns.Length() == 0 && + // if this entry is empty, we have too many entries, + // and this doesn't represent some painfully determined + // red condition, then we can clean it up and restart from + // yellow + if (ent->PipelineState() != PS_RED && + self->mCT.Count() > 125 && + ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && ent->mPendingQ.Length() == 0 && @@ -832,7 +798,7 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, return PL_DHASH_REMOVE; } - // else, use this opportunity to compact our arrays... + // otherwise use this opportunity to compact our arrays... ent->mIdleConns.Compact(); ent->mActiveConns.Compact(); ent->mPendingQ.Compact(); @@ -898,64 +864,155 @@ bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", - ent->mConnInfo->HashKey().get())); + ent->mConnInfo->HashKey().get())); ProcessSpdyPendingQ(ent); - PRUint32 i, count = ent->mPendingQ.Length(); - if (count > 0) { - LOG((" pending-count=%u\n", count)); - nsHttpTransaction *trans = nsnull; - nsHttpConnection *conn = nsnull; - for (i = 0; i < count; ++i) { - trans = ent->mPendingQ[i]; + PRUint32 count = ent->mPendingQ.Length(); + nsHttpTransaction *trans; + nsresult rv; + bool dispatchedSuccessfully = false; - // When this transaction has already established a half-open - // connection, we want to prevent any duplicate half-open - // connections from being established and bound to this - // transaction. Allow only use of an idle persistent connection - // (if found) for transactions referred by a half-open connection. - bool alreadyHalfOpen = false; - for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); j++) { - if (ent->mHalfOpens[j]->Transaction() == trans) { - alreadyHalfOpen = true; - break; - } - } + // iterate the pending list until one is dispatched successfully. Keep + // iterating afterwards only until a transaction fails to dispatch. + for (PRUint32 i = 0; i < count; ++i) { + trans = ent->mPendingQ[i]; - GetConnection(ent, trans, alreadyHalfOpen, &conn); - if (conn) + // When this transaction has already established a half-open + // connection, we want to prevent any duplicate half-open + // connections from being established and bound to this + // transaction. Allow only use of an idle persistent connection + // (if found) for transactions referred by a half-open connection. + bool alreadyHalfOpen = false; + for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); ++j) { + if (ent->mHalfOpens[j]->Transaction() == trans) { + alreadyHalfOpen = true; break; - - NS_ABORT_IF_FALSE(count == ent->mPendingQ.Length(), - "something mutated pending queue from " - "GetConnection()"); - } - if (conn) { - LOG((" dispatching pending transaction...\n")); - - // remove pending transaction - ent->mPendingQ.RemoveElementAt(i); - - nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn); - if (NS_SUCCEEDED(rv)) - NS_RELEASE(trans); - else { - LOG((" DispatchTransaction failed [rv=%x]\n", rv)); - // on failure, just put the transaction back - ent->mPendingQ.InsertElementAt(i, trans); - // might be something wrong with the connection... close it. - conn->Close(rv); } - - NS_RELEASE(conn); - return true; } + + rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans); + if (NS_SUCCEEDED(rv)) { + LOG((" dispatching pending transaction...\n")); + ent->mPendingQ.RemoveElementAt(i); + NS_RELEASE(trans); + + // reset index and array length after RemoveElelmentAt() + dispatchedSuccessfully = true; + count = ent->mPendingQ.Length(); + --i; + continue; + } + + if (dispatchedSuccessfully) + return true; + + NS_ABORT_IF_FALSE(count == ((PRInt32) ent->mPendingQ.Length()), + "something mutated pending queue from " + "GetConnection()"); } return false; } +bool +nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (ent) + return ProcessPendingQForEntry(ent); + return false; +} + +bool +nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (ent) + return ent->SupportsPipelining(); + return false; +} + +// nsHttpPipelineFeedback used to hold references across events + +class nsHttpPipelineFeedback +{ +public: + nsHttpPipelineFeedback(nsHttpConnectionInfo *ci, + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *conn, PRUint32 data) + : mConnInfo(ci) + , mConn(conn) + , mInfo(info) + , mData(data) + { + } + + ~nsHttpPipelineFeedback() + { + } + + nsRefPtr mConnInfo; + nsRefPtr mConn; + nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo; + PRUint32 mData; +}; + +void +nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci, + PipelineFeedbackInfoType info, + nsHttpConnection *conn, + PRUint32 data) +{ + if (!ci) + return; + + // Post this to the socket thread if we are not running there already + if (PR_GetCurrentThread() != gSocketThread) { + nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info, + conn, data); + + nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, + 0, fb); + if (NS_FAILED(rv)) + delete fb; + return; + } + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + + if (ent) + ent->OnPipelineFeedbackInfo(info, conn, data); +} + +void +nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri) +{ + NS_ABORT_IF_FALSE(uri, "precondition"); + + nsCAutoString host; + PRInt32 port = -1; + bool usingSSL = false; + + nsresult rv = uri->SchemeIs("https", &usingSSL); + if (NS_SUCCEEDED(rv)) + rv = uri->GetAsciiHost(host); + if (NS_SUCCEEDED(rv)) + rv = uri->GetPort(&port); + if (NS_FAILED(rv) || host.IsEmpty()) + return; + + nsRefPtr ci = + new nsHttpConnectionInfo(host, port, nsnull, usingSSL); + + PipelineFeedbackInfo(ci, RedCorruptedContent, nsnull, 0); +} + // we're at the active connection limit if any one of the following conditions is true: // (1) at max-connections // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy @@ -1049,122 +1106,458 @@ nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key, return PL_DHASH_NEXT; } -void -nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, - nsHttpTransaction *trans, - bool onlyReusedConnection, - nsHttpConnection **result) +bool +nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, + nsHttpTransaction *trans) { - LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n", - ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); - - // First, see if an existing connection can be used - either an idle - // persistent connection or an active spdy session may be reused instead of - // establishing a new socket. We do not need to check the connection limits - // yet as they govern the maximum number of open connections and reusing - // an old connection never increases that. - - *result = nsnull; - - nsHttpConnection *conn = nsnull; - bool addConnToActiveList = true; - - if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { - - // if willing to use spdy look for an active spdy connections - // before considering idle http ones. - if (gHttpHandler->IsSpdyEnabled()) { - conn = GetSpdyPreferredConn(ent); - if (conn) - addConnToActiveList = false; - } + LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", + this, ent, trans)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - // search the idle connection list. Each element in the list - // has a reference, so if we remove it from the list into a local - // ptr, that ptr now owns the reference + // If this host is trying to negotiate a SPDY session right now, + // don't create any new connections until the result of the + // negotiation is known. + + if (gHttpHandler->IsSpdyEnabled() && + ent->mConnInfo->UsingSSL() && + !ent->mConnInfo->UsingHttpProxy() && + !(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && + (!ent->mTestedSpdy || ent->mUsingSpdy) && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) { + return false; + } + + // We need to make a new connection. If that is going to exceed the + // global connection limit then try and free up some room by closing + // an idle connection to another host. We know it won't select "ent" + // beacuse we have already determined there are no idle connections + // to our destination + + if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) + mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); + + if (AtActiveConnectionLimit(ent, trans->Caps())) + return false; + + nsresult rv = CreateTransport(ent, trans); + if (NS_FAILED(rv)) /* hard failure */ + trans->Close(rv); + + return true; +} + +bool +nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpTransaction::Classifier classification, + PRUint16 depthLimit) +{ + if (classification == nsAHttpTransaction::CLASS_SOLO) + return false; + + PRUint32 maxdepth = ent->MaxPipelineDepth(classification); + if (maxdepth == 0) { + ent->CreditPenalty(); + maxdepth = ent->MaxPipelineDepth(classification); + } + + if (ent->PipelineState() == PS_RED) + return false; + + if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection) + return false; + + // The maximum depth of a pipeline in yellow is 1 pipeline of + // depth 2 for entire CI. When that transaction completes successfully + // we transition to green and that expands the allowed depth + // to any number of pipelines of up to depth 4. When a transaction + // queued at position 3 or deeper succeeds we open it all the way + // up to depths limited only by configuration. The staggered start + // in green is simply because a successful yellow test of depth=2 + // might really just be a race condition (i.e. depth=1 from the + // server's point of view), while depth=3 is a stronger indicator - + // keeping the pipelines to a modest depth during that period limits + // the damage if something is going to go wrong. + + maxdepth = PR_MIN(maxdepth, depthLimit); + + if (maxdepth < 2) + return false; + + nsAHttpTransaction *activeTrans; + + nsHttpConnection *bestConn = nsnull; + PRUint32 activeCount = ent->mActiveConns.Length(); + PRUint32 bestConnLength = 0; + PRUint32 connLength; + + for (PRUint32 i = 0; i < activeCount; ++i) { + nsHttpConnection *conn = ent->mActiveConns[i]; + if (!conn->SupportsPipelining()) + continue; + + if (conn->Classification() != classification) + continue; + + activeTrans = conn->Transaction(); + if (!activeTrans || + activeTrans->IsDone() || + NS_FAILED(activeTrans->Status())) + continue; + + connLength = activeTrans->PipelineDepth(); + + if (maxdepth <= connLength) + continue; + + if (!bestConn || (connLength < bestConnLength)) { + bestConn = conn; + bestConnLength = connLength; + } + } + + if (!bestConn) + return false; + + activeTrans = bestConn->Transaction(); + nsresult rv = activeTrans->AddTransaction(trans); + if (NS_FAILED(rv)) + return false; + + LOG((" scheduling trans %p on pipeline at position %d\n", + trans, trans->PipelinePosition())); + + if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1)) + ent->SetYellowConnection(bestConn); + return true; +} + +bool +nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent, + nsHttpTransaction::Classifier classification) +{ + // A connection entry is declared to be "under pressure" if most of the + // allowed parallel connections are already used up. In that case we want to + // favor existing pipelines over more parallelism so as to reserve any + // unused parallel connections for types that don't have existing pipelines. + // + // The defintion of connection pressure is a pretty liberal one here - that + // is why we are using the more restrictive maxPersist* counters. + // + // Pipelines are also favored when the requested classification is already + // using 3 or more of the connections. Failure to do this could result in + // one class (e.g. images) establishing self replenishing queues on all the + // connections that would starve the other transaction types. + + PRInt32 currentConns = ent->mActiveConns.Length(); + PRInt32 maxConns = + (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingSSL()) ? + mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost; + + // Leave room for at least 3 distinct types to operate concurrently, + // this satisfies the typical {html, js/css, img} page. + if (currentConns >= (maxConns - 2)) + return true; /* prefer pipeline */ + + PRInt32 sameClass = 0; + for (PRInt32 i = 0; i < currentConns; ++i) + if (classification == ent->mActiveConns[i]->Classification()) + if (++sameClass == 3) + return true; /* prefer pipeline */ + + return false; /* normal behavior */ +} + +// returns OK if a connection is found for the transaction +// and the transaction is started. +// returns ERROR_NOT_AVAILABLE if no connection can be found and it +// should be queued +nsresult +nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, + bool onlyReusedConnection, + nsHttpTransaction *trans) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn " + "[ci=%s caps=%x]\n", + ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); + + nsHttpTransaction::Classifier classification = trans->Classification(); + PRUint8 caps = trans->Caps(); + + // no keep-alive means no pipelines either + if (!(caps & NS_HTTP_ALLOW_KEEPALIVE)) + caps = caps & ~NS_HTTP_ALLOW_PIPELINING; + + // 0 - If this should use spdy then dispatch it post haste. + // 1 - If there is connection pressure then see if we can pipeline this on + // a connection of a matching type instead of using a new conn + // 2 - If there is an idle connection, use it! + // 3 - if class == reval or script and there is an open conn of that type + // then pipeline onto shortest pipeline of that class if limits allow + // 4 - If we aren't up against our connection limit, + // then open a new one + // 5 - Try a pipeline if we haven't already - this will be unusual because + // it implies a low connection pressure situation where + // MakeNewConnection() failed.. that is possible, but unlikely, due to + // global limits + // 6 - no connection is available - queue it + + bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING); + + // step 0 + // look for existing spdy connection - that's always best because it is + // essentially pipelining without head of line blocking + + if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) { + nsRefPtr conn = GetSpdyPreferredConn(ent); + if (conn) { + LOG((" dispatch to spdy: [conn=%x]\n", conn.get())); + DispatchTransaction(ent, trans, conn); + return NS_OK; + } + } + + // step 1 + // If connection pressure, then we want to favor pipelining of any kind + if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) { + attemptedOptimisticPipeline = true; + if (AddToShortestPipeline(ent, trans, + classification, + mMaxOptimisticPipelinedRequests)) { + return NS_OK; + } + } + + // step 2 + // consider an idle persistent connection + if (caps & NS_HTTP_ALLOW_KEEPALIVE) { + nsRefPtr conn; while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; + ent->mIdleConns.RemoveElementAt(0); + mNumIdleConns--; + nsHttpConnection *temp = conn; + NS_RELEASE(temp); + // we check if the connection can be reused before even checking if // it is a "matching" connection. if (!conn->CanReuse()) { - LOG((" dropping stale connection: [conn=%x]\n", conn)); + LOG((" dropping stale connection: [conn=%x]\n", conn.get())); conn->Close(NS_ERROR_ABORT); - NS_RELEASE(conn); + conn = nsnull; } else { - LOG((" reusing connection [conn=%x]\n", conn)); + LOG((" reusing connection [conn=%x]\n", conn.get())); conn->EndIdleMonitoring(); } - ent->mIdleConns.RemoveElementAt(0); - mNumIdleConns--; - // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. ConditionallyStopPruneDeadConnectionsTimer(); } + if (conn) { + // This will update the class of the connection to be the class of + // the transaction dispatched on it. + AddActiveConn(conn, ent); + DispatchTransaction(ent, trans, conn); + return NS_OK; + } } - if (!conn) { - - // If the onlyReusedConnection parameter is TRUE, then GetConnection() - // does not create new transports under any circumstances. - if (onlyReusedConnection) - return; - - if (gHttpHandler->IsSpdyEnabled() && - ent->mConnInfo->UsingSSL() && - !ent->mConnInfo->UsingHttpProxy()) - { - // If this host is trying to negotiate a SPDY session right now, - // don't create any new connections until the result of the - // negotiation is known. - - if ((!ent->mTestedSpdy || ent->mUsingSpdy) && - (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) - return; + // step 3 + // consider pipelining scripts and revalidations + if (!attemptedOptimisticPipeline && + (classification == nsHttpTransaction::CLASS_REVALIDATION || + classification == nsHttpTransaction::CLASS_SCRIPT)) { + attemptedOptimisticPipeline = true; + if (AddToShortestPipeline(ent, trans, + classification, + mMaxOptimisticPipelinedRequests)) { + return NS_OK; } - - // Check if we need to purge an idle connection. Note that we may have - // removed one above; if so, this will be a no-op. We do this before - // checking the active connection limit to catch the case where we do - // have an idle connection, but the purge timer hasn't fired yet. - // XXX this just purges a random idle connection. we should instead - // enumerate the entire hash table to find the eldest idle connection. - if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) - mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); - - // Need to make a new TCP connection. First, we check if we've hit - // either the maximum connection limit globally or for this particular - // host or proxy. If we have, we're done. - if (AtActiveConnectionLimit(ent, trans->Caps())) { - LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]" - "at active connection limit - will queue\n", - ent->mConnInfo->HashKey().get())); - return; - } - - LOG(("nsHttpConnectionMgr::GetConnection Open Connection " - "%s %s ent=%p spdy=%d", - ent->mConnInfo->Host(), ent->mCoalescingKey.get(), - ent, ent->mUsingSpdy)); - - nsresult rv = CreateTransport(ent, trans); - if (NS_FAILED(rv)) - trans->Close(rv); - return; } - if (addConnToActiveList) { - // hold an owning ref to this connection - ent->mActiveConns.AppendElement(conn); - mNumActiveConns++; + // step 4 + if (!onlyReusedConnection && MakeNewConnection(ent, trans)) { + return NS_ERROR_IN_PROGRESS; } - NS_ADDREF(conn); - *result = conn; + // step 5 + if (caps & NS_HTTP_ALLOW_PIPELINING) { + if (AddToShortestPipeline(ent, trans, + classification, + mMaxPipelinedRequests)) { + return NS_OK; + } + } + + // step 6 + return NS_ERROR_NOT_AVAILABLE; /* queue it */ } +nsresult +nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpConnection *conn) +{ + PRUint8 caps = trans->Caps(); + PRInt32 priority = trans->Priority(); + + LOG(("nsHttpConnectionMgr::DispatchTransaction " + "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n", + ent->mConnInfo->HashKey().get(), trans, caps, conn, priority)); + + if (conn->UsingSpdy()) { + LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," + "Connection host = %s\n", + trans->ConnectionInfo()->Host(), + conn->ConnectionInfo()->Host())); + nsresult rv = conn->Activate(trans, caps, priority); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); + return rv; + } + + NS_ABORT_IF_FALSE(conn && !conn->Transaction(), + "DispatchTranaction() on non spdy active connection"); + + /* Use pipeline datastructure even if connection does not currently qualify + to pipeline this transaction because a different pipeline-eligible + transaction might be placed on the active connection */ + + nsRefPtr pipeline; + nsresult rv = BuildPipeline(ent, trans, getter_AddRefs(pipeline)); + if (!NS_SUCCEEDED(rv)) + return rv; + + nsRefPtr handle = new nsConnectionHandle(conn); + + // give the transaction the indirect reference to the connection. + pipeline->SetConnection(handle); + + if (!(caps & NS_HTTP_ALLOW_PIPELINING)) + conn->Classify(nsAHttpTransaction::CLASS_SOLO); + else + conn->Classify(trans->Classification()); + + rv = conn->Activate(pipeline, caps, priority); + if (NS_FAILED(rv)) { + LOG((" conn->Activate failed [rv=%x]\n", rv)); + ent->mActiveConns.RemoveElement(conn); + if (conn == ent->mYellowConnection) + ent->OnYellowComplete(); + mNumActiveConns--; + // sever back references to connection, and do so without triggering + // a call to ReclaimConnection ;-) + pipeline->SetConnection(nsnull); + NS_RELEASE(handle->mConn); + // destroy the connection + NS_RELEASE(conn); + } + + // As pipeline goes out of scope it will drop the last refernece to the + // pipeline if activation failed, in which case this will destroy + // the pipeline, which will cause each the transactions owned by the + // pipeline to be restarted. + + return rv; +} + +nsresult +nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, + nsAHttpTransaction *firstTrans, + nsHttpPipeline **result) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + /* form a pipeline here even if nothing is pending so that we + can stream-feed it as new transactions arrive */ + + /* the first transaction can go in unconditionally - 1 transaction + on a nsHttpPipeline object is not a real HTTP pipeline */ + + nsRefPtr pipeline = new nsHttpPipeline(); + pipeline->AddTransaction(firstTrans); + NS_ADDREF(*result = pipeline); + return NS_OK; +} + +nsresult +nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // since "adds" and "cancels" are processed asynchronously and because + // various events might trigger an "add" directly on the socket thread, + // we must take care to avoid dispatching a transaction that has already + // been canceled (see bug 190001). + if (NS_FAILED(trans->Status())) { + LOG((" transaction was canceled... dropping event!\n")); + return NS_OK; + } + + nsresult rv = NS_OK; + nsHttpConnectionInfo *ci = trans->ConnectionInfo(); + NS_ASSERTION(ci, "no connection info"); + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (!ent) { + nsHttpConnectionInfo *clone = ci->Clone(); + if (!clone) + return NS_ERROR_OUT_OF_MEMORY; + ent = new nsConnectionEntry(clone); + mCT.Put(ci->HashKey(), ent); + } + + // SPDY coalescing of hostnames means we might redirect from this + // connection entry onto the preferred one. + nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); + if (preferredEntry && (preferredEntry != ent)) { + LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " + "redirected via coalescing from %s to %s\n", trans, + ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); + + ent = preferredEntry; + } + + // If we are doing a force reload then close out any existing conns + // to this host so that changes in DNS, LBs, etc.. are reflected + if (trans->Caps() & NS_HTTP_CLEAR_KEEPALIVES) + ClosePersistentConnections(ent); + + // Check if the transaction already has a sticky reference to a connection. + // If so, then we can just use it directly by transferring its reference + // to the new connection variable instead of searching for a new one + + nsAHttpConnection *wrappedConnection = trans->Connection(); + nsRefPtr conn; + if (wrappedConnection) + conn = dont_AddRef(wrappedConnection->TakeHttpConnection()); + + if (conn) { + NS_ASSERTION(trans->Caps() & NS_HTTP_STICKY_CONNECTION, + "unexpected caps"); + NS_ABORT_IF_FALSE(((PRInt32)ent->mActiveConns.IndexOf(conn)) != -1, + "Sticky Connection Not In Active List"); + trans->SetConnection(nsnull); + rv = DispatchTransaction(ent, trans, conn); + } + else + rv = TryDispatchTransaction(ent, false, trans); + + if (NS_FAILED(rv)) { + LOG((" adding transaction to pending queue " + "[trans=%p pending-count=%u]\n", + trans, ent->mPendingQ.Length()+1)); + // put this transaction on the pending queue... + InsertTransactionSorted(ent->mPendingQ, trans); + NS_ADDREF(trans); + } + + return NS_OK; +} + + void nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, nsConnectionEntry *ent) @@ -1201,190 +1594,6 @@ nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, return NS_OK; } -nsresult -nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, - nsHttpTransaction *aTrans, - PRUint8 caps, - nsHttpConnection *conn) -{ - LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n", - ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); - nsresult rv; - - PRInt32 priority = aTrans->Priority(); - - if (conn->UsingSpdy()) { - LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," - "Connection host = %s\n", - aTrans->ConnectionInfo()->Host(), - conn->ConnectionInfo()->Host())); - rv = conn->Activate(aTrans, caps, priority); - NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); - return rv; - } - - nsConnectionHandle *handle = new nsConnectionHandle(conn); - if (!handle) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(handle); - - nsHttpPipeline *pipeline = nsnull; - nsAHttpTransaction *trans = aTrans; - - if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) { - LOG((" looking to build pipeline...\n")); - if (BuildPipeline(ent, trans, &pipeline)) - trans = pipeline; - } - - // give the transaction the indirect reference to the connection. - trans->SetConnection(handle); - - rv = conn->Activate(trans, caps, priority); - - if (NS_FAILED(rv)) { - LOG((" conn->Activate failed [rv=%x]\n", rv)); - ent->mActiveConns.RemoveElement(conn); - mNumActiveConns--; - // sever back references to connection, and do so without triggering - // a call to ReclaimConnection ;-) - trans->SetConnection(nsnull); - NS_RELEASE(handle->mConn); - // destroy the connection - NS_RELEASE(conn); - } - - // if we were unable to activate the pipeline, then this will destroy - // the pipeline, which will cause each the transactions owned by the - // pipeline to be restarted. - NS_IF_RELEASE(pipeline); - - NS_RELEASE(handle); - return rv; -} - -bool -nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, - nsAHttpTransaction *firstTrans, - nsHttpPipeline **result) -{ - if (mMaxPipelinedRequests < 2) - return false; - - nsHttpPipeline *pipeline = nsnull; - nsHttpTransaction *trans; - - PRUint32 i = 0, numAdded = 0; - while (i < ent->mPendingQ.Length()) { - trans = ent->mPendingQ[i]; - if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) { - if (numAdded == 0) { - pipeline = new nsHttpPipeline; - if (!pipeline) - return false; - pipeline->AddTransaction(firstTrans); - numAdded = 1; - } - pipeline->AddTransaction(trans); - - // remove transaction from pending queue - ent->mPendingQ.RemoveElementAt(i); - NS_RELEASE(trans); - - if (++numAdded == mMaxPipelinedRequests) - break; - } - else - ++i; // skip to next pending transaction - } - - if (numAdded == 0) - return false; - - LOG((" pipelined %u transactions\n", numAdded)); - NS_ADDREF(*result = pipeline); - return true; -} - -nsresult -nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) -{ - NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - - // since "adds" and "cancels" are processed asynchronously and because - // various events might trigger an "add" directly on the socket thread, - // we must take care to avoid dispatching a transaction that has already - // been canceled (see bug 190001). - if (NS_FAILED(trans->Status())) { - LOG((" transaction was canceled... dropping event!\n")); - return NS_OK; - } - - PRUint8 caps = trans->Caps(); - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - NS_ASSERTION(ci, "no connection info"); - - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - if (!ent) { - nsHttpConnectionInfo *clone = ci->Clone(); - if (!clone) - return NS_ERROR_OUT_OF_MEMORY; - ent = new nsConnectionEntry(clone); - if (!ent) - return NS_ERROR_OUT_OF_MEMORY; - mCT.Put(ci->HashKey(), ent); - } - - // SPDY coalescing of hostnames means we might redirect from this - // connection entry onto the preferred one. - nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); - if (preferredEntry && (preferredEntry != ent)) { - LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " - "redirected via coalescing from %s to %s\n", trans, - ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); - - ent = preferredEntry; - } - - // If we are doing a force reload then close out any existing conns - // to this host so that changes in DNS, LBs, etc.. are reflected - if (caps & NS_HTTP_CLEAR_KEEPALIVES) - ClosePersistentConnections(ent); - - // Check if the transaction already has a sticky reference to a connection. - // If so, then we can just use it directly by transferring its reference - // to the new connection var instead of calling GetConnection() to search - // for an available one. - - nsAHttpConnection *wrappedConnection = trans->Connection(); - nsHttpConnection *conn; - conn = wrappedConnection ? wrappedConnection->TakeHttpConnection() : nsnull; - - if (conn) { - NS_ASSERTION(caps & NS_HTTP_STICKY_CONNECTION, "unexpected caps"); - - trans->SetConnection(nsnull); - } - else - GetConnection(ent, trans, false, &conn); - - nsresult rv; - if (!conn) { - LOG((" adding transaction to pending queue [trans=%x pending-count=%u]\n", - trans, ent->mPendingQ.Length()+1)); - // put this transaction on the pending queue... - InsertTransactionSorted(ent->mPendingQ, trans); - NS_ADDREF(trans); - rv = NS_OK; - } - else { - rv = DispatchTransaction(ent, trans, caps, conn); - NS_RELEASE(conn); - } - - return rv; -} - // This function tries to dispatch the pending spdy transactions on // the connection entry sent in as an argument. It will do so on the // active spdy connection either in that same entry or in the @@ -1409,7 +1618,7 @@ nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) ent->mPendingQ.RemoveElementAt(index); - nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn); + nsresult rv = DispatchTransaction(ent, trans, conn); if (NS_FAILED(rv)) { // this cannot happen, but if due to some bug it does then // close the transaction @@ -1474,6 +1683,7 @@ nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) void nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); mCT.Enumerate(ShutdownPassCB, this); @@ -1505,7 +1715,8 @@ nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param) void nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) { - LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); @@ -1527,6 +1738,7 @@ nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) void nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; @@ -1558,6 +1770,7 @@ nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) void nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param; LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get())); @@ -1576,6 +1789,7 @@ nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param) void nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. @@ -1598,6 +1812,7 @@ nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) void nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param)); nsHttpConnection *conn = (nsHttpConnection *) param; @@ -1637,6 +1852,8 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) } if (ent->mActiveConns.RemoveElement(conn)) { + if (conn == ent->mYellowConnection) + ent->OnYellowComplete(); nsHttpConnection *temp = conn; NS_RELEASE(temp); mNumActiveConns--; @@ -1672,7 +1889,6 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) } else { LOG((" connection cannot be reused; closing connection\n")); - // make sure the connection is closed and release our reference. conn->Close(NS_ERROR_ABORT); } } @@ -1709,6 +1925,9 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param) case MAX_PIPELINED_REQUESTS: mMaxPipelinedRequests = value; break; + case MAX_OPTIMISTIC_PIPELINED_REQUESTS: + mMaxOptimisticPipelinedRequests = value; + break; default: NS_NOTREACHED("unexpected parameter name"); } @@ -1723,6 +1942,16 @@ nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() NS_RELEASE(mConnInfo); } +void +nsHttpConnectionMgr::OnMsgProcessFeedback(PRInt32, void *param) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param; + + PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData); + delete fb; +} + // Read Timeout Tick handlers void @@ -1732,12 +1961,6 @@ nsHttpConnectionMgr::ActivateTimeoutTick() LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() " "this=%p mReadTimeoutTick=%p\n")); - // right now the spdy timeout code is the only thing hooked to the timeout - // tick, so disable it if spdy is not being used. However pipelining code - // will also want this functionality soon. - if (!gHttpHandler->IsSpdyEnabled()) - return; - // The timer tick should be enabled if it is not already pending. // Upon running the tick will rearm itself if there are active // connections available. @@ -1755,8 +1978,7 @@ nsHttpConnectionMgr::ActivateTimeoutTick() NS_ABORT_IF_FALSE(!mReadTimeoutTickArmed, "timer tick armed"); mReadTimeoutTickArmed = true; - // pipeline will expect a 1000ms granuality - mReadTimeoutTick->Init(this, 15000, nsITimer::TYPE_REPEATING_SLACK); + mReadTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK); } void @@ -1867,6 +2089,12 @@ nsHttpConnectionMgr::nsConnectionHandle::IsReused() return mConn->IsReused(); } +void +nsHttpConnectionMgr::nsConnectionHandle::DontReuse() +{ + mConn->DontReuse(); +} + nsresult nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufLen) { @@ -1991,6 +2219,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() nsresult rv; + mPrimarySynStarted = mozilla::TimeStamp::Now(); rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn), getter_AddRefs(mStreamOut), @@ -2010,6 +2239,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams() { + mBackupSynStarted = mozilla::TimeStamp::Now(); nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport), getter_AddRefs(mBackupStreamIn), getter_AddRefs(mBackupStreamOut), @@ -2131,10 +2361,13 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks), getter_AddRefs(callbackTarget)); if (out == mStreamOut) { + mozilla::TimeDuration rtt = + mozilla::TimeStamp::Now() - mPrimarySynStarted; rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mSocketTransport, mStreamIn, mStreamOut, - callbacks, callbackTarget); + callbacks, callbackTarget, + PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mStreamOut = nsnull; @@ -2142,10 +2375,14 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) mSocketTransport = nsnull; } else { + mozilla::TimeDuration rtt = + mozilla::TimeStamp::Now() - mBackupSynStarted; + rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mBackupTransport, mBackupStreamIn, mBackupStreamOut, - callbacks, callbackTarget); + callbacks, callbackTarget, + PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mBackupStreamOut = nsnull; @@ -2167,7 +2404,6 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) NS_RELEASE(temp); gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction, - mTransaction->Caps(), conn); } else { @@ -2297,6 +2533,291 @@ nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection() return conn; } +bool +nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress() +{ + return mConn->IsProxyConnectInProgress(); +} + +PRUint32 +nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason) +{ + // no pipeline to cancel + return 0; +} + +nsAHttpTransaction::Classifier +nsHttpConnectionMgr::nsConnectionHandle::Classification() +{ + if (mConn) + return mConn->Classification(); + + LOG(("nsConnectionHandle::Classification this=%p " + "has null mConn using CLASS_SOLO default", this)); + return nsAHttpTransaction::CLASS_SOLO; +} + +void +nsHttpConnectionMgr:: +nsConnectionHandle::Classify(nsAHttpTransaction::Classifier newclass) +{ + if (mConn) + mConn->Classify(newclass); +} + +// nsConnectionEntry + +nsHttpConnectionMgr:: +nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) + : mConnInfo(ci) + , mPipelineState(PS_YELLOW) + , mYellowGoodEvents(0) + , mYellowBadEvents(0) + , mYellowConnection(nsnull) + , mGreenDepth(kPipelineOpen) + , mPipeliningPenalty(0) + , mUsingSpdy(false) + , mTestedSpdy(false) + , mSpdyPreferred(false) +{ + NS_ADDREF(mConnInfo); + if (gHttpHandler->GetPipelineAggressive()) { + mGreenDepth = kPipelineUnlimited; + mPipelineState = PS_GREEN; + } + mInitialGreenDepth = mGreenDepth; + memset(mPipeliningClassPenalty, 0, sizeof(PRInt16) * nsAHttpTransaction::CLASS_MAX); +} + +bool +nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining() +{ + return mPipelineState != nsHttpConnectionMgr::PS_RED; +} + +nsHttpConnectionMgr::PipeliningState +nsHttpConnectionMgr::nsConnectionEntry::PipelineState() +{ + return mPipelineState; +} + +void +nsHttpConnectionMgr:: +nsConnectionEntry::OnPipelineFeedbackInfo( + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *conn, + PRUint32 data) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mPipelineState == PS_YELLOW) { + if (info & kPipelineInfoTypeBad) + mYellowBadEvents++; + else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood)) + mYellowGoodEvents++; + } + + if (mPipelineState == PS_GREEN && info == GoodCompletedOK) { + PRInt32 depth = data; + LOG(("Transaction completed at pipeline depty of %d. Host = %s\n", + depth, mConnInfo->Host())); + + if (depth >= 3) + mGreenDepth = kPipelineUnlimited; + } + + nsAHttpTransaction::Classifier classification; + if (conn) + classification = conn->Classification(); + else if (info == BadInsufficientFraming || + info == BadUnexpectedLarge) + classification = (nsAHttpTransaction::Classifier) data; + else + classification = nsAHttpTransaction::CLASS_SOLO; + + if (gHttpHandler->GetPipelineAggressive() && + info & kPipelineInfoTypeBad && + info != BadExplicitClose && + info != RedVersionTooLow && + info != RedBannedServer && + info != RedCorruptedContent && + info != BadInsufficientFraming) { + LOG(("minor negative feedback ignored " + "because of pipeline aggressive mode")); + } + else if (info & kPipelineInfoTypeBad) { + if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) { + LOG(("transition to red from %d. Host = %s.\n", + mPipelineState, mConnInfo->Host())); + mPipelineState = PS_RED; + mPipeliningPenalty = 0; + } + + if (mLastCreditTime.IsNull()) + mLastCreditTime = mozilla::TimeStamp::Now(); + + // Red* events impact the host globally via mPipeliningPenalty, while + // Bad* events impact the per class penalty. + + // The individual penalties should be < 16bit-signed-maxint - 25000 + // (approx 7500). Penalties are paid-off either when something promising + // happens (a successful transaction, or promising headers) or when + // time goes by at a rate of 1 penalty point every 16 seconds. + + switch (info) { + case RedVersionTooLow: + mPipeliningPenalty += 1000; + break; + case RedBannedServer: + mPipeliningPenalty += 7000; + break; + case RedCorruptedContent: + mPipeliningPenalty += 7000; + break; + case RedCanceledPipeline: + mPipeliningPenalty += 60; + break; + case BadExplicitClose: + mPipeliningClassPenalty[classification] += 250; + break; + case BadSlowReadMinor: + mPipeliningClassPenalty[classification] += 5; + break; + case BadSlowReadMajor: + mPipeliningClassPenalty[classification] += 25; + break; + case BadInsufficientFraming: + mPipeliningClassPenalty[classification] += 7000; + break; + case BadUnexpectedLarge: + mPipeliningClassPenalty[classification] += 120; + break; + + default: + NS_ABORT_IF_FALSE(0, "Unknown Bad/Red Pipeline Feedback Event"); + } + + mPipeliningPenalty = PR_MIN(mPipeliningPenalty, 25000); + mPipeliningClassPenalty[classification] = PR_MIN(mPipeliningClassPenalty[classification], 25000); + + LOG(("Assessing red penalty to %s class %d for event %d. " + "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(), + classification, info, mPipeliningPenalty, classification, + mPipeliningClassPenalty[classification])); + } + else { + // hand out credits for neutral and good events such as + // "headers look ok" events + + mPipeliningPenalty = PR_MAX(mPipeliningPenalty - 1, 0); + mPipeliningClassPenalty[classification] = PR_MAX(mPipeliningClassPenalty[classification] - 1, 0); + } + + if (mPipelineState == PS_RED && !mPipeliningPenalty) + { + LOG(("transition %s to yellow\n", mConnInfo->Host())); + mPipelineState = PS_YELLOW; + mYellowConnection = nsnull; + } +} + +void +nsHttpConnectionMgr:: +nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn) +{ + NS_ABORT_IF_FALSE(!mYellowConnection && mPipelineState == PS_YELLOW, + "yellow connection already set or state is not yellow"); + mYellowConnection = conn; + mYellowGoodEvents = mYellowBadEvents = 0; +} + +void +nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete() +{ + if (mPipelineState == PS_YELLOW) { + if (mYellowGoodEvents && !mYellowBadEvents) { + LOG(("transition %s to green\n", mConnInfo->Host())); + mPipelineState = PS_GREEN; + mGreenDepth = mInitialGreenDepth; + } + else { + // The purpose of the yellow state is to witness at least + // one successful pipelined transaction without seeing any + // kind of negative feedback before opening the flood gates. + // If we haven't confirmed that, then transfer back to red. + LOG(("transition %s to red from yellow return\n", + mConnInfo->Host())); + mPipelineState = PS_RED; + } + } + + mYellowConnection = nsnull; +} + +void +nsHttpConnectionMgr::nsConnectionEntry::CreditPenalty() +{ + if (mLastCreditTime.IsNull()) + return; + + // Decrease penalty values by 1 for every 16 seconds + // (i.e 3.7 per minute, or 1000 every 4h20m) + + mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + mozilla::TimeDuration elapsedTime = now - mLastCreditTime; + PRUint32 creditsEarned = + static_cast(elapsedTime.ToSeconds()) >> 4; + + bool failed = false; + if (creditsEarned > 0) { + mPipeliningPenalty = + PR_MAX(PRInt32(mPipeliningPenalty - creditsEarned), 0); + if (mPipeliningPenalty > 0) + failed = true; + + for (PRInt32 i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) { + mPipeliningClassPenalty[i] = + PR_MAX(PRInt32(mPipeliningClassPenalty[i] - creditsEarned), 0); + failed = failed || (mPipeliningClassPenalty[i] > 0); + } + + // update last credit mark to reflect elapsed time + mLastCreditTime += + mozilla::TimeDuration::FromSeconds(creditsEarned << 4); + } + else { + failed = true; /* just assume this */ + } + + // If we are no longer red then clear the credit counter - you only + // get credits for time spent in the red state + if (!failed) + mLastCreditTime = mozilla::TimeStamp(); /* reset to null timestamp */ + + if (mPipelineState == PS_RED && !mPipeliningPenalty) + { + LOG(("transition %s to yellow based on time credit\n", + mConnInfo->Host())); + mPipelineState = PS_YELLOW; + mYellowConnection = nsnull; + } +} + +PRUint32 +nsHttpConnectionMgr:: +nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass) +{ + // Still subject to configuration limit no matter return value + + if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0)) + return 0; + + if (mPipelineState == PS_YELLOW) + return kPipelineRestricted; + + return mGreenDepth; +} + bool nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent() { diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index 51c56b19c962..19bb33594340 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -49,12 +49,13 @@ #include "nsAutoPtr.h" #include "mozilla/ReentrantMonitor.h" #include "nsISocketTransportService.h" +#include "mozilla/TimeStamp.h" #include "nsIObserver.h" #include "nsITimer.h" #include "nsIX509Cert3.h" -class nsHttpPipeline; +#include "nsHttpPipeline.h" //----------------------------------------------------------------------------- @@ -72,7 +73,8 @@ public: MAX_PERSISTENT_CONNECTIONS_PER_HOST, MAX_PERSISTENT_CONNECTIONS_PER_PROXY, MAX_REQUEST_DELAY, - MAX_PIPELINED_REQUESTS + MAX_PIPELINED_REQUESTS, + MAX_OPTIMISTIC_PIPELINED_REQUESTS }; //------------------------------------------------------------------------- @@ -87,7 +89,8 @@ public: PRUint16 maxPersistentConnectionsPerHost, PRUint16 maxPersistentConnectionsPerProxy, PRUint16 maxRequestDelay, - PRUint16 maxPipelinedRequests); + PRUint16 maxPipelinedRequests, + PRUint16 maxOptimisticPipelinedRequests); nsresult Shutdown(); //------------------------------------------------------------------------- @@ -138,17 +141,80 @@ public: void ReportSpdyAlternateProtocol(nsHttpConnection *); void RemoveSpdyAlternateProtocol(nsACString &key); + // Pipielining Interfaces and Datatypes + + const static PRUint32 kPipelineInfoTypeMask = 0xffff0000; + const static PRUint32 kPipelineInfoIDMask = ~kPipelineInfoTypeMask; + + const static PRUint32 kPipelineInfoTypeRed = 0x00010000; + const static PRUint32 kPipelineInfoTypeBad = 0x00020000; + const static PRUint32 kPipelineInfoTypeNeutral = 0x00040000; + const static PRUint32 kPipelineInfoTypeGood = 0x00080000; + + enum PipelineFeedbackInfoType + { + // Used when an HTTP response less than 1.1 is received + RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001, + + // Used when a HTTP Server response header that is on the banned from + // pipelining list is received + RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002, + + // Used when a response is terminated early, when it fails an + // integrity check such as assoc-req or when a 304 contained a Last-Modified + // differnet than the entry being validated. + RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004, + + // Used when a pipeline is only partly satisfied - for instance if the + // server closed the connection after responding to the first + // request but left some requests unprocessed. + RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005, + + // Used when a connection that we expected to stay persistently open + // was closed by the server. Not used when simply timed out. + BadExplicitClose = kPipelineInfoTypeBad | 0x0003, + + // Used when there is a gap of around 400 - 1200ms in between data being + // read from the server + BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006, + + // Used when there is a gap of > 1200ms in between data being + // read from the server + BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007, + + // Used when a response is received that is not framed with either chunked + // encoding or a complete content length. + BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008, + + // Used when a very large response is recevied in a potential pipelining + // context. Large responses cause head of line blocking. + BadUnexpectedLarge = kPipelineInfoTypeBad | 0x000B, + + // Used when a response is received that has headers that appear to support + // pipelining. + NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009, + + // Used when a response is received successfully to a pipelined request. + GoodCompletedOK = kPipelineInfoTypeGood | 0x000A + }; + + // called to provide information relevant to the pipelining manager + // may be called from any thread + void PipelineFeedbackInfo(nsHttpConnectionInfo *, + PipelineFeedbackInfoType info, + nsHttpConnection *, + PRUint32); + + void ReportFailedToProcess(nsIURI *uri); + //------------------------------------------------------------------------- // NOTE: functions below may be called only on the socket thread. //------------------------------------------------------------------------- - // removes the next transaction for the specified connection from the - // pending transaction queue. - void AddTransactionToPipeline(nsHttpPipeline *); - // called to force the transaction queue to be processed once more, giving // preference to the specified connection. nsresult ProcessPendingQ(nsHttpConnectionInfo *); + bool ProcessPendingQForEntry(nsHttpConnectionInfo *); // This is used to force an idle connection to be closed and removed from // the idle connection list. It is called when the idle connection detects @@ -160,26 +226,41 @@ public: // bit different. void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy); + + bool SupportsPipelining(nsHttpConnectionInfo *); + private: virtual ~nsHttpConnectionMgr(); - class nsHalfOpenSocket; + + enum PipeliningState { + // Host has proven itself pipeline capable through past experience and + // large pipeline depths are allowed on multiple connections. + PS_GREEN, + + // Not enough information is available yet with this host to be certain + // of pipeline capability. Small pipelines on a single connection are + // allowed in order to decide whether or not to proceed to green. + PS_YELLOW, + + // One or more bad events has happened that indicate that pipelining + // to this host (or a particular type of transaction with this host) + // is a bad idea. Pipelining is not currently allowed, but time and + // other positive experiences will eventually allow it to try again. + PS_RED + }; + class nsHalfOpenSocket; + // nsConnectionEntry // // mCT maps connection info hash key to nsConnectionEntry object, which // contains list of active and idle connections as well as the list of // pending transactions. // - struct nsConnectionEntry + class nsConnectionEntry { - nsConnectionEntry(nsHttpConnectionInfo *ci) - : mConnInfo(ci), - mUsingSpdy(false), - mTestedSpdy(false), - mSpdyPreferred(false) - { - NS_ADDREF(mConnInfo); - } + public: + nsConnectionEntry(nsHttpConnectionInfo *ci); ~nsConnectionEntry(); nsHttpConnectionInfo *mConnInfo; @@ -188,6 +269,54 @@ private: nsTArray mIdleConns; // idle persistent connections nsTArray mHalfOpens; + // Pipeline depths for various states + const static PRUint32 kPipelineUnlimited = 1024; // fully open - extended green + const static PRUint32 kPipelineOpen = 6; // 6 on each conn - normal green + const static PRUint32 kPipelineRestricted = 2; // 2 on just 1 conn in yellow + + nsHttpConnectionMgr::PipeliningState PipelineState(); + void OnPipelineFeedbackInfo( + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *, PRUint32); + bool SupportsPipelining(); + PRUint32 MaxPipelineDepth(nsAHttpTransaction::Classifier classification); + void CreditPenalty(); + + nsHttpConnectionMgr::PipeliningState mPipelineState; + + void SetYellowConnection(nsHttpConnection *); + void OnYellowComplete(); + PRUint32 mYellowGoodEvents; + PRUint32 mYellowBadEvents; + nsHttpConnection *mYellowConnection; + + // initialGreenDepth is the max depth of a pipeline when you first + // transition to green. Normally this is kPipelineOpen, but it can + // be kPipelineUnlimited in aggressive mode. + PRUint32 mInitialGreenDepth; + + // greenDepth is the current max allowed depth of a pipeline when + // in the green state. Normally this starts as kPipelineOpen and + // grows to kPipelineUnlimited after a pipeline of depth 3 has been + // successfully transacted. + PRUint32 mGreenDepth; + + // pipeliningPenalty is the current amount of penalty points this host + // entry has earned for participating in events that are not conducive + // to good pipelines - such as head of line blocking, canceled pipelines, + // etc.. penalties are paid back either through elapsed time or simply + // healthy transactions. Having penalty points means that this host is + // not currently eligible for pipelines. + PRInt16 mPipeliningPenalty; + + // some penalty points only apply to particular classifications of + // transactions - this allows a server that perhaps has head of line + // blocking problems on CGI queries to still serve JS pipelined. + PRInt16 mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX]; + + // for calculating penalty repair credits + mozilla::TimeStamp mLastCreditTime; + // Spdy sometimes resolves the address in the socket manager in order // to re-coalesce sharded HTTP hosts. The dotted decimal address is // combined with the Anonymous flag from the connection information @@ -272,6 +401,9 @@ private: nsCOMPtr mStreamOut; nsCOMPtr mStreamIn; + mozilla::TimeStamp mPrimarySynStarted; + mozilla::TimeStamp mBackupSynStarted; + // for syn retry nsCOMPtr mSynTimer; nsCOMPtr mBackupTransport; @@ -296,7 +428,7 @@ private: PRUint16 mMaxPersistConnsPerProxy; PRUint16 mMaxRequestDelay; // in seconds PRUint16 mMaxPipelinedRequests; - + PRUint16 mMaxOptimisticPipelinedRequests; bool mIsShuttingDown; //------------------------------------------------------------------------- @@ -310,12 +442,18 @@ private: static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr &, void *); static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr &, void *); bool ProcessPendingQForEntry(nsConnectionEntry *); + bool IsUnderPressure(nsConnectionEntry *ent, + nsHttpTransaction::Classifier classification); bool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); - void GetConnection(nsConnectionEntry *, nsHttpTransaction *, - bool, nsHttpConnection **); - nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, - PRUint8 caps, nsHttpConnection *); - bool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); + nsresult TryDispatchTransaction(nsConnectionEntry *ent, + bool onlyReusedConnection, + nsHttpTransaction *trans); + nsresult DispatchTransaction(nsConnectionEntry *, + nsHttpTransaction *, + nsHttpConnection *); + nsresult BuildPipeline(nsConnectionEntry *, + nsAHttpTransaction *, + nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTargetIfOnline(); void ClosePersistentConnections(nsConnectionEntry *ent); @@ -324,6 +462,13 @@ private: void StartedConnect(); void RecvdConnect(); + bool MakeNewConnection(nsConnectionEntry *ent, + nsHttpTransaction *trans); + bool AddToShortestPipeline(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpTransaction::Classifier classification, + PRUint16 depthLimit); + // Manage the preferred spdy connection entry for this address nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry); void RemoveSpdyPreferredEnt(nsACString &aDottedDecimal); @@ -396,6 +541,7 @@ private: void OnMsgReclaimConnection (PRInt32, void *); void OnMsgUpdateParam (PRInt32, void *); void OnMsgClosePersistentConnections (PRInt32, void *); + void OnMsgProcessFeedback (PRInt32, void *); // Total number of active connections in all of the ConnectionEntry objects // that are accessed from mCT connection table. diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index dcdf729368bf..5702498a4c05 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -185,11 +185,16 @@ nsHttpHandler::nsHttpHandler() , mMaxConnectionsPerServer(8) , mMaxPersistentConnectionsPerServer(2) , mMaxPersistentConnectionsPerProxy(4) - , mMaxPipelinedRequests(2) + , mMaxPipelinedRequests(32) + , mMaxOptimisticPipelinedRequests(4) + , mPipelineAggressive(false) + , mMaxPipelineObjectSize(300000) + , mPipelineReadTimeout(PR_MillisecondsToInterval(10000)) , mRedirectionLimit(10) , mPhishyUserPassLength(1) , mQoSBits(0x00) , mPipeliningOverSSL(false) + , mEnforceAssocReq(false) , mInPrivateBrowsingMode(PRIVATE_BROWSING_UNKNOWN) , mLastUniqueID(NowInSeconds()) , mSessionStartTime(0) @@ -338,6 +343,7 @@ nsHttpHandler::Init() mObserverService->AddObserver(this, "net:clear-active-logins", true); mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true); mObserverService->AddObserver(this, "net:prune-dead-connections", true); + mObserverService->AddObserver(this, "net:failed-to-process-uri", true); } return NS_OK; @@ -363,7 +369,8 @@ nsHttpHandler::InitConnectionMgr() mMaxPersistentConnectionsPerServer, mMaxPersistentConnectionsPerProxy, mMaxRequestDelay, - mMaxPipelinedRequests); + mMaxPipelinedRequests, + mMaxOptimisticPipelinedRequests); return rv; } @@ -1015,13 +1022,47 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) { rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val); if (NS_SUCCEEDED(rv)) { - mMaxPipelinedRequests = clamped(val, 1, NS_HTTP_MAX_PIPELINED_REQUESTS); + mMaxPipelinedRequests = clamped(val, 1, 0xffff); if (mConnMgr) mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS, mMaxPipelinedRequests); } } + if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) { + rv = prefs-> + GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff); + if (mConnMgr) + mConnMgr->UpdateParam + (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS, + mMaxOptimisticPipelinedRequests); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar); + if (NS_SUCCEEDED(rv)) + mPipelineAggressive = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.maxsize"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxPipelineObjectSize = + static_cast(clamped(val, 1000, 100000000)); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.read-timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("pipelining.read-timeout"), &val); + if (NS_SUCCEEDED(rv)) { + mPipelineReadTimeout = + PR_MillisecondsToInterval(clamped(val, 500, 0xffff)); + } + } + if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) { rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar); if (NS_SUCCEEDED(rv)) @@ -1103,6 +1144,13 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) } } + if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) { + cVar = false; + rv = prefs->GetBoolPref(HTTP_PREF("assoc-req.enforce"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnforceAssocReq = cVar; + } + // enable Persistent caching for HTTPS - bug#205921 if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) { cVar = false; @@ -1587,6 +1635,11 @@ nsHttpHandler::Observe(nsISupports *subject, mConnMgr->PruneDeadConnections(); } } + else if (strcmp(topic, "net:failed-to-process-uri") == 0) { + nsCOMPtr uri = do_QueryInterface(subject); + if (uri && mConnMgr) + mConnMgr->ReportFailedToProcess(uri); + } return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index b1d02acb9e24..210c1bfdeaf3 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -110,6 +110,7 @@ public: PRUint16 GetIdleSynTimeout() { return mIdleSynTimeout; } bool FastFallbackToIPv4() { return mFastFallbackToIPv4; } PRUint32 MaxSocketCount(); + bool EnforceAssocReq() { return mEnforceAssocReq; } bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; } bool IsTelemetryEnabled() { return mTelemetryEnabled; } @@ -231,6 +232,13 @@ public: static nsresult GenerateHostPort(const nsCString& host, PRInt32 port, nsCString& hostLine); + bool GetPipelineAggressive() { return mPipelineAggressive; } + void GetMaxPipelineObjectSize(PRInt64 &outVal) + { + outVal = mMaxPipelineObjectSize; + } + PRIntervalTime GetPipelineTimeout() { return mPipelineReadTimeout; } + private: // @@ -287,7 +295,12 @@ private: PRUint8 mMaxConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerProxy; - PRUint8 mMaxPipelinedRequests; + PRUint16 mMaxPipelinedRequests; + PRUint16 mMaxOptimisticPipelinedRequests; + bool mPipelineAggressive; + PRInt64 mMaxPipelineObjectSize; + + PRIntervalTime mPipelineReadTimeout; PRUint8 mRedirectionLimit; @@ -300,6 +313,7 @@ private: PRUint8 mQoSBits; bool mPipeliningOverSSL; + bool mEnforceAssocReq; // cached value of whether or not the browser is in private browsing mode. enum { diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index 6eb1908bfeee..c75336b5af83 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -98,6 +98,7 @@ nsHttpPipeline::nsHttpPipeline() , mRequestIsPartial(false) , mResponseIsPartial(false) , mClosed(false) + , mUtilizedPipeline(false) , mPushBackBuf(nsnull) , mPushBackLen(0) , mPushBackMax(0) @@ -124,19 +125,62 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) { LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans)); + if (mRequestQ.Length() || mResponseQ.Length()) + mUtilizedPipeline = true; + NS_ADDREF(trans); mRequestQ.AppendElement(trans); - - if (mConnection && !mClosed) { - trans->SetConnection(this); - - if (mRequestQ.Length() == 1) - mConnection->ResumeSend(); + PRUint32 qlen = PipelineDepth(); + + if (qlen != 1) { + trans->SetPipelinePosition(qlen); + } + else { + // do it for this case in case an idempotent cancellation + // is being repeated and an old value needs to be cleared + trans->SetPipelinePosition(0); } + // trans->SetConnection() needs to be updated to point back at + // the pipeline object. + trans->SetConnection(this); + + if (mConnection && !mClosed && mRequestQ.Length() == 1) + mConnection->ResumeSend(); + return NS_OK; } +PRUint32 +nsHttpPipeline::PipelineDepth() +{ + return mRequestQ.Length() + mResponseQ.Length(); +} + +nsresult +nsHttpPipeline::SetPipelinePosition(PRInt32 position) +{ + nsAHttpTransaction *trans = Response(0); + if (trans) + return trans->SetPipelinePosition(position); + return NS_OK; +} + +PRInt32 +nsHttpPipeline::PipelinePosition() +{ + nsAHttpTransaction *trans = Response(0); + if (trans) + return trans->PipelinePosition(); + return 2; +} + +nsHttpPipeline * +nsHttpPipeline::QueryPipeline() +{ + return this; +} + //----------------------------------------------------------------------------- // nsHttpPipeline::nsISupports //----------------------------------------------------------------------------- @@ -164,9 +208,26 @@ nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans, NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); + + nsRefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); + NS_ABORT_IF_FALSE(ci, "no connection info"); + + bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci); + // trans has now received its response headers; forward to the real connection - return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset); + nsresult rv = mConnection->OnHeadersAvailable(trans, + requestHead, + responseHead, + reset); + + if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci)) + // The received headers have expanded the eligible + // pipeline depth for this connection + gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci); + + return rv; } nsresult @@ -191,7 +252,7 @@ nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n", this, trans, reason)); - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(NS_FAILED(reason), "expecting failure code"); // the specified transaction is to be closed with the given "reason" @@ -219,21 +280,27 @@ nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) killPipeline = true; } + // Marking this connection as non-reusable prevents other items from being + // added to it and causes it to be torn down soon. Don't tear it down yet + // as that would prevent Response(0) from being processed. + DontReuse(); + trans->Close(reason); NS_RELEASE(trans); - if (killPipeline) { - if (mConnection) - mConnection->CloseTransaction(this, reason); - else - Close(reason); - } + if (killPipeline) + // reschedule anything from this pipeline onto a different connection + CancelPipeline(reason); } void nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result) { - NS_ASSERTION(mConnection, "no connection"); + if (!mConnection) { + *result = nsnull; + return; + } + mConnection->GetConnectionInfo(result); } @@ -261,7 +328,16 @@ nsHttpPipeline::IsPersistent() bool nsHttpPipeline::IsReused() { - return true; // pipelining requires this + if (!mUtilizedPipeline && mConnection) + return mConnection->IsReused(); + return true; +} + +void +nsHttpPipeline::DontReuse() +{ + if (mConnection) + mConnection->DontReuse(); } nsresult @@ -269,7 +345,7 @@ nsHttpPipeline::PushBack(const char *data, PRUint32 length) { LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length)); - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mPushBackLen == 0, "push back buffer already has data!"); // If we have no chance for a pipeline (e.g. due to an Upgrade) @@ -309,6 +385,13 @@ nsHttpPipeline::PushBack(const char *data, PRUint32 length) return NS_OK; } +bool +nsHttpPipeline::IsProxyConnectInProgress() +{ + NS_ABORT_IF_FALSE(mConnection, "no connection"); + return mConnection->IsProxyConnectInProgress(); +} + bool nsHttpPipeline::LastTransactionExpectedNoContent() { @@ -339,6 +422,24 @@ nsHttpPipeline::Transport() return mConnection->Transport(); } +nsAHttpTransaction::Classifier +nsHttpPipeline::Classification() +{ + if (mConnection) + return mConnection->Classification(); + + LOG(("nsHttpPipeline::Classification this=%p " + "has null mConnection using CLASS_SOLO default", this)); + return nsAHttpTransaction::CLASS_SOLO; +} + +void +nsHttpPipeline::Classify(nsAHttpTransaction::Classifier newclass) +{ + if (mConnection) + mConnection->Classify(newclass); +} + void nsHttpPipeline::SetSSLConnectFailed() { @@ -373,12 +474,6 @@ nsHttpPipeline::TakeSubTransactions( if (mResponseQ.Length() || mRequestIsPartial) return NS_ERROR_ALREADY_OPENED; - // request queue could be empty if it was already canceled, in which - // case it is safe to treat this as a case without any - // sub-transactions. - if (!mRequestQ.Length()) - return NS_ERROR_NOT_IMPLEMENTED; - PRInt32 i, count = mRequestQ.Length(); for (i = 0; i < count; ++i) { nsAHttpTransaction *trans = Request(i); @@ -392,7 +487,7 @@ nsHttpPipeline::TakeSubTransactions( } //----------------------------------------------------------------------------- -// nsHttpPipeline::nsAHttpConnection +// nsHttpPipeline::nsAHttpTransaction //----------------------------------------------------------------------------- void @@ -404,10 +499,6 @@ nsHttpPipeline::SetConnection(nsAHttpConnection *conn) NS_ASSERTION(!mConnection, "already have a connection"); NS_IF_ADDREF(mConnection = conn); - - PRInt32 i, count = mRequestQ.Length(); - for (i=0; iSetConnection(this); } nsAHttpConnection * @@ -425,8 +516,13 @@ nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result, { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - // return security callbacks from first request + // depending on timing this could be either the request or the response + // that is needed - but they both go to the same host. A request for these + // callbacks directly in nsHttpTransaction would not make a distinction + // over whether the the request had been transmitted yet. nsAHttpTransaction *trans = Request(0); + if (!trans) + trans = Response(0); if (trans) trans->GetSecurityCallbacks(result, target); else { @@ -546,6 +642,16 @@ nsHttpPipeline::Status() return mStatus; } +PRUint8 +nsHttpPipeline::Caps() +{ + nsAHttpTransaction *trans = Request(0); + if (!trans) + trans = Response(0); + + return trans ? trans->Caps() : 0; +} + PRUint32 nsHttpPipeline::Available() { @@ -632,6 +738,17 @@ nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer, nsresult rv; trans = Response(0); + // This code deals with the establishment of a CONNECT tunnel through + // an HTTP proxy. It allows the connection to do the CONNECT/200 + // HTTP transaction to establish an SSL tunnel as a precursor to the + // actual pipeline of regular HTTP transactions. + if (!trans && mRequestQ.Length() && + mConnection->IsProxyConnectInProgress()) { + LOG(("nsHttpPipeline::WriteSegments [this=%p] Forced Delegation\n", + this)); + trans = Request(0); + } + if (!trans) { if (mRequestQ.Length() > 0) rv = NS_BASE_STREAM_WOULD_BLOCK; @@ -654,7 +771,10 @@ nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer, // ask the connection manager to add additional transactions // to our pipeline. - gHttpHandler->ConnMgr()->AddTransactionToPipeline(this); + nsRefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); + if (ci) + gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci); } else mResponseIsPartial = true; @@ -683,6 +803,52 @@ nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer, return rv; } +PRUint32 +nsHttpPipeline::CancelPipeline(nsresult originalReason) +{ + PRUint32 i, reqLen, respLen, total; + nsAHttpTransaction *trans; + + reqLen = mRequestQ.Length(); + respLen = mResponseQ.Length(); + total = reqLen + respLen; + + // don't count the first response, if presnet + if (respLen) + total--; + + if (!total) + return 0; + + // any pending requests can ignore this error and be restarted + // unless it is during a CONNECT tunnel request + for (i = 0; i < reqLen; ++i) { + trans = Request(i); + if (mConnection && mConnection->IsProxyConnectInProgress()) + trans->Close(originalReason); + else + trans->Close(NS_ERROR_NET_RESET); + NS_RELEASE(trans); + } + mRequestQ.Clear(); + + // any pending responses can be restarted except for the first one, + // that we might want to finish on this pipeline or cancel individually + for (i = 1; i < respLen; ++i) { + trans = Response(i); + trans->Close(NS_ERROR_NET_RESET); + NS_RELEASE(trans); + } + + if (respLen > 1) + mResponseQ.TruncateLength(1); + + DontReuse(); + Classify(nsAHttpTransaction::CLASS_SOLO); + + return total; +} + void nsHttpPipeline::Close(nsresult reason) { @@ -697,38 +863,37 @@ nsHttpPipeline::Close(nsresult reason) mStatus = reason; mClosed = true; - PRUint32 i, count; - nsAHttpTransaction *trans; + nsRefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); + PRUint32 numRescheduled = CancelPipeline(reason); - // any pending requests can ignore this error and be restarted - count = mRequestQ.Length(); - for (i=0; iConnMgr()->PipelineFeedbackInfo( + ci, nsHttpConnectionMgr::RedCanceledPipeline, nsnull, 0); + + nsAHttpTransaction *trans = Response(0); + if (!trans) + return; + + // The current transaction can be restarted via reset + // if the response has not started to arrive and the reason + // for failure is innocuous (e.g. not an SSL error) + if (!mResponseIsPartial && + (reason == NS_ERROR_NET_RESET || + reason == NS_OK || + reason == NS_BASE_STREAM_CLOSED)) { trans->Close(NS_ERROR_NET_RESET); - NS_RELEASE(trans); } - mRequestQ.Clear(); - - trans = Response(0); - if (trans) { - // if the current response is partially complete, then it cannot be - // restarted and will have to fail with the status of the connection. - if (mResponseIsPartial) - trans->Close(reason); - else - trans->Close(NS_ERROR_NET_RESET); - NS_RELEASE(trans); - - // any remaining pending responses can be restarted - count = mResponseQ.Length(); - for (i=1; iClose(NS_ERROR_NET_RESET); - NS_RELEASE(trans); - } - mResponseQ.Clear(); + else { + trans->Close(reason); } + NS_RELEASE(trans); + mResponseQ.Clear(); } nsresult @@ -764,6 +929,13 @@ nsHttpPipeline::FillSendBuf() while ((trans = Request(0)) != nsnull) { avail = trans->Available(); if (avail) { + // if there is already a response in the responseq then this + // new data comprises a pipeline. Update the transaction in the + // response queue to reflect that if necessary. We are now sending + // out a request while we haven't received all responses. + nsAHttpTransaction *response = Response(0); + if (response && !response->PipelinePosition()) + response->SetPipelinePosition(1); rv = trans->ReadSegments(this, avail, &n); if (NS_FAILED(rv)) return rv; @@ -794,6 +966,10 @@ nsHttpPipeline::FillSendBuf() NS_NET_STATUS_WAITING_FOR, mSendingToProgress); } + + // It would be good to re-enable data read handlers via ResumeRecv() + // except the read handler code can be synchronously dispatched on + // the stack. } else mRequestIsPartial = true; diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h index ca45fc30f2d0..dd6b98acbfb7 100644 --- a/netwerk/protocol/http/nsHttpPipeline.h +++ b/netwerk/protocol/http/nsHttpPipeline.h @@ -60,8 +60,6 @@ public: nsHttpPipeline(); virtual ~nsHttpPipeline(); - nsresult AddTransaction(nsAHttpTransaction *); - private: nsresult FillSendBuf(); @@ -84,6 +82,9 @@ private: return mResponseQ[i]; } + // overload of nsAHttpTransaction::QueryPipeline() + nsHttpPipeline *QueryPipeline(); + nsAHttpConnection *mConnection; nsTArray mRequestQ; // array of transactions nsTArray mResponseQ; // array of transactions @@ -99,6 +100,10 @@ private: // indicates whether or not the pipeline has been explicitly closed. bool mClosed; + // indicates whether or not a true pipeline (more than 1 request without + // a synchronous response) has been formed. + bool mUtilizedPipeline; + // used when calling ReadSegments/WriteSegments on a transaction. nsAHttpSegmentReader *mReader; nsAHttpSegmentWriter *mWriter; diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 4571429d0b54..30c644c26aa2 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -64,6 +64,8 @@ #include "mozilla/FunctionTimer.h" +using namespace mozilla; + //----------------------------------------------------------------------------- #ifdef DEBUG @@ -119,6 +121,8 @@ nsHttpTransaction::nsHttpTransaction() , mPriority(0) , mRestartCount(0) , mCaps(0) + , mClassification(CLASS_GENERAL) + , mPipelinePosition(0) , mClosed(false) , mConnected(false) , mHaveStatusLine(false) @@ -134,8 +138,14 @@ nsHttpTransaction::nsHttpTransaction() , mSSLConnectFailed(false) , mHttpResponseMatched(false) , mPreserveStream(false) + , mToReadBeforeRestart(0) + , mReportedStart(false) + , mReportedResponseHeader(false) + , mForTakeResponseHead(nsnull) + , mTakenResponseHeader(false) { LOG(("Creating nsHttpTransaction @%x\n", this)); + gHttpHandler->GetMaxPipelineObjectSize(mMaxPipelineObjectSize); } nsHttpTransaction::~nsHttpTransaction() @@ -146,9 +156,44 @@ nsHttpTransaction::~nsHttpTransaction() NS_IF_RELEASE(mConnInfo); delete mResponseHead; + delete mForTakeResponseHead; delete mChunkedDecoder; } +nsHttpTransaction::Classifier +nsHttpTransaction::Classify() +{ + if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) + return (mClassification = CLASS_SOLO); + + if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) || + mRequestHead->PeekHeader(nsHttp::If_None_Match)) + return (mClassification = CLASS_REVALIDATION); + + const char *accept = mRequestHead->PeekHeader(nsHttp::Accept); + if (accept && !PL_strncmp(accept, "image/", 6)) + return (mClassification = CLASS_IMAGE); + + if (accept && !PL_strncmp(accept, "text/css", 8)) + return (mClassification = CLASS_SCRIPT); + + mClassification = CLASS_GENERAL; + + PRInt32 queryPos = mRequestHead->RequestURI().FindChar('?'); + if (queryPos == kNotFound) { + if (StringEndsWith(mRequestHead->RequestURI(), + NS_LITERAL_CSTRING(".js"))) + mClassification = CLASS_SCRIPT; + } + else if (queryPos >= 3 && + Substring(mRequestHead->RequestURI(), queryPos - 3, 3). + EqualsLiteral(".js")) { + mClassification = CLASS_SCRIPT; + } + + return mClassification; +} + nsresult nsHttpTransaction::Init(PRUint8 caps, nsHttpConnectionInfo *cinfo, @@ -300,6 +345,8 @@ nsHttpTransaction::Init(PRUint8 caps, nsIOService::gDefaultSegmentCount); if (NS_FAILED(rv)) return rv; + Classify(); + NS_ADDREF(*responseBody = mPipeIn); return NS_OK; } @@ -313,13 +360,30 @@ nsHttpTransaction::Connection() nsHttpResponseHead * nsHttpTransaction::TakeResponseHead() { + NS_ABORT_IF_FALSE(!mTakenResponseHeader, "TakeResponseHead called 2x"); + + // Lock RestartInProgress() and TakeResponseHead() against main thread + MutexAutoLock lock(*nsHttp::GetLock()); + + mTakenResponseHeader = true; + + // Even in OnStartRequest() the headers won't be available if we were + // canceled if (!mHaveAllHeaders) { NS_WARNING("response headers not available or incomplete"); return nsnull; } - nsHttpResponseHead *head = mResponseHead; - mResponseHead = nsnull; + // Prefer mForTakeResponseHead over mResponseHead + nsHttpResponseHead *head; + if (mForTakeResponseHead) { + head = mForTakeResponseHead; + mForTakeResponseHead = nsnull; + } + else { + head = mResponseHead; + mResponseHead = nsnull; + } return head; } @@ -405,13 +469,14 @@ nsHttpTransaction::OnTransportStatus(nsITransport* transport, PR_Now(), LL_ZERO, EmptyCString()); // report the status and progress - mActivityDistributor->ObserveActivity( - mChannel, - NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, - static_cast(status), - PR_Now(), - progress, - EmptyCString()); + if (!mRestartInProgressVerifier.Active()) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, + static_cast(status), + PR_Now(), + progress, + EmptyCString()); } // nsHttpChannel synthesizes progress events in OnDataAvailable @@ -456,6 +521,12 @@ nsHttpTransaction::Status() return mStatus; } +PRUint8 +nsHttpTransaction::Caps() +{ + return mCaps; +} + PRUint32 nsHttpTransaction::Available() { @@ -654,16 +725,47 @@ nsHttpTransaction::Close(nsresult reason) // mReceivedData == FALSE. (see bug 203057 for more info.) // if (reason == NS_ERROR_NET_RESET || reason == NS_OK) { - if (!mReceivedData && (!mSentData || connReused)) { + if (!mReceivedData && (!mSentData || connReused || mPipelinePosition)) { // if restarting fails, then we must proceed to close the pipe, // which will notify the channel that the transaction failed. + + if (mPipelinePosition) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline, + nsnull, 0); + } if (NS_SUCCEEDED(Restart())) return; } + else if (!mResponseIsComplete && mPipelinePosition && + reason == NS_ERROR_NET_RESET) { + // due to unhandled rst on a pipeline - safe to + // restart as only idempotent is found there + + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nsnull, 0); + if (NS_SUCCEEDED(RestartInProgress())) + return; + } } bool relConn = true; if (NS_SUCCEEDED(reason)) { + if (!mResponseIsComplete) { + // The response has not been delimited with a high-confidence + // algorithm like Content-Length or Chunked Encoding. We + // need to use a strong framing mechanism to pipeline. + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nsnull, mClassification); + } + else if (mPipelinePosition) { + // report this success as feedback + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::GoodCompletedOK, + nsnull, mPipelinePosition); + } + // the server has not sent the final \r\n terminating the header // section, and there may still be a header line unparsed. let's make // sure we parse the remaining header line, and then hopefully, the @@ -704,10 +806,86 @@ nsHttpTransaction::Close(nsresult reason) mPipeOut->CloseWithStatus(reason); } +nsresult +nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +PRUint32 +nsHttpTransaction::PipelineDepth() +{ + return IsDone() ? 0 : 1; +} + +nsresult +nsHttpTransaction::SetPipelinePosition(PRInt32 position) +{ + mPipelinePosition = position; + return NS_OK; +} + +PRInt32 +nsHttpTransaction::PipelinePosition() +{ + return mPipelinePosition; +} + //----------------------------------------------------------------------------- // nsHttpTransaction //----------------------------------------------------------------------------- +nsresult +nsHttpTransaction::RestartInProgress() +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // Lock RestartInProgress() and TakeResponseHead() against main thread + MutexAutoLock lock(*nsHttp::GetLock()); + + // don't try and restart 0.9 + if (mHaveAllHeaders && !mRestartInProgressVerifier.IsSetup()) + return NS_ERROR_NET_RESET; + + LOG(("Will restart transaction %p and skip first %lld bytes, " + "old Content-Length %lld", + this, mContentRead, mContentLength)); + + if (mHaveAllHeaders) { + mRestartInProgressVerifier.SetAlreadyProcessed( + PR_MAX(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead)); + mToReadBeforeRestart = mRestartInProgressVerifier.AlreadyProcessed(); + mRestartInProgressVerifier.SetActive(true); + + if (!mTakenResponseHeader && !mForTakeResponseHead) { + // TakeResponseHeader() has not been called yet and this + // is the first restart. Store the resp headers exclusively + // for TakeResponseHeader() + mForTakeResponseHead = mResponseHead; + mResponseHead = nsnull; + } + } + + if (mResponseHead) { + mResponseHead->Reset(); + } + + mContentRead = 0; + mContentLength = -1; + delete mChunkedDecoder; + mChunkedDecoder = nsnull; + mHaveStatusLine = false; + mHaveAllHeaders = false; + mHttpResponseMatched = false; + mResponseIsComplete = false; + mDidContentStart = false; + mNoContent = false; + mSentData = false; + mReceivedData = false; + + return Restart(); +} + nsresult nsHttpTransaction::Restart() { @@ -839,6 +1017,9 @@ nsHttpTransaction::ParseLineSegment(char *segment, PRUint32 len) nsresult rv = ParseLine(mLineBuf.BeginWriting()); mLineBuf.Truncate(); if (NS_FAILED(rv)) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); return rv; } } @@ -887,12 +1068,14 @@ nsHttpTransaction::ParseHead(char *buf, return NS_ERROR_OUT_OF_MEMORY; // report that we have a least some of the response - if (mActivityDistributor) + if (mActivityDistributor && !mReportedStart) { + mReportedStart = true; mActivityDistributor->ObserveActivity( mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), LL_ZERO, EmptyCString()); + } } if (!mHttpResponseMatched) { @@ -1006,7 +1189,8 @@ nsHttpTransaction::HandleContentStart() #endif // notify the connection, give it a chance to cause a reset. bool reset = false; - mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset); + if (!mRestartInProgressVerifier.IsSetup()) + mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset); // looks like we should ignore this response, resetting... if (reset) { @@ -1033,6 +1217,10 @@ nsHttpTransaction::HandleContentStart() break; } mConnection->SetLastTransactionExpectedNoContent(mNoContent); + if (mInvalidResponseBytesRead) + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nsnull, mClassification); if (mNoContent) mContentLength = 0; @@ -1040,6 +1228,10 @@ nsHttpTransaction::HandleContentStart() // grab the content-length from the response headers mContentLength = mResponseHead->ContentLength(); + if ((mClassification != CLASS_SOLO) && + (mContentLength > mMaxPipelineObjectSize)) + CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); + // handle chunked encoding here, so we'll know immediately when // we're done with the socket. please note that _all_ other // decoding is done when the channel receives the content data @@ -1060,9 +1252,15 @@ nsHttpTransaction::HandleContentStart() LOG(("waiting for the server to close the connection.\n")); #endif } + if (mRestartInProgressVerifier.Active() && + !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) { + LOG(("Restart in progress subsequent transaction failed to match")); + return NS_ERROR_ABORT; + } } mDidContentStart = true; + mRestartInProgressVerifier.Set(mContentLength, mResponseHead); return NS_OK; } @@ -1121,6 +1319,19 @@ nsHttpTransaction::HandleContent(char *buf, // (no explicit content-length given) *contentRead = count; } + + if (mRestartInProgressVerifier.Active() && + mToReadBeforeRestart && *contentRead) { + PRUint32 ignore = PR_MIN(*contentRead, PRUint32(mToReadBeforeRestart)); + LOG(("Due To Restart ignoring %d of remaining %ld", + ignore, mToReadBeforeRestart)); + *contentRead -= ignore; + mContentRead += ignore; + mToReadBeforeRestart -= ignore; + memmove(buf, buf + ignore, *contentRead + *contentRemaining); + if (!mToReadBeforeRestart) + mRestartInProgressVerifier.SetActive(false); + } if (*contentRead) { // update count of content bytes read and report progress... @@ -1134,6 +1345,13 @@ nsHttpTransaction::HandleContent(char *buf, LOG(("nsHttpTransaction::HandleContent [this=%x count=%u read=%u mContentRead=%lld mContentLength=%lld]\n", this, count, *contentRead, mContentRead, mContentLength)); + // Check the size of chunked responses. If we exceed the max pipeline size + // for this response reschedule the pipeline + if ((mClassification != CLASS_SOLO) && + mChunkedDecoder && + (mContentRead > mMaxPipelineObjectSize)) + CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); + // check for end-of-file if ((mContentRead == mContentLength) || (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) { @@ -1189,7 +1407,9 @@ nsHttpTransaction::ProcessData(char *buf, PRUint32 count, PRUint32 *countRead) memmove(buf, buf + bytesConsumed, count); // report the completed response header - if (mActivityDistributor && mResponseHead && mHaveAllHeaders) { + if (mActivityDistributor && mResponseHead && mHaveAllHeaders && + !mReportedResponseHeader) { + mReportedResponseHeader = true; nsCAutoString completeResponseHeaders; mResponseHead->Flatten(completeResponseHeaders, false); completeResponseHeaders.AppendLiteral("\r\n"); @@ -1233,6 +1453,23 @@ nsHttpTransaction::ProcessData(char *buf, PRUint32 count, PRUint32 *countRead) return NS_OK; } +void +nsHttpTransaction::CancelPipeline(PRUint32 reason) +{ + // reason is casted through a uint to avoid compiler header deps + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, + static_cast(reason), + nsnull, mClassification); + + mConnection->CancelPipeline(NS_ERROR_CORRUPTED_CONTENT); + + // Avoid pipelining this transaction on restart by classifying it as solo. + // This also prevents BadUnexpectedLarge from being reported more + // than one time per transaction. + mClassification = CLASS_SOLO; +} + //----------------------------------------------------------------------------- // nsHttpTransaction deletion event //----------------------------------------------------------------------------- @@ -1328,3 +1565,76 @@ nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) } return NS_OK; } + +// nsHttpTransaction::RestartVerifier + +static bool +matchOld(nsHttpResponseHead *newHead, nsCString &old, + nsHttpAtom headerAtom) +{ + const char *val; + + val = newHead->PeekHeader(headerAtom); + if (val && old.IsEmpty()) + return false; + if (!val && !old.IsEmpty()) + return false; + if (val && !old.Equals(val)) + return false; + return true; +} + +bool +nsHttpTransaction::RestartVerifier::Verify(PRInt64 contentLength, + nsHttpResponseHead *newHead) +{ + if (mContentLength != contentLength) + return false; + + if (!matchOld(newHead, mContentRange, nsHttp::Content_Range)) + return false; + + if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified)) + return false; + + if (!matchOld(newHead, mETag, nsHttp::ETag)) + return false; + + if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding)) + return false; + + if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding)) + return false; + + return true; +} + +void +nsHttpTransaction::RestartVerifier::Set(PRInt64 contentLength, + nsHttpResponseHead *head) +{ + if (mSetup) + return; + + mContentLength = contentLength; + + if (head) { + const char *val; + val = head->PeekHeader(nsHttp::ETag); + if (val) + mETag.Assign(val); + val = head->PeekHeader(nsHttp::Last_Modified); + if (val) + mLastModified.Assign(val); + val = head->PeekHeader(nsHttp::Content_Range); + if (val) + mContentRange.Assign(val); + val = head->PeekHeader(nsHttp::Content_Encoding); + if (val) + mContentEncoding.Assign(val); + val = head->PeekHeader(nsHttp::Transfer_Encoding); + if (val) + mTransferEncoding.Assign(val); + mSetup = true; + } +} diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 93bd4e7ad6e3..cbb7f04acd5a 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -113,7 +113,6 @@ public: nsIAsyncInputStream **responseBody); // attributes - PRUint8 Caps() { return mCaps; } nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; } nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nsnull; } nsISupports *SecurityInfo() { return mSecurityInfo; } @@ -135,9 +134,11 @@ public: PRInt32 Priority() { return mPriority; } const TimingStruct& Timings() const { return mTimings; } + enum Classifier Classification() { return mClassification; } private: nsresult Restart(); + nsresult RestartInProgress(); char *LocateHttpStart(char *buf, PRUint32 len, bool aAllowPartialMatch); nsresult ParseLine(char *line); @@ -148,6 +149,9 @@ private: nsresult ProcessData(char *, PRUint32, PRUint32 *); void DeleteSelfOnConsumerThread(); + Classifier Classify(); + void CancelPipeline(PRUint32 reason); + static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *, PRUint32, PRUint32, PRUint32 *); static NS_METHOD WritePipeSegment(nsIOutputStream *, void *, char *, @@ -200,6 +204,9 @@ private: PRUint16 mRestartCount; // the number of times this transaction has been restarted PRUint8 mCaps; + enum Classifier mClassification; + PRInt32 mPipelinePosition; + PRInt64 mMaxPipelineObjectSize; // state flags, all logically boolean, but not packed together into a // bitfield so as to avoid bitfield-induced races. See bug 560579. @@ -222,6 +229,66 @@ private: // mClosed := transaction has been explicitly closed // mTransactionDone := transaction ran to completion or was interrupted // mResponseComplete := transaction ran to completion + + // For Restart-In-Progress Functionality + PRInt64 mToReadBeforeRestart; + bool mReportedStart; + bool mReportedResponseHeader; + + // protected by nsHttp::GetLock() + nsHttpResponseHead *mForTakeResponseHead; + bool mTakenResponseHeader; + + class RestartVerifier + { + + // When a idemptotent transaction has received part of its response body + // and incurs an error it can be restarted. To do this we mark the place + // where we stopped feeding the body to the consumer and start the + // network call over again. If everything we track (headers, length, etc..) + // matches up to the place where we left off then the consumer starts being + // fed data again with the new information. This can be done N times up + // to the normal restart (i.e. with no response info) limit. + + public: + RestartVerifier() + : mContentLength(-1) + , mAlreadyProcessed(0) + , mActive(false) + , mSetup(false) + {} + ~RestartVerifier() {} + + void Set(PRInt64 contentLength, nsHttpResponseHead *head); + bool Verify(PRInt64 contentLength, nsHttpResponseHead *head); + bool Active() { return mActive; } + void SetActive(bool val) { mActive = val; } + bool IsSetup() { return mSetup; } + PRInt64 AlreadyProcessed() { return mAlreadyProcessed; } + void SetAlreadyProcessed(PRInt64 val) { mAlreadyProcessed = val; } + + private: + // This is the data from the first complete response header + // used to make sure that all subsequent response headers match + + PRInt64 mContentLength; + nsCString mETag; + nsCString mLastModified; + nsCString mContentRange; + nsCString mContentEncoding; + nsCString mTransferEncoding; + + // This is the amount of data that has been passed to the channel + // from previous iterations of the transaction and must therefore + // be skipped in the new one. + PRInt64 mAlreadyProcessed; + + // true when iteration > 0 has started + bool mActive; + + // true when ::Set has been called with a response header + bool mSetup; + } mRestartInProgressVerifier; }; #endif // nsHttpTransaction_h__ diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp index f90f2f7614c7..0f88b980d142 100644 --- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp +++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp @@ -612,6 +612,12 @@ nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor * aCacheEntry, n return NS_OK; } +NS_IMETHODIMP +nsWyciwygChannel::OnCacheEntryDoomed(nsresult status) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + //----------------------------------------------------------------------------- // nsWyciwygChannel::nsIStreamListener //----------------------------------------------------------------------------- @@ -709,7 +715,7 @@ nsWyciwygChannel::OpenCacheEntry(const nsACString & aCacheKey, rv = cacheSession->OpenCacheEntry(aCacheKey, aAccessMode, false, getter_AddRefs(mCacheEntry)); else - rv = cacheSession->AsyncOpenCacheEntry(aCacheKey, aAccessMode, this); + rv = cacheSession->AsyncOpenCacheEntry(aCacheKey, aAccessMode, this, false); return rv; } diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js index 69bf2c99ff94..42002a39d312 100644 --- a/netwerk/test/unit/head_channels.js +++ b/netwerk/test/unit/head_channels.js @@ -26,6 +26,7 @@ const CL_EXPECT_GZIP = 0x2; const CL_EXPECT_3S_DELAY = 0x4; const CL_SUSPEND = 0x8; const CL_ALLOW_UNKNOWN_CL = 0x10; +const CL_EXPECT_LATE_FAILURE = 0x20; const SUSPEND_DELAY = 3000; @@ -38,7 +39,8 @@ const SUSPEND_DELAY = 3000; * * This listener makes sure that various parts of the channel API are * implemented correctly and that the channel's status is a success code - * (you can pass CL_EXPECT_FAILURE as flags to allow a failure code) + * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags + * to allow a failure code) * * Note that it also requires a valid content length on the channel and * is thus not fully generic. @@ -131,15 +133,15 @@ ChannelListener.prototype = { if (this._got_onstoprequest) do_throw("Got second onStopRequest event!"); this._got_onstoprequest = true; - if ((this._flags & CL_EXPECT_FAILURE) && success) + if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success) do_throw("Should have failed to load URL (status is " + status.toString(16) + ")"); - else if (!(this._flags & CL_EXPECT_FAILURE) && !success) + else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success) do_throw("Failed to load URL: " + status.toString(16)); if (status != request.status) do_throw("request.status does not match status arg to onStopRequest!"); if (request.isPending()) do_throw("request reports itself as pending from onStopRequest!"); - if (!(this._flags & CL_EXPECT_FAILURE) && + if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !(this._flags & CL_EXPECT_GZIP) && this._contentLen != -1) do_check_eq(this._buffer.length, this._contentLen) diff --git a/netwerk/test/unit/test_assoc.js b/netwerk/test/unit/test_assoc.js new file mode 100644 index 000000000000..c1f4087447b2 --- /dev/null +++ b/netwerk/test/unit/test_assoc.js @@ -0,0 +1,90 @@ +do_load_httpd_js(); + +var httpserver = new nsHttpServer(); +var currentTestIndex = 0; +var tests = [ + // this is valid + {url: "/assoc/assoctest?valid", + responseheader: [ "Assoc-Req: GET http://localhost:4444/assoc/assoctest?valid", + "Pragma: X-Verify-Assoc-Req" ], + flags : 0}, + + // this is invalid because the method is wrong + {url: "/assoc/assoctest?invalid", + responseheader: [ "Assoc-Req: POST http://localhost:4444/assoc/assoctest?invalid", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, + + // this is invalid because the url is wrong + {url: "/assoc/assoctest?notvalid", + responseheader: [ "Assoc-Req: GET http://localhost:4444/wrongpath/assoc/assoctest?notvalid", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, + + // this is invalid because the space between method and URL is missing + {url: "/assoc/assoctest?invalid2", + responseheader: [ "Assoc-Req: GEThttp://localhost:4444/assoc/assoctest?invalid2", + "Pragma: X-Verify-Assoc-Req" ], + flags : CL_EXPECT_LATE_FAILURE}, +]; + +var oldPrefVal; +var domBranch; + +function setupChannel(url) +{ + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var chan = ios.newChannel("http://localhost:4444" + url, "", null); + return chan; +} + +function startIter() +{ + var channel = setupChannel(tests[currentTestIndex].url); + channel.asyncOpen(new ChannelListener(completeIter, + channel, tests[currentTestIndex].flags), null); +} + +function completeIter(request, data, ctx) +{ + if (++currentTestIndex < tests.length ) { + startIter(); + } else { + domBranch.setBoolPref("enforce", oldPrefVal); + httpserver.stop(do_test_finished); + } +} + +function run_test() +{ + var prefService = + Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + domBranch = prefService.getBranch("network.http.assoc-req."); + oldPrefVal = domBranch.getBoolPref("enforce"); + domBranch.setBoolPref("enforce", true); + + httpserver.registerPathHandler("/assoc/assoctest", handler); + httpserver.start(4444); + + startIter(); + do_test_pending(); +} + +function handler(metadata, response) +{ + var body = "thequickbrownfox"; + response.setHeader("Content-Type", "text/plain", false); + + var header = tests[currentTestIndex].responseheader; + if (header != undefined) { + for (var i = 0; i < header.length; i++) { + var splitHdr = header[i].split(": "); + response.setHeader(splitHdr[0], splitHdr[1], false); + } + } + + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} diff --git a/netwerk/test/unit/test_doomentry.js b/netwerk/test/unit/test_doomentry.js new file mode 100644 index 000000000000..941881c304e3 --- /dev/null +++ b/netwerk/test/unit/test_doomentry.js @@ -0,0 +1,170 @@ +/** + * Test for nsICacheSession.doomEntry(). + * It tests dooming + * - an existent inactive entry + * - a non-existent inactive entry + * - an existent active entry + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +var _CSvc; +function get_cache_service() { + if (_CSvc) + return _CSvc; + + return _CSvc = Cc["@mozilla.org/network/cache-service;1"]. + getService(Ci.nsICacheService); +} + +function GetOutputStreamForEntry(key, asFile, append, callback) +{ + this._key = key; + this._asFile = asFile; + this._append = append; + this._callback = callback; + this.run(); +} + +GetOutputStreamForEntry.prototype = { + _key: "", + _asFile: false, + _append: false, + _callback: null, + + QueryInterface: function(iid) { + if (iid.equals(Ci.nsICacheListener) || + iid.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + onCacheEntryAvailable: function (entry, access, status) { + if (!entry) + do_throw("entry not available"); + + var ostream = entry.openOutputStream(this._append ? entry.dataSize : 0); + this._callback(entry, ostream); + }, + + run: function() { + var cache = get_cache_service(); + var session = cache.createSession( + "HTTP", + this._asFile ? Ci.nsICache.STORE_ON_DISK_AS_FILE + : Ci.nsICache.STORE_ON_DISK, + Ci.nsICache.STREAM_BASED); + var cacheEntry = session.asyncOpenCacheEntry( + this._key, + this._append ? Ci.nsICache.ACCESS_READ_WRITE + : Ci.nsICache.ACCESS_WRITE, + this); + } +}; + +function DoomEntry(key, callback) { + this._key = key; + this._callback = callback; + this.run(); +} + +DoomEntry.prototype = { + _key: "", + _callback: null, + + QueryInterface: function(iid) { + if (iid.equals(Ci.nsICacheListener) || + iid.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + onCacheEntryDoomed: function (status) { + this._callback(status); + }, + + run: function() { + get_cache_service() + .createSession("HTTP", + Ci.nsICache.STORE_ANYWHERE, + Ci.nsICache.STREAM_BASED) + .doomEntry(this._key, this); + } +}; + +function write_and_check(str, data, len) +{ + var written = str.write(data, len); + if (written != len) { + do_throw("str.write has not written all data!\n" + + " Expected: " + len + "\n" + + " Actual: " + written + "\n"); + } +} + +function write_entry() +{ + new GetOutputStreamForEntry("testentry", true, false, write_entry_cont); +} + +function write_entry_cont(entry, ostream) +{ + var data = "testdata"; + write_and_check(ostream, data, data.length); + ostream.close(); + entry.close(); + new DoomEntry("testentry", check_doom1); +} + +function check_doom1(status) +{ + do_check_eq(status, Cr.NS_OK); + new DoomEntry("nonexistententry", check_doom2); +} + +function check_doom2(status) +{ + do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE); + new GetOutputStreamForEntry("testentry", true, false, write_entry2); +} + +var gEntry; +var gOstream; +function write_entry2(entry, ostream) +{ + // write some data and doom the entry while it is active + var data = "testdata"; + write_and_check(ostream, data, data.length); + gEntry = entry; + gOstream = ostream; + new DoomEntry("testentry", check_doom3); +} + +function check_doom3(status) +{ + do_check_eq(status, Cr.NS_OK); + // entry was doomed but writing should still succeed + var data = "testdata"; + write_and_check(gOstream, data, data.length); + gEntry.close(); + gOstream.close(); + // dooming the same entry again should fail + new DoomEntry("testentry", check_doom4); +} + +function check_doom4(status) +{ + do_check_eq(status, Cr.NS_ERROR_NOT_AVAILABLE); + do_test_finished(); +} + +function run_test() { + do_get_profile(); + + // clear the cache + get_cache_service().evictEntries(Ci.nsICache.STORE_ANYWHERE); + write_entry(); + do_test_pending(); +} diff --git a/netwerk/test/unit/test_mismatch_lm.js b/netwerk/test/unit/test_mismatch_lm.js new file mode 100644 index 000000000000..6431abc9933e --- /dev/null +++ b/netwerk/test/unit/test_mismatch_lm.js @@ -0,0 +1,126 @@ +do_load_httpd_js(); +var httpserver = new nsHttpServer(); +var cacheService; +var ios; + +// Test the handling of a cache revalidation with mismatching last-modified +// headers. If we get such a revalidation the cache entry should be purged. +// see bug 717350 + +// In this test the wrong data is from 11-16-1994 with a value of 'A', +// and the right data is from 11-15-1994 with a value of 'B'. + +// the same URL is requested 3 times. the first time the wrong data comes +// back, the second time that wrong data is revalidated with a 304 but +// a L-M header of the right data (this triggers a cache purge), and +// the third time the right data is returned. + +var listener_3 = { + // this listener is used to process the the request made after + // the cache invalidation. it expects to see the 'right data' + + onStartRequest: function test_onStartR(request, ctx) {}, + + onDataAvailable: function test_ODA(request, cx, inputStream, + offset, count) { + var data = new BinaryInputStream(inputStream).readByteArray(count); + + // This is 'B' + do_check_eq(data, 66); + }, + + onStopRequest: function test_onStopR(request, ctx, status) { + httpserver.stop(do_test_finished); + } +}; + +var listener_2 = { + // this listener is used to process the revalidation of the + // corrupted cache entry. its revalidation prompts it to be cleaned + + onStartRequest: function test_onStartR(request, ctx) {}, + + onDataAvailable: function test_ODA(request, cx, inputStream, + offset, count) { + var data = new BinaryInputStream(inputStream).readByteArray(count); + + // This is 'A' from a cache revalidation, but that reval will clean the cache + // because of mismatched last-modified response headers + + do_check_eq(data, 65); + }, + + onStopRequest: function test_onStopR(request, ctx, status) { + var channel = request.QueryInterface(Ci.nsIHttpChannel); + + var chan = ios.newChannel("http://localhost:4444/test1", "", null); + var httpChan = chan.QueryInterface(Ci.nsIHttpChannel); + httpChan.requestMethod = "GET"; + httpChan.asyncOpen(listener_3, null); + } +}; + +var listener_1 = { + // this listener processes the initial request from a empty cache. + // the server responds with the wrong data ('A') + + onStartRequest: function test_onStartR(request, ctx) {}, + + onDataAvailable: function test_ODA(request, cx, inputStream, + offset, count) { + var data = new BinaryInputStream(inputStream).readByteArray(count); + do_check_eq(data, 65); + }, + + onStopRequest: function test_onStopR(request, ctx, status) { + var channel = request.QueryInterface(Ci.nsIHttpChannel); + + var chan = ios.newChannel("http://localhost:4444/test1", "", null); + var httpChan = chan.QueryInterface(Ci.nsIHttpChannel); + httpChan.requestMethod = "GET"; + httpChan.asyncOpen(listener_2, null); + } +}; + +function run_test() { + do_get_profile(); + cacheService = Cc["@mozilla.org/network/cache-service;1"]. + getService(Ci.nsICacheService); + ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE); + + httpserver.registerPathHandler("/test1", handler); + httpserver.start(4444); + + var chan = ios.newChannel("http://localhost:4444/test1", "", null); + var httpChan = chan.QueryInterface(Ci.nsIHttpChannel); + httpChan.requestMethod = "GET"; + httpChan.asyncOpen(listener_1, null); + + do_test_pending(); +} + +var iter=0; +function handler(metadata, response) { + iter++; + if (metadata.hasHeader("If-Modified-Since")) { + response.setStatusLine(metadata.httpVersion, 304, "Not Modified"); + response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false); + } + else { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Cache-Control", "max-age=0", false) + if (iter == 1) { + // simulated wrong response + response.setHeader("Last-Modified", "Wed, 16 Nov 1994 00:00:00 GMT", false); + response.bodyOutputStream.write("A", 1); + } + if (iter == 3) { + // 'correct' response + response.setHeader("Last-Modified", "Tue, 15 Nov 1994 12:45:26 GMT", false); + response.bodyOutputStream.write("B", 1); + } + } +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 9d61663e63ae..e51b239c5ed1 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -6,6 +6,7 @@ tail = [test_NetUtil.js] [test_URIs.js] [test_aboutblank.js] +[test_assoc.js] [test_auth_proxy.js] [test_authentication.js] # Bug 675039: test hangs consistently on Android @@ -83,6 +84,7 @@ fail-if = os == "android" [test_bug667907.js] [test_bug667818.js] [test_bug669001.js] +[test_doomentry.js] [test_cacheflags.js] [test_channel_close.js] [test_compareURIs.js] @@ -131,6 +133,7 @@ skip-if = os == "android" [test_httpsuspend.js] [test_idnservice.js] [test_localstreams.js] +[test_mismatch_lm.js] [test_MIME_params.js] [test_multipart_streamconv.js] [test_multipart_streamconv_missing_lead_boundary.js] diff --git a/toolkit/components/places/nsAndroidHistory.cpp b/toolkit/components/places/nsAndroidHistory.cpp index 5faccde4541c..ee445269395f 100644 --- a/toolkit/components/places/nsAndroidHistory.cpp +++ b/toolkit/components/places/nsAndroidHistory.cpp @@ -72,7 +72,7 @@ nsAndroidHistory::RegisterVisitedCallback(nsIURI *aURI, Link *aContent) nsCAutoString uri; nsresult rv = aURI->GetSpec(uri); if (NS_FAILED(rv)) return rv; - nsString uriString = NS_ConvertUTF8toUTF16(uri); + NS_ConvertUTF8toUTF16 uriString(uri); nsTArray* list = mListeners.Get(uriString); if (! list) { @@ -98,7 +98,7 @@ nsAndroidHistory::UnregisterVisitedCallback(nsIURI *aURI, Link *aContent) nsCAutoString uri; nsresult rv = aURI->GetSpec(uri); if (NS_FAILED(rv)) return rv; - nsString uriString = NS_ConvertUTF8toUTF16(uri); + NS_ConvertUTF8toUTF16 uriString(uri); nsTArray* list = mListeners.Get(uriString); if (! list) @@ -126,7 +126,7 @@ nsAndroidHistory::VisitURI(nsIURI *aURI, nsIURI *aLastVisitedURI, PRUint32 aFlag nsCAutoString uri; nsresult rv = aURI->GetSpec(uri); if (NS_FAILED(rv)) return rv; - nsString uriString = NS_ConvertUTF8toUTF16(uri); + NS_ConvertUTF8toUTF16 uriString(uri); bridge->MarkURIVisited(uriString); } return NS_OK; diff --git a/toolkit/components/places/nsNavHistoryQuery.cpp b/toolkit/components/places/nsNavHistoryQuery.cpp index 2578290df3e3..6ddd9a4c16cc 100644 --- a/toolkit/components/places/nsNavHistoryQuery.cpp +++ b/toolkit/components/places/nsNavHistoryQuery.cpp @@ -791,7 +791,7 @@ nsNavHistory::TokensToQueries(const nsTArray& aTokens, } else if (kvp.key.EqualsLiteral(QUERYKEY_TAG)) { nsCAutoString unescaped(kvp.value); NS_UnescapeURL(unescaped); // modifies input - nsString tag = NS_ConvertUTF8toUTF16(unescaped); + NS_ConvertUTF8toUTF16 tag(unescaped); if (!tags.Contains(tag)) { NS_ENSURE_TRUE(tags.AppendElement(tag), NS_ERROR_OUT_OF_MEMORY); } diff --git a/toolkit/content/widgets/scrollbar.xml b/toolkit/content/widgets/scrollbar.xml index 416f1b5be6a6..0c948fdf314d 100644 --- a/toolkit/content/widgets/scrollbar.xml +++ b/toolkit/content/widgets/scrollbar.xml @@ -17,7 +17,7 @@ - + diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index f3d3138e70fb..f487ab57b6d0 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -792,7 +792,6 @@ if [ "$ENABLE_TESTS" ]; then dom/tests/mochitest/dom-level2-html/files/Makefile dom/tests/mochitest/general/Makefile dom/tests/mochitest/geolocation/Makefile - dom/tests/mochitest/globalstorage/Makefile dom/tests/mochitest/localstorage/Makefile dom/tests/mochitest/orientation/Makefile dom/tests/mochitest/sessionstorage/Makefile diff --git a/tools/profiler/Makefile.in b/tools/profiler/Makefile.in index ece1c09a9a61..fdb3445dfa0c 100644 --- a/tools/profiler/Makefile.in +++ b/tools/profiler/Makefile.in @@ -69,6 +69,8 @@ SHARED_LIBRARY_LIBS += \ $(NULL) export:: + # Avoid building libunwind documentation + $(topsrcdir)/tools/profiler/libunwind/dont_build_docs.sh $(DEPTH)/tools/profiler/libunwind/src/doc/Makefile $(call SUBMAKE,,libunwind/src) distclean:: diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 59c943df88b5..ed07d33d8c06 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -523,12 +523,16 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) mozilla::ArrayLength(pc_array), 0 }; + + // Start with the current function. + StackWalkCallback(aSample->pc, &array); + #ifdef XP_MACOSX pthread_t pt = GetProfiledThread(platform_data()); void *stackEnd = reinterpret_cast(-1); if (pt) stackEnd = static_cast(pthread_get_stackaddr_np(pt)); - nsresult rv = FramePointerStackWalk(StackWalkCallback, 1, &array, reinterpret_cast(aSample->fp), stackEnd); + nsresult rv = FramePointerStackWalk(StackWalkCallback, 0, &array, reinterpret_cast(aSample->fp), stackEnd); #else nsresult rv = NS_StackWalk(StackWalkCallback, 0, &array, thread); #endif diff --git a/tools/profiler/libunwind/dont_build_docs.sh b/tools/profiler/libunwind/dont_build_docs.sh new file mode 100755 index 000000000000..f77ff8799700 --- /dev/null +++ b/tools/profiler/libunwind/dont_build_docs.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# Edit the doc Makefile to just echo the commands +sed -e 's/latex2man/echo/' -e 's/pdflatex/echo/' -i "$1" diff --git a/xpcom/string/public/nsSubstring.h b/xpcom/string/public/nsSubstring.h index f488472e8200..442c1534df31 100644 --- a/xpcom/string/public/nsSubstring.h +++ b/xpcom/string/public/nsSubstring.h @@ -36,6 +36,11 @@ * * ***** END LICENSE BLOCK ***** */ +#ifndef nsSubstring_h___ +#define nsSubstring_h___ + #ifndef nsAString_h___ #include "nsAString.h" #endif + +#endif // !defined(nsSubstring_h___)