mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Merge m-c to fx-team.
This commit is contained in:
commit
61505342f9
@ -21,9 +21,6 @@
|
||||
src="../events.js" />
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.expectAssertions(0, 1);
|
||||
|
||||
function getColorBtn(aBtnObj)
|
||||
{
|
||||
var colorpicker = aBtnObj.colorpicker;
|
||||
|
@ -16,8 +16,6 @@
|
||||
src="../states.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
SimpleTest.expectAssertions(0, 1);
|
||||
|
||||
function doTest()
|
||||
{
|
||||
var tree =
|
||||
|
@ -50,6 +50,9 @@ pref("network.http.max-connections", 20);
|
||||
pref("network.http.max-persistent-connections-per-server", 6);
|
||||
pref("network.http.max-persistent-connections-per-proxy", 20);
|
||||
|
||||
// spdy
|
||||
pref("network.http.spdy.push-allowance", 32768);
|
||||
|
||||
// See bug 545869 for details on why these are set the way they are
|
||||
pref("network.buffer.cache.count", 24);
|
||||
pref("network.buffer.cache.size", 16384);
|
||||
|
@ -199,12 +199,15 @@ Components.utils.import('resource://gre/modules/ctypes.jsm');
|
||||
// Get the hardware info and firmware revision from device properties.
|
||||
let hardware_info = null;
|
||||
let firmware_revision = null;
|
||||
let product_model = null;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
hardware_info = libcutils.property_get('ro.hardware');
|
||||
firmware_revision = libcutils.property_get('ro.firmware_revision');
|
||||
product_model = libcutils.property_get('ro.product.model');
|
||||
#endif
|
||||
lock.set('deviceinfo.hardware', hardware_info, null, null);
|
||||
lock.set('deviceinfo.firmware_revision', firmware_revision, null, null);
|
||||
lock.set('deviceinfo.product_model', product_model, null, null);
|
||||
})();
|
||||
|
||||
// =================== Debugger ====================
|
||||
@ -291,3 +294,15 @@ SettingsListener.observe('app.reportCrashes', 'ask', function(value) {
|
||||
SettingsListener.observe('app.update.interval', 86400, function(value) {
|
||||
Services.prefs.setIntPref('app.update.interval', value);
|
||||
});
|
||||
|
||||
// ================ Debug ================
|
||||
// XXX could factor out into a settings->pref map.
|
||||
SettingsListener.observe("debug.fps.enabled", false, function(value) {
|
||||
Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
|
||||
});
|
||||
SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
|
||||
Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
|
||||
});
|
||||
SettingsListener.observe("layers.draw-borders", false, function(value) {
|
||||
Services.prefs.setBoolPref("layers.draw-borders", value);
|
||||
});
|
||||
|
@ -320,14 +320,6 @@ var shell = {
|
||||
IndexedDBPromptHelper.init();
|
||||
CaptivePortalLoginHelper.init();
|
||||
|
||||
// XXX could factor out into a settings->pref map. Not worth it yet.
|
||||
SettingsListener.observe("debug.fps.enabled", false, function(value) {
|
||||
Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
|
||||
});
|
||||
SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
|
||||
Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
|
||||
});
|
||||
|
||||
this.contentBrowser.src = homeURL;
|
||||
this.isHomeLoaded = false;
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
"mock_target": "mozilla-centos6-x86_64",
|
||||
"mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git"],
|
||||
"mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]],
|
||||
"build_targets": ["droid", "package-emulator"],
|
||||
"build_targets": ["droid", "package-emulator", "package-tests"],
|
||||
"upload_files": [
|
||||
"{workdir}/out/target/product/generic/*.tar.bz2",
|
||||
"{workdir}/out/target/product/generic/tests/*.zip",
|
||||
"{workdir}/out/emulator.tar.gz",
|
||||
"{objdir}/dist/b2g-*.crashreporter-symbols.zip",
|
||||
"{workdir}/sources.xml"
|
||||
|
@ -151,25 +151,6 @@
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "close-button");
|
||||
</field>
|
||||
|
||||
<method name="contentHasChanged">
|
||||
<body><![CDATA[
|
||||
if (!this.isActive)
|
||||
return;
|
||||
|
||||
// There is a race condition with getBoundingClientRect and when the
|
||||
// box is displayed, the padding is ignored in the size calculation.
|
||||
// A nested timeouts below are used to workaround this problem.
|
||||
this.getBoundingClientRect();
|
||||
|
||||
setTimeout(function(self) {
|
||||
setTimeout(function(self) {
|
||||
let height = Math.floor(self.getBoundingClientRect().height);
|
||||
self.top = window.innerHeight - height;
|
||||
}, 0, self);
|
||||
}, 0, this);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="isActive" onget="return !!this.model;"/>
|
||||
|
||||
<field name="model">null</field>
|
||||
@ -192,7 +173,6 @@
|
||||
this._nextButton.disabled = document.getElementById(aModel.commands.next).getAttribute("disabled") == "true";
|
||||
|
||||
this.model = aModel;
|
||||
this.contentHasChanged();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
@ -204,7 +184,6 @@
|
||||
this.removeAttribute("close");
|
||||
this.removeAttribute("type");
|
||||
|
||||
this.contentHasChanged();
|
||||
this.model = null;
|
||||
]]></body>
|
||||
</method>
|
||||
|
@ -594,8 +594,6 @@ var BrowserUI = {
|
||||
}
|
||||
Elements.windowState.setAttribute("viewstate", currViewState);
|
||||
}
|
||||
// content navigator helper
|
||||
document.getElementById("content-navigator").contentHasChanged();
|
||||
},
|
||||
|
||||
_titleChanged: function(aBrowser) {
|
||||
@ -761,7 +759,7 @@ var BrowserUI = {
|
||||
}
|
||||
|
||||
// Check content helper
|
||||
let contentHelper = document.getElementById("content-navigator");
|
||||
let contentHelper = Elements.contentNavigator;
|
||||
if (contentHelper.isActive) {
|
||||
contentHelper.model.hide();
|
||||
return;
|
||||
|
@ -619,9 +619,9 @@ var Browser = {
|
||||
let sslExceptions = new SSLExceptions();
|
||||
|
||||
if (json.action == "permanent")
|
||||
sslExceptions.addPermanentException(uri, errorDoc.defaultView);
|
||||
sslExceptions.addPermanentException(uri, window);
|
||||
else
|
||||
sslExceptions.addTemporaryException(uri, errorDoc.defaultView);
|
||||
sslExceptions.addTemporaryException(uri, window);
|
||||
} catch (e) {
|
||||
dump("EXCEPTION handle content command: " + e + "\n" );
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ var FindHelperUI = {
|
||||
|
||||
init: function findHelperInit() {
|
||||
this._textbox = document.getElementById("find-helper-textbox");
|
||||
this._container = document.getElementById("content-navigator");
|
||||
this._container = Elements.contentNavigator;
|
||||
|
||||
this._cmdPrevious = document.getElementById(this.commands.previous);
|
||||
this._cmdNext = document.getElementById(this.commands.next);
|
||||
@ -50,10 +50,6 @@ var FindHelperUI = {
|
||||
messageManager.addMessageListener("FindAssist:Show", this);
|
||||
messageManager.addMessageListener("FindAssist:Hide", this);
|
||||
|
||||
// Listen for pan events happening on the browsers
|
||||
Elements.browsers.addEventListener("PanBegin", this, false);
|
||||
Elements.browsers.addEventListener("PanFinished", this, false);
|
||||
|
||||
// Listen for events where form assistant should be closed
|
||||
Elements.tabList.addEventListener("TabSelect", this, true);
|
||||
Elements.browsers.addEventListener("URLChanged", this, true);
|
||||
@ -91,16 +87,6 @@ var FindHelperUI = {
|
||||
this.hide();
|
||||
break;
|
||||
|
||||
case "PanBegin":
|
||||
this._container.style.visibility = "hidden";
|
||||
this._textbox.collapsed = true;
|
||||
break;
|
||||
|
||||
case "PanFinished":
|
||||
this._container.style.visibility = "visible";
|
||||
this._textbox.collapsed = false;
|
||||
break;
|
||||
|
||||
case "keydown":
|
||||
if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
|
||||
if (aEvent.shiftKey) {
|
||||
@ -113,6 +99,9 @@ var FindHelperUI = {
|
||||
},
|
||||
|
||||
show: function findHelperShow() {
|
||||
if (this._open)
|
||||
return;
|
||||
|
||||
// Hide any menus
|
||||
ContextUI.dismiss();
|
||||
|
||||
@ -124,6 +113,8 @@ var FindHelperUI = {
|
||||
this._textbox.select();
|
||||
this._textbox.focus();
|
||||
this._open = true;
|
||||
Elements.browsers.setAttribute("findbar", true);
|
||||
setTimeout(() => this._container.setAttribute("showing", true), 0);
|
||||
|
||||
// Prevent the view to scroll automatically while searching
|
||||
Browser.selectedBrowser.scrollSync = false;
|
||||
@ -133,14 +124,21 @@ var FindHelperUI = {
|
||||
if (!this._open)
|
||||
return;
|
||||
|
||||
this._textbox.value = "";
|
||||
this.status = null;
|
||||
this._textbox.blur();
|
||||
this._container.hide(this);
|
||||
this._open = false;
|
||||
let onTransitionEnd = () => {
|
||||
this._container.removeEventListener("transitionend", onTransitionEnd, true);
|
||||
this._textbox.value = "";
|
||||
this.status = null;
|
||||
this._textbox.blur();
|
||||
this._container.hide(this);
|
||||
this._open = false;
|
||||
|
||||
// Restore the scroll synchronisation
|
||||
Browser.selectedBrowser.scrollSync = true;
|
||||
// Restore the scroll synchronisation
|
||||
Browser.selectedBrowser.scrollSync = true;
|
||||
};
|
||||
|
||||
this._container.addEventListener("transitionend", onTransitionEnd, true);
|
||||
this._container.removeAttribute("showing");
|
||||
Elements.browsers.removeAttribute("findbar");
|
||||
},
|
||||
|
||||
goToPrevious: function findHelperGoToPrevious() {
|
||||
|
@ -194,7 +194,7 @@ var ContextMenuUI = {
|
||||
|
||||
// chrome calls don't need to be translated and as such
|
||||
// don't provide target.
|
||||
if (aMessage.target) {
|
||||
if (aMessage.target && aMessage.target.localName === "browser") {
|
||||
coords = aMessage.target.msgBrowserToClient(aMessage, true);
|
||||
}
|
||||
this._menuPopup.show(Util.extend({}, this._defaultPositionOptions, {
|
||||
|
@ -733,6 +733,14 @@ setting[type="radio"] > vbox {
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
#browsers browser {
|
||||
transition: padding-bottom @metro_animation_duration@ @metro_animation_easing@;
|
||||
}
|
||||
|
||||
#browsers[findbar] browser {
|
||||
padding-bottom: @findbar_height@;
|
||||
}
|
||||
|
||||
/* Panel UI ---------------------------------------------------------------- */
|
||||
|
||||
#panel-container {
|
||||
|
@ -21,6 +21,7 @@
|
||||
%define toolbar_horizontal_spacing 20px
|
||||
%define toolbar_height 68px
|
||||
%define tabs_height 178px
|
||||
%define findbar_height 54px
|
||||
|
||||
%define progress_height 5px
|
||||
|
||||
|
@ -13,6 +13,12 @@
|
||||
background-color: @metro_orange@;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
transition: margin-bottom @metro_animation_duration@ @metro_animation_easing@;
|
||||
margin-bottom: -@findbar_height@;
|
||||
}
|
||||
|
||||
#content-navigator[showing] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#content-navigator[type="find"],
|
||||
|
@ -1728,7 +1728,6 @@ public:
|
||||
#undef EVENT
|
||||
|
||||
protected:
|
||||
static void Trace(nsINode *tmp, const TraceCallbacks &cb, void *closure);
|
||||
static bool Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb);
|
||||
static void Unlink(nsINode *tmp);
|
||||
|
||||
|
@ -66,9 +66,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Attr)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttrMap)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Attr)
|
||||
nsINode::Trace(tmp, aCallbacks, aClosure);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Attr)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Attr)
|
||||
nsINode::Unlink(tmp);
|
||||
|
@ -460,12 +460,12 @@ public:
|
||||
|
||||
void RemoveEntry(nsINode* aTextNode, Element* aElement)
|
||||
{
|
||||
if (mElements.Contains(aElement)) {
|
||||
mElements.Remove(aElement);
|
||||
NS_ASSERTION(mElements.Contains(aElement),
|
||||
"element already removed from map");
|
||||
|
||||
aElement->ClearHasDirAutoSet();
|
||||
aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
|
||||
}
|
||||
mElements.Remove(aElement);
|
||||
aElement->ClearHasDirAutoSet();
|
||||
aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1156,9 +1156,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FragmentOrElement)
|
||||
nsINode::Trace(tmp, aCallbacks, aClosure);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement)
|
||||
|
||||
void
|
||||
FragmentOrElement::MarkUserData(void* aObject, nsIAtom* aKey, void* aChild,
|
||||
|
@ -1803,7 +1803,7 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument)
|
||||
if (tmp->PreservingWrapper()) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando);
|
||||
}
|
||||
nsINode::Trace(tmp, aCallbacks, aClosure);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
|
||||
|
@ -62,9 +62,7 @@ nsGenericDOMDataNode::~nsGenericDOMDataNode()
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGenericDOMDataNode)
|
||||
nsINode::Trace(tmp, aCallbacks, aClosure);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsGenericDOMDataNode)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGenericDOMDataNode)
|
||||
return Element::CanSkip(tmp, aRemovingAllowed);
|
||||
|
@ -1163,14 +1163,6 @@ nsINode::GetContextForEventHandlers(nsresult* aRv)
|
||||
return nsContentUtils::GetContextForEventHandlers(this, aRv);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsINode::Trace(nsINode *tmp, const TraceCallbacks& cb, void *closure)
|
||||
{
|
||||
tmp->TraceWrapper(cb, closure);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
nsINode::UnoptimizableCCNode() const
|
||||
{
|
||||
|
4
content/media/test/crashtests/876834.html
Normal file
4
content/media/test/crashtests/876834.html
Normal file
@ -0,0 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<script>
|
||||
OfflineAudioContext(0, 0, 3229622);
|
||||
</script>
|
@ -24,3 +24,4 @@ load 875596.html
|
||||
load 875911.html
|
||||
load 876249.html
|
||||
load 876252.html
|
||||
load 876834.html
|
||||
|
@ -109,7 +109,7 @@ AudioContext::Constructor(const GlobalObject& aGlobal,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aSampleRate <= 0.0f) {
|
||||
if (aSampleRate <= 0.0f || aSampleRate >= TRACK_RATE_MAX) {
|
||||
// The DOM binding protects us against infinity and NaN
|
||||
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
||||
return nullptr;
|
||||
|
@ -525,9 +525,25 @@ WebappsApplication.prototype = {
|
||||
if (msg.manifestURL != this.manifestURL)
|
||||
return;
|
||||
|
||||
for (let prop in msg.app) {
|
||||
this[prop] = msg.app[prop];
|
||||
}
|
||||
manifestCache.evict(this.manifestURL, this.innerWindowID);
|
||||
|
||||
let hiddenProps = ["manifest", "updateManifest"];
|
||||
let updatableProps = ["installOrigin", "installTime", "installState",
|
||||
"lastUpdateCheck", "updateTime", "progress", "downloadAvailable",
|
||||
"downloading", "readyToApplyDownload", "downloadSize"];
|
||||
// Props that we don't update: origin, receipts, manifestURL, removable.
|
||||
|
||||
updatableProps.forEach(function(prop) {
|
||||
if (msg.app[prop]) {
|
||||
this[prop] = msg.app[prop];
|
||||
}
|
||||
}, this);
|
||||
|
||||
hiddenProps.forEach(function(prop) {
|
||||
if (msg.app[prop]) {
|
||||
this["_" + prop] = msg.app[prop];
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (msg.event == "downloadapplied") {
|
||||
this._fireEvent("downloadapplied", this._ondownloadapplied);
|
||||
|
@ -1459,6 +1459,7 @@ this.DOMApplicationRegistry = {
|
||||
|
||||
app.name = manifest.name;
|
||||
app.csp = manifest.csp || "";
|
||||
app.updateTime = Date.now();
|
||||
} else {
|
||||
manifest = new ManifestHelper(aOldManifest, app.origin);
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
var gBasePath = "tests/dom/apps/tests/";
|
||||
var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html";
|
||||
var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache";
|
||||
var gDefaultIcon = "default_icon";
|
||||
|
||||
function makeResource(templatePath, version, apptype) {
|
||||
let icon = getState('icon') || gDefaultIcon;
|
||||
var res = readTemplate(templatePath).replace(/VERSIONTOKEN/g, version)
|
||||
.replace(/APPTYPETOKEN/g, apptype);
|
||||
.replace(/APPTYPETOKEN/g, apptype)
|
||||
.replace(/ICONTOKEN/g, icon);
|
||||
|
||||
// Hack - This is necessary to make the tests pass, but hbambas says it
|
||||
// shouldn't be necessary. Comment it out and watch the tests fail.
|
||||
@ -27,6 +30,20 @@ function handleRequest(request, response) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("setIcon" in query) {
|
||||
let icon = query.setIcon;
|
||||
if (icon === 'DEFAULT') {
|
||||
icon = null;
|
||||
}
|
||||
|
||||
setState('icon', icon);
|
||||
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Access-Control-Allow-Origin", "*", false);
|
||||
response.write('OK');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the app type.
|
||||
var apptype = query.apptype;
|
||||
if (apptype != 'hosted' && apptype != 'cached')
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"name": "Really Rapid Release (hosted)",
|
||||
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
|
||||
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=hosted"
|
||||
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=hosted",
|
||||
"icons": {
|
||||
"128": "ICONTOKEN"
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,31 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=826058
|
||||
checkAppState(app, true, 3, continueTest);
|
||||
yield;
|
||||
|
||||
// check for update
|
||||
var icons = app.manifest.icons;
|
||||
var oldIcon = icons[Object.keys(icons)[0]];
|
||||
var oldUpdateTime = app.updateTime;
|
||||
setAppIcon('new_icon', continueTest);
|
||||
yield;
|
||||
|
||||
app.ondownloadavailable = function() {
|
||||
ok(false, 'Got a downloadavailable event for non-cached hosted apps');
|
||||
};
|
||||
|
||||
app.ondownloadapplied = function() {
|
||||
ok(true, 'Got a downloadapplied when checking for update');
|
||||
app.ondownloadapplied = app.ondownloadavailable = null;
|
||||
continueTest();
|
||||
};
|
||||
app.checkForUpdate();
|
||||
yield;
|
||||
|
||||
icons = app.manifest.icons;
|
||||
var newIcon = icons[Object.keys(icons)[0]];
|
||||
var newUpdateTime = app.updateTime;
|
||||
isnot(oldIcon, newIcon, 'The icon should be updated');
|
||||
isnot(oldUpdateTime, newUpdateTime, 'The update time should be updated');
|
||||
|
||||
// Uninstall the app.
|
||||
request = navigator.mozApps.mgmt.uninstall(app);
|
||||
request.onerror = cbError;
|
||||
@ -157,6 +182,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=826058
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function setAppIcon(icon, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function() { is(xhr.responseText, "OK", "setIcon OK"); cb(); });
|
||||
xhr.addEventListener("error", cbError);
|
||||
xhr.addEventListener("abort", cbError);
|
||||
xhr.open('GET', gBaseURL + 'file_app.sjs?setIcon=' + icon, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// This function checks the state of an installed app. It does the following:
|
||||
//
|
||||
// * Check various state on the app object itself.
|
||||
@ -199,6 +233,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=826058
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
// This event is triggered when the app calls "alert".
|
||||
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
|
||||
|
||||
// Add the iframe to the DOM, triggering the launch.
|
||||
@ -227,6 +263,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=826058
|
||||
</head>
|
||||
<body onload="go()">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=826058">Mozilla Bug 826058</a>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=863337">Mozilla Bug 863337</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
|
@ -112,7 +112,7 @@ this.PHONE_NUMBER_META_DATA = {
|
||||
"964": '["IQ","00","0",,,"$NP$FG","\\d{6,10}","[1-7]\\d{7,9}",[["(1)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["([2-6]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[2-6]",,],["(7\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]',
|
||||
"225": '["CI","00",,,,,"\\d{8}","[02-6]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
|
||||
"992": '["TJ","810","8",,,"($NP) $FG","\\d{3,9}","[3-59]\\d{8}",[["([349]\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","[34]7|91[78]",,],["([459]\\d)(\\d{3})(\\d{4})","$1 $2 $3","4[48]|5|9(?:1[59]|[0235-9])",,],["(331700)(\\d)(\\d{2})","$1 $2 $3","331",,],["(\\d{4})(\\d)(\\d{4})","$1 $2 $3","3[1-5]",,]]]',
|
||||
"55": '["BR","00(?:1[45]|2[135]|[34]1|43)","0","0(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?","$2",,"\\d{8,11}","[1-46-9]\\d{7,10}|5\\d{8,9}",[["(\\d{4})(\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\d{5})(\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\d{2})(\\d{5})(\\d{4})","$1 $2-$3","1[1-9]9","($FG)",],["(\\d{2})(\\d{4})(\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)",],["([34]00\\d)(\\d{4})","$1-$2","[34]00",,],["([3589]00)(\\d{2,3})(\\d{4})","$1 $2 $3","[3589]00","$NP$FG",]]]',
|
||||
"55": '["BR","00(?:1[45]|2[135]|[34]1|43)","0","(?:0|90)(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?","$2",,"\\d{8,11}","[1-46-9]\\d{7,10}|5\\d{8,9}",[["(\\d{4})(\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\d{5})(\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\d{2})(\\d{5})(\\d{4})","$1 $2-$3","1[1-9]9","($FG)",],["(\\d{2})(\\d{4})(\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)",],["([34]00\\d)(\\d{4})","$1-$2","[34]00",,],["([3589]00)(\\d{2,3})(\\d{4})","$1 $2 $3","[3589]00","$NP$FG",]]]',
|
||||
"674": '["NR","00",,,,,"\\d{7}","[458]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
|
||||
"967": '["YE","00","0",,,"$NP$FG","\\d{6,9}","[1-7]\\d{6,8}",[["([1-7])(\\d{3})(\\d{3,4})","$1 $2 $3","[1-6]|7[24-68]",,],["(7\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","7[0137]",,]]]',
|
||||
"49": '["DE","00","0",,,"$NP$FG","\\d{2,15}","[1-35-9]\\d{3,14}|4(?:[0-8]\\d{4,12}|9(?:[0-37]\\d|4(?:[1-35-8]|4\\d?)|5\\d{1,2}|6[1-8]\\d?)\\d{2,7})",[["(1\\d{2})(\\d{7,8})","$1 $2","1[67]",,],["(1\\d{3})(\\d{7})","$1 $2","15",,],["(\\d{2})(\\d{4,11})","$1 $2","3[02]|40|[68]9",,],["(\\d{3})(\\d{3,11})","$1 $2","2(?:\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",,],["(\\d{4})(\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\d[1-9]|[1-9]\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",,],["(3\\d{4})(\\d{1,10})","$1 $2","3",,],["(800)(\\d{7,10})","$1 $2","800",,],["(177)(99)(\\d{7,8})","$1 $2 $3","177",,],["(\\d{3})(\\d)(\\d{4,10})","$1 $2 $3","(?:18|90)0",,],["(1\\d{2})(\\d{5,11})","$1 $2","181",,],["(18\\d{3})(\\d{6})","$1 $2","185",,],["(18\\d{2})(\\d{7})","$1 $2","18[68]",,],["(18\\d)(\\d{8})","$1 $2","18[2-579]",,],["(700)(\\d{4})(\\d{4})","$1 $2 $3","700",,]]]',
|
||||
|
@ -114,6 +114,16 @@ function TestProperties(dial, currentRegion) {
|
||||
}
|
||||
}
|
||||
|
||||
function AllEqual(list, currentRegion) {
|
||||
var first = PhoneNumber.Parse(list.shift(), currentRegion);
|
||||
ok(!!first, "first parses");
|
||||
for (var index in list) {
|
||||
var other = PhoneNumber.Parse(list[index], currentRegion);
|
||||
ok(!!other, "other parses");
|
||||
ok(first.internationalNumber == other.internationalNumber, "first and other match");
|
||||
}
|
||||
}
|
||||
|
||||
TestProperties("+0988782456");
|
||||
TestProperties("+33442020", "ES");
|
||||
TestProperties("+43987614", "ES");
|
||||
@ -319,5 +329,9 @@ Normalize("+ABC # * , 9 _ 1 _0", "+222#*,910");
|
||||
Normalize("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "22233344455566677778889999");
|
||||
Normalize("abcdefghijklmnopqrstuvwxyz", "22233344455566677778889999");
|
||||
|
||||
// 8 and 9 digit numbers with area code in Brazil with collect call prefix (90)
|
||||
AllEqual(["01187654321","0411187654321","551187654321","90411187654321","+551187654321"],"BR");
|
||||
AllEqual(["011987654321","04111987654321","5511987654321","904111987654321","+5511987654321"],"BR");
|
||||
|
||||
</script>
|
||||
</window>
|
||||
|
@ -874,7 +874,7 @@ NetworkManager.prototype = {
|
||||
try {
|
||||
let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" +
|
||||
this.possibleInterface[i]);
|
||||
if (file.IsDirectory()) {
|
||||
if (file.exists()) {
|
||||
return this.possibleInterface[i];
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -26,6 +26,7 @@ CPP_SOURCES += [
|
||||
'testDefineProperty.cpp',
|
||||
'testEnclosingFunction.cpp',
|
||||
'testErrorCopying.cpp',
|
||||
'testException.cpp',
|
||||
'testExternalStrings.cpp',
|
||||
'testFindSCCs.cpp',
|
||||
'testFuncCallback.cpp',
|
||||
|
26
js/src/jsapi-tests/testException.cpp
Normal file
26
js/src/jsapi-tests/testException.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#include "tests.h"
|
||||
|
||||
BEGIN_TEST(testException_bug860435)
|
||||
{
|
||||
JS::RootedValue fun(cx);
|
||||
|
||||
EVAL("ReferenceError", fun.address());
|
||||
CHECK(fun.isObject());
|
||||
|
||||
JS::RootedValue v(cx);
|
||||
JS_CallFunctionValue(cx, global, fun, 0, v.address(), v.address());
|
||||
CHECK(v.isObject());
|
||||
|
||||
JS_GetProperty(cx, &v.toObject(), "stack", v.address());
|
||||
CHECK(v.isString());
|
||||
return true;
|
||||
}
|
||||
END_TEST(testException_bug860435)
|
@ -571,7 +571,7 @@ Exception(JSContext *cx, unsigned argc, Value *vp)
|
||||
NonBuiltinScriptFrameIter iter(cx);
|
||||
|
||||
/* Set the 'fileName' property. */
|
||||
RootedScript script(cx, iter.script());
|
||||
RootedScript script(cx, iter.done() ? NULL : iter.script());
|
||||
RootedString filename(cx);
|
||||
if (args.length() > 1) {
|
||||
filename = ToString<CanGC>(cx, args[1]);
|
||||
|
@ -3768,7 +3768,9 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
|
||||
AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
|
||||
}
|
||||
|
||||
AdjustOverlappingScrollbars(vRect, hRect);
|
||||
if (!LookAndFeel::GetInt(LookAndFeel::eIntID_AllowOverlayScrollbarsOverlap)) {
|
||||
AdjustOverlappingScrollbars(vRect, hRect);
|
||||
}
|
||||
if (mVScrollbarBox) {
|
||||
nsBoxFrame::LayoutChildAt(aState, mVScrollbarBox, vRect);
|
||||
}
|
||||
|
@ -97,6 +97,38 @@ nsPlaceholderFrame::Reflow(nsPresContext* aPresContext,
|
||||
const nsHTMLReflowState& aReflowState,
|
||||
nsReflowStatus& aStatus)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// We should be getting reflowed before our out-of-flow.
|
||||
// If this is our first reflow, and our out-of-flow has already received its
|
||||
// first reflow (before us), complain.
|
||||
// XXXdholbert This "look for a previous continuation or IB-split sibling"
|
||||
// code could use nsLayoutUtils::GetPrevContinuationOrSpecialSibling(), if
|
||||
// we ever add a function like that. (We currently have a "Next" version.)
|
||||
if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) &&
|
||||
!(mOutOfFlowFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
|
||||
|
||||
// Unfortunately, this can currently happen when the placeholder is in a
|
||||
// later continuation or later IB-split sibling than its out-of-flow (as
|
||||
// is the case in some of our existing unit tests). So for now, in that
|
||||
// case, we'll warn instead of asserting.
|
||||
bool isInContinuationOrIBSplit = false;
|
||||
nsIFrame* ancestor = this;
|
||||
while ((ancestor = ancestor->GetParent())) {
|
||||
if (ancestor->GetPrevContinuation() ||
|
||||
ancestor->Properties().Get(IBSplitSpecialPrevSibling())) {
|
||||
isInContinuationOrIBSplit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInContinuationOrIBSplit) {
|
||||
NS_WARNING("Out-of-flow frame got reflowed before its placeholder");
|
||||
} else {
|
||||
NS_ERROR("Out-of-flow frame got reflowed before its placeholder");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DO_GLOBAL_REFLOW_COUNT("nsPlaceholderFrame");
|
||||
DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
|
||||
aDesiredSize.width = 0;
|
||||
|
@ -40,6 +40,8 @@ public:
|
||||
const nsHTMLReflowState& aReflowState,
|
||||
nsReflowStatus& aStatus) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsQueryFrame::FrameIID GetFrameId() = 0;
|
||||
|
||||
protected:
|
||||
nsMathMLSelectedFrame(nsStyleContext* aContext) :
|
||||
nsMathMLContainerFrame(aContext) {}
|
||||
@ -49,6 +51,9 @@ protected:
|
||||
nsIFrame* mSelectedFrame;
|
||||
|
||||
bool mInvalidMarkup;
|
||||
|
||||
private:
|
||||
void* operator new(size_t, nsIPresShell*) MOZ_MUST_OVERRIDE MOZ_DELETE;
|
||||
};
|
||||
|
||||
#endif /* nsMathMLSelectedFrame_h___ */
|
||||
|
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html class="reftest-print">
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
p {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
11
layout/reftests/css-animations/print-no-animations-ref.html
Normal file
11
layout/reftests/css-animations/print-no-animations-ref.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html class="reftest-print">
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
p {
|
||||
color: olive;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
16
layout/reftests/css-animations/print-no-animations.html
Normal file
16
layout/reftests/css-animations/print-no-animations.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html class="reftest-print">
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
@keyframes a {
|
||||
from, to { color: blue }
|
||||
}
|
||||
|
||||
p {
|
||||
color: olive;
|
||||
animation: a 1s infinite;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
4
layout/reftests/css-animations/reftest.list
Normal file
4
layout/reftests/css-animations/reftest.list
Normal file
@ -0,0 +1,4 @@
|
||||
== screen-animations.html screen-animations-ref.html
|
||||
!= screen-animations.html screen-animations-notref.html
|
||||
fails == print-no-animations.html print-no-animations-ref.html # reftest harness doesn't actually make pres context non-dynamic for reftest-print tests
|
||||
fails != print-no-animations.html print-no-animations-notref.html # reftest harness doesn't actually make pres context non-dynamic for reftest-print tests
|
11
layout/reftests/css-animations/screen-animations-notref.html
Normal file
11
layout/reftests/css-animations/screen-animations-notref.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
p {
|
||||
color: olive;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
11
layout/reftests/css-animations/screen-animations-ref.html
Normal file
11
layout/reftests/css-animations/screen-animations-ref.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
p {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
16
layout/reftests/css-animations/screen-animations.html
Normal file
16
layout/reftests/css-animations/screen-animations.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<title>Static CSS animation</title>
|
||||
<style>
|
||||
|
||||
@keyframes a {
|
||||
from, to { color: blue }
|
||||
}
|
||||
|
||||
p {
|
||||
color: olive;
|
||||
animation: a 1s infinite;
|
||||
}
|
||||
|
||||
</style>
|
||||
<p>blue with animation support; olive without</p>
|
@ -48,6 +48,9 @@ include bugs/reftest.list
|
||||
# canvas 2D
|
||||
include canvas/reftest.list
|
||||
|
||||
# css animations
|
||||
include css-animations/reftest.list
|
||||
|
||||
# css calc() tests
|
||||
include css-calc/reftest.list
|
||||
|
||||
|
@ -539,6 +539,11 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
||||
mozilla::dom::Element* aElement)
|
||||
{
|
||||
if (!mPresContext->IsProcessingAnimationStyleChange()) {
|
||||
if (!mPresContext->IsDynamic()) {
|
||||
// For print or print preview, ignore animations.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Everything that causes our animation data to change triggers a
|
||||
// style change, which in turn triggers a non-animation restyle.
|
||||
// Likewise, when we initially construct frames, we're not in a
|
||||
@ -949,6 +954,11 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
|
||||
aPseudoType == nsCSSPseudoElements::ePseudo_after,
|
||||
"forbidden pseudo type");
|
||||
|
||||
if (!mPresContext->IsDynamic()) {
|
||||
// For print or print preview, ignore animations.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ElementAnimations *ea =
|
||||
GetElementAnimations(aElement, aPseudoType, false);
|
||||
if (!ea) {
|
||||
|
@ -4177,6 +4177,7 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
|
||||
if (eCSSToken_Number != mToken.mType ||
|
||||
!mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
|
||||
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
||||
UngetToken();
|
||||
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
||||
}
|
||||
numbers[1] = mToken.mInteger * sign[1];
|
||||
|
@ -451,6 +451,11 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
|
||||
aNewStyleContext->HasPseudoElementData(),
|
||||
"pseudo type mismatch");
|
||||
|
||||
if (!mPresContext->IsDynamic()) {
|
||||
// For print or print preview, ignore transitions.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// NOTE: Things in this function (and ConsiderStartingTransition)
|
||||
// should never call PeekStyleData because we don't preserve gotten
|
||||
// structs across reframes.
|
||||
@ -893,6 +898,11 @@ nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mPresContext->IsDynamic()) {
|
||||
// For print or print preview, ignore animations.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData->mPresContext->IsProcessingRestyles() &&
|
||||
!aData->mPresContext->IsProcessingAnimationStyleChange()) {
|
||||
// If we're processing a normal style change rather than one from
|
||||
|
@ -628,6 +628,19 @@ function run() {
|
||||
test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )");
|
||||
test_parseable(":nth-child(+1/**/n-1)");
|
||||
test_parseable(":nth-child(1/**/n-1)");
|
||||
// bug 876570
|
||||
test_balanced_unparseable(":nth-child(+2n-)");
|
||||
test_balanced_unparseable(":nth-child(+n-)");
|
||||
test_balanced_unparseable(":nth-child(-2n-)");
|
||||
test_balanced_unparseable(":nth-child(-n-)");
|
||||
test_balanced_unparseable(":nth-child(2n-)");
|
||||
test_balanced_unparseable(":nth-child(n-)");
|
||||
test_balanced_unparseable(":nth-child(+2n+)");
|
||||
test_balanced_unparseable(":nth-child(+n+)");
|
||||
test_balanced_unparseable(":nth-child(-2n+)");
|
||||
test_balanced_unparseable(":nth-child(-n+)");
|
||||
test_balanced_unparseable(":nth-child(2n+)");
|
||||
test_balanced_unparseable(":nth-child(n+)");
|
||||
|
||||
// exercise the an+b matching logic particularly hard for
|
||||
// :nth-child() (since we know we use the same code for all 4)
|
||||
|
@ -1504,7 +1504,7 @@ function RecordResult(testRunTime, errorMsg, scriptResults)
|
||||
gCurrentCanvas = gURICanvases[gCurrentURL];
|
||||
}
|
||||
if (gCurrentCanvas == null) {
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | program error managing snapshots\n");
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | program error managing snapshots\n");
|
||||
++gTestResults.Exception;
|
||||
}
|
||||
if (gState == 1) {
|
||||
@ -1817,7 +1817,7 @@ function RecvContentReady()
|
||||
|
||||
function RecvException(what)
|
||||
{
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | "+ what +"\n");
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | " + what + "\n");
|
||||
++gTestResults.Exception;
|
||||
}
|
||||
|
||||
@ -1840,7 +1840,7 @@ function RecvLog(type, msg)
|
||||
} else if (type == "warning") {
|
||||
LogWarning(msg);
|
||||
} else {
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | unknown log type "+ type +"\n");
|
||||
gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | unknown log type " + type + "\n");
|
||||
++gTestResults.Exception;
|
||||
}
|
||||
}
|
||||
|
@ -205,10 +205,10 @@ class RemoteSourceStreamInfo {
|
||||
|
||||
RemoteSourceStreamInfo(already_AddRefed<DOMMediaStream> aMediaStream,
|
||||
PeerConnectionMedia *aParent)
|
||||
: mMediaStream(aMediaStream),
|
||||
: mTrackTypeHints(0),
|
||||
mMediaStream(aMediaStream),
|
||||
mPipelines(),
|
||||
mParent(aParent),
|
||||
mTrackTypeHints(0) {
|
||||
mParent(aParent) {
|
||||
MOZ_ASSERT(mMediaStream);
|
||||
}
|
||||
|
||||
|
@ -581,9 +581,9 @@ static void updateVideoPref( unsigned int event, line_t line_id, callid_t call_i
|
||||
* digits - memory to return the first param
|
||||
* Returns:
|
||||
*/
|
||||
void getDigits(string_t data, char *digits) {
|
||||
static void getDigits(string_t data, char *digits, unsigned int buffer_length) {
|
||||
char *endptr;
|
||||
int len=0;
|
||||
unsigned int len=0;
|
||||
|
||||
digits[0]=0;
|
||||
|
||||
@ -595,6 +595,11 @@ void getDigits(string_t data, char *digits) {
|
||||
len = strlen(data);
|
||||
}
|
||||
|
||||
/* prevent len from writing past buffer size */
|
||||
if (len >= buffer_length) {
|
||||
len = buffer_length - 1;
|
||||
}
|
||||
|
||||
if ( len) {
|
||||
memcpy(digits, data, len);
|
||||
digits[len] = 0;
|
||||
@ -692,7 +697,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d
|
||||
break;
|
||||
case CC_FEATURE_DIALSTR:
|
||||
if (CheckAndGetAvailableLine(&line_id, &call_id) == TRUE) {
|
||||
getDigits(data, digits);
|
||||
getDigits(data, digits, sizeof(digits));
|
||||
if (strlen(digits) == 0) {
|
||||
//if dial string is empty then go offhook
|
||||
cc_offhook(CC_SRC_UI, call_id, line_id);
|
||||
@ -779,7 +784,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d
|
||||
break;
|
||||
}
|
||||
|
||||
getDigits(data,digits);
|
||||
getDigits(data, digits, sizeof(digits));
|
||||
|
||||
dp_int_init_dialing_data(line_id, call_id);
|
||||
dp_int_dial_immediate(line_id, call_id, TRUE,
|
||||
@ -890,7 +895,7 @@ processSessionEvent (line_t line_id, callid_t call_id, unsigned int event, sdp_d
|
||||
}// DON'T ADD BREAK HERE. EVENT IS PASSED BELOW
|
||||
case CC_FEATURE_B2BCONF:
|
||||
case CC_FEATURE_XFER:
|
||||
getDigits(data,digits);
|
||||
getDigits(data, digits, sizeof(digits));
|
||||
if ( strlen(digits)) {
|
||||
cc_feature_data_t ftr_data;
|
||||
CCAPP_DEBUG(DEB_F_PREFIX"conf: sid=%s.", DEB_F_PREFIX_ARGS(SIP_CC_PROV, fname),data);
|
||||
|
@ -391,8 +391,8 @@
|
||||
* error to use it, or an array of such objects, as the type of a new
|
||||
* expression (unless placement new is being used). If a member of another
|
||||
* class uses this class, or if another class inherits from this class, then
|
||||
* it is considered to be a stack class as well, although this attribute need
|
||||
* not be provided in such cases.
|
||||
* it is considered to be a non-heap class as well, although this attribute
|
||||
* need not be provided in such cases.
|
||||
*/
|
||||
#ifdef MOZ_CLANG_PLUGIN
|
||||
# define MOZ_MUST_OVERRIDE __attribute__((annotate("moz_must_override")))
|
||||
|
@ -95,6 +95,9 @@ pref("network.http.max-connections", 20);
|
||||
pref("network.http.max-persistent-connections-per-server", 6);
|
||||
pref("network.http.max-persistent-connections-per-proxy", 20);
|
||||
|
||||
// spdy
|
||||
pref("network.http.spdy.push-allowance", 32768);
|
||||
|
||||
// See bug 545869 for details on why these are set the way they are
|
||||
pref("network.buffer.cache.count", 24);
|
||||
pref("network.buffer.cache.size", 16384);
|
||||
|
@ -1277,7 +1277,14 @@ public class GeckoAppShell
|
||||
if (android.os.Build.VERSION.SDK_INT >= 11) {
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager)
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText("Text", text));
|
||||
ClipData clip = ClipData.newPlainText("Text", text);
|
||||
try {
|
||||
cm.setPrimaryClip(clip);
|
||||
} catch (NullPointerException e) {
|
||||
// Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw
|
||||
// a NullPointerException if Samsung's /data/clipboard directory is full.
|
||||
// Fortunately, the text is still successfully copied to the clipboard.
|
||||
}
|
||||
} else {
|
||||
android.text.ClipboardManager cm = (android.text.ClipboardManager)
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
@ -149,9 +149,9 @@ let Downloads = {
|
||||
this._stepAddEntries(privateEntries, this._privateList, privateEntries.length);
|
||||
|
||||
// Add non-private downloads
|
||||
let normalEntries = this.getDownloads({ isPrivate: false });
|
||||
this._stepAddEntries(normalEntries, this._normalList, 1);
|
||||
ContextMenus.init();
|
||||
let normalEntries = this.getDownloads({ isPrivate: false });
|
||||
this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this));
|
||||
ContextMenus.init();
|
||||
},
|
||||
|
||||
uninit: function dl_uninit() {
|
||||
@ -358,6 +358,7 @@ let Downloads = {
|
||||
|
||||
let updatedState = this._getState(aStmt.row.state);
|
||||
// Try to get the attribute values from the statement
|
||||
|
||||
return {
|
||||
guid: aStmt.row.guid,
|
||||
target: aStmt.row.name,
|
||||
@ -377,9 +378,14 @@ let Downloads = {
|
||||
}
|
||||
},
|
||||
|
||||
_stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems) {
|
||||
if (aEntries.length == 0)
|
||||
_stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) {
|
||||
|
||||
if (aEntries.length == 0){
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let attrs = aEntries.shift();
|
||||
let item = this._createItem(downloadTemplate, attrs);
|
||||
@ -388,12 +394,12 @@ let Downloads = {
|
||||
// Add another item to the list if we should; otherwise, let the UI update
|
||||
// and continue later
|
||||
if (aNumItems > 1) {
|
||||
this._stepAddEntries(aEntries, aList, aNumItems - 1);
|
||||
this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback);
|
||||
} else {
|
||||
// Use a shorter delay for earlier downloads to display them faster
|
||||
let delay = Math.min(aList.itemCount * 10, 300);
|
||||
setTimeout(function () {
|
||||
this._stepAddEntries(aEntries, aList, 5);
|
||||
this._stepAddEntries(aEntries, aList, 5, aCallback);
|
||||
}.bind(this), delay);
|
||||
}
|
||||
},
|
||||
@ -552,6 +558,31 @@ let Downloads = {
|
||||
this.logError("_updateDownloadRow() " + ex, aDownload);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* In case a specific downloadId was passed while opening, scrolls the list to
|
||||
* the given elemenet
|
||||
*/
|
||||
|
||||
_scrollToSelectedDownload : function dl_scrollToSelected() {
|
||||
let spec = document.location.href;
|
||||
let pos = spec.indexOf("?");
|
||||
let query = "";
|
||||
if (pos >= 0)
|
||||
query = spec.substring(pos + 1);
|
||||
|
||||
// Just assume the query is "id=<id>"
|
||||
let id = query.substring(3);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
downloadElement = this._getElementForDownload(id);
|
||||
if (!downloadElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadElement.scrollIntoView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs the error to the console.
|
||||
|
@ -57,6 +57,16 @@ XPCOMUtils.defineLazyGetter(this, "Sanitizer", function() {
|
||||
return Sanitizer;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Prompt", function() {
|
||||
let temp = {};
|
||||
Cu.import("resource://gre/modules/Prompt.jsm", temp);
|
||||
return temp.Prompt;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
|
||||
// Lazily-loaded browser scripts:
|
||||
[
|
||||
["HelperApps", "chrome://browser/content/HelperApps.js"],
|
||||
@ -238,6 +248,7 @@ var BrowserApp = {
|
||||
_tabs: [],
|
||||
_selectedTab: null,
|
||||
_prefObservers: [],
|
||||
_promptHandlers: {},
|
||||
|
||||
get isTablet() {
|
||||
let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
|
||||
@ -287,6 +298,7 @@ var BrowserApp = {
|
||||
Services.obs.addObserver(this, "FormHistory:Init", false);
|
||||
Services.obs.addObserver(this, "gather-telemetry", false);
|
||||
Services.obs.addObserver(this, "keyword-search", false);
|
||||
Services.obs.addObserver(this, "Prompt:Reply", false);
|
||||
|
||||
Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
|
||||
|
||||
@ -1412,6 +1424,18 @@ var BrowserApp = {
|
||||
browser.contentDocument.mozCancelFullScreen();
|
||||
break;
|
||||
|
||||
case "Prompt:Reply":
|
||||
{
|
||||
let data = JSON.parse(aData);
|
||||
let guid = data.guid;
|
||||
let handler = this._promptHandlers[guid];
|
||||
if (!handler)
|
||||
break;
|
||||
this._promptHandlers[guid];
|
||||
handler(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Viewport:Change":
|
||||
if (this.isBrowserContentDocumentDisplayed())
|
||||
this.selectedTab.setViewport(JSON.parse(aData));
|
||||
@ -1476,9 +1500,12 @@ var BrowserApp = {
|
||||
// selecting selIndex(if fromIndex<=selIndex<=toIndex)
|
||||
showHistory: function(fromIndex, toIndex, selIndex) {
|
||||
let browser = this.selectedBrowser;
|
||||
let guid = uuidgen.generateUUID().toString();
|
||||
let result = {
|
||||
type: "Prompt:Show",
|
||||
multiple: false,
|
||||
async: true,
|
||||
guid: guid,
|
||||
selected: [],
|
||||
listitems: []
|
||||
};
|
||||
@ -1495,11 +1522,13 @@ var BrowserApp = {
|
||||
result.listitems.push(item);
|
||||
result.selected.push(i == selIndex);
|
||||
}
|
||||
let data = JSON.parse(sendMessageToJava(result));
|
||||
let selected = data.button;
|
||||
if (selected == -1)
|
||||
return;
|
||||
browser.gotoIndex(toIndex-selected);
|
||||
this._promptHandlers[guid] = function (data) {
|
||||
let selected = data.button;
|
||||
if (selected == -1)
|
||||
return;
|
||||
browser.gotoIndex(toIndex-selected);
|
||||
};
|
||||
sendMessageToJava(result);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -37,11 +37,17 @@ var Downloads = {
|
||||
Services.obs.addObserver(this, "last-pb-context-exited", true);
|
||||
},
|
||||
|
||||
openDownload: function dl_openDownload(aFileURI) {
|
||||
let f = this._getLocalFile(aFileURI);
|
||||
openDownload: function dl_openDownload(aDownload) {
|
||||
let fileUri = aDownload.target.spec;
|
||||
let guid = aDownload.guid;
|
||||
let f = this._getLocalFile(fileUri);
|
||||
try {
|
||||
f.launch();
|
||||
} catch (ex) { }
|
||||
} catch (ex) {
|
||||
// in case we are not able to open the file (i.e. there is no app able to handle it)
|
||||
// we just open the browser tab showing it
|
||||
BrowserApp.addTab("about:downloads?id=" + guid);
|
||||
}
|
||||
},
|
||||
|
||||
cancelDownload: function dl_cancelDownload(aDownload) {
|
||||
@ -65,7 +71,7 @@ var Downloads = {
|
||||
if (aTopic == "alertclickcallback") {
|
||||
if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
|
||||
// Only open the downloaded file if the download is complete
|
||||
self.openDownload(aDownload.target.spec);
|
||||
self.openDownload(aDownload);
|
||||
} else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING &&
|
||||
!cancelPrompt) {
|
||||
cancelPrompt = true;
|
||||
|
@ -15,6 +15,7 @@ EXTRA_JS_MODULES = \
|
||||
OrderedBroadcast.jsm \
|
||||
Sanitizer.jsm \
|
||||
SharedPreferences.jsm \
|
||||
Prompt.jsm \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
169
mobile/android/modules/Prompt.jsm
Normal file
169
mobile/android/modules/Prompt.jsm
Normal file
@ -0,0 +1,169 @@
|
||||
/* 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/. */
|
||||
"use strict"
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Prompt"];
|
||||
|
||||
function log(msg) {
|
||||
//Services.console.logStringMessage(msg);
|
||||
}
|
||||
|
||||
function Prompt(aOptions) {
|
||||
this.window = "window" in aOptions ? aOptions.window : null;
|
||||
this.msg = { type: "Prompt:Show", async: true };
|
||||
|
||||
if ("title" in aOptions)
|
||||
this.msg.title = aOptions.title;
|
||||
|
||||
if ("message" in aOptions)
|
||||
this.msg.text = aOptions.message;
|
||||
|
||||
if ("buttons" in aOptions)
|
||||
this.msg.buttons = aOptions.buttons;
|
||||
|
||||
let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
this.guid = idService.generateUUID().toString();
|
||||
this.msg.guid = this.guid;
|
||||
}
|
||||
|
||||
Prompt.prototype = {
|
||||
addButton: function(aOptions) {
|
||||
if (!this.msg.buttons)
|
||||
this.msg.buttons = [];
|
||||
this.msg.buttons.push(aOptions.label);
|
||||
return this;
|
||||
},
|
||||
|
||||
_addInput: function(aOptions) {
|
||||
let obj = aOptions;
|
||||
if (this[aOptions.type + "_count"] === undefined)
|
||||
this[aOptions.type + "_count"] = 0;
|
||||
|
||||
obj.id = aOptions.id || (aOptions.type + this[aOptions.type + "_count"]);
|
||||
this[aOptions.type + "_count"]++;
|
||||
|
||||
this.msg.inputs.push(obj);
|
||||
return this;
|
||||
},
|
||||
|
||||
addCheckbox: function(aOptions) {
|
||||
return this._addInput({
|
||||
type: "checkbox",
|
||||
label: aOptions.label,
|
||||
checked: aOptions.checked,
|
||||
id: aOptions.id
|
||||
});
|
||||
},
|
||||
|
||||
addTextbox: function(aOptions) {
|
||||
return this._addInput({
|
||||
type: "textbox",
|
||||
value: aOptions.value,
|
||||
hint: aOptions.hint,
|
||||
autofocus: aOptions.autofocus,
|
||||
id: aOptions.id
|
||||
});
|
||||
},
|
||||
|
||||
addPassword: function(aOptions) {
|
||||
return this._addInput({
|
||||
type: "password",
|
||||
value: aOptions.value,
|
||||
hint: aOptions.hint,
|
||||
autofocus: aOptions.autofocus,
|
||||
id : aOptions.id
|
||||
});
|
||||
},
|
||||
|
||||
addMenulist: function(aOptions) {
|
||||
return this._addInput({
|
||||
type: "menulist",
|
||||
values: aOptions.values,
|
||||
id: aOptions.id
|
||||
});
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
this.callback = callback;
|
||||
log("Sending message");
|
||||
Services.obs.addObserver(this, "Prompt:Reply", false);
|
||||
this._innerShow();
|
||||
},
|
||||
|
||||
_innerShow: function() {
|
||||
this.bridge.handleGeckoMessage(JSON.stringify(this.msg));
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
log("observe " + aData);
|
||||
let data = JSON.parse(aData);
|
||||
if (data.guid != this.guid)
|
||||
return;
|
||||
|
||||
Services.obs.removeObserver(this, "Prompt:Reply", false);
|
||||
|
||||
if (this.callback)
|
||||
this.callback(data);
|
||||
},
|
||||
|
||||
_setListItems: function(aItems, aInGroup) {
|
||||
let hasSelected = false;
|
||||
if (!aInGroup)
|
||||
this.msg.listitems = [];
|
||||
|
||||
aItems.forEach(function(item) {
|
||||
let obj = { id: item.id };
|
||||
|
||||
if (aInGroup !== undefined)
|
||||
obj.inGroup = aInGroup;
|
||||
|
||||
obj.label = item.label;
|
||||
|
||||
if (item.disabled)
|
||||
obj.disabled = true;
|
||||
|
||||
if (item.selected || hasSelected || this.msg.multiple) {
|
||||
if (!this.msg.selected) {
|
||||
this.msg.selected = new Array(this.msg.listitems.length);
|
||||
hasSelected = true;
|
||||
}
|
||||
this.msg.selected[this.msg.listitems.length] = item.selected;
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
obj.isGroup = true;
|
||||
} else if (item.submenu) {
|
||||
obj.isParent = true;
|
||||
}
|
||||
|
||||
// Order matters in the java message, so make sure we add the obj
|
||||
// to the list before we add its children
|
||||
this.msg.listitems.push(obj);
|
||||
|
||||
if (item.children)
|
||||
this._setListItems(item.children, true);
|
||||
|
||||
}, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
setSingleChoiceItems: function(aItems) {
|
||||
return this._setListItems(aItems);
|
||||
},
|
||||
|
||||
setMultiChoiceItems: function(aItems) {
|
||||
this.msg.multiple = true;
|
||||
return this._setListItems(aItems);
|
||||
},
|
||||
|
||||
get bridge() {
|
||||
return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
|
||||
},
|
||||
|
||||
}
|
@ -1020,6 +1020,8 @@ pref("network.http.spdy.persistent-settings", false);
|
||||
pref("network.http.spdy.ping-threshold", 58);
|
||||
pref("network.http.spdy.ping-timeout", 8);
|
||||
pref("network.http.spdy.send-buffer-size", 131072);
|
||||
pref("network.http.spdy.allow-push", true);
|
||||
pref("network.http.spdy.push-allowance", 65536);
|
||||
|
||||
pref("network.http.diagnostics", false);
|
||||
|
||||
|
@ -80,11 +80,18 @@ interface nsILoadGroup : nsIRequest
|
||||
readonly attribute nsILoadGroupConnectionInfo connectionInfo;
|
||||
};
|
||||
|
||||
%{C++
|
||||
#include "mozilla/net/PSpdyPush3.h"
|
||||
%}
|
||||
|
||||
[ptr] native SpdyPushCache3Ptr(mozilla::net::SpdyPushCache3);
|
||||
|
||||
/**
|
||||
* Used to maintain state about the connections of a load group and
|
||||
* how they interact with blocking items like HEAD css/js loads.
|
||||
*/
|
||||
[uuid(d1f9f18e-3d85-473a-ad58-a2367d7cdb2a)]
|
||||
|
||||
[uuid(5361f30e-f968-437c-8f41-69d2756a6022)]
|
||||
interface nsILoadGroupConnectionInfo : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -104,4 +111,11 @@ interface nsILoadGroupConnectionInfo : nsISupports
|
||||
* blockers.
|
||||
*/
|
||||
unsigned long removeBlockingTransaction();
|
||||
|
||||
/* reading this attribute gives out weak pointers to the push
|
||||
* cache. The nsILoadGroupConnectionInfo implemenation owns the cache
|
||||
* and will destroy it when overwritten or when the load group
|
||||
* ends.
|
||||
*/
|
||||
[noscript] attribute SpdyPushCache3Ptr spdyPushCache3;
|
||||
};
|
||||
|
@ -1054,6 +1054,7 @@ public:
|
||||
nsLoadGroupConnectionInfo();
|
||||
private:
|
||||
int32_t mBlockingTransactionCount; // signed for PR_ATOMIC_*
|
||||
nsAutoPtr<mozilla::net::SpdyPushCache3> mSpdyCache3;
|
||||
};
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo)
|
||||
@ -1087,6 +1088,20 @@ nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* [noscript] attribute SpdyPushCache3Ptr spdyPushCache3; */
|
||||
NS_IMETHODIMP
|
||||
nsLoadGroupConnectionInfo::GetSpdyPushCache3(mozilla::net::SpdyPushCache3 **aSpdyPushCache3)
|
||||
{
|
||||
*aSpdyPushCache3 = mSpdyCache3.get();
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP
|
||||
nsLoadGroupConnectionInfo::SetSpdyPushCache3(mozilla::net::SpdyPushCache3 *aSpdyPushCache3)
|
||||
{
|
||||
mSpdyCache3 = aSpdyPushCache3;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsLoadGroup::Init()
|
||||
{
|
||||
static PLDHashTableOps hash_table_ops =
|
||||
|
@ -42,6 +42,12 @@ public:
|
||||
|
||||
const static uint32_t kSendingChunkSize = 4096;
|
||||
const static uint32_t kTCPSendBufferSize = 131072;
|
||||
|
||||
// until we have an API that can push back on receiving data (right now
|
||||
// WriteSegments is obligated to accept data and buffer) there is no
|
||||
// reason to throttle with the rwin other than in server push
|
||||
// scenarios.
|
||||
const static uint32_t kInitialRwin = 256 * 1024 * 1024;
|
||||
};
|
||||
|
||||
// this is essentially a single instantiation as a member of nsHttpHandler.
|
||||
|
60
netwerk/protocol/http/PSpdyPush3.h
Normal file
60
netwerk/protocol/http/PSpdyPush3.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
// SPDY Server Push as defined by
|
||||
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
|
||||
|
||||
/*
|
||||
A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer)
|
||||
and spooled there until a GET is made that can be matched up with it. At
|
||||
that time we have two spdy streams - the GET (aka the sink) and the PUSH
|
||||
(aka the source). Data is copied between those two streams for the lifetime
|
||||
of the transaction. This is true even if the transaction buffer is empty,
|
||||
partly complete, or totally loaded at the time the GET correspondence is made.
|
||||
|
||||
correspondence is done through a hash table of the full url, the spdy session,
|
||||
and the load group. The load group is implicit because that's where the
|
||||
hash is stored, the other items comprise the hash key.
|
||||
|
||||
Pushed streams are subject to aggressive flow control before they are matched
|
||||
with a GET at which point flow control is effectively disabled to match the
|
||||
client pull behavior.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_net_SpdyPush3_Public_h
|
||||
#define mozilla_net_SpdyPush3_Public_h
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsISupports.h"
|
||||
|
||||
class nsCString;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class SpdyPushedStream3;
|
||||
|
||||
// One Cache per load group
|
||||
class SpdyPushCache3
|
||||
{
|
||||
public:
|
||||
SpdyPushCache3();
|
||||
virtual ~SpdyPushCache3();
|
||||
|
||||
// The cache holds only weak pointers - no references
|
||||
bool RegisterPushedStream(nsCString key,
|
||||
SpdyPushedStream3 *stream);
|
||||
SpdyPushedStream3 *RemovePushedStream(nsCString key);
|
||||
SpdyPushedStream3 *GetPushedStream(nsCString key);
|
||||
|
||||
private:
|
||||
nsDataHashtable<nsCStringHashKey, SpdyPushedStream3 *> mHash;
|
||||
};
|
||||
|
||||
} // namespace mozilla::net
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_net_SpdyPush3_Public_h
|
395
netwerk/protocol/http/SpdyPush3.cpp
Normal file
395
netwerk/protocol/http/SpdyPush3.cpp
Normal file
@ -0,0 +1,395 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "nsDependentString.h"
|
||||
#include "SpdyPush3.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SpdyPushedStream3
|
||||
//////////////////////////////////////////
|
||||
|
||||
SpdyPushedStream3::SpdyPushedStream3(SpdyPush3TransactionBuffer *aTransaction,
|
||||
SpdySession3 *aSession,
|
||||
SpdyStream3 *aAssociatedStream,
|
||||
uint32_t aID)
|
||||
:SpdyStream3(aTransaction, aSession,
|
||||
0 /* priority is only for sending, so ignore it on push */)
|
||||
, mConsumerStream(nullptr)
|
||||
, mBufferedPush(aTransaction)
|
||||
, mStatus(NS_OK)
|
||||
, mPushCompleted(false)
|
||||
, mDeferCleanupOnSuccess(true)
|
||||
{
|
||||
LOG3(("SpdyPushedStream3 ctor this=%p 0x%X\n", this, aID));
|
||||
mStreamID = aID;
|
||||
mBufferedPush->SetPushStream(this);
|
||||
mLoadGroupCI = aAssociatedStream->LoadGroupConnectionInfo();
|
||||
mLastRead = TimeStamp::Now();
|
||||
}
|
||||
|
||||
bool
|
||||
SpdyPushedStream3::GetPushComplete()
|
||||
{
|
||||
return mPushCompleted;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPushedStream3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
{
|
||||
nsresult rv = SpdyStream3::WriteSegments(writer, count, countWritten);
|
||||
if (NS_SUCCEEDED(rv) && *countWritten) {
|
||||
mLastRead = TimeStamp::Now();
|
||||
}
|
||||
|
||||
if (rv == NS_BASE_STREAM_CLOSED) {
|
||||
mPushCompleted = true;
|
||||
rv = NS_OK; // this is what a normal HTTP transaction would do
|
||||
}
|
||||
if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv))
|
||||
mStatus = rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPushedStream3::ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *count)
|
||||
{
|
||||
// The SYN_STREAM for this has been processed, so we need to verify
|
||||
// that :host, :scheme, and :path MUST be present
|
||||
nsDependentCSubstring host, scheme, path;
|
||||
nsresult rv;
|
||||
|
||||
rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":host"), host);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X "
|
||||
"push without required :host\n", mSession, mStreamID));
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":scheme"), scheme);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X "
|
||||
"push without required :scheme\n", mSession, mStreamID));
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = SpdyStream3::FindHeader(NS_LITERAL_CSTRING(":path"), path);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG3(("SpdyPushedStream3::ReadSegments session=%p ID 0x%X "
|
||||
"push without required :host\n", mSession, mStreamID));
|
||||
return rv;
|
||||
}
|
||||
|
||||
CreatePushHashKey(nsCString(scheme), nsCString(host),
|
||||
mSession->Serial(), path,
|
||||
mOrigin, mHashKey);
|
||||
|
||||
LOG3(("SpdyPushStream3 0x%X hash key %s\n", mStreamID, mHashKey.get()));
|
||||
|
||||
// the write side of a pushed transaction just involves manipulating a little state
|
||||
SpdyStream3::mSentFinOnData = 1;
|
||||
SpdyStream3::mSynFrameComplete = 1;
|
||||
SpdyStream3::ChangeState(UPSTREAM_COMPLETE);
|
||||
*count = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
SpdyPushedStream3::GetHashKey(nsCString &key)
|
||||
{
|
||||
if (mHashKey.IsEmpty())
|
||||
return false;
|
||||
|
||||
key = mHashKey;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPushedStream3::ConnectPushedStream(SpdyStream3 *stream)
|
||||
{
|
||||
mSession->ConnectPushedStream(stream);
|
||||
}
|
||||
|
||||
bool
|
||||
SpdyPushedStream3::IsOrphaned(TimeStamp now)
|
||||
{
|
||||
MOZ_ASSERT(!now.IsNull());
|
||||
|
||||
// if spdy is not transmitting, and is also not connected to a consumer
|
||||
// stream, and its been like that for too long then it is oprhaned
|
||||
|
||||
if (mConsumerStream)
|
||||
return false;
|
||||
|
||||
bool rv = ((now - mLastRead).ToSeconds() > 30.0);
|
||||
if (rv) {
|
||||
LOG3(("SpdyPushCache3::IsOrphaned 0x%X IsOrphaned %3.2f\n",
|
||||
mStreamID, (now - mLastRead).ToSeconds()));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPushedStream3::GetBufferedData(char *buf,
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
{
|
||||
if (NS_FAILED(mStatus))
|
||||
return mStatus;
|
||||
|
||||
nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (!*countWritten)
|
||||
rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SpdyPushCache3
|
||||
//////////////////////////////////////////
|
||||
|
||||
SpdyPushCache3::SpdyPushCache3()
|
||||
{
|
||||
mHash.Init();
|
||||
}
|
||||
|
||||
SpdyPushCache3::~SpdyPushCache3()
|
||||
{
|
||||
mHash.Clear();
|
||||
}
|
||||
|
||||
SpdyPushedStream3 *
|
||||
SpdyPushCache3::GetPushedStream(nsCString key)
|
||||
{
|
||||
return mHash.Get(key);
|
||||
}
|
||||
|
||||
bool
|
||||
SpdyPushCache3::RegisterPushedStream(nsCString key,
|
||||
SpdyPushedStream3 *stream)
|
||||
{
|
||||
LOG3(("SpdyPushCache3::RegisterPushedStream %s 0x%X\n",
|
||||
key.get(), stream->StreamID()));
|
||||
if(mHash.Get(key))
|
||||
return false;
|
||||
mHash.Put(key, stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
SpdyPushedStream3 *
|
||||
SpdyPushCache3::RemovePushedStream(nsCString key)
|
||||
{
|
||||
SpdyPushedStream3 *rv = mHash.Get(key);
|
||||
LOG3(("SpdyPushCache3::RemovePushedStream %s 0x%X\n",
|
||||
key.get(), rv ? rv->StreamID() : 0));
|
||||
if (rv)
|
||||
mHash.Remove(key);
|
||||
return rv;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SpdyPush3TransactionBuffer
|
||||
// This is the nsAHttpTransction owned by the stream when the pushed
|
||||
// stream has not yet been matched with a pull request
|
||||
//////////////////////////////////////////
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS0(SpdyPush3TransactionBuffer)
|
||||
|
||||
SpdyPush3TransactionBuffer::SpdyPush3TransactionBuffer()
|
||||
: mStatus(NS_OK)
|
||||
, mRequestHead(nullptr)
|
||||
, mPushStream(nullptr)
|
||||
, mIsDone(false)
|
||||
, mBufferedHTTP1Size(kDefaultBufferSize)
|
||||
, mBufferedHTTP1Used(0)
|
||||
, mBufferedHTTP1Consumed(0)
|
||||
{
|
||||
mBufferedHTTP1 = new char[mBufferedHTTP1Size];
|
||||
}
|
||||
|
||||
SpdyPush3TransactionBuffer::~SpdyPush3TransactionBuffer()
|
||||
{
|
||||
delete mRequestHead;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPush3TransactionBuffer::SetConnection(nsAHttpConnection *conn)
|
||||
{
|
||||
}
|
||||
|
||||
nsAHttpConnection *
|
||||
SpdyPush3TransactionBuffer::Connection()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPush3TransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
|
||||
{
|
||||
*outCB = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPush3TransactionBuffer::OnTransportStatus(nsITransport* transport,
|
||||
nsresult status, uint64_t progress)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
SpdyPush3TransactionBuffer::IsDone()
|
||||
{
|
||||
return mIsDone;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::Status()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SpdyPush3TransactionBuffer::Caps()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
SpdyPush3TransactionBuffer::Available()
|
||||
{
|
||||
return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader,
|
||||
uint32_t count, uint32_t *countRead)
|
||||
{
|
||||
*countRead = 0;
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
uint32_t count, uint32_t *countWritten)
|
||||
{
|
||||
if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
|
||||
SpdySession3::EnsureBuffer(mBufferedHTTP1,
|
||||
mBufferedHTTP1Size + kDefaultBufferSize,
|
||||
mBufferedHTTP1Used,
|
||||
mBufferedHTTP1Size);
|
||||
}
|
||||
|
||||
count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
|
||||
nsresult rv = writer->OnWriteSegment(mBufferedHTTP1 + mBufferedHTTP1Used,
|
||||
count, countWritten);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mBufferedHTTP1Used += *countWritten;
|
||||
}
|
||||
else if (rv == NS_BASE_STREAM_CLOSED) {
|
||||
mIsDone = true;
|
||||
}
|
||||
|
||||
if (Available()) {
|
||||
SpdyStream3 *consumer = mPushStream->GetConsumerStream();
|
||||
|
||||
if (consumer) {
|
||||
LOG3(("SpdyPush3TransactionBuffer::WriteSegments notifying connection "
|
||||
"consumer data available 0x%X [%u]\n",
|
||||
mPushStream->StreamID(), Available()));
|
||||
mPushStream->ConnectPushedStream(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SpdyPush3TransactionBuffer::Http1xTransactionCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsHttpRequestHead *
|
||||
SpdyPush3TransactionBuffer::RequestHead()
|
||||
{
|
||||
if (!mRequestHead)
|
||||
mRequestHead = new nsHttpRequestHead();
|
||||
return mRequestHead;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::TakeSubTransactions(
|
||||
nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPush3TransactionBuffer::SetProxyConnectFailed()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SpdyPush3TransactionBuffer::Close(nsresult reason)
|
||||
{
|
||||
mStatus = reason;
|
||||
mIsDone = true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::AddTransaction(nsAHttpTransaction *trans)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SpdyPush3TransactionBuffer::PipelineDepth()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::SetPipelinePosition(int32_t position)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
SpdyPush3TransactionBuffer::PipelinePosition()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdyPush3TransactionBuffer::GetBufferedData(char *buf,
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
{
|
||||
*countWritten = std::min(count, static_cast<uint32_t>(Available()));
|
||||
if (*countWritten) {
|
||||
memcpy(buf, mBufferedHTTP1 + mBufferedHTTP1Consumed, *countWritten);
|
||||
mBufferedHTTP1Consumed += *countWritten;
|
||||
}
|
||||
|
||||
// If all the data has been consumed then reset the buffer
|
||||
if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
|
||||
mBufferedHTTP1Consumed = 0;
|
||||
mBufferedHTTP1Used = 0;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla::net
|
||||
} // namespace mozilla
|
102
netwerk/protocol/http/SpdyPush3.h
Normal file
102
netwerk/protocol/http/SpdyPush3.h
Normal file
@ -0,0 +1,102 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
// SPDY Server Push as defined by
|
||||
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
|
||||
|
||||
#ifndef mozilla_net_SpdyPush3_Internal_h
|
||||
#define mozilla_net_SpdyPush3_Internal_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsHttpRequestHead.h"
|
||||
#include "nsILoadGroup.h"
|
||||
#include "nsString.h"
|
||||
#include "PSpdyPush3.h"
|
||||
#include "SpdySession3.h"
|
||||
#include "SpdyStream3.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class SpdyPush3TransactionBuffer;
|
||||
|
||||
class SpdyPushedStream3 MOZ_FINAL : public SpdyStream3
|
||||
{
|
||||
public:
|
||||
SpdyPushedStream3(SpdyPush3TransactionBuffer *aTransaction,
|
||||
SpdySession3 *aSession,
|
||||
SpdyStream3 *aAssociatedStream,
|
||||
uint32_t aID);
|
||||
virtual ~SpdyPushedStream3() {}
|
||||
|
||||
bool GetPushComplete();
|
||||
SpdyStream3 *GetConsumerStream() { return mConsumerStream; };
|
||||
void SetConsumerStream(SpdyStream3 *aStream) { mConsumerStream = aStream; }
|
||||
bool GetHashKey(nsCString &key);
|
||||
|
||||
// override of SpdyStream3
|
||||
nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
|
||||
nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
|
||||
|
||||
nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI; };
|
||||
void ConnectPushedStream(SpdyStream3 *consumer);
|
||||
|
||||
bool DeferCleanupOnSuccess() { return mDeferCleanupOnSuccess; }
|
||||
void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; }
|
||||
|
||||
bool IsOrphaned(TimeStamp now);
|
||||
|
||||
nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
|
||||
|
||||
// overload of SpdyStream3
|
||||
virtual bool HasSink() { return !!mConsumerStream; }
|
||||
|
||||
private:
|
||||
|
||||
SpdyStream3 *mConsumerStream; // paired request stream that consumes from
|
||||
// real spdy one.. null until a match is made.
|
||||
|
||||
nsCOMPtr<nsILoadGroupConnectionInfo> mLoadGroupCI;
|
||||
|
||||
SpdyPush3TransactionBuffer *mBufferedPush;
|
||||
mozilla::TimeStamp mLastRead;
|
||||
|
||||
nsCString mHashKey;
|
||||
nsresult mStatus;
|
||||
bool mPushCompleted; // server push FIN received
|
||||
bool mDeferCleanupOnSuccess;
|
||||
};
|
||||
|
||||
class SpdyPush3TransactionBuffer MOZ_FINAL : public nsAHttpTransaction
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSAHTTPTRANSACTION
|
||||
|
||||
SpdyPush3TransactionBuffer();
|
||||
virtual ~SpdyPush3TransactionBuffer();
|
||||
|
||||
nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
|
||||
void SetPushStream(SpdyPushedStream3 *stream) { mPushStream = stream; }
|
||||
|
||||
private:
|
||||
const static uint32_t kDefaultBufferSize = 4096;
|
||||
|
||||
nsresult mStatus;
|
||||
nsHttpRequestHead *mRequestHead;
|
||||
SpdyPushedStream3 *mPushStream;
|
||||
bool mIsDone;
|
||||
|
||||
nsAutoArrayPtr<char> mBufferedHTTP1;
|
||||
uint32_t mBufferedHTTP1Size;
|
||||
uint32_t mBufferedHTTP1Used;
|
||||
uint32_t mBufferedHTTP1Consumed;
|
||||
};
|
||||
|
||||
} // namespace mozilla::net
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_net_SpdyPush3_Internal_h
|
@ -4,15 +4,18 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "SpdySession3.h"
|
||||
#include "SpdyStream3.h"
|
||||
#include "nsHttpConnection.h"
|
||||
#include "nsHttpHandler.h"
|
||||
#include "prnetdb.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsHttp.h"
|
||||
#include "nsHttpHandler.h"
|
||||
#include "nsHttpConnection.h"
|
||||
#include "nsILoadGroup.h"
|
||||
#include "prprf.h"
|
||||
#include "prnetdb.h"
|
||||
#include "SpdyPush3.h"
|
||||
#include "SpdySession3.h"
|
||||
#include "SpdyStream3.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -38,7 +41,6 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction,
|
||||
: mSocketTransport(aSocketTransport),
|
||||
mSegmentReader(nullptr),
|
||||
mSegmentWriter(nullptr),
|
||||
mSendingChunkSize(ASpdySession::kSendingChunkSize),
|
||||
mNextStreamID(1),
|
||||
mConcurrentHighWater(0),
|
||||
mDownstreamState(BUFFERING_FRAME_HEADER),
|
||||
@ -65,8 +67,11 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction,
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
LOG3(("SpdySession3::SpdySession3 %p transaction 1 = %p",
|
||||
this, aHttpTransaction));
|
||||
static uint64_t sSerial;
|
||||
mSerial = ++sSerial;
|
||||
|
||||
LOG3(("SpdySession3::SpdySession3 %p transaction 1 = %p serial=0x%X\n",
|
||||
this, aHttpTransaction, mSerial));
|
||||
|
||||
mStreamIDHash.Init();
|
||||
mStreamTransactionHash.Init();
|
||||
@ -75,6 +80,7 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction,
|
||||
mOutputQueueBuffer = new char[mOutputQueueSize];
|
||||
zlibInit();
|
||||
|
||||
mPushAllowance = gHttpHandler->SpdyPushAllowance();
|
||||
mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
|
||||
GenerateSettings();
|
||||
|
||||
@ -116,9 +122,12 @@ SpdySession3::GoAwayEnumerator(nsAHttpTransaction *key,
|
||||
|
||||
// these streams were not processed by the server and can be restarted.
|
||||
// Do that after the enumerator completes to avoid the risk of
|
||||
// a restart event re-entrantly modifying this hash.
|
||||
if (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())
|
||||
// a restart event re-entrantly modifying this hash. Be sure not to restart
|
||||
// a pushed (even numbered) stream
|
||||
if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
|
||||
!stream->HasRegisteredID()) {
|
||||
self->mGoAwayStreamsToRestart.Push(stream);
|
||||
}
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
@ -187,7 +196,8 @@ static Control_FX sControlFunctions[] =
|
||||
SpdySession3::HandlePing,
|
||||
SpdySession3::HandleGoAway,
|
||||
SpdySession3::HandleHeaders,
|
||||
SpdySession3::HandleWindowUpdate
|
||||
SpdySession3::HandleWindowUpdate,
|
||||
SpdySession3::HandleCredential
|
||||
};
|
||||
|
||||
bool
|
||||
@ -219,12 +229,12 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now)
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
MOZ_ASSERT(mNextPingID & 1, "Ping Counter Not Odd");
|
||||
|
||||
if (!mPingThreshold)
|
||||
return;
|
||||
|
||||
LOG(("SpdySession3::ReadTimeoutTick %p delta since last read %ds\n",
|
||||
this, PR_IntervalToSeconds(now - mLastReadEpoch)));
|
||||
|
||||
if (!mPingThreshold)
|
||||
return;
|
||||
|
||||
if ((now - mLastReadEpoch) < mPingThreshold) {
|
||||
// recent activity means ping is not an issue
|
||||
if (mPingSentEpoch)
|
||||
@ -259,6 +269,35 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now)
|
||||
mNextPingID += 2;
|
||||
ResumeRecv(); // read the ping reply
|
||||
|
||||
// Check for orphaned push streams. This looks expensive, but generally the
|
||||
// list is empty.
|
||||
SpdyPushedStream3 *deleteMe;
|
||||
TimeStamp timestampNow;
|
||||
do {
|
||||
deleteMe = nullptr;
|
||||
|
||||
for (uint32_t index = mPushedStreams.Length();
|
||||
index > 0 ; --index) {
|
||||
SpdyPushedStream3 *pushedStream = mPushedStreams[index - 1];
|
||||
|
||||
if (timestampNow.IsNull())
|
||||
timestampNow = TimeStamp::Now(); // lazy initializer
|
||||
|
||||
// if spdy finished, but not connected, and its been like that for too long..
|
||||
// cleanup the stream..
|
||||
if (pushedStream->IsOrphaned(timestampNow))
|
||||
{
|
||||
LOG3(("SpdySession3 Timeout Pushed Stream %p 0x%X\n",
|
||||
this, pushedStream->StreamID()));
|
||||
deleteMe = pushedStream;
|
||||
break; // don't CleanupStream() while iterating this vector
|
||||
}
|
||||
}
|
||||
if (deleteMe)
|
||||
CleanupStream(deleteMe, NS_ERROR_ABORT, RST_CANCEL);
|
||||
|
||||
} while (deleteMe);
|
||||
|
||||
if (mNextPingID == 0xffffffff) {
|
||||
LOG(("SpdySession3::ReadTimeoutTick %p "
|
||||
"ping ids exhausted marking goaway\n", this));
|
||||
@ -267,35 +306,41 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now)
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SpdySession3::RegisterStreamID(SpdyStream3 *stream)
|
||||
SpdySession3::RegisterStreamID(SpdyStream3 *stream, uint32_t aNewID)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
LOG3(("SpdySession3::RegisterStreamID session=%p stream=%p id=0x%X "
|
||||
"concurrent=%d",this, stream, mNextStreamID, mConcurrent));
|
||||
|
||||
MOZ_ASSERT(mNextStreamID < 0xfffffff0,
|
||||
"should have stopped admitting streams");
|
||||
|
||||
uint32_t result = mNextStreamID;
|
||||
mNextStreamID += 2;
|
||||
MOZ_ASSERT(!(aNewID & 1),
|
||||
"0 for autoassign pull, otherwise explicit even push assignment");
|
||||
if (!aNewID) {
|
||||
// auto generate a new pull stream ID
|
||||
aNewID = mNextStreamID;
|
||||
MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
|
||||
mNextStreamID += 2;
|
||||
}
|
||||
|
||||
LOG3(("SpdySession3::RegisterStreamID session=%p stream=%p id=0x%X "
|
||||
"concurrent=%d",this, stream, aNewID, mConcurrent));
|
||||
|
||||
// We've used up plenty of ID's on this session. Start
|
||||
// moving to a new one before there is a crunch involving
|
||||
// server push streams or concurrent non-registered submits
|
||||
if (mNextStreamID >= kMaxStreamID)
|
||||
if (aNewID >= kMaxStreamID)
|
||||
mShouldGoAway = true;
|
||||
|
||||
// integrity check
|
||||
if (mStreamIDHash.Get(result)) {
|
||||
if (mStreamIDHash.Get(aNewID)) {
|
||||
LOG3((" New ID already present\n"));
|
||||
MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
|
||||
mShouldGoAway = true;
|
||||
return kDeadStreamID;
|
||||
}
|
||||
|
||||
mStreamIDHash.Put(result, stream);
|
||||
return result;
|
||||
mStreamIDHash.Put(aNewID, stream);
|
||||
return aNewID;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -312,12 +357,7 @@ SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction,
|
||||
}
|
||||
|
||||
aHttpTransaction->SetConnection(this);
|
||||
SpdyStream3 *stream = new SpdyStream3(aHttpTransaction,
|
||||
this,
|
||||
mSocketTransport,
|
||||
mSendingChunkSize,
|
||||
&mUpstreamZlib,
|
||||
aPriority);
|
||||
SpdyStream3 *stream = new SpdyStream3(aHttpTransaction, this, aPriority);
|
||||
|
||||
LOG3(("SpdySession3::AddStream session=%p stream=%p NextID=0x%X (tentative)",
|
||||
this, stream, mNextStreamID));
|
||||
@ -330,8 +370,7 @@ SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction,
|
||||
ActivateStream(stream);
|
||||
}
|
||||
else {
|
||||
LOG3(("SpdySession3::AddStream %p stream %p queued.",
|
||||
this, stream));
|
||||
LOG3(("SpdySession3::AddStream %p stream %p queued.", this, stream));
|
||||
mQueuedStreams.Push(stream);
|
||||
}
|
||||
|
||||
@ -342,8 +381,10 @@ void
|
||||
SpdySession3::ActivateStream(SpdyStream3 *stream)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
|
||||
"Do not activate pushed streams");
|
||||
|
||||
mConcurrent++;
|
||||
++mConcurrent;
|
||||
if (mConcurrent > mConcurrentHighWater)
|
||||
mConcurrentHighWater = mConcurrent;
|
||||
LOG3(("SpdySession3::AddStream %p activating stream %p Currently %d "
|
||||
@ -485,9 +526,9 @@ SpdySession3::ResetDownstreamState()
|
||||
if (mInputFrameDataLast && mInputFrameDataStream) {
|
||||
mInputFrameDataLast = false;
|
||||
if (!mInputFrameDataStream->RecvdFin()) {
|
||||
LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
|
||||
mInputFrameDataStream->SetRecvdFin(true);
|
||||
--mConcurrent;
|
||||
ProcessPending();
|
||||
DecrementConcurrent(mInputFrameDataStream);
|
||||
}
|
||||
}
|
||||
mInputFrameBufferUsed = 0;
|
||||
@ -528,6 +569,21 @@ SpdySession3::EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf,
|
||||
uint32_t preserve,
|
||||
uint32_t &objSize);
|
||||
|
||||
void
|
||||
SpdySession3::DecrementConcurrent(SpdyStream3 *aStream)
|
||||
{
|
||||
uint32_t id = aStream->StreamID();
|
||||
|
||||
if (id && !(id & 0x1))
|
||||
return; // pushed streams aren't counted in concurrent limit
|
||||
|
||||
MOZ_ASSERT(mConcurrent);
|
||||
--mConcurrent;
|
||||
LOG3(("DecrementConcurrent %p id=0x%X concurrent=%d\n",
|
||||
this, id, mConcurrent));
|
||||
ProcessPending();
|
||||
}
|
||||
|
||||
void
|
||||
SpdySession3::zlibInit()
|
||||
{
|
||||
@ -691,11 +747,13 @@ SpdySession3::GenerateSettings()
|
||||
// 2nd entry is bytes 20 to 27
|
||||
// 3rd entry is bytes 28 to 35
|
||||
|
||||
if (!gHttpHandler->AllowSpdyPush()) {
|
||||
// announcing that we accept 0 incoming streams is done to
|
||||
// disable server push until that is implemented.
|
||||
packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT;
|
||||
// The value portion of the setting pair is already initialized to 0
|
||||
numberOfEntries++;
|
||||
// disable server push
|
||||
packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT;
|
||||
// The value portion of the setting pair is already initialized to 0
|
||||
numberOfEntries++;
|
||||
}
|
||||
|
||||
nsRefPtr<nsHttpConnectionInfo> ci;
|
||||
uint32_t cwnd = 0;
|
||||
@ -711,8 +769,11 @@ SpdySession3::GenerateSettings()
|
||||
numberOfEntries++;
|
||||
}
|
||||
|
||||
// Advertise the Push RWIN and on each client SYN_STREAM pipeline
|
||||
// a window update with it in order to use larger initial windows with pulled
|
||||
// streams.
|
||||
packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW;
|
||||
uint32_t rwin = PR_htonl(kInitialRwin);
|
||||
uint32_t rwin = PR_htonl(mPushAllowance);
|
||||
memcpy(packet + 16 + 8 * numberOfEntries, &rwin, 4);
|
||||
numberOfEntries++;
|
||||
|
||||
@ -779,6 +840,7 @@ SpdySession3::VerifyStream(SpdyStream3 *aStream, uint32_t aOptionalID = 0)
|
||||
"optionalID=0x%X trans=%p test=%d\n",
|
||||
this, aStream, aStream->StreamID(),
|
||||
aOptionalID, aStream->Transaction(), test));
|
||||
|
||||
MOZ_ASSERT(false, "VerifyStream");
|
||||
return false;
|
||||
}
|
||||
@ -791,24 +853,39 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult,
|
||||
LOG3(("SpdySession3::CleanupStream %p %p 0x%X %X\n",
|
||||
this, aStream, aStream->StreamID(), aResult));
|
||||
|
||||
SpdyPushedStream3 *pushSource = nullptr;
|
||||
|
||||
if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) {
|
||||
LOG(("SpdySession3::CleanupStream 0x%X deferred\n", aStream->StreamID()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VerifyStream(aStream)) {
|
||||
LOG(("SpdySession3::CleanupStream failed to verify stream\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
pushSource = aStream->PushSource();
|
||||
|
||||
if (!aStream->RecvdFin() && aStream->StreamID()) {
|
||||
LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
|
||||
aResetCode));
|
||||
GenerateRstStream(aResetCode, aStream->StreamID());
|
||||
--mConcurrent;
|
||||
ProcessPending();
|
||||
DecrementConcurrent(aStream);
|
||||
}
|
||||
|
||||
CloseStream(aStream, aResult);
|
||||
|
||||
// Remove the stream from the ID hash table. (this one isn't short, which is
|
||||
// why it is hashed.)
|
||||
mStreamIDHash.Remove(aStream->StreamID());
|
||||
// Remove the stream from the ID hash table and, if an even id, the pushed
|
||||
// table too.
|
||||
uint32_t id = aStream->StreamID();
|
||||
if (id > 0) {
|
||||
mStreamIDHash.Remove(id);
|
||||
if (!(id & 1))
|
||||
mPushedStreams.RemoveElement(aStream);
|
||||
}
|
||||
|
||||
RemoveStreamFromQueues(aStream);
|
||||
|
||||
// removing from the stream transaction hash will
|
||||
// delete the SpdyStream3 and drop the reference to
|
||||
@ -817,6 +894,29 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult,
|
||||
|
||||
if (mShouldGoAway && !mStreamTransactionHash.Count())
|
||||
Close(NS_OK);
|
||||
|
||||
if (pushSource) {
|
||||
pushSource->SetDeferCleanupOnSuccess(false);
|
||||
CleanupStream(pushSource, aResult, aResetCode);
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveStreamFromQueue(SpdyStream3 *aStream, nsDeque &queue)
|
||||
{
|
||||
uint32_t size = queue.GetSize();
|
||||
for (uint32_t count = 0; count < size; ++count) {
|
||||
SpdyStream3 *stream = static_cast<SpdyStream3 *>(queue.PopFront());
|
||||
if (stream != aStream)
|
||||
queue.Push(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SpdySession3::RemoveStreamFromQueues(SpdyStream3 *aStream)
|
||||
{
|
||||
RemoveStreamFromQueue(aStream, mReadyForWrite);
|
||||
RemoveStreamFromQueue(aStream, mQueuedStreams);
|
||||
RemoveStreamFromQueue(aStream, mReadyForRead);
|
||||
}
|
||||
|
||||
void
|
||||
@ -833,23 +933,7 @@ SpdySession3::CloseStream(SpdyStream3 *aStream, nsresult aResult)
|
||||
mInputFrameDataStream = nullptr;
|
||||
}
|
||||
|
||||
// check the streams blocked on write, this is linear but the list
|
||||
// should be pretty short.
|
||||
uint32_t size = mReadyForWrite.GetSize();
|
||||
for (uint32_t count = 0; count < size; ++count) {
|
||||
SpdyStream3 *stream = static_cast<SpdyStream3 *>(mReadyForWrite.PopFront());
|
||||
if (stream != aStream)
|
||||
mReadyForWrite.Push(stream);
|
||||
}
|
||||
|
||||
// Check the streams queued for activation. Because we normally accept a high
|
||||
// level of parallelization this should also be short.
|
||||
size = mQueuedStreams.GetSize();
|
||||
for (uint32_t count = 0; count < size; ++count) {
|
||||
SpdyStream3 *stream = static_cast<SpdyStream3 *>(mQueuedStreams.PopFront());
|
||||
if (stream != aStream)
|
||||
mQueuedStreams.Push(stream);
|
||||
}
|
||||
RemoveStreamFromQueues(aStream);
|
||||
|
||||
// Send the stream the close() indication
|
||||
aStream->Close(aResult);
|
||||
@ -870,9 +954,10 @@ SpdySession3::HandleSynStream(SpdySession3 *self)
|
||||
PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
|
||||
uint32_t associatedID =
|
||||
PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]);
|
||||
uint8_t flags = reinterpret_cast<uint8_t *>(self->mInputFrameBuffer.get())[4];
|
||||
|
||||
LOG3(("SpdySession3::HandleSynStream %p recv SYN_STREAM (push) "
|
||||
"for ID 0x%X associated with 0x%X.",
|
||||
"for ID 0x%X associated with 0x%X.\n",
|
||||
self, streamID, associatedID));
|
||||
|
||||
if (streamID & 0x01) { // test for odd stream ID
|
||||
@ -881,6 +966,12 @@ SpdySession3::HandleSynStream(SpdySession3 *self)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// confirm associated-to
|
||||
nsresult rv = self->SetInputFrameDataStream(associatedID);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
SpdyStream3 *associatedStream = self->mInputFrameDataStream;
|
||||
|
||||
++(self->mServerPushedResources);
|
||||
|
||||
// Anytime we start using the high bit of stream ID (either client or server)
|
||||
@ -888,17 +979,127 @@ SpdySession3::HandleSynStream(SpdySession3 *self)
|
||||
if (streamID >= kMaxStreamID)
|
||||
self->mShouldGoAway = true;
|
||||
|
||||
// Need to decompress the headers even though we aren't using them yet in
|
||||
// order to keep the compression context consistent for other syn_reply frames
|
||||
nsresult rv =
|
||||
self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10);
|
||||
bool resetStream = true;
|
||||
SpdyPushCache3 *cache = nullptr;
|
||||
|
||||
if (!(flags & kFlag_Data_UNI)) {
|
||||
// pushed streams require UNIDIRECTIONAL flag
|
||||
LOG3(("SpdySession3::HandleSynStream %p ID %0x%X associated ID 0x%X failed.\n",
|
||||
self, streamID, associatedID));
|
||||
self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID);
|
||||
|
||||
} else if (!associatedID) {
|
||||
// associated stream 0 will never find a match, but the spec requires a
|
||||
// PROTOCOL_ERROR in this specific case
|
||||
LOG3(("SpdySession3::HandleSynStream %p associated ID of 0 failed.\n", self));
|
||||
self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID);
|
||||
|
||||
} else if (!gHttpHandler->AllowSpdyPush()) {
|
||||
// MAX_CONCURRENT_STREAMS of 0 in settings should have disabled push,
|
||||
// but some servers are buggy about that.. or the config could have
|
||||
// been updated after the settings frame was sent. In both cases just
|
||||
// reject the pushed stream as refused
|
||||
LOG3(("SpdySession3::HandleSynStream Push Recevied when Disabled\n"));
|
||||
self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
|
||||
|
||||
} else if (!associatedStream) {
|
||||
LOG3(("SpdySession3::HandleSynStream %p lookup associated ID failed.\n", self));
|
||||
self->GenerateRstStream(RST_INVALID_STREAM, streamID);
|
||||
|
||||
} else {
|
||||
nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo();
|
||||
if (loadGroupCI) {
|
||||
loadGroupCI->GetSpdyPushCache3(&cache);
|
||||
if (!cache) {
|
||||
cache = new SpdyPushCache3();
|
||||
if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache3(cache))) {
|
||||
delete cache;
|
||||
cache = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cache) {
|
||||
// this is unexpected, but we can handle it just be refusing the push
|
||||
LOG3(("SpdySession3::HandleSynStream Push Recevied without loadgroup cache\n"));
|
||||
self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
|
||||
}
|
||||
else {
|
||||
resetStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (resetStream) {
|
||||
// Need to decompress the headers even though we aren't using them yet in
|
||||
// order to keep the compression context consistent for other syn_reply frames
|
||||
rv = self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("SpdySession3::HandleSynStream uncompress failed\n"));
|
||||
return rv;
|
||||
}
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Create the buffering transaction and push stream
|
||||
nsRefPtr<SpdyPush3TransactionBuffer> transactionBuffer =
|
||||
new SpdyPush3TransactionBuffer();
|
||||
transactionBuffer->SetConnection(self);
|
||||
SpdyPushedStream3 *pushedStream =
|
||||
new SpdyPushedStream3(transactionBuffer, self,
|
||||
associatedStream, streamID);
|
||||
|
||||
// ownership of the pushed stream is by the transaction hash, just as it
|
||||
// is for a client initiated stream. Errors that aren't fatal to the
|
||||
// whole session must call cleanupStream() after this point in order
|
||||
// to remove the stream from that hash.
|
||||
self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
|
||||
self->mPushedStreams.AppendElement(pushedStream);
|
||||
|
||||
// The pushed stream is unidirectional so it is fully open immediately
|
||||
pushedStream->SetFullyOpen();
|
||||
|
||||
// Uncompress the response headers into a stream specific buffer, leaving them
|
||||
// in spdy format for the time being.
|
||||
rv = pushedStream->Uncompress(&self->mDownstreamZlib,
|
||||
self->mInputFrameBuffer + 18,
|
||||
self->mInputFrameDataSize - 10);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("SpdySession3::HandleSynStream uncompress failed\n"));
|
||||
return rv;
|
||||
}
|
||||
|
||||
// todo populate cache. For now, just reject server push p3
|
||||
self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
|
||||
if (self->RegisterStreamID(pushedStream, streamID) == kDeadStreamID) {
|
||||
LOG(("SpdySession3::HandleSynStream registerstreamid failed\n"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Fake the request side of the pushed HTTP transaction. Sets up hash
|
||||
// key and origin
|
||||
uint32_t notUsed;
|
||||
pushedStream->ReadSegments(nullptr, 1, ¬Used);
|
||||
|
||||
nsAutoCString key;
|
||||
if (!pushedStream->GetHashKey(key)) {
|
||||
LOG(("SpdySession3::HandleSynStream one of :host :scheme :path missing from push\n"));
|
||||
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM);
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!associatedStream->Origin().Equals(pushedStream->Origin())) {
|
||||
LOG(("SpdySession3::HandleSynStream pushed stream mismatched origin\n"));
|
||||
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM);
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!cache->RegisterPushedStream(key, pushedStream)) {
|
||||
LOG(("SpdySession3::HandleSynStream registerPushedStream Failed\n"));
|
||||
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM);
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1266,8 +1467,8 @@ SpdySession3::HandleGoAway(SpdySession3 *self)
|
||||
self->mCleanShutdown = true;
|
||||
|
||||
// Find streams greater than the last-good ID and mark them for deletion
|
||||
// in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. They can
|
||||
// be restarted.
|
||||
// in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The
|
||||
// underlying transaction can be restarted.
|
||||
self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self);
|
||||
|
||||
// Process the streams marked for deletion and restart.
|
||||
@ -1298,7 +1499,6 @@ SpdySession3::HandleGoAway(SpdySession3 *self)
|
||||
PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]),
|
||||
self->mStreamTransactionHash.Count()));
|
||||
|
||||
self->ResumeRecv();
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1330,7 +1530,7 @@ SpdySession3::HandleHeaders(SpdySession3 *self)
|
||||
|
||||
if (NS_FAILED(self->UncompressAndDiscard(12,
|
||||
self->mInputFrameDataSize - 4))) {
|
||||
LOG(("SpdySession3::HandleSynReply uncompress failed\n"));
|
||||
LOG(("SpdySession3::HandleHeaders uncompress failed\n"));
|
||||
// this is fatal to the session
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -1429,7 +1629,19 @@ SpdySession3::HandleWindowUpdate(SpdySession3 *self)
|
||||
}
|
||||
|
||||
self->ResetDownstreamState();
|
||||
self->ResumeRecv();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SpdySession3::HandleCredential(SpdySession3 *self)
|
||||
{
|
||||
MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_CREDENTIAL);
|
||||
|
||||
// These aren't used yet. Just ignore the frame.
|
||||
|
||||
LOG3(("SpdySession3::HandleCredential %p NOP.", self));
|
||||
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1454,8 +1666,9 @@ SpdySession3::OnTransportStatus(nsITransport* aTransport,
|
||||
case NS_NET_STATUS_CONNECTED_TO:
|
||||
{
|
||||
SpdyStream3 *target = mStreamIDHash.Get(1);
|
||||
if (target)
|
||||
target->Transaction()->OnTransportStatus(aTransport, aStatus, aProgress);
|
||||
nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
|
||||
if (transaction)
|
||||
transaction->OnTransportStatus(aTransport, aStatus, aProgress);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1596,7 +1809,7 @@ SpdySession3::ReadSegments(nsAHttpSegmentReader *reader,
|
||||
// we call writer->OnWriteSegment via NetworkRead() to get a spdy header..
|
||||
// and decide if it is data or control.. if it is control, just deal with it.
|
||||
// if it is data, identify the spdy stream
|
||||
// call stream->WriteSegemnts which can call this::OnWriteSegment to get the
|
||||
// call stream->WriteSegments which can call this::OnWriteSegment to get the
|
||||
// data. It always gets full frames if they are part of the stream
|
||||
|
||||
nsresult
|
||||
@ -1614,6 +1827,42 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
|
||||
SetWriteCallbacks();
|
||||
|
||||
// If there are http transactions attached to a push stream with filled buffers
|
||||
// trigger that data pump here. This only reads from buffers (not the network)
|
||||
// so mDownstreamState doesn't matter.
|
||||
SpdyStream3 *pushConnectedStream =
|
||||
static_cast<SpdyStream3 *>(mReadyForRead.PopFront());
|
||||
if (pushConnectedStream) {
|
||||
LOG3(("SpdySession3::WriteSegments %p processing pushed stream 0x%X\n",
|
||||
this, pushConnectedStream->StreamID()));
|
||||
mSegmentWriter = writer;
|
||||
rv = pushConnectedStream->WriteSegments(this, count, countWritten);
|
||||
mSegmentWriter = nullptr;
|
||||
|
||||
// The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
|
||||
// so we need this check to determine the truth.
|
||||
if (NS_SUCCEEDED(rv) && !*countWritten &&
|
||||
pushConnectedStream->PushSource() &&
|
||||
pushConnectedStream->PushSource()->GetPushComplete()) {
|
||||
rv = NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
if (rv == NS_BASE_STREAM_CLOSED) {
|
||||
CleanupStream(pushConnectedStream, NS_OK, RST_CANCEL);
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
// if we return OK to nsHttpConnection it will use mSocketInCondition
|
||||
// to determine whether to schedule more reads, incorrectly
|
||||
// assuming that nsHttpConnection::OnSocketWrite() was called.
|
||||
if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
rv = NS_BASE_STREAM_WOULD_BLOCK;
|
||||
ResumeRecv();
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We buffer all control frames and act on them in this layer.
|
||||
// We buffer the first 8 bytes of data frames (the header) but
|
||||
// the actual data is passed through unprocessed.
|
||||
@ -1634,7 +1883,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
this, rv));
|
||||
// maybe just blocked reading from network
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
||||
ResumeRecv();
|
||||
rv = NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -1809,6 +2058,13 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
mNeedsCleanup = nullptr;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG3(("SpdySession3 %p data frame read failure %x\n", this, rv));
|
||||
// maybe just blocked reading from network
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -1828,7 +2084,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
LOG3(("SpdySession3 %p discard frame read failure %x\n", this, rv));
|
||||
// maybe just blocked reading from network
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
||||
ResumeRecv();
|
||||
rv = NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -1841,9 +2097,9 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
return rv;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mDownstreamState == BUFFERING_CONTROL_FRAME);
|
||||
if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
|
||||
// this cannot happen
|
||||
MOZ_ASSERT(false, "Not in Bufering Control Frame State");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
@ -1858,7 +2114,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
this, rv));
|
||||
// maybe just blocked reading from network
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
||||
ResumeRecv();
|
||||
rv = NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -1899,27 +2155,34 @@ SpdySession3::UpdateLocalRwin(SpdyStream3 *stream,
|
||||
if (!stream || stream->RecvdFin())
|
||||
return;
|
||||
|
||||
LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X %d\n",
|
||||
this, stream->StreamID(), bytes));
|
||||
stream->DecrementLocalWindow(bytes);
|
||||
|
||||
// Don't necessarily ack every data packet. Only do it
|
||||
// after a significant amount of data.
|
||||
uint64_t unacked = stream->LocalUnAcked();
|
||||
int64_t localWindow = stream->LocalWindow();
|
||||
|
||||
if (unacked < kMinimumToAck) {
|
||||
// Sanity check to make sure this won't let the window drop below 1MB
|
||||
PR_STATIC_ASSERT(kMinimumToAck < kInitialRwin);
|
||||
PR_STATIC_ASSERT((kInitialRwin - kMinimumToAck) > 1024 * 1024);
|
||||
LOG3(("SpdySession3::UpdateLocalRwin this=%p id=0x%X newbytes=%u "
|
||||
"unacked=%llu localWindow=%lld\n",
|
||||
this, stream->StreamID(), bytes, unacked, localWindow));
|
||||
|
||||
if (!unacked)
|
||||
return;
|
||||
|
||||
if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
|
||||
return;
|
||||
|
||||
if (!stream->HasSink()) {
|
||||
LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X Pushed Stream Has No Sink\n",
|
||||
this, stream->StreamID()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate window updates directly out of spdysession instead of the stream
|
||||
// in order to avoid queue delays in getting the ACK out.
|
||||
uint32_t toack = unacked & 0x7fffffff;
|
||||
// in order to avoid queue delays in getting the 'ACK' out.
|
||||
uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
|
||||
|
||||
LOG3(("SpdySession3::UpdateLocalRwin Ack %p 0x%X %d\n",
|
||||
LOG3(("SpdySession3::UpdateLocalRwin Ack this=%p id=0x%X acksize=%d\n",
|
||||
this, stream->StreamID(), toack));
|
||||
stream->IncrementLocalWindow(toack);
|
||||
|
||||
@ -2186,6 +2449,13 @@ SpdySession3::SetNeedsCleanup()
|
||||
ResetDownstreamState();
|
||||
}
|
||||
|
||||
void
|
||||
SpdySession3::ConnectPushedStream(SpdyStream3 *stream)
|
||||
{
|
||||
mReadyForRead.Push(stream);
|
||||
ForceRecv();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Modified methods of nsAHttpConnection
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -10,14 +10,13 @@
|
||||
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
|
||||
|
||||
#include "ASpdySession.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsDeque.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "zlib.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
class nsHttpConnection;
|
||||
class nsISocketTransport;
|
||||
|
||||
namespace mozilla { namespace net {
|
||||
@ -49,7 +48,8 @@ public:
|
||||
// Idle time represents time since "goodput".. e.g. a data or header frame
|
||||
PRIntervalTime IdleTime();
|
||||
|
||||
uint32_t RegisterStreamID(SpdyStream3 *);
|
||||
// Registering with a newID of 0 means pick the next available odd ID
|
||||
uint32_t RegisterStreamID(SpdyStream3 *, uint32_t aNewID = 0);
|
||||
|
||||
const static uint8_t kVersion = 3;
|
||||
|
||||
@ -134,11 +134,9 @@ public:
|
||||
// 31 bit stream ID.
|
||||
const static uint32_t kDeadStreamID = 0xffffdead;
|
||||
|
||||
// until we have an API that can push back on receiving data (right now
|
||||
// WriteSegments is obligated to accept data and buffer) there is no
|
||||
// reason to throttle with the rwin other than in server push
|
||||
// scenarios.
|
||||
const static uint32_t kInitialRwin = 256 * 1024 * 1024;
|
||||
// below the emergency threshold of local window we ack every received
|
||||
// byte. Above that we coalesce bytes into the MinimumToAck size.
|
||||
const static int32_t kEmergencyWindowThreshold = 1024 * 1024;
|
||||
const static uint32_t kMinimumToAck = 64 * 1024;
|
||||
|
||||
// The default peer rwin is 64KB unless updated by a settings frame
|
||||
@ -153,6 +151,7 @@ public:
|
||||
static nsresult HandleGoAway(SpdySession3 *);
|
||||
static nsresult HandleHeaders(SpdySession3 *);
|
||||
static nsresult HandleWindowUpdate(SpdySession3 *);
|
||||
static nsresult HandleCredential(SpdySession3 *);
|
||||
|
||||
template<typename T>
|
||||
static void EnsureBuffer(nsAutoArrayPtr<T> &,
|
||||
@ -173,8 +172,18 @@ public:
|
||||
|
||||
uint32_t GetServerInitialWindow() { return mServerInitialWindow; }
|
||||
|
||||
void ConnectPushedStream(SpdyStream3 *stream);
|
||||
|
||||
uint64_t Serial() { return mSerial; }
|
||||
|
||||
void PrintDiagnostics (nsCString &log);
|
||||
|
||||
// Streams need access to these
|
||||
uint32_t SendingChunkSize() { return mSendingChunkSize; }
|
||||
uint32_t PushAllowance() { return mPushAllowance; }
|
||||
z_stream *UpstreamZlib() { return &mUpstreamZlib; }
|
||||
nsISocketTransport *SocketTransport() { return mSocketTransport; }
|
||||
|
||||
private:
|
||||
|
||||
enum stateType {
|
||||
@ -191,6 +200,7 @@ private:
|
||||
void ChangeDownstreamState(enum stateType);
|
||||
void ResetDownstreamState();
|
||||
nsresult UncompressAndDiscard(uint32_t, uint32_t);
|
||||
void DecrementConcurrent(SpdyStream3 *);
|
||||
void zlibInit();
|
||||
void GeneratePing(uint32_t);
|
||||
void GenerateRstStream(uint32_t, uint32_t);
|
||||
@ -198,6 +208,7 @@ private:
|
||||
void CleanupStream(SpdyStream3 *, nsresult, rstReason);
|
||||
void CloseStream(SpdyStream3 *, nsresult);
|
||||
void GenerateSettings();
|
||||
void RemoveStreamFromQueues(SpdyStream3 *);
|
||||
|
||||
void SetWriteCallbacks();
|
||||
void FlushOutputQueue();
|
||||
@ -228,7 +239,7 @@ private:
|
||||
nsAutoPtr<SpdyStream3> &,
|
||||
void *);
|
||||
|
||||
// This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken
|
||||
// This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken
|
||||
// from the first transaction on this session. That object contains the
|
||||
// pointer to the real network-level nsHttpConnection object.
|
||||
nsRefPtr<nsAHttpConnection> mConnection;
|
||||
@ -245,20 +256,25 @@ private:
|
||||
uint32_t mSendingChunkSize; /* the transmission chunk size */
|
||||
uint32_t mNextStreamID; /* 24 bits */
|
||||
uint32_t mConcurrentHighWater; /* max parallelism on session */
|
||||
uint32_t mPushAllowance; /* rwin for unmatched pushes */
|
||||
|
||||
stateType mDownstreamState; /* in frame, between frames, etc.. */
|
||||
|
||||
// Maintain 4 indexes - one by stream ID, one by transaction ptr,
|
||||
// one list of streams ready to write, one list of streams that are queued
|
||||
// due to max parallelism settings. The objects
|
||||
// are not ref counted - they get destroyed
|
||||
// Maintain 2 indexes - one by stream ID, one by transaction pointer.
|
||||
// There are also several lists of streams: ready to write, queued due to
|
||||
// max parallelism, streams that need to force a read for push, and the full
|
||||
// set of pushed streams.
|
||||
// The objects are not ref counted - they get destroyed
|
||||
// by the nsClassHashtable implementation when they are removed from
|
||||
// the transaction hash.
|
||||
nsDataHashtable<nsUint32HashKey, SpdyStream3 *> mStreamIDHash;
|
||||
nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
|
||||
SpdyStream3> mStreamTransactionHash;
|
||||
|
||||
nsDeque mReadyForWrite;
|
||||
nsDeque mQueuedStreams;
|
||||
nsDeque mReadyForRead;
|
||||
nsTArray<SpdyPushedStream3 *> mPushedStreams;
|
||||
|
||||
// Compression contexts for header transport using deflate.
|
||||
// SPDY compresses only HTTP headers and does not reset zlib in between
|
||||
@ -360,6 +376,11 @@ private:
|
||||
|
||||
// used as a temporary buffer while enumerating the stream hash during GoAway
|
||||
nsDeque mGoAwayStreamsToRestart;
|
||||
|
||||
// Each session gets a unique serial number because the push cache is correlated
|
||||
// by the load group and the serial number can be used as part of the cache key
|
||||
// to make sure streams aren't shared across sessions.
|
||||
uint64_t mSerial;
|
||||
};
|
||||
|
||||
}} // namespace mozilla::net
|
||||
|
@ -4,16 +4,18 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "SpdySession3.h"
|
||||
#include "SpdyStream3.h"
|
||||
#include "nsAlgorithm.h"
|
||||
#include "prnetdb.h"
|
||||
#include "nsHttpRequestHead.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsAlgorithm.h"
|
||||
#include "nsHttp.h"
|
||||
#include "nsHttpHandler.h"
|
||||
#include "nsHttpRequestHead.h"
|
||||
#include "nsISocketTransport.h"
|
||||
#include "nsISupportsPriority.h"
|
||||
#include "nsHttpHandler.h"
|
||||
#include "prnetdb.h"
|
||||
#include "SpdyPush3.h"
|
||||
#include "SpdySession3.h"
|
||||
#include "SpdyStream3.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -26,21 +28,18 @@ namespace net {
|
||||
|
||||
SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction,
|
||||
SpdySession3 *spdySession,
|
||||
nsISocketTransport *socketTransport,
|
||||
uint32_t chunkSize,
|
||||
z_stream *compressionContext,
|
||||
int32_t priority)
|
||||
: mUpstreamState(GENERATING_SYN_STREAM),
|
||||
mTransaction(httpTransaction),
|
||||
: mStreamID(0),
|
||||
mSession(spdySession),
|
||||
mSocketTransport(socketTransport),
|
||||
mUpstreamState(GENERATING_SYN_STREAM),
|
||||
mSynFrameComplete(0),
|
||||
mSentFinOnData(0),
|
||||
mTransaction(httpTransaction),
|
||||
mSocketTransport(spdySession->SocketTransport()),
|
||||
mSegmentReader(nullptr),
|
||||
mSegmentWriter(nullptr),
|
||||
mStreamID(0),
|
||||
mChunkSize(chunkSize),
|
||||
mSynFrameComplete(0),
|
||||
mChunkSize(spdySession->SendingChunkSize()),
|
||||
mRequestBlockedOnRead(0),
|
||||
mSentFinOnData(0),
|
||||
mRecvdFin(0),
|
||||
mFullyOpen(0),
|
||||
mSentWaitingFor(0),
|
||||
@ -49,23 +48,25 @@ SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction,
|
||||
mTxInlineFrameSize(SpdySession3::kDefaultBufferSize),
|
||||
mTxInlineFrameUsed(0),
|
||||
mTxStreamFrameSize(0),
|
||||
mZlib(compressionContext),
|
||||
mZlib(spdySession->UpstreamZlib()),
|
||||
mDecompressBufferSize(SpdySession3::kDefaultBufferSize),
|
||||
mDecompressBufferUsed(0),
|
||||
mDecompressedBytes(0),
|
||||
mRequestBodyLenRemaining(0),
|
||||
mPriority(priority),
|
||||
mLocalWindow(SpdySession3::kInitialRwin),
|
||||
mLocalUnacked(0),
|
||||
mBlockedOnRwin(false),
|
||||
mTotalSent(0),
|
||||
mTotalRead(0)
|
||||
mTotalRead(0),
|
||||
mPushSource(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
LOG3(("SpdyStream3::SpdyStream3 %p", this));
|
||||
|
||||
mRemoteWindow = spdySession->GetServerInitialWindow();
|
||||
mLocalWindow = spdySession->PushAllowance();
|
||||
|
||||
mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
|
||||
mDecompressBuffer = new char[mDecompressBufferSize];
|
||||
}
|
||||
@ -186,15 +187,16 @@ SpdyStream3::WriteSegments(nsAHttpSegmentWriter *writer,
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
{
|
||||
LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x",
|
||||
this, count, mUpstreamState));
|
||||
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
|
||||
|
||||
LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x",
|
||||
this, count, mUpstreamState));
|
||||
|
||||
mSegmentWriter = writer;
|
||||
nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
|
||||
nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
|
||||
mSegmentWriter = nullptr;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -210,6 +212,26 @@ SpdyStream3::hdrHashEnumerate(const nsACString &key,
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyStream3::CreatePushHashKey(const nsCString &scheme,
|
||||
const nsCString &hostHeader,
|
||||
uint64_t serial,
|
||||
const nsCSubstring &pathInfo,
|
||||
nsCString &outOrigin,
|
||||
nsCString &outKey)
|
||||
{
|
||||
outOrigin = scheme;
|
||||
outOrigin.Append(NS_LITERAL_CSTRING("://"));
|
||||
outOrigin.Append(hostHeader);
|
||||
|
||||
outKey = outOrigin;
|
||||
outKey.Append(NS_LITERAL_CSTRING("/[spdy3."));
|
||||
outKey.AppendInt(serial);
|
||||
outKey.Append(NS_LITERAL_CSTRING("]"));
|
||||
outKey.Append(pathInfo);
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
||||
uint32_t avail,
|
||||
@ -247,6 +269,44 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
||||
*countUsed = avail - (oldLen - endHeader) + 4;
|
||||
mSynFrameComplete = 1;
|
||||
|
||||
nsCString hostHeader;
|
||||
nsCString hashkey;
|
||||
mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
|
||||
|
||||
CreatePushHashKey(NS_LITERAL_CSTRING("https"),
|
||||
hostHeader, mSession->Serial(),
|
||||
mTransaction->RequestHead()->RequestURI(),
|
||||
mOrigin, hashkey);
|
||||
|
||||
// check the push cache for GET
|
||||
if (mTransaction->RequestHead()->Method() == nsHttp::Get) {
|
||||
// from :scheme, :host, :path
|
||||
nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
|
||||
SpdyPushCache3 *cache = nullptr;
|
||||
if (loadGroupCI)
|
||||
loadGroupCI->GetSpdyPushCache3(&cache);
|
||||
|
||||
SpdyPushedStream3 *pushedStream = nullptr;
|
||||
// we remove the pushedstream from the push cache so that
|
||||
// it will not be used for another GET. This does not destroy the
|
||||
// stream itself - that is done when the transactionhash is done with it.
|
||||
if (cache)
|
||||
pushedStream = cache->RemovePushedStream(hashkey);
|
||||
|
||||
if (pushedStream) {
|
||||
LOG3(("Pushed Stream Match located id=0x%X key=%s\n",
|
||||
pushedStream->StreamID(), hashkey.get()));
|
||||
pushedStream->SetConsumerStream(this);
|
||||
mPushSource = pushedStream;
|
||||
mSentFinOnData = 1;
|
||||
|
||||
// There is probably pushed data buffered so trigger a read manually
|
||||
// as we can't rely on future network events to do it
|
||||
mSession->ConnectPushedStream(this);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// It is now OK to assign a streamID that we are assured will
|
||||
// be monotonically increasing amongst syn-streams on this
|
||||
// session
|
||||
@ -309,11 +369,6 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
||||
// The client cert "slot". Right now we don't send client certs
|
||||
mTxInlineFrame[17] = 0;
|
||||
|
||||
const char *methodHeader = mTransaction->RequestHead()->Method().get();
|
||||
|
||||
nsCString hostHeader;
|
||||
mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
|
||||
|
||||
nsCString versionHeader;
|
||||
if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
|
||||
versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
|
||||
@ -388,6 +443,8 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
||||
// contain auth. The http transaction already logs the sanitized request
|
||||
// headers at this same level so it is not necessary to do so here.
|
||||
|
||||
const char *methodHeader = mTransaction->RequestHead()->Method().get();
|
||||
|
||||
// The header block length
|
||||
uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */
|
||||
CompressToFrame(count);
|
||||
@ -452,6 +509,75 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SpdyStream3::AdjustInitialWindow()
|
||||
{
|
||||
MOZ_ASSERT(mSession->PushAllowance() <= ASpdySession::kInitialRwin);
|
||||
|
||||
// The session initial_window is sized for serverpushed streams. When we
|
||||
// generate a client pulled stream we want to adjust the initial window
|
||||
// to a huge value in a pipeline with that SYN_STREAM.
|
||||
|
||||
// >0 even numbered IDs are pushed streams.
|
||||
// odd numbered IDs are pulled streams.
|
||||
// 0 is the sink for a pushed stream.
|
||||
SpdyStream3 *stream = this;
|
||||
if (!mStreamID) {
|
||||
MOZ_ASSERT(mPushSource);
|
||||
if (!mPushSource)
|
||||
return;
|
||||
stream = mPushSource;
|
||||
MOZ_ASSERT(stream->mStreamID);
|
||||
MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
|
||||
|
||||
// If the pushed stream has sent a FIN, there is no reason to update
|
||||
// the window
|
||||
if (stream->RecvdFin())
|
||||
return;
|
||||
}
|
||||
|
||||
// For server pushes we also want to include in the ack any data that has been
|
||||
// buffered but unacknowledged.
|
||||
|
||||
// mLocalUnacked is technically 64 bits, but because it can never grow larger than
|
||||
// our window size (which is closer to 29bits max) we know it fits comfortably in 32.
|
||||
// However we don't really enforce that, and track it as a 64 so that broken senders
|
||||
// can still interoperate. That means we have to be careful with this calculation.
|
||||
uint64_t toack64 = (ASpdySession::kInitialRwin - mSession->PushAllowance()) +
|
||||
stream->mLocalUnacked;
|
||||
stream->mLocalUnacked = 0;
|
||||
if (toack64 > 0x7fffffff) {
|
||||
stream->mLocalUnacked = toack64 - 0x7fffffff;
|
||||
toack64 = 0x7fffffff;
|
||||
}
|
||||
uint32_t toack = static_cast<uint32_t>(toack64);
|
||||
if (!toack)
|
||||
return;
|
||||
toack = PR_htonl(toack);
|
||||
|
||||
SpdySession3::EnsureBuffer(mTxInlineFrame,
|
||||
mTxInlineFrameUsed + 16,
|
||||
mTxInlineFrameUsed,
|
||||
mTxInlineFrameSize);
|
||||
|
||||
unsigned char *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
|
||||
mTxInlineFrameUsed += 16;
|
||||
|
||||
memset(packet, 0, 8);
|
||||
packet[0] = SpdySession3::kFlag_Control;
|
||||
packet[1] = SpdySession3::kVersion;
|
||||
packet[3] = SpdySession3::CONTROL_TYPE_WINDOW_UPDATE;
|
||||
packet[7] = 8; // 8 data bytes after 8 byte header
|
||||
|
||||
uint32_t id = PR_htonl(stream->mStreamID);
|
||||
memcpy(packet + 8, &id, 4);
|
||||
memcpy(packet + 12, &toack, 4);
|
||||
|
||||
stream->mLocalWindow += PR_ntohl(toack);
|
||||
LOG3(("AdjustInitialwindow %p 0x%X %u\n",
|
||||
this, stream->mStreamID, PR_ntohl(toack)));
|
||||
}
|
||||
|
||||
void
|
||||
SpdyStream3::UpdateTransportReadEvents(uint32_t count)
|
||||
{
|
||||
@ -510,6 +636,16 @@ SpdyStream3::TransmitFrame(const char *buf,
|
||||
// flush internal buffers that were previously blocked on writing. You can
|
||||
// of course feed new data to it as well.
|
||||
|
||||
LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d",
|
||||
this, mTxInlineFrameUsed, mTxStreamFrameSize));
|
||||
if (countUsed)
|
||||
*countUsed = 0;
|
||||
|
||||
if (!mTxInlineFrameUsed) {
|
||||
MOZ_ASSERT(!buf);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
|
||||
MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
|
||||
MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
|
||||
@ -518,11 +654,6 @@ SpdyStream3::TransmitFrame(const char *buf,
|
||||
uint32_t transmittedCount;
|
||||
nsresult rv;
|
||||
|
||||
LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d",
|
||||
this, mTxInlineFrameUsed, mTxStreamFrameSize));
|
||||
if (countUsed)
|
||||
*countUsed = 0;
|
||||
|
||||
// In the (relatively common) event that we have a small amount of data
|
||||
// split between the inlineframe and the streamframe, then move the stream
|
||||
// data into the inlineframe via copy in order to coalesce into one write.
|
||||
@ -862,6 +993,7 @@ SpdyStream3::zlib_destructor(void *opaque, void *addr)
|
||||
moz_free(addr);
|
||||
}
|
||||
|
||||
// This can be called N times.. 1 for syn_reply and 0->N for headers
|
||||
nsresult
|
||||
SpdyStream3::Uncompress(z_stream *context,
|
||||
char *blockStart,
|
||||
@ -915,6 +1047,7 @@ SpdyStream3::Uncompress(z_stream *context,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// mDecompressBuffer contains 0 to N uncompressed Name/Value Header blocks
|
||||
nsresult
|
||||
SpdyStream3::FindHeader(nsCString name,
|
||||
nsDependentCSubstring &value)
|
||||
@ -925,30 +1058,40 @@ SpdyStream3::FindHeader(nsCString name,
|
||||
(mDecompressBuffer.get()) + mDecompressBufferUsed;
|
||||
if (lastHeaderByte < nvpair)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
uint32_t numPairs =
|
||||
PR_ntohl(reinterpret_cast<uint32_t *>(mDecompressBuffer.get())[0]);
|
||||
for (uint32_t index = 0; index < numPairs; ++index) {
|
||||
if (lastHeaderByte < nvpair + 4)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
||||
(nvpair[2] << 8) + nvpair[3];
|
||||
if (lastHeaderByte < nvpair + 4 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
nsDependentCSubstring nameString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
||||
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
||||
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
if (nameString.Equals(name)) {
|
||||
value.Assign(((char *)nvpair) + 8 + nameLen, valueLen);
|
||||
return NS_OK;
|
||||
|
||||
do {
|
||||
uint32_t numPairs = PR_ntohl(reinterpret_cast<const uint32_t *>(nvpair)[-1]);
|
||||
|
||||
for (uint32_t index = 0; index < numPairs; ++index) {
|
||||
if (lastHeaderByte < nvpair + 4)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
||||
(nvpair[2] << 8) + nvpair[3];
|
||||
if (lastHeaderByte < nvpair + 4 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
nsDependentCSubstring nameString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
||||
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
||||
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
if (nameString.Equals(name)) {
|
||||
value.Assign(((char *)nvpair) + 8 + nameLen, valueLen);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// that pair didn't match - try the next one in this block
|
||||
nvpair += 8 + nameLen + valueLen;
|
||||
}
|
||||
nvpair += 8 + nameLen + valueLen;
|
||||
}
|
||||
|
||||
// move to the next name/value header block (if there is one) - the
|
||||
// first pair is offset 4 bytes into it
|
||||
nvpair += 4;
|
||||
} while (lastHeaderByte >= nvpair);
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
@ -995,101 +1138,106 @@ SpdyStream3::ConvertHeaders(nsACString &aHeadersOut)
|
||||
(mDecompressBuffer.get()) + 4;
|
||||
const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
|
||||
(mDecompressBuffer.get()) + mDecompressBufferUsed;
|
||||
|
||||
if (lastHeaderByte < nvpair)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
uint32_t numPairs =
|
||||
PR_ntohl(reinterpret_cast<uint32_t *>(mDecompressBuffer.get())[0]);
|
||||
|
||||
for (uint32_t index = 0; index < numPairs; ++index) {
|
||||
if (lastHeaderByte < nvpair + 4)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
||||
(nvpair[2] << 8) + nvpair[3];
|
||||
if (lastHeaderByte < nvpair + 4 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
nsDependentCSubstring nameString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
||||
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
||||
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
// Look for illegal characters in the nameString.
|
||||
// This includes upper case characters and nulls (as they will
|
||||
// break the fixup-nulls-in-value-string algorithm)
|
||||
// Look for upper case characters in the name. They are illegal.
|
||||
for (char *cPtr = nameString.BeginWriting();
|
||||
cPtr && cPtr < nameString.EndWriting();
|
||||
++cPtr) {
|
||||
if (*cPtr <= 'Z' && *cPtr >= 'A') {
|
||||
nsCString toLog(nameString);
|
||||
|
||||
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
||||
"upper case response header found. [%s]\n",
|
||||
mSession, this, toLog.get()));
|
||||
do {
|
||||
uint32_t numPairs = PR_ntohl(reinterpret_cast<const uint32_t *>(nvpair)[-1]);
|
||||
|
||||
for (uint32_t index = 0; index < numPairs; ++index) {
|
||||
if (lastHeaderByte < nvpair + 4)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// check for null characters
|
||||
if (*cPtr == '\0')
|
||||
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
||||
(nvpair[2] << 8) + nvpair[3];
|
||||
if (lastHeaderByte < nvpair + 4 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// HTTP Chunked responses are not legal over spdy. We do not need
|
||||
// to look for chunked specifically because it is the only HTTP
|
||||
// allowed default encoding and we did not negotiate further encodings
|
||||
// via TE
|
||||
if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) {
|
||||
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
||||
"transfer-encoding found. Chunked is invalid and no TE sent.",
|
||||
mSession, this));
|
||||
nsDependentCSubstring nameString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
||||
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
||||
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
uint32_t valueLen =
|
||||
(nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
||||
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
||||
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
if (!nameString.Equals(NS_LITERAL_CSTRING(":version")) &&
|
||||
!nameString.Equals(NS_LITERAL_CSTRING(":status")) &&
|
||||
!nameString.Equals(NS_LITERAL_CSTRING("connection")) &&
|
||||
!nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) {
|
||||
nsDependentCSubstring valueString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 8 + nameLen,
|
||||
reinterpret_cast<const char *>(nvpair) + 8 + nameLen +
|
||||
valueLen);
|
||||
|
||||
aHeadersOut.Append(nameString);
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING(": "));
|
||||
|
||||
// expand NULL bytes in the value string
|
||||
for (char *cPtr = valueString.BeginWriting();
|
||||
cPtr && cPtr < valueString.EndWriting();
|
||||
// Look for illegal characters in the nameString.
|
||||
// This includes upper case characters and nulls (as they will
|
||||
// break the fixup-nulls-in-value-string algorithm)
|
||||
// Look for upper case characters in the name. They are illegal.
|
||||
for (char *cPtr = nameString.BeginWriting();
|
||||
cPtr && cPtr < nameString.EndWriting();
|
||||
++cPtr) {
|
||||
if (*cPtr != 0) {
|
||||
aHeadersOut.Append(*cPtr);
|
||||
continue;
|
||||
if (*cPtr <= 'Z' && *cPtr >= 'A') {
|
||||
nsCString toLog(nameString);
|
||||
|
||||
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
||||
"upper case response header found. [%s]\n",
|
||||
mSession, this, toLog.get()));
|
||||
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// NULLs are really "\r\nhdr: "
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n"));
|
||||
aHeadersOut.Append(nameString);
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING(": "));
|
||||
// check for null characters
|
||||
if (*cPtr == '\0')
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n"));
|
||||
// HTTP Chunked responses are not legal over spdy. We do not need
|
||||
// to look for chunked specifically because it is the only HTTP
|
||||
// allowed default encoding and we did not negotiate further encodings
|
||||
// via TE
|
||||
if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) {
|
||||
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
||||
"transfer-encoding found. Chunked is invalid and no TE sent.",
|
||||
mSession, this));
|
||||
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
uint32_t valueLen =
|
||||
(nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
||||
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
||||
|
||||
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
// spdy transport level headers shouldn't be gatewayed into http/1
|
||||
if (!nameString.IsEmpty() && nameString[0] != ':' &&
|
||||
!nameString.Equals(NS_LITERAL_CSTRING("connection")) &&
|
||||
!nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) {
|
||||
nsDependentCSubstring valueString =
|
||||
Substring(reinterpret_cast<const char *>(nvpair) + 8 + nameLen,
|
||||
reinterpret_cast<const char *>(nvpair) + 8 + nameLen +
|
||||
valueLen);
|
||||
|
||||
aHeadersOut.Append(nameString);
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING(": "));
|
||||
|
||||
// expand NULL bytes in the value string
|
||||
for (char *cPtr = valueString.BeginWriting();
|
||||
cPtr && cPtr < valueString.EndWriting();
|
||||
++cPtr) {
|
||||
if (*cPtr != 0) {
|
||||
aHeadersOut.Append(*cPtr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// NULLs are really "\r\nhdr: "
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n"));
|
||||
aHeadersOut.Append(nameString);
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING(": "));
|
||||
}
|
||||
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n"));
|
||||
}
|
||||
// move to the next name/value pair in this block
|
||||
nvpair += 8 + nameLen + valueLen;
|
||||
}
|
||||
nvpair += 8 + nameLen + valueLen;
|
||||
}
|
||||
|
||||
// move to the next name/value header block (if there is one) - the
|
||||
// first pair is offset 4 bytes into it
|
||||
nvpair += 4;
|
||||
} while (lastHeaderByte >= nvpair);
|
||||
|
||||
aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 3\r\n\r\n"));
|
||||
LOG (("decoded response headers are:\n%s",
|
||||
@ -1206,7 +1354,7 @@ SpdyStream3::OnReadSegment(const char *buf,
|
||||
LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
|
||||
this, *countRead, count, mSynFrameComplete));
|
||||
if (mSynFrameComplete) {
|
||||
MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment SynFrameComplete 0b");
|
||||
AdjustInitialWindow();
|
||||
rv = TransmitFrame(nullptr, nullptr, true);
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
// this can't happen
|
||||
@ -1288,16 +1436,25 @@ SpdyStream3::OnReadSegment(const char *buf,
|
||||
|
||||
nsresult
|
||||
SpdyStream3::OnWriteSegment(char *buf,
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
uint32_t count,
|
||||
uint32_t *countWritten)
|
||||
{
|
||||
LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x",
|
||||
this, count, mUpstreamState));
|
||||
LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x 0x%X\n",
|
||||
this, count, mUpstreamState, mStreamID));
|
||||
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
MOZ_ASSERT(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
|
||||
MOZ_ASSERT(mSegmentWriter);
|
||||
|
||||
return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
|
||||
if (!mPushSource)
|
||||
return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
|
||||
|
||||
nsresult rv;
|
||||
rv = mPushSource->GetBufferedData(buf, count, countWritten);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
mSession->ConnectPushedStream(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla::net
|
||||
|
@ -6,26 +6,28 @@
|
||||
#ifndef mozilla_net_SpdyStream3_h
|
||||
#define mozilla_net_SpdyStream3_h
|
||||
|
||||
#include "nsAHttpTransaction.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsAHttpTransaction.h"
|
||||
|
||||
namespace mozilla { namespace net {
|
||||
|
||||
class SpdyStream3 MOZ_FINAL : public nsAHttpSegmentReader
|
||||
, public nsAHttpSegmentWriter
|
||||
class SpdyStream3 : public nsAHttpSegmentReader
|
||||
, public nsAHttpSegmentWriter
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSAHTTPSEGMENTREADER
|
||||
NS_DECL_NSAHTTPSEGMENTWRITER
|
||||
|
||||
SpdyStream3(nsAHttpTransaction *,
|
||||
SpdySession3 *, nsISocketTransport *,
|
||||
uint32_t, z_stream *, int32_t);
|
||||
SpdyStream3(nsAHttpTransaction *, SpdySession3 *, int32_t);
|
||||
|
||||
uint32_t StreamID() { return mStreamID; }
|
||||
SpdyPushedStream3 *PushSource() { return mPushSource; }
|
||||
|
||||
nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
|
||||
nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
|
||||
virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
|
||||
virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
|
||||
virtual bool DeferCleanupOnSuccess() { return false; }
|
||||
|
||||
const nsAFlatCString &Origin() const { return mOrigin; }
|
||||
|
||||
bool RequestBlockedOnRead()
|
||||
{
|
||||
@ -42,9 +44,10 @@ public:
|
||||
|
||||
bool HasRegisteredID() { return mStreamID != 0; }
|
||||
|
||||
nsAHttpTransaction *Transaction()
|
||||
nsAHttpTransaction *Transaction() { return mTransaction; }
|
||||
virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo()
|
||||
{
|
||||
return mTransaction;
|
||||
return mTransaction ? mTransaction->LoadGroupConnectionInfo() : nullptr;
|
||||
}
|
||||
|
||||
void Close(nsresult reason);
|
||||
@ -81,15 +84,25 @@ public:
|
||||
}
|
||||
|
||||
uint64_t LocalUnAcked() { return mLocalUnacked; }
|
||||
int64_t LocalWindow() { return mLocalWindow; }
|
||||
|
||||
bool BlockedOnRwin() { return mBlockedOnRwin; }
|
||||
|
||||
private:
|
||||
// A pull stream has an implicit sink, a pushed stream has a sink
|
||||
// once it is matched to a pull stream.
|
||||
virtual bool HasSink() { return true; }
|
||||
|
||||
// a SpdyStream3 object is only destroyed by being removed from the
|
||||
// SpdySession3 mStreamTransactionHash - make the dtor private to
|
||||
// just the AutoPtr implementation needed for that hash.
|
||||
friend class nsAutoPtr<SpdyStream3>;
|
||||
~SpdyStream3();
|
||||
virtual ~SpdyStream3();
|
||||
|
||||
protected:
|
||||
nsresult FindHeader(nsCString, nsDependentCSubstring &);
|
||||
|
||||
static void CreatePushHashKey(const nsCString &scheme,
|
||||
const nsCString &hostHeader,
|
||||
uint64_t serial,
|
||||
const nsCSubstring &pathInfo,
|
||||
nsCString &outOrigin,
|
||||
nsCString &outKey);
|
||||
|
||||
enum stateType {
|
||||
GENERATING_SYN_STREAM,
|
||||
@ -99,12 +112,36 @@ private:
|
||||
UPSTREAM_COMPLETE
|
||||
};
|
||||
|
||||
uint32_t mStreamID;
|
||||
|
||||
// The session that this stream is a subset of
|
||||
SpdySession3 *mSession;
|
||||
|
||||
nsCString mOrigin;
|
||||
|
||||
// Each stream goes from syn_stream to upstream_complete, perhaps
|
||||
// looping on multiple instances of generating_request_body and
|
||||
// sending_request_body for each SPDY chunk in the upload.
|
||||
enum stateType mUpstreamState;
|
||||
|
||||
// Flag is set when all http request headers have been read and ID is stable
|
||||
uint32_t mSynFrameComplete : 1;
|
||||
|
||||
// Flag is set when a FIN has been placed on a data or syn packet
|
||||
// (i.e after the client has closed)
|
||||
uint32_t mSentFinOnData : 1;
|
||||
|
||||
void ChangeState(enum stateType);
|
||||
|
||||
private:
|
||||
friend class nsAutoPtr<SpdyStream3>;
|
||||
|
||||
static PLDHashOperator hdrHashEnumerate(const nsACString &,
|
||||
nsAutoPtr<nsCString> &,
|
||||
void *);
|
||||
|
||||
void ChangeState(enum stateType);
|
||||
nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
|
||||
void AdjustInitialWindow();
|
||||
nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
|
||||
void GenerateDataFrameHeader(uint32_t, bool);
|
||||
|
||||
@ -114,12 +151,6 @@ private:
|
||||
void CompressToFrame(uint32_t);
|
||||
void CompressFlushFrame();
|
||||
void ExecuteCompress(uint32_t);
|
||||
nsresult FindHeader(nsCString, nsDependentCSubstring &);
|
||||
|
||||
// Each stream goes from syn_stream to upstream_complete, perhaps
|
||||
// looping on multiple instances of generating_request_body and
|
||||
// sending_request_body for each SPDY chunk in the upload.
|
||||
enum stateType mUpstreamState;
|
||||
|
||||
// The underlying HTTP transaction. This pointer is used as the key
|
||||
// in the SpdySession3 mStreamTransactionHash so it is important to
|
||||
@ -127,9 +158,6 @@ private:
|
||||
// (i.e. don't change it or release it after it is set in the ctor).
|
||||
nsRefPtr<nsAHttpTransaction> mTransaction;
|
||||
|
||||
// The session that this stream is a subset of
|
||||
SpdySession3 *mSession;
|
||||
|
||||
// The underlying socket transport object is needed to propogate some events
|
||||
nsISocketTransport *mSocketTransport;
|
||||
|
||||
@ -139,23 +167,13 @@ private:
|
||||
nsAHttpSegmentReader *mSegmentReader;
|
||||
nsAHttpSegmentWriter *mSegmentWriter;
|
||||
|
||||
// The 24 bit SPDY stream ID
|
||||
uint32_t mStreamID;
|
||||
|
||||
// The quanta upstream data frames are chopped into
|
||||
uint32_t mChunkSize;
|
||||
|
||||
// Flag is set when all http request headers have been read and ID is stable
|
||||
uint32_t mSynFrameComplete : 1;
|
||||
|
||||
// Flag is set when the HTTP processor has more data to send
|
||||
// but has blocked in doing so.
|
||||
uint32_t mRequestBlockedOnRead : 1;
|
||||
|
||||
// Flag is set when a FIN has been placed on a data or syn packet
|
||||
// (i.e after the client has closed)
|
||||
uint32_t mSentFinOnData : 1;
|
||||
|
||||
// Flag is set after the response frame bearing the fin bit has
|
||||
// been processed. (i.e. after the server has closed).
|
||||
uint32_t mRecvdFin : 1;
|
||||
@ -230,6 +248,9 @@ private:
|
||||
// For Progress Events
|
||||
uint64_t mTotalSent;
|
||||
uint64_t mTotalRead;
|
||||
|
||||
// For SpdyPush
|
||||
SpdyPushedStream3 *mPushSource;
|
||||
};
|
||||
|
||||
}} // namespace mozilla::net
|
||||
|
@ -39,6 +39,7 @@ EXPORTS.mozilla.net += [
|
||||
'HttpChannelParent.h',
|
||||
'HttpInfo.h',
|
||||
'PHttpChannelParams.h',
|
||||
'PSpdyPush3.h',
|
||||
]
|
||||
|
||||
CPP_SOURCES += [
|
||||
@ -50,6 +51,7 @@ CPP_SOURCES += [
|
||||
'HttpChannelParentListener.cpp',
|
||||
'HttpInfo.cpp',
|
||||
'NullHttpTransaction.cpp',
|
||||
'SpdyPush3.cpp',
|
||||
'SpdySession2.cpp',
|
||||
'SpdySession3.cpp',
|
||||
'SpdyStream2.cpp',
|
||||
|
@ -47,6 +47,10 @@ public:
|
||||
virtual nsresult ResumeSend() = 0;
|
||||
virtual nsresult ResumeRecv() = 0;
|
||||
|
||||
// called by a transaction to force a "read from network" iteration
|
||||
// even if not scheduled by socket associated with connection
|
||||
virtual nsresult ForceRecv() = 0;
|
||||
|
||||
// After a connection has had ResumeSend() called by a transaction,
|
||||
// and it is ready to write to the network it may need to know the
|
||||
// transaction that has data to write. This is only an issue for
|
||||
@ -175,6 +179,12 @@ public:
|
||||
return NS_ERROR_FAILURE; \
|
||||
return (fwdObject)->ResumeRecv(); \
|
||||
} \
|
||||
nsresult ForceRecv() \
|
||||
{ \
|
||||
if (!(fwdObject)) \
|
||||
return NS_ERROR_FAILURE; \
|
||||
return (fwdObject)->ForceRecv(); \
|
||||
} \
|
||||
nsISocketTransport *Transport() \
|
||||
{ \
|
||||
if (!(fwdObject)) \
|
||||
|
@ -16,6 +16,8 @@ class nsIEventTarget;
|
||||
class nsITransport;
|
||||
class nsHttpRequestHead;
|
||||
class nsHttpPipeline;
|
||||
class nsHttpTransaction;
|
||||
class nsILoadGroupConnectionInfo;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Abstract base class for a HTTP transaction:
|
||||
@ -100,6 +102,12 @@ public:
|
||||
virtual nsresult SetPipelinePosition(int32_t) = 0;
|
||||
virtual int32_t PipelinePosition() = 0;
|
||||
|
||||
// Occasionally the abstract interface has to give way to base implementations
|
||||
// to respect differences between spdy, pipelines, etc..
|
||||
// These Query* (and IsNUllTransaction()) functions provide a way to do
|
||||
// that without using xpcom or rtti. Any calling code that can't deal with
|
||||
// a null response from one of them probably shouldn't be using nsAHttpTransaction
|
||||
|
||||
// If we used rtti this would be the result of doing
|
||||
// dynamic_cast<nsHttpPipeline *>(this).. i.e. it can be nullptr for
|
||||
// non pipeline implementations of nsAHttpTransaction
|
||||
@ -110,6 +118,9 @@ public:
|
||||
// its IO functions all the time.
|
||||
virtual bool IsNullTransaction() { return false; }
|
||||
|
||||
// return the load group connection information associated with the transaction
|
||||
virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return nullptr; }
|
||||
|
||||
// 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.
|
||||
@ -177,7 +188,7 @@ public:
|
||||
// commitment now but might in the future and forceCommitment is not true .
|
||||
// (forceCommitment requires a hard failure or OK at this moment.)
|
||||
//
|
||||
// Spdy uses this to make sure frames are atomic.
|
||||
// SpdySession uses this to make sure frames are atomic.
|
||||
virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -383,6 +383,13 @@ nsHttpConnection::SetupNPN(uint32_t caps)
|
||||
return;
|
||||
|
||||
nsTArray<nsCString> protocolArray;
|
||||
|
||||
// The first protocol is used as the fallback if none of the
|
||||
// protocols supported overlap with the server's list.
|
||||
// In the case of overlap, matching priority is driven by
|
||||
// the order of the server's advertisement.
|
||||
protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
|
||||
|
||||
if (gHttpHandler->IsSpdyEnabled() &&
|
||||
!(caps & NS_HTTP_DISALLOW_SPDY)) {
|
||||
LOG(("nsHttpConnection::SetupNPN Allow SPDY NPN selection"));
|
||||
@ -394,7 +401,6 @@ nsHttpConnection::SetupNPN(uint32_t caps)
|
||||
gHttpHandler->SpdyInfo()->VersionString[1]);
|
||||
}
|
||||
|
||||
protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
|
||||
if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
|
||||
LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
|
||||
mNPNComplete = false;
|
||||
@ -1090,6 +1096,35 @@ nsHttpConnection::ResumeRecv()
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
|
||||
class nsHttpConnectionForceRecv : public nsRunnable
|
||||
{
|
||||
public:
|
||||
nsHttpConnectionForceRecv(nsHttpConnection *aConn)
|
||||
: mConn(aConn) {}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
if (!mConn->mSocketIn)
|
||||
return NS_OK;
|
||||
return mConn->OnInputStreamReady(mConn->mSocketIn);
|
||||
}
|
||||
private:
|
||||
nsRefPtr<nsHttpConnection> mConn;
|
||||
};
|
||||
|
||||
// trigger an asynchronous read
|
||||
nsresult
|
||||
nsHttpConnection::ForceRecv()
|
||||
{
|
||||
LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
return NS_DispatchToCurrentThread(new nsHttpConnectionForceRecv(this));
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpConnection::BeginIdleMonitoring()
|
||||
{
|
||||
|
@ -120,6 +120,9 @@ public:
|
||||
nsresult ResumeRecv();
|
||||
int64_t MaxBytesRead() {return mMaxBytesRead;}
|
||||
|
||||
friend class nsHttpConnectionForceRecv;
|
||||
nsresult ForceRecv();
|
||||
|
||||
static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *,
|
||||
uint32_t, uint32_t, uint32_t *);
|
||||
|
||||
|
@ -183,8 +183,10 @@ nsHttpHandler::nsHttpHandler()
|
||||
, mCoalesceSpdy(true)
|
||||
, mUseAlternateProtocol(false)
|
||||
, mSpdyPersistentSettings(false)
|
||||
, mAllowSpdyPush(true)
|
||||
, mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
|
||||
, mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
|
||||
, mSpdyPushAllowance(32768)
|
||||
, mSpdyPingThreshold(PR_SecondsToInterval(58))
|
||||
, mSpdyPingTimeout(PR_SecondsToInterval(8))
|
||||
, mConnectTimeout(90000)
|
||||
@ -1159,6 +1161,22 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
|
||||
PR_SecondsToInterval((uint16_t) clamped(val, 0, 0x7fffffff));
|
||||
}
|
||||
|
||||
if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) {
|
||||
rv = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"),
|
||||
&cVar);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
mAllowSpdyPush = cVar;
|
||||
}
|
||||
|
||||
if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) {
|
||||
rv = prefs->GetIntPref(HTTP_PREF("spdy.push-allowance"), &val);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mSpdyPushAllowance =
|
||||
static_cast<uint32_t>
|
||||
(clamped(val, 1024, static_cast<int32_t>(ASpdySession::kInitialRwin)));
|
||||
}
|
||||
}
|
||||
|
||||
// The amount of seconds to wait for a spdy ping response before
|
||||
// closing the session.
|
||||
if (PREF_CHANGED(HTTP_PREF("spdy.send-buffer-size"))) {
|
||||
|
@ -101,8 +101,10 @@ public:
|
||||
bool UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
|
||||
uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; }
|
||||
uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; }
|
||||
uint32_t SpdyPushAllowance() { return mSpdyPushAllowance; }
|
||||
PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
|
||||
PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
|
||||
bool AllowSpdyPush() { return mAllowSpdyPush; }
|
||||
uint32_t ConnectTimeout() { return mConnectTimeout; }
|
||||
uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
|
||||
bool CritialRequestPrioritization() { return mCritialRequestPrioritization; }
|
||||
@ -416,8 +418,10 @@ private:
|
||||
bool mCoalesceSpdy;
|
||||
bool mUseAlternateProtocol;
|
||||
bool mSpdyPersistentSettings;
|
||||
bool mAllowSpdyPush;
|
||||
uint32_t mSpdySendingChunkSize;
|
||||
uint32_t mSpdySendBufferSize;
|
||||
uint32_t mSpdyPushAllowance;
|
||||
PRIntervalTime mSpdyPingThreshold;
|
||||
PRIntervalTime mSpdyPingTimeout;
|
||||
|
||||
|
@ -115,8 +115,9 @@ public:
|
||||
const mozilla::TimeStamp GetPendingTime() { return mPendingTime; }
|
||||
bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
|
||||
|
||||
void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; }
|
||||
// overload of nsAHttpTransaction::LoadGroupConnectionInfo()
|
||||
nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI.get(); }
|
||||
void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; }
|
||||
void DispatchedAsBlocking();
|
||||
void RemoveDispatchedAsBlocking();
|
||||
|
||||
|
@ -26,7 +26,9 @@ interface nsISSLSocketControl : nsISupports {
|
||||
tunnel during the SSL handshake. The NPNList is the list
|
||||
of offered client side protocols. setNPNList() needs to
|
||||
be called before any data is read or written (including the
|
||||
handshake to be setup correctly. */
|
||||
handshake to be setup correctly. The server determines the
|
||||
priority when multiple matches occur, but if there is no overlap
|
||||
the first protocol in the list is used. */
|
||||
|
||||
[noscript] void setNPNList(in nsCStringTArrayRef aNPNList);
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
// test spdy/3
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
|
||||
@ -20,8 +22,7 @@ var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
|
||||
|
||||
function checkIsSpdy(request) {
|
||||
try {
|
||||
if (request.getResponseHeader("X-Firefox-Spdy") == "2" ||
|
||||
request.getResponseHeader("X-Firefox-Spdy") == "3") {
|
||||
if (request.getResponseHeader("X-Firefox-Spdy") == "3") {
|
||||
if (request.getResponseHeader("X-Connection-Spdy") == "yes") {
|
||||
return true;
|
||||
}
|
||||
@ -125,6 +126,20 @@ SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, of
|
||||
read_stream(stream, cnt);
|
||||
};
|
||||
|
||||
var SpdyPushListener = function() {};
|
||||
|
||||
SpdyPushListener.prototype = new SpdyCheckListener();
|
||||
|
||||
SpdyPushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
if (ctx.originalURI.spec == "https://localhost:4443/push.js" ||
|
||||
ctx.originalURI.spec == "https://localhost:4443/push2.js") {
|
||||
do_check_eq(request.getResponseHeader("pushed"), "yes");
|
||||
}
|
||||
read_stream(stream, cnt);
|
||||
};
|
||||
|
||||
// Does the appropriate checks for a large GET response
|
||||
var SpdyBigListener = function() {};
|
||||
|
||||
@ -222,6 +237,34 @@ function test_spdy_header() {
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
function test_spdy_push1() {
|
||||
var chan = makeChan("https://localhost:4443/push");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new SpdyPushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_spdy_push2() {
|
||||
var chan = makeChan("https://localhost:4443/push.js");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new SpdyPushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_spdy_push3() {
|
||||
var chan = makeChan("https://localhost:4443/push2");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new SpdyPushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
function test_spdy_push4() {
|
||||
var chan = makeChan("https://localhost:4443/push2.js");
|
||||
chan.loadGroup = loadGroup;
|
||||
var listener = new SpdyPushListener();
|
||||
chan.asyncOpen(listener, chan);
|
||||
}
|
||||
|
||||
// Make sure we handle GETs that cover more than 2 frames properly
|
||||
function test_spdy_big() {
|
||||
var chan = makeChan("https://localhost:4443/big");
|
||||
@ -257,10 +300,17 @@ function test_spdy_post_big() {
|
||||
do_post(posts[1], chan, listener);
|
||||
}
|
||||
|
||||
// hack - the header test resets the multiplex object on the server,
|
||||
// so make sure header is always run before the multiplex test.
|
||||
|
||||
var tests = [ test_spdy_basic
|
||||
, test_spdy_push1
|
||||
, test_spdy_push2
|
||||
, test_spdy_push3
|
||||
, test_spdy_push4
|
||||
, test_spdy_xhr
|
||||
, test_spdy_multiplex
|
||||
, test_spdy_header
|
||||
, test_spdy_multiplex
|
||||
, test_spdy_big
|
||||
, test_spdy_post
|
||||
, test_spdy_post_big
|
||||
@ -328,6 +378,21 @@ function addCertOverride(host, port, bits) {
|
||||
}
|
||||
}
|
||||
|
||||
var prefs;
|
||||
var spdypref;
|
||||
var spdy2pref;
|
||||
var spdy3pref;
|
||||
var spdypush;
|
||||
|
||||
var loadGroup;
|
||||
|
||||
function resetPrefs() {
|
||||
prefs.setBoolPref("network.http.spdy.enabled", spdypref);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
|
||||
prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
// Set to allow the cert presented by our SPDY server
|
||||
do_get_profile();
|
||||
@ -342,8 +407,17 @@ function run_test() {
|
||||
|
||||
prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
|
||||
|
||||
// Make sure spdy is enabled
|
||||
// Enable all versions of spdy to see that we auto negotiate spdy/3
|
||||
spdypref = prefs.getBoolPref("network.http.spdy.enabled");
|
||||
spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2");
|
||||
spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
|
||||
spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
|
||||
prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v2", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", true);
|
||||
prefs.setBoolPref("network.http.spdy.allow-push", true);
|
||||
|
||||
loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
|
||||
|
||||
// And make go!
|
||||
run_next_test();
|
||||
|
372
netwerk/test/unit/test_spdy2.js
Normal file
372
netwerk/test/unit/test_spdy2.js
Normal file
@ -0,0 +1,372 @@
|
||||
// test spdy/2
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
|
||||
// Generate a small and a large post with known pre-calculated md5 sums
|
||||
function generateContent(size) {
|
||||
var content = "";
|
||||
for (var i = 0; i < size; i++) {
|
||||
content += "0";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
var posts = [];
|
||||
posts.push(generateContent(10));
|
||||
posts.push(generateContent(128 * 1024));
|
||||
|
||||
// pre-calculated md5sums (in hex) of the above posts
|
||||
var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
|
||||
'8f607cfdd2c87d6a7eedb657dafbd836'];
|
||||
|
||||
function checkIsSpdy(request) {
|
||||
try {
|
||||
if (request.getResponseHeader("X-Firefox-Spdy") == "2") {
|
||||
if (request.getResponseHeader("X-Connection-Spdy") == "yes") {
|
||||
return true;
|
||||
}
|
||||
return false; // Weird case, but the server disagrees with us
|
||||
}
|
||||
} catch (e) {
|
||||
// Nothing to do here
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var SpdyCheckListener = function() {};
|
||||
|
||||
SpdyCheckListener.prototype = {
|
||||
onStartRequestFired: false,
|
||||
onDataAvailableFired: false,
|
||||
isSpdyConnection: false,
|
||||
|
||||
onStartRequest: function testOnStartRequest(request, ctx) {
|
||||
this.onStartRequestFired = true;
|
||||
|
||||
if (!Components.isSuccessCode(request.status))
|
||||
do_throw("Channel should have a success code! (" + request.status + ")");
|
||||
if (!(request instanceof Components.interfaces.nsIHttpChannel))
|
||||
do_throw("Expecting an HTTP channel");
|
||||
|
||||
do_check_eq(request.responseStatus, 200);
|
||||
do_check_eq(request.requestSucceeded, true);
|
||||
},
|
||||
|
||||
onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
|
||||
read_stream(stream, cnt);
|
||||
},
|
||||
|
||||
onStopRequest: function testOnStopRequest(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isSpdyConnection);
|
||||
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Support for testing valid multiplexing of streams
|
||||
*/
|
||||
|
||||
var multiplexContent = generateContent(30*1024);
|
||||
var completed_channels = [];
|
||||
function register_completed_channel(listener) {
|
||||
completed_channels.push(listener);
|
||||
if (completed_channels.length == 2) {
|
||||
do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
}
|
||||
|
||||
/* Listener class to control the testing of multiplexing */
|
||||
var SpdyMultiplexListener = function() {};
|
||||
|
||||
SpdyMultiplexListener.prototype = new SpdyCheckListener();
|
||||
|
||||
SpdyMultiplexListener.prototype.streamID = 0;
|
||||
SpdyMultiplexListener.prototype.buffer = "";
|
||||
|
||||
SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID"));
|
||||
var data = read_stream(stream, cnt);
|
||||
this.buffer = this.buffer.concat(data);
|
||||
};
|
||||
|
||||
SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isSpdyConnection);
|
||||
do_check_true(this.buffer == multiplexContent);
|
||||
|
||||
// This is what does most of the hard work for us
|
||||
register_completed_channel(this);
|
||||
};
|
||||
|
||||
// Does the appropriate checks for header gatewaying
|
||||
var SpdyHeaderListener = function(value) {
|
||||
this.value = value
|
||||
};
|
||||
|
||||
SpdyHeaderListener.prototype = new SpdyCheckListener();
|
||||
SpdyHeaderListener.prototype.value = "";
|
||||
|
||||
SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value);
|
||||
read_stream(stream, cnt);
|
||||
};
|
||||
|
||||
// Does the appropriate checks for a large GET response
|
||||
var SpdyBigListener = function() {};
|
||||
|
||||
SpdyBigListener.prototype = new SpdyCheckListener();
|
||||
SpdyBigListener.prototype.buffer = "";
|
||||
|
||||
SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
this.buffer = this.buffer.concat(read_stream(stream, cnt));
|
||||
// We know the server should send us the same data as our big post will be,
|
||||
// so the md5 should be the same
|
||||
do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5"));
|
||||
};
|
||||
|
||||
SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isSpdyConnection);
|
||||
|
||||
// Don't want to flood output, so don't use do_check_eq
|
||||
do_check_true(this.buffer == posts[1]);
|
||||
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
};
|
||||
|
||||
// Does the appropriate checks for POSTs
|
||||
var SpdyPostListener = function(expected_md5) {
|
||||
this.expected_md5 = expected_md5;
|
||||
};
|
||||
|
||||
SpdyPostListener.prototype = new SpdyCheckListener();
|
||||
SpdyPostListener.prototype.expected_md5 = "";
|
||||
|
||||
SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isSpdyConnection = checkIsSpdy(request);
|
||||
read_stream(stream, cnt);
|
||||
do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
|
||||
};
|
||||
|
||||
function makeChan(url) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
// Make sure we make a spdy connection and both us and the server mark it as such
|
||||
function test_spdy_basic() {
|
||||
var chan = makeChan("https://localhost:4443/");
|
||||
var listener = new SpdyCheckListener();
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Support for making sure XHR works over SPDY
|
||||
function checkXhr(xhr) {
|
||||
if (xhr.readyState != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_check_eq(xhr.status, 200);
|
||||
do_check_eq(checkIsSpdy(xhr), true);
|
||||
run_next_test();
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
// Fires off an XHR request over SPDY
|
||||
function test_spdy_xhr() {
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
req.open("GET", "https://localhost:4443/", true);
|
||||
req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
|
||||
false);
|
||||
req.send(null);
|
||||
}
|
||||
|
||||
// Test to make sure we get multiplexing right
|
||||
function test_spdy_multiplex() {
|
||||
var chan1 = makeChan("https://localhost:4443/multiplex1");
|
||||
var chan2 = makeChan("https://localhost:4443/multiplex2");
|
||||
var listener1 = new SpdyMultiplexListener();
|
||||
var listener2 = new SpdyMultiplexListener();
|
||||
chan1.asyncOpen(listener1, null);
|
||||
chan2.asyncOpen(listener2, null);
|
||||
}
|
||||
|
||||
// Test to make sure we gateway non-standard headers properly
|
||||
function test_spdy_header() {
|
||||
var chan = makeChan("https://localhost:4443/header");
|
||||
var hvalue = "Headers are fun";
|
||||
var listener = new SpdyHeaderListener(hvalue);
|
||||
chan.setRequestHeader("X-Test-Header", hvalue, false);
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Make sure we handle GETs that cover more than 2 frames properly
|
||||
function test_spdy_big() {
|
||||
var chan = makeChan("https://localhost:4443/big");
|
||||
var listener = new SpdyBigListener();
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Support for doing a POST
|
||||
function do_post(content, chan, listener) {
|
||||
var stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.data = content;
|
||||
|
||||
var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
|
||||
uchan.setUploadStream(stream, "text/plain", stream.available());
|
||||
|
||||
chan.requestMethod = "POST";
|
||||
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
// Make sure we can do a simple POST
|
||||
function test_spdy_post() {
|
||||
var chan = makeChan("https://localhost:4443/post");
|
||||
var listener = new SpdyPostListener(md5s[0]);
|
||||
do_post(posts[0], chan, listener);
|
||||
}
|
||||
|
||||
// Make sure we can do a POST that covers more than 2 frames
|
||||
function test_spdy_post_big() {
|
||||
var chan = makeChan("https://localhost:4443/post");
|
||||
var listener = new SpdyPostListener(md5s[1]);
|
||||
do_post(posts[1], chan, listener);
|
||||
}
|
||||
|
||||
// hack - the header test resets the multiplex object on the server,
|
||||
// so make sure header is always run before the multiplex test.
|
||||
|
||||
var tests = [ test_spdy_basic
|
||||
, test_spdy_xhr
|
||||
, test_spdy_header
|
||||
, test_spdy_multiplex
|
||||
, test_spdy_big
|
||||
, test_spdy_post
|
||||
, test_spdy_post_big
|
||||
];
|
||||
var current_test = 0;
|
||||
|
||||
function run_next_test() {
|
||||
if (current_test < tests.length) {
|
||||
tests[current_test]();
|
||||
current_test++;
|
||||
do_test_pending();
|
||||
}
|
||||
}
|
||||
|
||||
// Support for making sure we can talk to the invalid cert the server presents
|
||||
var CertOverrideListener = function(host, port, bits) {
|
||||
this.host = host;
|
||||
if (port) {
|
||||
this.port = port;
|
||||
}
|
||||
this.bits = bits;
|
||||
};
|
||||
|
||||
CertOverrideListener.prototype = {
|
||||
host: null,
|
||||
port: -1,
|
||||
bits: null,
|
||||
|
||||
getInterface: function(aIID) {
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.nsIBadCertListener2) ||
|
||||
aIID.equals(Ci.nsIInterfaceRequestor) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
|
||||
var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
|
||||
var cos = Cc["@mozilla.org/security/certoverride;1"].
|
||||
getService(Ci.nsICertOverrideService);
|
||||
cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
function addCertOverride(host, port, bits) {
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
try {
|
||||
var url;
|
||||
if (port) {
|
||||
url = "https://" + host + ":" + port + "/";
|
||||
} else {
|
||||
url = "https://" + host + "/";
|
||||
}
|
||||
req.open("GET", url, false);
|
||||
req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
|
||||
req.send(null);
|
||||
} catch (e) {
|
||||
// This will fail since the server is not trusted yet
|
||||
}
|
||||
}
|
||||
|
||||
var prefs;
|
||||
var spdypref;
|
||||
var spdy2pref;
|
||||
var spdy3pref;
|
||||
|
||||
function resetPrefs() {
|
||||
prefs.setBoolPref("network.http.spdy.enabled", spdypref);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
// Set to allow the cert presented by our SPDY server
|
||||
do_get_profile();
|
||||
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
|
||||
prefs.setIntPref("network.http.speculative-parallel-limit", 0);
|
||||
|
||||
addCertOverride("localhost", 4443,
|
||||
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
|
||||
Ci.nsICertOverrideService.ERROR_MISMATCH |
|
||||
Ci.nsICertOverrideService.ERROR_TIME);
|
||||
|
||||
prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
|
||||
|
||||
// Make sure just spdy/2 is enabled
|
||||
spdypref = prefs.getBoolPref("network.http.spdy.enabled");
|
||||
spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2");
|
||||
spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
|
||||
prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v2", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.v3", false);
|
||||
|
||||
do_register_cleanup(resetPrefs);
|
||||
|
||||
// And make go!
|
||||
run_next_test();
|
||||
}
|
@ -170,8 +170,10 @@ fail-if = os == "android"
|
||||
[test_socks.js]
|
||||
# Bug 675039: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
[test_spdy.js]
|
||||
# spdy unit tests require us to have node available to run the spdy server
|
||||
[test_spdy2.js]
|
||||
run-if = hasNode
|
||||
[test_spdy.js]
|
||||
run-if = hasNode
|
||||
[test_speculative_connect.js]
|
||||
[test_standardurl.js]
|
||||
|
@ -21,7 +21,7 @@ class testMultiFinger(MarionetteTestCase):
|
||||
expected = "button1-touchstart"
|
||||
self.wait_for_condition(lambda m: m.execute_script("return document.getElementById('button1').innerHTML;") == expected)
|
||||
self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;"))
|
||||
self.assertIn("button3-touchstart-touchend", self.marionette.execute_script("return document.getElementById('button3').innerHTML;"))
|
||||
self.assertTrue("button3-touchstart-touchend" in self.marionette.execute_script("return document.getElementById('button3').innerHTML;"))
|
||||
|
||||
def test_move_offset_element(self):
|
||||
testAction = self.marionette.absolute_url("testAction.html")
|
||||
@ -37,7 +37,7 @@ class testMultiFinger(MarionetteTestCase):
|
||||
expected = "button1-touchstart"
|
||||
self.wait_for_condition(lambda m: m.execute_script("return document.getElementById('button1').innerHTML;") == expected)
|
||||
self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;"))
|
||||
self.assertIn("button3-touchstart-touchend", self.marionette.execute_script("return document.getElementById('button3').innerHTML;"))
|
||||
self.assertTrue("button3-touchstart-touchend" in self.marionette.execute_script("return document.getElementById('button3').innerHTML;"))
|
||||
|
||||
def test_three_fingers(self):
|
||||
testAction = self.marionette.absolute_url("testAction.html")
|
||||
@ -59,7 +59,7 @@ class testMultiFinger(MarionetteTestCase):
|
||||
self.assertEqual("button2-touchmove-touchend", self.marionette.execute_script("return document.getElementById('button2').innerHTML;"))
|
||||
button3_text = self.marionette.execute_script("return document.getElementById('button3').innerHTML;")
|
||||
button4_text = self.marionette.execute_script("return document.getElementById('button4').innerHTML;")
|
||||
self.assertIn("button3-touchstart-touchend", button3_text)
|
||||
self.assertIn("button4-touchstart-touchend", button4_text)
|
||||
self.assertTrue("button3-touchstart-touchend" in button3_text)
|
||||
self.assertTrue("button4-touchstart-touchend" in button4_text)
|
||||
self.assertTrue(int(button3_text.rsplit("-")[-1]) >= 5000)
|
||||
self.assertTrue(int(button4_text.rsplit("-")[-1]) >= 5000)
|
||||
|
@ -103,10 +103,33 @@ function handleRequest(req, res) {
|
||||
m.checkReady();
|
||||
return;
|
||||
} else if (u.pathname == "/header") {
|
||||
m = new Multiplex();
|
||||
var val = req.headers["x-test-header"];
|
||||
if (val) {
|
||||
res.setHeader("X-Received-Test-Header", val);
|
||||
}
|
||||
} else if (u.pathname == "/push") {
|
||||
res.push('/push.js',
|
||||
{ 'content-type': 'application/javascript',
|
||||
'pushed' : 'yes',
|
||||
'content-length' : 11,
|
||||
'X-Connection-Spdy': 'yes'},
|
||||
function(err, stream) {
|
||||
if (err) return;
|
||||
stream.end('// comments');
|
||||
});
|
||||
content = '<head> <script src="push.js"/></head>body text';
|
||||
} else if (u.pathname == "/push2") {
|
||||
res.push('/push2.js',
|
||||
{ 'content-type': 'application/javascript',
|
||||
'pushed' : 'yes',
|
||||
// no content-length
|
||||
'X-Connection-Spdy': 'yes'},
|
||||
function(err, stream) {
|
||||
if (err) return;
|
||||
stream.end('// comments');
|
||||
});
|
||||
content = '<head> <script src="push2.js"/></head>body text';
|
||||
} else if (u.pathname == "/big") {
|
||||
content = getHugeContent(128 * 1024);
|
||||
var hash = crypto.createHash('md5');
|
||||
|
@ -1,79 +1,84 @@
|
||||
# SPDY Server for node.js [![Build Status](https://secure.travis-ci.org/indutny/node-spdy.png)](http://travis-ci.org/indutny/node-spdy)
|
||||
|
||||
<a href="http://flattr.com/thing/758213/indutnynode-spdy-on-GitHub" target="_blank">
|
||||
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
|
||||
|
||||
With this module you can create [SPDY](http://www.chromium.org/spdy) servers
|
||||
in node.js with natural http module interface and fallback to regular https
|
||||
(for browsers that doesn't support SPDY yet).
|
||||
|
||||
## Node+OpenSSL building
|
||||
|
||||
At the moment node-spdy requires zlib dictionary support, which will come to
|
||||
node.js only in 0.7.x version. To build 0.7.x version follow instructions below:
|
||||
|
||||
```bash
|
||||
git clone git://github.com/joyent/node.git
|
||||
cd node
|
||||
./configure --prefix=$HOME/.node/dev # <- or any other dir
|
||||
|
||||
make install -j4 # in -jN, N is number of CPU cores on your machine
|
||||
|
||||
# Add node's bin to PATH env variable
|
||||
echo 'export PATH=$HOME/.node/dev/bin:$PATH' >> ~/.bashrc
|
||||
|
||||
#
|
||||
# You have working node 0.7.x + NPN now !!!
|
||||
#
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
var spdy = require('spdy');
|
||||
var spdy = require('spdy'),
|
||||
fs = require('fs');
|
||||
|
||||
var options = {
|
||||
key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
|
||||
cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
|
||||
ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem')
|
||||
ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem'),
|
||||
|
||||
// SPDY-specific options
|
||||
windowSize: 1024, // Server's window size
|
||||
};
|
||||
|
||||
spdy.createServer(options, function(req, res) {
|
||||
var server = spdy.createServer(options, function(req, res) {
|
||||
res.writeHead(200);
|
||||
res.end('hello world!');
|
||||
});
|
||||
|
||||
spdy.listen(443);
|
||||
server.listen(443);
|
||||
```
|
||||
|
||||
And by popular demand - usage with
|
||||
[express](https://github.com/visionmedia/express):
|
||||
|
||||
```javascript
|
||||
var spdy = require('spdy'),
|
||||
express = require('express'),
|
||||
fs = require('fs');
|
||||
|
||||
var options = { /* the same as above */ };
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(/* your favorite middleware */);
|
||||
|
||||
var server = spdy.createServer(options, app);
|
||||
|
||||
server.listen(443);
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
API is compatible with `http` and `https` module, but you can use another
|
||||
function as base class for SPDYServer. For example,
|
||||
`require('express').HTTPSServer` given that as base class you'll get a server
|
||||
compatible with [express](https://github.com/visionmedia/express) API.
|
||||
function as base class for SPDYServer.
|
||||
|
||||
```javascript
|
||||
spdy.createServer(
|
||||
[base class constructor, i.e. https.Server or express.HTTPSServer],
|
||||
[base class constructor, i.e. https.Server],
|
||||
{ /* keys and options */ }, // <- the only one required argument
|
||||
[request listener]
|
||||
).listen([port], [host], [callback]);
|
||||
```
|
||||
|
||||
Request listener will receive two arguments: `request` and `response`. They're
|
||||
both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But two
|
||||
custom properties are added to both of them: `streamID` and `isSpdy`. The first
|
||||
one indicates on which spdy stream are sitting request and response. Latter one
|
||||
is always true and can be checked to ensure that incoming request wasn't
|
||||
received by HTTPS callback.
|
||||
both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But three
|
||||
custom properties are added to both of them: `streamID`, `isSpdy`,
|
||||
`spdyVersion`. The first one indicates on which spdy stream are sitting request
|
||||
and response. Second is always true and can be checked to ensure that incoming
|
||||
request wasn't received by HTTPS fallback and last one is a number representing
|
||||
used SPDY protocol version (2 or 3 for now).
|
||||
|
||||
### Push streams
|
||||
|
||||
It's possible to initiate 'push' stream to send content to client, before one'll
|
||||
request it.
|
||||
It is possible to initiate 'push' streams to send content to clients _before_
|
||||
the client requests it.
|
||||
|
||||
```javascript
|
||||
spdy.createServer(options, function(req, res) {
|
||||
var headers = { 'content-type': 'application/javascript' };
|
||||
res.send('/main.js', headers, function(err, stream) {
|
||||
res.push('/main.js', headers, function(err, stream) {
|
||||
if (err) return;
|
||||
|
||||
stream.end('alert("hello from push stream!");');
|
||||
@ -83,7 +88,11 @@ spdy.createServer(options, function(req, res) {
|
||||
}).listen(443);
|
||||
```
|
||||
|
||||
`.push('full or relative url', { ... headers ... }, callback)`
|
||||
Push is accomplished via the `push()` method invoked on the current response
|
||||
object (this works for express.js response objects as well). The format of the
|
||||
`push()` method is:
|
||||
|
||||
`.push('full or relative url', { ... headers ... }, optional priority, callback)`
|
||||
|
||||
You can use either full ( `http://host/path` ) or relative ( `/path` ) urls with
|
||||
`.push()`. `headers` are the same as for regular response object. `callback`
|
||||
@ -100,11 +109,20 @@ controlling [maximum concurrent streams][http://www.chromium.org/spdy/spdy-proto
|
||||
protocol option (if client will start more streams than that limit, RST_STREAM
|
||||
will be sent for each additional stream).
|
||||
|
||||
Additional options:
|
||||
|
||||
* `plain` - if defined, server will accept only plain (non-encrypted)
|
||||
connections.
|
||||
|
||||
#### Contributors
|
||||
|
||||
* [Fedor Indutny](https://github.com/indutny)
|
||||
* [Chris Storm](https://github.com/eee-c)
|
||||
* [Chris Strom](https://github.com/eee-c)
|
||||
* [François de Metz](https://github.com/francois2metz)
|
||||
* [Ilya Grigorik](https://github.com/igrigorik)
|
||||
* [Roberto Peon](https://github.com/grmocg)
|
||||
* [Tatsuhiro Tsujikawa](https://github.com/tatsuhiro-t)
|
||||
* [Jesse Cravens](https://github.com/jessecravens)
|
||||
|
||||
#### LICENSE
|
||||
|
||||
|
@ -1,14 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
|
||||
SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
|
||||
OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
|
||||
BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
|
||||
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
|
||||
p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
|
||||
gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
|
||||
5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
|
||||
vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
|
||||
yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
|
||||
Uws6Lif3P9UbsuRiYPxMgg98wg==
|
||||
MIIDBjCCAe4CCQCC8qgopCwXKDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMB4XDTEzMDEwODEyMzYyM1oXDTIzMDEwNjEyMzYyM1owRTELMAkG
|
||||
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ALYWwPVTdWu+55Y9WKcZKi+iC/BVoV6uejF/7twOQZRAMAs2rFhCnxmds8d7jcX6
|
||||
wBHOejKFYqbm9WPORckieOVmHRHYLhzCXHrhYMHSYW5NxwTCxdka+QEzoFJqBSwO
|
||||
AkBQaF9ETA/GDa22TGN0DqUAtH6AiT3FAf4lSng1gLnMaa6+jYDmHmGHf2epRw+N
|
||||
U8Kb4g8iNLPP10M3x1DIlVtB2MvXgHFY5/fz4BSD8QweqUGaoIuAcLdJ6qJmbztm
|
||||
FWCWmr3uNHo2V6KxjHMMpziCa+280im6OgsdsgMuQO949hk+5k+PZ/WR+Ia4NylE
|
||||
FI6cVXpZwALAVWxC9VO1oocCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAluAKUeNS
|
||||
brX8+Jr7hOEftd+maTb8ERV4CU2wFSeCqupoSGh4Er37FO+eL1cAzaewpQzx8Uvp
|
||||
q7oO+utrOKGA42P0bkdX6vqXZ73seorKl4Mv6Lnq1z1uTRTQyAbvJpv6mYrr+oz5
|
||||
QgTGvPqUWqpv+OPf2R9J00IVLlfIjdKGLt0iZ3QEeS6SDkV/MHT4fEc9fImsg6Rq
|
||||
xcb8kmcv9GAHo7YzxOw2F85h93epl6zUUlW2QCzUbvEF2S5NQowdkDQz0py6o4tv
|
||||
S5Dh5XYLDm7gjcRElLNLaTmdhq9IGBEdbXy6a9kzJqZz4+AOn5+WPnA/KhVhWoTE
|
||||
nJHIzjR3CQEkbA==
|
||||
-----END CERTIFICATE-----
|
||||
|
@ -1,11 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
|
||||
MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
|
||||
3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
|
||||
i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
|
||||
A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
|
||||
FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
|
||||
3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
|
||||
hC3dz5odyKqe4nmoofomALkBL9t4H8s=
|
||||
MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBALYWwPVTdWu+55Y9WKcZKi+iC/BVoV6uejF/7twO
|
||||
QZRAMAs2rFhCnxmds8d7jcX6wBHOejKFYqbm9WPORckieOVmHRHYLhzCXHrhYMHS
|
||||
YW5NxwTCxdka+QEzoFJqBSwOAkBQaF9ETA/GDa22TGN0DqUAtH6AiT3FAf4lSng1
|
||||
gLnMaa6+jYDmHmGHf2epRw+NU8Kb4g8iNLPP10M3x1DIlVtB2MvXgHFY5/fz4BSD
|
||||
8QweqUGaoIuAcLdJ6qJmbztmFWCWmr3uNHo2V6KxjHMMpziCa+280im6OgsdsgMu
|
||||
QO949hk+5k+PZ/WR+Ia4NylEFI6cVXpZwALAVWxC9VO1oocCAwEAAaAAMA0GCSqG
|
||||
SIb3DQEBBQUAA4IBAQCuMWhXWTpHDZTYFtkasd/b02D45RyyQTYNvw0qS67rBKnj
|
||||
52YvHKXvdjXe+TFuEzq6G7QAa7cgcdv12ffzboa8l9E4hY4OTO/lysCB1gMxgpTg
|
||||
ulDFRTQrIgCjrIm4rIGkFj47VL0ieSRhU4bf9npQydXdaYk0DHOxpLdGrs85LbZf
|
||||
lt/5Bsls9bSsh+JYcgoOPYxcgKBzhingh/FV93HQ39JHxtcqkrpZqrfNfHSVr9gT
|
||||
t+YyvUh0ctO5oGEfQj27Ewk3yt1nwgCETP55yCUhWIhJF5ATSFqm0nfeS1QYHMcU
|
||||
27Hcp+20/4D4MbZ04gi4bi6Z92DawssfKfow/B4u
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
@ -1,15 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
|
||||
dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
|
||||
GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
|
||||
AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
|
||||
C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
|
||||
KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
|
||||
FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
|
||||
Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
|
||||
M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
|
||||
20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
|
||||
I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
|
||||
ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
|
||||
rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
|
||||
MIIEowIBAAKCAQEAthbA9VN1a77nlj1YpxkqL6IL8FWhXq56MX/u3A5BlEAwCzas
|
||||
WEKfGZ2zx3uNxfrAEc56MoVipub1Y85FySJ45WYdEdguHMJceuFgwdJhbk3HBMLF
|
||||
2Rr5ATOgUmoFLA4CQFBoX0RMD8YNrbZMY3QOpQC0foCJPcUB/iVKeDWAucxprr6N
|
||||
gOYeYYd/Z6lHD41TwpviDyI0s8/XQzfHUMiVW0HYy9eAcVjn9/PgFIPxDB6pQZqg
|
||||
i4Bwt0nqomZvO2YVYJaave40ejZXorGMcwynOIJr7bzSKbo6Cx2yAy5A73j2GT7m
|
||||
T49n9ZH4hrg3KUQUjpxVelnAAsBVbEL1U7WihwIDAQABAoIBACdvHBDFJ0vTRzI5
|
||||
TOa7Q3CXZoCA+vaXUK1BqIgNqlQh5oW3LHHc07nndlTARD7ZBBmXHs2sJ2Y/5Grd
|
||||
9C0QAyCjEa6Yo7vkt8SA5MR0/Fa4D17Pk6tl9QE2ngTbIw2cZw5om4HuN46+9J1n
|
||||
OnnbW4SOd4hh69btwHW6u7r2007pQXme0AMlAxhgfIEiD6tHjqZDCtK4G0d+GSq6
|
||||
Ks/IpXckLcrluqJ2gHVB2sbBbjh1m7dHCVzBKynCwaHrWbNla8P6j3SdabP4Umcb
|
||||
tu3hP3nLigMJmA16KdkayUg05OvUTBAkReLcVZYZQ80LmM+NioB6UvwYy677h4ma
|
||||
XFihvLkCgYEA3aLOY2rve6DxvTXDJe8D91V//+5B9XBcyLP2cMEXIP+3W9Gto9tz
|
||||
vTIOcwj8MLKh22KBBMCP7OQlXFalBoG/d5Y9YKr6X2XHBcEw96Nm1HZctUPRPQdd
|
||||
+C8S5ikwTre9QMfLmLGKzgfIpDRYyijv0ZQZw+8qlNATtVrAMQGu6D0CgYEA0lI9
|
||||
zXK+4P28+SkkCUIG/Y31wF4KeM8V5s8eUYej8LV67GQIGs9XPCEIJY2cHZpn7qD8
|
||||
H90lucr90rwAHN2rLUSdq28NVL0+RTYKNBFRRvPNloLlKOR9RvorsFz5U4z48i9x
|
||||
yZ6gk+WL0ycc2oUFfihmQpHhRUiV46IB7deEXhMCgYEA1rW+3V8eC5VaOuOXXutS
|
||||
20vwCX7GVUB6ElENICRfBK/V8NSLM98IG7QffV+p+H9E/+RIetMVWveWHgMuMcSG
|
||||
ORLJ+RkKHlrZ2IBUsMKSfqb/nvbJACdf6GuqEmC6lLe5VsV3PkBY6MlvnWu8zHOm
|
||||
CFFCOKc8iBef0CPPZmpsCD0CgYAOtS+bOWX9x+C6L9VUTGi+vHmuDSWAU0L91AgT
|
||||
vX+KaraA53HlphA8pTazoZaEP3L7LgjTlZx4xKhBX2JGon3A+aZpAagV//Hl1ySZ
|
||||
hYiAhLYgy2CJHolgOEhr2eSZoicakJTNe6lRDmFbz8Vlxp2et+aGyzrMpInO1Fp8
|
||||
LnEUPwKBgExMk6THLz8CzjJhaKDrNn1fSs6jUxgK12+1cIJxb5JEgRJs4W+yFuLw
|
||||
exCE5YKPwnZNrbji+bdkk9FwHPLuf20aH5fIB0UlMMVcvLGKyxYl7BG1pShN4k8C
|
||||
kKXaClwkngJ2eSs/AsNAe9hhbEpFjAHt+3D7Sbg8EvgeCwdjnIxw
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
var spdy = exports;
|
||||
|
||||
// Exports utils
|
||||
@ -16,8 +12,9 @@ try {
|
||||
spdy.protocol.generic = require('./spdy/protocol/generic.js');
|
||||
}
|
||||
|
||||
// Only SPDY v2 is supported now
|
||||
// Supported SPDY versions
|
||||
spdy.protocol[2] = require('./spdy/protocol/v2');
|
||||
spdy.protocol[3] = require('./spdy/protocol/v3');
|
||||
|
||||
spdy.parser = require('./spdy/parser');
|
||||
|
||||
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
var parser = exports;
|
||||
|
||||
var spdy = require('../spdy'),
|
||||
@ -9,15 +5,21 @@ var spdy = require('../spdy'),
|
||||
stream = require('stream'),
|
||||
Buffer = require('buffer').Buffer;
|
||||
|
||||
var legacy = !stream.Duplex;
|
||||
|
||||
if (legacy) {
|
||||
var DuplexStream = stream;
|
||||
} else {
|
||||
var DuplexStream = stream.Duplex;
|
||||
}
|
||||
|
||||
//
|
||||
// ### function Parser (connection, deflate, inflate)
|
||||
// ### function Parser (connection)
|
||||
// #### @connection {spdy.Connection} connection
|
||||
// #### @deflate {zlib.Deflate} Deflate stream
|
||||
// #### @inflate {zlib.Inflate} Inflate stream
|
||||
// SPDY protocol frames parser's @constructor
|
||||
//
|
||||
function Parser(connection, deflate, inflate) {
|
||||
stream.Stream.call(this);
|
||||
function Parser(connection) {
|
||||
DuplexStream.call(this);
|
||||
|
||||
this.drained = true;
|
||||
this.paused = false;
|
||||
@ -26,33 +28,45 @@ function Parser(connection, deflate, inflate) {
|
||||
this.waiting = 8;
|
||||
|
||||
this.state = { type: 'frame-head' };
|
||||
this.deflate = deflate;
|
||||
this.inflate = inflate;
|
||||
this.socket = connection.socket;
|
||||
this.connection = connection;
|
||||
this.framer = null;
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
this.readable = this.writable = true;
|
||||
if (legacy) {
|
||||
this.readable = this.writable = true;
|
||||
}
|
||||
}
|
||||
util.inherits(Parser, stream.Stream);
|
||||
util.inherits(Parser, DuplexStream);
|
||||
|
||||
//
|
||||
// ### function create (connection, deflate, inflate)
|
||||
// ### function create (connection)
|
||||
// #### @connection {spdy.Connection} connection
|
||||
// #### @deflate {zlib.Deflate} Deflate stream
|
||||
// #### @inflate {zlib.Inflate} Inflate stream
|
||||
// @constructor wrapper
|
||||
//
|
||||
parser.create = function create(connection, deflate, inflate) {
|
||||
return new Parser(connection, deflate, inflate);
|
||||
parser.create = function create(connection) {
|
||||
return new Parser(connection);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function write (data)
|
||||
// ### function destroy ()
|
||||
// Just a stub.
|
||||
//
|
||||
Parser.prototype.destroy = function destroy() {
|
||||
};
|
||||
|
||||
//
|
||||
// ### function _write (data, encoding, cb)
|
||||
// #### @data {Buffer} chunk of data
|
||||
// #### @encoding {Null} encoding
|
||||
// #### @cb {Function} callback
|
||||
// Writes or buffers data to parser
|
||||
//
|
||||
Parser.prototype.write = function write(data) {
|
||||
Parser.prototype._write = function write(data, encoding, cb) {
|
||||
// Legacy compatibility
|
||||
if (!cb) cb = function() {};
|
||||
|
||||
if (data !== undefined) {
|
||||
// Buffer data
|
||||
this.buffer.push(data);
|
||||
@ -63,7 +77,7 @@ Parser.prototype.write = function write(data) {
|
||||
if (this.paused) return false;
|
||||
|
||||
// We shall not do anything until we get all expected data
|
||||
if (this.buffered < this.waiting) return;
|
||||
if (this.buffered < this.waiting) return cb();
|
||||
|
||||
// Mark parser as not drained
|
||||
if (data !== undefined) this.drained = false;
|
||||
@ -106,13 +120,16 @@ Parser.prototype.write = function write(data) {
|
||||
self.paused = false;
|
||||
|
||||
// Propagate errors
|
||||
if (err) return self.emit('error', err);
|
||||
if (err) {
|
||||
cb();
|
||||
return self.emit('error', err);
|
||||
}
|
||||
|
||||
// Set new `waiting`
|
||||
self.waiting = waiting;
|
||||
|
||||
if (self.waiting <= self.buffered) {
|
||||
self.write();
|
||||
self._write(undefined, null, cb);
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
if (self.drained) return;
|
||||
@ -121,16 +138,54 @@ Parser.prototype.write = function write(data) {
|
||||
self.drained = true;
|
||||
self.emit('drain');
|
||||
});
|
||||
|
||||
cb();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (legacy) {
|
||||
//
|
||||
// ### function write (data, encoding, cb)
|
||||
// #### @data {Buffer} chunk of data
|
||||
// #### @encoding {Null} encoding
|
||||
// #### @cb {Function} callback
|
||||
// Legacy method
|
||||
//
|
||||
Parser.prototype.write = Parser.prototype._write;
|
||||
|
||||
//
|
||||
// ### function end ()
|
||||
// Stream's end() implementation
|
||||
//
|
||||
Parser.prototype.end = function end() {
|
||||
this.emit('end');
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// ### function end ()
|
||||
// Stream's end() implementation
|
||||
// ### function createFramer (version)
|
||||
// #### @version {Number} Protocol version, either 2 or 3
|
||||
// Sets framer instance on Parser's instance
|
||||
//
|
||||
Parser.prototype.end = function end() {
|
||||
this.emit('end');
|
||||
Parser.prototype.createFramer = function createFramer(version) {
|
||||
if (spdy.protocol[version]) {
|
||||
this.emit('version', version);
|
||||
|
||||
this.framer = new spdy.protocol[version].Framer(
|
||||
spdy.utils.zwrap(this.connection._deflate),
|
||||
spdy.utils.zwrap(this.connection._inflate)
|
||||
);
|
||||
|
||||
// Propagate framer to connection
|
||||
this.connection._framer = this.framer;
|
||||
this.emit('framer', this.framer);
|
||||
} else {
|
||||
this.emit(
|
||||
'error',
|
||||
new Error('Unknown protocol version requested: ' + version)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
@ -146,16 +201,7 @@ Parser.prototype.execute = function execute(state, data, callback) {
|
||||
|
||||
// Lazily create framer
|
||||
if (!this.framer && header.control) {
|
||||
if (spdy.protocol[header.version]) {
|
||||
this.framer = new spdy.protocol[header.version].Framer(
|
||||
spdy.utils.zwrap(this.deflate),
|
||||
spdy.utils.zwrap(this.inflate)
|
||||
);
|
||||
|
||||
// Propagate framer to connection
|
||||
this.connection.framer = this.framer;
|
||||
this.emit('_framer', this.framer);
|
||||
}
|
||||
this.createFramer(header.version);
|
||||
}
|
||||
|
||||
state.type = 'frame-body';
|
||||
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
//
|
||||
// ### function parseHeader (data)
|
||||
// ### @data {Buffer} incoming data
|
||||
|
@ -0,0 +1,15 @@
|
||||
exports.dictionary = new Buffer([
|
||||
'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
|
||||
'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
|
||||
'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
|
||||
'-agent10010120020120220320420520630030130230330430530630740040140240340440',
|
||||
'5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
|
||||
'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
|
||||
'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
|
||||
'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
|
||||
'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
|
||||
'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
|
||||
'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
|
||||
'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
|
||||
'.1statusversionurl\x00'
|
||||
].join(''));
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
var framer = exports;
|
||||
|
||||
var spdy = require('../../../spdy'),
|
||||
@ -15,6 +11,7 @@ var spdy = require('../../../spdy'),
|
||||
// Framer constructor
|
||||
//
|
||||
function Framer(deflate, inflate) {
|
||||
this.version = 2;
|
||||
this.deflate = deflate;
|
||||
this.inflate = inflate;
|
||||
}
|
||||
@ -36,6 +33,8 @@ Framer.prototype.execute = function execute(header, body, callback) {
|
||||
body = body.slice(frame._offset);
|
||||
|
||||
this.inflate(body, function(err, chunks, length) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var pairs = new Buffer(length);
|
||||
for (var i = 0, offset = 0; i < chunks.length; i++) {
|
||||
chunks[i].copy(pairs, offset);
|
||||
@ -43,6 +42,7 @@ Framer.prototype.execute = function execute(header, body, callback) {
|
||||
}
|
||||
|
||||
frame.headers = protocol.parseHeaders(pairs);
|
||||
frame.url = frame.headers.url || '';
|
||||
|
||||
callback(null, frame);
|
||||
});
|
||||
@ -130,7 +130,7 @@ function headersToDict(headers, preprocess) {
|
||||
return result;
|
||||
};
|
||||
|
||||
Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
|
||||
Framer.prototype._synFrame = function _synFrame(type, id, assoc, priority, dict,
|
||||
callback) {
|
||||
// Compress headers
|
||||
this.deflate(dict, function (err, chunks, size) {
|
||||
@ -150,6 +150,8 @@ Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
|
||||
frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
|
||||
}
|
||||
|
||||
frame.writeUInt8(priority & 0x3, 16, true); // Priority
|
||||
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
chunks[i].copy(frame, offset);
|
||||
offset += chunks[i].length;
|
||||
@ -171,11 +173,11 @@ Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
|
||||
Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
|
||||
callback) {
|
||||
var dict = headersToDict(headers, function(headers) {
|
||||
headers.status = code + ' ' + reason;
|
||||
headers.version = 'HTTP/1.1';
|
||||
});
|
||||
headers.status = code + ' ' + reason;
|
||||
headers.version = 'HTTP/1.1';
|
||||
});
|
||||
|
||||
this._synFrame('SYN_REPLY', id, null, dict, callback);
|
||||
this._synFrame('SYN_REPLY', id, null, 0, dict, callback);
|
||||
};
|
||||
|
||||
//
|
||||
@ -196,7 +198,7 @@ Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
|
||||
headers.url = meta.url;
|
||||
});
|
||||
|
||||
this._synFrame('SYN_STREAM', id, assoc, dict, callback);
|
||||
this._synFrame('SYN_STREAM', id, assoc, meta.priority, dict, callback);
|
||||
};
|
||||
|
||||
//
|
||||
@ -260,23 +262,23 @@ Framer.prototype.rstFrame = function rstFrame(id, code) {
|
||||
Framer.rstCache = {};
|
||||
|
||||
//
|
||||
// ### function maxStreamsFrame (count)
|
||||
// #### @count {Number} Max Concurrent Streams count
|
||||
// ### function settingsFrame (options)
|
||||
// #### @options {Object} settings frame options
|
||||
// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS
|
||||
//
|
||||
Framer.prototype.maxStreamsFrame = function maxStreamsFrame(count) {
|
||||
Framer.prototype.settingsFrame = function settingsFrame(options) {
|
||||
var settings;
|
||||
|
||||
if (!(settings = Framer.settingsCache[count])) {
|
||||
if (!(settings = Framer.settingsCache[options.maxStreams])) {
|
||||
settings = new Buffer(20);
|
||||
|
||||
settings.writeUInt32BE(0x80020004, 0, true); // Version and type
|
||||
settings.writeUInt32BE(0x0000000C, 4, true); // length
|
||||
settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
|
||||
settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag
|
||||
settings.writeUInt32BE(count, 16, true); // 100 Streams
|
||||
settings.writeUInt32BE(options.maxStreams, 16, true);
|
||||
|
||||
Framer.settingsCache[count] = settings;
|
||||
Framer.settingsCache[options.maxStreams] = settings;
|
||||
}
|
||||
|
||||
return settings;
|
||||
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
var v2;
|
||||
|
||||
try {
|
||||
@ -12,3 +8,5 @@ try {
|
||||
module.exports = v2;
|
||||
|
||||
v2.Framer = require('./framer').Framer;
|
||||
|
||||
v2.dictionary = require('./dictionary').dictionary;
|
||||
|
@ -1,7 +1,3 @@
|
||||
/* 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/. */
|
||||
|
||||
var protocol = exports;
|
||||
|
||||
//
|
||||
@ -17,6 +13,7 @@ protocol.parseSynHead = function parseSynHead(type, flags, data) {
|
||||
return {
|
||||
type: stream ? 'SYN_STREAM' : 'SYN_REPLY',
|
||||
id: data.readUInt32BE(0, true) & 0x7fffffff,
|
||||
version: 2,
|
||||
associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0,
|
||||
priority: stream ? data[8] >> 6 : 0,
|
||||
fin: (flags & 0x01) === 0x01,
|
||||
|
180
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/dictionary.js
Normal file
180
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/dictionary.js
Normal file
@ -0,0 +1,180 @@
|
||||
exports.dictionary = new Buffer([
|
||||
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,
|
||||
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,
|
||||
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,
|
||||
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,
|
||||
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,
|
||||
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,
|
||||
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,
|
||||
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,
|
||||
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,
|
||||
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,
|
||||
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,
|
||||
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
|
||||
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,
|
||||
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,
|
||||
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,
|
||||
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,
|
||||
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,
|
||||
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,
|
||||
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,
|
||||
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,
|
||||
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,
|
||||
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
|
||||
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,
|
||||
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
|
||||
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,
|
||||
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,
|
||||
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,
|
||||
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,
|
||||
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,
|
||||
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,
|
||||
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,
|
||||
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,
|
||||
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,
|
||||
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,
|
||||
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,
|
||||
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,
|
||||
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,
|
||||
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,
|
||||
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,
|
||||
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,
|
||||
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,
|
||||
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,
|
||||
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,
|
||||
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,
|
||||
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,
|
||||
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,
|
||||
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,
|
||||
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,
|
||||
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,
|
||||
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,
|
||||
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,
|
||||
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,
|
||||
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,
|
||||
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,
|
||||
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,
|
||||
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,
|
||||
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,
|
||||
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,
|
||||
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,
|
||||
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,
|
||||
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,
|
||||
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,
|
||||
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,
|
||||
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,
|
||||
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,
|
||||
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,
|
||||
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,
|
||||
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,
|
||||
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,
|
||||
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,
|
||||
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,
|
||||
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,
|
||||
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,
|
||||
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,
|
||||
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,
|
||||
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,
|
||||
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,
|
||||
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,
|
||||
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,
|
||||
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,
|
||||
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,
|
||||
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,
|
||||
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,
|
||||
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,
|
||||
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,
|
||||
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,
|
||||
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,
|
||||
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,
|
||||
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,
|
||||
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,
|
||||
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,
|
||||
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,
|
||||
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,
|
||||
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
|
||||
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,
|
||||
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,
|
||||
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,
|
||||
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,
|
||||
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,
|
||||
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,
|
||||
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,
|
||||
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e
|
||||
]);
|
335
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/framer.js
Normal file
335
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/framer.js
Normal file
@ -0,0 +1,335 @@
|
||||
var framer = exports;
|
||||
|
||||
var spdy = require('../../../spdy'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
protocol = require('./');
|
||||
|
||||
//
|
||||
// ### function Framer (deflate, inflate)
|
||||
// #### @deflate {zlib.Deflate} Deflate stream
|
||||
// #### @inflate {zlib.Inflate} Inflate stream
|
||||
// Framer constructor
|
||||
//
|
||||
function Framer(deflate, inflate) {
|
||||
this.version = 3;
|
||||
this.deflate = deflate;
|
||||
this.inflate = inflate;
|
||||
}
|
||||
exports.Framer = Framer;
|
||||
|
||||
//
|
||||
// ### function execute (header, body, callback)
|
||||
// #### @header {Object} Frame headers
|
||||
// #### @body {Buffer} Frame's body
|
||||
// #### @callback {Function} Continuation callback
|
||||
// Parse frame (decompress data and create streams)
|
||||
//
|
||||
Framer.prototype.execute = function execute(header, body, callback) {
|
||||
// SYN_STREAM or SYN_REPLY
|
||||
if (header.type === 0x01 || header.type === 0x02) {
|
||||
var frame = protocol.parseSynHead(header.type, header.flags, body);
|
||||
|
||||
body = body.slice(frame._offset);
|
||||
|
||||
this.inflate(body, function(err, chunks, length) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var pairs = new Buffer(length);
|
||||
for (var i = 0, offset = 0; i < chunks.length; i++) {
|
||||
chunks[i].copy(pairs, offset);
|
||||
offset += chunks[i].length;
|
||||
}
|
||||
|
||||
frame.headers = protocol.parseHeaders(pairs);
|
||||
frame.url = frame.headers.path || '';
|
||||
|
||||
callback(null, frame);
|
||||
});
|
||||
// RST_STREAM
|
||||
} else if (header.type === 0x03) {
|
||||
callback(null, protocol.parseRst(body));
|
||||
// SETTINGS
|
||||
} else if (header.type === 0x04) {
|
||||
callback(null, protocol.parseSettings(body));
|
||||
} else if (header.type === 0x05) {
|
||||
callback(null, { type: 'NOOP' });
|
||||
// PING
|
||||
} else if (header.type === 0x06) {
|
||||
callback(null, { type: 'PING', pingId: body });
|
||||
// GOAWAY
|
||||
} else if (header.type === 0x07) {
|
||||
callback(null, protocol.parseGoaway(body));
|
||||
} else if (header.type === 0x09) {
|
||||
callback(null, protocol.parseWindowUpdate(body));
|
||||
} else {
|
||||
callback(null, { type: 'unknown: ' + header.type, body: body });
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// internal, converts object into spdy dictionary
|
||||
//
|
||||
function headersToDict(headers, preprocess) {
|
||||
function stringify(value) {
|
||||
if (value !== undefined) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join('\x00');
|
||||
} else if (typeof value === 'string') {
|
||||
return value;
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Lower case of all headers keys
|
||||
var loweredHeaders = {};
|
||||
Object.keys(headers || {}).map(function(key) {
|
||||
loweredHeaders[key.toLowerCase()] = headers[key];
|
||||
});
|
||||
|
||||
// Allow outer code to add custom headers or remove something
|
||||
if (preprocess) preprocess(loweredHeaders);
|
||||
|
||||
// Transform object into kv pairs
|
||||
var len = 4,
|
||||
pairs = Object.keys(loweredHeaders).filter(function(key) {
|
||||
var lkey = key.toLowerCase();
|
||||
return lkey !== 'connection' && lkey !== 'keep-alive' &&
|
||||
lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
|
||||
}).map(function(key) {
|
||||
var klen = Buffer.byteLength(key),
|
||||
value = stringify(loweredHeaders[key]),
|
||||
vlen = Buffer.byteLength(value);
|
||||
|
||||
len += 8 + klen + vlen;
|
||||
return [klen, key, vlen, value];
|
||||
}),
|
||||
result = new Buffer(len);
|
||||
|
||||
result.writeUInt32BE(pairs.length, 0, true);
|
||||
|
||||
var offset = 4;
|
||||
pairs.forEach(function(pair) {
|
||||
// Write key length
|
||||
result.writeUInt32BE(pair[0], offset, true);
|
||||
// Write key
|
||||
result.write(pair[1], offset + 4);
|
||||
|
||||
offset += pair[0] + 4;
|
||||
|
||||
// Write value length
|
||||
result.writeUInt32BE(pair[2], offset, true);
|
||||
// Write value
|
||||
result.write(pair[3], offset + 4);
|
||||
|
||||
offset += pair[2] + 4;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Framer.prototype._synFrame = function _synFrame(type, id, assoc, priority, dict,
|
||||
callback) {
|
||||
// Compress headers
|
||||
this.deflate(dict, function (err, chunks, size) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var offset = type === 'SYN_STREAM' ? 18 : 12,
|
||||
total = (type === 'SYN_STREAM' ? 10 : 4) + size,
|
||||
frame = new Buffer(offset + size);;
|
||||
|
||||
frame.writeUInt16BE(0x8003, 0, true); // Control + Version
|
||||
frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
|
||||
frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
|
||||
frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
|
||||
|
||||
if (type === 'SYN_STREAM') {
|
||||
frame[4] = 2;
|
||||
frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
|
||||
}
|
||||
|
||||
frame.writeUInt8(priority & 0x7, 16, true); // Priority
|
||||
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
chunks[i].copy(frame, offset);
|
||||
offset += chunks[i].length;
|
||||
}
|
||||
|
||||
callback(null, frame);
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### function replyFrame (id, code, reason, headers, callback)
|
||||
// #### @id {Number} Stream ID
|
||||
// #### @code {Number} HTTP Status Code
|
||||
// #### @reason {String} (optional)
|
||||
// #### @headers {Object|Array} (optional) HTTP headers
|
||||
// #### @callback {Function} Continuation function
|
||||
// Sends SYN_REPLY frame
|
||||
//
|
||||
Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
|
||||
callback) {
|
||||
var dict = headersToDict(headers, function(headers) {
|
||||
headers[':status'] = code + ' ' + reason;
|
||||
headers[':version'] = 'HTTP/1.1';
|
||||
});
|
||||
|
||||
this._synFrame('SYN_REPLY', id, null, 0, dict, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function streamFrame (id, assoc, headers, callback)
|
||||
// #### @id {Number} stream id
|
||||
// #### @assoc {Number} associated stream id
|
||||
// #### @meta {Object} meta headers ( method, scheme, url, version )
|
||||
// #### @headers {Object} stream headers
|
||||
// #### @callback {Function} continuation callback
|
||||
// Create SYN_STREAM frame
|
||||
// (needed for server push and testing)
|
||||
//
|
||||
Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
|
||||
callback) {
|
||||
var dict = headersToDict(headers, function(headers) {
|
||||
headers[':status'] = 200;
|
||||
headers[':version'] = meta.version || 'HTTP/1.1';
|
||||
headers[':path'] = meta.path;
|
||||
headers[':scheme'] = meta.scheme || 'https';
|
||||
headers[':host'] = meta.host;
|
||||
});
|
||||
|
||||
this._synFrame('SYN_STREAM', id, assoc, meta.priority, dict, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function dataFrame (id, fin, data)
|
||||
// #### @id {Number} Stream id
|
||||
// #### @fin {Bool} Is this data frame last frame
|
||||
// #### @data {Buffer} Response data
|
||||
// Sends DATA frame
|
||||
//
|
||||
Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
|
||||
if (!fin && !data.length) return [];
|
||||
|
||||
var frame = new Buffer(8 + data.length);
|
||||
|
||||
frame.writeUInt32BE(id & 0x7fffffff, 0, true);
|
||||
frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
|
||||
frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
|
||||
|
||||
if (data.length) data.copy(frame, 8);
|
||||
|
||||
return frame;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function pingFrame (id)
|
||||
// #### @id {Buffer} Ping ID
|
||||
// Sends PING frame
|
||||
//
|
||||
Framer.prototype.pingFrame = function pingFrame(id) {
|
||||
var header = new Buffer(12);
|
||||
|
||||
header.writeUInt32BE(0x80030006, 0, true); // Version and type
|
||||
header.writeUInt32BE(0x00000004, 4, true); // Length
|
||||
id.copy(header, 8, 0, 4); // ID
|
||||
|
||||
return header;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function rstFrame (id, code)
|
||||
// #### @id {Number} Stream ID
|
||||
// #### @code {NUmber} RST Code
|
||||
// Sends PING frame
|
||||
//
|
||||
Framer.prototype.rstFrame = function rstFrame(id, code) {
|
||||
var header;
|
||||
|
||||
if (!(header = Framer.rstCache[code])) {
|
||||
header = new Buffer(16);
|
||||
|
||||
header.writeUInt32BE(0x80030003, 0, true); // Version and type
|
||||
header.writeUInt32BE(0x00000008, 4, true); // Length
|
||||
header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
|
||||
header.writeUInt32BE(code, 12, true); // Status Code
|
||||
|
||||
Framer.rstCache[code] = header;
|
||||
}
|
||||
|
||||
return header;
|
||||
};
|
||||
Framer.rstCache = {};
|
||||
|
||||
//
|
||||
// ### function settingsFrame (options)
|
||||
// #### @options {Object} settings frame options
|
||||
// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS and initial window
|
||||
//
|
||||
Framer.prototype.settingsFrame = function settingsFrame(options) {
|
||||
var settings,
|
||||
key = options.maxStreams + ':' + options.windowSize;
|
||||
|
||||
if (!(settings = Framer.settingsCache[key])) {
|
||||
settings = new Buffer(28);
|
||||
|
||||
settings.writeUInt32BE(0x80030004, 0, true); // Version and type
|
||||
settings.writeUInt32BE((4 + 8 * 2) & 0x00FFFFFF, 4, true); // length
|
||||
settings.writeUInt32BE(0x00000002, 8, true); // Count of entries
|
||||
|
||||
settings.writeUInt32BE(0x01000004, 12, true); // Entry ID and Persist flag
|
||||
settings.writeUInt32BE(options.maxStreams & 0x7fffffff, 16, true);
|
||||
|
||||
settings.writeUInt32BE(0x01000007, 20, true); // Entry ID and Persist flag
|
||||
settings.writeUInt32BE(options.windowSize & 0x7fffffff, 24, true);
|
||||
|
||||
Framer.settingsCache[key] = settings;
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
Framer.settingsCache = {};
|
||||
|
||||
//
|
||||
// ### function windowSizeFrame (size)
|
||||
// #### @size {Number} data transfer window size
|
||||
// Sends SETTINGS frame with window size
|
||||
//
|
||||
Framer.prototype.windowSizeFrame = function windowSizeFrame(size) {
|
||||
var settings;
|
||||
|
||||
if (!(settings = Framer.windowSizeCache[size])) {
|
||||
settings = new Buffer(20);
|
||||
|
||||
settings.writeUInt32BE(0x80030004, 0, true); // Version and type
|
||||
settings.writeUInt32BE((4 + 8) & 0x00FFFFFF, 4, true); // length
|
||||
settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
|
||||
|
||||
settings.writeUInt32BE(0x01000007, 12, true); // Entry ID and Persist flag
|
||||
settings.writeUInt32BE(size & 0x7fffffff, 16, true); // Window Size (KB)
|
||||
|
||||
Framer.windowSizeCache[size] = settings;
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
Framer.windowSizeCache = {};
|
||||
|
||||
//
|
||||
// ### function windowUpdateFrame (id)
|
||||
// #### @id {Buffer} WindowUpdate ID
|
||||
// Sends WINDOW_UPDATE frame
|
||||
//
|
||||
Framer.prototype.windowUpdateFrame = function windowUpdateFrame(id, delta) {
|
||||
var header = new Buffer(16);
|
||||
|
||||
header.writeUInt32BE(0x80030009, 0, true); // Version and type
|
||||
header.writeUInt32BE(0x00000008, 4, true); // Length
|
||||
header.writeUInt32BE(id & 0x7fffffff, 8, true); // ID
|
||||
header.writeUInt32BE(delta & 0x7fffffff, 12, true); // delta
|
||||
|
||||
return header;
|
||||
};
|
12
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/index.js
Normal file
12
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
var v3;
|
||||
|
||||
try {
|
||||
v3 = require('./protocol.node');
|
||||
} catch (e) {
|
||||
v3 = require('./protocol.js');
|
||||
}
|
||||
module.exports = v3;
|
||||
|
||||
v3.Framer = require('./framer').Framer;
|
||||
|
||||
v3.dictionary = require('./dictionary').dictionary;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user