michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:set et cin sw=2 sts=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: /* michael@0: * A base class implementing nsIObjectLoadingContent for use by michael@0: * various content nodes that want to provide plugin/document/image michael@0: * loading functionality (eg , , , etc). michael@0: */ michael@0: michael@0: // Interface headers michael@0: #include "imgLoader.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMCustomEvent.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMHTMLObjectElement.h" michael@0: #include "nsIDOMHTMLAppletElement.h" michael@0: #include "nsIExternalProtocolHandler.h" michael@0: #include "nsIObjectFrame.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsPluginHost.h" michael@0: #include "nsPluginInstanceOwner.h" michael@0: #include "nsJSNPRuntime.h" michael@0: #include "nsINestedURI.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsScriptSecurityManager.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsIURILoader.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIWebNavigationInfo.h" michael@0: #include "nsIScriptChannel.h" michael@0: #include "nsIBlocklistService.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "nsIAppShell.h" michael@0: michael@0: #include "nsError.h" michael@0: michael@0: // Util headers michael@0: #include "prenv.h" michael@0: #include "prlog.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsDocShellCID.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsSandboxFlags.h" michael@0: michael@0: // Concrete classes michael@0: #include "nsFrameLoader.h" michael@0: michael@0: #include "nsObjectLoadingContent.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsIChannelPolicy.h" michael@0: #include "nsChannelPolicy.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsObjectFrame.h" michael@0: #include "nsDOMClassInfo.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "nsDOMJSUtils.h" michael@0: michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsContentCID.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Event.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #ifdef XP_WIN michael@0: // Thanks so much, Microsoft! :( michael@0: #ifdef CreateEvent michael@0: #undef CreateEvent michael@0: #endif michael@0: #endif // XP_WIN michael@0: michael@0: static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: michael@0: static const char *kPrefJavaMIME = "plugin.java.mime"; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* michael@0: GetObjectLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("objlc"); michael@0: return sLog; michael@0: } michael@0: #endif michael@0: michael@0: #define LOG(args) PR_LOG(GetObjectLog(), PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(GetObjectLog(), PR_LOG_DEBUG) michael@0: michael@0: static bool michael@0: InActiveDocument(nsIContent *aContent) michael@0: { michael@0: if (!aContent->IsInDoc()) { michael@0: return false; michael@0: } michael@0: nsIDocument *doc = aContent->OwnerDoc(); michael@0: return (doc && doc->IsActive()); michael@0: } michael@0: michael@0: /// michael@0: /// Runnables and helper classes michael@0: /// michael@0: michael@0: class nsAsyncInstantiateEvent : public nsRunnable { michael@0: public: michael@0: nsAsyncInstantiateEvent(nsObjectLoadingContent *aContent) michael@0: : mContent(aContent) {} michael@0: michael@0: ~nsAsyncInstantiateEvent() {} michael@0: michael@0: NS_IMETHOD Run(); michael@0: michael@0: private: michael@0: nsCOMPtr mContent; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsAsyncInstantiateEvent::Run() michael@0: { michael@0: nsObjectLoadingContent *objLC = michael@0: static_cast(mContent.get()); michael@0: michael@0: // If objLC is no longer tracking this event, we've been canceled or michael@0: // superseded michael@0: if (objLC->mPendingInstantiateEvent != this) { michael@0: return NS_OK; michael@0: } michael@0: objLC->mPendingInstantiateEvent = nullptr; michael@0: michael@0: return objLC->SyncStartPluginInstance(); michael@0: } michael@0: michael@0: // Checks to see if the content for a plugin instance should be unloaded michael@0: // (outside an active document) or stopped (in a document but unrendered). This michael@0: // is used to allow scripts to move a plugin around the document hierarchy michael@0: // without re-instantiating it. michael@0: class CheckPluginStopEvent : public nsRunnable { michael@0: public: michael@0: CheckPluginStopEvent(nsObjectLoadingContent *aContent) michael@0: : mContent(aContent) {} michael@0: michael@0: ~CheckPluginStopEvent() {} michael@0: michael@0: NS_IMETHOD Run(); michael@0: michael@0: private: michael@0: nsCOMPtr mContent; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: CheckPluginStopEvent::Run() michael@0: { michael@0: nsObjectLoadingContent *objLC = michael@0: static_cast(mContent.get()); michael@0: michael@0: // If objLC is no longer tracking this event, we've been canceled or michael@0: // superseded. We clear this before we finish - either by calling michael@0: // UnloadObject/StopPluginInstance, or directly if we took no action. michael@0: if (objLC->mPendingCheckPluginStopEvent != this) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr content = michael@0: do_QueryInterface(static_cast(objLC)); michael@0: if (!InActiveDocument(content)) { michael@0: // Unload the object entirely michael@0: LOG(("OBJLC [%p]: Unloading plugin outside of document", this)); michael@0: objLC->UnloadObject(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!content->GetPrimaryFrame()) { michael@0: LOG(("OBJLC [%p]: CheckPluginStopEvent - No frame, flushing layout", this)); michael@0: nsIDocument* currentDoc = content->GetCurrentDoc(); michael@0: if (currentDoc) { michael@0: currentDoc->FlushPendingNotifications(Flush_Layout); michael@0: if (objLC->mPendingCheckPluginStopEvent != this) { michael@0: LOG(("OBJLC [%p]: CheckPluginStopEvent - superseded in layout flush", michael@0: this)); michael@0: return NS_OK; michael@0: } else if (content->GetPrimaryFrame()) { michael@0: LOG(("OBJLC [%p]: CheckPluginStopEvent - frame gained in layout flush", michael@0: this)); michael@0: objLC->mPendingCheckPluginStopEvent = nullptr; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // Still no frame, suspend plugin. HasNewFrame will restart us when we michael@0: // become rendered again michael@0: LOG(("OBJLC [%p]: Stopping plugin that lost frame", this)); michael@0: // Okay to leave loaded as a plugin, but stop the unrendered instance michael@0: objLC->StopPluginInstance(); michael@0: } michael@0: michael@0: objLC->mPendingCheckPluginStopEvent = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Helper task for firing simple events michael@0: */ michael@0: class nsSimplePluginEvent : public nsRunnable { michael@0: public: michael@0: nsSimplePluginEvent(nsIContent* aTarget, const nsAString &aEvent) michael@0: : mTarget(aTarget) michael@0: , mDocument(aTarget->GetCurrentDoc()) michael@0: , mEvent(aEvent) michael@0: { michael@0: MOZ_ASSERT(aTarget && mDocument); michael@0: } michael@0: michael@0: nsSimplePluginEvent(nsIDocument* aTarget, const nsAString& aEvent) michael@0: : mTarget(aTarget) michael@0: , mDocument(aTarget) michael@0: , mEvent(aEvent) michael@0: { michael@0: MOZ_ASSERT(aTarget); michael@0: } michael@0: michael@0: nsSimplePluginEvent(nsIContent* aTarget, michael@0: nsIDocument* aDocument, michael@0: const nsAString& aEvent) michael@0: : mTarget(aTarget) michael@0: , mDocument(aDocument) michael@0: , mEvent(aEvent) michael@0: { michael@0: MOZ_ASSERT(aTarget && aDocument); michael@0: } michael@0: michael@0: ~nsSimplePluginEvent() {} michael@0: michael@0: NS_IMETHOD Run(); michael@0: michael@0: private: michael@0: nsCOMPtr mTarget; michael@0: nsCOMPtr mDocument; michael@0: nsString mEvent; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsSimplePluginEvent::Run() michael@0: { michael@0: if (mDocument && mDocument->IsActive()) { michael@0: LOG(("OBJLC [%p]: nsSimplePluginEvent firing event \"%s\"", mTarget.get(), michael@0: NS_ConvertUTF16toUTF8(mEvent).get())); michael@0: nsContentUtils::DispatchTrustedEvent(mDocument, mTarget, michael@0: mEvent, true, true); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * A task for firing PluginCrashed DOM Events. michael@0: */ michael@0: class nsPluginCrashedEvent : public nsRunnable { michael@0: public: michael@0: nsCOMPtr mContent; michael@0: nsString mPluginDumpID; michael@0: nsString mBrowserDumpID; michael@0: nsString mPluginName; michael@0: nsString mPluginFilename; michael@0: bool mSubmittedCrashReport; michael@0: michael@0: nsPluginCrashedEvent(nsIContent* aContent, michael@0: const nsAString& aPluginDumpID, michael@0: const nsAString& aBrowserDumpID, michael@0: const nsAString& aPluginName, michael@0: const nsAString& aPluginFilename, michael@0: bool submittedCrashReport) michael@0: : mContent(aContent), michael@0: mPluginDumpID(aPluginDumpID), michael@0: mBrowserDumpID(aBrowserDumpID), michael@0: mPluginName(aPluginName), michael@0: mPluginFilename(aPluginFilename), michael@0: mSubmittedCrashReport(submittedCrashReport) michael@0: {} michael@0: michael@0: ~nsPluginCrashedEvent() {} michael@0: michael@0: NS_IMETHOD Run(); michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginCrashedEvent::Run() michael@0: { michael@0: LOG(("OBJLC [%p]: Firing plugin crashed event\n", michael@0: mContent.get())); michael@0: michael@0: nsCOMPtr doc = mContent->GetDocument(); michael@0: if (!doc) { michael@0: NS_WARNING("Couldn't get document for PluginCrashed event!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: nsRefPtr event = michael@0: doc->CreateEvent(NS_LITERAL_STRING("customevent"), rv); michael@0: nsCOMPtr customEvent(do_QueryObject(event)); michael@0: if (!customEvent) { michael@0: NS_WARNING("Couldn't QI event for PluginCrashed event!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr variant; michael@0: variant = do_CreateInstance("@mozilla.org/variant;1"); michael@0: if (!variant) { michael@0: NS_WARNING("Couldn't create detail variant for PluginCrashed event!"); michael@0: return NS_OK; michael@0: } michael@0: customEvent->InitCustomEvent(NS_LITERAL_STRING("PluginCrashed"), michael@0: true, true, variant); michael@0: event->SetTrusted(true); michael@0: event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true; michael@0: michael@0: nsCOMPtr propBag; michael@0: propBag = do_CreateInstance("@mozilla.org/hash-property-bag;1"); michael@0: if (!propBag) { michael@0: NS_WARNING("Couldn't create a property bag for PluginCrashed event!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // add a "pluginDumpID" property to this event michael@0: propBag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), michael@0: mPluginDumpID); michael@0: michael@0: // add a "browserDumpID" property to this event michael@0: propBag->SetPropertyAsAString(NS_LITERAL_STRING("browserDumpID"), michael@0: mBrowserDumpID); michael@0: michael@0: // add a "pluginName" property to this event michael@0: propBag->SetPropertyAsAString(NS_LITERAL_STRING("pluginName"), michael@0: mPluginName); michael@0: michael@0: // add a "pluginFilename" property to this event michael@0: propBag->SetPropertyAsAString(NS_LITERAL_STRING("pluginFilename"), michael@0: mPluginFilename); michael@0: michael@0: // add a "submittedCrashReport" property to this event michael@0: propBag->SetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"), michael@0: mSubmittedCrashReport); michael@0: michael@0: variant->SetAsISupports(propBag); michael@0: michael@0: EventDispatcher::DispatchDOMEvent(mContent, nullptr, event, nullptr, nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class nsStopPluginRunnable : public nsRunnable, public nsITimerCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: nsStopPluginRunnable(nsPluginInstanceOwner* aInstanceOwner, michael@0: nsObjectLoadingContent* aContent) michael@0: : mInstanceOwner(aInstanceOwner) michael@0: , mContent(aContent) michael@0: { michael@0: NS_ASSERTION(aInstanceOwner, "need an owner"); michael@0: NS_ASSERTION(aContent, "need a nsObjectLoadingContent"); michael@0: } michael@0: michael@0: // nsRunnable michael@0: NS_IMETHOD Run(); michael@0: michael@0: // nsITimerCallback michael@0: NS_IMETHOD Notify(nsITimer *timer); michael@0: michael@0: private: michael@0: nsCOMPtr mTimer; michael@0: nsRefPtr mInstanceOwner; michael@0: nsCOMPtr mContent; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsStopPluginRunnable, nsRunnable, nsITimerCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: nsStopPluginRunnable::Notify(nsITimer *aTimer) michael@0: { michael@0: return Run(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStopPluginRunnable::Run() michael@0: { michael@0: // InitWithCallback calls Release before AddRef so we need to hold a michael@0: // strong ref on 'this' since we fall through to this scope if it fails. michael@0: nsCOMPtr kungFuDeathGrip = this; michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: if (appShell) { michael@0: uint32_t currentLevel = 0; michael@0: appShell->GetEventloopNestingLevel(¤tLevel); michael@0: if (currentLevel > mInstanceOwner->GetLastEventloopNestingLevel()) { michael@0: if (!mTimer) michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (mTimer) { michael@0: // Fire 100ms timer to try to tear down this plugin as quickly as michael@0: // possible once the nesting level comes back down. michael@0: nsresult rv = mTimer->InitWithCallback(this, 100, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: NS_ERROR("Failed to setup a timer to stop the plugin later (at a safe " michael@0: "time). Stopping the plugin now, this might crash."); michael@0: } michael@0: } michael@0: michael@0: mTimer = nullptr; michael@0: michael@0: static_cast(mContent.get())-> michael@0: DoStopPlugin(mInstanceOwner, false, true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // You can't take the address of bitfield members, so we have two separate michael@0: // classes for these :-/ michael@0: michael@0: // Sets a object's mInstantiating bit to false when destroyed michael@0: class AutoSetInstantiatingToFalse { michael@0: public: michael@0: AutoSetInstantiatingToFalse(nsObjectLoadingContent *aContent) michael@0: : mContent(aContent) {} michael@0: ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; } michael@0: private: michael@0: nsObjectLoadingContent* mContent; michael@0: }; michael@0: michael@0: // Sets a object's mInstantiating bit to false when destroyed michael@0: class AutoSetLoadingToFalse { michael@0: public: michael@0: AutoSetLoadingToFalse(nsObjectLoadingContent *aContent) michael@0: : mContent(aContent) {} michael@0: ~AutoSetLoadingToFalse() { mContent->mIsLoading = false; } michael@0: private: michael@0: nsObjectLoadingContent* mContent; michael@0: }; michael@0: michael@0: /// michael@0: /// Helper functions michael@0: /// michael@0: michael@0: static bool michael@0: IsSuccessfulRequest(nsIRequest* aRequest) michael@0: { michael@0: nsresult status; michael@0: nsresult rv = aRequest->GetStatus(&status); michael@0: if (NS_FAILED(rv) || NS_FAILED(status)) { michael@0: return false; michael@0: } michael@0: michael@0: // This may still be an error page or somesuch michael@0: nsCOMPtr httpChan(do_QueryInterface(aRequest)); michael@0: if (httpChan) { michael@0: bool success; michael@0: rv = httpChan->GetRequestSucceeded(&success); michael@0: if (NS_FAILED(rv) || !success) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Otherwise, the request is successful michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CanHandleURI(nsIURI* aURI) michael@0: { michael@0: nsAutoCString scheme; michael@0: if (NS_FAILED(aURI->GetScheme(scheme))) { michael@0: return false; michael@0: } michael@0: michael@0: nsIIOService* ios = nsContentUtils::GetIOService(); michael@0: if (!ios) michael@0: return false; michael@0: michael@0: nsCOMPtr handler; michael@0: ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); michael@0: if (!handler) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr extHandler = michael@0: do_QueryInterface(handler); michael@0: // We can handle this URI if its protocol handler is not the external one michael@0: return extHandler == nullptr; michael@0: } michael@0: michael@0: // Helper for tedious URI equality syntax when one or both arguments may be michael@0: // null and URIEquals(null, null) should be true michael@0: static bool inline michael@0: URIEquals(nsIURI *a, nsIURI *b) michael@0: { michael@0: bool equal; michael@0: return (!a && !b) || (a && b && NS_SUCCEEDED(a->Equals(b, &equal)) && equal); michael@0: } michael@0: michael@0: static bool michael@0: IsSupportedImage(const nsCString& aMimeType) michael@0: { michael@0: return imgLoader::SupportImageWithMimeType(aMimeType.get()); michael@0: } michael@0: michael@0: static void michael@0: GetExtensionFromURI(nsIURI* uri, nsCString& ext) michael@0: { michael@0: nsCOMPtr url(do_QueryInterface(uri)); michael@0: if (url) { michael@0: url->GetFileExtension(ext); michael@0: } else { michael@0: nsCString spec; michael@0: uri->GetSpec(spec); michael@0: michael@0: int32_t offset = spec.RFindChar('.'); michael@0: if (offset != kNotFound) { michael@0: ext = Substring(spec, offset + 1, spec.Length()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Checks whether a plugin exists and is enabled for the extension michael@0: * in the given URI. The MIME type is returned in the mimeType out parameter. michael@0: */ michael@0: bool michael@0: IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType) michael@0: { michael@0: nsAutoCString ext; michael@0: GetExtensionFromURI(uri, ext); michael@0: michael@0: if (ext.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: michael@0: if (!pluginHost) { michael@0: NS_NOTREACHED("No pluginhost"); michael@0: return false; michael@0: } michael@0: michael@0: const char* typeFromExt; michael@0: nsresult rv = pluginHost->IsPluginEnabledForExtension(ext.get(), typeFromExt); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mimeType = typeFromExt; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: PluginExistsForType(const char* aMIMEType) michael@0: { michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: michael@0: if (!pluginHost) { michael@0: NS_NOTREACHED("No pluginhost"); michael@0: return false; michael@0: } michael@0: michael@0: return pluginHost->PluginExistsForType(aMIMEType); michael@0: } michael@0: michael@0: /// michael@0: /// Member Functions michael@0: /// michael@0: michael@0: // Helper to queue a CheckPluginStopEvent for a OBJLC object michael@0: void michael@0: nsObjectLoadingContent::QueueCheckPluginStopEvent() michael@0: { michael@0: nsCOMPtr event = new CheckPluginStopEvent(this); michael@0: mPendingCheckPluginStopEvent = event; michael@0: michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: if (appShell) { michael@0: appShell->RunInStableState(event); michael@0: } michael@0: } michael@0: michael@0: // Tedious syntax to create a plugin stream listener with checks and put it in michael@0: // mFinalListener michael@0: bool michael@0: nsObjectLoadingContent::MakePluginListener() michael@0: { michael@0: if (!mInstanceOwner) { michael@0: NS_NOTREACHED("expecting a spawned plugin"); michael@0: return false; michael@0: } michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: if (!pluginHost) { michael@0: NS_NOTREACHED("No pluginHost"); michael@0: return false; michael@0: } michael@0: NS_ASSERTION(!mFinalListener, "overwriting a final listener"); michael@0: nsresult rv; michael@0: nsRefPtr inst; michael@0: nsCOMPtr finalListener; michael@0: rv = mInstanceOwner->GetInstance(getter_AddRefs(inst)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: rv = pluginHost->NewPluginStreamListener(mURI, inst, michael@0: getter_AddRefs(finalListener)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: mFinalListener = finalListener; michael@0: return true; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsObjectLoadingContent::IsSupportedDocument(const nsCString& aMimeType) michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "must be a content"); michael@0: michael@0: nsCOMPtr info( michael@0: do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID)); michael@0: if (!info) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr webNav; michael@0: nsIDocument* currentDoc = thisContent->GetCurrentDoc(); michael@0: if (currentDoc) { michael@0: webNav = do_GetInterface(currentDoc->GetWindow()); michael@0: } michael@0: michael@0: uint32_t supported; michael@0: nsresult rv = info->IsTypeSupported(aMimeType, webNav, &supported); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: if (supported != nsIWebNavigationInfo::UNSUPPORTED) { michael@0: // Don't want to support plugins as documents michael@0: return supported != nsIWebNavigationInfo::PLUGIN; michael@0: } michael@0: michael@0: // Try a stream converter michael@0: // NOTE: We treat any type we can convert from as a supported type. If a michael@0: // type is not actually supported, the URI loader will detect that and michael@0: // return an error, and we'll fallback. michael@0: nsCOMPtr convServ = michael@0: do_GetService("@mozilla.org/streamConverters;1"); michael@0: bool canConvert = false; michael@0: if (convServ) { michael@0: rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert); michael@0: } michael@0: return NS_SUCCEEDED(rv) && canConvert; michael@0: } michael@0: michael@0: nsresult michael@0: nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, michael@0: nsIContent* aParent, michael@0: nsIContent* aBindingParent, michael@0: bool aCompileEventHandlers) michael@0: { michael@0: nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent, michael@0: aCompileEventHandlers); michael@0: michael@0: if (aDocument) { michael@0: return aDocument->AddPlugin(this); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) michael@0: { michael@0: nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent); michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: MOZ_ASSERT(thisContent); michael@0: nsIDocument* ownerDoc = thisContent->OwnerDoc(); michael@0: ownerDoc->RemovePlugin(this); michael@0: michael@0: if (mType == eType_Plugin && (mInstanceOwner || mInstantiating)) { michael@0: // we'll let the plugin continue to run at least until we get back to michael@0: // the event loop. If we get back to the event loop and the node michael@0: // has still not been added back to the document then we tear down the michael@0: // plugin michael@0: QueueCheckPluginStopEvent(); michael@0: } else if (mType != eType_Image) { michael@0: // nsImageLoadingContent handles the image case. michael@0: // Reset state and clear pending events michael@0: /// XXX(johns): The implementation for GenericFrame notes that ideally we michael@0: /// would keep the docshell around, but trash the frameloader michael@0: UnloadObject(); michael@0: } michael@0: nsIDocument* doc = thisContent->GetCurrentDoc(); michael@0: if (doc && doc->IsActive()) { michael@0: nsCOMPtr ev = new nsSimplePluginEvent(doc, michael@0: NS_LITERAL_STRING("PluginRemoved")); michael@0: NS_DispatchToCurrentThread(ev); michael@0: } michael@0: } michael@0: michael@0: nsObjectLoadingContent::nsObjectLoadingContent() michael@0: : mType(eType_Loading) michael@0: , mFallbackType(eFallbackAlternate) michael@0: , mChannelLoaded(false) michael@0: , mInstantiating(false) michael@0: , mNetworkCreated(true) michael@0: , mActivated(false) michael@0: , mPlayPreviewCanceled(false) michael@0: , mIsStopping(false) michael@0: , mIsLoading(false) michael@0: , mScriptRequested(false) {} michael@0: michael@0: nsObjectLoadingContent::~nsObjectLoadingContent() michael@0: { michael@0: // Should have been unbound from the tree at this point, and michael@0: // CheckPluginStopEvent keeps us alive michael@0: if (mFrameLoader) { michael@0: NS_NOTREACHED("Should not be tearing down frame loaders at this point"); michael@0: mFrameLoader->Destroy(); michael@0: } michael@0: if (mInstanceOwner || mInstantiating) { michael@0: // This is especially bad as delayed stop will try to hold on to this michael@0: // object... michael@0: NS_NOTREACHED("Should not be tearing down a plugin at this point!"); michael@0: StopPluginInstance(); michael@0: } michael@0: DestroyImageLoadingContent(); michael@0: } michael@0: michael@0: nsresult michael@0: nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) michael@0: { michael@0: if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) || michael@0: mInstantiating) { michael@0: // If we hit this assertion it's probably because LoadObject re-entered :( michael@0: // michael@0: // XXX(johns): This hackiness will go away in bug 767635 michael@0: NS_ASSERTION(mIsLoading || !aIsLoading, michael@0: "aIsLoading should only be true inside LoadObject"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mInstantiating = true; michael@0: AutoSetInstantiatingToFalse autoInstantiating(this); michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: nsCOMPtr doc = thisContent->GetCurrentDoc(); michael@0: if (!doc || !InActiveDocument(thisContent)) { michael@0: NS_ERROR("Shouldn't be calling " michael@0: "InstantiatePluginInstance without an active document"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Instantiating an instance can result in script execution, which michael@0: // can destroy this DOM object. Don't allow that for the scope michael@0: // of this method. michael@0: nsCOMPtr kungFuDeathGrip = this; michael@0: michael@0: // Flush layout so that the frame is created if possible and the plugin is michael@0: // initialized with the latest information. michael@0: doc->FlushPendingNotifications(Flush_Layout); michael@0: // Flushing layout may have re-entered and loaded something underneath us michael@0: NS_ENSURE_TRUE(mInstantiating, NS_OK); michael@0: michael@0: if (!thisContent->GetPrimaryFrame()) { michael@0: LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: michael@0: if (!pluginHost) { michael@0: NS_NOTREACHED("No pluginhost"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If you add early return(s), be sure to balance this call to michael@0: // appShell->SuspendNative() with additional call(s) to michael@0: // appShell->ReturnNative(). michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: if (appShell) { michael@0: appShell->SuspendNative(); michael@0: } michael@0: michael@0: nsRefPtr newOwner; michael@0: rv = pluginHost->InstantiatePluginInstance(mContentType.get(), michael@0: mURI.get(), this, michael@0: getter_AddRefs(newOwner)); michael@0: michael@0: // XXX(johns): We don't suspend native inside stopping plugins... michael@0: if (appShell) { michael@0: appShell->ResumeNative(); michael@0: } michael@0: michael@0: if (!mInstantiating || NS_FAILED(rv)) { michael@0: LOG(("OBJLC [%p]: Plugin instantiation failed or re-entered, " michael@0: "killing old instance", this)); michael@0: // XXX(johns): This needs to be de-duplicated with DoStopPlugin, but we michael@0: // don't want to touch the protochain or delayed stop. michael@0: // (Bug 767635) michael@0: if (newOwner) { michael@0: nsRefPtr inst; michael@0: newOwner->GetInstance(getter_AddRefs(inst)); michael@0: newOwner->SetFrame(nullptr); michael@0: if (inst) { michael@0: pluginHost->StopPluginInstance(inst); michael@0: } michael@0: newOwner->Destroy(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: mInstanceOwner = newOwner; michael@0: michael@0: // Ensure the frame did not change during instantiation re-entry (common). michael@0: // HasNewFrame would not have mInstanceOwner yet, so the new frame would be michael@0: // dangling. (Bug 854082) michael@0: nsIFrame* frame = thisContent->GetPrimaryFrame(); michael@0: if (frame && mInstanceOwner) { michael@0: mInstanceOwner->SetFrame(static_cast(frame)); michael@0: michael@0: // Bug 870216 - Adobe Reader renders with incorrect dimensions until it gets michael@0: // a second SetWindow call. This is otherwise redundant. michael@0: mInstanceOwner->CallSetWindow(); michael@0: } michael@0: michael@0: // Set up scripting interfaces. michael@0: NotifyContentObjectWrapper(); michael@0: michael@0: nsRefPtr pluginInstance; michael@0: GetPluginInstance(getter_AddRefs(pluginInstance)); michael@0: if (pluginInstance) { michael@0: nsCOMPtr pluginTag; michael@0: pluginHost->GetPluginTagForInstance(pluginInstance, michael@0: getter_AddRefs(pluginTag)); michael@0: michael@0: nsCOMPtr blocklist = michael@0: do_GetService("@mozilla.org/extensions/blocklist;1"); michael@0: if (blocklist) { michael@0: uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED; michael@0: blocklist->GetPluginBlocklistState(pluginTag, EmptyString(), michael@0: EmptyString(), &blockState); michael@0: if (blockState == nsIBlocklistService::STATE_OUTDATED) { michael@0: // Fire plugin outdated event if necessary michael@0: LOG(("OBJLC [%p]: Dispatching plugin outdated event for content %p\n", michael@0: this)); michael@0: nsCOMPtr ev = new nsSimplePluginEvent(thisContent, michael@0: NS_LITERAL_STRING("PluginOutdated")); michael@0: nsresult rv = NS_DispatchToCurrentThread(ev); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("failed to dispatch nsSimplePluginEvent"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we have a URI but didn't open a channel yet (eAllowPluginSkipChannel) michael@0: // or we did load with a channel but are re-instantiating, re-open the michael@0: // channel. OpenChannel() performs security checks, and this plugin has michael@0: // already passed content policy in LoadObject. michael@0: if ((mURI && !mChannelLoaded) || (mChannelLoaded && !aIsLoading)) { michael@0: NS_ASSERTION(!mChannel, "should not have an existing channel here"); michael@0: // We intentionally ignore errors here, leaving it up to the plugin to michael@0: // deal with not having an initial stream. michael@0: OpenChannel(); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr ev = \ michael@0: new nsSimplePluginEvent(thisContent, michael@0: doc, michael@0: NS_LITERAL_STRING("PluginInstantiated")); michael@0: NS_DispatchToCurrentThread(ev); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged() michael@0: { michael@0: // XXX(johns): We cannot touch plugins or run arbitrary script from this call, michael@0: // as nsDocument is in a non-reentrant state. michael@0: michael@0: // If we have a plugin we want to queue an event to stop it unless we are michael@0: // moved into an active document before returning to the event loop. michael@0: if (mInstanceOwner || mInstantiating) michael@0: QueueCheckPluginStopEvent(); michael@0: } michael@0: michael@0: // nsIRequestObserver michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: PROFILER_LABEL("nsObjectLoadingContent", "OnStartRequest"); michael@0: michael@0: LOG(("OBJLC [%p]: Channel OnStartRequest", this)); michael@0: michael@0: if (aRequest != mChannel || !aRequest) { michael@0: // happens when a new load starts before the previous one got here michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: // If we already switched to type plugin, this channel can just be passed to michael@0: // the final listener. michael@0: if (mType == eType_Plugin) { michael@0: if (!mInstanceOwner) { michael@0: // We drop mChannel when stopping plugins, so something is wrong michael@0: NS_NOTREACHED("Opened a channel in plugin mode, but don't have a plugin"); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: if (MakePluginListener()) { michael@0: return mFinalListener->OnStartRequest(aRequest, nullptr); michael@0: } else { michael@0: NS_NOTREACHED("Failed to create PluginStreamListener, aborting channel"); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: } michael@0: michael@0: // Otherwise we should be state loading, and call LoadObject with the channel michael@0: if (mType != eType_Loading) { michael@0: NS_NOTREACHED("Should be type loading at this point"); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?"); michael@0: NS_ASSERTION(!mFinalListener, "mFinalListener exists already?"); michael@0: michael@0: mChannelLoaded = true; michael@0: michael@0: nsCOMPtr chan(do_QueryInterface(aRequest)); michael@0: NS_ASSERTION(chan, "Why is our request not a channel?"); michael@0: michael@0: nsCOMPtr uri; michael@0: michael@0: if (IsSuccessfulRequest(aRequest)) { michael@0: chan->GetURI(getter_AddRefs(uri)); michael@0: } michael@0: michael@0: if (!uri) { michael@0: LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this)); michael@0: // If the request fails, we still call LoadObject() to handle fallback michael@0: // content and notifying of failure. (mChannelLoaded && !mChannel) indicates michael@0: // the bad state. michael@0: mChannel = nullptr; michael@0: LoadObject(true, false); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return LoadObject(true, false, aRequest); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatusCode) michael@0: { michael@0: NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: if (aRequest != mChannel) { michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: mChannel = nullptr; michael@0: michael@0: if (mFinalListener) { michael@0: // This may re-enter in the case of plugin listeners michael@0: nsCOMPtr listenerGrip(mFinalListener); michael@0: mFinalListener = nullptr; michael@0: listenerGrip->OnStopRequest(aRequest, aContext, aStatusCode); michael@0: } michael@0: michael@0: // Return value doesn't matter michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsIStreamListener michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aInputStream, michael@0: uint64_t aOffset, uint32_t aCount) michael@0: { michael@0: NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: if (aRequest != mChannel) { michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: if (mFinalListener) { michael@0: // This may re-enter in the case of plugin listeners michael@0: nsCOMPtr listenerGrip(mFinalListener); michael@0: return listenerGrip->OnDataAvailable(aRequest, aContext, aInputStream, michael@0: aOffset, aCount); michael@0: } michael@0: michael@0: // We shouldn't have a connected channel with no final listener michael@0: NS_NOTREACHED("Got data for channel with no connected final listener"); michael@0: mChannel = nullptr; michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // nsIFrameLoaderOwner michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetFrameLoader(nsIFrameLoader** aFrameLoader) michael@0: { michael@0: NS_IF_ADDREF(*aFrameLoader = mFrameLoader); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(already_AddRefed) michael@0: nsObjectLoadingContent::GetFrameLoader() michael@0: { michael@0: nsRefPtr loader = mFrameLoader; michael@0: return loader.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoader) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetActualType(nsACString& aType) michael@0: { michael@0: aType = mContentType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetDisplayedType(uint32_t* aType) michael@0: { michael@0: *aType = DisplayedType(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::HasNewFrame(nsIObjectFrame* aFrame) michael@0: { michael@0: if (mType != eType_Plugin) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!aFrame) { michael@0: // Lost our frame. If we aren't going to be getting a new frame, e.g. we've michael@0: // become display:none, we'll want to stop the plugin. Queue a michael@0: // CheckPluginStopEvent michael@0: if (mInstanceOwner || mInstantiating) { michael@0: if (mInstanceOwner) { michael@0: mInstanceOwner->SetFrame(nullptr); michael@0: } michael@0: QueueCheckPluginStopEvent(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Have a new frame michael@0: michael@0: if (!mInstanceOwner) { michael@0: // We are successfully setup as type plugin, but have not spawned an michael@0: // instance due to a lack of a frame. michael@0: AsyncStartPluginInstance(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise, we're just changing frames michael@0: // Set up relationship between instance owner and frame. michael@0: nsObjectFrame *objFrame = static_cast(aFrame); michael@0: mInstanceOwner->SetFrame(objFrame); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetPluginInstance(nsNPAPIPluginInstance** aInstance) michael@0: { michael@0: *aInstance = nullptr; michael@0: michael@0: if (!mInstanceOwner) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mInstanceOwner->GetInstance(aInstance); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetContentTypeForMIMEType(const nsACString& aMIMEType, michael@0: uint32_t* aType) michael@0: { michael@0: *aType = GetTypeOfContent(PromiseFlatCString(aMIMEType)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetBaseURI(nsIURI **aResult) michael@0: { michael@0: NS_IF_ADDREF(*aResult = mBaseURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIInterfaceRequestor michael@0: // We use a shim class to implement this so that JS consumers still michael@0: // see an interface requestor even though WebIDL bindings don't expose michael@0: // that stuff. michael@0: class ObjectInterfaceRequestorShim MOZ_FINAL : public nsIInterfaceRequestor, michael@0: public nsIChannelEventSink, michael@0: public nsIStreamListener michael@0: { michael@0: public: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ObjectInterfaceRequestorShim, michael@0: nsIInterfaceRequestor) michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: // nsRefPtr fails due to ambiguous AddRef/Release, michael@0: // hence the ugly static cast :( michael@0: NS_FORWARD_NSICHANNELEVENTSINK(static_cast michael@0: (mContent.get())->) michael@0: NS_FORWARD_NSISTREAMLISTENER (static_cast michael@0: (mContent.get())->) michael@0: NS_FORWARD_NSIREQUESTOBSERVER (static_cast michael@0: (mContent.get())->) michael@0: michael@0: ObjectInterfaceRequestorShim(nsIObjectLoadingContent* aContent) michael@0: : mContent(aContent) michael@0: {} michael@0: michael@0: protected: michael@0: nsCOMPtr mContent; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(ObjectInterfaceRequestorShim, mContent) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ObjectInterfaceRequestorShim) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(ObjectInterfaceRequestorShim) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(ObjectInterfaceRequestorShim) michael@0: michael@0: NS_IMETHODIMP michael@0: ObjectInterfaceRequestorShim::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: nsIChannelEventSink* sink = this; michael@0: *aResult = sink; michael@0: NS_ADDREF(sink); michael@0: return NS_OK; michael@0: } michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: // nsIChannelEventSink michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *cb) michael@0: { michael@0: // If we're already busy with a new load, or have no load at all, michael@0: // cancel the redirect. michael@0: if (!mChannel || aOldChannel != mChannel) { michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: mChannel = aNewChannel; michael@0: cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: EventStates michael@0: nsObjectLoadingContent::ObjectState() const michael@0: { michael@0: switch (mType) { michael@0: case eType_Loading: michael@0: return NS_EVENT_STATE_LOADING; michael@0: case eType_Image: michael@0: return ImageState(); michael@0: case eType_Plugin: michael@0: case eType_Document: michael@0: // These are OK. If documents start to load successfully, they display michael@0: // something, and are thus not broken in this sense. The same goes for michael@0: // plugins. michael@0: return EventStates(); michael@0: case eType_Null: michael@0: switch (mFallbackType) { michael@0: case eFallbackSuppressed: michael@0: return NS_EVENT_STATE_SUPPRESSED; michael@0: case eFallbackUserDisabled: michael@0: return NS_EVENT_STATE_USERDISABLED; michael@0: case eFallbackClickToPlay: michael@0: return NS_EVENT_STATE_TYPE_CLICK_TO_PLAY; michael@0: case eFallbackPlayPreview: michael@0: return NS_EVENT_STATE_TYPE_PLAY_PREVIEW; michael@0: case eFallbackDisabled: michael@0: return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_DISABLED; michael@0: case eFallbackBlocklisted: michael@0: return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_BLOCKED; michael@0: case eFallbackCrashed: michael@0: return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_CRASHED; michael@0: case eFallbackUnsupported: { michael@0: // Check to see if plugins are blocked on this platform. michael@0: char* pluginsBlocked = PR_GetEnv("MOZ_PLUGINS_BLOCKED"); michael@0: if (pluginsBlocked && pluginsBlocked[0] == '1') { michael@0: return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_TYPE_UNSUPPORTED_PLATFORM; michael@0: } else { michael@0: return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_TYPE_UNSUPPORTED; michael@0: } michael@0: } michael@0: case eFallbackOutdated: michael@0: case eFallbackAlternate: michael@0: return NS_EVENT_STATE_BROKEN; michael@0: case eFallbackVulnerableUpdatable: michael@0: return NS_EVENT_STATE_VULNERABLE_UPDATABLE; michael@0: case eFallbackVulnerableNoUpdate: michael@0: return NS_EVENT_STATE_VULNERABLE_NO_UPDATE; michael@0: } michael@0: }; michael@0: NS_NOTREACHED("unknown type?"); michael@0: return NS_EVENT_STATE_LOADING; michael@0: } michael@0: michael@0: // Returns false if mBaseURI is not acceptable for java applets. michael@0: bool michael@0: nsObjectLoadingContent::CheckJavaCodebase() michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: nsCOMPtr secMan = michael@0: nsContentUtils::GetSecurityManager(); michael@0: nsCOMPtr netutil = do_GetNetUtil(); michael@0: NS_ASSERTION(thisContent && secMan && netutil, "expected interfaces"); michael@0: michael@0: michael@0: // Note that mBaseURI is this tag's requested base URI, not the codebase of michael@0: // the document for security purposes michael@0: nsresult rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), michael@0: mBaseURI, 0); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("OBJLC [%p]: Java codebase check failed", this)); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr principalBaseURI; michael@0: rv = thisContent->NodePrincipal()->GetURI(getter_AddRefs(principalBaseURI)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("Failed to URI from node principal?"); michael@0: return false; michael@0: } michael@0: // We currently allow java's codebase to be non-same-origin, with michael@0: // the exception of URIs that represent local files michael@0: if (NS_URIIsLocalFile(mBaseURI) && michael@0: nsScriptSecurityManager::GetStrictFileOriginPolicy() && michael@0: !NS_RelaxStrictFileOriginPolicy(mBaseURI, principalBaseURI, true)) { michael@0: LOG(("OBJLC [%p]: Java failed RelaxStrictFileOriginPolicy for file URI", michael@0: this)); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsObjectLoadingContent::CheckLoadPolicy(int16_t *aContentPolicy) michael@0: { michael@0: if (!aContentPolicy || !mURI) { michael@0: NS_NOTREACHED("Doing it wrong"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "Must be an instance of content"); michael@0: michael@0: nsIDocument* doc = thisContent->OwnerDoc(); michael@0: michael@0: *aContentPolicy = nsIContentPolicy::ACCEPT; michael@0: nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OBJECT, michael@0: mURI, michael@0: doc->NodePrincipal(), michael@0: thisContent, michael@0: mContentType, michael@0: nullptr, //extra michael@0: aContentPolicy, michael@0: nsContentUtils::GetContentPolicy(), michael@0: nsContentUtils::GetSecurityManager()); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (NS_CP_REJECTED(*aContentPolicy)) { michael@0: nsAutoCString uri; michael@0: nsAutoCString baseUri; michael@0: mURI->GetSpec(uri); michael@0: mURI->GetSpec(baseUri); michael@0: LOG(("OBJLC [%p]: Content policy denied load of %s (base %s)", michael@0: this, uri.get(), baseUri.get())); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsObjectLoadingContent::CheckProcessPolicy(int16_t *aContentPolicy) michael@0: { michael@0: if (!aContentPolicy) { michael@0: NS_NOTREACHED("Null out variable"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "Must be an instance of content"); michael@0: michael@0: nsIDocument* doc = thisContent->OwnerDoc(); michael@0: michael@0: int32_t objectType; michael@0: switch (mType) { michael@0: case eType_Image: michael@0: objectType = nsIContentPolicy::TYPE_IMAGE; michael@0: break; michael@0: case eType_Document: michael@0: objectType = nsIContentPolicy::TYPE_DOCUMENT; michael@0: break; michael@0: case eType_Plugin: michael@0: objectType = nsIContentPolicy::TYPE_OBJECT; michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("Calling checkProcessPolicy with a unloadable type"); michael@0: return false; michael@0: } michael@0: michael@0: *aContentPolicy = nsIContentPolicy::ACCEPT; michael@0: nsresult rv = michael@0: NS_CheckContentProcessPolicy(objectType, michael@0: mURI ? mURI : mBaseURI, michael@0: doc->NodePrincipal(), michael@0: static_cast(this), michael@0: mContentType, michael@0: nullptr, //extra michael@0: aContentPolicy, michael@0: nsContentUtils::GetContentPolicy(), michael@0: nsContentUtils::GetSecurityManager()); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (NS_CP_REJECTED(*aContentPolicy)) { michael@0: LOG(("OBJLC [%p]: CheckContentProcessPolicy rejected load", this)); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsObjectLoadingContent::ParameterUpdateFlags michael@0: nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "Must be an instance of content"); michael@0: michael@0: uint32_t caps = GetCapabilities(); michael@0: LOG(("OBJLC [%p]: Updating object parameters", this)); michael@0: michael@0: nsresult rv; michael@0: nsAutoCString newMime; michael@0: nsAutoString typeAttr; michael@0: nsCOMPtr newURI; michael@0: nsCOMPtr newBaseURI; michael@0: ObjectType newType; michael@0: bool isJava = false; michael@0: // Set if this state can't be used to load anything, forces eType_Null michael@0: bool stateInvalid = false; michael@0: // Indicates what parameters changed. michael@0: // eParamChannelChanged - means parameters that affect channel opening michael@0: // decisions changed michael@0: // eParamStateChanged - means anything that affects what content we load michael@0: // changed, even if the channel we'd open remains the michael@0: // same. michael@0: // michael@0: // State changes outside of the channel parameters only matter if we've michael@0: // already opened a channel or tried to instantiate content, whereas channel michael@0: // parameter changes require re-opening the channel even if we haven't gotten michael@0: // that far. michael@0: nsObjectLoadingContent::ParameterUpdateFlags retval = eParamNoChange; michael@0: michael@0: /// michael@0: /// Initial MIME Type michael@0: /// michael@0: michael@0: if (aJavaURI || thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) { michael@0: nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); michael@0: newMime = javaMIME; michael@0: NS_ASSERTION(nsPluginHost::IsJavaMIMEType(newMime.get()), michael@0: "plugin.mime.java should be recognized by IsJavaMIMEType"); michael@0: isJava = true; michael@0: } else { michael@0: nsAutoString rawTypeAttr; michael@0: thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, rawTypeAttr); michael@0: if (!rawTypeAttr.IsEmpty()) { michael@0: typeAttr = rawTypeAttr; michael@0: CopyUTF16toUTF8(rawTypeAttr, newMime); michael@0: isJava = nsPluginHost::IsJavaMIMEType(newMime.get()); michael@0: } michael@0: } michael@0: michael@0: /// michael@0: /// classID michael@0: /// michael@0: michael@0: if (caps & eSupportClassID) { michael@0: nsAutoString classIDAttr; michael@0: thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classIDAttr); michael@0: if (!classIDAttr.IsEmpty()) { michael@0: // Our classid support is limited to 'java:' ids michael@0: nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); michael@0: NS_ASSERTION(nsPluginHost::IsJavaMIMEType(javaMIME.get()), michael@0: "plugin.mime.java should be recognized by IsJavaMIMEType"); michael@0: if (StringBeginsWith(classIDAttr, NS_LITERAL_STRING("java:")) && michael@0: PluginExistsForType(javaMIME)) { michael@0: newMime = javaMIME; michael@0: isJava = true; michael@0: } else { michael@0: // XXX(johns): Our de-facto behavior since forever was to refuse to load michael@0: // Objects who don't have a classid we support, regardless of other type michael@0: // or uri info leads to a valid plugin. michael@0: newMime.Truncate(); michael@0: stateInvalid = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /// michael@0: /// Codebase michael@0: /// michael@0: michael@0: nsAutoString codebaseStr; michael@0: nsCOMPtr docBaseURI = thisContent->GetBaseURI(); michael@0: bool hasCodebase = thisContent->HasAttr(kNameSpaceID_None, nsGkAtoms::codebase); michael@0: if (hasCodebase) michael@0: thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebaseStr); michael@0: michael@0: michael@0: // Java wants the codebase attribute even if it occurs in tags michael@0: // XXX(johns): This duplicates a chunk of code from nsInstanceOwner, see michael@0: // bug 853995 michael@0: if (isJava) { michael@0: // Find all tags that are nested beneath us, but not beneath another michael@0: // object/applet tag. michael@0: nsCOMArray ourParams; michael@0: nsCOMPtr mydomElement = do_QueryInterface(thisContent); michael@0: michael@0: nsCOMPtr allParams; michael@0: NS_NAMED_LITERAL_STRING(xhtml_ns, "http://www.w3.org/1999/xhtml"); michael@0: mydomElement->GetElementsByTagNameNS(xhtml_ns, NS_LITERAL_STRING("param"), michael@0: getter_AddRefs(allParams)); michael@0: if (allParams) { michael@0: uint32_t numAllParams; michael@0: allParams->GetLength(&numAllParams); michael@0: for (uint32_t i = 0; i < numAllParams; i++) { michael@0: nsCOMPtr pnode; michael@0: allParams->Item(i, getter_AddRefs(pnode)); michael@0: nsCOMPtr domelement = do_QueryInterface(pnode); michael@0: if (domelement) { michael@0: nsAutoString name; michael@0: domelement->GetAttribute(NS_LITERAL_STRING("name"), name); michael@0: name.Trim(" \n\r\t\b", true, true, false); michael@0: if (name.EqualsIgnoreCase("codebase")) { michael@0: // Find the first plugin element parent michael@0: nsCOMPtr parent; michael@0: nsCOMPtr domobject; michael@0: nsCOMPtr domapplet; michael@0: pnode->GetParentNode(getter_AddRefs(parent)); michael@0: while (!(domobject || domapplet) && parent) { michael@0: domobject = do_QueryInterface(parent); michael@0: domapplet = do_QueryInterface(parent); michael@0: nsCOMPtr temp; michael@0: parent->GetParentNode(getter_AddRefs(temp)); michael@0: parent = temp; michael@0: } michael@0: if (domapplet || domobject) { michael@0: if (domapplet) { michael@0: parent = do_QueryInterface(domapplet); michael@0: } michael@0: else { michael@0: parent = do_QueryInterface(domobject); michael@0: } michael@0: nsCOMPtr mydomNode = do_QueryInterface(mydomElement); michael@0: if (parent == mydomNode) { michael@0: hasCodebase = true; michael@0: domelement->GetAttribute(NS_LITERAL_STRING("value"), michael@0: codebaseStr); michael@0: codebaseStr.Trim(" \n\r\t\b", true, true, false); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (isJava && hasCodebase && codebaseStr.IsEmpty()) { michael@0: // Java treats codebase="" as "/" michael@0: codebaseStr.AssignLiteral("/"); michael@0: // XXX(johns): This doesn't cover the case of "https:" which java would michael@0: // interpret as "https:///" but we interpret as this document's michael@0: // URI but with a changed scheme. michael@0: } else if (isJava && !hasCodebase) { michael@0: // Java expects a directory as the codebase, or else it will construct michael@0: // relative URIs incorrectly :( michael@0: codebaseStr.AssignLiteral("."); michael@0: } michael@0: michael@0: if (!codebaseStr.IsEmpty()) { michael@0: rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(newBaseURI), michael@0: codebaseStr, michael@0: thisContent->OwnerDoc(), michael@0: docBaseURI); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: NS_TryToSetImmutable(newBaseURI); michael@0: } else { michael@0: // Malformed URI michael@0: LOG(("OBJLC [%p]: Could not parse plugin's codebase as a URI, " michael@0: "will use document baseURI instead", this)); michael@0: } michael@0: } michael@0: michael@0: // If we failed to build a valid URI, use the document's base URI michael@0: if (!newBaseURI) { michael@0: newBaseURI = docBaseURI; michael@0: } michael@0: michael@0: /// michael@0: /// URI michael@0: /// michael@0: michael@0: nsAutoString uriStr; michael@0: // Different elements keep this in various locations michael@0: if (isJava) { michael@0: // Applet tags and embed/object with explicit java MIMEs have src/data michael@0: // attributes that are not meant to be parsed as URIs or opened by the michael@0: // browser -- act as if they are null. (Setting these attributes triggers a michael@0: // force-load, so tracking the old value to determine if they have changed michael@0: // is not necessary.) michael@0: } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::object)) { michael@0: thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr); michael@0: } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::embed)) { michael@0: thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr); michael@0: } else { michael@0: // Applet tags should always have a java MIME type at this point michael@0: NS_NOTREACHED("Unrecognized plugin-loading tag"); michael@0: } michael@0: michael@0: // Note that the baseURI changing could affect the newURI, even if uriStr did michael@0: // not change. michael@0: if (!uriStr.IsEmpty()) { michael@0: rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(newURI), michael@0: uriStr, michael@0: thisContent->OwnerDoc(), michael@0: newBaseURI); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: NS_TryToSetImmutable(newURI); michael@0: } else { michael@0: stateInvalid = true; michael@0: } michael@0: } michael@0: michael@0: // For eAllowPluginSkipChannel tags, if we have a non-plugin type, but can get michael@0: // a plugin type from the extension, prefer that to falling back to a channel. michael@0: if (GetTypeOfContent(newMime) != eType_Plugin && newURI && michael@0: (caps & eAllowPluginSkipChannel) && michael@0: IsPluginEnabledByExtension(newURI, newMime)) { michael@0: LOG(("OBJLC [%p]: Using extension as type hint (%s)", this, newMime.get())); michael@0: if (!isJava && nsPluginHost::IsJavaMIMEType(newMime.get())) { michael@0: return UpdateObjectParameters(true); michael@0: } michael@0: } michael@0: michael@0: /// michael@0: /// Check if the original (pre-channel) content-type or URI changed, and michael@0: /// record mOriginal{ContentType,URI} michael@0: /// michael@0: michael@0: if ((mOriginalContentType != newMime) || !URIEquals(mOriginalURI, newURI)) { michael@0: // These parameters changing requires re-opening the channel, so don't michael@0: // consider the currently-open channel below michael@0: // XXX(johns): Changing the mime type might change our decision on whether michael@0: // or not we load a channel, so we count changes to it as a michael@0: // channel parameter change for the sake of simplicity. michael@0: retval = (ParameterUpdateFlags)(retval | eParamChannelChanged); michael@0: LOG(("OBJLC [%p]: Channel parameters changed", this)); michael@0: } michael@0: mOriginalContentType = newMime; michael@0: mOriginalURI = newURI; michael@0: michael@0: /// michael@0: /// If we have a channel, see if its MIME type should take precendence and michael@0: /// check the final (redirected) URL michael@0: /// michael@0: michael@0: // If we have a loaded channel and channel parameters did not change, use it michael@0: // to determine what we would load. michael@0: bool useChannel = mChannelLoaded && !(retval & eParamChannelChanged); michael@0: // If we have a channel and are type loading, as opposed to having an existing michael@0: // channel for a previous load. michael@0: bool newChannel = useChannel && mType == eType_Loading; michael@0: michael@0: if (newChannel && mChannel) { michael@0: nsCString channelType; michael@0: rv = mChannel->GetContentType(channelType); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("GetContentType failed"); michael@0: stateInvalid = true; michael@0: channelType.Truncate(); michael@0: } michael@0: michael@0: LOG(("OBJLC [%p]: Channel has a content type of %s", this, channelType.get())); michael@0: michael@0: bool binaryChannelType = false; michael@0: if (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT)) { michael@0: channelType = APPLICATION_OCTET_STREAM; michael@0: mChannel->SetContentType(channelType); michael@0: binaryChannelType = true; michael@0: } else if (channelType.EqualsASCII(APPLICATION_OCTET_STREAM) michael@0: || channelType.EqualsASCII(BINARY_OCTET_STREAM)) { michael@0: binaryChannelType = true; michael@0: } michael@0: michael@0: // Channel can change our URI through redirection michael@0: rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(newURI)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("NS_GetFinalChannelURI failure"); michael@0: stateInvalid = true; michael@0: } michael@0: michael@0: ObjectType typeHint = newMime.IsEmpty() ? michael@0: eType_Null : GetTypeOfContent(newMime); michael@0: michael@0: // michael@0: // In order of preference: michael@0: // michael@0: // 1) Perform typemustmatch check. michael@0: // If check is sucessful use type without further checks. michael@0: // If check is unsuccessful set stateInvalid to true michael@0: // 2) Use our type hint if it matches a plugin michael@0: // 3) If we have eAllowPluginSkipChannel, use the uri file extension if michael@0: // it matches a plugin michael@0: // 4) If the channel returns a binary stream type: michael@0: // 4a) If we have a type non-null non-document type hint, use that michael@0: // 4b) If the uri file extension matches a plugin type, use that michael@0: // 5) Use the channel type michael@0: michael@0: bool overrideChannelType = false; michael@0: if (thisContent->HasAttr(kNameSpaceID_None, nsGkAtoms::typemustmatch)) { michael@0: if (!typeAttr.LowerCaseEqualsASCII(channelType.get())) { michael@0: stateInvalid = true; michael@0: } michael@0: } else if (typeHint == eType_Plugin) { michael@0: LOG(("OBJLC [%p]: Using plugin type hint in favor of any channel type", michael@0: this)); michael@0: overrideChannelType = true; michael@0: } else if ((caps & eAllowPluginSkipChannel) && michael@0: IsPluginEnabledByExtension(newURI, newMime)) { michael@0: LOG(("OBJLC [%p]: Using extension as type hint for " michael@0: "eAllowPluginSkipChannel tag (%s)", this, newMime.get())); michael@0: overrideChannelType = true; michael@0: } else if (binaryChannelType && michael@0: typeHint != eType_Null && typeHint != eType_Document) { michael@0: LOG(("OBJLC [%p]: Using type hint in favor of binary channel type", michael@0: this)); michael@0: overrideChannelType = true; michael@0: } else if (binaryChannelType && michael@0: IsPluginEnabledByExtension(newURI, newMime)) { michael@0: LOG(("OBJLC [%p]: Using extension as type hint for binary channel (%s)", michael@0: this, newMime.get())); michael@0: overrideChannelType = true; michael@0: } michael@0: michael@0: if (overrideChannelType) { michael@0: // Set the type we'll use for dispatch on the channel. Otherwise we could michael@0: // end up trying to dispatch to a nsFrameLoader, which will complain that michael@0: // it couldn't find a way to handle application/octet-stream michael@0: nsAutoCString parsedMime, dummy; michael@0: NS_ParseContentType(newMime, parsedMime, dummy); michael@0: if (!parsedMime.IsEmpty()) { michael@0: mChannel->SetContentType(parsedMime); michael@0: } michael@0: } else { michael@0: newMime = channelType; michael@0: if (nsPluginHost::IsJavaMIMEType(newMime.get())) { michael@0: // Java does not load with a channel, and being java retroactively michael@0: // changes how we may have interpreted the codebase to construct this michael@0: // URI above. Because the behavior here is more or less undefined, play michael@0: // it safe and reject the load. michael@0: LOG(("OBJLC [%p]: Refusing to load with channel with java MIME", michael@0: this)); michael@0: stateInvalid = true; michael@0: } michael@0: } michael@0: } else if (newChannel) { michael@0: LOG(("OBJLC [%p]: We failed to open a channel, marking invalid", this)); michael@0: stateInvalid = true; michael@0: } michael@0: michael@0: /// michael@0: /// Determine final type michael@0: /// michael@0: // In order of preference: michael@0: // 1) If we have attempted channel load, or set stateInvalid above, the type michael@0: // is always null (fallback) michael@0: // 2) If we have a loaded channel, we grabbed its mimeType above, use that michael@0: // type. michael@0: // 3) If we have a plugin type and no URI, use that type. michael@0: // 4) If we have a plugin type and eAllowPluginSkipChannel, use that type. michael@0: // 5) if we have a URI, set type to loading to indicate we'd need a channel michael@0: // to proceed. michael@0: // 6) Otherwise, type null to indicate unloadable content (fallback) michael@0: // michael@0: michael@0: if (stateInvalid) { michael@0: newType = eType_Null; michael@0: newMime.Truncate(); michael@0: } else if (newChannel) { michael@0: // If newChannel is set above, we considered it in setting newMime michael@0: newType = GetTypeOfContent(newMime); michael@0: LOG(("OBJLC [%p]: Using channel type", this)); michael@0: } else if (((caps & eAllowPluginSkipChannel) || !newURI) && michael@0: GetTypeOfContent(newMime) == eType_Plugin) { michael@0: newType = eType_Plugin; michael@0: LOG(("OBJLC [%p]: Plugin type with no URI, skipping channel load", this)); michael@0: } else if (newURI) { michael@0: // We could potentially load this if we opened a channel on mURI, indicate michael@0: // This by leaving type as loading michael@0: newType = eType_Loading; michael@0: } else { michael@0: // Unloadable - no URI, and no plugin type. Non-plugin types (images, michael@0: // documents) always load with a channel. michael@0: newType = eType_Null; michael@0: } michael@0: michael@0: /// michael@0: /// Handle existing channels michael@0: /// michael@0: michael@0: if (useChannel && newType == eType_Loading) { michael@0: // We decided to use a channel, and also that the previous channel is still michael@0: // usable, so re-use the existing values. michael@0: newType = mType; michael@0: newMime = mContentType; michael@0: newURI = mURI; michael@0: } else if (useChannel && !newChannel) { michael@0: // We have an existing channel, but did not decide to use one. michael@0: retval = (ParameterUpdateFlags)(retval | eParamChannelChanged); michael@0: useChannel = false; michael@0: } michael@0: michael@0: /// michael@0: /// Update changed values michael@0: /// michael@0: michael@0: if (newType != mType) { michael@0: retval = (ParameterUpdateFlags)(retval | eParamStateChanged); michael@0: LOG(("OBJLC [%p]: Type changed from %u -> %u", this, mType, newType)); michael@0: mType = newType; michael@0: } michael@0: michael@0: if (!URIEquals(mBaseURI, newBaseURI)) { michael@0: if (isJava) { michael@0: // Java bases its class loading on the base URI, so we consider the state michael@0: // to have changed if this changes. If the object is using a relative URI, michael@0: // mURI will have changed below regardless michael@0: retval = (ParameterUpdateFlags)(retval | eParamStateChanged); michael@0: } michael@0: LOG(("OBJLC [%p]: Object effective baseURI changed", this)); michael@0: mBaseURI = newBaseURI; michael@0: } michael@0: michael@0: if (!URIEquals(newURI, mURI)) { michael@0: retval = (ParameterUpdateFlags)(retval | eParamStateChanged); michael@0: LOG(("OBJLC [%p]: Object effective URI changed", this)); michael@0: mURI = newURI; michael@0: } michael@0: michael@0: // We don't update content type when loading, as the type is not final and we michael@0: // don't want to superfluously change between mOriginalContentType -> michael@0: // mContentType when doing |obj.data = obj.data| with a channel and differing michael@0: // type. michael@0: if (mType != eType_Loading && mContentType != newMime) { michael@0: retval = (ParameterUpdateFlags)(retval | eParamStateChanged); michael@0: retval = (ParameterUpdateFlags)(retval | eParamContentTypeChanged); michael@0: LOG(("OBJLC [%p]: Object effective mime type changed (%s -> %s)", michael@0: this, mContentType.get(), newMime.get())); michael@0: mContentType = newMime; michael@0: } michael@0: michael@0: // If we decided to keep using info from an old channel, but also that state michael@0: // changed, we need to invalidate it. michael@0: if (useChannel && !newChannel && (retval & eParamStateChanged)) { michael@0: mType = eType_Loading; michael@0: retval = (ParameterUpdateFlags)(retval | eParamChannelChanged); michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // Used by PluginDocument to kick off our initial load from the already-opened michael@0: // channel. michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::InitializeFromChannel(nsIRequest *aChannel) michael@0: { michael@0: LOG(("OBJLC [%p] InitializeFromChannel: %p", this, aChannel)); michael@0: if (mType != eType_Loading || mChannel) { michael@0: // We could technically call UnloadObject() here, if consumers have a valid michael@0: // reason for wanting to call this on an already-loaded tag. michael@0: NS_NOTREACHED("Should not have begun loading at this point"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Because we didn't open this channel from an initial LoadObject, we'll michael@0: // update our parameters now, so the OnStartRequest->LoadObject doesn't michael@0: // believe our src/type suddenly changed. michael@0: UpdateObjectParameters(); michael@0: // But we always want to load from a channel, in this case. michael@0: mType = eType_Loading; michael@0: mChannel = do_QueryInterface(aChannel); michael@0: NS_ASSERTION(mChannel, "passed a request that is not a channel"); michael@0: michael@0: // OnStartRequest will now see we have a channel in the loading state, and michael@0: // call into LoadObject. There's a possibility LoadObject will decide not to michael@0: // load anything from a channel - it will call CloseChannel() in that case. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Only OnStartRequest should be passing the channel parameter michael@0: nsresult michael@0: nsObjectLoadingContent::LoadObject(bool aNotify, michael@0: bool aForceLoad) michael@0: { michael@0: return LoadObject(aNotify, aForceLoad, nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: nsObjectLoadingContent::LoadObject(bool aNotify, michael@0: bool aForceLoad, michael@0: nsIRequest *aLoadingChannel) michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "must be a content"); michael@0: nsIDocument* doc = thisContent->OwnerDoc(); michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Sanity check michael@0: if (!InActiveDocument(thisContent)) { michael@0: NS_NOTREACHED("LoadObject called while not bound to an active document"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // XXX(johns): In these cases, we refuse to touch our content and just michael@0: // remain unloaded, as per legacy behavior. It would make more sense to michael@0: // load fallback content initially and refuse to ever change state again. michael@0: if (doc->IsBeingUsedAsImage() || doc->IsLoadedAsData()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("OBJLC [%p]: LoadObject called, notify %u, forceload %u, channel %p", michael@0: this, aNotify, aForceLoad, aLoadingChannel)); michael@0: michael@0: // We can't re-use an already open channel, but aForceLoad may make us try michael@0: // to load a plugin without any changes in channel state. michael@0: if (aForceLoad && mChannelLoaded) { michael@0: CloseChannel(); michael@0: mChannelLoaded = false; michael@0: } michael@0: michael@0: // Save these for NotifyStateChanged(); michael@0: EventStates oldState = ObjectState(); michael@0: ObjectType oldType = mType; michael@0: michael@0: ParameterUpdateFlags stateChange = UpdateObjectParameters(); michael@0: michael@0: if (!stateChange && !aForceLoad) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: /// michael@0: /// State has changed, unload existing content and attempt to load new type michael@0: /// michael@0: LOG(("OBJLC [%p]: LoadObject - plugin state changed (%u)", michael@0: this, stateChange)); michael@0: michael@0: // Setup fallback info. We may also change type to fallback below in case of michael@0: // sanity/OOM/etc. errors. We default to showing alternate content michael@0: // NOTE LoadFallback can override this in some cases michael@0: FallbackType fallbackType = eFallbackAlternate; michael@0: michael@0: // mType can differ with GetTypeOfContent(mContentType) if we support this michael@0: // type, but the parameters are invalid e.g. a embed tag with type "image/png" michael@0: // but no URI -- don't show a plugin error or unknown type error in that case. michael@0: if (mType == eType_Null && GetTypeOfContent(mContentType) == eType_Null) { michael@0: fallbackType = eFallbackUnsupported; michael@0: } michael@0: michael@0: // Explicit user activation should reset if the object changes content types michael@0: if (mActivated && (stateChange & eParamContentTypeChanged)) { michael@0: LOG(("OBJLC [%p]: Content type changed, clearing activation state", this)); michael@0: mActivated = false; michael@0: } michael@0: michael@0: // We synchronously start/stop plugin instances below, which may spin the michael@0: // event loop. Re-entering into the load is fine, but at that point the michael@0: // original load call needs to abort when unwinding michael@0: // NOTE this is located *after* the state change check, a subseqent load michael@0: // with no subsequently changed state will be a no-op. michael@0: if (mIsLoading) { michael@0: LOG(("OBJLC [%p]: Re-entering into LoadObject", this)); michael@0: } michael@0: mIsLoading = true; michael@0: AutoSetLoadingToFalse reentryCheck(this); michael@0: michael@0: // Unload existing content, keeping in mind stopping plugins might spin the michael@0: // event loop. Note that we check for still-open channels below michael@0: UnloadObject(false); // Don't reset state michael@0: if (!mIsLoading) { michael@0: // The event loop must've spun and re-entered into LoadObject, which michael@0: // finished the load michael@0: LOG(("OBJLC [%p]: Re-entered into LoadObject, aborting outer load", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Determine what's going on with our channel. michael@0: if (stateChange & eParamChannelChanged) { michael@0: // If the channel params changed, throw away the channel, but unset michael@0: // mChannelLoaded so we'll still try to open a new one for this load if michael@0: // necessary michael@0: CloseChannel(); michael@0: mChannelLoaded = false; michael@0: } else if (mType == eType_Null && mChannel) { michael@0: // If we opened a channel but then failed to find a loadable state, throw it michael@0: // away. mChannelLoaded will indicate that we tried to load a channel at one michael@0: // point so we wont recurse michael@0: CloseChannel(); michael@0: } else if (mType == eType_Loading && mChannel) { michael@0: // We're still waiting on a channel load, already opened one, and michael@0: // channel parameters didn't change michael@0: return NS_OK; michael@0: } else if (mChannelLoaded && mChannel != aLoadingChannel) { michael@0: // The only time we should have a loaded channel with a changed state is michael@0: // when the channel has just opened -- in which case this call should michael@0: // have originated from OnStartRequest michael@0: NS_NOTREACHED("Loading with a channel, but state doesn't make sense"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Security checks michael@0: // michael@0: michael@0: if (mType != eType_Null) { michael@0: bool allowLoad = true; michael@0: if (nsPluginHost::IsJavaMIMEType(mContentType.get())) { michael@0: allowLoad = CheckJavaCodebase(); michael@0: } michael@0: int16_t contentPolicy = nsIContentPolicy::ACCEPT; michael@0: // If mChannelLoaded is set we presumably already passed load policy michael@0: if (allowLoad && mURI && !mChannelLoaded) { michael@0: allowLoad = CheckLoadPolicy(&contentPolicy); michael@0: } michael@0: // If we're loading a type now, check ProcessPolicy. Note that we may check michael@0: // both now in the case of plugins whose type is determined before opening a michael@0: // channel. michael@0: if (allowLoad && mType != eType_Loading) { michael@0: allowLoad = CheckProcessPolicy(&contentPolicy); michael@0: } michael@0: michael@0: // Content policy implementations can mutate the DOM, check for re-entry michael@0: if (!mIsLoading) { michael@0: LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load", michael@0: this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Load denied, switch to fallback and set disabled/suppressed if applicable michael@0: if (!allowLoad) { michael@0: LOG(("OBJLC [%p]: Load denied by policy", this)); michael@0: mType = eType_Null; michael@0: if (contentPolicy == nsIContentPolicy::REJECT_TYPE) { michael@0: // XXX(johns) This is assuming that we were rejected by michael@0: // nsContentBlocker, which rejects by type if permissions michael@0: // reject plugins michael@0: fallbackType = eFallbackUserDisabled; michael@0: } else { michael@0: fallbackType = eFallbackSuppressed; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Don't allow view-source scheme. michael@0: // view-source is the only scheme to which this applies at the moment due to michael@0: // potential timing attacks to read data from cross-origin documents. If this michael@0: // widens we should add a protocol flag for whether the scheme is only allowed michael@0: // in top and use something like nsNetUtil::NS_URIChainHasFlags. michael@0: if (mType != eType_Null) { michael@0: nsCOMPtr tempURI = mURI; michael@0: nsCOMPtr nestedURI = do_QueryInterface(tempURI); michael@0: while (nestedURI) { michael@0: // view-source should always be an nsINestedURI, loop and check the michael@0: // scheme on this and all inner URIs that are also nested URIs. michael@0: bool isViewSource = false; michael@0: rv = tempURI->SchemeIs("view-source", &isViewSource); michael@0: if (NS_FAILED(rv) || isViewSource) { michael@0: LOG(("OBJLC [%p]: Blocking as effective URI has view-source scheme", michael@0: this)); michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: michael@0: nestedURI->GetInnerURI(getter_AddRefs(tempURI)); michael@0: nestedURI = do_QueryInterface(tempURI); michael@0: } michael@0: } michael@0: michael@0: // If we're a plugin but shouldn't start yet, load fallback with michael@0: // reason click-to-play instead. Items resolved as Image/Document michael@0: // will not be checked for previews, as well as invalid plugins michael@0: // (they will not have the mContentType set). michael@0: FallbackType clickToPlayReason; michael@0: if (!mActivated && (mType == eType_Null || mType == eType_Plugin) && michael@0: !ShouldPlay(clickToPlayReason, false)) { michael@0: LOG(("OBJLC [%p]: Marking plugin as click-to-play", this)); michael@0: mType = eType_Null; michael@0: fallbackType = clickToPlayReason; michael@0: } michael@0: michael@0: if (!mActivated && mType == eType_Plugin) { michael@0: // Object passed ShouldPlay, so it should be considered michael@0: // activated until it changes content type michael@0: LOG(("OBJLC [%p]: Object implicitly activated", this)); michael@0: mActivated = true; michael@0: } michael@0: michael@0: // Sanity check: We shouldn't have any loaded resources, pending events, or michael@0: // a final listener at this point michael@0: if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner || michael@0: mPendingCheckPluginStopEvent || mFinalListener) michael@0: { michael@0: NS_NOTREACHED("Trying to load new plugin with existing content"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // More sanity-checking: michael@0: // If mChannel is set, mChannelLoaded should be set, and vice-versa michael@0: if (mType != eType_Null && !!mChannel != mChannelLoaded) { michael@0: NS_NOTREACHED("Trying to load with bad channel state"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /// michael@0: /// Attempt to load new type michael@0: /// michael@0: michael@0: // We don't set mFinalListener until OnStartRequest has been called, to michael@0: // prevent re-entry ugliness with CloseChannel() michael@0: nsCOMPtr finalListener; michael@0: // If we decide to synchronously spawn a plugin, we do it after firing michael@0: // notifications to avoid re-entry causing notifications to fire out of order. michael@0: bool doSpawnPlugin = false; michael@0: switch (mType) { michael@0: case eType_Image: michael@0: if (!mChannel) { michael@0: // We have a LoadImage() call, but UpdateObjectParameters requires a michael@0: // channel for images, so this is not a valid state. michael@0: NS_NOTREACHED("Attempting to load image without a channel?"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: break; michael@0: } michael@0: rv = LoadImageWithChannel(mChannel, getter_AddRefs(finalListener)); michael@0: // finalListener will receive OnStartRequest below michael@0: break; michael@0: case eType_Plugin: michael@0: { michael@0: if (mChannel) { michael@0: // Force a sync state change now, we need the frame created michael@0: NotifyStateChanged(oldType, oldState, true, aNotify); michael@0: oldType = mType; michael@0: oldState = ObjectState(); michael@0: michael@0: if (!thisContent->GetPrimaryFrame()) { michael@0: // We're un-rendered, and can't instantiate a plugin. HasNewFrame will michael@0: // re-start us when we can proceed. michael@0: LOG(("OBJLC [%p]: Aborting load - plugin-type, but no frame", this)); michael@0: CloseChannel(); michael@0: break; michael@0: } michael@0: michael@0: // We'll handle this below michael@0: doSpawnPlugin = true; michael@0: } else { michael@0: rv = AsyncStartPluginInstance(); michael@0: } michael@0: } michael@0: break; michael@0: case eType_Document: michael@0: { michael@0: if (!mChannel) { michael@0: // We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters michael@0: // requires documents have a channel, so this is not a valid state. michael@0: NS_NOTREACHED("Attempting to load a document without a channel"); michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: michael@0: mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(), michael@0: mNetworkCreated); michael@0: if (!mFrameLoader) { michael@0: NS_NOTREACHED("nsFrameLoader::Create failed"); michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: michael@0: rv = mFrameLoader->CheckForRecursiveLoad(mURI); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("OBJLC [%p]: Aborting recursive load", this)); michael@0: mFrameLoader->Destroy(); michael@0: mFrameLoader = nullptr; michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: michael@0: // We're loading a document, so we have to set LOAD_DOCUMENT_URI michael@0: // (especially important for firing onload) michael@0: nsLoadFlags flags = 0; michael@0: mChannel->GetLoadFlags(&flags); michael@0: flags |= nsIChannel::LOAD_DOCUMENT_URI; michael@0: mChannel->SetLoadFlags(flags); michael@0: michael@0: nsCOMPtr docShell; michael@0: rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("Could not get DocShell from mFrameLoader?"); michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr req(do_QueryInterface(docShell)); michael@0: NS_ASSERTION(req, "Docshell must be an ifreq"); michael@0: michael@0: nsCOMPtr michael@0: uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID, &rv)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("Failed to get uriLoader service"); michael@0: mType = eType_Null; michael@0: break; michael@0: } michael@0: rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req, michael@0: getter_AddRefs(finalListener)); michael@0: // finalListener will receive OnStartRequest below michael@0: } michael@0: break; michael@0: case eType_Loading: michael@0: // If our type remains Loading, we need a channel to proceed michael@0: rv = OpenChannel(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("OBJLC [%p]: OpenChannel returned failure (%u)", this, rv)); michael@0: } michael@0: break; michael@0: case eType_Null: michael@0: // Handled below, silence compiler warnings michael@0: break; michael@0: }; michael@0: michael@0: // michael@0: // Loaded, handle notifications and fallback michael@0: // michael@0: if (NS_FAILED(rv)) { michael@0: // If we failed in the loading hunk above, switch to fallback michael@0: LOG(("OBJLC [%p]: Loading failed, switching to fallback", this)); michael@0: mType = eType_Null; michael@0: } michael@0: michael@0: // If we didn't load anything, handle switching to fallback state michael@0: if (mType == eType_Null) { michael@0: LOG(("OBJLC [%p]: Loading fallback, type %u", this, fallbackType)); michael@0: NS_ASSERTION(!mFrameLoader && !mInstanceOwner, michael@0: "switched to type null but also loaded something"); michael@0: michael@0: if (mChannel) { michael@0: // If we were loading with a channel but then failed over, throw it away michael@0: CloseChannel(); michael@0: } michael@0: michael@0: // Don't try to initialize plugins or final listener below michael@0: doSpawnPlugin = false; michael@0: finalListener = nullptr; michael@0: michael@0: // Don't notify, as LoadFallback doesn't know of our previous state michael@0: // (so really this is just setting mFallbackType) michael@0: LoadFallback(fallbackType, false); michael@0: } michael@0: michael@0: // Notify of our final state michael@0: NotifyStateChanged(oldType, oldState, false, aNotify); michael@0: NS_ENSURE_TRUE(mIsLoading, NS_OK); michael@0: michael@0: michael@0: // michael@0: // Spawning plugins and dispatching to the final listener may re-enter, so are michael@0: // delayed until after we fire a notification, to prevent missing michael@0: // notifications or firing them out of order. michael@0: // michael@0: // Note that we ensured that we entered into LoadObject() from michael@0: // ::OnStartRequest above when loading with a channel. michael@0: // michael@0: michael@0: rv = NS_OK; michael@0: if (doSpawnPlugin) { michael@0: rv = InstantiatePluginInstance(true); michael@0: NS_ENSURE_TRUE(mIsLoading, NS_OK); michael@0: // Create the final listener if we're loading with a channel. We can't do michael@0: // this in the loading block above as it requires an instance. michael@0: if (aLoadingChannel && NS_SUCCEEDED(rv)) { michael@0: if (NS_SUCCEEDED(rv) && MakePluginListener()) { michael@0: rv = mFinalListener->OnStartRequest(mChannel, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: // Plugins can reject their initial stream, but continue to run. michael@0: CloseChannel(); michael@0: NS_ENSURE_TRUE(mIsLoading, NS_OK); michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: } michael@0: } else if (finalListener) { michael@0: NS_ASSERTION(mType != eType_Null && mType != eType_Loading, michael@0: "We should not have a final listener with a non-loaded type"); michael@0: mFinalListener = finalListener; michael@0: rv = finalListener->OnStartRequest(mChannel, nullptr); michael@0: } michael@0: michael@0: if (NS_FAILED(rv) && mIsLoading) { michael@0: // Since we've already notified of our transition, we can just Unload and michael@0: // call LoadFallback (which will notify again) michael@0: mType = eType_Null; michael@0: UnloadObject(false); michael@0: NS_ENSURE_TRUE(mIsLoading, NS_OK); michael@0: CloseChannel(); michael@0: LoadFallback(fallbackType, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This call can re-enter when dealing with plugin listeners michael@0: nsresult michael@0: nsObjectLoadingContent::CloseChannel() michael@0: { michael@0: if (mChannel) { michael@0: LOG(("OBJLC [%p]: Closing channel\n", this)); michael@0: // Null the values before potentially-reentering, and ensure they survive michael@0: // the call michael@0: nsCOMPtr channelGrip(mChannel); michael@0: nsCOMPtr listenerGrip(mFinalListener); michael@0: mChannel = nullptr; michael@0: mFinalListener = nullptr; michael@0: channelGrip->Cancel(NS_BINDING_ABORTED); michael@0: if (listenerGrip) { michael@0: // mFinalListener is only set by LoadObject after OnStartRequest, or michael@0: // by OnStartRequest in the case of late-opened plugin streams michael@0: listenerGrip->OnStopRequest(channelGrip, nullptr, NS_BINDING_ABORTED); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsObjectLoadingContent::OpenChannel() michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: nsCOMPtr secMan = michael@0: nsContentUtils::GetSecurityManager(); michael@0: NS_ASSERTION(thisContent, "must be a content"); michael@0: nsIDocument* doc = thisContent->OwnerDoc(); michael@0: NS_ASSERTION(doc, "No owner document?"); michael@0: michael@0: nsresult rv; michael@0: mChannel = nullptr; michael@0: michael@0: // E.g. mms:// michael@0: if (!mURI || !CanHandleURI(mURI)) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), mURI, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr group = doc->GetDocumentLoadGroup(); michael@0: nsCOMPtr chan; michael@0: nsCOMPtr channelPolicy; michael@0: nsCOMPtr csp; michael@0: rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (csp) { michael@0: channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); michael@0: channelPolicy->SetContentSecurityPolicy(csp); michael@0: channelPolicy->SetLoadType(nsIContentPolicy::TYPE_OBJECT); michael@0: } michael@0: nsRefPtr shim = michael@0: new ObjectInterfaceRequestorShim(this); michael@0: rv = NS_NewChannel(getter_AddRefs(chan), mURI, nullptr, group, shim, michael@0: nsIChannel::LOAD_CALL_CONTENT_SNIFFERS | michael@0: nsIChannel::LOAD_CLASSIFY_URI, michael@0: channelPolicy); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Referrer michael@0: nsCOMPtr httpChan(do_QueryInterface(chan)); michael@0: if (httpChan) { michael@0: httpChan->SetReferrer(doc->GetDocumentURI()); michael@0: michael@0: // Set the initiator type michael@0: nsCOMPtr timedChannel(do_QueryInterface(httpChan)); michael@0: if (timedChannel) { michael@0: timedChannel->SetInitiatorType(thisContent->LocalName()); michael@0: } michael@0: } michael@0: michael@0: // Set up the channel's principal and such, like nsDocShell::DoURILoad does. michael@0: // If the content being loaded should be sandboxed with respect to origin we michael@0: // create a new null principal here. nsContentUtils::SetUpChannelOwner is michael@0: // used with a flag to force it to be set as the channel owner. michael@0: nsCOMPtr ownerPrincipal; michael@0: uint32_t sandboxFlags = doc->GetSandboxFlags(); michael@0: if (sandboxFlags & SANDBOXED_ORIGIN) { michael@0: ownerPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); michael@0: } else { michael@0: // Not sandboxed - we allow the content to assume its natural owner. michael@0: ownerPrincipal = thisContent->NodePrincipal(); michael@0: } michael@0: nsContentUtils::SetUpChannelOwner(ownerPrincipal, chan, mURI, true, michael@0: sandboxFlags & SANDBOXED_ORIGIN); michael@0: michael@0: nsCOMPtr scriptChannel = do_QueryInterface(chan); michael@0: if (scriptChannel) { michael@0: // Allow execution against our context if the principals match michael@0: scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); michael@0: } michael@0: michael@0: // AsyncOpen can fail if a file does not exist. michael@0: rv = chan->AsyncOpen(shim, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: LOG(("OBJLC [%p]: Channel opened", this)); michael@0: mChannel = chan; michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsObjectLoadingContent::GetCapabilities() const michael@0: { michael@0: return eSupportImages | michael@0: eSupportPlugins | michael@0: eSupportDocuments | michael@0: eSupportSVG; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::DestroyContent() michael@0: { michael@0: if (mFrameLoader) { michael@0: mFrameLoader->Destroy(); michael@0: mFrameLoader = nullptr; michael@0: } michael@0: michael@0: QueueCheckPluginStopEvent(); michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsObjectLoadingContent::Traverse(nsObjectLoadingContent *tmp, michael@0: nsCycleCollectionTraversalCallback &cb) michael@0: { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameLoader"); michael@0: cb.NoteXPCOMChild(static_cast(tmp->mFrameLoader)); michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::UnloadObject(bool aResetState) michael@0: { michael@0: // Don't notify in CancelImageRequests until we transition to a new loaded michael@0: // state michael@0: CancelImageRequests(false); michael@0: if (mFrameLoader) { michael@0: mFrameLoader->Destroy(); michael@0: mFrameLoader = nullptr; michael@0: } michael@0: michael@0: if (aResetState) { michael@0: if (mType != eType_Plugin) { michael@0: // This can re-enter when dealing with plugins, and StopPluginInstance michael@0: // will handle it michael@0: CloseChannel(); michael@0: } michael@0: mChannelLoaded = false; michael@0: mType = eType_Loading; michael@0: mURI = mOriginalURI = mBaseURI = nullptr; michael@0: mContentType.Truncate(); michael@0: mOriginalContentType.Truncate(); michael@0: } michael@0: michael@0: // InstantiatePluginInstance checks this after re-entrant calls and aborts if michael@0: // it was cleared from under it michael@0: mInstantiating = false; michael@0: michael@0: mScriptRequested = false; michael@0: michael@0: if (!mInstanceOwner) { michael@0: // The protochain is normally thrown out after a plugin stops, but if we michael@0: // re-enter while stopping a plugin and try to load something new, we need michael@0: // to throw away the old protochain in the nested unload. michael@0: TeardownProtoChain(); michael@0: mIsStopping = false; michael@0: } michael@0: michael@0: // This call should be last as it may re-enter michael@0: StopPluginInstance(); michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType, michael@0: EventStates aOldState, michael@0: bool aSync, michael@0: bool aNotify) michael@0: { michael@0: LOG(("OBJLC [%p]: Notifying about state change: (%u, %llx) -> (%u, %llx)" michael@0: " (sync %i, notify %i)", this, aOldType, aOldState.GetInternalValue(), michael@0: mType, ObjectState().GetInternalValue(), aSync, aNotify)); michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "must be a content"); michael@0: michael@0: NS_ASSERTION(thisContent->IsElement(), "Not an element?"); michael@0: michael@0: // XXX(johns): A good bit of the code below replicates UpdateState(true) michael@0: michael@0: // Unfortunately, we do some state changes without notifying michael@0: // (e.g. in Fallback when canceling image requests), so we have to michael@0: // manually notify object state changes. michael@0: thisContent->AsElement()->UpdateState(false); michael@0: michael@0: if (!aNotify) { michael@0: // We're done here michael@0: return; michael@0: } michael@0: michael@0: nsIDocument* doc = thisContent->GetCurrentDoc(); michael@0: if (!doc) { michael@0: return; // Nothing to do michael@0: } michael@0: michael@0: EventStates newState = ObjectState(); michael@0: michael@0: if (newState != aOldState) { michael@0: // This will trigger frame construction michael@0: NS_ASSERTION(InActiveDocument(thisContent), "Something is confused"); michael@0: EventStates changedBits = aOldState ^ newState; michael@0: michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: doc->ContentStateChanged(thisContent, changedBits); michael@0: } michael@0: if (aSync) { michael@0: // Make sure that frames are actually constructed immediately. michael@0: doc->FlushPendingNotifications(Flush_Frames); michael@0: } michael@0: } else if (aOldType != mType) { michael@0: // If our state changed, then we already recreated frames michael@0: // Otherwise, need to do that here michael@0: nsCOMPtr shell = doc->GetShell(); michael@0: if (shell) { michael@0: shell->RecreateFramesFor(thisContent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsObjectLoadingContent::ObjectType michael@0: nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType) michael@0: { michael@0: if (aMIMEType.IsEmpty()) { michael@0: return eType_Null; michael@0: } michael@0: michael@0: uint32_t caps = GetCapabilities(); michael@0: michael@0: if ((caps & eSupportImages) && IsSupportedImage(aMIMEType)) { michael@0: return eType_Image; michael@0: } michael@0: michael@0: // SVGs load as documents, but are their own capability michael@0: bool isSVG = aMIMEType.LowerCaseEqualsLiteral("image/svg+xml"); michael@0: Capabilities supportType = isSVG ? eSupportSVG : eSupportDocuments; michael@0: if ((caps & supportType) && IsSupportedDocument(aMIMEType)) { michael@0: return eType_Document; michael@0: } michael@0: michael@0: if (caps & eSupportPlugins && PluginExistsForType(aMIMEType.get())) { michael@0: // ShouldPlay will handle checking for disabled plugins michael@0: return eType_Plugin; michael@0: } michael@0: michael@0: return eType_Null; michael@0: } michael@0: michael@0: nsObjectFrame* michael@0: nsObjectLoadingContent::GetExistingFrame() michael@0: { michael@0: nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); michael@0: nsIFrame* frame = thisContent->GetPrimaryFrame(); michael@0: nsIObjectFrame* objFrame = do_QueryFrame(frame); michael@0: return static_cast(objFrame); michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::CreateStaticClone(nsObjectLoadingContent* aDest) const michael@0: { michael@0: nsImageLoadingContent::CreateStaticImageClone(aDest); michael@0: michael@0: aDest->mType = mType; michael@0: nsObjectLoadingContent* thisObj = const_cast(this); michael@0: if (thisObj->mPrintFrame.IsAlive()) { michael@0: aDest->mPrintFrame = thisObj->mPrintFrame; michael@0: } else { michael@0: aDest->mPrintFrame = const_cast(this)->GetExistingFrame(); michael@0: } michael@0: michael@0: if (mFrameLoader) { michael@0: nsCOMPtr content = michael@0: do_QueryInterface(static_cast(aDest)); michael@0: nsFrameLoader* fl = nsFrameLoader::Create(content->AsElement(), false); michael@0: if (fl) { michael@0: aDest->mFrameLoader = fl; michael@0: mFrameLoader->CreateStaticClone(fl); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetPrintFrame(nsIFrame** aFrame) michael@0: { michael@0: *aFrame = mPrintFrame.GetFrame(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::PluginDestroyed() michael@0: { michael@0: // Called when our plugin is destroyed from under us, usually when reloading michael@0: // plugins in plugin host. Invalidate instance owner / prototype but otherwise michael@0: // don't take any action. michael@0: TeardownProtoChain(); michael@0: mInstanceOwner->Destroy(); michael@0: mInstanceOwner = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag, michael@0: const nsAString& pluginDumpID, michael@0: const nsAString& browserDumpID, michael@0: bool submittedCrashReport) michael@0: { michael@0: LOG(("OBJLC [%p]: Plugin Crashed, queuing crash event", this)); michael@0: NS_ASSERTION(mType == eType_Plugin, "PluginCrashed at non-plugin type"); michael@0: michael@0: PluginDestroyed(); michael@0: michael@0: // Switch to fallback/crashed state, notify michael@0: LoadFallback(eFallbackCrashed, true); michael@0: michael@0: // send nsPluginCrashedEvent michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: // Note that aPluginTag in invalidated after we're called, so copy michael@0: // out any data we need now. michael@0: nsAutoCString pluginName; michael@0: aPluginTag->GetName(pluginName); michael@0: nsAutoCString pluginFilename; michael@0: aPluginTag->GetFilename(pluginFilename); michael@0: michael@0: nsCOMPtr ev = michael@0: new nsPluginCrashedEvent(thisContent, michael@0: pluginDumpID, michael@0: browserDumpID, michael@0: NS_ConvertUTF8toUTF16(pluginName), michael@0: NS_ConvertUTF8toUTF16(pluginFilename), michael@0: submittedCrashReport); michael@0: nsresult rv = NS_DispatchToCurrentThread(ev); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("failed to dispatch nsPluginCrashedEvent"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsObjectLoadingContent::ScriptRequestPluginInstance(JSContext* aCx, michael@0: nsNPAPIPluginInstance **aResult) michael@0: { michael@0: // The below methods pull the cx off the stack, so make sure they match. michael@0: // michael@0: // NB: Sometimes there's a null cx on the stack, in which case |cx| is the michael@0: // safe JS context. But in that case, IsCallerChrome() will return true, michael@0: // so the ensuing expression is short-circuited. michael@0: MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContext(), michael@0: aCx == nsContentUtils::GetCurrentJSContext()); michael@0: bool callerIsContentJS = (!nsContentUtils::IsCallerChrome() && michael@0: !nsContentUtils::IsCallerXBL() && michael@0: js::IsContextRunningJS(aCx)); michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: *aResult = nullptr; michael@0: michael@0: // The first time content script attempts to access placeholder content, fire michael@0: // an event. Fallback types >= eFallbackClickToPlay are plugin-replacement michael@0: // types, see header. michael@0: if (callerIsContentJS && !mScriptRequested && michael@0: InActiveDocument(thisContent) && mType == eType_Null && michael@0: mFallbackType >= eFallbackClickToPlay) { michael@0: nsCOMPtr ev = michael@0: new nsSimplePluginEvent(thisContent, michael@0: NS_LITERAL_STRING("PluginScripted")); michael@0: nsresult rv = NS_DispatchToCurrentThread(ev); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("failed to dispatch PluginScripted event"); michael@0: } michael@0: mScriptRequested = true; michael@0: } else if (callerIsContentJS && mType == eType_Plugin && !mInstanceOwner && michael@0: nsContentUtils::IsSafeToRunScript() && michael@0: InActiveDocument(thisContent)) { michael@0: // If we're configured as a plugin in an active document and it's safe to michael@0: // run scripts right now, try spawning synchronously michael@0: SyncStartPluginInstance(); michael@0: } michael@0: michael@0: if (mInstanceOwner) { michael@0: return mInstanceOwner->GetInstance(aResult); michael@0: } michael@0: michael@0: // Note that returning a null plugin is expected (and happens often) michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::SyncStartPluginInstance() michael@0: { michael@0: NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), michael@0: "Must be able to run script in order to instantiate a plugin instance!"); michael@0: michael@0: // Don't even attempt to start an instance unless the content is in michael@0: // the document and active michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: if (!InActiveDocument(thisContent)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr kungFuURIGrip(mURI); michael@0: nsCString contentType(mContentType); michael@0: return InstantiatePluginInstance(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::AsyncStartPluginInstance() michael@0: { michael@0: // OK to have an instance already or a pending spawn. michael@0: if (mInstanceOwner || mPendingInstantiateEvent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: nsIDocument* doc = thisContent->OwnerDoc(); michael@0: if (doc->IsStaticDocument() || doc->IsBeingUsedAsImage()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr event = new nsAsyncInstantiateEvent(this); michael@0: if (!event) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: nsresult rv = NS_DispatchToCurrentThread(event); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Track pending events michael@0: mPendingInstantiateEvent = event; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) michael@0: { michael@0: NS_IF_ADDREF(*aURI = GetSrcURI()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: DoDelayedStop(nsPluginInstanceOwner* aInstanceOwner, michael@0: nsObjectLoadingContent* aContent, michael@0: bool aDelayedStop) michael@0: { michael@0: // Don't delay stopping QuickTime (bug 425157), Flip4Mac (bug 426524), michael@0: // XStandard (bug 430219), CMISS Zinc (bug 429604). michael@0: if (aDelayedStop michael@0: #if !(defined XP_WIN || defined MOZ_X11) michael@0: && !aInstanceOwner->MatchPluginName("QuickTime") michael@0: && !aInstanceOwner->MatchPluginName("Flip4Mac") michael@0: && !aInstanceOwner->MatchPluginName("XStandard plugin") michael@0: && !aInstanceOwner->MatchPluginName("CMISS Zinc Plugin") michael@0: #endif michael@0: ) { michael@0: nsCOMPtr evt = michael@0: new nsStopPluginRunnable(aInstanceOwner, aContent); michael@0: NS_DispatchToCurrentThread(evt); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::LoadFallback(FallbackType aType, bool aNotify) { michael@0: EventStates oldState = ObjectState(); michael@0: ObjectType oldType = mType; michael@0: michael@0: NS_ASSERTION(!mInstanceOwner && !mFrameLoader && !mChannel, michael@0: "LoadFallback called with loaded content"); michael@0: michael@0: // michael@0: // Fixup mFallbackType michael@0: // michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: NS_ASSERTION(thisContent, "must be a content"); michael@0: michael@0: if (!thisContent->IsHTML() || mContentType.IsEmpty()) { michael@0: // Don't let custom fallback handlers run outside HTML, tags without a michael@0: // determined type should always just be alternate content michael@0: aType = eFallbackAlternate; michael@0: } michael@0: michael@0: if (thisContent->Tag() == nsGkAtoms::object && michael@0: (aType == eFallbackUnsupported || michael@0: aType == eFallbackDisabled || michael@0: aType == eFallbackBlocklisted)) michael@0: { michael@0: // Show alternate content instead, if it exists michael@0: for (nsIContent* child = thisContent->GetFirstChild(); michael@0: child; child = child->GetNextSibling()) { michael@0: if (!child->IsHTML(nsGkAtoms::param) && michael@0: nsStyleUtil::IsSignificantChild(child, true, false)) { michael@0: aType = eFallbackAlternate; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: mType = eType_Null; michael@0: mFallbackType = aType; michael@0: michael@0: // Notify michael@0: if (!aNotify) { michael@0: return; // done michael@0: } michael@0: michael@0: NotifyStateChanged(oldType, oldState, false, true); michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, michael@0: bool aDelayedStop, michael@0: bool aForcedReentry) michael@0: { michael@0: // DoStopPlugin can process events -- There may be pending michael@0: // CheckPluginStopEvent events which can drop in underneath us and destroy the michael@0: // instance we are about to destroy. We prevent that with the mPluginStopping michael@0: // flag. (aForcedReentry is only true from the callback of an earlier delayed michael@0: // stop) michael@0: if (mIsStopping && !aForcedReentry) { michael@0: return; michael@0: } michael@0: mIsStopping = true; michael@0: michael@0: nsRefPtr kungFuDeathGrip(aInstanceOwner); michael@0: nsRefPtr inst; michael@0: aInstanceOwner->GetInstance(getter_AddRefs(inst)); michael@0: if (inst) { michael@0: if (DoDelayedStop(aInstanceOwner, this, aDelayedStop)) { michael@0: return; michael@0: } michael@0: michael@0: #if defined(XP_MACOSX) michael@0: aInstanceOwner->HidePluginWindow(); michael@0: #endif michael@0: michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: NS_ASSERTION(pluginHost, "No plugin host?"); michael@0: pluginHost->StopPluginInstance(inst); michael@0: } michael@0: michael@0: aInstanceOwner->Destroy(); michael@0: michael@0: // If we re-enter in plugin teardown UnloadObject will tear down the michael@0: // protochain -- the current protochain could be from a new, unrelated, load. michael@0: if (!mIsStopping) { michael@0: LOG(("OBJLC [%p]: Re-entered in plugin teardown", this)); michael@0: return; michael@0: } michael@0: michael@0: TeardownProtoChain(); michael@0: mIsStopping = false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::StopPluginInstance() michael@0: { michael@0: // Clear any pending events michael@0: mPendingInstantiateEvent = nullptr; michael@0: mPendingCheckPluginStopEvent = nullptr; michael@0: michael@0: // If we're currently instantiating, clearing this will cause michael@0: // InstantiatePluginInstance's re-entrance check to destroy the created plugin michael@0: mInstantiating = false; michael@0: michael@0: if (!mInstanceOwner) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mChannel) { michael@0: // The plugin has already used data from this channel, we'll need to michael@0: // re-open it to handle instantiating again, even if we don't invalidate michael@0: // our loaded state. michael@0: /// XXX(johns): Except currently, we don't, just leaving re-opening channels michael@0: /// to plugins... michael@0: LOG(("OBJLC [%p]: StopPluginInstance - Closing used channel", this)); michael@0: CloseChannel(); michael@0: } michael@0: michael@0: // We detach the instance owner's frame before destruction, but don't destroy michael@0: // the instance owner until the plugin is stopped. michael@0: mInstanceOwner->SetFrame(nullptr); michael@0: michael@0: bool delayedStop = false; michael@0: #ifdef XP_WIN michael@0: // Force delayed stop for Real plugin only; see bug 420886, 426852. michael@0: nsRefPtr inst; michael@0: mInstanceOwner->GetInstance(getter_AddRefs(inst)); michael@0: if (inst) { michael@0: const char* mime = nullptr; michael@0: if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) { michael@0: if (strcmp(mime, "audio/x-pn-realaudio-plugin") == 0) { michael@0: delayedStop = true; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: nsRefPtr ownerGrip(mInstanceOwner); michael@0: mInstanceOwner = nullptr; michael@0: michael@0: // This can/will re-enter michael@0: DoStopPlugin(ownerGrip, delayedStop); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::NotifyContentObjectWrapper() michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: nsCOMPtr doc = thisContent->GetDocument(); michael@0: if (!doc) michael@0: return; michael@0: michael@0: nsCOMPtr sgo = do_QueryInterface(doc->GetScopeObject()); michael@0: if (!sgo) michael@0: return; michael@0: michael@0: nsIScriptContext *scx = sgo->GetContext(); michael@0: if (!scx) michael@0: return; michael@0: michael@0: JSContext *cx = scx->GetNativeContext(); michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: michael@0: JS::Rooted obj(cx, thisContent->GetWrapper()); michael@0: if (!obj) { michael@0: // Nothing to do here if there's no wrapper for mContent. The proto michael@0: // chain will be fixed appropriately when the wrapper is created. michael@0: return; michael@0: } michael@0: michael@0: SetupProtoChain(cx, obj); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::PlayPlugin() michael@0: { michael@0: if (!nsContentUtils::IsCallerChrome()) michael@0: return NS_OK; michael@0: michael@0: if (!mActivated) { michael@0: mActivated = true; michael@0: LOG(("OBJLC [%p]: Activated by user", this)); michael@0: } michael@0: michael@0: // If we're in a click-to-play or play preview state, we need to reload michael@0: // Fallback types >= eFallbackClickToPlay are plugin-replacement types, see michael@0: // header michael@0: if (mType == eType_Null && mFallbackType >= eFallbackClickToPlay) { michael@0: return LoadObject(true, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::Reload(bool aClearActivation) michael@0: { michael@0: if (aClearActivation) { michael@0: mActivated = false; michael@0: mPlayPreviewCanceled = false; michael@0: } michael@0: michael@0: return LoadObject(true, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetActivated(bool *aActivated) michael@0: { michael@0: *aActivated = Activated(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetPluginFallbackType(uint32_t* aPluginFallbackType) michael@0: { michael@0: NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); michael@0: *aPluginFallbackType = mFallbackType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsObjectLoadingContent::DefaultFallbackType() michael@0: { michael@0: FallbackType reason; michael@0: bool go = ShouldPlay(reason, true); michael@0: if (go) { michael@0: return PLUGIN_ACTIVE; michael@0: } michael@0: return reason; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::GetHasRunningPlugin(bool *aHasPlugin) michael@0: { michael@0: NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); michael@0: *aHasPlugin = HasRunningPlugin(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::CancelPlayPreview() michael@0: { michael@0: if (!nsContentUtils::IsCallerChrome()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mPlayPreviewCanceled = true; michael@0: michael@0: // If we're in play preview state already, reload michael@0: if (mType == eType_Null && mFallbackType == eFallbackPlayPreview) { michael@0: return LoadObject(true, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool sPrefsInitialized; michael@0: static uint32_t sSessionTimeoutMinutes; michael@0: static uint32_t sPersistentTimeoutDays; michael@0: michael@0: bool michael@0: nsObjectLoadingContent::ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentType) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!sPrefsInitialized) { michael@0: Preferences::AddUintVarCache(&sSessionTimeoutMinutes, michael@0: "plugin.sessionPermissionNow.intervalInMinutes", 60); michael@0: Preferences::AddUintVarCache(&sPersistentTimeoutDays, michael@0: "plugin.persistentPermissionAlways.intervalInDays", 90); michael@0: sPrefsInitialized = true; michael@0: } michael@0: michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: michael@0: nsCOMPtr playPreviewInfo; michael@0: bool isPlayPreviewSpecified = NS_SUCCEEDED(pluginHost->GetPlayPreviewInfo( michael@0: mContentType, getter_AddRefs(playPreviewInfo))); michael@0: bool ignoreCTP = false; michael@0: if (isPlayPreviewSpecified) { michael@0: playPreviewInfo->GetIgnoreCTP(&ignoreCTP); michael@0: } michael@0: if (isPlayPreviewSpecified && !mPlayPreviewCanceled && michael@0: ignoreCTP) { michael@0: // play preview in ignoreCTP mode is shown even if the native plugin michael@0: // is not present/installed michael@0: aReason = eFallbackPlayPreview; michael@0: return false; michael@0: } michael@0: // at this point if it's not a plugin, we let it play/fallback michael@0: if (!aIgnoreCurrentType && mType != eType_Plugin) { michael@0: return true; michael@0: } michael@0: michael@0: // Order of checks: michael@0: // * Assume a default of click-to-play michael@0: // * If globally disabled, per-site permissions cannot override. michael@0: // * If blocklisted, override the reason with the blocklist reason michael@0: // * If not blocklisted but playPreview, override the reason with the michael@0: // playPreview reason. michael@0: // * Check per-site permissions and follow those if specified. michael@0: // * Honor per-plugin disabled permission michael@0: // * Blocklisted plugins are forced to CtP michael@0: // * Check per-plugin permission and follow that. michael@0: michael@0: aReason = eFallbackClickToPlay; michael@0: michael@0: uint32_t enabledState = nsIPluginTag::STATE_DISABLED; michael@0: pluginHost->GetStateForType(mContentType, &enabledState); michael@0: if (nsIPluginTag::STATE_DISABLED == enabledState) { michael@0: aReason = eFallbackDisabled; michael@0: return false; michael@0: } michael@0: michael@0: // Before we check permissions, get the blocklist state of this plugin to set michael@0: // the fallback reason correctly. michael@0: uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED; michael@0: pluginHost->GetBlocklistStateForType(mContentType.get(), &blocklistState); michael@0: if (blocklistState == nsIBlocklistService::STATE_BLOCKED) { michael@0: // no override possible michael@0: aReason = eFallbackBlocklisted; michael@0: return false; michael@0: } michael@0: michael@0: if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) { michael@0: aReason = eFallbackVulnerableUpdatable; michael@0: } michael@0: else if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { michael@0: aReason = eFallbackVulnerableNoUpdate; michael@0: } michael@0: michael@0: if (aReason == eFallbackClickToPlay && isPlayPreviewSpecified && michael@0: !mPlayPreviewCanceled && !ignoreCTP) { michael@0: // play preview in click-to-play mode is shown instead of standard CTP UI michael@0: aReason = eFallbackPlayPreview; michael@0: } michael@0: michael@0: // Check the permission manager for permission based on the principal of michael@0: // the toplevel content. michael@0: michael@0: nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); michael@0: MOZ_ASSERT(thisContent); michael@0: nsIDocument* ownerDoc = thisContent->OwnerDoc(); michael@0: michael@0: nsCOMPtr window = ownerDoc->GetWindow(); michael@0: if (!window) { michael@0: return false; michael@0: } michael@0: nsCOMPtr topWindow; michael@0: rv = window->GetTop(getter_AddRefs(topWindow)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: nsCOMPtr topDocument; michael@0: rv = topWindow->GetDocument(getter_AddRefs(topDocument)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: nsCOMPtr topDoc = do_QueryInterface(topDocument); michael@0: michael@0: nsCOMPtr permissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // For now we always say that the system principal uses click-to-play since michael@0: // that maintains current behavior and we have tests that expect this. michael@0: // What we really should do is disable plugins entirely in pages that use michael@0: // the system principal, i.e. in chrome pages. That way the click-to-play michael@0: // code here wouldn't matter at all. Bug 775301 is tracking this. michael@0: if (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) { michael@0: nsAutoCString permissionString; michael@0: rv = pluginHost->GetPermissionStringForType(mContentType, permissionString); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: uint32_t permission; michael@0: rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(), michael@0: permissionString.Data(), michael@0: &permission); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (permission != nsIPermissionManager::UNKNOWN_ACTION) { michael@0: uint64_t nowms = PR_Now() / 1000; michael@0: permissionManager->UpdateExpireTime( michael@0: topDoc->NodePrincipal(), permissionString.Data(), false, michael@0: nowms + sSessionTimeoutMinutes * 60 * 1000, michael@0: nowms / 1000 + uint64_t(sPersistentTimeoutDays) * 24 * 60 * 60 * 1000); michael@0: } michael@0: switch (permission) { michael@0: case nsIPermissionManager::ALLOW_ACTION: michael@0: return true; michael@0: case nsIPermissionManager::DENY_ACTION: michael@0: aReason = eFallbackDisabled; michael@0: return false; michael@0: case nsIPermissionManager::PROMPT_ACTION: michael@0: return false; michael@0: case nsIPermissionManager::UNKNOWN_ACTION: michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // No site-specific permissions. Vulnerable plugins are automatically CtP michael@0: if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || michael@0: blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { michael@0: return false; michael@0: } michael@0: michael@0: switch (enabledState) { michael@0: case nsIPluginTag::STATE_ENABLED: michael@0: return true; michael@0: case nsIPluginTag::STATE_CLICKTOPLAY: michael@0: return false; michael@0: } michael@0: MOZ_CRASH("Unexpected enabledState"); michael@0: } michael@0: michael@0: nsIDocument* michael@0: nsObjectLoadingContent::GetContentDocument() michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: if (!thisContent->IsInDoc()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // XXXbz should this use GetCurrentDoc()? sXBL/XBL2 issue! michael@0: nsIDocument *sub_doc = thisContent->OwnerDoc()->GetSubDocumentFor(thisContent); michael@0: if (!sub_doc) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Return null for cross-origin contentDocument. michael@0: if (!nsContentUtils::GetSubjectPrincipal()->SubsumesConsideringDomain(sub_doc->NodePrincipal())) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return sub_doc; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::LegacyCall(JSContext* aCx, michael@0: JS::Handle aThisVal, michael@0: const Sequence& aArguments, michael@0: JS::MutableHandle aRetval, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: JS::Rooted obj(aCx, thisContent->GetWrapper()); michael@0: MOZ_ASSERT(obj, "How did we get called?"); michael@0: michael@0: // Make sure we're not dealing with an Xray. Our DoCall code can't handle michael@0: // random cross-compartment wrappers, so we're going to have to wrap michael@0: // everything up into our compartment, but that means we need to check that michael@0: // this is not an Xray situation by hand. michael@0: if (!JS_WrapObject(aCx, &obj)) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: if (nsDOMClassInfo::ObjectIsNativeWrapper(aCx, obj)) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: obj = thisContent->GetWrapper(); michael@0: // Now wrap things up into the compartment of "obj" michael@0: JSAutoCompartment ac(aCx, obj); michael@0: JS::AutoValueVector args(aCx); michael@0: if (!args.append(aArguments.Elements(), aArguments.Length())) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: for (size_t i = 0; i < args.length(); i++) { michael@0: if (!JS_WrapValue(aCx, args.handleAt(i))) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted thisVal(aCx, aThisVal); michael@0: if (!JS_WrapValue(aCx, &thisVal)) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr pi; michael@0: nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi)); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: // if there's no plugin around for this object, throw. michael@0: if (!pi) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted pi_obj(aCx); michael@0: JS::Rooted pi_proto(aCx); michael@0: michael@0: rv = GetPluginJSObject(aCx, obj, pi, &pi_obj, &pi_proto); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: if (!pi_obj) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: bool ok = JS::Call(aCx, thisVal, pi_obj, args, aRetval); michael@0: if (!ok) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::PLUGIN_CALLED_DIRECTLY, true); michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::SetupProtoChain(JSContext* aCx, michael@0: JS::Handle aObject) michael@0: { michael@0: MOZ_ASSERT(nsCOMPtr(do_QueryInterface( michael@0: static_cast(this)))->IsDOMBinding()); michael@0: michael@0: if (mType != eType_Plugin) { michael@0: return; michael@0: } michael@0: michael@0: if (!nsContentUtils::IsSafeToRunScript()) { michael@0: // This may be null if the JS context is not a DOM context. That's ok, we'll michael@0: // use the safe context from XPConnect in the runnable. michael@0: nsCOMPtr scriptContext = GetScriptContextFromJSContext(aCx); michael@0: michael@0: nsRefPtr runner = michael@0: new SetupProtoChainRunner(scriptContext, this); michael@0: nsContentUtils::AddScriptRunner(runner); michael@0: return; michael@0: } michael@0: michael@0: // We get called on random compartments here for some reason michael@0: // (perhaps because WrapObject can happen on a random compartment?) michael@0: // so make sure to enter the compartment of aObject. michael@0: MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); michael@0: michael@0: JSAutoCompartment ac(aCx, aObject); michael@0: michael@0: nsRefPtr pi; michael@0: nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi)); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: if (!pi) { michael@0: // No plugin around for this object. michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted pi_obj(aCx); // XPConnect-wrapped peer object, when we get it. michael@0: JS::Rooted pi_proto(aCx); // 'pi.__proto__' michael@0: michael@0: rv = GetPluginJSObject(aCx, aObject, pi, &pi_obj, &pi_proto); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: if (!pi_obj) { michael@0: // Didn't get a plugin instance JSObject, nothing we can do then. michael@0: return; michael@0: } michael@0: michael@0: // If we got an xpconnect-wrapped plugin object, set obj's michael@0: // prototype's prototype to the scriptable plugin. michael@0: michael@0: JS::Rooted global(aCx, JS_GetGlobalForObject(aCx, aObject)); michael@0: JS::Handle my_proto = GetDOMClass(aObject)->mGetProto(aCx, global); michael@0: MOZ_ASSERT(my_proto); michael@0: michael@0: // Set 'this.__proto__' to pi michael@0: if (!::JS_SetPrototype(aCx, aObject, pi_obj)) { michael@0: return; michael@0: } michael@0: michael@0: if (pi_proto && js::GetObjectClass(pi_proto) != js::ObjectClassPtr) { michael@0: // The plugin wrapper has a proto that's not Object.prototype, set michael@0: // 'pi.__proto__.__proto__' to the original 'this.__proto__' michael@0: if (pi_proto != my_proto && !::JS_SetPrototype(aCx, pi_proto, my_proto)) { michael@0: return; michael@0: } michael@0: } else { michael@0: // 'pi' didn't have a prototype, or pi's proto was michael@0: // 'Object.prototype' (i.e. pi is an NPRuntime wrapped JS object) michael@0: // set 'pi.__proto__' to the original 'this.__proto__' michael@0: if (!::JS_SetPrototype(aCx, pi_obj, my_proto)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Before this proto dance the objects involved looked like this: michael@0: // michael@0: // this.__proto__.__proto__ michael@0: // ^ ^ ^ michael@0: // | | |__ Object.prototype michael@0: // | | michael@0: // | |__ WebIDL prototype (shared) michael@0: // | michael@0: // |__ WebIDL object michael@0: // michael@0: // pi.__proto__ michael@0: // ^ ^ michael@0: // | |__ Object.prototype or some other object michael@0: // | michael@0: // |__ Plugin NPRuntime JS object wrapper michael@0: // michael@0: // Now, after the above prototype setup the prototype chain should michael@0: // look like this if pi.__proto__ was Object.prototype: michael@0: // michael@0: // this.__proto__.__proto__.__proto__ michael@0: // ^ ^ ^ ^ michael@0: // | | | |__ Object.prototype michael@0: // | | | michael@0: // | | |__ WebIDL prototype (shared) michael@0: // | | michael@0: // | |__ Plugin NPRuntime JS object wrapper michael@0: // | michael@0: // |__ WebIDL object michael@0: // michael@0: // or like this if pi.__proto__ was some other object: michael@0: // michael@0: // this.__proto__.__proto__.__proto__.__proto__ michael@0: // ^ ^ ^ ^ ^ michael@0: // | | | | |__ Object.prototype michael@0: // | | | | michael@0: // | | | |__ WebIDL prototype (shared) michael@0: // | | | michael@0: // | | |__ old pi.__proto__ michael@0: // | | michael@0: // | |__ Plugin NPRuntime JS object wrapper michael@0: // | michael@0: // |__ WebIDL object michael@0: // michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsObjectLoadingContent::GetPluginJSObject(JSContext *cx, michael@0: JS::Handle obj, michael@0: nsNPAPIPluginInstance *plugin_inst, michael@0: JS::MutableHandle plugin_obj, michael@0: JS::MutableHandle plugin_proto) michael@0: { michael@0: // NB: We need an AutoEnterCompartment because we can be called from michael@0: // nsObjectFrame when the plugin loads after the JS object for our content michael@0: // node has been created. michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: if (plugin_inst) { michael@0: plugin_inst->GetJSObject(cx, plugin_obj.address()); michael@0: if (plugin_obj) { michael@0: if (!::JS_GetPrototype(cx, plugin_obj, plugin_proto)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::TeardownProtoChain() michael@0: { michael@0: nsCOMPtr thisContent = michael@0: do_QueryInterface(static_cast(this)); michael@0: michael@0: // Use the safe JSContext here as we're not always able to find the michael@0: // JSContext associated with the NPP any more. michael@0: AutoSafeJSContext cx; michael@0: JS::Rooted obj(cx, thisContent->GetWrapper()); michael@0: NS_ENSURE_TRUE(obj, /* void */); michael@0: michael@0: JS::Rooted proto(cx); michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: // Loop over the DOM element's JS object prototype chain and remove michael@0: // all JS objects of the class sNPObjectJSWrapperClass michael@0: DebugOnly removed = false; michael@0: while (obj) { michael@0: if (!::JS_GetPrototype(cx, obj, &proto)) { michael@0: return; michael@0: } michael@0: if (!proto) { michael@0: break; michael@0: } michael@0: // Unwrap while checking the jsclass - if the prototype is a wrapper for michael@0: // an NP object, that counts too. michael@0: if (JS_GetClass(js::UncheckedUnwrap(proto)) == &sNPObjectJSWrapperClass) { michael@0: // We found an NPObject on the proto chain, get its prototype... michael@0: if (!::JS_GetPrototype(cx, proto, &proto)) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(!removed, "more than one NPObject in prototype chain"); michael@0: removed = true; michael@0: michael@0: // ... and pull it out of the chain. michael@0: ::JS_SetPrototype(cx, obj, proto); michael@0: } michael@0: michael@0: obj = proto; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsObjectLoadingContent::DoNewResolve(JSContext* aCx, JS::Handle aObject, michael@0: JS::Handle aId, michael@0: JS::MutableHandle aDesc) michael@0: { michael@0: // We don't resolve anything; we just try to make sure we're instantiated. michael@0: // This purposefully does not fire for chrome/xray resolves, see bug 967694 michael@0: michael@0: nsRefPtr pi; michael@0: nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi)); michael@0: if (NS_FAILED(rv)) { michael@0: return mozilla::dom::Throw(aCx, rv); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsObjectLoadingContent::GetOwnPropertyNames(JSContext* aCx, michael@0: nsTArray& /* unused */, michael@0: ErrorResult& aRv) michael@0: { michael@0: // Just like DoNewResolve, just make sure we're instantiated. That will do michael@0: // the work our Enumerate hook needs to do. This purposefully does not fire michael@0: // for xray resolves, see bug 967694 michael@0: nsRefPtr pi; michael@0: aRv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi)); michael@0: } michael@0: michael@0: michael@0: // SetupProtoChainRunner implementation michael@0: nsObjectLoadingContent::SetupProtoChainRunner::SetupProtoChainRunner( michael@0: nsIScriptContext* scriptContext, michael@0: nsObjectLoadingContent* aContent) michael@0: : mContext(scriptContext) michael@0: , mContent(aContent) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsObjectLoadingContent::SetupProtoChainRunner::Run() michael@0: { michael@0: // XXXbz Does it really matter what JSContext we use here? Seems michael@0: // like we could just always use the safe context.... michael@0: nsCxPusher pusher; michael@0: JSContext* cx = mContext ? mContext->GetNativeContext() michael@0: : nsContentUtils::GetSafeJSContext(); michael@0: pusher.Push(cx); michael@0: michael@0: nsCOMPtr content; michael@0: CallQueryInterface(mContent.get(), getter_AddRefs(content)); michael@0: JS::Rooted obj(cx, content->GetWrapper()); michael@0: if (!obj) { michael@0: // No need to set up our proto chain if we don't even have an object michael@0: return NS_OK; michael@0: } michael@0: nsObjectLoadingContent* objectLoadingContent = michael@0: static_cast(mContent.get()); michael@0: objectLoadingContent->SetupProtoChain(cx, obj); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsObjectLoadingContent::SetupProtoChainRunner, nsIRunnable) michael@0: