325 lines
12 KiB
C++
325 lines
12 KiB
C++
/* -*- 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 "mozilla/Try.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 "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)));
|
|
|
|
// Prevent "about", "chrome", "resource", "moz-src", and "moz-extension" URIs
|
|
// being 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-src") ||
|
|
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
|