Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | // vim: ft=cpp tw=78 sw=2 et ts=2 |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | /* |
michael@0 | 8 | * A class that handles loading and evaluation of <script> elements. |
michael@0 | 9 | */ |
michael@0 | 10 | |
michael@0 | 11 | #include "nsScriptLoader.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "jsapi.h" |
michael@0 | 14 | #include "jsfriendapi.h" |
michael@0 | 15 | #include "nsIUnicodeDecoder.h" |
michael@0 | 16 | #include "nsIContent.h" |
michael@0 | 17 | #include "nsJSUtils.h" |
michael@0 | 18 | #include "mozilla/dom/ScriptSettings.h" |
michael@0 | 19 | #include "mozilla/dom/Element.h" |
michael@0 | 20 | #include "nsGkAtoms.h" |
michael@0 | 21 | #include "nsNetUtil.h" |
michael@0 | 22 | #include "nsIJSRuntimeService.h" |
michael@0 | 23 | #include "nsIScriptGlobalObject.h" |
michael@0 | 24 | #include "nsIScriptContext.h" |
michael@0 | 25 | #include "nsIScriptSecurityManager.h" |
michael@0 | 26 | #include "nsIPrincipal.h" |
michael@0 | 27 | #include "nsJSPrincipals.h" |
michael@0 | 28 | #include "nsContentPolicyUtils.h" |
michael@0 | 29 | #include "nsIHttpChannel.h" |
michael@0 | 30 | #include "nsIHttpChannelInternal.h" |
michael@0 | 31 | #include "nsITimedChannel.h" |
michael@0 | 32 | #include "nsIScriptElement.h" |
michael@0 | 33 | #include "nsIDOMHTMLScriptElement.h" |
michael@0 | 34 | #include "nsIDocShell.h" |
michael@0 | 35 | #include "nsContentUtils.h" |
michael@0 | 36 | #include "nsCxPusher.h" |
michael@0 | 37 | #include "nsUnicharUtils.h" |
michael@0 | 38 | #include "nsAutoPtr.h" |
michael@0 | 39 | #include "nsIXPConnect.h" |
michael@0 | 40 | #include "nsError.h" |
michael@0 | 41 | #include "nsThreadUtils.h" |
michael@0 | 42 | #include "nsDocShellCID.h" |
michael@0 | 43 | #include "nsIContentSecurityPolicy.h" |
michael@0 | 44 | #include "prlog.h" |
michael@0 | 45 | #include "nsIChannelPolicy.h" |
michael@0 | 46 | #include "nsChannelPolicy.h" |
michael@0 | 47 | #include "nsCRT.h" |
michael@0 | 48 | #include "nsContentCreatorFunctions.h" |
michael@0 | 49 | #include "nsCrossSiteListenerProxy.h" |
michael@0 | 50 | #include "nsSandboxFlags.h" |
michael@0 | 51 | #include "nsContentTypeParser.h" |
michael@0 | 52 | #include "nsINetworkSeer.h" |
michael@0 | 53 | #include "mozilla/dom/EncodingUtils.h" |
michael@0 | 54 | |
michael@0 | 55 | #include "mozilla/CORSMode.h" |
michael@0 | 56 | #include "mozilla/Attributes.h" |
michael@0 | 57 | #include "mozilla/unused.h" |
michael@0 | 58 | |
michael@0 | 59 | #ifdef PR_LOGGING |
michael@0 | 60 | static PRLogModuleInfo* gCspPRLog; |
michael@0 | 61 | #endif |
michael@0 | 62 | |
michael@0 | 63 | using namespace mozilla; |
michael@0 | 64 | using namespace mozilla::dom; |
michael@0 | 65 | |
michael@0 | 66 | ////////////////////////////////////////////////////////////// |
michael@0 | 67 | // Per-request data structure |
michael@0 | 68 | ////////////////////////////////////////////////////////////// |
michael@0 | 69 | |
michael@0 | 70 | class nsScriptLoadRequest MOZ_FINAL : public nsISupports { |
michael@0 | 71 | public: |
michael@0 | 72 | nsScriptLoadRequest(nsIScriptElement* aElement, |
michael@0 | 73 | uint32_t aVersion, |
michael@0 | 74 | CORSMode aCORSMode) |
michael@0 | 75 | : mElement(aElement), |
michael@0 | 76 | mLoading(true), |
michael@0 | 77 | mIsInline(true), |
michael@0 | 78 | mHasSourceMapURL(false), |
michael@0 | 79 | mScriptTextBuf(nullptr), |
michael@0 | 80 | mScriptTextLength(0), |
michael@0 | 81 | mJSVersion(aVersion), |
michael@0 | 82 | mLineNo(1), |
michael@0 | 83 | mCORSMode(aCORSMode) |
michael@0 | 84 | { |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | ~nsScriptLoadRequest() |
michael@0 | 88 | { |
michael@0 | 89 | if (mScriptTextBuf) { |
michael@0 | 90 | js_free(mScriptTextBuf); |
michael@0 | 91 | } |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | NS_DECL_THREADSAFE_ISUPPORTS |
michael@0 | 95 | |
michael@0 | 96 | void FireScriptAvailable(nsresult aResult) |
michael@0 | 97 | { |
michael@0 | 98 | mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo); |
michael@0 | 99 | } |
michael@0 | 100 | void FireScriptEvaluated(nsresult aResult) |
michael@0 | 101 | { |
michael@0 | 102 | mElement->ScriptEvaluated(aResult, mElement, mIsInline); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | bool IsPreload() |
michael@0 | 106 | { |
michael@0 | 107 | return mElement == nullptr; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | nsCOMPtr<nsIScriptElement> mElement; |
michael@0 | 111 | bool mLoading; // Are we still waiting for a load to complete? |
michael@0 | 112 | bool mIsInline; // Is the script inline or loaded? |
michael@0 | 113 | bool mHasSourceMapURL; // Does the HTTP header have a source map url? |
michael@0 | 114 | nsString mSourceMapURL; // Holds source map url for loaded scripts |
michael@0 | 115 | jschar* mScriptTextBuf; // Holds script text for non-inline scripts. Don't |
michael@0 | 116 | size_t mScriptTextLength; // use nsString so we can give ownership to jsapi. |
michael@0 | 117 | uint32_t mJSVersion; |
michael@0 | 118 | nsCOMPtr<nsIURI> mURI; |
michael@0 | 119 | nsCOMPtr<nsIPrincipal> mOriginPrincipal; |
michael@0 | 120 | nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing. |
michael@0 | 121 | int32_t mLineNo; |
michael@0 | 122 | const CORSMode mCORSMode; |
michael@0 | 123 | }; |
michael@0 | 124 | |
michael@0 | 125 | // The nsScriptLoadRequest is passed as the context to necko, and thus |
michael@0 | 126 | // it needs to be threadsafe. Necko won't do anything with this |
michael@0 | 127 | // context, but it will AddRef and Release it on other threads. |
michael@0 | 128 | NS_IMPL_ISUPPORTS0(nsScriptLoadRequest) |
michael@0 | 129 | |
michael@0 | 130 | ////////////////////////////////////////////////////////////// |
michael@0 | 131 | // |
michael@0 | 132 | ////////////////////////////////////////////////////////////// |
michael@0 | 133 | |
michael@0 | 134 | nsScriptLoader::nsScriptLoader(nsIDocument *aDocument) |
michael@0 | 135 | : mDocument(aDocument), |
michael@0 | 136 | mBlockerCount(0), |
michael@0 | 137 | mEnabled(true), |
michael@0 | 138 | mDeferEnabled(false), |
michael@0 | 139 | mDocumentParsingDone(false), |
michael@0 | 140 | mBlockingDOMContentLoaded(false) |
michael@0 | 141 | { |
michael@0 | 142 | // enable logging for CSP |
michael@0 | 143 | #ifdef PR_LOGGING |
michael@0 | 144 | if (!gCspPRLog) |
michael@0 | 145 | gCspPRLog = PR_NewLogModule("CSP"); |
michael@0 | 146 | #endif |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | nsScriptLoader::~nsScriptLoader() |
michael@0 | 150 | { |
michael@0 | 151 | mObservers.Clear(); |
michael@0 | 152 | |
michael@0 | 153 | if (mParserBlockingRequest) { |
michael@0 | 154 | mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT); |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | for (uint32_t i = 0; i < mXSLTRequests.Length(); i++) { |
michael@0 | 158 | mXSLTRequests[i]->FireScriptAvailable(NS_ERROR_ABORT); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | for (uint32_t i = 0; i < mDeferRequests.Length(); i++) { |
michael@0 | 162 | mDeferRequests[i]->FireScriptAvailable(NS_ERROR_ABORT); |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | for (uint32_t i = 0; i < mAsyncRequests.Length(); i++) { |
michael@0 | 166 | mAsyncRequests[i]->FireScriptAvailable(NS_ERROR_ABORT); |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | for (uint32_t i = 0; i < mNonAsyncExternalScriptInsertedRequests.Length(); i++) { |
michael@0 | 170 | mNonAsyncExternalScriptInsertedRequests[i]->FireScriptAvailable(NS_ERROR_ABORT); |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | // Unblock the kids, in case any of them moved to a different document |
michael@0 | 174 | // subtree in the meantime and therefore aren't actually going away. |
michael@0 | 175 | for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) { |
michael@0 | 176 | mPendingChildLoaders[j]->RemoveExecuteBlocker(); |
michael@0 | 177 | } |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | NS_IMPL_ISUPPORTS(nsScriptLoader, nsIStreamLoaderObserver) |
michael@0 | 181 | |
michael@0 | 182 | // Helper method for checking if the script element is an event-handler |
michael@0 | 183 | // This means that it has both a for-attribute and a event-attribute. |
michael@0 | 184 | // Also, if the for-attribute has a value that matches "\s*window\s*", |
michael@0 | 185 | // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an |
michael@0 | 186 | // eventhandler. (both matches are case insensitive). |
michael@0 | 187 | // This is how IE seems to filter out a window's onload handler from a |
michael@0 | 188 | // <script for=... event=...> element. |
michael@0 | 189 | |
michael@0 | 190 | static bool |
michael@0 | 191 | IsScriptEventHandler(nsIContent* aScriptElement) |
michael@0 | 192 | { |
michael@0 | 193 | if (!aScriptElement->IsHTML()) { |
michael@0 | 194 | return false; |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | nsAutoString forAttr, eventAttr; |
michael@0 | 198 | if (!aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) || |
michael@0 | 199 | !aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) { |
michael@0 | 200 | return false; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | const nsAString& for_str = |
michael@0 | 204 | nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr); |
michael@0 | 205 | if (!for_str.LowerCaseEqualsLiteral("window")) { |
michael@0 | 206 | return true; |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | // We found for="window", now check for event="onload". |
michael@0 | 210 | const nsAString& event_str = |
michael@0 | 211 | nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false); |
michael@0 | 212 | if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"), |
michael@0 | 213 | nsCaseInsensitiveStringComparator())) { |
michael@0 | 214 | // It ain't "onload.*". |
michael@0 | 215 | |
michael@0 | 216 | return true; |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | nsAutoString::const_iterator start, end; |
michael@0 | 220 | event_str.BeginReading(start); |
michael@0 | 221 | event_str.EndReading(end); |
michael@0 | 222 | |
michael@0 | 223 | start.advance(6); // advance past "onload" |
michael@0 | 224 | |
michael@0 | 225 | if (start != end && *start != '(' && *start != ' ') { |
michael@0 | 226 | // We got onload followed by something other than space or |
michael@0 | 227 | // '('. Not good enough. |
michael@0 | 228 | |
michael@0 | 229 | return true; |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | return false; |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | nsresult |
michael@0 | 236 | nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument, |
michael@0 | 237 | nsISupports *aContext, |
michael@0 | 238 | nsIURI *aURI, |
michael@0 | 239 | const nsAString &aType) |
michael@0 | 240 | { |
michael@0 | 241 | int16_t shouldLoad = nsIContentPolicy::ACCEPT; |
michael@0 | 242 | nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, |
michael@0 | 243 | aURI, |
michael@0 | 244 | aDocument->NodePrincipal(), |
michael@0 | 245 | aContext, |
michael@0 | 246 | NS_LossyConvertUTF16toASCII(aType), |
michael@0 | 247 | nullptr, //extra |
michael@0 | 248 | &shouldLoad, |
michael@0 | 249 | nsContentUtils::GetContentPolicy(), |
michael@0 | 250 | nsContentUtils::GetSecurityManager()); |
michael@0 | 251 | if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { |
michael@0 | 252 | if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { |
michael@0 | 253 | return NS_ERROR_CONTENT_BLOCKED; |
michael@0 | 254 | } |
michael@0 | 255 | return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | return NS_OK; |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | nsresult |
michael@0 | 262 | nsScriptLoader::ShouldLoadScript(nsIDocument* aDocument, |
michael@0 | 263 | nsISupports* aContext, |
michael@0 | 264 | nsIURI* aURI, |
michael@0 | 265 | const nsAString &aType) |
michael@0 | 266 | { |
michael@0 | 267 | // Check that the containing page is allowed to load this URI. |
michael@0 | 268 | nsresult rv = nsContentUtils::GetSecurityManager()-> |
michael@0 | 269 | CheckLoadURIWithPrincipal(aDocument->NodePrincipal(), aURI, |
michael@0 | 270 | nsIScriptSecurityManager::ALLOW_CHROME); |
michael@0 | 271 | |
michael@0 | 272 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 273 | |
michael@0 | 274 | // After the security manager, the content-policy stuff gets a veto |
michael@0 | 275 | rv = CheckContentPolicy(aDocument, aContext, aURI, aType); |
michael@0 | 276 | if (NS_FAILED(rv)) { |
michael@0 | 277 | return rv; |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | return NS_OK; |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | nsresult |
michael@0 | 284 | nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType, |
michael@0 | 285 | bool aScriptFromHead) |
michael@0 | 286 | { |
michael@0 | 287 | nsISupports *context = aRequest->mElement.get() |
michael@0 | 288 | ? static_cast<nsISupports *>(aRequest->mElement.get()) |
michael@0 | 289 | : static_cast<nsISupports *>(mDocument); |
michael@0 | 290 | nsresult rv = ShouldLoadScript(mDocument, context, aRequest->mURI, aType); |
michael@0 | 291 | if (NS_FAILED(rv)) { |
michael@0 | 292 | return rv; |
michael@0 | 293 | } |
michael@0 | 294 | |
michael@0 | 295 | nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup(); |
michael@0 | 296 | |
michael@0 | 297 | nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->GetWindow())); |
michael@0 | 298 | if (!window) { |
michael@0 | 299 | return NS_ERROR_NULL_POINTER; |
michael@0 | 300 | } |
michael@0 | 301 | |
michael@0 | 302 | nsIDocShell *docshell = window->GetDocShell(); |
michael@0 | 303 | |
michael@0 | 304 | nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell)); |
michael@0 | 305 | |
michael@0 | 306 | // If this document is sandboxed without 'allow-scripts', abort. |
michael@0 | 307 | if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) { |
michael@0 | 308 | return NS_OK; |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | // check for a Content Security Policy to pass down to the channel |
michael@0 | 312 | // that will be created to load the script |
michael@0 | 313 | nsCOMPtr<nsIChannelPolicy> channelPolicy; |
michael@0 | 314 | nsCOMPtr<nsIContentSecurityPolicy> csp; |
michael@0 | 315 | rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp)); |
michael@0 | 316 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 317 | if (csp) { |
michael@0 | 318 | channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); |
michael@0 | 319 | channelPolicy->SetContentSecurityPolicy(csp); |
michael@0 | 320 | channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | nsCOMPtr<nsIChannel> channel; |
michael@0 | 324 | rv = NS_NewChannel(getter_AddRefs(channel), |
michael@0 | 325 | aRequest->mURI, nullptr, loadGroup, prompter, |
michael@0 | 326 | nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI, |
michael@0 | 327 | channelPolicy); |
michael@0 | 328 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 329 | |
michael@0 | 330 | nsIScriptElement *script = aRequest->mElement; |
michael@0 | 331 | if (aScriptFromHead && |
michael@0 | 332 | !(script && (script->GetScriptAsync() || script->GetScriptDeferred()))) { |
michael@0 | 333 | nsCOMPtr<nsIHttpChannelInternal> |
michael@0 | 334 | internalHttpChannel(do_QueryInterface(channel)); |
michael@0 | 335 | if (internalHttpChannel) |
michael@0 | 336 | internalHttpChannel->SetLoadAsBlocking(true); |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); |
michael@0 | 340 | if (httpChannel) { |
michael@0 | 341 | // HTTP content negotation has little value in this context. |
michael@0 | 342 | httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), |
michael@0 | 343 | NS_LITERAL_CSTRING("*/*"), |
michael@0 | 344 | false); |
michael@0 | 345 | httpChannel->SetReferrer(mDocument->GetDocumentURI()); |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(docshell)); |
michael@0 | 349 | mozilla::net::SeerLearn(aRequest->mURI, mDocument->GetDocumentURI(), |
michael@0 | 350 | nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadContext); |
michael@0 | 351 | |
michael@0 | 352 | // Set the initiator type |
michael@0 | 353 | nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel)); |
michael@0 | 354 | if (timedChannel) { |
michael@0 | 355 | timedChannel->SetInitiatorType(NS_LITERAL_STRING("script")); |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | nsCOMPtr<nsIStreamLoader> loader; |
michael@0 | 359 | rv = NS_NewStreamLoader(getter_AddRefs(loader), this); |
michael@0 | 360 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 361 | |
michael@0 | 362 | nsCOMPtr<nsIStreamListener> listener = loader.get(); |
michael@0 | 363 | |
michael@0 | 364 | if (aRequest->mCORSMode != CORS_NONE) { |
michael@0 | 365 | bool withCredentials = (aRequest->mCORSMode == CORS_USE_CREDENTIALS); |
michael@0 | 366 | nsRefPtr<nsCORSListenerProxy> corsListener = |
michael@0 | 367 | new nsCORSListenerProxy(listener, mDocument->NodePrincipal(), |
michael@0 | 368 | withCredentials); |
michael@0 | 369 | rv = corsListener->Init(channel); |
michael@0 | 370 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 371 | listener = corsListener; |
michael@0 | 372 | } |
michael@0 | 373 | |
michael@0 | 374 | rv = channel->AsyncOpen(listener, aRequest); |
michael@0 | 375 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 376 | |
michael@0 | 377 | return NS_OK; |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | bool |
michael@0 | 381 | nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi, |
michael@0 | 382 | nsIURI * const &aURI) const |
michael@0 | 383 | { |
michael@0 | 384 | bool same; |
michael@0 | 385 | return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && |
michael@0 | 386 | same; |
michael@0 | 387 | } |
michael@0 | 388 | |
michael@0 | 389 | class nsScriptRequestProcessor : public nsRunnable |
michael@0 | 390 | { |
michael@0 | 391 | private: |
michael@0 | 392 | nsRefPtr<nsScriptLoader> mLoader; |
michael@0 | 393 | nsRefPtr<nsScriptLoadRequest> mRequest; |
michael@0 | 394 | public: |
michael@0 | 395 | nsScriptRequestProcessor(nsScriptLoader* aLoader, |
michael@0 | 396 | nsScriptLoadRequest* aRequest) |
michael@0 | 397 | : mLoader(aLoader) |
michael@0 | 398 | , mRequest(aRequest) |
michael@0 | 399 | {} |
michael@0 | 400 | NS_IMETHODIMP Run() |
michael@0 | 401 | { |
michael@0 | 402 | return mLoader->ProcessRequest(mRequest); |
michael@0 | 403 | } |
michael@0 | 404 | }; |
michael@0 | 405 | |
michael@0 | 406 | static inline bool |
michael@0 | 407 | ParseTypeAttribute(const nsAString& aType, JSVersion* aVersion) |
michael@0 | 408 | { |
michael@0 | 409 | MOZ_ASSERT(!aType.IsEmpty()); |
michael@0 | 410 | MOZ_ASSERT(aVersion); |
michael@0 | 411 | MOZ_ASSERT(*aVersion == JSVERSION_DEFAULT); |
michael@0 | 412 | |
michael@0 | 413 | nsContentTypeParser parser(aType); |
michael@0 | 414 | |
michael@0 | 415 | nsAutoString mimeType; |
michael@0 | 416 | nsresult rv = parser.GetType(mimeType); |
michael@0 | 417 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 418 | |
michael@0 | 419 | if (!nsContentUtils::IsJavascriptMIMEType(mimeType)) { |
michael@0 | 420 | return false; |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | // Get the version string, and ensure the language supports it. |
michael@0 | 424 | nsAutoString versionName; |
michael@0 | 425 | rv = parser.GetParameter("version", versionName); |
michael@0 | 426 | |
michael@0 | 427 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 428 | *aVersion = nsContentUtils::ParseJavascriptVersion(versionName); |
michael@0 | 429 | } else if (rv != NS_ERROR_INVALID_ARG) { |
michael@0 | 430 | return false; |
michael@0 | 431 | } |
michael@0 | 432 | |
michael@0 | 433 | return true; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | bool |
michael@0 | 437 | CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument) |
michael@0 | 438 | { |
michael@0 | 439 | nsCOMPtr<nsIContentSecurityPolicy> csp; |
michael@0 | 440 | nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp)); |
michael@0 | 441 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 442 | |
michael@0 | 443 | if (!csp) { |
michael@0 | 444 | // no CSP --> allow |
michael@0 | 445 | return true; |
michael@0 | 446 | } |
michael@0 | 447 | |
michael@0 | 448 | // An inline script can be allowed because all inline scripts are allowed, |
michael@0 | 449 | // or else because it is whitelisted by a nonce-source or hash-source. This |
michael@0 | 450 | // is a logical OR between whitelisting methods, so the allowInlineScript |
michael@0 | 451 | // outparam can be reused for each check as long as we stop checking as soon |
michael@0 | 452 | // as it is set to true. This also optimizes performance by avoiding the |
michael@0 | 453 | // overhead of unnecessary checks. |
michael@0 | 454 | bool allowInlineScript = true; |
michael@0 | 455 | nsAutoTArray<unsigned short, 3> violations; |
michael@0 | 456 | |
michael@0 | 457 | bool reportInlineViolation = false; |
michael@0 | 458 | rv = csp->GetAllowsInlineScript(&reportInlineViolation, &allowInlineScript); |
michael@0 | 459 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 460 | if (reportInlineViolation) { |
michael@0 | 461 | violations.AppendElement(static_cast<unsigned short>( |
michael@0 | 462 | nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT)); |
michael@0 | 463 | } |
michael@0 | 464 | |
michael@0 | 465 | nsAutoString nonce; |
michael@0 | 466 | if (!allowInlineScript) { |
michael@0 | 467 | nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement); |
michael@0 | 468 | bool foundNonce = scriptContent->GetAttr(kNameSpaceID_None, |
michael@0 | 469 | nsGkAtoms::nonce, nonce); |
michael@0 | 470 | if (foundNonce) { |
michael@0 | 471 | bool reportNonceViolation; |
michael@0 | 472 | rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT, |
michael@0 | 473 | &reportNonceViolation, &allowInlineScript); |
michael@0 | 474 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 475 | if (reportNonceViolation) { |
michael@0 | 476 | violations.AppendElement(static_cast<unsigned short>( |
michael@0 | 477 | nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT)); |
michael@0 | 478 | } |
michael@0 | 479 | } |
michael@0 | 480 | } |
michael@0 | 481 | |
michael@0 | 482 | if (!allowInlineScript) { |
michael@0 | 483 | bool reportHashViolation; |
michael@0 | 484 | nsAutoString scriptText; |
michael@0 | 485 | aElement->GetScriptText(scriptText); |
michael@0 | 486 | rv = csp->GetAllowsHash(scriptText, nsIContentPolicy::TYPE_SCRIPT, |
michael@0 | 487 | &reportHashViolation, &allowInlineScript); |
michael@0 | 488 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 489 | if (reportHashViolation) { |
michael@0 | 490 | violations.AppendElement(static_cast<unsigned short>( |
michael@0 | 491 | nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_SCRIPT)); |
michael@0 | 492 | } |
michael@0 | 493 | } |
michael@0 | 494 | |
michael@0 | 495 | // What violation(s) should be reported? |
michael@0 | 496 | // |
michael@0 | 497 | // 1. If the script tag has a nonce attribute, and the nonce does not match |
michael@0 | 498 | // the policy, report VIOLATION_TYPE_NONCE_SCRIPT. |
michael@0 | 499 | // 2. If the policy has at least one hash-source, and the hashed contents of |
michael@0 | 500 | // the script tag did not match any of them, report VIOLATION_TYPE_HASH_SCRIPT |
michael@0 | 501 | // 3. Otherwise, report VIOLATION_TYPE_INLINE_SCRIPT if appropriate. |
michael@0 | 502 | // |
michael@0 | 503 | // 1 and 2 may occur together, 3 should only occur by itself. Naturally, |
michael@0 | 504 | // every VIOLATION_TYPE_NONCE_SCRIPT and VIOLATION_TYPE_HASH_SCRIPT are also |
michael@0 | 505 | // VIOLATION_TYPE_INLINE_SCRIPT, but reporting the |
michael@0 | 506 | // VIOLATION_TYPE_INLINE_SCRIPT is redundant and does not help the developer. |
michael@0 | 507 | if (!violations.IsEmpty()) { |
michael@0 | 508 | MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT, |
michael@0 | 509 | "How did we get any violations without an initial inline script violation?"); |
michael@0 | 510 | // gather information to log with violation report |
michael@0 | 511 | nsIURI* uri = aDocument->GetDocumentURI(); |
michael@0 | 512 | nsAutoCString asciiSpec; |
michael@0 | 513 | uri->GetAsciiSpec(asciiSpec); |
michael@0 | 514 | nsAutoString scriptText; |
michael@0 | 515 | aElement->GetScriptText(scriptText); |
michael@0 | 516 | nsAutoString scriptSample(scriptText); |
michael@0 | 517 | |
michael@0 | 518 | // cap the length of the script sample at 40 chars |
michael@0 | 519 | if (scriptSample.Length() > 40) { |
michael@0 | 520 | scriptSample.Truncate(40); |
michael@0 | 521 | scriptSample.AppendLiteral("..."); |
michael@0 | 522 | } |
michael@0 | 523 | |
michael@0 | 524 | for (uint32_t i = 0; i < violations.Length(); i++) { |
michael@0 | 525 | // Skip reporting the redundant inline script violation if there are |
michael@0 | 526 | // other (nonce and/or hash violations) as well. |
michael@0 | 527 | if (i > 0 || violations.Length() == 1) { |
michael@0 | 528 | csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec), |
michael@0 | 529 | scriptSample, aElement->GetScriptLineNumber(), |
michael@0 | 530 | nonce, scriptText); |
michael@0 | 531 | } |
michael@0 | 532 | } |
michael@0 | 533 | } |
michael@0 | 534 | |
michael@0 | 535 | if (!allowInlineScript) { |
michael@0 | 536 | NS_ASSERTION(!violations.IsEmpty(), |
michael@0 | 537 | "CSP blocked inline script but is not reporting a violation"); |
michael@0 | 538 | return false; |
michael@0 | 539 | } |
michael@0 | 540 | return true; |
michael@0 | 541 | } |
michael@0 | 542 | |
michael@0 | 543 | bool |
michael@0 | 544 | nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) |
michael@0 | 545 | { |
michael@0 | 546 | // We need a document to evaluate scripts. |
michael@0 | 547 | NS_ENSURE_TRUE(mDocument, false); |
michael@0 | 548 | |
michael@0 | 549 | // Check to see if scripts has been turned off. |
michael@0 | 550 | if (!mEnabled || !mDocument->IsScriptEnabled()) { |
michael@0 | 551 | return false; |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script"); |
michael@0 | 555 | |
michael@0 | 556 | nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement); |
michael@0 | 557 | |
michael@0 | 558 | // Step 12. Check that the script is not an eventhandler |
michael@0 | 559 | if (IsScriptEventHandler(scriptContent)) { |
michael@0 | 560 | return false; |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | JSVersion version = JSVERSION_DEFAULT; |
michael@0 | 564 | |
michael@0 | 565 | // Check the type attribute to determine language and version. |
michael@0 | 566 | // If type exists, it trumps the deprecated 'language=' |
michael@0 | 567 | nsAutoString type; |
michael@0 | 568 | aElement->GetScriptType(type); |
michael@0 | 569 | if (!type.IsEmpty()) { |
michael@0 | 570 | NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false); |
michael@0 | 571 | } else { |
michael@0 | 572 | // no 'type=' element |
michael@0 | 573 | // "language" is a deprecated attribute of HTML, so we check it only for |
michael@0 | 574 | // HTML script elements. |
michael@0 | 575 | if (scriptContent->IsHTML()) { |
michael@0 | 576 | nsAutoString language; |
michael@0 | 577 | scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language); |
michael@0 | 578 | if (!language.IsEmpty()) { |
michael@0 | 579 | if (!nsContentUtils::IsJavaScriptLanguage(language)) { |
michael@0 | 580 | return false; |
michael@0 | 581 | } |
michael@0 | 582 | } |
michael@0 | 583 | } |
michael@0 | 584 | } |
michael@0 | 585 | |
michael@0 | 586 | // Step 14. in the HTML5 spec |
michael@0 | 587 | nsresult rv = NS_OK; |
michael@0 | 588 | nsRefPtr<nsScriptLoadRequest> request; |
michael@0 | 589 | if (aElement->GetScriptExternal()) { |
michael@0 | 590 | // external script |
michael@0 | 591 | nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI(); |
michael@0 | 592 | if (!scriptURI) { |
michael@0 | 593 | // Asynchronously report the failure to create a URI object |
michael@0 | 594 | NS_DispatchToCurrentThread( |
michael@0 | 595 | NS_NewRunnableMethod(aElement, |
michael@0 | 596 | &nsIScriptElement::FireErrorEvent)); |
michael@0 | 597 | return false; |
michael@0 | 598 | } |
michael@0 | 599 | CORSMode ourCORSMode = aElement->GetCORSMode(); |
michael@0 | 600 | nsTArray<PreloadInfo>::index_type i = |
michael@0 | 601 | mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator()); |
michael@0 | 602 | if (i != nsTArray<PreloadInfo>::NoIndex) { |
michael@0 | 603 | // preloaded |
michael@0 | 604 | // note that a script-inserted script can steal a preload! |
michael@0 | 605 | request = mPreloads[i].mRequest; |
michael@0 | 606 | request->mElement = aElement; |
michael@0 | 607 | nsString preloadCharset(mPreloads[i].mCharset); |
michael@0 | 608 | mPreloads.RemoveElementAt(i); |
michael@0 | 609 | |
michael@0 | 610 | // Double-check that the charset the preload used is the same as |
michael@0 | 611 | // the charset we have now. |
michael@0 | 612 | nsAutoString elementCharset; |
michael@0 | 613 | aElement->GetScriptCharset(elementCharset); |
michael@0 | 614 | if (elementCharset.Equals(preloadCharset) && |
michael@0 | 615 | ourCORSMode == request->mCORSMode) { |
michael@0 | 616 | rv = CheckContentPolicy(mDocument, aElement, request->mURI, type); |
michael@0 | 617 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 618 | } else { |
michael@0 | 619 | // Drop the preload |
michael@0 | 620 | request = nullptr; |
michael@0 | 621 | } |
michael@0 | 622 | } |
michael@0 | 623 | |
michael@0 | 624 | if (!request) { |
michael@0 | 625 | // no usable preload |
michael@0 | 626 | request = new nsScriptLoadRequest(aElement, version, ourCORSMode); |
michael@0 | 627 | request->mURI = scriptURI; |
michael@0 | 628 | request->mIsInline = false; |
michael@0 | 629 | request->mLoading = true; |
michael@0 | 630 | |
michael@0 | 631 | // set aScriptFromHead to false so we don't treat non preloaded scripts as |
michael@0 | 632 | // blockers for full page load. See bug 792438. |
michael@0 | 633 | rv = StartLoad(request, type, false); |
michael@0 | 634 | if (NS_FAILED(rv)) { |
michael@0 | 635 | // Asynchronously report the load failure |
michael@0 | 636 | NS_DispatchToCurrentThread( |
michael@0 | 637 | NS_NewRunnableMethod(aElement, |
michael@0 | 638 | &nsIScriptElement::FireErrorEvent)); |
michael@0 | 639 | return false; |
michael@0 | 640 | } |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | request->mJSVersion = version; |
michael@0 | 644 | |
michael@0 | 645 | if (aElement->GetScriptAsync()) { |
michael@0 | 646 | mAsyncRequests.AppendElement(request); |
michael@0 | 647 | if (!request->mLoading) { |
michael@0 | 648 | // The script is available already. Run it ASAP when the event |
michael@0 | 649 | // loop gets a chance to spin. |
michael@0 | 650 | ProcessPendingRequestsAsync(); |
michael@0 | 651 | } |
michael@0 | 652 | return false; |
michael@0 | 653 | } |
michael@0 | 654 | if (!aElement->GetParserCreated()) { |
michael@0 | 655 | // Violate the HTML5 spec in order to make LABjs and the "order" plug-in |
michael@0 | 656 | // for RequireJS work with their Gecko-sniffed code path. See |
michael@0 | 657 | // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html |
michael@0 | 658 | mNonAsyncExternalScriptInsertedRequests.AppendElement(request); |
michael@0 | 659 | if (!request->mLoading) { |
michael@0 | 660 | // The script is available already. Run it ASAP when the event |
michael@0 | 661 | // loop gets a chance to spin. |
michael@0 | 662 | ProcessPendingRequestsAsync(); |
michael@0 | 663 | } |
michael@0 | 664 | return false; |
michael@0 | 665 | } |
michael@0 | 666 | // we now have a parser-inserted request that may or may not be still |
michael@0 | 667 | // loading |
michael@0 | 668 | if (aElement->GetScriptDeferred()) { |
michael@0 | 669 | // We don't want to run this yet. |
michael@0 | 670 | // If we come here, the script is a parser-created script and it has |
michael@0 | 671 | // the defer attribute but not the async attribute. Since a |
michael@0 | 672 | // a parser-inserted script is being run, we came here by the parser |
michael@0 | 673 | // running the script, which means the parser is still alive and the |
michael@0 | 674 | // parse is ongoing. |
michael@0 | 675 | NS_ASSERTION(mDocument->GetCurrentContentSink() || |
michael@0 | 676 | aElement->GetParserCreated() == FROM_PARSER_XSLT, |
michael@0 | 677 | "Non-XSLT Defer script on a document without an active parser; bug 592366."); |
michael@0 | 678 | AddDeferRequest(request); |
michael@0 | 679 | return false; |
michael@0 | 680 | } |
michael@0 | 681 | |
michael@0 | 682 | if (aElement->GetParserCreated() == FROM_PARSER_XSLT) { |
michael@0 | 683 | // Need to maintain order for XSLT-inserted scripts |
michael@0 | 684 | NS_ASSERTION(!mParserBlockingRequest, |
michael@0 | 685 | "Parser-blocking scripts and XSLT scripts in the same doc!"); |
michael@0 | 686 | mXSLTRequests.AppendElement(request); |
michael@0 | 687 | if (!request->mLoading) { |
michael@0 | 688 | // The script is available already. Run it ASAP when the event |
michael@0 | 689 | // loop gets a chance to spin. |
michael@0 | 690 | ProcessPendingRequestsAsync(); |
michael@0 | 691 | } |
michael@0 | 692 | return true; |
michael@0 | 693 | } |
michael@0 | 694 | if (!request->mLoading && ReadyToExecuteScripts()) { |
michael@0 | 695 | // The request has already been loaded and there are no pending style |
michael@0 | 696 | // sheets. If the script comes from the network stream, cheat for |
michael@0 | 697 | // performance reasons and avoid a trip through the event loop. |
michael@0 | 698 | if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) { |
michael@0 | 699 | return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; |
michael@0 | 700 | } |
michael@0 | 701 | // Otherwise, we've got a document.written script, make a trip through |
michael@0 | 702 | // the event loop to hide the preload effects from the scripts on the |
michael@0 | 703 | // Web page. |
michael@0 | 704 | NS_ASSERTION(!mParserBlockingRequest, |
michael@0 | 705 | "There can be only one parser-blocking script at a time"); |
michael@0 | 706 | NS_ASSERTION(mXSLTRequests.IsEmpty(), |
michael@0 | 707 | "Parser-blocking scripts and XSLT scripts in the same doc!"); |
michael@0 | 708 | mParserBlockingRequest = request; |
michael@0 | 709 | ProcessPendingRequestsAsync(); |
michael@0 | 710 | return true; |
michael@0 | 711 | } |
michael@0 | 712 | // The script hasn't loaded yet or there's a style sheet blocking it. |
michael@0 | 713 | // The script will be run when it loads or the style sheet loads. |
michael@0 | 714 | NS_ASSERTION(!mParserBlockingRequest, |
michael@0 | 715 | "There can be only one parser-blocking script at a time"); |
michael@0 | 716 | NS_ASSERTION(mXSLTRequests.IsEmpty(), |
michael@0 | 717 | "Parser-blocking scripts and XSLT scripts in the same doc!"); |
michael@0 | 718 | mParserBlockingRequest = request; |
michael@0 | 719 | return true; |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | // inline script |
michael@0 | 723 | // Is this document sandboxed without 'allow-scripts'? |
michael@0 | 724 | if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) { |
michael@0 | 725 | return false; |
michael@0 | 726 | } |
michael@0 | 727 | |
michael@0 | 728 | // Does CSP allow this inline script to run? |
michael@0 | 729 | if (!CSPAllowsInlineScript(aElement, mDocument)) { |
michael@0 | 730 | return false; |
michael@0 | 731 | } |
michael@0 | 732 | |
michael@0 | 733 | // Inline scripts ignore ther CORS mode and are always CORS_NONE |
michael@0 | 734 | request = new nsScriptLoadRequest(aElement, version, CORS_NONE); |
michael@0 | 735 | request->mJSVersion = version; |
michael@0 | 736 | request->mLoading = false; |
michael@0 | 737 | request->mIsInline = true; |
michael@0 | 738 | request->mURI = mDocument->GetDocumentURI(); |
michael@0 | 739 | request->mLineNo = aElement->GetScriptLineNumber(); |
michael@0 | 740 | |
michael@0 | 741 | if (aElement->GetParserCreated() == FROM_PARSER_XSLT && |
michael@0 | 742 | (!ReadyToExecuteScripts() || !mXSLTRequests.IsEmpty())) { |
michael@0 | 743 | // Need to maintain order for XSLT-inserted scripts |
michael@0 | 744 | NS_ASSERTION(!mParserBlockingRequest, |
michael@0 | 745 | "Parser-blocking scripts and XSLT scripts in the same doc!"); |
michael@0 | 746 | mXSLTRequests.AppendElement(request); |
michael@0 | 747 | return true; |
michael@0 | 748 | } |
michael@0 | 749 | if (aElement->GetParserCreated() == NOT_FROM_PARSER) { |
michael@0 | 750 | NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), |
michael@0 | 751 | "A script-inserted script is inserted without an update batch?"); |
michael@0 | 752 | nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this, |
michael@0 | 753 | request)); |
michael@0 | 754 | return false; |
michael@0 | 755 | } |
michael@0 | 756 | if (aElement->GetParserCreated() == FROM_PARSER_NETWORK && |
michael@0 | 757 | !ReadyToExecuteScripts()) { |
michael@0 | 758 | NS_ASSERTION(!mParserBlockingRequest, |
michael@0 | 759 | "There can be only one parser-blocking script at a time"); |
michael@0 | 760 | mParserBlockingRequest = request; |
michael@0 | 761 | NS_ASSERTION(mXSLTRequests.IsEmpty(), |
michael@0 | 762 | "Parser-blocking scripts and XSLT scripts in the same doc!"); |
michael@0 | 763 | return true; |
michael@0 | 764 | } |
michael@0 | 765 | // We now have a document.written inline script or we have an inline script |
michael@0 | 766 | // from the network but there is no style sheet that is blocking scripts. |
michael@0 | 767 | // Don't check for style sheets blocking scripts in the document.write |
michael@0 | 768 | // case to avoid style sheet network activity affecting when |
michael@0 | 769 | // document.write returns. It's not really necessary to do this if |
michael@0 | 770 | // there's no document.write currently on the call stack. However, |
michael@0 | 771 | // this way matches IE more closely than checking if document.write |
michael@0 | 772 | // is on the call stack. |
michael@0 | 773 | NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), |
michael@0 | 774 | "Not safe to run a parser-inserted script?"); |
michael@0 | 775 | return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK; |
michael@0 | 776 | } |
michael@0 | 777 | |
michael@0 | 778 | namespace { |
michael@0 | 779 | |
michael@0 | 780 | class NotifyOffThreadScriptLoadCompletedRunnable : public nsRunnable |
michael@0 | 781 | { |
michael@0 | 782 | nsRefPtr<nsScriptLoadRequest> mRequest; |
michael@0 | 783 | nsRefPtr<nsScriptLoader> mLoader; |
michael@0 | 784 | void *mToken; |
michael@0 | 785 | |
michael@0 | 786 | public: |
michael@0 | 787 | NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest, |
michael@0 | 788 | nsScriptLoader* aLoader) |
michael@0 | 789 | : mRequest(aRequest), mLoader(aLoader), mToken(nullptr) |
michael@0 | 790 | {} |
michael@0 | 791 | |
michael@0 | 792 | void SetToken(void* aToken) { |
michael@0 | 793 | MOZ_ASSERT(aToken && !mToken); |
michael@0 | 794 | mToken = aToken; |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | NS_DECL_NSIRUNNABLE |
michael@0 | 798 | }; |
michael@0 | 799 | |
michael@0 | 800 | } /* anonymous namespace */ |
michael@0 | 801 | |
michael@0 | 802 | nsresult |
michael@0 | 803 | nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken) |
michael@0 | 804 | { |
michael@0 | 805 | nsresult rv = ProcessRequest(aRequest, aOffThreadToken); |
michael@0 | 806 | mDocument->UnblockOnload(false); |
michael@0 | 807 | return rv; |
michael@0 | 808 | } |
michael@0 | 809 | |
michael@0 | 810 | NS_IMETHODIMP |
michael@0 | 811 | NotifyOffThreadScriptLoadCompletedRunnable::Run() |
michael@0 | 812 | { |
michael@0 | 813 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 814 | |
michael@0 | 815 | nsresult rv = mLoader->ProcessOffThreadRequest(mRequest, &mToken); |
michael@0 | 816 | |
michael@0 | 817 | if (mToken) { |
michael@0 | 818 | // The result of the off thread parse was not actually needed to process |
michael@0 | 819 | // the request (disappearing window, some other error, ...). Finish the |
michael@0 | 820 | // request to avoid leaks in the JS engine. |
michael@0 | 821 | nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); |
michael@0 | 822 | NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE); |
michael@0 | 823 | JSRuntime *rt; |
michael@0 | 824 | svc->GetRuntime(&rt); |
michael@0 | 825 | NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE); |
michael@0 | 826 | JS::FinishOffThreadScript(nullptr, rt, mToken); |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | return rv; |
michael@0 | 830 | } |
michael@0 | 831 | |
michael@0 | 832 | static void |
michael@0 | 833 | OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData) |
michael@0 | 834 | { |
michael@0 | 835 | nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable = |
michael@0 | 836 | dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData)); |
michael@0 | 837 | aRunnable->SetToken(aToken); |
michael@0 | 838 | NS_DispatchToMainThread(aRunnable); |
michael@0 | 839 | } |
michael@0 | 840 | |
michael@0 | 841 | nsresult |
michael@0 | 842 | nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest) |
michael@0 | 843 | { |
michael@0 | 844 | if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) { |
michael@0 | 845 | return NS_ERROR_FAILURE; |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); |
michael@0 | 849 | if (!globalObject) { |
michael@0 | 850 | return NS_ERROR_FAILURE; |
michael@0 | 851 | } |
michael@0 | 852 | |
michael@0 | 853 | nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); |
michael@0 | 854 | if (!context) { |
michael@0 | 855 | return NS_ERROR_FAILURE; |
michael@0 | 856 | } |
michael@0 | 857 | |
michael@0 | 858 | AutoPushJSContext cx(context->GetNativeContext()); |
michael@0 | 859 | JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject()); |
michael@0 | 860 | |
michael@0 | 861 | JS::CompileOptions options(cx); |
michael@0 | 862 | FillCompileOptionsForRequest(aRequest, global, &options); |
michael@0 | 863 | |
michael@0 | 864 | if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) { |
michael@0 | 865 | return NS_ERROR_FAILURE; |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable = |
michael@0 | 869 | new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this); |
michael@0 | 870 | |
michael@0 | 871 | if (!JS::CompileOffThread(cx, options, |
michael@0 | 872 | aRequest->mScriptTextBuf, aRequest->mScriptTextLength, |
michael@0 | 873 | OffThreadScriptLoaderCallback, |
michael@0 | 874 | static_cast<void*>(runnable))) { |
michael@0 | 875 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 876 | } |
michael@0 | 877 | |
michael@0 | 878 | mDocument->BlockOnload(); |
michael@0 | 879 | |
michael@0 | 880 | unused << runnable.forget(); |
michael@0 | 881 | return NS_OK; |
michael@0 | 882 | } |
michael@0 | 883 | |
michael@0 | 884 | nsresult |
michael@0 | 885 | nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken) |
michael@0 | 886 | { |
michael@0 | 887 | NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), |
michael@0 | 888 | "Processing requests when running scripts is unsafe."); |
michael@0 | 889 | |
michael@0 | 890 | if (!aOffThreadToken) { |
michael@0 | 891 | nsresult rv = AttemptAsyncScriptParse(aRequest); |
michael@0 | 892 | if (rv != NS_ERROR_FAILURE) |
michael@0 | 893 | return rv; |
michael@0 | 894 | } |
michael@0 | 895 | |
michael@0 | 896 | NS_ENSURE_ARG(aRequest); |
michael@0 | 897 | nsAutoString textData; |
michael@0 | 898 | const jschar* scriptBuf = nullptr; |
michael@0 | 899 | size_t scriptLength = 0; |
michael@0 | 900 | JS::SourceBufferHolder::Ownership giveScriptOwnership = |
michael@0 | 901 | JS::SourceBufferHolder::NoOwnership; |
michael@0 | 902 | |
michael@0 | 903 | nsCOMPtr<nsIDocument> doc; |
michael@0 | 904 | |
michael@0 | 905 | nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement); |
michael@0 | 906 | |
michael@0 | 907 | // If there's no script text, we try to get it from the element |
michael@0 | 908 | if (aRequest->mIsInline) { |
michael@0 | 909 | // XXX This is inefficient - GetText makes multiple |
michael@0 | 910 | // copies. |
michael@0 | 911 | aRequest->mElement->GetScriptText(textData); |
michael@0 | 912 | |
michael@0 | 913 | scriptBuf = textData.get(); |
michael@0 | 914 | scriptLength = textData.Length(); |
michael@0 | 915 | giveScriptOwnership = JS::SourceBufferHolder::NoOwnership; |
michael@0 | 916 | } |
michael@0 | 917 | else { |
michael@0 | 918 | scriptBuf = aRequest->mScriptTextBuf; |
michael@0 | 919 | scriptLength = aRequest->mScriptTextLength; |
michael@0 | 920 | |
michael@0 | 921 | giveScriptOwnership = JS::SourceBufferHolder::GiveOwnership; |
michael@0 | 922 | aRequest->mScriptTextBuf = nullptr; |
michael@0 | 923 | aRequest->mScriptTextLength = 0; |
michael@0 | 924 | |
michael@0 | 925 | doc = scriptElem->OwnerDoc(); |
michael@0 | 926 | } |
michael@0 | 927 | |
michael@0 | 928 | JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, giveScriptOwnership); |
michael@0 | 929 | |
michael@0 | 930 | nsCOMPtr<nsIScriptElement> oldParserInsertedScript; |
michael@0 | 931 | uint32_t parserCreated = aRequest->mElement->GetParserCreated(); |
michael@0 | 932 | if (parserCreated) { |
michael@0 | 933 | oldParserInsertedScript = mCurrentParserInsertedScript; |
michael@0 | 934 | mCurrentParserInsertedScript = aRequest->mElement; |
michael@0 | 935 | } |
michael@0 | 936 | |
michael@0 | 937 | FireScriptAvailable(NS_OK, aRequest); |
michael@0 | 938 | |
michael@0 | 939 | // The window may have gone away by this point, in which case there's no point |
michael@0 | 940 | // in trying to run the script. |
michael@0 | 941 | nsPIDOMWindow *pwin = mDocument->GetInnerWindow(); |
michael@0 | 942 | bool runScript = !!pwin; |
michael@0 | 943 | if (runScript) { |
michael@0 | 944 | nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), |
michael@0 | 945 | scriptElem, |
michael@0 | 946 | NS_LITERAL_STRING("beforescriptexecute"), |
michael@0 | 947 | true, true, &runScript); |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | // Inner window could have gone away after firing beforescriptexecute |
michael@0 | 951 | pwin = mDocument->GetInnerWindow(); |
michael@0 | 952 | if (!pwin) { |
michael@0 | 953 | runScript = false; |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | nsresult rv = NS_OK; |
michael@0 | 957 | if (runScript) { |
michael@0 | 958 | if (doc) { |
michael@0 | 959 | doc->BeginEvaluatingExternalScript(); |
michael@0 | 960 | } |
michael@0 | 961 | aRequest->mElement->BeginEvaluating(); |
michael@0 | 962 | rv = EvaluateScript(aRequest, srcBuf, aOffThreadToken); |
michael@0 | 963 | aRequest->mElement->EndEvaluating(); |
michael@0 | 964 | if (doc) { |
michael@0 | 965 | doc->EndEvaluatingExternalScript(); |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), |
michael@0 | 969 | scriptElem, |
michael@0 | 970 | NS_LITERAL_STRING("afterscriptexecute"), |
michael@0 | 971 | true, false); |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | FireScriptEvaluated(rv, aRequest); |
michael@0 | 975 | |
michael@0 | 976 | if (parserCreated) { |
michael@0 | 977 | mCurrentParserInsertedScript = oldParserInsertedScript; |
michael@0 | 978 | } |
michael@0 | 979 | |
michael@0 | 980 | return rv; |
michael@0 | 981 | } |
michael@0 | 982 | |
michael@0 | 983 | void |
michael@0 | 984 | nsScriptLoader::FireScriptAvailable(nsresult aResult, |
michael@0 | 985 | nsScriptLoadRequest* aRequest) |
michael@0 | 986 | { |
michael@0 | 987 | for (int32_t i = 0; i < mObservers.Count(); i++) { |
michael@0 | 988 | nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; |
michael@0 | 989 | obs->ScriptAvailable(aResult, aRequest->mElement, |
michael@0 | 990 | aRequest->mIsInline, aRequest->mURI, |
michael@0 | 991 | aRequest->mLineNo); |
michael@0 | 992 | } |
michael@0 | 993 | |
michael@0 | 994 | aRequest->FireScriptAvailable(aResult); |
michael@0 | 995 | } |
michael@0 | 996 | |
michael@0 | 997 | void |
michael@0 | 998 | nsScriptLoader::FireScriptEvaluated(nsresult aResult, |
michael@0 | 999 | nsScriptLoadRequest* aRequest) |
michael@0 | 1000 | { |
michael@0 | 1001 | for (int32_t i = 0; i < mObservers.Count(); i++) { |
michael@0 | 1002 | nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i]; |
michael@0 | 1003 | obs->ScriptEvaluated(aResult, aRequest->mElement, |
michael@0 | 1004 | aRequest->mIsInline); |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | aRequest->FireScriptEvaluated(aResult); |
michael@0 | 1008 | } |
michael@0 | 1009 | |
michael@0 | 1010 | already_AddRefed<nsIScriptGlobalObject> |
michael@0 | 1011 | nsScriptLoader::GetScriptGlobalObject() |
michael@0 | 1012 | { |
michael@0 | 1013 | nsPIDOMWindow *pwin = mDocument->GetInnerWindow(); |
michael@0 | 1014 | if (!pwin) { |
michael@0 | 1015 | return nullptr; |
michael@0 | 1016 | } |
michael@0 | 1017 | |
michael@0 | 1018 | nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin); |
michael@0 | 1019 | NS_ASSERTION(globalObject, "windows must be global objects"); |
michael@0 | 1020 | |
michael@0 | 1021 | // and make sure we are setup for this type of script. |
michael@0 | 1022 | nsresult rv = globalObject->EnsureScriptEnvironment(); |
michael@0 | 1023 | if (NS_FAILED(rv)) { |
michael@0 | 1024 | return nullptr; |
michael@0 | 1025 | } |
michael@0 | 1026 | |
michael@0 | 1027 | return globalObject.forget(); |
michael@0 | 1028 | } |
michael@0 | 1029 | |
michael@0 | 1030 | void |
michael@0 | 1031 | nsScriptLoader::FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest, |
michael@0 | 1032 | JS::Handle<JSObject *> aScopeChain, |
michael@0 | 1033 | JS::CompileOptions *aOptions) |
michael@0 | 1034 | { |
michael@0 | 1035 | // It's very important to use aRequest->mURI, not the final URI of the channel |
michael@0 | 1036 | // aRequest ended up getting script data from, as the script filename. |
michael@0 | 1037 | nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, aRequest->mURL); |
michael@0 | 1038 | |
michael@0 | 1039 | aOptions->setIntroductionType("scriptElement"); |
michael@0 | 1040 | aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo); |
michael@0 | 1041 | aOptions->setVersion(JSVersion(aRequest->mJSVersion)); |
michael@0 | 1042 | aOptions->setCompileAndGo(JS_IsGlobalObject(aScopeChain)); |
michael@0 | 1043 | if (aRequest->mHasSourceMapURL) { |
michael@0 | 1044 | aOptions->setSourceMapURL(aRequest->mSourceMapURL.get()); |
michael@0 | 1045 | } |
michael@0 | 1046 | if (aRequest->mOriginPrincipal) { |
michael@0 | 1047 | aOptions->setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal)); |
michael@0 | 1048 | } |
michael@0 | 1049 | |
michael@0 | 1050 | AutoJSContext cx; |
michael@0 | 1051 | JS::Rooted<JS::Value> elementVal(cx); |
michael@0 | 1052 | MOZ_ASSERT(aRequest->mElement); |
michael@0 | 1053 | // XXXbz this is super-fragile, because the code that _uses_ the |
michael@0 | 1054 | // JS::CompileOptions is nowhere near us, but we have to coordinate |
michael@0 | 1055 | // compartments with it... and in particular, it will compile in the |
michael@0 | 1056 | // compartment of aScopeChain, so we want to wrap into that compartment as |
michael@0 | 1057 | // well. |
michael@0 | 1058 | JSAutoCompartment ac(cx, aScopeChain); |
michael@0 | 1059 | if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement, |
michael@0 | 1060 | &elementVal, |
michael@0 | 1061 | /* aAllowWrapping = */ true))) { |
michael@0 | 1062 | MOZ_ASSERT(elementVal.isObject()); |
michael@0 | 1063 | aOptions->setElement(&elementVal.toObject()); |
michael@0 | 1064 | } |
michael@0 | 1065 | } |
michael@0 | 1066 | |
michael@0 | 1067 | nsresult |
michael@0 | 1068 | nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, |
michael@0 | 1069 | JS::SourceBufferHolder& aSrcBuf, |
michael@0 | 1070 | void** aOffThreadToken) |
michael@0 | 1071 | { |
michael@0 | 1072 | // We need a document to evaluate scripts. |
michael@0 | 1073 | if (!mDocument) { |
michael@0 | 1074 | return NS_ERROR_FAILURE; |
michael@0 | 1075 | } |
michael@0 | 1076 | |
michael@0 | 1077 | nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement)); |
michael@0 | 1078 | nsIDocument* ownerDoc = scriptContent->OwnerDoc(); |
michael@0 | 1079 | if (ownerDoc != mDocument) { |
michael@0 | 1080 | // Willful violation of HTML5 as of 2010-12-01 |
michael@0 | 1081 | return NS_ERROR_FAILURE; |
michael@0 | 1082 | } |
michael@0 | 1083 | |
michael@0 | 1084 | // Get the script-type to be used by this element. |
michael@0 | 1085 | NS_ASSERTION(scriptContent, "no content - what is default script-type?"); |
michael@0 | 1086 | |
michael@0 | 1087 | nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); |
michael@0 | 1088 | if (!globalObject) { |
michael@0 | 1089 | return NS_ERROR_FAILURE; |
michael@0 | 1090 | } |
michael@0 | 1091 | |
michael@0 | 1092 | // Make sure context is a strong reference since we access it after |
michael@0 | 1093 | // we've executed a script, which may cause all other references to |
michael@0 | 1094 | // the context to go away. |
michael@0 | 1095 | nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); |
michael@0 | 1096 | if (!context) { |
michael@0 | 1097 | return NS_ERROR_FAILURE; |
michael@0 | 1098 | } |
michael@0 | 1099 | |
michael@0 | 1100 | JSVersion version = JSVersion(aRequest->mJSVersion); |
michael@0 | 1101 | if (version == JSVERSION_UNKNOWN) { |
michael@0 | 1102 | return NS_OK; |
michael@0 | 1103 | } |
michael@0 | 1104 | |
michael@0 | 1105 | // New script entry point required, due to the "Create a script" sub-step of |
michael@0 | 1106 | // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block |
michael@0 | 1107 | AutoEntryScript entryScript(globalObject, true, context->GetNativeContext()); |
michael@0 | 1108 | JS::Rooted<JSObject*> global(entryScript.cx(), |
michael@0 | 1109 | globalObject->GetGlobalJSObject()); |
michael@0 | 1110 | |
michael@0 | 1111 | bool oldProcessingScriptTag = context->GetProcessingScriptTag(); |
michael@0 | 1112 | context->SetProcessingScriptTag(true); |
michael@0 | 1113 | |
michael@0 | 1114 | // Update our current script. |
michael@0 | 1115 | nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript; |
michael@0 | 1116 | mCurrentScript = aRequest->mElement; |
michael@0 | 1117 | |
michael@0 | 1118 | JS::CompileOptions options(entryScript.cx()); |
michael@0 | 1119 | FillCompileOptionsForRequest(aRequest, global, &options); |
michael@0 | 1120 | nsresult rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options, |
michael@0 | 1121 | aOffThreadToken); |
michael@0 | 1122 | |
michael@0 | 1123 | // Put the old script back in case it wants to do anything else. |
michael@0 | 1124 | mCurrentScript = oldCurrent; |
michael@0 | 1125 | |
michael@0 | 1126 | context->SetProcessingScriptTag(oldProcessingScriptTag); |
michael@0 | 1127 | return rv; |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | void |
michael@0 | 1131 | nsScriptLoader::ProcessPendingRequestsAsync() |
michael@0 | 1132 | { |
michael@0 | 1133 | if (mParserBlockingRequest || !mPendingChildLoaders.IsEmpty()) { |
michael@0 | 1134 | nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this, |
michael@0 | 1135 | &nsScriptLoader::ProcessPendingRequests); |
michael@0 | 1136 | |
michael@0 | 1137 | NS_DispatchToCurrentThread(ev); |
michael@0 | 1138 | } |
michael@0 | 1139 | } |
michael@0 | 1140 | |
michael@0 | 1141 | void |
michael@0 | 1142 | nsScriptLoader::ProcessPendingRequests() |
michael@0 | 1143 | { |
michael@0 | 1144 | nsRefPtr<nsScriptLoadRequest> request; |
michael@0 | 1145 | if (mParserBlockingRequest && |
michael@0 | 1146 | !mParserBlockingRequest->mLoading && |
michael@0 | 1147 | ReadyToExecuteScripts()) { |
michael@0 | 1148 | request.swap(mParserBlockingRequest); |
michael@0 | 1149 | UnblockParser(request); |
michael@0 | 1150 | ProcessRequest(request); |
michael@0 | 1151 | ContinueParserAsync(request); |
michael@0 | 1152 | } |
michael@0 | 1153 | |
michael@0 | 1154 | while (ReadyToExecuteScripts() && |
michael@0 | 1155 | !mXSLTRequests.IsEmpty() && |
michael@0 | 1156 | !mXSLTRequests[0]->mLoading) { |
michael@0 | 1157 | request.swap(mXSLTRequests[0]); |
michael@0 | 1158 | mXSLTRequests.RemoveElementAt(0); |
michael@0 | 1159 | ProcessRequest(request); |
michael@0 | 1160 | } |
michael@0 | 1161 | |
michael@0 | 1162 | uint32_t i = 0; |
michael@0 | 1163 | while (mEnabled && i < mAsyncRequests.Length()) { |
michael@0 | 1164 | if (!mAsyncRequests[i]->mLoading) { |
michael@0 | 1165 | request.swap(mAsyncRequests[i]); |
michael@0 | 1166 | mAsyncRequests.RemoveElementAt(i); |
michael@0 | 1167 | ProcessRequest(request); |
michael@0 | 1168 | continue; |
michael@0 | 1169 | } |
michael@0 | 1170 | ++i; |
michael@0 | 1171 | } |
michael@0 | 1172 | |
michael@0 | 1173 | while (mEnabled && !mNonAsyncExternalScriptInsertedRequests.IsEmpty() && |
michael@0 | 1174 | !mNonAsyncExternalScriptInsertedRequests[0]->mLoading) { |
michael@0 | 1175 | // Violate the HTML5 spec and execute these in the insertion order in |
michael@0 | 1176 | // order to make LABjs and the "order" plug-in for RequireJS work with |
michael@0 | 1177 | // their Gecko-sniffed code path. See |
michael@0 | 1178 | // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html |
michael@0 | 1179 | request.swap(mNonAsyncExternalScriptInsertedRequests[0]); |
michael@0 | 1180 | mNonAsyncExternalScriptInsertedRequests.RemoveElementAt(0); |
michael@0 | 1181 | ProcessRequest(request); |
michael@0 | 1182 | } |
michael@0 | 1183 | |
michael@0 | 1184 | if (mDocumentParsingDone && mXSLTRequests.IsEmpty()) { |
michael@0 | 1185 | while (!mDeferRequests.IsEmpty() && !mDeferRequests[0]->mLoading) { |
michael@0 | 1186 | request.swap(mDeferRequests[0]); |
michael@0 | 1187 | mDeferRequests.RemoveElementAt(0); |
michael@0 | 1188 | ProcessRequest(request); |
michael@0 | 1189 | } |
michael@0 | 1190 | } |
michael@0 | 1191 | |
michael@0 | 1192 | while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) { |
michael@0 | 1193 | nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0]; |
michael@0 | 1194 | mPendingChildLoaders.RemoveElementAt(0); |
michael@0 | 1195 | child->RemoveExecuteBlocker(); |
michael@0 | 1196 | } |
michael@0 | 1197 | |
michael@0 | 1198 | if (mDocumentParsingDone && mDocument && |
michael@0 | 1199 | !mParserBlockingRequest && mAsyncRequests.IsEmpty() && |
michael@0 | 1200 | mNonAsyncExternalScriptInsertedRequests.IsEmpty() && |
michael@0 | 1201 | mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) { |
michael@0 | 1202 | if (MaybeRemovedDeferRequests()) { |
michael@0 | 1203 | return ProcessPendingRequests(); |
michael@0 | 1204 | } |
michael@0 | 1205 | // No more pending scripts; time to unblock onload. |
michael@0 | 1206 | // OK to unblock onload synchronously here, since callers must be |
michael@0 | 1207 | // prepared for the world changing anyway. |
michael@0 | 1208 | mDocumentParsingDone = false; |
michael@0 | 1209 | mDocument->UnblockOnload(true); |
michael@0 | 1210 | } |
michael@0 | 1211 | } |
michael@0 | 1212 | |
michael@0 | 1213 | bool |
michael@0 | 1214 | nsScriptLoader::ReadyToExecuteScripts() |
michael@0 | 1215 | { |
michael@0 | 1216 | // Make sure the SelfReadyToExecuteScripts check is first, so that |
michael@0 | 1217 | // we don't block twice on an ancestor. |
michael@0 | 1218 | if (!SelfReadyToExecuteScripts()) { |
michael@0 | 1219 | return false; |
michael@0 | 1220 | } |
michael@0 | 1221 | |
michael@0 | 1222 | for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) { |
michael@0 | 1223 | nsScriptLoader* ancestor = doc->ScriptLoader(); |
michael@0 | 1224 | if (!ancestor->SelfReadyToExecuteScripts() && |
michael@0 | 1225 | ancestor->AddPendingChildLoader(this)) { |
michael@0 | 1226 | AddExecuteBlocker(); |
michael@0 | 1227 | return false; |
michael@0 | 1228 | } |
michael@0 | 1229 | } |
michael@0 | 1230 | |
michael@0 | 1231 | return true; |
michael@0 | 1232 | } |
michael@0 | 1233 | |
michael@0 | 1234 | |
michael@0 | 1235 | // This function was copied from nsParser.cpp. It was simplified a bit. |
michael@0 | 1236 | static bool |
michael@0 | 1237 | DetectByteOrderMark(const unsigned char* aBytes, int32_t aLen, nsCString& oCharset) |
michael@0 | 1238 | { |
michael@0 | 1239 | if (aLen < 2) |
michael@0 | 1240 | return false; |
michael@0 | 1241 | |
michael@0 | 1242 | switch(aBytes[0]) { |
michael@0 | 1243 | case 0xEF: |
michael@0 | 1244 | if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) { |
michael@0 | 1245 | // EF BB BF |
michael@0 | 1246 | // Win2K UTF-8 BOM |
michael@0 | 1247 | oCharset.Assign("UTF-8"); |
michael@0 | 1248 | } |
michael@0 | 1249 | break; |
michael@0 | 1250 | case 0xFE: |
michael@0 | 1251 | if (0xFF == aBytes[1]) { |
michael@0 | 1252 | // FE FF |
michael@0 | 1253 | // UTF-16, big-endian |
michael@0 | 1254 | oCharset.Assign("UTF-16BE"); |
michael@0 | 1255 | } |
michael@0 | 1256 | break; |
michael@0 | 1257 | case 0xFF: |
michael@0 | 1258 | if (0xFE == aBytes[1]) { |
michael@0 | 1259 | // FF FE |
michael@0 | 1260 | // UTF-16, little-endian |
michael@0 | 1261 | oCharset.Assign("UTF-16LE"); |
michael@0 | 1262 | } |
michael@0 | 1263 | break; |
michael@0 | 1264 | } |
michael@0 | 1265 | return !oCharset.IsEmpty(); |
michael@0 | 1266 | } |
michael@0 | 1267 | |
michael@0 | 1268 | /* static */ nsresult |
michael@0 | 1269 | nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData, |
michael@0 | 1270 | uint32_t aLength, const nsAString& aHintCharset, |
michael@0 | 1271 | nsIDocument* aDocument, |
michael@0 | 1272 | jschar*& aBufOut, size_t& aLengthOut) |
michael@0 | 1273 | { |
michael@0 | 1274 | if (!aLength) { |
michael@0 | 1275 | aBufOut = nullptr; |
michael@0 | 1276 | aLengthOut = 0; |
michael@0 | 1277 | return NS_OK; |
michael@0 | 1278 | } |
michael@0 | 1279 | |
michael@0 | 1280 | // The encoding info precedence is as follows from high to low: |
michael@0 | 1281 | // The BOM |
michael@0 | 1282 | // HTTP Content-Type (if name recognized) |
michael@0 | 1283 | // charset attribute (if name recognized) |
michael@0 | 1284 | // The encoding of the document |
michael@0 | 1285 | |
michael@0 | 1286 | nsAutoCString charset; |
michael@0 | 1287 | |
michael@0 | 1288 | nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder; |
michael@0 | 1289 | |
michael@0 | 1290 | if (DetectByteOrderMark(aData, aLength, charset)) { |
michael@0 | 1291 | // charset is now "UTF-8" or "UTF-16". The UTF-16 decoder will re-sniff |
michael@0 | 1292 | // the BOM for endianness. Both the UTF-16 and the UTF-8 decoder will |
michael@0 | 1293 | // take care of swallowing the BOM. |
michael@0 | 1294 | unicodeDecoder = EncodingUtils::DecoderForEncoding(charset); |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | if (!unicodeDecoder && |
michael@0 | 1298 | aChannel && |
michael@0 | 1299 | NS_SUCCEEDED(aChannel->GetContentCharset(charset)) && |
michael@0 | 1300 | EncodingUtils::FindEncodingForLabel(charset, charset)) { |
michael@0 | 1301 | unicodeDecoder = EncodingUtils::DecoderForEncoding(charset); |
michael@0 | 1302 | } |
michael@0 | 1303 | |
michael@0 | 1304 | if (!unicodeDecoder && |
michael@0 | 1305 | EncodingUtils::FindEncodingForLabel(aHintCharset, charset)) { |
michael@0 | 1306 | unicodeDecoder = EncodingUtils::DecoderForEncoding(charset); |
michael@0 | 1307 | } |
michael@0 | 1308 | |
michael@0 | 1309 | if (!unicodeDecoder && aDocument) { |
michael@0 | 1310 | charset = aDocument->GetDocumentCharacterSet(); |
michael@0 | 1311 | unicodeDecoder = EncodingUtils::DecoderForEncoding(charset); |
michael@0 | 1312 | } |
michael@0 | 1313 | |
michael@0 | 1314 | if (!unicodeDecoder) { |
michael@0 | 1315 | // Curiously, there are various callers that don't pass aDocument. The |
michael@0 | 1316 | // fallback in the old code was ISO-8859-1, which behaved like |
michael@0 | 1317 | // windows-1252. Saying windows-1252 for clarity and for compliance |
michael@0 | 1318 | // with the Encoding Standard. |
michael@0 | 1319 | unicodeDecoder = EncodingUtils::DecoderForEncoding("windows-1252"); |
michael@0 | 1320 | } |
michael@0 | 1321 | |
michael@0 | 1322 | int32_t unicodeLength = 0; |
michael@0 | 1323 | |
michael@0 | 1324 | nsresult rv = |
michael@0 | 1325 | unicodeDecoder->GetMaxLength(reinterpret_cast<const char*>(aData), |
michael@0 | 1326 | aLength, &unicodeLength); |
michael@0 | 1327 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1328 | |
michael@0 | 1329 | aBufOut = static_cast<jschar*>(js_malloc(unicodeLength * sizeof(jschar))); |
michael@0 | 1330 | if (!aBufOut) { |
michael@0 | 1331 | aLengthOut = 0; |
michael@0 | 1332 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1333 | } |
michael@0 | 1334 | aLengthOut = unicodeLength; |
michael@0 | 1335 | |
michael@0 | 1336 | rv = unicodeDecoder->Convert(reinterpret_cast<const char*>(aData), |
michael@0 | 1337 | (int32_t *) &aLength, aBufOut, |
michael@0 | 1338 | &unicodeLength); |
michael@0 | 1339 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
michael@0 | 1340 | aLengthOut = unicodeLength; |
michael@0 | 1341 | if (NS_FAILED(rv)) { |
michael@0 | 1342 | js_free(aBufOut); |
michael@0 | 1343 | aBufOut = nullptr; |
michael@0 | 1344 | aLengthOut = 0; |
michael@0 | 1345 | } |
michael@0 | 1346 | return rv; |
michael@0 | 1347 | } |
michael@0 | 1348 | |
michael@0 | 1349 | NS_IMETHODIMP |
michael@0 | 1350 | nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, |
michael@0 | 1351 | nsISupports* aContext, |
michael@0 | 1352 | nsresult aStatus, |
michael@0 | 1353 | uint32_t aStringLen, |
michael@0 | 1354 | const uint8_t* aString) |
michael@0 | 1355 | { |
michael@0 | 1356 | nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext); |
michael@0 | 1357 | NS_ASSERTION(request, "null request in stream complete handler"); |
michael@0 | 1358 | NS_ENSURE_TRUE(request, NS_ERROR_FAILURE); |
michael@0 | 1359 | |
michael@0 | 1360 | nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, |
michael@0 | 1361 | aString); |
michael@0 | 1362 | if (NS_FAILED(rv)) { |
michael@0 | 1363 | if (mDeferRequests.RemoveElement(request) || |
michael@0 | 1364 | mAsyncRequests.RemoveElement(request) || |
michael@0 | 1365 | mNonAsyncExternalScriptInsertedRequests.RemoveElement(request) || |
michael@0 | 1366 | mXSLTRequests.RemoveElement(request)) { |
michael@0 | 1367 | FireScriptAvailable(rv, request); |
michael@0 | 1368 | } else if (mParserBlockingRequest == request) { |
michael@0 | 1369 | mParserBlockingRequest = nullptr; |
michael@0 | 1370 | UnblockParser(request); |
michael@0 | 1371 | FireScriptAvailable(rv, request); |
michael@0 | 1372 | ContinueParserAsync(request); |
michael@0 | 1373 | } else { |
michael@0 | 1374 | mPreloads.RemoveElement(request, PreloadRequestComparator()); |
michael@0 | 1375 | } |
michael@0 | 1376 | rv = NS_OK; |
michael@0 | 1377 | } else { |
michael@0 | 1378 | NS_Free(const_cast<uint8_t *>(aString)); |
michael@0 | 1379 | rv = NS_SUCCESS_ADOPTED_DATA; |
michael@0 | 1380 | } |
michael@0 | 1381 | |
michael@0 | 1382 | // Process our request and/or any pending ones |
michael@0 | 1383 | ProcessPendingRequests(); |
michael@0 | 1384 | |
michael@0 | 1385 | return rv; |
michael@0 | 1386 | } |
michael@0 | 1387 | |
michael@0 | 1388 | void |
michael@0 | 1389 | nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest) |
michael@0 | 1390 | { |
michael@0 | 1391 | aParserBlockingRequest->mElement->UnblockParser(); |
michael@0 | 1392 | } |
michael@0 | 1393 | |
michael@0 | 1394 | void |
michael@0 | 1395 | nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest) |
michael@0 | 1396 | { |
michael@0 | 1397 | aParserBlockingRequest->mElement->ContinueParserAsync(); |
michael@0 | 1398 | } |
michael@0 | 1399 | |
michael@0 | 1400 | nsresult |
michael@0 | 1401 | nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest, |
michael@0 | 1402 | nsIStreamLoader* aLoader, |
michael@0 | 1403 | nsresult aStatus, |
michael@0 | 1404 | uint32_t aStringLen, |
michael@0 | 1405 | const uint8_t* aString) |
michael@0 | 1406 | { |
michael@0 | 1407 | if (NS_FAILED(aStatus)) { |
michael@0 | 1408 | return aStatus; |
michael@0 | 1409 | } |
michael@0 | 1410 | |
michael@0 | 1411 | // If we don't have a document, then we need to abort further |
michael@0 | 1412 | // evaluation. |
michael@0 | 1413 | if (!mDocument) { |
michael@0 | 1414 | return NS_ERROR_NOT_AVAILABLE; |
michael@0 | 1415 | } |
michael@0 | 1416 | |
michael@0 | 1417 | // If the load returned an error page, then we need to abort |
michael@0 | 1418 | nsCOMPtr<nsIRequest> req; |
michael@0 | 1419 | nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); |
michael@0 | 1420 | NS_ASSERTION(req, "StreamLoader's request went away prematurely"); |
michael@0 | 1421 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1422 | |
michael@0 | 1423 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req); |
michael@0 | 1424 | if (httpChannel) { |
michael@0 | 1425 | bool requestSucceeded; |
michael@0 | 1426 | rv = httpChannel->GetRequestSucceeded(&requestSucceeded); |
michael@0 | 1427 | if (NS_SUCCEEDED(rv) && !requestSucceeded) { |
michael@0 | 1428 | return NS_ERROR_NOT_AVAILABLE; |
michael@0 | 1429 | } |
michael@0 | 1430 | |
michael@0 | 1431 | nsAutoCString sourceMapURL; |
michael@0 | 1432 | httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL); |
michael@0 | 1433 | aRequest->mHasSourceMapURL = true; |
michael@0 | 1434 | aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL); |
michael@0 | 1435 | } |
michael@0 | 1436 | |
michael@0 | 1437 | nsCOMPtr<nsIChannel> channel = do_QueryInterface(req); |
michael@0 | 1438 | // If this load was subject to a CORS check; don't flag it with a |
michael@0 | 1439 | // separate origin principal, so that it will treat our document's |
michael@0 | 1440 | // principal as the origin principal |
michael@0 | 1441 | if (aRequest->mCORSMode == CORS_NONE) { |
michael@0 | 1442 | rv = nsContentUtils::GetSecurityManager()-> |
michael@0 | 1443 | GetChannelPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal)); |
michael@0 | 1444 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1445 | } |
michael@0 | 1446 | |
michael@0 | 1447 | if (aStringLen) { |
michael@0 | 1448 | // Check the charset attribute to determine script charset. |
michael@0 | 1449 | nsAutoString hintCharset; |
michael@0 | 1450 | if (!aRequest->IsPreload()) { |
michael@0 | 1451 | aRequest->mElement->GetScriptCharset(hintCharset); |
michael@0 | 1452 | } else { |
michael@0 | 1453 | nsTArray<PreloadInfo>::index_type i = |
michael@0 | 1454 | mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator()); |
michael@0 | 1455 | NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping"); |
michael@0 | 1456 | hintCharset = mPreloads[i].mCharset; |
michael@0 | 1457 | } |
michael@0 | 1458 | rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument, |
michael@0 | 1459 | aRequest->mScriptTextBuf, aRequest->mScriptTextLength); |
michael@0 | 1460 | |
michael@0 | 1461 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1462 | } |
michael@0 | 1463 | |
michael@0 | 1464 | // This assertion could fire errorously if we ran out of memory when |
michael@0 | 1465 | // inserting the request in the array. However it's an unlikely case |
michael@0 | 1466 | // so if you see this assertion it is likely something else that is |
michael@0 | 1467 | // wrong, especially if you see it more than once. |
michael@0 | 1468 | NS_ASSERTION(mDeferRequests.Contains(aRequest) || |
michael@0 | 1469 | mAsyncRequests.Contains(aRequest) || |
michael@0 | 1470 | mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) || |
michael@0 | 1471 | mXSLTRequests.Contains(aRequest) || |
michael@0 | 1472 | mPreloads.Contains(aRequest, PreloadRequestComparator()) || |
michael@0 | 1473 | mParserBlockingRequest, |
michael@0 | 1474 | "aRequest should be pending!"); |
michael@0 | 1475 | |
michael@0 | 1476 | // Mark this as loaded |
michael@0 | 1477 | aRequest->mLoading = false; |
michael@0 | 1478 | |
michael@0 | 1479 | return NS_OK; |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | void |
michael@0 | 1483 | nsScriptLoader::ParsingComplete(bool aTerminated) |
michael@0 | 1484 | { |
michael@0 | 1485 | if (mDeferEnabled) { |
michael@0 | 1486 | // Have to check because we apparently get ParsingComplete |
michael@0 | 1487 | // without BeginDeferringScripts in some cases |
michael@0 | 1488 | mDocumentParsingDone = true; |
michael@0 | 1489 | } |
michael@0 | 1490 | mDeferEnabled = false; |
michael@0 | 1491 | if (aTerminated) { |
michael@0 | 1492 | mDeferRequests.Clear(); |
michael@0 | 1493 | mAsyncRequests.Clear(); |
michael@0 | 1494 | mNonAsyncExternalScriptInsertedRequests.Clear(); |
michael@0 | 1495 | mXSLTRequests.Clear(); |
michael@0 | 1496 | mParserBlockingRequest = nullptr; |
michael@0 | 1497 | } |
michael@0 | 1498 | |
michael@0 | 1499 | // Have to call this even if aTerminated so we'll correctly unblock |
michael@0 | 1500 | // onload and all. |
michael@0 | 1501 | ProcessPendingRequests(); |
michael@0 | 1502 | } |
michael@0 | 1503 | |
michael@0 | 1504 | void |
michael@0 | 1505 | nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset, |
michael@0 | 1506 | const nsAString &aType, |
michael@0 | 1507 | const nsAString &aCrossOrigin, |
michael@0 | 1508 | bool aScriptFromHead) |
michael@0 | 1509 | { |
michael@0 | 1510 | // Check to see if scripts has been turned off. |
michael@0 | 1511 | if (!mEnabled || !mDocument->IsScriptEnabled()) { |
michael@0 | 1512 | return; |
michael@0 | 1513 | } |
michael@0 | 1514 | |
michael@0 | 1515 | nsRefPtr<nsScriptLoadRequest> request = |
michael@0 | 1516 | new nsScriptLoadRequest(nullptr, 0, |
michael@0 | 1517 | Element::StringToCORSMode(aCrossOrigin)); |
michael@0 | 1518 | request->mURI = aURI; |
michael@0 | 1519 | request->mIsInline = false; |
michael@0 | 1520 | request->mLoading = true; |
michael@0 | 1521 | nsresult rv = StartLoad(request, aType, aScriptFromHead); |
michael@0 | 1522 | if (NS_FAILED(rv)) { |
michael@0 | 1523 | return; |
michael@0 | 1524 | } |
michael@0 | 1525 | |
michael@0 | 1526 | PreloadInfo *pi = mPreloads.AppendElement(); |
michael@0 | 1527 | pi->mRequest = request; |
michael@0 | 1528 | pi->mCharset = aCharset; |
michael@0 | 1529 | } |
michael@0 | 1530 | |
michael@0 | 1531 | void |
michael@0 | 1532 | nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest) |
michael@0 | 1533 | { |
michael@0 | 1534 | mDeferRequests.AppendElement(aRequest); |
michael@0 | 1535 | if (mDeferEnabled && mDeferRequests.Length() == 1 && mDocument && |
michael@0 | 1536 | !mBlockingDOMContentLoaded) { |
michael@0 | 1537 | MOZ_ASSERT(mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING); |
michael@0 | 1538 | mBlockingDOMContentLoaded = true; |
michael@0 | 1539 | mDocument->BlockDOMContentLoaded(); |
michael@0 | 1540 | } |
michael@0 | 1541 | } |
michael@0 | 1542 | |
michael@0 | 1543 | bool |
michael@0 | 1544 | nsScriptLoader::MaybeRemovedDeferRequests() |
michael@0 | 1545 | { |
michael@0 | 1546 | if (mDeferRequests.Length() == 0 && mDocument && |
michael@0 | 1547 | mBlockingDOMContentLoaded) { |
michael@0 | 1548 | mBlockingDOMContentLoaded = false; |
michael@0 | 1549 | mDocument->UnblockDOMContentLoaded(); |
michael@0 | 1550 | return true; |
michael@0 | 1551 | } |
michael@0 | 1552 | return false; |
michael@0 | 1553 | } |