diff options
Diffstat (limited to 'toolkit/components/extensions/webrequest/WebNavigationContent.cpp')
-rw-r--r-- | toolkit/components/extensions/webrequest/WebNavigationContent.cpp | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/toolkit/components/extensions/webrequest/WebNavigationContent.cpp b/toolkit/components/extensions/webrequest/WebNavigationContent.cpp new file mode 100644 index 0000000000..7de4516372 --- /dev/null +++ b/toolkit/components/extensions/webrequest/WebNavigationContent.cpp @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/extensions/WebNavigationContent.h" + +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/extensions/ExtensionsChild.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "nsCRT.h" +#include "nsDocShellLoadTypes.h" +#include "nsPIWindowRoot.h" +#include "nsIChannel.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIObserverService.h" +#include "nsIPropertyBag2.h" +#include "nsIWebNavigation.h" +#include "nsIWebProgress.h" +#include "nsGlobalWindowOuter.h" +#include "nsQueryObject.h" + +namespace mozilla { +namespace extensions { + +/* static */ +already_AddRefed<WebNavigationContent> WebNavigationContent::GetSingleton() { + static RefPtr<WebNavigationContent> sSingleton; + if (!sSingleton) { + sSingleton = new WebNavigationContent(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return do_AddRef(sSingleton); +} + +NS_IMPL_ISUPPORTS(WebNavigationContent, nsIObserver, nsIDOMEventListener, + nsIWebProgressListener, nsISupportsWeakReference) + +void WebNavigationContent::Init() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + + obs->AddObserver(this, "chrome-event-target-created", true); + obs->AddObserver(this, "webNavigation-createdNavigationTarget-from-js", true); +} + +NS_IMETHODIMP WebNavigationContent::Observe(nsISupports* aSubject, + char const* aTopic, + char16_t const* aData) { + if (!nsCRT::strcmp(aTopic, "chrome-event-target-created")) { + // This notification is sent whenever a new window root is created, with the + // subject being an EventTarget corresponding to either an nsWindowRoot, or + // additionally also an InProcessBrowserChildMessageManager in the parent. + // This is the same entry point used to register listeners for the JS window + // actor API. + if (RefPtr<dom::EventTarget> eventTarget = do_QueryObject(aSubject)) { + AttachListeners(eventTarget); + } + + nsCOMPtr<nsIDocShell> docShell; + if (nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(aSubject)) { + docShell = root->GetWindow()->GetDocShell(); + } else if (RefPtr<dom::ContentFrameMessageManager> mm = + do_QueryObject(aSubject)) { + docShell = mm->GetDocShell(IgnoreErrors()); + } + if (docShell && docShell->GetBrowsingContext()->IsContent()) { + nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(docShell)); + + webProgress->AddProgressListener(this, + nsIWebProgress::NOTIFY_STATE_WINDOW | + nsIWebProgress::NOTIFY_LOCATION); + } + } else if (!nsCRT::strcmp(aTopic, + "webNavigation-createdNavigationTarget-from-js")) { + if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject)) { + return OnCreatedNavigationTargetFromJS(props); + } + } + return NS_OK; +} + +void WebNavigationContent::AttachListeners(dom::EventTarget* aEventTarget) { + EventListenerManager* elm = aEventTarget->GetOrCreateListenerManager(); + NS_ENSURE_TRUE_VOID(elm); + + elm->AddEventListenerByType(this, u"DOMContentLoaded"_ns, + TrustedEventsAtCapture()); +} + +NS_IMETHODIMP +WebNavigationContent::HandleEvent(dom::Event* aEvent) { + if (aEvent->ShouldIgnoreChromeEventTargetListener()) { + return NS_OK; + } + +#ifdef DEBUG + { + nsAutoString type; + aEvent->GetType(type); + MOZ_ASSERT(type.EqualsLiteral("DOMContentLoaded")); + } +#endif + + if (RefPtr<dom::Document> doc = do_QueryObject(aEvent->GetTarget())) { + dom::BrowsingContext* bc = doc->GetBrowsingContext(); + if (bc && bc->IsContent()) { + ExtensionsChild::Get().SendDOMContentLoaded(bc, doc->GetDocumentURI()); + } + } + + return NS_OK; +} + +static dom::BrowsingContext* GetBrowsingContext(nsIWebProgress* aWebProgress) { + // FIXME: Get this via nsIWebNavigation instead. + nsCOMPtr<nsIDocShell> docShell(do_GetInterface(aWebProgress)); + return docShell->GetBrowsingContext(); +} + +FrameTransitionData WebNavigationContent::GetFrameTransitionData( + nsIWebProgress* aWebProgress, nsIRequest* aRequest) { + FrameTransitionData result; + + uint32_t loadType = 0; + Unused << aWebProgress->GetLoadType(&loadType); + + if (loadType & nsIDocShell::LOAD_CMD_HISTORY) { + result.forwardBack() = true; + } + + if (loadType & nsIDocShell::LOAD_CMD_RELOAD) { + result.reload() = true; + } + + if (LOAD_TYPE_HAS_FLAGS(loadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH)) { + result.clientRedirect() = true; + } + + if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest)) { + nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo()); + if (loadInfo->RedirectChain().Length()) { + result.serverRedirect() = true; + } + if (loadInfo->GetIsFormSubmission() && + !(loadType & (nsIDocShell::LOAD_CMD_HISTORY | + + nsIDocShell::LOAD_CMD_RELOAD))) { + result.formSubmit() = true; + } + } + + return result; +} + +nsresult WebNavigationContent::OnCreatedNavigationTargetFromJS( + nsIPropertyBag2* aProps) { + nsCOMPtr<nsIDocShell> createdDocShell( + do_GetProperty(aProps, u"createdTabDocShell"_ns)); + nsCOMPtr<nsIDocShell> sourceDocShell( + do_GetProperty(aProps, u"sourceTabDocShell"_ns)); + + NS_ENSURE_ARG_POINTER(createdDocShell); + NS_ENSURE_ARG_POINTER(sourceDocShell); + + dom::BrowsingContext* createdBC = createdDocShell->GetBrowsingContext(); + dom::BrowsingContext* sourceBC = sourceDocShell->GetBrowsingContext(); + if (createdBC->IsContent() && sourceBC->IsContent()) { + nsCString url; + Unused << aProps->GetPropertyAsACString(u"url"_ns, url); + + ExtensionsChild::Get().SendCreatedNavigationTarget(createdBC, sourceBC, + url); + } + return NS_OK; +} + +// nsIWebProgressListener +NS_IMETHODIMP +WebNavigationContent::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aStateFlags, + nsresult aStatus) { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIURI> uri; + MOZ_TRY(channel->GetURI(getter_AddRefs(uri))); + + // Prevents "about", "chrome", "resource" and "moz-extension" URI schemes to + // be reported with the resolved "file" or "jar" URIs (see bug 1246125) + if (uri->SchemeIs("file") || uri->SchemeIs("jar")) { + nsCOMPtr<nsIURI> originalURI; + MOZ_TRY(channel->GetOriginalURI(getter_AddRefs(originalURI))); + // FIXME: We probably actually want NS_GetFinalChannelURI here. + if (originalURI->SchemeIs("about") || originalURI->SchemeIs("chrome") || + originalURI->SchemeIs("resource") || + originalURI->SchemeIs("moz-extension")) { + uri = originalURI.forget(); + } + } + + RefPtr<dom::BrowsingContext> bc(GetBrowsingContext(aWebProgress)); + NS_ENSURE_ARG_POINTER(bc); + + ExtensionsChild::Get().SendStateChange(bc, uri, aStatus, aStateFlags); + + // Based on the docs of the webNavigation.onCommitted event, it should be + // raised when: "The document might still be downloading, but at least part + // of the document has been received" and for some reason we don't fire + // onLocationChange for the initial navigation of a sub-frame. For the above + // two reasons, when the navigation event is related to a sub-frame we process + // the document change here and then send an OnDocumentChange message to the + // main process, where it will be turned into a webNavigation.onCommitted + // event. (bug 1264936 and bug 125662) + if (!bc->IsTop() && aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) { + ExtensionsChild::Get().SendDocumentChange( + bc, GetFrameTransitionData(aWebProgress, aRequest), uri); + } + return NS_OK; +} + +NS_IMETHODIMP +WebNavigationContent::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + MOZ_ASSERT_UNREACHABLE("Listener did not request ProgressChange events"); + return NS_OK; +} + +NS_IMETHODIMP +WebNavigationContent::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, nsIURI* aLocation, + uint32_t aFlags) { + RefPtr<dom::BrowsingContext> bc(GetBrowsingContext(aWebProgress)); + NS_ENSURE_ARG_POINTER(bc); + + // When a frame navigation doesn't change the current loaded document + // (which can be due to history.pushState/replaceState or to a changed hash in + // the url), it is reported only to the onLocationChange, for this reason we + // process the history change here and then we are going to send an + // OnHistoryChange message to the main process, where it will be turned into + // a webNavigation.onHistoryStateUpdated/onReferenceFragmentUpdated event. + if (aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) { + uint32_t loadType = 0; + MOZ_TRY(aWebProgress->GetLoadType(&loadType)); + + // When the location changes but the document is the same: + // - path not changed and hash changed -> |onReferenceFragmentUpdated| + // (even if it changed using |history.pushState|) + // - path not changed and hash not changed -> |onHistoryStateUpdated| + // (only if it changes using |history.pushState|) + // - path changed -> |onHistoryStateUpdated| + bool isHistoryStateUpdated = false; + bool isReferenceFragmentUpdated = false; + if (aFlags & nsIWebProgressListener::LOCATION_CHANGE_HASHCHANGE) { + isReferenceFragmentUpdated = true; + } else if (loadType & nsIDocShell::LOAD_CMD_PUSHSTATE) { + isHistoryStateUpdated = true; + } else if (loadType & nsIDocShell::LOAD_CMD_HISTORY) { + isHistoryStateUpdated = true; + } + + if (isHistoryStateUpdated || isReferenceFragmentUpdated) { + ExtensionsChild::Get().SendHistoryChange( + bc, GetFrameTransitionData(aWebProgress, aRequest), aLocation, + isHistoryStateUpdated, isReferenceFragmentUpdated); + } + } else if (bc->IsTop()) { + MOZ_ASSERT(bc->IsInProcess()); + if (RefPtr browserChild = dom::BrowserChild::GetFrom(bc->GetDocShell())) { + // Only send progress events which happen after we've started loading + // things into the BrowserChild. This matches the behavior of the remote + // WebProgress implementation. + if (browserChild->ShouldSendWebProgressEventsToParent()) { + // We have to catch the document changes from top level frames here, + // where we can detect the "server redirect" transition. + // (bug 1264936 and bug 125662) + ExtensionsChild::Get().SendDocumentChange( + bc, GetFrameTransitionData(aWebProgress, aRequest), aLocation); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +WebNavigationContent::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, nsresult aStatus, + const char16_t* aMessage) { + MOZ_ASSERT_UNREACHABLE("Listener did not request StatusChange events"); + return NS_OK; +} + +NS_IMETHODIMP +WebNavigationContent::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aState) { + MOZ_ASSERT_UNREACHABLE("Listener did not request SecurityChange events"); + return NS_OK; +} + +NS_IMETHODIMP +WebNavigationContent::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + MOZ_ASSERT_UNREACHABLE("Listener did not request ContentBlocking events"); + return NS_OK; +} + +} // namespace extensions +} // namespace mozilla |