From b984c50fec2115ca329ae74eaabeb3fbd997af64 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Tue, 1 Dec 2015 08:00:58 -0600 Subject: [PATCH] Bug 1218029 - Adds ScriptLoadHandler and implements OnIncrementalData callback. r=djvj --HG-- extra : commitid : 7TyrlgQ2f2Z --- dom/base/nsScriptLoader.cpp | 77 +++++++++--------- dom/base/nsScriptLoader.h | 30 ++++++- js/xpconnect/loader/mozJSSubScriptLoader.cpp | 20 +++++ netwerk/base/nsIIncrementalStreamLoader.idl | 29 ++++++- netwerk/base/nsIncrementalStreamLoader.cpp | 73 +++++++++++++++-- netwerk/base/nsIncrementalStreamLoader.h | 3 + netwerk/test/unit/test_bug1218029.js | 82 ++++++++++++++++++++ netwerk/test/unit/xpcshell.ini | 1 + 8 files changed, 271 insertions(+), 44 deletions(-) create mode 100644 netwerk/test/unit/test_bug1218029.js diff --git a/dom/base/nsScriptLoader.cpp b/dom/base/nsScriptLoader.cpp index e8a5699a16ac..7162073ee4e7 100644 --- a/dom/base/nsScriptLoader.cpp +++ b/dom/base/nsScriptLoader.cpp @@ -160,7 +160,7 @@ nsScriptLoader::~nsScriptLoader() } } -NS_IMPL_ISUPPORTS(nsScriptLoader, nsIIncrementalStreamLoaderObserver) +NS_IMPL_ISUPPORTS(nsScriptLoader, nsISupports) // Helper method for checking if the script element is an event-handler // This means that it has both a for-attribute and a event-attribute. @@ -269,37 +269,6 @@ nsScriptLoader::ShouldLoadScript(nsIDocument* aDocument, return NS_OK; } -class ContextMediator : public nsIIncrementalStreamLoaderObserver -{ -public: - explicit ContextMediator(nsScriptLoader *aScriptLoader, nsISupports *aContext) - : mScriptLoader(aScriptLoader) - , mContext(aContext) {} - - NS_DECL_ISUPPORTS - NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER - -private: - virtual ~ContextMediator() {} - RefPtr mScriptLoader; - nsCOMPtr mContext; -}; - -NS_IMPL_ISUPPORTS(ContextMediator, nsIIncrementalStreamLoaderObserver) - -NS_IMETHODIMP -ContextMediator::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, - nsISupports* aContext, - nsresult aStatus, - uint32_t aStringLen, - const uint8_t* aString) -{ - // pass arguments through except for the aContext, - // we have to mediate and use mContext instead. - return mScriptLoader->OnStreamComplete(aLoader, mContext, aStatus, - aStringLen, aString); -} - nsresult nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType, bool aScriptFromHead) @@ -383,10 +352,10 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType, timedChannel->SetInitiatorType(NS_LITERAL_STRING("script")); } - RefPtr mediator = new ContextMediator(this, aRequest); + RefPtr handler = new nsScriptLoadHandler(this, aRequest); nsCOMPtr loader; - rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), mediator); + rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler); NS_ENSURE_SUCCESS(rv, rv); return channel->AsyncOpen2(loader); @@ -1438,7 +1407,7 @@ nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData, return rv; } -NS_IMETHODIMP +nsresult nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, nsISupports* aContext, nsresult aStatus, @@ -1739,3 +1708,41 @@ nsScriptLoader::MaybeRemovedDeferRequests() } return false; } + +////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////// + +nsScriptLoadHandler::nsScriptLoadHandler(nsScriptLoader *aScriptLoader, + nsScriptLoadRequest *aRequest) + : mScriptLoader(aScriptLoader), + mRequest(aRequest) +{} + +nsScriptLoadHandler::~nsScriptLoadHandler() +{} + +NS_IMPL_ISUPPORTS(nsScriptLoadHandler, nsIIncrementalStreamLoaderObserver) + +NS_IMETHODIMP +nsScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + uint32_t aDataLength, + const uint8_t* aData, + uint32_t *aConsumedLength) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + nsresult aStatus, + uint32_t aStringLen, + const uint8_t* aString) +{ + // pass arguments through except for the aContext, + // we have to mediate and use mRequest instead. + return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, + aStringLen, aString); +} diff --git a/dom/base/nsScriptLoader.h b/dom/base/nsScriptLoader.h index b3f0afb595ab..8261c49cb0cd 100644 --- a/dom/base/nsScriptLoader.h +++ b/dom/base/nsScriptLoader.h @@ -195,7 +195,7 @@ public: // Script loader implementation ////////////////////////////////////////////////////////////// -class nsScriptLoader final : public nsIIncrementalStreamLoaderObserver +class nsScriptLoader final : public nsISupports { class MOZ_STACK_CLASS AutoCurrentScriptUpdater { @@ -223,7 +223,6 @@ public: explicit nsScriptLoader(nsIDocument* aDocument); NS_DECL_ISUPPORTS - NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER /** * The loader maintains a weak reference to the document with @@ -342,6 +341,17 @@ public: nsIDocument* aDocument, char16_t*& aBufOut, size_t& aLengthOut); + /** + * Handle the completion of a stream. This is called by the + * nsScriptLoadHandler object which observes the IncrementalStreamLoader + * loading the script. + */ + nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + nsresult aStatus, + uint32_t aStringLen, + const uint8_t* aString); + /** * Processes any pending requests that are ready for processing. */ @@ -538,6 +548,22 @@ private: bool mBlockingDOMContentLoaded; }; +class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver +{ +public: + explicit nsScriptLoadHandler(nsScriptLoader* aScriptLoader, + nsScriptLoadRequest *aRequest); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER + +private: + virtual ~nsScriptLoadHandler(); + + RefPtr mScriptLoader; + RefPtr mRequest; +}; + class nsAutoScriptLoaderDisabler { public: diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp index a5a255d56cd5..67b27dcac0e1 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -325,6 +325,16 @@ class MOZ_STACK_CLASS AutoRejectPromise nsCOMPtr mGlobalObject; }; +NS_IMETHODIMP +AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + uint32_t aDataLength, + const uint8_t* aData, + uint32_t *aConsumedData) +{ + return NS_OK; +} + NS_IMETHODIMP AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, nsISupports* aContext, @@ -769,6 +779,16 @@ NotifyPrecompilationCompleteRunnable::Run(void) return NS_OK; } +NS_IMETHODIMP +ScriptPrecompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + uint32_t aDataLength, + const uint8_t* aData, + uint32_t *aConsumedData) +{ + return NS_OK; +} + NS_IMETHODIMP ScriptPrecompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, nsISupports* aContext, diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl index 1e4af9a99d60..60aa9cfef55c 100644 --- a/netwerk/base/nsIIncrementalStreamLoader.idl +++ b/netwerk/base/nsIIncrementalStreamLoader.idl @@ -8,9 +8,36 @@ interface nsIRequest; interface nsIIncrementalStreamLoader; -[scriptable, uuid(2143eaad-674e-4613-87f8-359d4c40f590)] +[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)] interface nsIIncrementalStreamLoaderObserver : nsISupports { + /** + * Called when new data has arrived on the stream. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param dataLength the length of the new data received + * @param data the contents of the new data received. + * + * This method will always be called asynchronously by the + * nsIIncrementalStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to not accumulate all or portional of the data in + * the internal buffer, the consumedLength shall be set to the value of + * the dataLength or less. By default the consumedLength value is assumed 0. + * The data and dataLength reflect the non-consumed data and will be + * accumulated if consumedLength is not set. + * + * In comparison with onStreamComplete(), the data buffer cannot be + * adopted if this method returns NS_SUCCESS_ADOPTED_DATA. + */ + void onIncrementalData(in nsIIncrementalStreamLoader loader, + in nsISupports ctxt, + in unsigned long dataLength, + [const,array,size_is(dataLength)] in octet data, + inout unsigned long consumedLength); + /** * Called when the entire stream has been loaded. * diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp index 49726625479d..4f4e144b8f0e 100644 --- a/netwerk/base/nsIncrementalStreamLoader.cpp +++ b/netwerk/base/nsIncrementalStreamLoader.cpp @@ -12,7 +12,7 @@ #include nsIncrementalStreamLoader::nsIncrementalStreamLoader() - : mData() + : mData(), mBytesConsumed(0) { } @@ -49,7 +49,7 @@ NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader, NS_IMETHODIMP nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) { - *aNumBytes = mData.length(); + *aNumBytes = mBytesConsumed + mData.length(); return NS_OK; } @@ -121,11 +121,66 @@ nsIncrementalStreamLoader::WriteSegmentFun(nsIInputStream *inStr, { nsIncrementalStreamLoader *self = (nsIncrementalStreamLoader *) closure; - if (!self->mData.append(fromSegment, count)) { - self->mData.clearAndFree(); - return NS_ERROR_OUT_OF_MEMORY; + const uint8_t *data = reinterpret_cast(fromSegment); + uint32_t consumedCount = 0; + nsresult rv; + if (self->mData.empty()) { + // Shortcut when observer wants to keep the listener's buffer empty. + rv = self->mObserver->OnIncrementalData(self, self->mContext, + count, data, &consumedCount); + + if (rv != NS_OK) { + return rv; + } + + if (consumedCount > count) { + return NS_ERROR_INVALID_ARG; + } + + if (consumedCount < count) { + if (!self->mData.append(fromSegment + consumedCount, + count - consumedCount)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; + } + } + } else { + // We have some non-consumed data from previous OnIncrementalData call, + // appending new data and reporting combined data. + if (!self->mData.append(fromSegment, count)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; + } + size_t length = self->mData.length(); + uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length; + uint8_t* elems = self->mData.extractRawBuffer(); + + rv = self->mObserver->OnIncrementalData(self, self->mContext, + reportCount, elems, &consumedCount); + + // We still own elems, freeing its memory when exiting scope. + if (rv != NS_OK) { + free(elems); + return rv; + } + + if (consumedCount > reportCount) { + free(elems); + return NS_ERROR_INVALID_ARG; + } + + if (consumedCount == length) { + free(elems); // good case -- fully consumed data + } else { + // Adopting elems back (at least its portion). + self->mData.replaceRawBuffer(elems, length); + if (consumedCount > 0) { + self->mData.erase(self->mData.begin() + consumedCount); + } + } } + self->mBytesConsumed += consumedCount; *writeCount = count; return NS_OK; @@ -136,8 +191,14 @@ nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctx nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) { + if (mObserver) { + // provide nsIIncrementalStreamLoader::request during call to OnStreamComplete + mRequest = request; + } uint32_t countRead; - return inStr->ReadSegments(WriteSegmentFun, this, count, &countRead); + nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead); + mRequest = 0; + return rv; } void diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h index 40fa0721daef..988e87d820fa 100644 --- a/netwerk/base/nsIncrementalStreamLoader.h +++ b/netwerk/base/nsIncrementalStreamLoader.h @@ -46,6 +46,9 @@ protected: // Buffer to accumulate incoming data. We preallocate if contentSize is // available. mozilla::Vector mData; + + // Number of consumed bytes from the mData. + size_t mBytesConsumed; }; #endif // nsIncrementalStreamLoader_h__ diff --git a/netwerk/test/unit/test_bug1218029.js b/netwerk/test/unit/test_bug1218029.js new file mode 100644 index 000000000000..cbab52797e84 --- /dev/null +++ b/netwerk/test/unit/test_bug1218029.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var tests = [ + {data: '', chunks: [], status: Cr.NS_OK, consume: [], + dataChunks: ['']}, + {data: 'TWO-PARTS', chunks: [4, 5], status: Cr.NS_OK, consume: [4, 5], + dataChunks: ['TWO-', 'PARTS', '']}, + {data: 'TWO-PARTS', chunks: [4, 5], status: Cr.NS_OK, consume: [0, 0], + dataChunks: ['TWO-', 'TWO-PARTS', 'TWO-PARTS']}, + {data: '3-PARTS', chunks: [1, 1, 5], status: Cr.NS_OK, consume: [0, 2, 5], + dataChunks: ['3', '3-', 'PARTS', '']}, + {data: 'ALL-AT-ONCE', chunks: [11], status: Cr.NS_OK, consume: [0], + dataChunks: ['ALL-AT-ONCE', 'ALL-AT-ONCE']}, + {data: 'ALL-AT-ONCE', chunks: [11], status: Cr.NS_OK, consume: [11], + dataChunks: ['ALL-AT-ONCE', '']}, + {data: 'ERROR', chunks: [1], status: Cr.NS_ERROR_OUT_OF_MEMORY, consume: [0], + dataChunks: ['E', 'E']} +]; + +/** + * @typedef TestData + * @property {string} data - data for the test. + * @property {Array} chunks - lengths of the chunks that are incrementally sent + * to the loader. + * @property {number} status - final status sent on onStopRequest. + * @property {Array} consume - lengths of consumed data that is reported at + * the onIncrementalData callback. + * @property {Array} dataChunks - data chunks that are reported at the + * onIncrementalData and onStreamComplete callbacks. + */ + +function execute_test(test) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); + stream.data = test.data; + + let channel = { + contentLength: -1, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel]) + }; + + let chunkIndex = 0; + + let observer = { + onStreamComplete: function(loader, context, status, length, data) { + equal(chunkIndex, test.dataChunks.length - 1); + var expectedChunk = test.dataChunks[chunkIndex]; + equal(length, expectedChunk.length); + equal(String.fromCharCode.apply(null, data), expectedChunk); + + equal(status, test.status); + }, + onIncrementalData: function (loader, context, length, data, consumed) { + ok(chunkIndex < test.dataChunks.length - 1); + var expectedChunk = test.dataChunks[chunkIndex]; + equal(length, expectedChunk.length); + equal(String.fromCharCode.apply(null, data), expectedChunk); + + consumed.value = test.consume[chunkIndex]; + chunkIndex++; + }, + QueryInterface: + XPCOMUtils.generateQI([Ci.nsIIncrementalStreamLoaderObserver]) + }; + + let listener = Cc["@mozilla.org/network/incremental-stream-loader;1"] + .createInstance(Ci.nsIIncrementalStreamLoader); + listener.init(observer); + + listener.onStartRequest(channel, null); + var offset = 0; + test.chunks.forEach(function (chunkLength) { + listener.onDataAvailable(channel, null, stream, offset, chunkLength); + offset += chunkLength; + }); + listener.onStopRequest(channel, null, test.status); +} + +function run_test() { + tests.forEach(execute_test); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 941f4620e7be..09cf4de13b37 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -169,6 +169,7 @@ skip-if = os == "android" skip-if = bits != 32 [test_bug935499.js] [test_bug1064258.js] +[test_bug1218029.js] [test_udpsocket.js] [test_doomentry.js] [test_cacheflags.js]